From 7d6d93f7aa5554db353385d9a3c2b3710cddb14f Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Tue, 4 Mar 2025 12:57:59 +0100 Subject: [PATCH 01/45] machine: bump rp2040 to 200MHz (#4768) * machine: add support for core voltage adjustments to rp2040 In preparation for bumping the core frequency of the rp2040, this change implements the required core voltage adjustment logic. * machine: bump rp2040 to 200MHz --- src/machine/machine_rp2_2040.go | 15 ++++++++++++++- src/machine/machine_rp2_2350.go | 4 ++++ src/machine/machine_rp2_clocks.go | 12 ++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/machine/machine_rp2_2040.go b/src/machine/machine_rp2_2040.go index e7ae38c069..9cdb3a072e 100644 --- a/src/machine/machine_rp2_2040.go +++ b/src/machine/machine_rp2_2040.go @@ -9,7 +9,7 @@ import ( ) const ( - cpuFreq = 125 * MHz + cpuFreq = 200 * MHz _NUMBANK0_GPIOS = 30 _NUMBANK0_IRQS = 4 _NUMIRQ = 32 @@ -208,3 +208,16 @@ func (clks *clocksType) initTicks() {} // No ticks on RP2040 func (wd *watchdogImpl) startTick(cycles uint32) { rp.WATCHDOG.TICK.Set(cycles | rp.WATCHDOG_TICK_ENABLE) } + +func adjustCoreVoltage() bool { + if cpuFreq <= 133*MHz { + return false + } + // The rp2040 is certified to run at 200MHz with the + // core voltage set to 1150mV. + const targetVoltage = 1150 + // 0b0101 maps to 800mV and each step is 50mV. + const vreg = 0b0101 + (targetVoltage-800)/50 + rp.VREG_AND_CHIP_RESET.SetVREG_VSEL(vreg) + return true +} diff --git a/src/machine/machine_rp2_2350.go b/src/machine/machine_rp2_2350.go index d20bb2d78d..d259e750d8 100644 --- a/src/machine/machine_rp2_2350.go +++ b/src/machine/machine_rp2_2350.go @@ -222,3 +222,7 @@ func EnterBootloader() { func (wd *watchdogImpl) startTick(cycles uint32) { rp.TICKS.WATCHDOG_CTRL.SetBits(1) } + +func adjustCoreVoltage() bool { + return false +} diff --git a/src/machine/machine_rp2_clocks.go b/src/machine/machine_rp2_clocks.go index 061f3dfe44..dafebbe5d3 100644 --- a/src/machine/machine_rp2_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -42,6 +42,10 @@ type clock struct { cix clockIndex } +// The delay in seconds for core voltage adjustments to +// settle. Taken from the Pico SDK. +const _VREG_VOLTAGE_AUTO_ADJUST_DELAY = 1 / 1e3 + // clock returns the clock identified by cix. func (clks *clocksType) clock(cix clockIndex) clock { return clock{ @@ -188,6 +192,14 @@ func (clks *clocksType) init() { xoscFreq, xoscFreq) + if adjustCoreVoltage() { + // Wait for the voltage to settle. + const cycles = _VREG_VOLTAGE_AUTO_ADJUST_DELAY * xoscFreq * MHz + for i := 0; i < cycles; i++ { + arm.Asm("nop") + } + } + // clkSys = pllSys (125MHz) / 1 = 125MHz csys := clks.clock(clkSys) csys.configure(rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX, From e49663809bc0ccf4516029c50de9ac2f56b2aa21 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Mar 2025 10:18:18 +0100 Subject: [PATCH 02/45] machine: fix RP2040 Pico board on the playground Right now it doesn't compile, with errors like the following: # machine /app/tinygo/src/machine/board_pico.go:7:13: undefined: GPIO0 /app/tinygo/src/machine/board_pico.go:8:13: undefined: GPIO1 /app/tinygo/src/machine/board_pico.go:9:13: undefined: GPIO2 /app/tinygo/src/machine/board_pico.go:10:13: undefined: GPIO3 [...etc...] This patch should fix that. --- GNUmakefile | 2 ++ src/machine/machine_rp2_pins.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index f740374f1c..45bca5d8a9 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -585,6 +585,8 @@ ifneq ($(WASM), 0) @$(MD5SUM) test.wasm GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1 @$(MD5SUM) test.wasm + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=pico examples/blinky1 + @$(MD5SUM) test.wasm endif # test all targets/boards $(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1 diff --git a/src/machine/machine_rp2_pins.go b/src/machine/machine_rp2_pins.go index 43b31f938d..36e9bd629c 100644 --- a/src/machine/machine_rp2_pins.go +++ b/src/machine/machine_rp2_pins.go @@ -1,4 +1,4 @@ -//go:build rp2040 || rp2350 || gopher_badge +//go:build rp2040 || rp2350 || gopher_badge || pico package machine From 6c9074772ce135c5c2bd2124be198fef913ba201 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Mar 2025 10:37:40 +0100 Subject: [PATCH 03/45] compiler: crypto/internal/sysrand is allowed to use unsafe signatures Apparently this package imports `runtime.getRandomData` from the gojs module. This is not yet implemented, but simply allowing this package to do such imports gets crypto/sha256 to compile. --- compiler/symbol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/symbol.go b/compiler/symbol.go index 1de3c6f39d..1226683d57 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -446,7 +446,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { // The list of allowed types is based on this proposal: // https://github.com/golang/go/issues/59149 func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) { - if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" { + if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" || c.pkg.Path() == "crypto/internal/sysrand" { // The runtime is a special case. Allow all kinds of parameters // (importantly, including pointers). return From 5a09084c732a4b07cd961508a5564e31ed9691f3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Mar 2025 10:43:28 +0100 Subject: [PATCH 04/45] os: add stub Symlink for wasm This doesn't do anything, but it makes tests work. --- src/os/file_other.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/os/file_other.go b/src/os/file_other.go index 8e7d33e00e..1fdf4b1ef4 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -134,6 +134,10 @@ func Pipe() (r *File, w *File, err error) { return nil, nil, ErrNotImplemented } +func Symlink(oldname, newname string) error { + return ErrNotImplemented +} + func Readlink(name string) (string, error) { return "", ErrNotImplemented } From dc876c6ed45b4c52dedca6b0c5a4bc3448f1bd64 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Mar 2025 10:43:55 +0100 Subject: [PATCH 05/45] ci: add single test for wasm It looks like we didn't have any tests for wasm. Having one tests is not much, but it proves that the infrastructure works and it actually verifies a fix to https://github.com/tinygo-org/tinygo/issues/4777. We should add more packages to this list in the future. --- .github/workflows/linux.yml | 1 + GNUmakefile | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index cbb0a7c554..a0a9086350 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -165,6 +165,7 @@ jobs: ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo - run: make tinygo-test-wasip1-fast - run: make tinygo-test-wasip2-fast + - run: make tinygo-test-wasm - run: make smoketest assert-test-linux: # Run all tests that can run on Linux, with LLVM assertions enabled to catch diff --git a/GNUmakefile b/GNUmakefile index 45bca5d8a9..13481aab74 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -407,6 +407,9 @@ TEST_PACKAGES_WINDOWS := \ text/template/parse \ $(nil) +TEST_PACKAGES_WASM := \ + crypto/sha256 + # Report platforms on which each standard library package is known to pass tests jointmp := $(shell echo /tmp/join.$$$$) report-stdlib-tests-pass: @@ -450,6 +453,8 @@ tinygo-bench-fast: $(TINYGO) test -bench . $(TEST_PACKAGES_HOST) # Same thing, except for wasi rather than the current platform. +tinygo-test-wasm: + $(TINYGO) test -target wasm $(TEST_PACKAGES_WASM) tinygo-test-wasi: $(TINYGO) test -target wasip1 $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi tinygo-test-wasip1: From 226744a53897a143a51cc123afe6a45207cef10c Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 9 Mar 2025 14:26:38 +0100 Subject: [PATCH 06/45] machine: correct register address for Pin.SetInterrupt for rp2350 (#4782) Fixes #4689 --- src/machine/machine_rp2_gpio.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/machine/machine_rp2_gpio.go b/src/machine/machine_rp2_gpio.go index 6854f8aad4..25d76261fe 100644 --- a/src/machine/machine_rp2_gpio.go +++ b/src/machine/machine_rp2_gpio.go @@ -33,6 +33,7 @@ type irqSummary struct { type ioBank0Type struct { io [_NUMBANK0_GPIOS]ioType + _ [rp2350ExtraReg][128]byte irqsum [rp2350ExtraReg]irqSummary intR [_NUMBANK0_IRQS]volatile.Register32 proc0IRQctrl irqCtrl From ebf70ab18ebe9e6bbcd2ce76c2cc2c16d86ba633 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Mar 2025 11:40:37 +0100 Subject: [PATCH 07/45] ci: add more tests for wasm and baremetal Run a range of tests in CI, to make sure browser wasm and baremetal don't regress too badly. I have intentionally filtered out tests, so that newly added tests to TEST_PACKAGES_FAST will be added here as well (and can be excluded if needed). --- .github/workflows/linux.yml | 2 +- GNUmakefile | 50 +++++++++++++++++++++++++++++++------ targets/riscv-qemu.ld | 10 ++++---- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a0a9086350..0a8fabf1f2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -278,7 +278,7 @@ jobs: run: make tinygo-test - run: make smoketest - run: make wasmtest - - run: make tinygo-baremetal + - run: make tinygo-test-baremetal build-linux-cross: # Build ARM Linux binaries, ready for release. # This intentionally uses an older Linux image, so that we compile against diff --git a/GNUmakefile b/GNUmakefile index 13481aab74..6e626a488d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -407,8 +407,45 @@ TEST_PACKAGES_WINDOWS := \ text/template/parse \ $(nil) -TEST_PACKAGES_WASM := \ - crypto/sha256 + +# These packages cannot be tested on wasm, mostly because these tests assume a +# working filesystem. This could perhaps be fixed, by supporting filesystem +# access when running inside Node.js. +TEST_PACKAGES_WASM = $(filter-out $(TEST_PACKAGES_NONWASM), $(TEST_PACKAGES_FAST)) +TEST_PACKAGES_NONWASM = \ + compress/lzw \ + compress/zlib \ + crypto/ecdsa \ + debug/macho \ + embed/internal/embedtest \ + go/format \ + os \ + testing \ + $(nil) + +# These packages cannot be tested on baremetal. +# +# Some reasons why the tests don't pass on baremetal: +# +# * No filesystem is available, so packages like compress/zlib can't be tested +# (just like wasm). +# * There is no RNG implemented (TODO, I think this is fixable). +# * picolibc math functions apparently are less precise, the math package +# fails on baremetal. +# * Some packages fail or hang for an unknown reason, this should be +# investigated and fixed. +TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST)) +TEST_PACKAGES_NONBAREMETAL = \ + $(TEST_PACKAGES_NONWASM) \ + crypto/elliptic \ + crypto/md5 \ + crypto/sha1 \ + math \ + reflect \ + encoding/asn1 \ + encoding/base32 \ + go/ast \ + $(nil) # Report platforms on which each standard library package is known to pass tests jointmp := $(shell echo /tmp/join.$$$$) @@ -489,6 +526,10 @@ tinygo-bench-wasip2: tinygo-bench-wasip2-fast: $(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST) +# Run tests on riscv-qemu since that one provides a large amount of memory. +tinygo-test-baremetal: + $(TINYGO) test -target riscv-qemu $(TEST_PACKAGES_BAREMETAL) + # Test external packages in a large corpus. test-corpus: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml @@ -499,11 +540,6 @@ test-corpus-wasi: wasi-libc test-corpus-wasip2: wasi-libc CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip2 -tinygo-baremetal: - # Regression tests that run on a baremetal target and don't fit in either main_test.go or smoketest. - # regression test for #2666: e.g. encoding/hex must pass on baremetal - $(TINYGO) test -target cortex-m-qemu encoding/hex - .PHONY: testchdir testchdir: # test 'build' command with{,out} -C argument diff --git a/targets/riscv-qemu.ld b/targets/riscv-qemu.ld index ab344571e2..822c00d10b 100644 --- a/targets/riscv-qemu.ld +++ b/targets/riscv-qemu.ld @@ -1,16 +1,16 @@ /* Memory map: * https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c - * RAM and flash are set to 1MB each. That should be enough for the foreseeable - * future. QEMU does not seem to limit the flash/RAM size and in fact doesn't - * seem to differentiate between it. + * Looks like we can use any address starting from 0x80000000 (so 2GB of space). + * However, using a large space slows down tests. */ MEMORY { - FLASH_TEXT (rw) : ORIGIN = 0x80000000, LENGTH = 0x100000 - RAM (xrw) : ORIGIN = 0x80100000, LENGTH = 0x100000 + RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 100M } +REGION_ALIAS("FLASH_TEXT", RAM) + _stack_size = 2K; INCLUDE "targets/riscv.ld" From 7f970a45c285863bccb53acbd266496bee63de5e Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 2 Mar 2025 14:27:18 +0100 Subject: [PATCH 08/45] machine: don't block the rp2xxx UART interrupt handler Don't block forever if there's nothing to receive. On the other hand, process the entire FIFO, not just a single byte. This fixes an issue where the rp2350 would hang after programming through openocd, where the UART0 interrupt would be spuriously pending. --- src/machine/machine_rp2_uart.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/machine/machine_rp2_uart.go b/src/machine/machine_rp2_uart.go index 77aa26e54c..03ebb9d874 100644 --- a/src/machine/machine_rp2_uart.go +++ b/src/machine/machine_rp2_uart.go @@ -67,7 +67,7 @@ func (uart *UART) Configure(config UARTConfig) error { uart.Interrupt.SetPriority(0x80) uart.Interrupt.Enable() - // setup interrupt on receive + // Setup interrupt on receive. uart.Bus.UARTIMSC.Set(rp.UART0_UARTIMSC_RXIM) return nil @@ -153,7 +153,7 @@ func initUART(uart *UART) { // handleInterrupt should be called from the appropriate interrupt handler for // this UART instance. func (uart *UART) handleInterrupt(interrupt.Interrupt) { - for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_RXFE) { + for !uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_RXFE) { + uart.Receive(byte((uart.Bus.UARTDR.Get() & 0xFF))) } - uart.Receive(byte((uart.Bus.UARTDR.Get() & 0xFF))) } From cab3834bc9609e2ccfe57045983c4bdcd7eddb5d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 10 Mar 2025 11:34:29 +0100 Subject: [PATCH 09/45] riscv-qemu: add VirtIO RNG device This implements machine.GetRNG() using VirtIO. This gets the tests to pass for crypto/md5 and crypto/sha1 that use crypto/rand in their tests. --- GNUmakefile | 3 - src/crypto/rand/rand_baremetal.go | 2 +- src/machine/virt.go | 189 ++++++++++++++++++++++++++++++ src/runtime/rand_hwrng.go | 2 +- src/runtime/rand_norng.go | 2 +- targets/riscv-qemu.json | 2 +- 6 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 src/machine/virt.go diff --git a/GNUmakefile b/GNUmakefile index 6e626a488d..978d7d9817 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -429,7 +429,6 @@ TEST_PACKAGES_NONWASM = \ # # * No filesystem is available, so packages like compress/zlib can't be tested # (just like wasm). -# * There is no RNG implemented (TODO, I think this is fixable). # * picolibc math functions apparently are less precise, the math package # fails on baremetal. # * Some packages fail or hang for an unknown reason, this should be @@ -438,8 +437,6 @@ TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PAC TEST_PACKAGES_NONBAREMETAL = \ $(TEST_PACKAGES_NONWASM) \ crypto/elliptic \ - crypto/md5 \ - crypto/sha1 \ math \ reflect \ encoding/asn1 \ diff --git a/src/crypto/rand/rand_baremetal.go b/src/crypto/rand/rand_baremetal.go index 30b2c3b230..5711f23eb0 100644 --- a/src/crypto/rand/rand_baremetal.go +++ b/src/crypto/rand/rand_baremetal.go @@ -1,4 +1,4 @@ -//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey +//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) // If you update the above build constraint, you'll probably also need to update // src/runtime/rand_hwrng.go. diff --git a/src/machine/virt.go b/src/machine/virt.go new file mode 100644 index 0000000000..2b28ae61e6 --- /dev/null +++ b/src/machine/virt.go @@ -0,0 +1,189 @@ +//go:build tinygo.riscv32 && virt + +// Machine implementation for VirtIO targets. +// At the moment only QEMU RISC-V is supported, but support for ARM for example +// should not be difficult to add with a change to virtioFindDevice. + +package machine + +import ( + "errors" + "runtime/volatile" + "sync" + "unsafe" +) + +const deviceName = "riscv-qemu" + +func (p Pin) Set(high bool) { + // no pins defined +} + +var rngLock sync.Mutex +var rngDevice *virtioDevice1 +var rngBuf volatile.Register32 + +var errNoRNG = errors.New("machine: no entropy source found") +var errNoRNGData = errors.New("machine: entropy source didn't return enough data") + +// GetRNG returns random numbers from a VirtIO entropy source. +// When running in QEMU, it requires adding the RNG device: +// +// -device virtio-rng-device +func GetRNG() (uint32, error) { + rngLock.Lock() + + // Initialize the device on first use. + if rngDevice == nil { + // Search for an available RNG. + rngDevice = virtioFindDevice(virtioDeviceEntropySource) + if rngDevice == nil { + rngLock.Unlock() + return 0, errNoRNG + } + + // Initialize the device. + rngDevice.status.Set(0) // reset device + rngDevice.status.Set(virtioDeviceStatusAcknowledge) + rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver) + rngDevice.hostFeaturesSel.Set(0) + rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver | virtioDeviceStatusDriverOk) + rngDevice.guestPageSize.Set(4096) + + // Configure queue, according to section 4.2.4 "Legacy interface". + // Note: we're skipping checks for queuePFM and queueNumMax. + rngDevice.queueSel.Set(0) // use queue 0 (the only queue) + rngDevice.queueNum.Set(1) // use a single buffer in the queue + rngDevice.queueAlign.Set(4096) // default alignment appears to be 4096 + rngDevice.queuePFN.Set(uint32(uintptr(unsafe.Pointer(&rngQueue))) / 4096) + + // Configure the only buffer in the queue (but don't increment + // rngQueue.available yet). + rngQueue.buffers[0].address = uint64(uintptr(unsafe.Pointer(&rngBuf))) + rngQueue.buffers[0].length = uint32(unsafe.Sizeof(rngBuf)) + rngQueue.buffers[0].flags = 2 // 2 means write-only buffer + } + + // Increment the available ring buffer. This doesn't actually change the + // buffer index (it's a ring with a single entry), but the number needs to + // be incremented otherwise the device won't recognize a new buffer. + index := rngQueue.available.index + rngQueue.available.index = index + 1 + rngDevice.queueNotify.Set(0) // notify the device of the 'new' (reused) buffer + for rngQueue.used.index.Get() != index+1 { + // Busy wait until the RNG buffer is filled. + // A better way would be to wait for an interrupt, but since this driver + // implementation is mostly used for testing it's good enough for now. + } + + // Check that we indeed got 4 bytes back. + if rngQueue.used.ring[0].length != 4 { + rngLock.Unlock() + return 0, errNoRNGData + } + + // Read the resulting random numbers. + result := rngBuf.Get() + + rngLock.Unlock() + + return result, nil +} + +// Implement a driver for the VirtIO entropy device. +// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html +// http://wiki.osdev.org/Virtio +// http://www.dumais.io/index.php?article=aca38a9a2b065b24dfa1dee728062a12 + +const ( + virtioDeviceStatusAcknowledge = 1 + virtioDeviceStatusDriver = 2 + virtioDeviceStatusDriverOk = 4 + virtioDeviceStatusFeaturesOk = 8 + virtioDeviceStatusFailed = 128 +) + +const ( + virtioDeviceReserved = iota + virtioDeviceNetworkCard + virtioDeviceBlockDevice + virtioDeviceConsole + virtioDeviceEntropySource + // there are more device types +) + +// VirtIO device version 1 +type virtioDevice1 struct { + magic volatile.Register32 // always 0x74726976 + version volatile.Register32 + deviceID volatile.Register32 + vendorID volatile.Register32 + hostFeatures volatile.Register32 + hostFeaturesSel volatile.Register32 + _ [2]uint32 + guestFeatures volatile.Register32 + guestFeaturesSel volatile.Register32 + guestPageSize volatile.Register32 + _ uint32 + queueSel volatile.Register32 + queueNumMax volatile.Register32 + queueNum volatile.Register32 + queueAlign volatile.Register32 + queuePFN volatile.Register32 + _ [3]uint32 + queueNotify volatile.Register32 + _ [3]uint32 + interruptStatus volatile.Register32 + interruptAck volatile.Register32 + _ [2]uint32 + status volatile.Register32 +} + +// VirtIO queue, with a single buffer. +type virtioQueue struct { + buffers [1]struct { + address uint64 + length uint32 + flags uint16 + next uint16 + } // 16 bytes + + available struct { + flags uint16 + index uint16 + ring [1]uint16 + eventIndex uint16 + } // 8 bytes + + _ [4096 - 16*1 - 8*1]byte // padding (to align on a 4096 byte boundary) + + used struct { + flags uint16 + index volatile.Register16 + ring [1]struct { + index uint32 + length uint32 + } + availEvent uint16 + } +} + +func virtioFindDevice(deviceID uint32) *virtioDevice1 { + // On RISC-V, QEMU defines 8 VirtIO devices starting at 0x10001000 and + // repeating every 0x1000 bytes. + // The memory map can be seen in the QEMU source code: + // https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c + for i := 0; i < 8; i++ { + dev := (*virtioDevice1)(unsafe.Pointer(uintptr(0x10001000 + i*0x1000))) + if dev.magic.Get() != 0x74726976 || dev.version.Get() != 1 || dev.deviceID.Get() != deviceID { + continue + } + return dev + } + return nil +} + +// A VirtIO queue needs to be page-aligned. +// +//go:align 4096 +var rngQueue virtioQueue diff --git a/src/runtime/rand_hwrng.go b/src/runtime/rand_hwrng.go index 2c690b4490..9f1a152d7f 100644 --- a/src/runtime/rand_hwrng.go +++ b/src/runtime/rand_hwrng.go @@ -1,4 +1,4 @@ -//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey) +//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)) // If you update the above build constraint, you'll probably also need to update // src/crypto/rand/rand_baremetal.go. diff --git a/src/runtime/rand_norng.go b/src/runtime/rand_norng.go index a86cdc5429..aa79c500dc 100644 --- a/src/runtime/rand_norng.go +++ b/src/runtime/rand_norng.go @@ -1,4 +1,4 @@ -//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey) +//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)) package runtime diff --git a/targets/riscv-qemu.json b/targets/riscv-qemu.json index cb26829686..84890308ba 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -4,5 +4,5 @@ "build-tags": ["virt", "qemu"], "default-stack-size": 4096, "linkerscript": "targets/riscv-qemu.ld", - "emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -kernel {}" + "emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -device virtio-rng-device -kernel {}" } From 2bee29959b9f35ad0cf3ff28ddcc08968eecee34 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 25 Feb 2025 11:09:14 +0100 Subject: [PATCH 10/45] refactor: use *SPI everywhere to make consistant for implementations. Fixes #4663 "in reverse" by making SPI a pointer everywhere, as discussed in the comments. Signed-off-by: deadprogram --- src/machine/board_feather-stm32f405.go | 6 ++-- src/machine/board_lgt92.go | 4 +-- src/machine/board_maixbit_baremetal.go | 4 +-- src/machine/board_mksnanov3.go | 4 +-- src/machine/board_nucleol031k6.go | 4 +-- src/machine/board_stm32f469disco.go | 4 +-- src/machine/board_stm32f4disco.go | 4 +-- src/machine/machine_atmega.go | 4 +-- src/machine/machine_atmega1280.go | 2 +- src/machine/machine_atmega1284p.go | 2 +- src/machine/machine_atmega2560.go | 2 +- src/machine/machine_atmega328p.go | 2 +- src/machine/machine_atmega328pb.go | 4 +-- src/machine/machine_atsamd21.go | 12 +++---- src/machine/machine_atsamd51.go | 12 +++---- src/machine/machine_esp32.go | 6 ++-- src/machine/machine_esp32c3_spi.go | 6 ++-- src/machine/machine_fe310.go | 4 +-- src/machine/machine_generic.go | 6 ++-- src/machine/machine_generic_peripherals.go | 4 +-- src/machine/machine_k210.go | 4 +-- src/machine/machine_nrf51.go | 10 +++--- src/machine/machine_nrf52xxx.go | 12 +++---- src/machine/machine_rp2_spi.go | 40 ++++++++++------------ src/machine/machine_stm32_spi.go | 4 +-- src/machine/machine_stm32f103.go | 8 ++--- src/machine/machine_stm32f4.go | 6 ++-- src/machine/machine_stm32l0.go | 6 ++-- src/machine/machine_stm32l4.go | 6 ++-- src/machine/machine_stm32wlx.go | 6 ++-- src/machine/spi_tx.go | 2 +- 31 files changed, 99 insertions(+), 101 deletions(-) diff --git a/src/machine/board_feather-stm32f405.go b/src/machine/board_feather-stm32f405.go index 5b980867f5..4a184bad51 100644 --- a/src/machine/board_feather-stm32f405.go +++ b/src/machine/board_feather-stm32f405.go @@ -186,15 +186,15 @@ const ( ) var ( - SPI1 = SPI{ + SPI1 = &SPI{ Bus: stm32.SPI2, AltFuncSelector: AF5_SPI1_SPI2, } - SPI2 = SPI{ + SPI2 = &SPI{ Bus: stm32.SPI3, AltFuncSelector: AF6_SPI3, } - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } diff --git a/src/machine/board_lgt92.go b/src/machine/board_lgt92.go index ff4d03cd82..71b05083cc 100644 --- a/src/machine/board_lgt92.go +++ b/src/machine/board_lgt92.go @@ -84,10 +84,10 @@ var ( I2C0 = I2C1 // SPI - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, } - SPI1 = &SPI0 + SPI1 = SPI0 ) func init() { diff --git a/src/machine/board_maixbit_baremetal.go b/src/machine/board_maixbit_baremetal.go index 7318cfa9e3..f5a7e8d4cb 100644 --- a/src/machine/board_maixbit_baremetal.go +++ b/src/machine/board_maixbit_baremetal.go @@ -6,10 +6,10 @@ import "device/kendryte" // SPI on the MAix Bit. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: kendryte.SPI0, } - SPI1 = SPI{ + SPI1 = &SPI{ Bus: kendryte.SPI1, } ) diff --git a/src/machine/board_mksnanov3.go b/src/machine/board_mksnanov3.go index d096cdb9d1..35fef682d4 100644 --- a/src/machine/board_mksnanov3.go +++ b/src/machine/board_mksnanov3.go @@ -89,11 +89,11 @@ const ( // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } - SPI1 = &SPI0 + SPI1 = SPI0 ) const ( diff --git a/src/machine/board_nucleol031k6.go b/src/machine/board_nucleol031k6.go index 1a57289b9d..aea92d8be0 100644 --- a/src/machine/board_nucleol031k6.go +++ b/src/machine/board_nucleol031k6.go @@ -86,11 +86,11 @@ var ( I2C0 = I2C1 // SPI - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: 0, } - SPI1 = &SPI0 + SPI1 = SPI0 ) func init() { diff --git a/src/machine/board_stm32f469disco.go b/src/machine/board_stm32f469disco.go index a4eef1420a..8fb5cde2ea 100644 --- a/src/machine/board_stm32f469disco.go +++ b/src/machine/board_stm32f469disco.go @@ -59,11 +59,11 @@ const ( // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } - SPI1 = &SPI0 + SPI1 = SPI0 ) const ( diff --git a/src/machine/board_stm32f4disco.go b/src/machine/board_stm32f4disco.go index 291109c5de..d048fcacf4 100644 --- a/src/machine/board_stm32f4disco.go +++ b/src/machine/board_stm32f4disco.go @@ -86,11 +86,11 @@ const ( // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } - SPI1 = &SPI0 + SPI1 = SPI0 ) const ( diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index 46dee5ef1e..7a59e5e092 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -257,7 +257,7 @@ type SPI struct { } // Configure is intended to setup the SPI interface. -func (s SPI) Configure(config SPIConfig) error { +func (s *SPI) Configure(config SPIConfig) error { // This is only here to help catch a bug with the configuration // where a machine missed a value. @@ -330,7 +330,7 @@ func (s SPI) Configure(config SPIConfig) error { } // Transfer writes the byte into the register and returns the read content -func (s SPI) Transfer(b byte) (byte, error) { +func (s *SPI) Transfer(b byte) (byte, error) { s.spdr.Set(uint8(b)) for !s.spsr.HasBits(s.spsrSPIF) { diff --git a/src/machine/machine_atmega1280.go b/src/machine/machine_atmega1280.go index 1b8cb848de..ad33dcf8c0 100644 --- a/src/machine/machine_atmega1280.go +++ b/src/machine/machine_atmega1280.go @@ -927,7 +927,7 @@ func (pwm PWM) Set(channel uint8, value uint32) { } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, diff --git a/src/machine/machine_atmega1284p.go b/src/machine/machine_atmega1284p.go index 511f9e8730..db8fd65a2f 100644 --- a/src/machine/machine_atmega1284p.go +++ b/src/machine/machine_atmega1284p.go @@ -71,7 +71,7 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spsr: avr.SPSR, spdr: avr.SPDR, diff --git a/src/machine/machine_atmega2560.go b/src/machine/machine_atmega2560.go index 339c35ae77..ede862a931 100644 --- a/src/machine/machine_atmega2560.go +++ b/src/machine/machine_atmega2560.go @@ -131,7 +131,7 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index 1995403aab..5bacfb8f20 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -25,7 +25,7 @@ var I2C0 = &I2C{ } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, diff --git a/src/machine/machine_atmega328pb.go b/src/machine/machine_atmega328pb.go index 091fb6337f..935c581d54 100644 --- a/src/machine/machine_atmega328pb.go +++ b/src/machine/machine_atmega328pb.go @@ -60,7 +60,7 @@ var I2C1 = &I2C{ } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR0, spdr: avr.SPDR0, spsr: avr.SPSR0, @@ -82,7 +82,7 @@ var SPI0 = SPI{ cs: PB2, } -var SPI1 = SPI{ +var SPI1 = &SPI{ spcr: avr.SPCR1, spdr: avr.SPDR1, spsr: avr.SPSR1, diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 0a5c320ca0..e0c5f2cc29 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -1224,7 +1224,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -1346,7 +1346,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // write data spi.Bus.DATA.Set(uint32(w)) @@ -1375,7 +1375,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { switch { case w == nil: // read only, so write zero and read a result. @@ -1396,7 +1396,7 @@ func (spi SPI) Tx(w, r []byte) error { return nil } -func (spi SPI) tx(tx []byte) { +func (spi *SPI) tx(tx []byte) { for i := 0; i < len(tx); i++ { for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } @@ -1411,7 +1411,7 @@ func (spi SPI) tx(tx []byte) { } } -func (spi SPI) rx(rx []byte) { +func (spi *SPI) rx(rx []byte) { spi.Bus.DATA.Set(0) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } @@ -1427,7 +1427,7 @@ func (spi SPI) rx(rx []byte) { rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } -func (spi SPI) txrx(tx, rx []byte) { +func (spi *SPI) txrx(tx, rx []byte) { spi.Bus.DATA.Set(uint32(tx[0])) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 5cd1d314a2..d169eab995 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -1431,7 +1431,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -1574,7 +1574,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // write data spi.Bus.DATA.Set(uint32(w)) @@ -1603,7 +1603,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { switch { case w == nil: // read only, so write zero and read a result. @@ -1624,7 +1624,7 @@ func (spi SPI) Tx(w, r []byte) error { return nil } -func (spi SPI) tx(tx []byte) { +func (spi *SPI) tx(tx []byte) { for i := 0; i < len(tx); i++ { for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } @@ -1639,7 +1639,7 @@ func (spi SPI) tx(tx []byte) { } } -func (spi SPI) rx(rx []byte) { +func (spi *SPI) rx(rx []byte) { spi.Bus.DATA.Set(0) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } @@ -1655,7 +1655,7 @@ func (spi SPI) rx(rx []byte) { rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } -func (spi SPI) txrx(tx, rx []byte) { +func (spi *SPI) txrx(tx, rx []byte) { spi.Bus.DATA.Set(uint32(tx[0])) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go index d6f6599288..50ff74d672 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -354,7 +354,7 @@ type SPIConfig struct { } // Configure and make the SPI peripheral ready to use. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { if config.Frequency == 0 { config.Frequency = 4e6 // default to 4MHz } @@ -445,7 +445,7 @@ func (spi SPI) Configure(config SPIConfig) error { // Transfer writes/reads a single byte using the SPI interface. If you need to // transfer larger amounts of data, Tx will be faster. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.MISO_DLEN.Set(7 << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos) spi.Bus.MOSI_DLEN.Set(7 << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos) @@ -464,7 +464,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // interface, there must always be the same number of bytes written as bytes read. // This is accomplished by sending zero bits if r is bigger than w or discarding // the incoming data if w is bigger than r. -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { toTransfer := len(w) if len(r) > toTransfer { toTransfer = len(r) diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go index 2fb8abc200..ecb1923dee 100644 --- a/src/machine/machine_esp32c3_spi.go +++ b/src/machine/machine_esp32c3_spi.go @@ -114,7 +114,7 @@ func freqToClockDiv(hz uint32) uint32 { } // Configure and make the SPI peripheral ready to use. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // right now this is only setup to work for the esp32c3 spi2 bus if spi.Bus != esp.SPI2 { return ErrInvalidSPIBus @@ -216,7 +216,7 @@ func (spi SPI) Configure(config SPIConfig) error { // Transfer writes/reads a single byte using the SPI interface. If you need to // transfer larger amounts of data, Tx will be faster. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(7) spi.Bus.SetW0(uint32(w)) @@ -238,7 +238,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // interface, there must always be the same number of bytes written as bytes read. // This is accomplished by sending zero bits if r is bigger than w or discarding // the incoming data if w is bigger than r. -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { toTransfer := len(w) if len(r) > toTransfer { toTransfer = len(r) diff --git a/src/machine/machine_fe310.go b/src/machine/machine_fe310.go index 4a15ad76e1..2f716d6168 100644 --- a/src/machine/machine_fe310.go +++ b/src/machine/machine_fe310.go @@ -140,7 +140,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -197,7 +197,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // wait for tx ready for spi.Bus.TXDATA.HasBits(sifive.QSPI_TXDATA_FULL) { } diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index 4f040fdbbd..1c8512244b 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -60,13 +60,13 @@ type SPIConfig struct { Mode uint8 } -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { spiConfigure(spi.Bus, config.SCK, config.SDO, config.SDI) return nil } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { return spiTransfer(spi.Bus, w), nil } @@ -87,7 +87,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { var wptr, rptr *byte var wlen, rlen int if len(w) != 0 { diff --git a/src/machine/machine_generic_peripherals.go b/src/machine/machine_generic_peripherals.go index 28539f0e7f..6c95c206cd 100644 --- a/src/machine/machine_generic_peripherals.go +++ b/src/machine/machine_generic_peripherals.go @@ -8,7 +8,7 @@ package machine var ( UART0 = hardwareUART0 UART1 = hardwareUART1 - SPI0 = SPI{0} - SPI1 = SPI{1} + SPI0 = &SPI{0} + SPI1 = &SPI{1} I2C0 = &I2C{0} ) diff --git a/src/machine/machine_k210.go b/src/machine/machine_k210.go index 28d5098e75..d83576a617 100644 --- a/src/machine/machine_k210.go +++ b/src/machine/machine_k210.go @@ -419,7 +419,7 @@ type SPIConfig struct { // Configure is intended to setup the SPI interface. // Only SPI controller 0 and 1 can be used because SPI2 is a special // peripheral-mode controller and SPI3 is used for flashing. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -476,7 +476,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.SSIENR.Set(0) // Set transfer-receive mode. diff --git a/src/machine/machine_nrf51.go b/src/machine/machine_nrf51.go index 8d27e6fc55..d627d63c21 100644 --- a/src/machine/machine_nrf51.go +++ b/src/machine/machine_nrf51.go @@ -34,8 +34,8 @@ type SPI struct { // There are 2 SPI interfaces on the NRF51. var ( - SPI0 = SPI{Bus: nrf.SPI0} - SPI1 = SPI{Bus: nrf.SPI1} + SPI0 = &SPI{Bus: nrf.SPI0} + SPI1 = &SPI{Bus: nrf.SPI1} ) // SPIConfig is used to store config info for SPI. @@ -49,7 +49,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Disable bus to configure it spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Disabled) @@ -122,7 +122,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.TXD.Set(uint32(w)) for spi.Bus.EVENTS_READY.Get() == 0 { } @@ -150,7 +150,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { var err error switch { diff --git a/src/machine/machine_nrf52xxx.go b/src/machine/machine_nrf52xxx.go index 927458f543..a582a7aa56 100644 --- a/src/machine/machine_nrf52xxx.go +++ b/src/machine/machine_nrf52xxx.go @@ -199,9 +199,9 @@ type SPI struct { // There are 3 SPI interfaces on the NRF528xx. var ( - SPI0 = SPI{Bus: nrf.SPIM0, buf: new([1]byte)} - SPI1 = SPI{Bus: nrf.SPIM1, buf: new([1]byte)} - SPI2 = SPI{Bus: nrf.SPIM2, buf: new([1]byte)} + SPI0 = &SPI{Bus: nrf.SPIM0, buf: new([1]byte)} + SPI1 = &SPI{Bus: nrf.SPIM1, buf: new([1]byte)} + SPI2 = &SPI{Bus: nrf.SPIM2, buf: new([1]byte)} ) // SPIConfig is used to store config info for SPI. @@ -215,7 +215,7 @@ type SPIConfig struct { } // Configure is intended to set up the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Disable bus to configure it spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled) @@ -288,7 +288,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { buf := spi.buf[:] buf[0] = w err := spi.Tx(buf[:], buf[:]) @@ -300,7 +300,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // as bytes read. Therefore, if the number of bytes don't match it will be // padded until they fit: if len(w) > len(r) the extra bytes received will be // dropped and if len(w) < len(r) extra 0 bytes will be sent. -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { // Unfortunately the hardware (on the nrf52832) only supports up to 255 // bytes in the buffers, so if either w or r is longer than that the // transfer needs to be broken up in pieces. diff --git a/src/machine/machine_rp2_spi.go b/src/machine/machine_rp2_spi.go index a589cb648c..88301c98b2 100644 --- a/src/machine/machine_rp2_spi.go +++ b/src/machine/machine_rp2_spi.go @@ -10,12 +10,10 @@ import ( // SPI on the RP2040 var ( - SPI0 = &_SPI0 - _SPI0 = SPI{ + SPI0 = &SPI{ Bus: rp.SPI0, } - SPI1 = &_SPI1 - _SPI1 = SPI{ + SPI1 = &SPI{ Bus: rp.SPI1, } ) @@ -73,7 +71,7 @@ type SPI struct { // // This form sends 0xff and puts the result into rx buffer. Useful for reading from SD cards // which require 0xff input on SI. -func (spi SPI) Tx(w, r []byte) (err error) { +func (spi *SPI) Tx(w, r []byte) (err error) { switch { case w == nil: // read only, so write zero and read a result. @@ -92,7 +90,7 @@ func (spi SPI) Tx(w, r []byte) (err error) { } // Write a single byte and read a single byte from TX/RX FIFO. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { for !spi.isWritable() { } @@ -103,7 +101,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return uint8(spi.Bus.SSPDR.Get()), nil } -func (spi SPI) SetBaudRate(br uint32) error { +func (spi *SPI) SetBaudRate(br uint32) error { const maxBaud uint32 = 66.5 * MHz // max output frequency is 66.5MHz on rp2040. see Note page 527. // Find smallest prescale value which puts output frequency in range of // post-divide. Prescale is an even number from 2 to 254 inclusive. @@ -129,8 +127,8 @@ func (spi SPI) SetBaudRate(br uint32) error { return nil } -func (spi SPI) GetBaudRate() uint32 { - const freqin uint32 = 125 * MHz +func (spi *SPI) GetBaudRate() uint32 { + freqin := CPUFrequency() prescale := spi.Bus.SSPCPSR.Get() postdiv := ((spi.Bus.SSPCR0.Get() & rp.SPI0_SSPCR0_SCR_Msk) >> rp.SPI0_SSPCR0_SCR_Pos) + 1 return freqin / (prescale * postdiv) @@ -152,7 +150,7 @@ func (spi SPI) GetBaudRate() uint32 { // SCK: 10, 14 // // No pin configuration is needed of SCK, SDO and SDI needed after calling Configure. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { const defaultBaud uint32 = 4 * MHz if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { // set default pins if config zero valued or invalid clock pin supplied. @@ -199,7 +197,7 @@ func (spi SPI) Configure(config SPIConfig) error { return spi.initSPI(config) } -func (spi SPI) initSPI(config SPIConfig) (err error) { +func (spi *SPI) initSPI(config SPIConfig) (err error) { spi.reset() // LSB-first not supported on PL022: if config.LSBFirst { @@ -217,7 +215,7 @@ func (spi SPI) initSPI(config SPIConfig) (err error) { } //go:inline -func (spi SPI) setFormat(mode uint8) { +func (spi *SPI) setFormat(mode uint8) { cpha := uint32(mode) & 1 cpol := uint32(mode>>1) & 1 spi.Bus.SSPCR0.ReplaceBits( @@ -230,7 +228,7 @@ func (spi SPI) setFormat(mode uint8) { // reset resets SPI and waits until reset is done. // //go:inline -func (spi SPI) reset() { +func (spi *SPI) reset() { resetVal := spi.deinit() rp.RESETS.RESET.ClearBits(resetVal) // Wait until reset is done. @@ -239,7 +237,7 @@ func (spi SPI) reset() { } //go:inline -func (spi SPI) deinit() (resetVal uint32) { +func (spi *SPI) deinit() (resetVal uint32) { switch spi.Bus { case rp.SPI0: resetVal = rp.RESETS_RESET_SPI0 @@ -254,19 +252,19 @@ func (spi SPI) deinit() (resetVal uint32) { // isWritable returns false if no space is available to write. True if a write is possible // //go:inline -func (spi SPI) isWritable() bool { +func (spi *SPI) isWritable() bool { return spi.Bus.SSPSR.HasBits(rp.SPI0_SSPSR_TNF) } // isReadable returns true if a read is possible i.e. data is present // //go:inline -func (spi SPI) isReadable() bool { +func (spi *SPI) isReadable() bool { return spi.Bus.SSPSR.HasBits(rp.SPI0_SSPSR_RNE) } // PrintRegs prints SPI's peripheral common registries current values -func (spi SPI) PrintRegs() { +func (spi *SPI) PrintRegs() { cr0 := spi.Bus.SSPCR0.Get() cr1 := spi.Bus.SSPCR1.Get() dmacr := spi.Bus.SSPDMACR.Get() @@ -282,12 +280,12 @@ func (spi SPI) PrintRegs() { } //go:inline -func (spi SPI) isBusy() bool { +func (spi *SPI) isBusy() bool { return spi.Bus.SSPSR.HasBits(rp.SPI0_SSPSR_BSY) } // tx writes buffer to SPI ignoring Rx. -func (spi SPI) tx(tx []byte) error { +func (spi *SPI) tx(tx []byte) error { if len(tx) == 0 { // We don't have to do anything. // This avoids a panic in &tx[0] when len(tx) == 0. @@ -352,7 +350,7 @@ func (spi SPI) tx(tx []byte) error { // txrepeat is output repeatedly on SO as data is read in from SI. // Generally this can be 0, but some devices require a specific value here, // e.g. SD cards expect 0xff -func (spi SPI) rx(rx []byte, txrepeat byte) error { +func (spi *SPI) rx(rx []byte, txrepeat byte) error { plen := len(rx) const fifoDepth = 8 // see txrx var rxleft, txleft = plen, plen @@ -375,7 +373,7 @@ func (spi SPI) rx(rx []byte, txrepeat byte) error { // Write len bytes from src to SPI. Simultaneously read len bytes from SPI to dst. // Note this function is guaranteed to exit in a known amount of time (bits sent * time per bit) -func (spi SPI) txrx(tx, rx []byte) error { +func (spi *SPI) txrx(tx, rx []byte) error { plen := len(tx) if plen != len(rx) { return ErrTxInvalidSliceSize diff --git a/src/machine/machine_stm32_spi.go b/src/machine/machine_stm32_spi.go index 72d4316616..3c6e0b6a88 100644 --- a/src/machine/machine_stm32_spi.go +++ b/src/machine/machine_stm32_spi.go @@ -21,7 +21,7 @@ type SPIConfig struct { } // Configure is intended to setup the STM32 SPI1 interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // -- CONFIGURING THE SPI IN MASTER MODE -- // @@ -98,7 +98,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // 1. Enable the SPI by setting the SPE bit to 1. // 2. Write the first data item to be transmitted into the SPI_DR register diff --git a/src/machine/machine_stm32f103.go b/src/machine/machine_stm32f103.go index b8d494ad7b..9e7bb347c9 100644 --- a/src/machine/machine_stm32f103.go +++ b/src/machine/machine_stm32f103.go @@ -333,16 +333,16 @@ type SPI struct { // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( - SPI1 = SPI{Bus: stm32.SPI1} + SPI1 = &SPI{Bus: stm32.SPI1} SPI0 = SPI1 ) -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // no-op on this series } // Set baud rate for SPI -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 // set frequency dependent on PCLK2 prescaler (div 1) @@ -368,7 +368,7 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // Configure SPI pins for input output and clock -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) config.SDO.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) config.SDI.Configure(PinConfig{Mode: PinInputModeFloating}) diff --git a/src/machine/machine_stm32f4.go b/src/machine/machine_stm32f4.go index 41e1b2204f..31f1d2c635 100644 --- a/src/machine/machine_stm32f4.go +++ b/src/machine/machine_stm32f4.go @@ -672,17 +672,17 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // no-op on this series } -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) } -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var clock uint32 switch spi.Bus { case stm32.SPI1: diff --git a/src/machine/machine_stm32l0.go b/src/machine/machine_stm32l0.go index b7dc1581c3..1ecd958b81 100644 --- a/src/machine/machine_stm32l0.go +++ b/src/machine/machine_stm32l0.go @@ -234,12 +234,12 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // no-op on this series } // Set baud rate for SPI -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 localFrequency := config.Frequency @@ -289,7 +289,7 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // Configure SPI pins for input output and clock -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) diff --git a/src/machine/machine_stm32l4.go b/src/machine/machine_stm32l4.go index c22cf616b8..b5babc0b2a 100644 --- a/src/machine/machine_stm32l4.go +++ b/src/machine/machine_stm32l4.go @@ -309,14 +309,14 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // Set rx threshold to 8-bits, so RXNE flag is set for 1 byte // (common STM32 SPI implementation does 8-bit transfers only) spi.Bus.CR2.SetBits(stm32.SPI_CR2_FRXTH) } // Set baud rate for SPI -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 // Default @@ -359,7 +359,7 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // Configure SPI pins for input output and clock -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) diff --git a/src/machine/machine_stm32wlx.go b/src/machine/machine_stm32wlx.go index 84e302a101..80ca791e6a 100644 --- a/src/machine/machine_stm32wlx.go +++ b/src/machine/machine_stm32wlx.go @@ -235,19 +235,19 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // Set rx threshold to 8-bits, so RXNE flag is set for 1 byte // (common STM32 SPI implementation does 8-bit transfers only) spi.Bus.CR2.SetBits(stm32.SPI_CR2_FRXTH) } -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) } -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var clock uint32 // We keep this switch and separate management of SPI Clocks diff --git a/src/machine/spi_tx.go b/src/machine/spi_tx.go index 67076b2bdf..97385bb596 100644 --- a/src/machine/spi_tx.go +++ b/src/machine/spi_tx.go @@ -22,7 +22,7 @@ package machine // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { var err error switch { From bcdc5e009756730962bdb5d1ef08e569009d82cd Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 11 Mar 2025 14:11:24 +0100 Subject: [PATCH 11/45] chore: update version to 0.37.0-dev Signed-off-by: deadprogram --- goenv/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goenv/version.go b/goenv/version.go index f2695d275d..1f0f4c75ea 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -10,7 +10,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.36.0" +const version = "0.37.0-dev" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012). From f76e8d2812ef41829c4af1156444c7b082e3df1e Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Tue, 11 Mar 2025 08:50:31 -0700 Subject: [PATCH 12/45] fix: ensure use of pointers for SPI interface on atsam21/atsam51 and other machines/boards that were missing implementation. (#4798) Signed-off-by: deadprogram --- src/machine/board_gnse.go | 2 +- src/machine/board_hifive1b_baremetal.go | 2 +- src/machine/board_lorae5.go | 2 +- src/machine/board_nucleowl55jc.go | 2 +- src/machine/board_teensy40.go | 11 ++++------- src/machine/machine_atsamd21e18.go | 8 ++++---- src/machine/machine_atsamd21g18.go | 12 ++++++------ src/machine/machine_atsamd51g19.go | 12 ++++++------ src/machine/machine_atsamd51j19.go | 12 ++++++------ src/machine/machine_atsamd51j20.go | 12 ++++++------ src/machine/machine_atsamd51p19.go | 16 ++++++++-------- src/machine/machine_atsamd51p20.go | 16 ++++++++-------- src/machine/machine_atsame51j19.go | 12 ++++++------ src/machine/machine_atsame54p20.go | 16 ++++++++-------- src/machine/machine_esp32.go | 4 ++-- src/machine/machine_esp32c3_spi.go | 2 +- src/machine/machine_generic.go | 16 ++++++++-------- 17 files changed, 77 insertions(+), 80 deletions(-) diff --git a/src/machine/board_gnse.go b/src/machine/board_gnse.go index 02a8038b14..8e78b43e5a 100644 --- a/src/machine/board_gnse.go +++ b/src/machine/board_gnse.go @@ -77,7 +77,7 @@ var ( I2C0 = I2C1 // SPI - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI3, } ) diff --git a/src/machine/board_hifive1b_baremetal.go b/src/machine/board_hifive1b_baremetal.go index cea0443e69..f621d3bc56 100644 --- a/src/machine/board_hifive1b_baremetal.go +++ b/src/machine/board_hifive1b_baremetal.go @@ -6,7 +6,7 @@ import "device/sifive" // SPI on the HiFive1. var ( - SPI1 = SPI{ + SPI1 = &SPI{ Bus: sifive.QSPI1, } ) diff --git a/src/machine/board_lorae5.go b/src/machine/board_lorae5.go index f2c26997c4..18b5d8e363 100644 --- a/src/machine/board_lorae5.go +++ b/src/machine/board_lorae5.go @@ -85,7 +85,7 @@ var ( I2C0 = I2C2 // SPI - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI3, } ) diff --git a/src/machine/board_nucleowl55jc.go b/src/machine/board_nucleowl55jc.go index fd4883cc02..a8e88dd8bb 100644 --- a/src/machine/board_nucleowl55jc.go +++ b/src/machine/board_nucleowl55jc.go @@ -84,7 +84,7 @@ var ( I2C0 = I2C1 // SPI - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI3, } ) diff --git a/src/machine/board_teensy40.go b/src/machine/board_teensy40.go index 92fe81bce9..22529a8d75 100644 --- a/src/machine/board_teensy40.go +++ b/src/machine/board_teensy40.go @@ -263,9 +263,8 @@ const ( ) var ( - SPI0 = SPI1 // SPI0 is an alias of SPI1 (LPSPI4) - SPI1 = &_SPI1 - _SPI1 = SPI{ + SPI0 = SPI1 // SPI0 is an alias of SPI1 (LPSPI4) + SPI1 = &SPI{ Bus: nxp.LPSPI4, muxSDI: muxSelect{ // D12 (PB1 [B0_01]) mux: nxp.IOMUXC_LPSPI4_SDI_SELECT_INPUT_DAISY_GPIO_B0_01_ALT3, @@ -284,8 +283,7 @@ var ( sel: &nxp.IOMUXC.LPSPI4_PCS0_SELECT_INPUT, }, } - SPI2 = &_SPI2 - _SPI2 = SPI{ + SPI2 = &SPI{ Bus: nxp.LPSPI3, muxSDI: muxSelect{ // D1 (PA2 [AD_B0_02]) mux: nxp.IOMUXC_LPSPI3_SDI_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT7, @@ -304,8 +302,7 @@ var ( sel: &nxp.IOMUXC.LPSPI3_PCS0_SELECT_INPUT, }, } - SPI3 = &_SPI3 - _SPI3 = SPI{ + SPI3 = &SPI{ Bus: nxp.LPSPI1, muxSDI: muxSelect{ // D34 (PC15 [SD_B0_03]) mux: nxp.IOMUXC_LPSPI1_SDI_SELECT_INPUT_DAISY_GPIO_SD_B0_03_ALT4, diff --git a/src/machine/machine_atsamd21e18.go b/src/machine/machine_atsamd21e18.go index 85d7ff2552..85d6853bc7 100644 --- a/src/machine/machine_atsamd21e18.go +++ b/src/machine/machine_atsamd21e18.go @@ -22,10 +22,10 @@ var ( sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} ) func init() { diff --git a/src/machine/machine_atsamd21g18.go b/src/machine/machine_atsamd21g18.go index 9b78ad3367..9e845cf3bc 100644 --- a/src/machine/machine_atsamd21g18.go +++ b/src/machine/machine_atsamd21g18.go @@ -26,12 +26,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPI, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPI, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPI, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPI, SERCOM: 5} ) func init() { diff --git a/src/machine/machine_atsamd51g19.go b/src/machine/machine_atsamd51g19.go index ade031bb04..f223f6ebc8 100644 --- a/src/machine/machine_atsamd51g19.go +++ b/src/machine/machine_atsamd51g19.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51j19.go b/src/machine/machine_atsamd51j19.go index b41c25c145..640e1ef263 100644 --- a/src/machine/machine_atsamd51j19.go +++ b/src/machine/machine_atsamd51j19.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51j20.go b/src/machine/machine_atsamd51j20.go index 2c5391afe7..d582278760 100644 --- a/src/machine/machine_atsamd51j20.go +++ b/src/machine/machine_atsamd51j20.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51p19.go b/src/machine/machine_atsamd51p19.go index 70050c2d6f..bcd66a93a7 100644 --- a/src/machine/machine_atsamd51p19.go +++ b/src/machine/machine_atsamd51p19.go @@ -20,14 +20,14 @@ var ( sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} - sercomSPIM6 = SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} - sercomSPIM7 = SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51p20.go b/src/machine/machine_atsamd51p20.go index 9f52ba257f..40e435fa1b 100644 --- a/src/machine/machine_atsamd51p20.go +++ b/src/machine/machine_atsamd51p20.go @@ -20,14 +20,14 @@ var ( sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} - sercomSPIM6 = SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} - sercomSPIM7 = SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsame51j19.go b/src/machine/machine_atsame51j19.go index 8f8294a266..29ea411784 100644 --- a/src/machine/machine_atsame51j19.go +++ b/src/machine/machine_atsame51j19.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsame54p20.go b/src/machine/machine_atsame54p20.go index 922ee31474..d7cc31f62d 100644 --- a/src/machine/machine_atsame54p20.go +++ b/src/machine/machine_atsame54p20.go @@ -20,14 +20,14 @@ var ( sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} - sercomSPIM6 = SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} - sercomSPIM7 = SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go index 50ff74d672..237a1292c2 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -334,8 +334,8 @@ type SPI struct { var ( // SPI0 and SPI1 are reserved for use by the caching system etc. - SPI2 = SPI{esp.SPI2} - SPI3 = SPI{esp.SPI3} + SPI2 = &SPI{esp.SPI2} + SPI3 = &SPI{esp.SPI3} ) // SPIConfig configures a SPI peripheral on the ESP32. Make sure to set at least diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go index ecb1923dee..aec3ca77a8 100644 --- a/src/machine/machine_esp32c3_spi.go +++ b/src/machine/machine_esp32c3_spi.go @@ -52,7 +52,7 @@ type SPI struct { var ( // SPI0 and SPI1 are reserved for use by the caching system etc. - SPI2 = SPI{esp.SPI2} + SPI2 = &SPI{esp.SPI2} ) // SPIConfig is used to store config info for SPI. diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index 1c8512244b..8bfa097f03 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -248,14 +248,14 @@ var ( sercomI2CM6 = &I2C{6} sercomI2CM7 = &I2C{7} - sercomSPIM0 = SPI{0} - sercomSPIM1 = SPI{1} - sercomSPIM2 = SPI{2} - sercomSPIM3 = SPI{3} - sercomSPIM4 = SPI{4} - sercomSPIM5 = SPI{5} - sercomSPIM6 = SPI{6} - sercomSPIM7 = SPI{7} + sercomSPIM0 = &SPI{0} + sercomSPIM1 = &SPI{1} + sercomSPIM2 = &SPI{2} + sercomSPIM3 = &SPI{3} + sercomSPIM4 = &SPI{4} + sercomSPIM5 = &SPI{5} + sercomSPIM6 = &SPI{6} + sercomSPIM7 = &SPI{7} ) // GetRNG returns 32 bits of random data from the WASI random source. From 04c7057ea61debe3f32ae0a6f623845a81867654 Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 4 Mar 2025 09:10:48 -0300 Subject: [PATCH 13/45] add goroutine benchmark to examples --- src/examples/bench-goro/bench.go | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/examples/bench-goro/bench.go diff --git a/src/examples/bench-goro/bench.go b/src/examples/bench-goro/bench.go new file mode 100644 index 0000000000..db08a03c19 --- /dev/null +++ b/src/examples/bench-goro/bench.go @@ -0,0 +1,40 @@ +package main + +import ( + "machine" + "runtime" + "sync" + "time" +) + +const N = 500000 +const Ngoro = 4 + +func main() { + start := time.Now() + var wg sync.WaitGroup + wg.Add(Ngoro) + for i := 0; i < Ngoro; i++ { + go adder(&wg, N) + } + wg.Wait() + elapsed := time.Since(start) + goroutineCtxSwitchOverhead := (elapsed / (Ngoro * N)).String() + + elapsedstr := elapsed.String() + machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) + for { + println("bench:", elapsedstr, "goroutine ctx switch:", goroutineCtxSwitchOverhead) + machine.LED.High() + time.Sleep(elapsed) + machine.LED.Low() + time.Sleep(elapsed) + } +} + +func adder(wg *sync.WaitGroup, num int) { + for i := 0; i < num; i++ { + runtime.Gosched() + } + wg.Done() +} From ff2a79de7c6d9d1f86910f95dffc6d560ebb9257 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 10 Mar 2025 11:51:38 +0100 Subject: [PATCH 14/45] riscv-qemu: increase stack size Bumping the stack size to 8kb (previous was 4kb) gets most remaining tests to pass on baremetal. --- GNUmakefile | 7 ------- targets/riscv-qemu.json | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 978d7d9817..bcbc21b263 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -431,17 +431,10 @@ TEST_PACKAGES_NONWASM = \ # (just like wasm). # * picolibc math functions apparently are less precise, the math package # fails on baremetal. -# * Some packages fail or hang for an unknown reason, this should be -# investigated and fixed. TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST)) TEST_PACKAGES_NONBAREMETAL = \ $(TEST_PACKAGES_NONWASM) \ - crypto/elliptic \ math \ - reflect \ - encoding/asn1 \ - encoding/base32 \ - go/ast \ $(nil) # Report platforms on which each standard library package is known to pass tests diff --git a/targets/riscv-qemu.json b/targets/riscv-qemu.json index 84890308ba..90f1c312fc 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -2,7 +2,7 @@ "inherits": ["riscv32"], "features": "+32bit,+a,+c,+m,+zmmul,-b,-d,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": ["virt", "qemu"], - "default-stack-size": 4096, + "default-stack-size": 8192, "linkerscript": "targets/riscv-qemu.ld", "emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -device virtio-rng-device -kernel {}" } From 4f7c64cb24853027e28fe0777c3bd0974297389a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=83=BC=E3=82=8B=E3=81=A9=E3=82=93?= <82225965+rdon-key@users.noreply.github.com> Date: Fri, 14 Mar 2025 00:39:43 +0900 Subject: [PATCH 15/45] =?UTF-8?q?fix(rp2040):=20replace=20loop=20counter?= =?UTF-8?q?=20with=20hw=20timer=20for=20USB=20SetAddressReq=E2=80=A6=20(#4?= =?UTF-8?q?796)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(rp2040/rp2350): replace loop counter with hw timer for USB SetAddressRequest timeout. * fix code format. --------- Co-authored-by: rdon --- src/machine/machine_rp2040_usb.go | 17 +++++++++-------- src/machine/machine_rp2350_usb.go | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/machine/machine_rp2040_usb.go b/src/machine/machine_rp2040_usb.go index ac7df98204..d69cd6ad5f 100644 --- a/src/machine/machine_rp2040_usb.go +++ b/src/machine/machine_rp2040_usb.go @@ -169,21 +169,22 @@ func initEndpoint(ep, config uint32) { } func handleUSBSetAddress(setup usb.Setup) bool { - sendUSBPacket(0, []byte{}, 0) + // Using 570μs timeout which is exactly the same as SAMD21. + const ackTimeout = 570 - // last, set the device address to that requested by host - // wait for transfer to complete - timeout := 3000 rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) + sendUSBPacket(0, []byte{}, 0) + + // Wait for transfer to complete with a timeout. + t := timer.timeElapsed() for (rp.USBCTRL_REGS.SIE_STATUS.Get() & rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) == 0 { - timeout-- - if timeout == 0 { - return true + if dt := timer.timeElapsed() - t; dt >= ackTimeout { + return false } } + // Set the device address to that requested by host. rp.USBCTRL_REGS.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USBCTRL_REGS_ADDR_ENDP_ADDRESS_Msk) - return true } diff --git a/src/machine/machine_rp2350_usb.go b/src/machine/machine_rp2350_usb.go index 48fbbcbd05..b42ce09cca 100644 --- a/src/machine/machine_rp2350_usb.go +++ b/src/machine/machine_rp2350_usb.go @@ -172,21 +172,22 @@ func initEndpoint(ep, config uint32) { } func handleUSBSetAddress(setup usb.Setup) bool { - sendUSBPacket(0, []byte{}, 0) + // Using 570μs timeout which is exactly the same as SAMD21. - // last, set the device address to that requested by host - // wait for transfer to complete - timeout := 3000 + const ackTimeout = 570 rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC) + sendUSBPacket(0, []byte{}, 0) + + // Wait for transfer to complete with a timeout. + t := timer.timeElapsed() for (rp.USB.SIE_STATUS.Get() & rp.USB_SIE_STATUS_ACK_REC) == 0 { - timeout-- - if timeout == 0 { - return true + if dt := timer.timeElapsed() - t; dt >= ackTimeout { + return false } } + // Set the device address to that requested by host. rp.USB.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USB_ADDR_ENDP_ADDRESS_Msk) - return true } From 4768c7d4313a6b8cf4c2f3eae91e9b6f8af3a439 Mon Sep 17 00:00:00 2001 From: Michael Smith <2015782+mikesmitty@users.noreply.github.com> Date: Sun, 16 Mar 2025 04:45:53 -0400 Subject: [PATCH 16/45] machine/rp2350: add flash support for rp2350 (#4803) * machine/rp2350: add flash support for rp2350 * combine duplicate files * clean things up and group by source file * add stubbed out xip cache clean func if needed in the future * update flash_enable_xip_via_boot2 * remove unused macros and fix inconsistent formatting * make flash size configurable like rp2040 * add missing flash size configs * retain big Go CGo compatibility per #4103 * clarify CS0_SIZE source and remove single-use typedef --- src/machine/flash.go | 2 +- src/machine/machine_rp2350_rom.go | 411 +++++++++++++++++- src/machine/machine_rp2_2350.go | 4 - ...e_rp2040_flash.go => machine_rp2_flash.go} | 2 +- targets/pga2350.json | 5 +- targets/pico2.json | 5 +- targets/rp2350.json | 3 + targets/rp2350.ld | 4 +- targets/tiny2350.json | 3 + 9 files changed, 427 insertions(+), 12 deletions(-) rename src/machine/{machine_rp2040_flash.go => machine_rp2_flash.go} (99%) diff --git a/src/machine/flash.go b/src/machine/flash.go index da832afdb8..c89c091b91 100644 --- a/src/machine/flash.go +++ b/src/machine/flash.go @@ -1,4 +1,4 @@ -//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 +//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350 package machine diff --git a/src/machine/machine_rp2350_rom.go b/src/machine/machine_rp2350_rom.go index 4f3d4762a8..665464ae69 100644 --- a/src/machine/machine_rp2350_rom.go +++ b/src/machine/machine_rp2350_rom.go @@ -2,7 +2,10 @@ package machine -import () +import ( + "runtime/interrupt" + "unsafe" +) /* typedef unsigned char uint8_t; @@ -10,11 +13,82 @@ typedef unsigned short uint16_t; typedef unsigned long uint32_t; typedef unsigned long size_t; typedef unsigned long uintptr_t; +typedef long int intptr_t; + +typedef const volatile uint16_t io_ro_16; +typedef const volatile uint32_t io_ro_32; +typedef volatile uint16_t io_rw_16; +typedef volatile uint32_t io_rw_32; +typedef volatile uint32_t io_wo_32; #define false 0 #define true 1 typedef int bool; +#define ram_func __attribute__((section(".ramfuncs"),noinline)) + +typedef void (*flash_exit_xip_fn)(void); +typedef void (*flash_flush_cache_fn)(void); +typedef void (*flash_connect_internal_fn)(void); +typedef void (*flash_range_erase_fn)(uint32_t, size_t, uint32_t, uint16_t); +typedef void (*flash_range_program_fn)(uint32_t, const uint8_t*, size_t); +static inline __attribute__((always_inline)) void __compiler_memory_barrier(void) { + __asm__ volatile ("" : : : "memory"); +} + +// https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf +// 13.9. Predefined OTP Data Locations +// OTP_DATA: FLASH_DEVINFO Register + +#define OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS 0x0F00 +#define OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB 8 +#define OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS 0xF000 +#define OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB 12 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_regs/include/hardware/regs/addressmap.h + +#define REG_ALIAS_RW_BITS (0x0 << 12) +#define REG_ALIAS_XOR_BITS (0x1 << 12) +#define REG_ALIAS_SET_BITS (0x2 << 12) +#define REG_ALIAS_CLR_BITS (0x3 << 12) + +#define XIP_BASE 0x10000000 +#define XIP_QMI_BASE 0x400d0000 +#define IO_QSPI_BASE 0x40030000 +#define BOOTRAM_BASE 0x400e0000 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_base/include/hardware/address_mapped.h + +#define hw_alias_check_addr(addr) ((uintptr_t)(addr)) +#define hw_set_alias_untyped(addr) ((void *)(REG_ALIAS_SET_BITS + hw_alias_check_addr(addr))) +#define hw_clear_alias_untyped(addr) ((void *)(REG_ALIAS_CLR_BITS + hw_alias_check_addr(addr))) +#define hw_xor_alias_untyped(addr) ((void *)(REG_ALIAS_XOR_BITS + hw_alias_check_addr(addr))) + +__attribute__((always_inline)) +static void hw_set_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_clear_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_clear_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_xor_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_xor_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_write_masked(io_rw_32 *addr, uint32_t values, uint32_t write_mask) { + hw_xor_bits(addr, (*addr ^ values) & write_mask); +} + + // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_platform_compiler/include/pico/platform/compiler.h @@ -88,6 +162,7 @@ static bool pico_processor_state_is_nonsecure(void) { #define BOOTROM_VTABLE_OFFSET 0x00 #define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE) + // https://github.com/raspberrypi/pico-sdk // src/common/boot_picoboot_headers/include/boot/picoboot_constants.h @@ -101,6 +176,8 @@ static bool pico_processor_state_is_nonsecure(void) { #define RT_FLAG_FUNC_ARM_SEC 0x0004 #define RT_FLAG_FUNC_ARM_NONSEC 0x0010 +#define RT_FLAG_DATA 0x0040 + // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_bootrom/include/pico/bootrom.h @@ -119,6 +196,11 @@ static void *rom_func_lookup_inline(uint32_t code) { } } +__attribute__((always_inline)) +static void *rom_data_lookup_inline(uint32_t code) { + rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) (uintptr_t)*(uint16_t*)(BOOTROM_TABLE_LOOKUP_OFFSET); + return rom_table_lookup(code, RT_FLAG_DATA); +} typedef int (*rom_reboot_fn)(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1); @@ -132,7 +214,6 @@ int rom_reboot(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1) { // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_bootrom/bootrom.c - void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interface_mask) { uint32_t flags = disable_interface_mask; if (usb_activity_gpio_pin_mask) { @@ -145,9 +226,335 @@ 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_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 +// src/rp2350/hardware_regs/include/hardware/regs/io_qspi.h + +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS 0x00003000 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB 12 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW 0x2 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH 0x3 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_structs/include/hardware/structs/io_qspi.h + +typedef struct { + io_rw_32 inte; // IO_QSPI_PROC0_INTE + io_rw_32 intf; // IO_QSPI_PROC0_INTF + io_ro_32 ints; // IO_QSPI_PROC0_INTS +} io_qspi_irq_ctrl_hw_t; + +typedef struct { + io_ro_32 status; // IO_QSPI_GPIO_QSPI_SCLK_STATUS + io_rw_32 ctrl; // IO_QSPI_GPIO_QSPI_SCLK_CTRL +} io_qspi_status_ctrl_hw_t; + +typedef struct { + io_ro_32 usbphy_dp_status; // IO_QSPI_USBPHY_DP_STATUS + io_rw_32 usbphy_dp_ctrl; // IO_QSPI_USBPHY_DP_CTRL + io_ro_32 usbphy_dm_status; // IO_QSPI_USBPHY_DM_STATUS + io_rw_32 usbphy_dm_ctrl; // IO_QSPI_USBPHY_DM_CTRL + io_qspi_status_ctrl_hw_t io[6]; + uint32_t _pad0[112]; + io_ro_32 irqsummary_proc0_secure; // IO_QSPI_IRQSUMMARY_PROC0_SECURE + io_ro_32 irqsummary_proc0_nonsecure; // IO_QSPI_IRQSUMMARY_PROC0_NONSECURE + io_ro_32 irqsummary_proc1_secure; // IO_QSPI_IRQSUMMARY_PROC1_SECURE + io_ro_32 irqsummary_proc1_nonsecure; // IO_QSPI_IRQSUMMARY_PROC1_NONSECURE + io_ro_32 irqsummary_dormant_wake_secure; // IO_QSPI_IRQSUMMARY_DORMANT_WAKE_SECURE + io_ro_32 irqsummary_dormant_wake_nonsecure; // IO_QSPI_IRQSUMMARY_DORMANT_WAKE_NONSECURE + io_rw_32 intr; // IO_QSPI_INTR + + union { + struct { + io_qspi_irq_ctrl_hw_t proc0_irq_ctrl; + io_qspi_irq_ctrl_hw_t proc1_irq_ctrl; + io_qspi_irq_ctrl_hw_t dormant_wake_irq_ctrl; + }; + io_qspi_irq_ctrl_hw_t irq_ctrl[3]; + }; +} io_qspi_hw_t; + +#define io_qspi_hw ((io_qspi_hw_t *)IO_QSPI_BASE) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_structs/include/hardware/structs/qmi.h + +typedef struct { + io_rw_32 timing; // QMI_M0_TIMING + io_rw_32 rfmt; // QMI_M0_RFMT + io_rw_32 rcmd; // QMI_M0_RCMD + io_rw_32 wfmt; // QMI_M0_WFMT + io_rw_32 wcmd; // QMI_M0_WCMD +} qmi_mem_hw_t; + +typedef struct { + io_rw_32 direct_csr; // QMI_DIRECT_CSR + io_wo_32 direct_tx; // QMI_DIRECT_TX + io_ro_32 direct_rx; // QMI_DIRECT_RX + qmi_mem_hw_t m[2]; + io_rw_32 atrans[8]; // QMI_ATRANS0 +} qmi_hw_t; + +#define qmi_hw ((qmi_hw_t *)XIP_QMI_BASE) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_xip_cache/include/hardware/xip_cache.h + +// Noop unless using XIP Cache-as-SRAM +// Non-noop version in src/rp2_common/hardware_xip_cache/xip_cache.c +static inline void xip_cache_clean_all(void) {} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_flash/include/hardware/flash.h + +#define FLASH_PAGE_SIZE (1u << 8) +#define FLASH_SECTOR_SIZE (1u << 12) +#define FLASH_BLOCK_SIZE (1u << 16) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_flash/flash.c + +#define BOOT2_SIZE_WORDS 64 +#define FLASH_BLOCK_ERASE_CMD 0xd8 + +static uint32_t boot2_copyout[BOOT2_SIZE_WORDS]; +static bool boot2_copyout_valid = false; + +static ram_func void flash_init_boot2_copyout(void) { + if (boot2_copyout_valid) + return; + for (int i = 0; i < BOOT2_SIZE_WORDS; ++i) + boot2_copyout[i] = ((uint32_t *)BOOTRAM_BASE)[i]; + __compiler_memory_barrier(); + boot2_copyout_valid = true; +} + +static ram_func void flash_enable_xip_via_boot2(void) { + ((void (*)(void))((intptr_t)boot2_copyout+1))(); +} + +// This is a static symbol because the layout of FLASH_DEVINFO is liable to change from device to +// device, so fields must have getters/setters. +static io_rw_16 * ram_func flash_devinfo_ptr(void) { + // Note the lookup returns a pointer to a 32-bit pointer literal in the ROM + io_rw_16 **p = (io_rw_16 **) rom_data_lookup_inline(ROM_DATA_FLASH_DEVINFO16_PTR); + return *p; +} + +// This is a RAM function because may be called during flash programming to enable save/restore of +// QMI window 1 registers on RP2350: +uint8_t ram_func flash_devinfo_get_cs_size(uint8_t cs) { + io_ro_16 *devinfo = (io_ro_16 *) flash_devinfo_ptr(); + if (cs == 0u) { + return (uint8_t) ( + (*devinfo & OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS) >> OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB + ); + } else { + return (uint8_t) ( + (*devinfo & OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS) >> OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB + ); + } +} + +// This is specifically for saving/restoring the registers modified by RP2350 +// flash_exit_xip() ROM func, not the entirety of the QMI window state. +typedef struct flash_rp2350_qmi_save_state { + uint32_t timing; + uint32_t rcmd; + uint32_t rfmt; +} flash_rp2350_qmi_save_state_t; + +static ram_func void flash_rp2350_save_qmi_cs1(flash_rp2350_qmi_save_state_t *state) { + state->timing = qmi_hw->m[1].timing; + state->rcmd = qmi_hw->m[1].rcmd; + state->rfmt = qmi_hw->m[1].rfmt; +} + +static ram_func void flash_rp2350_restore_qmi_cs1(const flash_rp2350_qmi_save_state_t *state) { + if (flash_devinfo_get_cs_size(1) == 0) { + // Case 1: The RP2350 ROM sets QMI to a clean (03h read) configuration + // during flash_exit_xip(), even though when CS1 is not enabled via + // FLASH_DEVINFO it does not issue an XIP exit sequence to CS1. In + // this case, restore the original register config for CS1 as it is + // still the correct config. + qmi_hw->m[1].timing = state->timing; + qmi_hw->m[1].rcmd = state->rcmd; + qmi_hw->m[1].rfmt = state->rfmt; + } else { + // Case 2: If RAM is attached to CS1, and the ROM has issued an XIP + // exit sequence to it, then the ROM re-initialisation of the QMI + // registers has actually not gone far enough. The old XIP write mode + // is no longer valid when the QSPI RAM is returned to a serial + // command state. Restore the default 02h serial write command config. + qmi_hw->m[1].wfmt = QMI_M1_WFMT_RESET; + qmi_hw->m[1].wcmd = QMI_M1_WCMD_RESET; + } +} + +void ram_func flash_cs_force(bool high) { + 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() +void ram_func flash_range_write(uint32_t offset, const uint8_t *data, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_range_program_fn flash_range_program_func = (flash_range_program_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_PROGRAM); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + xip_cache_clean_all(); + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + flash_range_program_func(offset, data, count); + flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + +// Adapted from flash_range_erase() +void ram_func flash_erase_blocks(uint32_t offset, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_range_erase_fn flash_range_erase_func = (flash_range_erase_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_ERASE); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + // Commit any pending writes to external RAM, to avoid losing them in the subsequent flush: + xip_cache_clean_all(); + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + // No flash accesses after this point + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + flash_range_erase_func(offset, count, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD); + flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + +void ram_func flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + xip_cache_clean_all(); + + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + __compiler_memory_barrier(); + flash_connect_internal_func(); + flash_exit_xip_func(); + + flash_cs_force(0); + size_t tx_remaining = count; + size_t rx_remaining = count; + + // QMI version -- no need to bound FIFO contents as QMI stalls on full DIRECT_RX. + hw_set_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS); + while (tx_remaining || rx_remaining) { + uint32_t flags = qmi_hw->direct_csr; + bool can_put = !(flags & QMI_DIRECT_CSR_TXFULL_BITS); + bool can_get = !(flags & QMI_DIRECT_CSR_RXEMPTY_BITS); + if (can_put && tx_remaining) { + qmi_hw->direct_tx = *txbuf++; + --tx_remaining; + } + if (can_get && rx_remaining) { + *rxbuf++ = (uint8_t)qmi_hw->direct_rx; + --rx_remaining; + } + } + hw_clear_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS); + + flash_cs_force(1); + + flash_flush_cache_func(); + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + */ import "C" func enterBootloader() { C.reset_usb_boot(0, 0) } + +func doFlashCommand(tx []byte, rx []byte) error { + if len(tx) != len(rx) { + return errFlashInvalidWriteLength + } + + C.flash_do_cmd( + (*C.uint8_t)(unsafe.Pointer(&tx[0])), + (*C.uint8_t)(unsafe.Pointer(&rx[0])), + C.ulong(len(tx))) + + return nil +} + +// Flash related code +const memoryStart = C.XIP_BASE // memory start for purpose of erase + +func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { + if writeAddress(off)+uintptr(C.XIP_BASE) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + // rp2350 writes to offset, not actual address + // e.g. real address 0x10003000 is written to at + // 0x00003000 + address := writeAddress(off) + padded := flashPad(p, int(f.WriteBlockSize())) + + C.flash_range_write(C.uint32_t(address), + (*C.uint8_t)(unsafe.Pointer(&padded[0])), + C.ulong(len(padded))) + + return len(padded), nil +} + +func (f flashBlockDevice) eraseBlocks(start, length int64) error { + address := writeAddress(start * f.EraseBlockSize()) + if address+uintptr(C.XIP_BASE) > FlashDataEnd() { + return errFlashCannotErasePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + C.flash_erase_blocks(C.uint32_t(address), C.ulong(length*f.EraseBlockSize())) + + return nil +} diff --git a/src/machine/machine_rp2_2350.go b/src/machine/machine_rp2_2350.go index d259e750d8..a6a6aa2eb1 100644 --- a/src/machine/machine_rp2_2350.go +++ b/src/machine/machine_rp2_2350.go @@ -212,10 +212,6 @@ func (clks *clocksType) initTicks() { rp.TICKS.SetTIMER0_CTRL_ENABLE(1) } -func EnterBootloader() { - enterBootloader() -} - // startTick starts the watchdog tick. // On RP2040, the watchdog contained a tick generator used to generate a 1μs tick for the watchdog. This was also // distributed to the system timer. On RP2350, the watchdog instead takes a tick input from the system-level ticks block. See Section 8.5. diff --git a/src/machine/machine_rp2040_flash.go b/src/machine/machine_rp2_flash.go similarity index 99% rename from src/machine/machine_rp2040_flash.go rename to src/machine/machine_rp2_flash.go index 1317c0926a..45263dfac3 100644 --- a/src/machine/machine_rp2040_flash.go +++ b/src/machine/machine_rp2_flash.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine diff --git a/targets/pga2350.json b/targets/pga2350.json index 3ff72c26c2..5afe89ec07 100644 --- a/targets/pga2350.json +++ b/targets/pga2350.json @@ -1,4 +1,7 @@ { "inherits": ["rp2350b"], - "build-tags": ["pga2350"] + "build-tags": ["pga2350"], + "ldflags": [ + "--defsym=__flash_size=16M" + ] } \ No newline at end of file diff --git a/targets/pico2.json b/targets/pico2.json index dfdac71ee0..af156d54d8 100644 --- a/targets/pico2.json +++ b/targets/pico2.json @@ -4,5 +4,8 @@ ], "build-tags": ["pico2"], "serial-port": ["2e8a:000A"], - "default-stack-size": 8192 + "default-stack-size": 8192, + "ldflags": [ + "--defsym=__flash_size=4M" + ] } diff --git a/targets/rp2350.json b/targets/rp2350.json index 9070560bcb..0487aa14d9 100644 --- a/targets/rp2350.json +++ b/targets/rp2350.json @@ -12,6 +12,9 @@ "src/device/rp/rp2350.s", "targets/rp2350_embedded_block.s" ], + "ldflags": [ + "--defsym=__flash_size=2M" + ], "linkerscript": "targets/rp2350.ld", "openocd-interface": "picoprobe", "openocd-transport": "swd", diff --git a/targets/rp2350.ld b/targets/rp2350.ld index dbe495cf7c..5296a1fb12 100644 --- a/targets/rp2350.ld +++ b/targets/rp2350.ld @@ -2,7 +2,7 @@ MEMORY { /* 2MiB safe default. */ - FLASH : ORIGIN = 0x10000000, LENGTH = 2048k + FLASH : ORIGIN = 0x10000000, LENGTH = __flash_size /* RAM consists of 8 banks, SRAM0..SRAM7 with striped mapping. */ SRAM : ORIGIN = 0x20000000, LENGTH = 512k /* Banks 8 and 9 use direct mapping which can be @@ -10,7 +10,7 @@ MEMORY i.e: Separate stacks for core0 and core1. */ SRAM4 : ORIGIN = 0x20080000, LENGTH = 4k SRAM5 : ORIGIN = 0x20081000, LENGTH = 4k - FLASH_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = 2048k + FLASH_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = __flash_size RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 512k } diff --git a/targets/tiny2350.json b/targets/tiny2350.json index 185be33202..660e096468 100644 --- a/targets/tiny2350.json +++ b/targets/tiny2350.json @@ -3,5 +3,8 @@ "rp2350" ], "build-tags": ["tiny2350"], + "ldflags": [ + "--defsym=__flash_size=4M" + ], "serial-port": ["2e8a:000f"] } From bbb2b0c95bda6e08f6e5acb28a92c6f8f53db85a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 17 Mar 2025 09:07:41 +0100 Subject: [PATCH 17/45] builder: use a separate module for command-line set strings Strings that are set via the command line (as in -ldflags="-X ...") are now created in a separate module to avoid type renaming issues. LLVM sometimes renames types or merges types that are structurally the same, and putting these strings in a separate module avoids this issue (and lets llvm.LinkModules deal with the difference). This fixes https://github.com/tinygo-org/tinygo/issues/4810. --- builder/build.go | 81 ++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/builder/build.go b/builder/build.go index 2d7156b9b7..c2c712456e 100644 --- a/builder/build.go +++ b/builder/build.go @@ -449,8 +449,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if global.IsNil() { return errors.New("global not found: " + globalName) } + globalType := global.GlobalValueType() + if globalType.TypeKind() != llvm.StructTypeKind || globalType.StructName() != "runtime._string" { + // Verify this is indeed a string. This is needed so + // that makeGlobalsModule can just create the right + // globals of string type without checking. + return fmt.Errorf("%s: not a string", globalName) + } name := global.Name() - newGlobal := llvm.AddGlobal(mod, global.GlobalValueType(), name+".tmp") + newGlobal := llvm.AddGlobal(mod, globalType, name+".tmp") global.ReplaceAllUsesWith(newGlobal) global.EraseFromParentAsGlobal() newGlobal.SetName(name) @@ -538,6 +545,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } } + // Insert values from -ldflags="-X ..." into the IR. + // This is a separate module, so that the "runtime._string" type + // doesn't need to match precisely. LLVM tends to rename that type + // sometimes, leading to errors. But linking in a separate module + // works fine. See: + // https://github.com/tinygo-org/tinygo/issues/4810 + globalsMod := makeGlobalsModule(ctx, globalValues, machine) + llvm.LinkModules(mod, globalsMod) + // Create runtime.initAll function that calls the runtime // initializer of each package. llvmInitFn := mod.NamedFunction("runtime.initAll") @@ -590,7 +606,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Run all optimization passes, which are much more effective now // that the optimizer can see the whole program at once. - err := optimizeProgram(mod, config, globalValues) + err := optimizeProgram(mod, config) if err != nil { return err } @@ -1145,7 +1161,7 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c // optimizeProgram runs a series of optimizations and transformations that are // needed to convert a program to its final form. Some transformations are not // optional and must be run as the compiler expects them to run. -func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues map[string]map[string]string) error { +func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA()) if err != nil { return err @@ -1163,12 +1179,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m } } - // Insert values from -ldflags="-X ..." into the IR. - err = setGlobalValues(mod, globalValues) - if err != nil { - return err - } - // Run most of the whole-program optimizations (including the whole // O0/O1/O2/Os/Oz optimization pipeline). errs := transform.Optimize(mod, config) @@ -1182,10 +1192,19 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m return nil } -// setGlobalValues sets the global values from the -ldflags="-X ..." compiler -// option in the given module. An error may be returned if the global is not of -// the expected type. -func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error { +func makeGlobalsModule(ctx llvm.Context, globals map[string]map[string]string, machine llvm.TargetMachine) llvm.Module { + mod := ctx.NewModule("cmdline-globals") + targetData := machine.CreateTargetData() + defer targetData.Dispose() + mod.SetDataLayout(targetData.String()) + + stringType := ctx.StructCreateNamed("runtime._string") + uintptrType := ctx.IntType(targetData.PointerSize() * 8) + stringType.StructSetBody([]llvm.Type{ + llvm.PointerType(ctx.Int8Type(), 0), + uintptrType, + }, false) + var pkgPaths []string for pkgPath := range globals { pkgPaths = append(pkgPaths, pkgPath) @@ -1201,24 +1220,6 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro for _, name := range names { value := pkg[name] globalName := pkgPath + "." + name - global := mod.NamedGlobal(globalName) - if global.IsNil() || !global.Initializer().IsNil() { - // The global either does not exist (optimized away?) or has - // some value, in which case it has already been initialized at - // package init time. - continue - } - - // A strin is a {ptr, len} pair. We need these types to build the - // initializer. - initializerType := global.GlobalValueType() - if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" { - return fmt.Errorf("%s: not a string", globalName) - } - elementTypes := initializerType.StructElementTypes() - if len(elementTypes) != 2 { - return fmt.Errorf("%s: not a string", globalName) - } // Create a buffer for the string contents. bufInitializer := mod.Context().ConstString(value, false) @@ -1229,22 +1230,20 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro buf.SetLinkage(llvm.PrivateLinkage) // Create the string value, which is a {ptr, len} pair. - zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false) - ptr := llvm.ConstGEP(bufInitializer.Type(), buf, []llvm.Value{zero, zero}) - if ptr.Type() != elementTypes[0] { - return fmt.Errorf("%s: not a string", globalName) - } - length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false) - initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{ - ptr, + length := llvm.ConstInt(uintptrType, uint64(len(value)), false) + initializer := llvm.ConstNamedStruct(stringType, []llvm.Value{ + buf, length, }) - // Set the initializer. No initializer should be set at this point. + // Create the string global. + global := llvm.AddGlobal(mod, stringType, globalName) global.SetInitializer(initializer) + global.SetAlignment(targetData.PrefTypeAlignment(stringType)) } } - return nil + + return mod } // functionStackSizes keeps stack size information about a single function From d5c70a1cd361ff46214c21200ffb6503aced2811 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 12:43:53 -0800 Subject: [PATCH 18/45] reflect, internal/reflectlite: embed reflectlite types into reflect types --- .../reflectlite}/deepequal.go | 4 +- .../reflectlite/endian_big.go} | 2 +- .../reflectlite/endian_little.go} | 2 +- src/{reflect => internal/reflectlite}/intw.go | 2 +- .../reflectlite}/intw_avr.go | 2 +- .../reflectlite}/intw_test.go | 2 +- src/internal/reflectlite/reflect.go | 2 + src/internal/reflectlite/strconv.go | 347 +++ src/internal/reflectlite/swapper.go | 40 + src/internal/reflectlite/type.go | 1130 +++++++++ src/internal/reflectlite/value.go | 2114 +++++++++++++++++ src/internal/reflectlite/visiblefields.go | 105 + src/reflect/swapper.go | 37 +- src/reflect/type.go | 1143 +-------- src/reflect/value.go | 2007 +--------------- src/reflect/visiblefields.go | 104 +- 16 files changed, 3861 insertions(+), 3182 deletions(-) rename src/{reflect => internal/reflectlite}/deepequal.go (99%) rename src/{reflect/endian-big.go => internal/reflectlite/endian_big.go} (98%) rename src/{reflect/endian-little.go => internal/reflectlite/endian_little.go} (98%) rename src/{reflect => internal/reflectlite}/intw.go (92%) rename src/{reflect => internal/reflectlite}/intw_avr.go (92%) rename src/{reflect => internal/reflectlite}/intw_test.go (97%) create mode 100644 src/internal/reflectlite/strconv.go create mode 100644 src/internal/reflectlite/swapper.go create mode 100644 src/internal/reflectlite/type.go create mode 100644 src/internal/reflectlite/value.go create mode 100644 src/internal/reflectlite/visiblefields.go diff --git a/src/reflect/deepequal.go b/src/internal/reflectlite/deepequal.go similarity index 99% rename from src/reflect/deepequal.go rename to src/internal/reflectlite/deepequal.go index 18a728458c..436dc007f8 100644 --- a/src/reflect/deepequal.go +++ b/src/internal/reflectlite/deepequal.go @@ -4,7 +4,7 @@ // Deep equality test via reflection -package reflect +package reflectlite import "unsafe" @@ -15,7 +15,7 @@ import "unsafe" type visit struct { a1 unsafe.Pointer a2 unsafe.Pointer - typ *rawType + typ *RawType } // Tests for deep equality using reflected types. The map argument tracks diff --git a/src/reflect/endian-big.go b/src/internal/reflectlite/endian_big.go similarity index 98% rename from src/reflect/endian-big.go rename to src/internal/reflectlite/endian_big.go index 94951e200d..5ad792dcc3 100644 --- a/src/reflect/endian-big.go +++ b/src/internal/reflectlite/endian_big.go @@ -1,6 +1,6 @@ //go:build mips -package reflect +package reflectlite import "unsafe" diff --git a/src/reflect/endian-little.go b/src/internal/reflectlite/endian_little.go similarity index 98% rename from src/reflect/endian-little.go rename to src/internal/reflectlite/endian_little.go index 7d7e30059d..035ec01d8b 100644 --- a/src/reflect/endian-little.go +++ b/src/internal/reflectlite/endian_little.go @@ -1,6 +1,6 @@ //go:build !mips -package reflect +package reflectlite import "unsafe" diff --git a/src/reflect/intw.go b/src/internal/reflectlite/intw.go similarity index 92% rename from src/reflect/intw.go rename to src/internal/reflectlite/intw.go index 20fbd4341e..4dd197cefc 100644 --- a/src/reflect/intw.go +++ b/src/internal/reflectlite/intw.go @@ -1,6 +1,6 @@ //go:build !avr -package reflect +package reflectlite // intw is an integer type, used in places where an int is typically required, // except architectures where the size of an int != word size. diff --git a/src/reflect/intw_avr.go b/src/internal/reflectlite/intw_avr.go similarity index 92% rename from src/reflect/intw_avr.go rename to src/internal/reflectlite/intw_avr.go index 8f294eeee2..24b4377777 100644 --- a/src/reflect/intw_avr.go +++ b/src/internal/reflectlite/intw_avr.go @@ -1,6 +1,6 @@ //go:build avr -package reflect +package reflectlite // intw is an integer type, used in places where an int is typically required, // except architectures where the size of an int != word size. diff --git a/src/reflect/intw_test.go b/src/internal/reflectlite/intw_test.go similarity index 97% rename from src/reflect/intw_test.go rename to src/internal/reflectlite/intw_test.go index 1014a9ae4e..b235b88f4c 100644 --- a/src/reflect/intw_test.go +++ b/src/internal/reflectlite/intw_test.go @@ -1,6 +1,6 @@ //go:build !avr -package reflect_test +package reflectlite_test import ( "reflect" diff --git a/src/internal/reflectlite/reflect.go b/src/internal/reflectlite/reflect.go index 938e56a556..df6abd3aa7 100644 --- a/src/internal/reflectlite/reflect.go +++ b/src/internal/reflectlite/reflect.go @@ -1,3 +1,5 @@ +//go:build never + package reflectlite import "reflect" diff --git a/src/internal/reflectlite/strconv.go b/src/internal/reflectlite/strconv.go new file mode 100644 index 0000000000..deabe4a5c7 --- /dev/null +++ b/src/internal/reflectlite/strconv.go @@ -0,0 +1,347 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflectlite + +import ( + "unicode/utf8" +) + +// errSyntax indicates that a value does not have the right syntax for the target type. +var errSyntax = badSyntax{} + +type badSyntax struct{} + +func (badSyntax) Error() string { + return "invalid syntax" +} + +func unhex(b byte) (v rune, ok bool) { + c := rune(b) + switch { + case '0' <= c && c <= '9': + return c - '0', true + case 'a' <= c && c <= 'f': + return c - 'a' + 10, true + case 'A' <= c && c <= 'F': + return c - 'A' + 10, true + } + return +} + +const ( + lowerhex = "0123456789abcef" +) + +// unquoteChar decodes the first character or byte in the escaped string +// or character literal represented by the string s. +// It returns four values: +// +// 1. value, the decoded Unicode code point or byte value; +// 2. multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation; +// 3. tail, the remainder of the string after the character; and +// 4. an error that will be nil if the character is syntactically valid. +// +// The second argument, quote, specifies the type of literal being parsed +// and therefore which escaped quote character is permitted. +// If set to a single quote, it permits the sequence \' and disallows unescaped '. +// If set to a double quote, it permits \" and disallows unescaped ". +// If set to zero, it does not permit either escape and allows both quote characters to appear unescaped. +func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) { + // easy cases + if len(s) == 0 { + err = errSyntax + return + } + switch c := s[0]; { + case c == quote && (quote == '\'' || quote == '"'): + err = errSyntax + return + case c >= utf8.RuneSelf: + r, size := utf8.DecodeRuneInString(s) + return r, true, s[size:], nil + case c != '\\': + return rune(s[0]), false, s[1:], nil + } + + // hard case: c is backslash + if len(s) <= 1 { + err = errSyntax + return + } + c := s[1] + s = s[2:] + + switch c { + case 'a': + value = '\a' + case 'b': + value = '\b' + case 'f': + value = '\f' + case 'n': + value = '\n' + case 'r': + value = '\r' + case 't': + value = '\t' + case 'v': + value = '\v' + case 'x', 'u', 'U': + n := 0 + switch c { + case 'x': + n = 2 + case 'u': + n = 4 + case 'U': + n = 8 + } + var v rune + if len(s) < n { + err = errSyntax + return + } + for j := 0; j < n; j++ { + x, ok := unhex(s[j]) + if !ok { + err = errSyntax + return + } + v = v<<4 | x + } + s = s[n:] + if c == 'x' { + // single-byte string, possibly not UTF-8 + value = v + break + } + if v > utf8.MaxRune { + err = errSyntax + return + } + value = v + multibyte = true + case '0', '1', '2', '3', '4', '5', '6', '7': + v := rune(c) - '0' + if len(s) < 2 { + err = errSyntax + return + } + for j := 0; j < 2; j++ { // one digit already; two more + x := rune(s[j]) - '0' + if x < 0 || x > 7 { + err = errSyntax + return + } + v = (v << 3) | x + } + s = s[2:] + if v > 255 { + err = errSyntax + return + } + value = v + case '\\': + value = '\\' + case '\'', '"': + if c != quote { + err = errSyntax + return + } + value = rune(c) + default: + err = errSyntax + return + } + tail = s + return +} + +// unquote interprets s as a single-quoted, double-quoted, +// or backquoted Go string literal, returning the string value +// that s quotes. (If s is single-quoted, it would be a Go +// character literal; unquote returns the corresponding +// one-character string.) +func unquote(s string) (string, error) { + n := len(s) + if n < 2 { + return "", errSyntax + } + quote := s[0] + if quote != s[n-1] { + return "", errSyntax + } + s = s[1 : n-1] + + if quote == '`' { + if contains(s, '`') { + return "", errSyntax + } + if contains(s, '\r') { + // -1 because we know there is at least one \r to remove. + buf := make([]byte, 0, len(s)-1) + for i := 0; i < len(s); i++ { + if s[i] != '\r' { + buf = append(buf, s[i]) + } + } + return string(buf), nil + } + return s, nil + } + if quote != '"' && quote != '\'' { + return "", errSyntax + } + if contains(s, '\n') { + return "", errSyntax + } + + // Is it trivial? Avoid allocation. + if !contains(s, '\\') && !contains(s, quote) { + switch quote { + case '"': + if utf8.ValidString(s) { + return s, nil + } + case '\'': + r, size := utf8.DecodeRuneInString(s) + if size == len(s) && (r != utf8.RuneError || size != 1) { + return s, nil + } + } + } + + var runeTmp [utf8.UTFMax]byte + buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations. + for len(s) > 0 { + c, multibyte, ss, err := unquoteChar(s, quote) + if err != nil { + return "", err + } + s = ss + if c < utf8.RuneSelf || !multibyte { + buf = append(buf, byte(c)) + } else { + n := utf8.EncodeRune(runeTmp[:], c) + buf = append(buf, runeTmp[:n]...) + } + if quote == '\'' && len(s) != 0 { + // single-quoted must be single character + return "", errSyntax + } + } + return string(buf), nil +} + +func quote(s string) string { + buf := make([]byte, 0, 3*len(s)/2) + const quote = '"' + + buf = append(buf, quote) + for width := 0; len(s) > 0; s = s[width:] { + r := rune(s[0]) + width = 1 + if r >= utf8.RuneSelf { + r, width = utf8.DecodeRuneInString(s) + } + if width == 1 && r == utf8.RuneError { + buf = append(buf, `\x`...) + buf = append(buf, lowerhex[s[0]>>4]) + buf = append(buf, lowerhex[s[0]&0xF]) + continue + } + buf = appendEscapedRune(buf, r) + } + buf = append(buf, quote) + return string(buf) +} + +func appendEscapedRune(buf []byte, r rune) []byte { + + const quote = '"' + + var runeTmp [utf8.UTFMax]byte + if r == rune(quote) || r == '\\' { // always backslashed + buf = append(buf, '\\') + buf = append(buf, byte(r)) + return buf + } + if isPrint(r) { + n := utf8.EncodeRune(runeTmp[:], r) + buf = append(buf, runeTmp[:n]...) + return buf + } + switch r { + case '\a': + buf = append(buf, `\a`...) + case '\b': + buf = append(buf, `\b`...) + case '\f': + buf = append(buf, `\f`...) + case '\n': + buf = append(buf, `\n`...) + case '\r': + buf = append(buf, `\r`...) + case '\t': + buf = append(buf, `\t`...) + case '\v': + buf = append(buf, `\v`...) + default: + switch { + case r < ' ' || r == 0x7f: + buf = append(buf, `\x`...) + buf = append(buf, lowerhex[byte(r)>>4]) + buf = append(buf, lowerhex[byte(r)&0xF]) + case !utf8.ValidRune(r): + r = 0xFFFD + fallthrough + case r < 0x10000: + buf = append(buf, `\u`...) + for s := 12; s >= 0; s -= 4 { + buf = append(buf, lowerhex[r>>uint(s)&0xF]) + } + default: + buf = append(buf, `\U`...) + for s := 28; s >= 0; s -= 4 { + buf = append(buf, lowerhex[r>>uint(s)&0xF]) + } + } + } + return buf +} + +// This is only used for struct tags. Assume +func isPrint(r rune) bool { + if r <= 0xFF { + if 0x20 <= r && r <= 0x7E { + // All the ASCII is printable from space through DEL-1. + return true + } + if 0xA1 <= r && r <= 0xFF { + // Similarly for ¡ through ÿ... + return r != 0xAD // ...except for the bizarre soft hyphen. + } + return false + } + + // TinyGo: Skip all other unicode processing + return false +} + +// contains reports whether the string contains the byte c. +func contains(s string, c byte) bool { + return indexByteString(s, c) != -1 +} + +// Index finds the index of the first instance of the specified byte in the string. +// If the byte is not found, this returns -1. +func indexByteString(s string, c byte) int { + for i := 0; i < len(s); i++ { + if s[i] == c { + return i + } + } + return -1 +} diff --git a/src/internal/reflectlite/swapper.go b/src/internal/reflectlite/swapper.go new file mode 100644 index 0000000000..b8d85a9442 --- /dev/null +++ b/src/internal/reflectlite/swapper.go @@ -0,0 +1,40 @@ +package reflectlite + +import "unsafe" + +// Some of code here has been copied from the Go sources: +// https://github.com/golang/go/blob/go1.15.2/src/reflect/swapper.go +// It has the following copyright note: +// +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +func Swapper(slice interface{}) func(i, j int) { + v := ValueOf(slice) + if v.Kind() != Slice { + panic(&ValueError{Method: "Swapper"}) + } + + // Just return Nop func if nothing to swap. + if v.Len() < 2 { + return func(i, j int) {} + } + + typ := v.typecode.Elem() + size := typ.Size() + + header := (*sliceHeader)(v.value) + tmp := unsafe.Pointer(&make([]byte, size)[0]) + + return func(i, j int) { + if uint(i) >= uint(header.len) || uint(j) >= uint(header.len) { + panic("reflect: slice index out of range") + } + val1 := unsafe.Add(header.data, uintptr(i)*size) + val2 := unsafe.Add(header.data, uintptr(j)*size) + memcpy(tmp, val1, size) + memcpy(val1, val2, size) + memcpy(val2, tmp, size) + } +} diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go new file mode 100644 index 0000000000..b81468de40 --- /dev/null +++ b/src/internal/reflectlite/type.go @@ -0,0 +1,1130 @@ +package reflectlite + +import ( + "internal/gclayout" + "internal/itoa" + "unsafe" +) + +// Flags stored in the first byte of the struct field byte array. Must be kept +// up to date with compiler/interface.go. +const ( + structFieldFlagAnonymous = 1 << iota + structFieldFlagHasTag + structFieldFlagIsExported + structFieldFlagIsEmbedded +) + +type Kind uint8 + +// Copied from reflect/type.go +// https://golang.org/src/reflect/type.go?s=8302:8316#L217 +// These constants must match basicTypes and the typeKind* constants in +// compiler/interface.go +const ( + Invalid Kind = iota + Bool + Int + Int8 + Int16 + Int32 + Int64 + Uint + Uint8 + Uint16 + Uint32 + Uint64 + Uintptr + Float32 + Float64 + Complex64 + Complex128 + String + UnsafePointer + Chan + Interface + Pointer + Slice + Array + Func + Map + Struct +) + +// Ptr is the old name for the Pointer kind. +const Ptr = Pointer + +func (k Kind) String() string { + switch k { + case Invalid: + return "invalid" + case Bool: + return "bool" + case Int: + return "int" + case Int8: + return "int8" + case Int16: + return "int16" + case Int32: + return "int32" + case Int64: + return "int64" + case Uint: + return "uint" + case Uint8: + return "uint8" + case Uint16: + return "uint16" + case Uint32: + return "uint32" + case Uint64: + return "uint64" + case Uintptr: + return "uintptr" + case Float32: + return "float32" + case Float64: + return "float64" + case Complex64: + return "complex64" + case Complex128: + return "complex128" + case String: + return "string" + case UnsafePointer: + return "unsafe.Pointer" + case Chan: + return "chan" + case Interface: + return "interface" + case Pointer: + return "ptr" + case Slice: + return "slice" + case Array: + return "array" + case Func: + return "func" + case Map: + return "map" + case Struct: + return "struct" + default: + return "kind" + itoa.Itoa(int(int8(k))) + } +} + +// Copied from reflect/type.go +// https://go.dev/src/reflect/type.go?#L348 + +// ChanDir represents a channel type's direction. +type ChanDir int + +const ( + RecvDir ChanDir = 1 << iota // <-chan + SendDir // chan<- + BothDir = RecvDir | SendDir // chan +) + +// Type represents the minimal interface for a Go type. +type Type interface { + // These should match the reflectlite.Type implementation in Go. + AssignableTo(u Type) bool + Comparable() bool + Elem() Type + Implements(u Type) bool + Kind() Kind + Name() string + PkgPath() string + Size() uintptr + String() string + + // Additional methods shared with reflect.Type. + Align() int + Field(i int) StructField + Key() Type + Len() int + NumField() int + NumMethod() int +} + +// Constants for the 'meta' byte. +const ( + kindMask = 31 // mask to apply to the meta byte to get the Kind value + flagNamed = 32 // flag that is set if this is a named type + flagComparable = 64 // flag that is set if this type is comparable + flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm +) + +// The base type struct. All type structs start with this. +type RawType struct { + meta uint8 // metadata byte, contains kind and flags (see constants above) +} + +// All types that have an element type: named, chan, slice, array, map (but not +// pointer because it doesn't have ptrTo). +type elemType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType +} + +type ptrType struct { + RawType + numMethod uint16 + elem *RawType +} + +type interfaceType struct { + RawType + ptrTo *RawType + // TODO: methods +} + +type arrayType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType + arrayLen uintptr + slicePtr *RawType +} + +type mapType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType + key *RawType +} + +type namedType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType + pkg *byte + name [1]byte +} + +// Type for struct types. The numField value is intentionally put before ptrTo +// for better struct packing on 32-bit and 64-bit architectures. On these +// architectures, the ptrTo field still has the same offset as in all the other +// type structs. +// The fields array isn't necessarily 1 structField long, instead it is as long +// as numFields. The array is given a length of 1 to satisfy the Go type +// checker. +type structType struct { + RawType + numMethod uint16 + ptrTo *RawType + pkgpath *byte + size uint32 + numField uint16 + fields [1]structField // the remaining fields are all of type structField +} + +type structField struct { + fieldType *RawType + data unsafe.Pointer // various bits of information, packed in a byte array +} + +// Equivalent to (go/types.Type).Underlying(): if this is a named type return +// the underlying type, else just return the type itself. +func (t *RawType) underlying() *RawType { + if t.isNamed() { + return (*elemType)(unsafe.Pointer(t)).elem + } + return t +} + +func (t *RawType) ptrtag() uintptr { + return uintptr(unsafe.Pointer(t)) & 0b11 +} + +func (t *RawType) isNamed() bool { + if tag := t.ptrtag(); tag != 0 { + return false + } + return t.meta&flagNamed != 0 +} + +func TypeOf(i interface{}) Type { + if i == nil { + return nil + } + typecode, _ := decomposeInterface(i) + return (*RawType)(typecode) +} + +func PtrTo(t Type) Type { return PointerTo(t) } + +func PointerTo(t Type) Type { + return pointerTo(t.(*RawType)) +} + +func pointerTo(t *RawType) *RawType { + if t.isNamed() { + return (*elemType)(unsafe.Pointer(t)).ptrTo + } + + switch t.Kind() { + case Pointer: + if tag := t.ptrtag(); tag < 3 { + return (*RawType)(unsafe.Add(unsafe.Pointer(t), 1)) + } + + // TODO(dgryski): This is blocking https://github.com/tinygo-org/tinygo/issues/3131 + // We need to be able to create types that match existing types to prevent typecode equality. + panic("reflect: cannot make *****T type") + case Struct: + return (*structType)(unsafe.Pointer(t)).ptrTo + default: + return (*elemType)(unsafe.Pointer(t)).ptrTo + } +} + +func (t *RawType) String() string { + if t.isNamed() { + s := t.name() + if s[0] == '.' { + return s[1:] + } + return s + } + + switch t.Kind() { + case Chan: + elem := t.elem().String() + switch t.ChanDir() { + case SendDir: + return "chan<- " + elem + case RecvDir: + return "<-chan " + elem + case BothDir: + if elem[0] == '<' { + // typ is recv chan, need parentheses as "<-" associates with leftmost + // chan possible, see: + // * https://golang.org/ref/spec#Channel_types + // * https://github.com/golang/go/issues/39897 + return "chan (" + elem + ")" + } + return "chan " + elem + } + + case Pointer: + return "*" + t.elem().String() + case Slice: + return "[]" + t.elem().String() + case Array: + return "[" + itoa.Itoa(t.Len()) + "]" + t.elem().String() + case Map: + return "map[" + t.key().String() + "]" + t.elem().String() + case Struct: + numField := t.NumField() + if numField == 0 { + return "struct {}" + } + s := "struct {" + for i := 0; i < numField; i++ { + f := t.rawField(i) + s += " " + f.Name + " " + f.Type.String() + if f.Tag != "" { + s += " " + quote(string(f.Tag)) + } + // every field except the last needs a semicolon + if i < numField-1 { + s += ";" + } + } + s += " }" + return s + case Interface: + // TODO(dgryski): Needs actual method set info + return "interface {}" + default: + return t.Kind().String() + } + + return t.Kind().String() +} + +func (t *RawType) Kind() Kind { + if t == nil { + return Invalid + } + + if tag := t.ptrtag(); tag != 0 { + return Pointer + } + + return Kind(t.meta & kindMask) +} + +var ( + errTypeElem = &TypeError{"Elem"} + errTypeKey = &TypeError{"Key"} + errTypeField = &TypeError{"Field"} + errTypeBits = &TypeError{"Bits"} + errTypeLen = &TypeError{"Len"} + errTypeNumField = &TypeError{"NumField"} + errTypeChanDir = &TypeError{"ChanDir"} + errTypeFieldByName = &TypeError{"FieldByName"} + errTypeFieldByIndex = &TypeError{"FieldByIndex"} +) + +// Elem returns the element type for channel, slice and array types, the +// pointed-to value for pointer types, and the key type for map types. +func (t *RawType) Elem() Type { + return t.elem() +} + +func (t *RawType) elem() *RawType { + if tag := t.ptrtag(); tag != 0 { + return (*RawType)(unsafe.Add(unsafe.Pointer(t), -1)) + } + + underlying := t.underlying() + switch underlying.Kind() { + case Pointer: + return (*ptrType)(unsafe.Pointer(underlying)).elem + case Chan, Slice, Array, Map: + return (*elemType)(unsafe.Pointer(underlying)).elem + default: + panic(errTypeElem) + } +} + +func (t *RawType) key() *RawType { + underlying := t.underlying() + if underlying.Kind() != Map { + panic(errTypeKey) + } + return (*mapType)(unsafe.Pointer(underlying)).key +} + +// Field returns the type of the i'th field of this struct type. It panics if t +// is not a struct type. +func (t *RawType) Field(i int) StructField { + field := t.rawField(i) + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: []int{i}, + } +} + +func rawStructFieldFromPointer(descriptor *structType, fieldType *RawType, data unsafe.Pointer, flagsByte uint8, name string, offset uint32) rawStructField { + // Read the field tag, if there is one. + var tag string + if flagsByte&structFieldFlagHasTag != 0 { + data = unsafe.Add(data, 1) // C: data+1 + tagLen := uintptr(*(*byte)(data)) + data = unsafe.Add(data, 1) // C: data+1 + tag = *(*string)(unsafe.Pointer(&stringHeader{ + data: data, + len: tagLen, + })) + } + + // Set the PkgPath to some (arbitrary) value if the package path is not + // exported. + pkgPath := "" + if flagsByte&structFieldFlagIsExported == 0 { + // This field is unexported. + pkgPath = readStringZ(unsafe.Pointer(descriptor.pkgpath)) + } + + return rawStructField{ + Name: name, + PkgPath: pkgPath, + Type: fieldType, + Tag: StructTag(tag), + Anonymous: flagsByte&structFieldFlagAnonymous != 0, + Offset: uintptr(offset), + } +} + +// rawField returns nearly the same value as Field but without converting the +// Type member to an interface. +// +// For internal use only. +func (t *RawType) rawField(n int) rawStructField { + if t.Kind() != Struct { + panic(errTypeField) + } + descriptor := (*structType)(unsafe.Pointer(t.underlying())) + if uint(n) >= uint(descriptor.numField) { + panic("reflect: field index out of range") + } + + // Iterate over all the fields to calculate the offset. + // This offset could have been stored directly in the array (to make the + // lookup faster), but by calculating it on-the-fly a bit of storage can be + // saved. + field := (*structField)(unsafe.Add(unsafe.Pointer(&descriptor.fields[0]), uintptr(n)*unsafe.Sizeof(structField{}))) + data := field.data + + // Read some flags of this field, like whether the field is an embedded + // field. See structFieldFlagAnonymous and similar flags. + flagsByte := *(*byte)(data) + data = unsafe.Add(data, 1) + offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) + data = unsafe.Add(data, lenOffs) + + name := readStringZ(data) + data = unsafe.Add(data, len(name)) + + return rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset) +} + +// rawFieldByNameFunc returns nearly the same value as FieldByNameFunc but without converting the +// Type member to an interface. +// +// For internal use only. +func (t *RawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, []int, bool) { + if t.Kind() != Struct { + panic(errTypeField) + } + + type fieldWalker struct { + t *RawType + index []int + } + + queue := make([]fieldWalker, 0, 4) + queue = append(queue, fieldWalker{t, nil}) + + for len(queue) > 0 { + type result struct { + r rawStructField + index []int + } + + var found []result + var nextlevel []fieldWalker + + // For all the structs at this level.. + for _, ll := range queue { + // Iterate over all the fields looking for the matching name + // Also calculate field offset. + + descriptor := (*structType)(unsafe.Pointer(ll.t.underlying())) + field := &descriptor.fields[0] + + for i := uint16(0); i < descriptor.numField; i++ { + data := field.data + + // Read some flags of this field, like whether the field is an embedded + // field. See structFieldFlagAnonymous and similar flags. + flagsByte := *(*byte)(data) + data = unsafe.Add(data, 1) + + offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) + data = unsafe.Add(data, lenOffs) + + name := readStringZ(data) + data = unsafe.Add(data, len(name)) + if match(name) { + found = append(found, result{ + rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset), + append(ll.index[:len(ll.index):len(ll.index)], int(i)), + }) + } + + structOrPtrToStruct := field.fieldType.Kind() == Struct || (field.fieldType.Kind() == Pointer && field.fieldType.elem().Kind() == Struct) + if flagsByte&structFieldFlagIsEmbedded == structFieldFlagIsEmbedded && structOrPtrToStruct { + embedded := field.fieldType + if embedded.Kind() == Pointer { + embedded = embedded.elem() + } + + nextlevel = append(nextlevel, fieldWalker{ + t: embedded, + index: append(ll.index[:len(ll.index):len(ll.index)], int(i)), + }) + } + + // update offset/field pointer if there *is* a next field + if i < descriptor.numField-1 { + // Increment pointer to the next field. + field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{}))) + } + } + } + + // found multiple hits at this level + if len(found) > 1 { + return rawStructField{}, nil, false + } + + // found the field we were looking for + if len(found) == 1 { + r := found[0] + return r.r, r.index, true + } + + // else len(found) == 0, move on to the next level + queue = append(queue[:0], nextlevel...) + } + + // didn't find it + return rawStructField{}, nil, false +} + +// Bits returns the number of bits that this type uses. It is only valid for +// arithmetic types (integers, floats, and complex numbers). For other types, it +// will panic. +func (t *RawType) Bits() int { + kind := t.Kind() + if kind >= Int && kind <= Complex128 { + return int(t.Size()) * 8 + } + panic(errTypeBits) +} + +// Len returns the number of elements in this array. It panics of the type kind +// is not Array. +func (t *RawType) Len() int { + if t.Kind() != Array { + panic(errTypeLen) + } + + return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen) +} + +// NumField returns the number of fields of a struct type. It panics for other +// type kinds. +func (t *RawType) NumField() int { + if t.Kind() != Struct { + panic(errTypeNumField) + } + return int((*structType)(unsafe.Pointer(t.underlying())).numField) +} + +// Size returns the size in bytes of a given type. It is similar to +// unsafe.Sizeof. +func (t *RawType) Size() uintptr { + switch t.Kind() { + case Bool, Int8, Uint8: + return 1 + case Int16, Uint16: + return 2 + case Int32, Uint32: + return 4 + case Int64, Uint64: + return 8 + case Int, Uint: + return unsafe.Sizeof(int(0)) + case Uintptr: + return unsafe.Sizeof(uintptr(0)) + case Float32: + return 4 + case Float64: + return 8 + case Complex64: + return 8 + case Complex128: + return 16 + case String: + return unsafe.Sizeof("") + case UnsafePointer, Chan, Map, Pointer: + return unsafe.Sizeof(uintptr(0)) + case Slice: + return unsafe.Sizeof([]int{}) + case Interface: + return unsafe.Sizeof(interface{}(nil)) + case Func: + var f func() + return unsafe.Sizeof(f) + case Array: + return t.elem().Size() * uintptr(t.Len()) + case Struct: + u := t.underlying() + return uintptr((*structType)(unsafe.Pointer(u)).size) + default: + panic("unimplemented: size of type") + } +} + +// Align returns the alignment of this type. It is similar to calling +// unsafe.Alignof. +func (t *RawType) Align() int { + switch t.Kind() { + case Bool, Int8, Uint8: + return int(unsafe.Alignof(int8(0))) + case Int16, Uint16: + return int(unsafe.Alignof(int16(0))) + case Int32, Uint32: + return int(unsafe.Alignof(int32(0))) + case Int64, Uint64: + return int(unsafe.Alignof(int64(0))) + case Int, Uint: + return int(unsafe.Alignof(int(0))) + case Uintptr: + return int(unsafe.Alignof(uintptr(0))) + case Float32: + return int(unsafe.Alignof(float32(0))) + case Float64: + return int(unsafe.Alignof(float64(0))) + case Complex64: + return int(unsafe.Alignof(complex64(0))) + case Complex128: + return int(unsafe.Alignof(complex128(0))) + case String: + return int(unsafe.Alignof("")) + case UnsafePointer, Chan, Map, Pointer: + return int(unsafe.Alignof(uintptr(0))) + case Slice: + return int(unsafe.Alignof([]int(nil))) + case Interface: + return int(unsafe.Alignof(interface{}(nil))) + case Func: + var f func() + return int(unsafe.Alignof(f)) + case Struct: + numField := t.NumField() + alignment := 1 + for i := 0; i < numField; i++ { + fieldAlignment := t.rawField(i).Type.Align() + if fieldAlignment > alignment { + alignment = fieldAlignment + } + } + return alignment + case Array: + return t.elem().Align() + default: + panic("unimplemented: alignment of type") + } +} + +func (r *RawType) gcLayout() unsafe.Pointer { + kind := r.Kind() + + if kind < String { + return gclayout.NoPtrs + } + + switch kind { + case Pointer, UnsafePointer, Chan, Map: + return gclayout.Pointer + case String: + return gclayout.String + case Slice: + return gclayout.Slice + } + + // Unknown (for now); let the conservative pointer scanning handle it + return nil +} + +// FieldAlign returns the alignment if this type is used in a struct field. It +// is currently an alias for Align() but this might change in the future. +func (t *RawType) FieldAlign() int { + return t.Align() +} + +// AssignableTo returns whether a value of type t can be assigned to a variable +// of type u. +func (t *RawType) AssignableTo(u Type) bool { + if t == u.(*RawType) { + return true + } + + if t.underlying() == u.(*RawType).underlying() && (!t.isNamed() || !u.(*RawType).isNamed()) { + return true + } + + if u.Kind() == Interface && u.NumMethod() == 0 { + return true + } + + if u.Kind() == Interface { + panic("reflect: unimplemented: AssignableTo with interface") + } + return false +} + +func (t *RawType) Implements(u Type) bool { + if u.Kind() != Interface { + panic("reflect: non-interface type passed to Type.Implements") + } + return t.AssignableTo(u) +} + +// Comparable returns whether values of this type can be compared to each other. +func (t *RawType) Comparable() bool { + return (t.meta & flagComparable) == flagComparable +} + +// isBinary returns if the hashmapAlgorithmBinary functions can be used on this type +func (t *RawType) isBinary() bool { + return (t.meta & flagIsBinary) == flagIsBinary +} + +func (t *RawType) ChanDir() ChanDir { + if t.Kind() != Chan { + panic(errTypeChanDir) + } + + dir := int((*elemType)(unsafe.Pointer(t)).numMethod) + + // nummethod is overloaded for channel to store channel direction + return ChanDir(dir) +} + +func (t *RawType) NumMethod() int { + + if t.isNamed() { + return int((*namedType)(unsafe.Pointer(t)).numMethod) + } + + switch t.Kind() { + case Pointer: + return int((*ptrType)(unsafe.Pointer(t)).numMethod) + case Struct: + return int((*structType)(unsafe.Pointer(t)).numMethod) + case Interface: + //FIXME: Use len(methods) + return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod() + } + + // Other types have no methods attached. Note we don't panic here. + return 0 +} + +// Read and return a null terminated string starting from data. +func readStringZ(data unsafe.Pointer) string { + start := data + var len uintptr + for *(*byte)(data) != 0 { + len++ + data = unsafe.Add(data, 1) // C: data++ + } + + return *(*string)(unsafe.Pointer(&stringHeader{ + data: start, + len: len, + })) +} + +func (t *RawType) name() string { + ntype := (*namedType)(unsafe.Pointer(t)) + return readStringZ(unsafe.Pointer(&ntype.name[0])) +} + +func (t *RawType) Name() string { + if t.isNamed() { + name := t.name() + for i := 0; i < len(name); i++ { + if name[i] == '.' { + return name[i+1:] + } + } + panic("corrupt name data") + } + + if kind := t.Kind(); kind < UnsafePointer { + return t.Kind().String() + } else if kind == UnsafePointer { + return "Pointer" + } + + return "" +} + +func (t *RawType) Key() Type { + return t.key() +} + +// OverflowComplex reports whether the complex128 x cannot be represented by type t. +// It panics if t's Kind is not Complex64 or Complex128. +func (t RawType) OverflowComplex(x complex128) bool { + k := t.Kind() + switch k { + case Complex64: + return overflowFloat32(real(x)) || overflowFloat32(imag(x)) + case Complex128: + return false + } + panic("reflect: OverflowComplex of non-complex type") +} + +// OverflowFloat reports whether the float64 x cannot be represented by type t. +// It panics if t's Kind is not Float32 or Float64. +func (t RawType) OverflowFloat(x float64) bool { + k := t.Kind() + switch k { + case Float32: + return overflowFloat32(x) + case Float64: + return false + } + panic("reflect: OverflowFloat of non-float type") +} + +// OverflowInt reports whether the int64 x cannot be represented by type t. +// It panics if t's Kind is not Int, Int8, Int16, Int32, or Int64. +func (t RawType) OverflowInt(x int64) bool { + k := t.Kind() + switch k { + case Int, Int8, Int16, Int32, Int64: + bitSize := t.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic("reflect: OverflowInt of non-int type") +} + +// OverflowUint reports whether the uint64 x cannot be represented by type t. +// It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. +func (t RawType) OverflowUint(x uint64) bool { + k := t.Kind() + switch k { + case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: + bitSize := t.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic("reflect: OverflowUint of non-uint type") +} + +func (t *RawType) PkgPath() string { + if t.isNamed() { + ntype := (*namedType)(unsafe.Pointer(t)) + return readStringZ(unsafe.Pointer(ntype.pkg)) + } + + return "" +} + +func (t *RawType) FieldByName(name string) (StructField, bool) { + if t.Kind() != Struct { + panic(errTypeFieldByName) + } + + field, index, ok := t.rawFieldByNameFunc(func(n string) bool { return n == name }) + if !ok { + return StructField{}, false + } + + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: index, + }, true +} + +func (t *RawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { + if t.Kind() != Struct { + panic(TypeError{"FieldByNameFunc"}) + } + + field, index, ok := t.rawFieldByNameFunc(match) + if !ok { + return StructField{}, false + } + + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: index, + }, true +} + +func (t *RawType) FieldByIndex(index []int) StructField { + ftype := t + var field rawStructField + + for _, n := range index { + structOrPtrToStruct := ftype.Kind() == Struct || (ftype.Kind() == Pointer && ftype.elem().Kind() == Struct) + if !structOrPtrToStruct { + panic(errTypeFieldByIndex) + } + + if ftype.Kind() == Pointer { + ftype = ftype.elem() + } + + field = ftype.rawField(n) + ftype = field.Type + } + + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: index, + } +} + +// A StructField describes a single field in a struct. +type StructField struct { + // Name indicates the field name. + Name string + + // PkgPath is the package path where the struct containing this field is + // declared for unexported fields, or the empty string for exported fields. + PkgPath string + + Type Type + Tag StructTag // field tag string + Offset uintptr + Index []int // index sequence for Type.FieldByIndex + Anonymous bool +} + +// IsExported reports whether the field is exported. +func (f StructField) IsExported() bool { + return f.PkgPath == "" +} + +// rawStructField is the same as StructField but with the Type member replaced +// with RawType. For internal use only. Avoiding this conversion to the Type +// interface improves code size in many cases. +type rawStructField struct { + Name string + PkgPath string + Type *RawType + Tag StructTag + Offset uintptr + Anonymous bool +} + +// A StructTag is the tag string in a struct field. +type StructTag string + +// TODO: it would be feasible to do the key/value splitting at compile time, +// avoiding the code size cost of doing it at runtime + +// Get returns the value associated with key in the tag string. +func (tag StructTag) Get(key string) string { + v, _ := tag.Lookup(key) + return v +} + +// Lookup returns the value associated with key in the tag string. +func (tag StructTag) Lookup(key string) (value string, ok bool) { + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + break + } + name := string(tag[:i]) + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + break + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + if key == name { + value, err := unquote(qvalue) + if err != nil { + break + } + return value, true + } + } + return "", false +} + +// TypeError is the error that is used in a panic when invoking a method on a +// type that is not applicable to that type. +type TypeError struct { + Method string +} + +func (e *TypeError) Error() string { + return "reflect: call of reflect.Type." + e.Method + " on invalid type" +} + +func align(offset uintptr, alignment uintptr) uintptr { + return (offset + alignment - 1) &^ (alignment - 1) +} + +func SliceOf(t Type) Type { + panic("unimplemented: reflect.SliceOf()") +} + +func ArrayOf(n int, t Type) Type { + panic("unimplemented: reflect.ArrayOf()") +} + +func StructOf([]StructField) Type { + panic("unimplemented: reflect.StructOf()") +} + +func MapOf(key, value Type) Type { + panic("unimplemented: reflect.MapOf()") +} + +func FuncOf(in, out []Type, variadic bool) Type { + panic("unimplemented: reflect.FuncOf()") +} + +const maxVarintLen32 = 5 + +// encoding/binary.Uvarint, specialized for uint32 +func uvarint32(buf []byte) (uint32, int) { + var x uint32 + var s uint + for i, b := range buf { + if b < 0x80 { + return x | uint32(b)< unsafe.Sizeof(uintptr(0)) { + return int64(*(*int)(v.value)) + } else { + return int64(int(uintptr(v.value))) + } + case Int8: + if v.isIndirect() { + return int64(*(*int8)(v.value)) + } else { + return int64(int8(uintptr(v.value))) + } + case Int16: + if v.isIndirect() { + return int64(*(*int16)(v.value)) + } else { + return int64(int16(uintptr(v.value))) + } + case Int32: + if v.isIndirect() || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) { + return int64(*(*int32)(v.value)) + } else { + return int64(int32(uintptr(v.value))) + } + case Int64: + if v.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) { + return int64(*(*int64)(v.value)) + } else { + return int64(int64(uintptr(v.value))) + } + default: + panic(&ValueError{Method: "Int", Kind: v.Kind()}) + } +} + +// CanUint reports whether Uint can be used without panicking. +func (v Value) CanUint() bool { + switch v.Kind() { + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return true + default: + return false + } +} + +func (v Value) Uint() uint64 { + switch v.Kind() { + case Uintptr: + if v.isIndirect() { + return uint64(*(*uintptr)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint8: + if v.isIndirect() { + return uint64(*(*uint8)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint16: + if v.isIndirect() { + return uint64(*(*uint16)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint: + if v.isIndirect() || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint32: + if v.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint32)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint64: + if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint64)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + default: + panic(&ValueError{Method: "Uint", Kind: v.Kind()}) + } +} + +// CanFloat reports whether Float can be used without panicking. +func (v Value) CanFloat() bool { + switch v.Kind() { + case Float32, Float64: + return true + default: + return false + } +} + +func (v Value) Float32() float32 { + switch v.Kind() { + case Float32: + if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { + // The float is stored as an external value on systems with 16-bit + // pointers. + return *(*float32)(v.value) + } else { + // The float is directly stored in the interface value on systems + // with 32-bit and 64-bit pointers. + return *(*float32)(unsafe.Pointer(&v.value)) + } + + case Float64: + return float32(v.Float()) + + } + + panic(&ValueError{Method: "Float", Kind: v.Kind()}) +} + +func (v Value) Float() float64 { + switch v.Kind() { + case Float32: + if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { + // The float is stored as an external value on systems with 16-bit + // pointers. + return float64(*(*float32)(v.value)) + } else { + // The float is directly stored in the interface value on systems + // with 32-bit and 64-bit pointers. + return float64(*(*float32)(unsafe.Pointer(&v.value))) + } + case Float64: + if v.isIndirect() || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) { + // For systems with 16-bit and 32-bit pointers. + return *(*float64)(v.value) + } else { + // The float is directly stored in the interface value on systems + // with 64-bit pointers. + return *(*float64)(unsafe.Pointer(&v.value)) + } + default: + panic(&ValueError{Method: "Float", Kind: v.Kind()}) + } +} + +// CanComplex reports whether Complex can be used without panicking. +func (v Value) CanComplex() bool { + switch v.Kind() { + case Complex64, Complex128: + return true + default: + return false + } +} + +func (v Value) Complex() complex128 { + switch v.Kind() { + case Complex64: + if v.isIndirect() || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) { + // The complex number is stored as an external value on systems with + // 16-bit and 32-bit pointers. + return complex128(*(*complex64)(v.value)) + } else { + // The complex number is directly stored in the interface value on + // systems with 64-bit pointers. + return complex128(*(*complex64)(unsafe.Pointer(&v.value))) + } + case Complex128: + // This is a 128-bit value, which is always stored as an external value. + // It may be stored in the pointer directly on very uncommon + // architectures with 128-bit pointers, however. + return *(*complex128)(v.value) + default: + panic(&ValueError{Method: "Complex", Kind: v.Kind()}) + } +} + +func (v Value) String() string { + switch v.Kind() { + case String: + // A string value is always bigger than a pointer as it is made of a + // pointer and a length. + return *(*string)(v.value) + default: + // Special case because of the special treatment of .String() in Go. + return "<" + v.typecode.String() + " Value>" + } +} + +func (v Value) Bytes() []byte { + switch v.Kind() { + case Slice: + if v.typecode.elem().Kind() != Uint8 { + panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) + } + return *(*[]byte)(v.value) + + case Array: + v.checkAddressable() + + if v.typecode.elem().Kind() != Uint8 { + panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) + } + + // Small inline arrays are not addressable, so we only have to + // handle addressable arrays which will be stored as pointers + // in v.value + return unsafe.Slice((*byte)(v.value), v.Len()) + } + + panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) +} + +func (v Value) Slice(i, j int) Value { + switch v.Kind() { + case Slice: + hdr := *(*sliceHeader)(v.value) + i, j := uintptr(i), uintptr(j) + + if j < i || hdr.cap < j { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + hdr.len = j - i + hdr.cap = hdr.cap - i + hdr.data = unsafe.Add(hdr.data, i*elemSize) + + return Value{ + typecode: v.typecode, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + + case Array: + v.checkAddressable() + buf, length := buflen(v) + i, j := uintptr(i), uintptr(j) + if j < i || length < j { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + var hdr sliceHeader + hdr.len = j - i + hdr.cap = length - i + hdr.data = unsafe.Add(buf, i*elemSize) + + sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr + return Value{ + typecode: sliceType, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + + case String: + i, j := uintptr(i), uintptr(j) + str := *(*stringHeader)(v.value) + + if j < i || str.len < j { + slicePanic() + } + + hdr := stringHeader{ + data: unsafe.Add(str.data, i), + len: j - i, + } + + return Value{ + typecode: v.typecode, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + } + + panic(&ValueError{Method: "Slice", Kind: v.Kind()}) +} + +func (v Value) Slice3(i, j, k int) Value { + switch v.Kind() { + case Slice: + hdr := *(*sliceHeader)(v.value) + i, j, k := uintptr(i), uintptr(j), uintptr(k) + + if j < i || k < j || hdr.len < k { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + hdr.len = j - i + hdr.cap = k - i + hdr.data = unsafe.Add(hdr.data, i*elemSize) + + return Value{ + typecode: v.typecode, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + + case Array: + v.checkAddressable() + buf, length := buflen(v) + i, j, k := uintptr(i), uintptr(j), uintptr(k) + if j < i || k < j || length < k { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + var hdr sliceHeader + hdr.len = j - i + hdr.cap = k - i + hdr.data = unsafe.Add(buf, i*elemSize) + + sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr + return Value{ + typecode: sliceType, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + } + + panic("unimplemented: (reflect.Value).Slice3()") +} + +//go:linkname maplen runtime.hashmapLen +func maplen(p unsafe.Pointer) int + +//go:linkname chanlen runtime.chanLen +func chanlen(p unsafe.Pointer) int + +// Len returns the length of this value for slices, strings, arrays, channels, +// and maps. For other types, it panics. +func (v Value) Len() int { + switch v.typecode.Kind() { + case Array: + return v.typecode.Len() + case Chan: + return chanlen(v.pointer()) + case Map: + return maplen(v.pointer()) + case Slice: + return int((*sliceHeader)(v.value).len) + case String: + return int((*stringHeader)(v.value).len) + default: + panic(&ValueError{Method: "Len", Kind: v.Kind()}) + } +} + +//go:linkname chancap runtime.chanCap +func chancap(p unsafe.Pointer) int + +// Cap returns the capacity of this value for arrays, channels and slices. +// For other types, it panics. +func (v Value) Cap() int { + switch v.typecode.Kind() { + case Array: + return v.typecode.Len() + case Chan: + return chancap(v.pointer()) + case Slice: + return int((*sliceHeader)(v.value).cap) + default: + panic(&ValueError{Method: "Cap", Kind: v.Kind()}) + } +} + +//go:linkname mapclear runtime.hashmapClear +func mapclear(p unsafe.Pointer) + +// Clear clears the contents of a map or zeros the contents of a slice +// +// It panics if v's Kind is not Map or Slice. +func (v Value) Clear() { + switch v.typecode.Kind() { + case Map: + mapclear(v.pointer()) + case Slice: + hdr := (*sliceHeader)(v.value) + elemSize := v.typecode.underlying().elem().Size() + memzero(hdr.data, elemSize*hdr.len) + default: + panic(&ValueError{Method: "Clear", Kind: v.Kind()}) + } +} + +// NumField returns the number of fields of this struct. It panics for other +// value types. +func (v Value) NumField() int { + return v.typecode.NumField() +} + +func (v Value) Elem() Value { + switch v.Kind() { + case Ptr: + ptr := v.pointer() + if ptr == nil { + return Value{} + } + // Don't copy RO flags + flags := (v.flags & (valueFlagIndirect | valueFlagExported)) | valueFlagIndirect + return Value{ + typecode: v.typecode.elem(), + value: ptr, + flags: flags, + } + case Interface: + typecode, value := decomposeInterface(*(*interface{})(v.value)) + return Value{ + typecode: (*RawType)(typecode), + value: value, + flags: v.flags &^ valueFlagIndirect, + } + default: + panic(&ValueError{Method: "Elem", Kind: v.Kind()}) + } +} + +// Field returns the value of the i'th field of this struct. +func (v Value) Field(i int) Value { + if v.Kind() != Struct { + panic(&ValueError{Method: "Field", Kind: v.Kind()}) + } + structField := v.typecode.rawField(i) + + // Copy flags but clear EmbedRO; we're not an embedded field anymore + flags := v.flags & ^valueFlagEmbedRO + if structField.PkgPath != "" { + // No PkgPath => not exported. + // Clear exported flag even if the parent was exported. + flags &^= valueFlagExported + + // Update the RO flag + if structField.Anonymous { + // Embedded field + flags |= valueFlagEmbedRO + } else { + flags |= valueFlagStickyRO + } + } else { + // Parent field may not have been exported but we are + flags |= valueFlagExported + } + + size := v.typecode.Size() + fieldType := structField.Type + fieldSize := fieldType.Size() + if v.isIndirect() || fieldSize > unsafe.Sizeof(uintptr(0)) { + // v.value was already a pointer to the value and it should stay that + // way. + return Value{ + flags: flags, + typecode: fieldType, + value: unsafe.Add(v.value, structField.Offset), + } + } + + // The fieldSize is smaller than uintptr, which means that the value will + // have to be stored directly in the interface value. + + if fieldSize == 0 { + // The struct field is zero sized. + // This is a rare situation, but because it's undefined behavior + // to shift the size of the value (zeroing the value), handle this + // situation explicitly. + return Value{ + flags: flags, + typecode: fieldType, + value: unsafe.Pointer(nil), + } + } + + if size > unsafe.Sizeof(uintptr(0)) { + // The value was not stored in the interface before but will be + // afterwards, so load the value (from the correct offset) and return + // it. + ptr := unsafe.Add(v.value, structField.Offset) + value := unsafe.Pointer(loadValue(ptr, fieldSize)) + return Value{ + flags: flags &^ valueFlagIndirect, + typecode: fieldType, + value: value, + } + } + + // The value was already stored directly in the interface and it still + // is. Cut out the part of the value that we need. + value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize) + return Value{ + flags: flags, + typecode: fieldType, + value: unsafe.Pointer(value), + } +} + +var uint8Type = TypeOf(uint8(0)).(*RawType) + +func (v Value) Index(i int) Value { + switch v.Kind() { + case Slice: + // Extract an element from the slice. + slice := *(*sliceHeader)(v.value) + if uint(i) >= uint(slice.len) { + panic("reflect: slice index out of range") + } + flags := (v.flags & (valueFlagExported | valueFlagIndirect)) | valueFlagIndirect | v.flags.ro() + elem := Value{ + typecode: v.typecode.elem(), + flags: flags, + } + elem.value = unsafe.Add(slice.data, elem.typecode.Size()*uintptr(i)) // pointer to new value + return elem + case String: + // Extract a character from a string. + // A string is never stored directly in the interface, but always as a + // pointer to the string value. + // Keeping valueFlagExported if set, but don't set valueFlagIndirect + // otherwise CanSet will return true for string elements (which is bad, + // strings are read-only). + s := *(*stringHeader)(v.value) + if uint(i) >= uint(s.len) { + panic("reflect: string index out of range") + } + return Value{ + typecode: uint8Type, + value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Add(s.data, i)))), + flags: v.flags & valueFlagExported, + } + case Array: + // Extract an element from the array. + elemType := v.typecode.elem() + elemSize := elemType.Size() + size := v.typecode.Size() + if size == 0 { + // The element size is 0 and/or the length of the array is 0. + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + } + } + if elemSize > unsafe.Sizeof(uintptr(0)) { + // The resulting value doesn't fit in a pointer so must be + // indirect. Also, because size != 0 this implies that the array + // length must be != 0, and thus that the total size is at least + // elemSize. + addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + value: addr, + } + } + + if size > unsafe.Sizeof(uintptr(0)) || v.isIndirect() { + // The element fits in a pointer, but the array is not stored in the pointer directly. + // Load the value from the pointer. + addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value + value := addr + if !v.isIndirect() { + // Use a pointer to the value (don't load the value) if the + // 'indirect' flag is set. + value = unsafe.Pointer(loadValue(addr, elemSize)) + } + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + value: value, + } + } + + // The value fits in a pointer, so extract it with some shifting and + // masking. + offset := elemSize * uintptr(i) + value := maskAndShift(uintptr(v.value), offset, elemSize) + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + value: unsafe.Pointer(value), + } + default: + panic(&ValueError{Method: "Index", Kind: v.Kind()}) + } +} + +func (v Value) NumMethod() int { + if v.typecode == nil { + panic(&ValueError{Method: "reflect.Value.NumMethod", Kind: Invalid}) + } + return v.typecode.NumMethod() +} + +// OverflowFloat reports whether the float64 x cannot be represented by v's type. +// It panics if v's Kind is not Float32 or Float64. +func (v Value) OverflowFloat(x float64) bool { + k := v.Kind() + switch k { + case Float32: + return overflowFloat32(x) + case Float64: + return false + } + panic(&ValueError{Method: "reflect.Value.OverflowFloat", Kind: v.Kind()}) +} + +func overflowFloat32(x float64) bool { + if x < 0 { + x = -x + } + return math.MaxFloat32 < x && x <= math.MaxFloat64 +} + +func (v Value) MapKeys() []Value { + if v.Kind() != Map { + panic(&ValueError{Method: "MapKeys", Kind: v.Kind()}) + } + + // empty map + if v.Len() == 0 { + return nil + } + + keys := make([]Value, 0, v.Len()) + + it := hashmapNewIterator() + k := New(v.typecode.Key()) + e := New(v.typecode.Elem()) + + keyType := v.typecode.key() + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() + + for hashmapNext(v.pointer(), it, k.value, e.value) { + if shouldUnpackInterface { + intf := *(*interface{})(k.value) + v := ValueOf(intf) + keys = append(keys, v) + } else { + keys = append(keys, k.Elem()) + } + k = New(v.typecode.Key()) + } + + return keys +} + +//go:linkname hashmapStringGet runtime.hashmapStringGet +func hashmapStringGet(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool + +//go:linkname hashmapBinaryGet runtime.hashmapBinaryGet +func hashmapBinaryGet(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool + +//go:linkname hashmapInterfaceGet runtime.hashmapInterfaceGet +func hashmapInterfaceGet(m unsafe.Pointer, key interface{}, value unsafe.Pointer, valueSize uintptr) bool + +func (v Value) MapIndex(key Value) Value { + if v.Kind() != Map { + panic(&ValueError{Method: "MapIndex", Kind: v.Kind()}) + } + + vkey := v.typecode.key() + + // compare key type with actual key type of map + if !key.typecode.AssignableTo(vkey) { + // type error? + panic("reflect.Value.MapIndex: incompatible types for key") + } + + elemType := v.typecode.Elem() + elem := New(elemType) + + if vkey.Kind() == String { + if ok := hashmapStringGet(v.pointer(), *(*string)(key.value), elem.value, elemType.Size()); !ok { + return Value{} + } + return elem.Elem() + } else if vkey.isBinary() { + var keyptr unsafe.Pointer + if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + keyptr = key.value + } else { + keyptr = unsafe.Pointer(&key.value) + } + //TODO(dgryski): zero out padding bytes in key, if any + if ok := hashmapBinaryGet(v.pointer(), keyptr, elem.value, elemType.Size()); !ok { + return Value{} + } + return elem.Elem() + } else { + if ok := hashmapInterfaceGet(v.pointer(), key.Interface(), elem.value, elemType.Size()); !ok { + return Value{} + } + return elem.Elem() + } +} + +//go:linkname hashmapNewIterator runtime.hashmapNewIterator +func hashmapNewIterator() unsafe.Pointer + +//go:linkname hashmapNext runtime.hashmapNext +func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool + +func (v Value) MapRange() *MapIter { + if v.Kind() != Map { + panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) + } + + keyType := v.typecode.key() + + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() + + return &MapIter{ + m: v, + it: hashmapNewIterator(), + unpackKeyInterface: shouldUnpackInterface, + } +} + +type MapIter struct { + m Value + it unsafe.Pointer + key Value + val Value + + valid bool + unpackKeyInterface bool +} + +func (it *MapIter) Key() Value { + if !it.valid { + panic("reflect.MapIter.Key called on invalid iterator") + } + + if it.unpackKeyInterface { + intf := *(*interface{})(it.key.value) + v := ValueOf(intf) + return v + } + + return it.key.Elem() +} + +func (it *MapIter) Value() Value { + if !it.valid { + panic("reflect.MapIter.Value called on invalid iterator") + } + + return it.val.Elem() +} + +func (it *MapIter) Next() bool { + it.key = New(it.m.typecode.Key()) + it.val = New(it.m.typecode.Elem()) + + it.valid = hashmapNext(it.m.pointer(), it.it, it.key.value, it.val.value) + return it.valid +} + +func (v Value) Set(x Value) { + v.checkAddressable() + v.checkRO() + if !x.typecode.AssignableTo(v.typecode) { + panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String()) + } + + if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { + // move the value of x back into the interface, if possible + if x.isIndirect() && x.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { + x.value = unsafe.Pointer(loadValue(x.value, x.typecode.Size())) + } + + intf := composeInterface(unsafe.Pointer(x.typecode), x.value) + x = Value{ + typecode: v.typecode, + value: unsafe.Pointer(&intf), + } + } + + size := v.typecode.Size() + if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() { + storeValue(v.value, size, uintptr(x.value)) + } else { + memcpy(v.value, x.value, size) + } +} + +func (v Value) SetZero() { + v.checkAddressable() + v.checkRO() + size := v.typecode.Size() + memzero(v.value, size) +} + +func (v Value) SetBool(x bool) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Bool: + *(*bool)(v.value) = x + default: + panic(&ValueError{Method: "SetBool", Kind: v.Kind()}) + } +} + +func (v Value) SetInt(x int64) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Int: + *(*int)(v.value) = int(x) + case Int8: + *(*int8)(v.value) = int8(x) + case Int16: + *(*int16)(v.value) = int16(x) + case Int32: + *(*int32)(v.value) = int32(x) + case Int64: + *(*int64)(v.value) = x + default: + panic(&ValueError{Method: "SetInt", Kind: v.Kind()}) + } +} + +func (v Value) SetUint(x uint64) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Uint: + *(*uint)(v.value) = uint(x) + case Uint8: + *(*uint8)(v.value) = uint8(x) + case Uint16: + *(*uint16)(v.value) = uint16(x) + case Uint32: + *(*uint32)(v.value) = uint32(x) + case Uint64: + *(*uint64)(v.value) = x + case Uintptr: + *(*uintptr)(v.value) = uintptr(x) + default: + panic(&ValueError{Method: "SetUint", Kind: v.Kind()}) + } +} + +func (v Value) SetFloat(x float64) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Float32: + *(*float32)(v.value) = float32(x) + case Float64: + *(*float64)(v.value) = x + default: + panic(&ValueError{Method: "SetFloat", Kind: v.Kind()}) + } +} + +func (v Value) SetComplex(x complex128) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Complex64: + *(*complex64)(v.value) = complex64(x) + case Complex128: + *(*complex128)(v.value) = x + default: + panic(&ValueError{Method: "SetComplex", Kind: v.Kind()}) + } +} + +func (v Value) SetString(x string) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case String: + *(*string)(v.value) = x + default: + panic(&ValueError{Method: "SetString", Kind: v.Kind()}) + } +} + +func (v Value) SetBytes(x []byte) { + v.checkAddressable() + v.checkRO() + if v.typecode.Kind() != Slice || v.typecode.elem().Kind() != Uint8 { + panic("reflect.Value.SetBytes called on not []byte") + } + + // copy the header contents over + *(*[]byte)(v.value) = x +} + +func (v Value) SetCap(n int) { + panic("unimplemented: (reflect.Value).SetCap()") +} + +func (v Value) SetLen(n int) { + if v.typecode.Kind() != Slice { + panic(&ValueError{Method: "reflect.Value.SetLen", Kind: v.Kind()}) + } + v.checkAddressable() + hdr := (*sliceHeader)(v.value) + if int(uintptr(n)) != n || uintptr(n) > hdr.cap { + panic("reflect.Value.SetLen: slice length out of range") + } + hdr.len = uintptr(n) +} + +func (v Value) checkAddressable() { + if !v.isIndirect() { + panic("reflect: value is not addressable") + } +} + +// OverflowInt reports whether the int64 x cannot be represented by v's type. +// It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64. +func (v Value) OverflowInt(x int64) bool { + switch v.Kind() { + case Int, Int8, Int16, Int32, Int64: + bitSize := v.typecode.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic(&ValueError{Method: "reflect.Value.OverflowInt", Kind: v.Kind()}) +} + +// OverflowUint reports whether the uint64 x cannot be represented by v's type. +// It panics if v's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. +func (v Value) OverflowUint(x uint64) bool { + k := v.Kind() + switch k { + case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: + bitSize := v.typecode.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic(&ValueError{Method: "reflect.Value.OverflowUint", Kind: v.Kind()}) +} + +func (v Value) CanConvert(t Type) bool { + // TODO: Optimize this to not actually perform a conversion + _, ok := convertOp(v, t) + return ok +} + +func (v Value) Convert(t Type) Value { + if v, ok := convertOp(v, t); ok { + return v + } + + panic("reflect.Value.Convert: value of type " + v.typecode.String() + " cannot be converted to type " + t.String()) +} + +func convertOp(src Value, typ Type) (Value, bool) { + + // Easy check first. Do we even need to do anything? + if src.typecode.underlying() == typ.(*RawType).underlying() { + return Value{ + typecode: typ.(*RawType), + value: src.value, + flags: src.flags, + }, true + } + + if rtype := typ.(*RawType); rtype.Kind() == Interface && rtype.NumMethod() == 0 { + iface := composeInterface(unsafe.Pointer(src.typecode), src.value) + return Value{ + typecode: rtype, + value: unsafe.Pointer(&iface), + flags: valueFlagExported, + }, true + } + + switch src.Kind() { + case Int, Int8, Int16, Int32, Int64: + switch rtype := typ.(*RawType); rtype.Kind() { + case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return cvtInt(src, rtype), true + case Float32, Float64: + return cvtIntFloat(src, rtype), true + case String: + return cvtIntString(src, rtype), true + } + + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + switch rtype := typ.(*RawType); rtype.Kind() { + case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return cvtUint(src, rtype), true + case Float32, Float64: + return cvtUintFloat(src, rtype), true + case String: + return cvtUintString(src, rtype), true + } + + case Float32, Float64: + switch rtype := typ.(*RawType); rtype.Kind() { + case Int, Int8, Int16, Int32, Int64: + return cvtFloatInt(src, rtype), true + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return cvtFloatUint(src, rtype), true + case Float32, Float64: + return cvtFloat(src, rtype), true + } + + /* + case Complex64, Complex128: + switch src.Kind() { + case Complex64, Complex128: + return cvtComplex + } + */ + + case Slice: + switch rtype := typ.(*RawType); rtype.Kind() { + case Array: + if src.typecode.elem() == rtype.elem() && rtype.Len() <= src.Len() { + return Value{ + typecode: rtype, + value: (*sliceHeader)(src.value).data, + flags: src.flags | valueFlagIndirect, + }, true + } + case Pointer: + if rtype.Elem().Kind() == Array { + if src.typecode.elem() == rtype.elem().elem() && rtype.elem().Len() <= src.Len() { + return Value{ + typecode: rtype, + value: (*sliceHeader)(src.value).data, + flags: src.flags & (valueFlagExported | valueFlagRO), + }, true + } + } + case String: + if !src.typecode.elem().isNamed() { + switch src.Type().Elem().Kind() { + case Uint8: + return cvtBytesString(src, rtype), true + case Int32: + return cvtRunesString(src, rtype), true + } + } + } + + case String: + rtype := typ.(*RawType) + if typ.Kind() == Slice && !rtype.elem().isNamed() { + switch typ.Elem().Kind() { + case Uint8: + return cvtStringBytes(src, rtype), true + case Int32: + return cvtStringRunes(src, rtype), true + } + } + } + + // TODO(dgryski): Unimplemented: + // Chan + // Non-defined pointers types with same underlying base type + // Interface <-> Type conversions + + return Value{}, false +} + +func cvtInt(v Value, t *RawType) Value { + return makeInt(v.flags, uint64(v.Int()), t) +} + +func cvtUint(v Value, t *RawType) Value { + return makeInt(v.flags, v.Uint(), t) +} + +func cvtIntFloat(v Value, t *RawType) Value { + return makeFloat(v.flags, float64(v.Int()), t) +} + +func cvtUintFloat(v Value, t *RawType) Value { + return makeFloat(v.flags, float64(v.Uint()), t) +} + +func cvtFloatInt(v Value, t *RawType) Value { + return makeInt(v.flags, uint64(int64(v.Float())), t) +} + +func cvtFloatUint(v Value, t *RawType) Value { + return makeInt(v.flags, uint64(v.Float()), t) +} + +func cvtFloat(v Value, t *RawType) Value { + if v.Type().Kind() == Float32 && t.Kind() == Float32 { + // Don't do any conversion if both types have underlying type float32. + // This avoids converting to float64 and back, which will + // convert a signaling NaN to a quiet NaN. See issue 36400. + return makeFloat32(v.flags, v.Float32(), t) + } + return makeFloat(v.flags, v.Float(), t) +} + +//go:linkname stringToBytes runtime.stringToBytes +func stringToBytes(x string) []byte + +func cvtStringBytes(v Value, t *RawType) Value { + b := stringToBytes(*(*string)(v.value)) + return Value{ + typecode: t, + value: unsafe.Pointer(&b), + flags: v.flags, + } +} + +//go:linkname stringFromBytes runtime.stringFromBytes +func stringFromBytes(x []byte) string + +func cvtBytesString(v Value, t *RawType) Value { + s := stringFromBytes(*(*[]byte)(v.value)) + return Value{ + typecode: t, + value: unsafe.Pointer(&s), + flags: v.flags, + } +} + +func makeInt(flags valueFlags, bits uint64, 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 1: + *(*uint8)(ptr) = uint8(bits) + case 2: + *(*uint16)(ptr) = uint16(bits) + case 4: + *(*uint32)(ptr) = uint32(bits) + case 8: + *(*uint64)(ptr) = bits + } + return v +} + +func makeFloat(flags valueFlags, f float64, 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 4: + *(*float32)(ptr) = float32(f) + case 8: + *(*float64)(ptr) = f + } + return v +} + +func makeFloat32(flags valueFlags, f float32, t *RawType) Value { + v := Value{ + typecode: t, + flags: flags, + } + *(*float32)(unsafe.Pointer(&v.value)) = float32(f) + return v +} + +func cvtIntString(src Value, t *RawType) Value { + panic("cvtUintString: unimplemented") +} + +func cvtUintString(src Value, t *RawType) Value { + panic("cvtUintString: unimplemented") +} + +func cvtStringRunes(src Value, t *RawType) Value { + panic("cvsStringRunes: unimplemented") +} + +func cvtRunesString(src Value, t *RawType) Value { + panic("cvsRunesString: unimplemented") +} + +//go:linkname slicePanic runtime.slicePanic +func slicePanic() + +func MakeSlice(typ Type, len, cap int) Value { + if typ.Kind() != Slice { + panic("reflect.MakeSlice of non-slice type") + } + + rtype := typ.(*RawType) + + ulen := uint(len) + ucap := uint(cap) + maxSize := (^uintptr(0)) / 2 + elem := rtype.elem() + elementSize := elem.Size() + if elementSize > 1 { + maxSize /= uintptr(elementSize) + } + if ulen > ucap || ucap > uint(maxSize) { + slicePanic() + } + + // This can't overflow because of the above checks. + size := uintptr(ucap) * elementSize + + var slice sliceHeader + slice.cap = uintptr(ucap) + slice.len = uintptr(ulen) + layout := elem.gcLayout() + + slice.data = alloc(size, layout) + + return Value{ + typecode: rtype, + value: unsafe.Pointer(&slice), + flags: valueFlagExported, + } +} + +var zerobuffer unsafe.Pointer + +const zerobufferLen = 32 + +func init() { + // 32 characters of zero bytes + zerobufferStr := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + s := (*stringHeader)(unsafe.Pointer(&zerobufferStr)) + zerobuffer = s.data +} + +func Zero(typ Type) Value { + size := typ.Size() + if size <= unsafe.Sizeof(uintptr(0)) { + return Value{ + typecode: typ.(*RawType), + value: nil, + flags: valueFlagExported | valueFlagRO, + } + } + + if size <= zerobufferLen { + return Value{ + typecode: typ.(*RawType), + value: unsafe.Pointer(zerobuffer), + flags: valueFlagExported | valueFlagRO, + } + } + + return Value{ + typecode: typ.(*RawType), + value: alloc(size, nil), + flags: valueFlagExported | valueFlagRO, + } +} + +// New is the reflect equivalent of the new(T) keyword, returning a pointer to a +// new value of the given type. +func New(typ Type) Value { + return Value{ + typecode: pointerTo(typ.(*RawType)), + value: alloc(typ.Size(), nil), + flags: valueFlagExported, + } +} + +type funcHeader struct { + Context unsafe.Pointer + Code unsafe.Pointer +} + +type SliceHeader struct { + Data uintptr + Len intw + Cap intw +} + +// Slice header that matches the underlying structure. Used for when we switch +// to a precise GC, which needs to know exactly where pointers live. +type sliceHeader struct { + data unsafe.Pointer + len uintptr + cap uintptr +} + +type StringHeader struct { + Data uintptr + Len intw +} + +// Like sliceHeader, this type is used internally to make sure pointer and +// non-pointer fields match those of actual strings. +type stringHeader struct { + data unsafe.Pointer + len uintptr +} + +// Verify SliceHeader and StringHeader sizes. +// See https://github.com/tinygo-org/tinygo/pull/4156 +// and https://github.com/tinygo-org/tinygo/issues/1284. +var ( + _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} + _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(sliceHeader{})]byte{} + _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} + _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(stringHeader{})]byte{} +) + +type ValueError struct { + Method string + Kind Kind +} + +func (e *ValueError) Error() string { + if e.Kind == 0 { + return "reflect: call of " + e.Method + " on zero Value" + } + return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value" +} + +//go:linkname memcpy runtime.memcpy +func memcpy(dst, src unsafe.Pointer, size uintptr) + +//go:linkname memzero runtime.memzero +func memzero(ptr unsafe.Pointer, size uintptr) + +//go:linkname alloc runtime.alloc +func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer + +//go:linkname sliceAppend runtime.sliceAppend +func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) + +//go:linkname sliceCopy runtime.sliceCopy +func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int + +// Copy copies the contents of src into dst until either +// dst has been filled or src has been exhausted. +func Copy(dst, src Value) int { + compatibleTypes := false || + // dst and src are both slices or arrays with equal types + ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && + (src.typecode.Kind() == Slice || src.typecode.Kind() == Array) && + (dst.typecode.elem() == src.typecode.elem())) || + // dst is array or slice of uint8 and src is string + ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && + dst.typecode.elem().Kind() == Uint8 && + src.typecode.Kind() == String) + + if !compatibleTypes { + panic("Copy: type mismatch: " + dst.typecode.String() + "/" + src.typecode.String()) + } + + // Can read from an unaddressable array but not write to one. + if dst.typecode.Kind() == Array && !dst.isIndirect() { + panic("reflect.Copy: unaddressable array value") + } + + dstbuf, dstlen := buflen(dst) + srcbuf, srclen := buflen(src) + + if srclen > 0 { + dst.checkRO() + } + + return sliceCopy(dstbuf, srcbuf, dstlen, srclen, dst.typecode.elem().Size()) +} + +func buflen(v Value) (unsafe.Pointer, uintptr) { + var buf unsafe.Pointer + var len uintptr + switch v.typecode.Kind() { + case Slice: + hdr := (*sliceHeader)(v.value) + buf = hdr.data + len = hdr.len + case Array: + if v.isIndirect() || v.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + buf = v.value + } else { + buf = unsafe.Pointer(&v.value) + } + len = uintptr(v.Len()) + case String: + hdr := (*stringHeader)(v.value) + buf = hdr.data + len = hdr.len + default: + // This shouldn't happen + panic("reflect.Copy: not slice or array or string") + } + + return buf, len +} + +//go:linkname sliceGrow runtime.sliceGrow +func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) + +// extend slice to hold n new elements +func extendSlice(v Value, n int) sliceHeader { + if v.Kind() != Slice { + panic(&ValueError{Method: "extendSlice", Kind: v.Kind()}) + } + + var old sliceHeader + if v.value != nil { + old = *(*sliceHeader)(v.value) + } + + nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), v.typecode.elem().Size()) + + return sliceHeader{ + data: nbuf, + len: nlen + uintptr(n), + cap: ncap, + } +} + +// Append appends the values x to a slice s and returns the resulting slice. +// As in Go, each x's value must be assignable to the slice's element type. +func Append(v Value, x ...Value) Value { + if v.Kind() != Slice { + panic(&ValueError{Method: "Append", Kind: v.Kind()}) + } + oldLen := v.Len() + newslice := extendSlice(v, len(x)) + v.flags = valueFlagExported + v.value = (unsafe.Pointer)(&newslice) + for i, xx := range x { + v.Index(oldLen + i).Set(xx) + } + return v +} + +// AppendSlice appends a slice t to a slice s and returns the resulting slice. +// The slices s and t must have the same element type. +func AppendSlice(s, t Value) Value { + if s.typecode.Kind() != Slice || t.typecode.Kind() != Slice || s.typecode != t.typecode { + // Not a very helpful error message, but shortened to just one error to + // keep code size down. + panic("reflect.AppendSlice: invalid types") + } + if !s.isExported() || !t.isExported() { + // One of the sides was not exported, so can't access the data. + panic("reflect.AppendSlice: unexported") + } + sSlice := (*sliceHeader)(s.value) + tSlice := (*sliceHeader)(t.value) + elemSize := s.typecode.elem().Size() + ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize) + result := &sliceHeader{ + data: ptr, + len: len, + cap: cap, + } + return Value{ + typecode: s.typecode, + value: unsafe.Pointer(result), + flags: valueFlagExported, + } +} + +// Grow increases the slice's capacity, if necessary, to guarantee space for +// another n elements. After Grow(n), at least n elements can be appended +// to the slice without another allocation. +// +// It panics if v's Kind is not a Slice or if n is negative or too large to +// allocate the memory. +func (v Value) Grow(n int) { + v.checkAddressable() + if n < 0 { + panic("reflect.Grow: negative length") + } + if v.Kind() != Slice { + panic(&ValueError{Method: "Grow", Kind: v.Kind()}) + } + slice := (*sliceHeader)(v.value) + newslice := extendSlice(v, n) + // Only copy the new data and cap: the len remains unchanged. + slice.data = newslice.data + slice.cap = newslice.cap +} + +//go:linkname hashmapStringSet runtime.hashmapStringSet +func hashmapStringSet(m unsafe.Pointer, key string, value unsafe.Pointer) + +//go:linkname hashmapBinarySet runtime.hashmapBinarySet +func hashmapBinarySet(m unsafe.Pointer, key, value unsafe.Pointer) + +//go:linkname hashmapInterfaceSet runtime.hashmapInterfaceSet +func hashmapInterfaceSet(m unsafe.Pointer, key interface{}, value unsafe.Pointer) + +//go:linkname hashmapStringDelete runtime.hashmapStringDelete +func hashmapStringDelete(m unsafe.Pointer, key string) + +//go:linkname hashmapBinaryDelete runtime.hashmapBinaryDelete +func hashmapBinaryDelete(m unsafe.Pointer, key unsafe.Pointer) + +//go:linkname hashmapInterfaceDelete runtime.hashmapInterfaceDelete +func hashmapInterfaceDelete(m unsafe.Pointer, key interface{}) + +func (v Value) SetMapIndex(key, elem Value) { + v.checkRO() + if v.Kind() != Map { + panic(&ValueError{Method: "SetMapIndex", Kind: v.Kind()}) + } + + vkey := v.typecode.key() + + // compare key type with actual key type of map + if !key.typecode.AssignableTo(vkey) { + panic("reflect.Value.SetMapIndex: incompatible types for key") + } + + // if elem is the zero Value, it means delete + del := elem == Value{} + + if !del && !elem.typecode.AssignableTo(v.typecode.elem()) { + panic("reflect.Value.SetMapIndex: incompatible types for value") + } + + // make elem an interface if it needs to be converted + if v.typecode.elem().Kind() == Interface && elem.typecode.Kind() != Interface { + intf := composeInterface(unsafe.Pointer(elem.typecode), elem.value) + elem = Value{ + typecode: v.typecode.elem(), + value: unsafe.Pointer(&intf), + } + } + + if key.Kind() == String { + if del { + hashmapStringDelete(v.pointer(), *(*string)(key.value)) + } else { + var elemptr unsafe.Pointer + if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + elemptr = elem.value + } else { + elemptr = unsafe.Pointer(&elem.value) + } + hashmapStringSet(v.pointer(), *(*string)(key.value), elemptr) + } + + } else if key.typecode.isBinary() { + var keyptr unsafe.Pointer + if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + keyptr = key.value + } else { + keyptr = unsafe.Pointer(&key.value) + } + + if del { + hashmapBinaryDelete(v.pointer(), keyptr) + } else { + var elemptr unsafe.Pointer + if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + elemptr = elem.value + } else { + elemptr = unsafe.Pointer(&elem.value) + } + hashmapBinarySet(v.pointer(), keyptr, elemptr) + } + } else { + if del { + hashmapInterfaceDelete(v.pointer(), key.Interface()) + } else { + var elemptr unsafe.Pointer + if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + elemptr = elem.value + } else { + elemptr = unsafe.Pointer(&elem.value) + } + + hashmapInterfaceSet(v.pointer(), key.Interface(), elemptr) + } + } +} + +// FieldByIndex returns the nested field corresponding to index. +func (v Value) FieldByIndex(index []int) Value { + if len(index) == 1 { + return v.Field(index[0]) + } + if v.Kind() != Struct { + panic(&ValueError{"FieldByIndex", v.Kind()}) + } + for i, x := range index { + if i > 0 { + if v.Kind() == Pointer && v.typecode.elem().Kind() == Struct { + if v.IsNil() { + panic("reflect: indirection through nil pointer to embedded struct") + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} + +// FieldByIndexErr returns the nested field corresponding to index. +func (v Value) FieldByIndexErr(index []int) (Value, error) { + return Value{}, &ValueError{Method: "FieldByIndexErr"} +} + +func (v Value) FieldByName(name string) Value { + if v.Kind() != Struct { + panic(&ValueError{"FieldByName", v.Kind()}) + } + + if field, ok := v.typecode.FieldByName(name); ok { + return v.FieldByIndex(field.Index) + } + return Value{} +} + +func (v Value) FieldByNameFunc(match func(string) bool) Value { + if v.Kind() != Struct { + panic(&ValueError{"FieldByName", v.Kind()}) + } + + if field, ok := v.typecode.FieldByNameFunc(match); ok { + return v.FieldByIndex(field.Index) + } + return Value{} +} + +//go:linkname hashmapMake runtime.hashmapMake +func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer + +// MakeMapWithSize creates a new map with the specified type and initial space +// for approximately n elements. +func MakeMapWithSize(typ Type, n int) Value { + + // TODO(dgryski): deduplicate these? runtime and reflect both need them. + const ( + hashmapAlgorithmBinary uint8 = iota + hashmapAlgorithmString + hashmapAlgorithmInterface + ) + + if typ.Kind() != Map { + panic(&ValueError{Method: "MakeMap", Kind: typ.Kind()}) + } + + if n < 0 { + panic("reflect.MakeMapWithSize: negative size hint") + } + + key := typ.Key().(*RawType) + val := typ.Elem().(*RawType) + + var alg uint8 + + if key.Kind() == String { + alg = hashmapAlgorithmString + } else if key.isBinary() { + alg = hashmapAlgorithmBinary + } else { + alg = hashmapAlgorithmInterface + } + + m := hashmapMake(key.Size(), val.Size(), uintptr(n), alg) + + return Value{ + typecode: typ.(*RawType), + value: m, + flags: valueFlagExported, + } +} + +// MakeMap creates a new map with the specified type. +func MakeMap(typ Type) Value { + return MakeMapWithSize(typ, 8) +} + +func (v Value) Call(in []Value) []Value { + panic("unimplemented: (reflect.Value).Call()") +} + +func (v Value) CallSlice(in []Value) []Value { + panic("unimplemented: (reflect.Value).CallSlice()") +} + +func (v Value) Method(i int) Value { + panic("unimplemented: (reflect.Value).Method()") +} + +func (v Value) MethodByName(name string) Value { + panic("unimplemented: (reflect.Value).MethodByName()") +} + +func (v Value) Recv() (x Value, ok bool) { + panic("unimplemented: (reflect.Value).Recv()") +} + +func NewAt(typ Type, p unsafe.Pointer) Value { + panic("unimplemented: reflect.New()") +} diff --git a/src/internal/reflectlite/visiblefields.go b/src/internal/reflectlite/visiblefields.go new file mode 100644 index 0000000000..b21af6178f --- /dev/null +++ b/src/internal/reflectlite/visiblefields.go @@ -0,0 +1,105 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflectlite + +// VisibleFields returns all the visible fields in t, which must be a +// struct type. A field is defined as visible if it's accessible +// directly with a FieldByName call. The returned fields include fields +// inside anonymous struct members and unexported fields. They follow +// the same order found in the struct, with anonymous fields followed +// immediately by their promoted fields. +// +// For each element e of the returned slice, the corresponding field +// can be retrieved from a value v of type t by calling v.FieldByIndex(e.Index). +func VisibleFields(t Type) []StructField { + if t == nil { + panic("reflect: VisibleFields(nil)") + } + if t.Kind() != Struct { + panic("reflect.VisibleFields of non-struct type") + } + w := &visibleFieldsWalker{ + byName: make(map[string]int), + visiting: make(map[Type]bool), + fields: make([]StructField, 0, t.NumField()), + index: make([]int, 0, 2), + } + w.walk(t) + // Remove all the fields that have been hidden. + // Use an in-place removal that avoids copying in + // the common case that there are no hidden fields. + j := 0 + for i := range w.fields { + f := &w.fields[i] + if f.Name == "" { + continue + } + if i != j { + // A field has been removed. We need to shuffle + // all the subsequent elements up. + w.fields[j] = *f + } + j++ + } + return w.fields[:j] +} + +type visibleFieldsWalker struct { + byName map[string]int + visiting map[Type]bool + fields []StructField + index []int +} + +// walk walks all the fields in the struct type t, visiting +// fields in index preorder and appending them to w.fields +// (this maintains the required ordering). +// Fields that have been overridden have their +// Name field cleared. +func (w *visibleFieldsWalker) walk(t Type) { + if w.visiting[t] { + return + } + w.visiting[t] = true + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + w.index = append(w.index, i) + add := true + if oldIndex, ok := w.byName[f.Name]; ok { + old := &w.fields[oldIndex] + if len(w.index) == len(old.Index) { + // Fields with the same name at the same depth + // cancel one another out. Set the field name + // to empty to signify that has happened, and + // there's no need to add this field. + old.Name = "" + add = false + } else if len(w.index) < len(old.Index) { + // The old field loses because it's deeper than the new one. + old.Name = "" + } else { + // The old field wins because it's shallower than the new one. + add = false + } + } + if add { + // Copy the index so that it's not overwritten + // by the other appends. + f.Index = append([]int(nil), w.index...) + w.byName[f.Name] = len(w.fields) + w.fields = append(w.fields, f) + } + if f.Anonymous { + if f.Type.Kind() == Pointer { + f.Type = f.Type.Elem() + } + if f.Type.Kind() == Struct { + w.walk(f.Type) + } + } + w.index = w.index[:len(w.index)-1] + } + delete(w.visiting, t) +} diff --git a/src/reflect/swapper.go b/src/reflect/swapper.go index a2fa44cef0..d49e33e04c 100644 --- a/src/reflect/swapper.go +++ b/src/reflect/swapper.go @@ -1,40 +1,7 @@ package reflect -import "unsafe" - -// Some of code here has been copied from the Go sources: -// https://github.com/golang/go/blob/go1.15.2/src/reflect/swapper.go -// It has the following copyright note: -// -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +import "internal/reflectlite" func Swapper(slice interface{}) func(i, j int) { - v := ValueOf(slice) - if v.Kind() != Slice { - panic(&ValueError{Method: "Swapper"}) - } - - // Just return Nop func if nothing to swap. - if v.Len() < 2 { - return func(i, j int) {} - } - - typ := v.typecode.Elem() - size := typ.Size() - - header := (*sliceHeader)(v.value) - tmp := unsafe.Pointer(&make([]byte, size)[0]) - - return func(i, j int) { - if uint(i) >= uint(header.len) || uint(j) >= uint(header.len) { - panic("reflect: slice index out of range") - } - val1 := unsafe.Add(header.data, uintptr(i)*size) - val2 := unsafe.Add(header.data, uintptr(j)*size) - memcpy(tmp, val1, size) - memcpy(val1, val2, size) - memcpy(val2, tmp, size) - } + return reflectlite.Swapper(slice) } diff --git a/src/reflect/type.go b/src/reflect/type.go index c81d6ba554..e5417f8e8f 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -64,130 +64,50 @@ package reflect import ( - "internal/gclayout" - "internal/itoa" + "internal/reflectlite" "unsafe" ) -// Flags stored in the first byte of the struct field byte array. Must be kept -// up to date with compiler/interface.go. -const ( - structFieldFlagAnonymous = 1 << iota - structFieldFlagHasTag - structFieldFlagIsExported - structFieldFlagIsEmbedded -) - -type Kind uint8 +type Kind = reflectlite.Kind -// Copied from reflect/type.go -// https://golang.org/src/reflect/type.go?s=8302:8316#L217 -// These constants must match basicTypes and the typeKind* constants in -// compiler/interface.go const ( - Invalid Kind = iota - Bool - Int - Int8 - Int16 - Int32 - Int64 - Uint - Uint8 - Uint16 - Uint32 - Uint64 - Uintptr - Float32 - Float64 - Complex64 - Complex128 - String - UnsafePointer - Chan - Interface - Pointer - Slice - Array - Func - Map - Struct + Invalid Kind = reflectlite.Invalid + Bool Kind = reflectlite.Bool + Int Kind = reflectlite.Int + Int8 Kind = reflectlite.Int8 + Int16 Kind = reflectlite.Int16 + Int32 Kind = reflectlite.Int32 + Int64 Kind = reflectlite.Int64 + Uint Kind = reflectlite.Uint + Uint8 Kind = reflectlite.Uint8 + Uint16 Kind = reflectlite.Uint16 + Uint32 Kind = reflectlite.Uint32 + Uint64 Kind = reflectlite.Uint64 + Uintptr Kind = reflectlite.Uintptr + Float32 Kind = reflectlite.Float32 + Float64 Kind = reflectlite.Float64 + Complex64 Kind = reflectlite.Complex64 + Complex128 Kind = reflectlite.Complex128 + Array Kind = reflectlite.Array + Chan Kind = reflectlite.Chan + Func Kind = reflectlite.Func + Interface Kind = reflectlite.Interface + Map Kind = reflectlite.Map + Pointer Kind = reflectlite.Pointer + Slice Kind = reflectlite.Slice + String Kind = reflectlite.String + Struct Kind = reflectlite.Struct + UnsafePointer Kind = reflectlite.UnsafePointer ) -// Ptr is the old name for the Pointer kind. -const Ptr = Pointer - -func (k Kind) String() string { - switch k { - case Invalid: - return "invalid" - case Bool: - return "bool" - case Int: - return "int" - case Int8: - return "int8" - case Int16: - return "int16" - case Int32: - return "int32" - case Int64: - return "int64" - case Uint: - return "uint" - case Uint8: - return "uint8" - case Uint16: - return "uint16" - case Uint32: - return "uint32" - case Uint64: - return "uint64" - case Uintptr: - return "uintptr" - case Float32: - return "float32" - case Float64: - return "float64" - case Complex64: - return "complex64" - case Complex128: - return "complex128" - case String: - return "string" - case UnsafePointer: - return "unsafe.Pointer" - case Chan: - return "chan" - case Interface: - return "interface" - case Pointer: - return "ptr" - case Slice: - return "slice" - case Array: - return "array" - case Func: - return "func" - case Map: - return "map" - case Struct: - return "struct" - default: - return "kind" + itoa.Itoa(int(int8(k))) - } -} - -// Copied from reflect/type.go -// https://go.dev/src/reflect/type.go?#L348 +const Ptr = reflectlite.Ptr -// ChanDir represents a channel type's direction. -type ChanDir int +type ChanDir = reflectlite.ChanDir const ( - RecvDir ChanDir = 1 << iota // <-chan - SendDir // chan<- - BothDir = RecvDir | SendDir // chan + RecvDir = reflectlite.RecvDir + SendDir = reflectlite.SendDir + BothDir = reflectlite.BothDir ) // Method represents a single method. @@ -411,1015 +331,80 @@ type Type interface { OverflowUint(x uint64) bool } -// Constants for the 'meta' byte. -const ( - kindMask = 31 // mask to apply to the meta byte to get the Kind value - flagNamed = 32 // flag that is set if this is a named type - flagComparable = 64 // flag that is set if this type is comparable - flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm -) - -// The base type struct. All type structs start with this. type rawType struct { - meta uint8 // metadata byte, contains kind and flags (see constants above) -} - -// All types that have an element type: named, chan, slice, array, map (but not -// pointer because it doesn't have ptrTo). -type elemType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType -} - -type ptrType struct { - rawType - numMethod uint16 - elem *rawType -} - -type interfaceType struct { - rawType - ptrTo *rawType - // TODO: methods -} - -type arrayType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - arrayLen uintptr - slicePtr *rawType -} - -type mapType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - key *rawType -} - -type namedType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - pkg *byte - name [1]byte + reflectlite.RawType } -// Type for struct types. The numField value is intentionally put before ptrTo -// for better struct packing on 32-bit and 64-bit architectures. On these -// architectures, the ptrTo field still has the same offset as in all the other -// type structs. -// The fields array isn't necessarily 1 structField long, instead it is as long -// as numFields. The array is given a length of 1 to satisfy the Go type -// checker. -type structType struct { - rawType - numMethod uint16 - ptrTo *rawType - pkgpath *byte - size uint32 - numField uint16 - fields [1]structField // the remaining fields are all of type structField +func toType(t reflectlite.Type) Type { + return (*rawType)(unsafe.Pointer(t.(*reflectlite.RawType))) } -type structField struct { - fieldType *rawType - data unsafe.Pointer // various bits of information, packed in a byte array -} - -// Equivalent to (go/types.Type).Underlying(): if this is a named type return -// the underlying type, else just return the type itself. -func (t *rawType) underlying() *rawType { - if t.isNamed() { - return (*elemType)(unsafe.Pointer(t)).elem - } - return t -} - -func (t *rawType) ptrtag() uintptr { - return uintptr(unsafe.Pointer(t)) & 0b11 -} - -func (t *rawType) isNamed() bool { - if tag := t.ptrtag(); tag != 0 { - return false - } - - return t.meta&flagNamed != 0 +func toRawType(t Type) *reflectlite.RawType { + return (*reflectlite.RawType)(unsafe.Pointer(t.(*rawType))) } func TypeOf(i interface{}) Type { - if i == nil { - return nil - } - typecode, _ := decomposeInterface(i) - return (*rawType)(typecode) -} - -func PtrTo(t Type) Type { return PointerTo(t) } - -func PointerTo(t Type) Type { - return pointerTo(t.(*rawType)) -} - -func pointerTo(t *rawType) *rawType { - if t.isNamed() { - return (*elemType)(unsafe.Pointer(t)).ptrTo - } - - switch t.Kind() { - case Pointer: - if tag := t.ptrtag(); tag < 3 { - return (*rawType)(unsafe.Add(unsafe.Pointer(t), 1)) - } - - // TODO(dgryski): This is blocking https://github.com/tinygo-org/tinygo/issues/3131 - // We need to be able to create types that match existing types to prevent typecode equality. - panic("reflect: cannot make *****T type") - case Struct: - return (*structType)(unsafe.Pointer(t)).ptrTo - default: - return (*elemType)(unsafe.Pointer(t)).ptrTo - } -} - -func (t *rawType) String() string { - if t.isNamed() { - s := t.name() - if s[0] == '.' { - return s[1:] - } - return s - } - - switch t.Kind() { - case Chan: - elem := t.elem().String() - switch t.ChanDir() { - case SendDir: - return "chan<- " + elem - case RecvDir: - return "<-chan " + elem - case BothDir: - if elem[0] == '<' { - // typ is recv chan, need parentheses as "<-" associates with leftmost - // chan possible, see: - // * https://golang.org/ref/spec#Channel_types - // * https://github.com/golang/go/issues/39897 - return "chan (" + elem + ")" - } - return "chan " + elem - } - - case Pointer: - return "*" + t.elem().String() - case Slice: - return "[]" + t.elem().String() - case Array: - return "[" + itoa.Itoa(t.Len()) + "]" + t.elem().String() - case Map: - return "map[" + t.key().String() + "]" + t.elem().String() - case Struct: - numField := t.NumField() - if numField == 0 { - return "struct {}" - } - s := "struct {" - for i := 0; i < numField; i++ { - f := t.rawField(i) - s += " " + f.Name + " " + f.Type.String() - if f.Tag != "" { - s += " " + quote(string(f.Tag)) - } - // every field except the last needs a semicolon - if i < numField-1 { - s += ";" - } - } - s += " }" - return s - case Interface: - // TODO(dgryski): Needs actual method set info - return "interface {}" - default: - return t.Kind().String() - } - - return t.Kind().String() -} - -func (t *rawType) Kind() Kind { - if t == nil { - return Invalid - } - - if tag := t.ptrtag(); tag != 0 { - return Pointer - } - - return Kind(t.meta & kindMask) -} - -var ( - errTypeElem = &TypeError{"Elem"} - errTypeKey = &TypeError{"Key"} - errTypeField = &TypeError{"Field"} - errTypeBits = &TypeError{"Bits"} - errTypeLen = &TypeError{"Len"} - errTypeNumField = &TypeError{"NumField"} - errTypeChanDir = &TypeError{"ChanDir"} - errTypeFieldByName = &TypeError{"FieldByName"} - errTypeFieldByIndex = &TypeError{"FieldByIndex"} -) - -// Elem returns the element type for channel, slice and array types, the -// pointed-to value for pointer types, and the key type for map types. -func (t *rawType) Elem() Type { - return t.elem() -} - -func (t *rawType) elem() *rawType { - if tag := t.ptrtag(); tag != 0 { - return (*rawType)(unsafe.Add(unsafe.Pointer(t), -1)) - } - - underlying := t.underlying() - switch underlying.Kind() { - case Pointer: - return (*ptrType)(unsafe.Pointer(underlying)).elem - case Chan, Slice, Array, Map: - return (*elemType)(unsafe.Pointer(underlying)).elem - default: - panic(errTypeElem) - } -} - -func (t *rawType) key() *rawType { - underlying := t.underlying() - if underlying.Kind() != Map { - panic(errTypeKey) - } - return (*mapType)(unsafe.Pointer(underlying)).key -} - -// Field returns the type of the i'th field of this struct type. It panics if t -// is not a struct type. -func (t *rawType) Field(i int) StructField { - field := t.rawField(i) - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: []int{i}, - } -} - -func rawStructFieldFromPointer(descriptor *structType, fieldType *rawType, data unsafe.Pointer, flagsByte uint8, name string, offset uint32) rawStructField { - // Read the field tag, if there is one. - var tag string - if flagsByte&structFieldFlagHasTag != 0 { - data = unsafe.Add(data, 1) // C: data+1 - tagLen := uintptr(*(*byte)(data)) - data = unsafe.Add(data, 1) // C: data+1 - tag = *(*string)(unsafe.Pointer(&stringHeader{ - data: data, - len: tagLen, - })) - } - - // Set the PkgPath to some (arbitrary) value if the package path is not - // exported. - pkgPath := "" - if flagsByte&structFieldFlagIsExported == 0 { - // This field is unexported. - pkgPath = readStringZ(unsafe.Pointer(descriptor.pkgpath)) - } - - return rawStructField{ - Name: name, - PkgPath: pkgPath, - Type: fieldType, - Tag: StructTag(tag), - Anonymous: flagsByte&structFieldFlagAnonymous != 0, - Offset: uintptr(offset), - } -} - -// rawField returns nearly the same value as Field but without converting the -// Type member to an interface. -// -// For internal use only. -func (t *rawType) rawField(n int) rawStructField { - if t.Kind() != Struct { - panic(errTypeField) - } - descriptor := (*structType)(unsafe.Pointer(t.underlying())) - if uint(n) >= uint(descriptor.numField) { - panic("reflect: field index out of range") - } - - // Iterate over all the fields to calculate the offset. - // This offset could have been stored directly in the array (to make the - // lookup faster), but by calculating it on-the-fly a bit of storage can be - // saved. - field := (*structField)(unsafe.Add(unsafe.Pointer(&descriptor.fields[0]), uintptr(n)*unsafe.Sizeof(structField{}))) - data := field.data - - // Read some flags of this field, like whether the field is an embedded - // field. See structFieldFlagAnonymous and similar flags. - flagsByte := *(*byte)(data) - data = unsafe.Add(data, 1) - offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) - data = unsafe.Add(data, lenOffs) - - name := readStringZ(data) - data = unsafe.Add(data, len(name)) - - return rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset) -} - -// rawFieldByNameFunc returns nearly the same value as FieldByNameFunc but without converting the -// Type member to an interface. -// -// For internal use only. -func (t *rawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, []int, bool) { - if t.Kind() != Struct { - panic(errTypeField) - } - - type fieldWalker struct { - t *rawType - index []int - } - - queue := make([]fieldWalker, 0, 4) - queue = append(queue, fieldWalker{t, nil}) - - for len(queue) > 0 { - type result struct { - r rawStructField - index []int - } - - var found []result - var nextlevel []fieldWalker - - // For all the structs at this level.. - for _, ll := range queue { - // Iterate over all the fields looking for the matching name - // Also calculate field offset. - - descriptor := (*structType)(unsafe.Pointer(ll.t.underlying())) - field := &descriptor.fields[0] - - for i := uint16(0); i < descriptor.numField; i++ { - data := field.data - - // Read some flags of this field, like whether the field is an embedded - // field. See structFieldFlagAnonymous and similar flags. - flagsByte := *(*byte)(data) - data = unsafe.Add(data, 1) - - offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) - data = unsafe.Add(data, lenOffs) - - name := readStringZ(data) - data = unsafe.Add(data, len(name)) - if match(name) { - found = append(found, result{ - rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset), - append(ll.index[:len(ll.index):len(ll.index)], int(i)), - }) - } - - structOrPtrToStruct := field.fieldType.Kind() == Struct || (field.fieldType.Kind() == Pointer && field.fieldType.elem().Kind() == Struct) - if flagsByte&structFieldFlagIsEmbedded == structFieldFlagIsEmbedded && structOrPtrToStruct { - embedded := field.fieldType - if embedded.Kind() == Pointer { - embedded = embedded.elem() - } - - nextlevel = append(nextlevel, fieldWalker{ - t: embedded, - index: append(ll.index[:len(ll.index):len(ll.index)], int(i)), - }) - } - - // update offset/field pointer if there *is* a next field - if i < descriptor.numField-1 { - // Increment pointer to the next field. - field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{}))) - } - } - } - - // found multiple hits at this level - if len(found) > 1 { - return rawStructField{}, nil, false - } - - // found the field we were looking for - if len(found) == 1 { - r := found[0] - return r.r, r.index, true - } - - // else len(found) == 0, move on to the next level - queue = append(queue[:0], nextlevel...) - } - - // didn't find it - return rawStructField{}, nil, false -} - -// Bits returns the number of bits that this type uses. It is only valid for -// arithmetic types (integers, floats, and complex numbers). For other types, it -// will panic. -func (t *rawType) Bits() int { - kind := t.Kind() - if kind >= Int && kind <= Complex128 { - return int(t.Size()) * 8 - } - panic(errTypeBits) + return toType(reflectlite.TypeOf(i)) } -// Len returns the number of elements in this array. It panics of the type kind -// is not Array. -func (t *rawType) Len() int { - if t.Kind() != Array { - panic(errTypeLen) - } - - return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen) -} - -// NumField returns the number of fields of a struct type. It panics for other -// type kinds. -func (t *rawType) NumField() int { - if t.Kind() != Struct { - panic(errTypeNumField) - } - return int((*structType)(unsafe.Pointer(t.underlying())).numField) -} - -// Size returns the size in bytes of a given type. It is similar to -// unsafe.Sizeof. -func (t *rawType) Size() uintptr { - switch t.Kind() { - case Bool, Int8, Uint8: - return 1 - case Int16, Uint16: - return 2 - case Int32, Uint32: - return 4 - case Int64, Uint64: - return 8 - case Int, Uint: - return unsafe.Sizeof(int(0)) - case Uintptr: - return unsafe.Sizeof(uintptr(0)) - case Float32: - return 4 - case Float64: - return 8 - case Complex64: - return 8 - case Complex128: - return 16 - case String: - return unsafe.Sizeof("") - case UnsafePointer, Chan, Map, Pointer: - return unsafe.Sizeof(uintptr(0)) - case Slice: - return unsafe.Sizeof([]int{}) - case Interface: - return unsafe.Sizeof(interface{}(nil)) - case Func: - var f func() - return unsafe.Sizeof(f) - case Array: - return t.elem().Size() * uintptr(t.Len()) - case Struct: - u := t.underlying() - return uintptr((*structType)(unsafe.Pointer(u)).size) - default: - panic("unimplemented: size of type") - } -} - -// Align returns the alignment of this type. It is similar to calling -// unsafe.Alignof. -func (t *rawType) Align() int { - switch t.Kind() { - case Bool, Int8, Uint8: - return int(unsafe.Alignof(int8(0))) - case Int16, Uint16: - return int(unsafe.Alignof(int16(0))) - case Int32, Uint32: - return int(unsafe.Alignof(int32(0))) - case Int64, Uint64: - return int(unsafe.Alignof(int64(0))) - case Int, Uint: - return int(unsafe.Alignof(int(0))) - case Uintptr: - return int(unsafe.Alignof(uintptr(0))) - case Float32: - return int(unsafe.Alignof(float32(0))) - case Float64: - return int(unsafe.Alignof(float64(0))) - case Complex64: - return int(unsafe.Alignof(complex64(0))) - case Complex128: - return int(unsafe.Alignof(complex128(0))) - case String: - return int(unsafe.Alignof("")) - case UnsafePointer, Chan, Map, Pointer: - return int(unsafe.Alignof(uintptr(0))) - case Slice: - return int(unsafe.Alignof([]int(nil))) - case Interface: - return int(unsafe.Alignof(interface{}(nil))) - case Func: - var f func() - return int(unsafe.Alignof(f)) - case Struct: - numField := t.NumField() - alignment := 1 - for i := 0; i < numField; i++ { - fieldAlignment := t.rawField(i).Type.Align() - if fieldAlignment > alignment { - alignment = fieldAlignment - } - } - return alignment - case Array: - return t.elem().Align() - default: - panic("unimplemented: alignment of type") - } -} - -func (r *rawType) gcLayout() unsafe.Pointer { - kind := r.Kind() - - if kind < String { - return gclayout.NoPtrs - } - - switch kind { - case Pointer, UnsafePointer, Chan, Map: - return gclayout.Pointer - case String: - return gclayout.String - case Slice: - return gclayout.Slice - } - - // Unknown (for now); let the conservative pointer scanning handle it - return nil +func PtrTo(t Type) Type { + return PointerTo(t) } -// FieldAlign returns the alignment if this type is used in a struct field. It -// is currently an alias for Align() but this might change in the future. -func (t *rawType) FieldAlign() int { - return t.Align() +func PointerTo(t Type) Type { + return toType(reflectlite.PointerTo(toRawType(t))) } -// AssignableTo returns whether a value of type t can be assigned to a variable -// of type u. func (t *rawType) AssignableTo(u Type) bool { - if t == u.(*rawType) { - return true - } - - if t.underlying() == u.(*rawType).underlying() && (!t.isNamed() || !u.(*rawType).isNamed()) { - return true - } - - if u.Kind() == Interface && u.NumMethod() == 0 { - return true - } - - if u.Kind() == Interface { - panic("reflect: unimplemented: AssignableTo with interface") - } - return false -} - -func (t *rawType) Implements(u Type) bool { - if u.Kind() != Interface { - panic("reflect: non-interface type passed to Type.Implements") - } - return t.AssignableTo(u) -} - -// Comparable returns whether values of this type can be compared to each other. -func (t *rawType) Comparable() bool { - return (t.meta & flagComparable) == flagComparable -} - -// isBinary returns if the hashmapAlgorithmBinary functions can be used on this type -func (t *rawType) isBinary() bool { - return (t.meta & flagIsBinary) == flagIsBinary -} - -func (t *rawType) ChanDir() ChanDir { - if t.Kind() != Chan { - panic(errTypeChanDir) - } - - dir := int((*elemType)(unsafe.Pointer(t)).numMethod) - - // nummethod is overloaded for channel to store channel direction - return ChanDir(dir) + return t.RawType.AssignableTo(&(u.(*rawType).RawType)) } func (t *rawType) ConvertibleTo(u Type) bool { panic("unimplemented: (reflect.Type).ConvertibleTo()") } -func (t *rawType) IsVariadic() bool { - panic("unimplemented: (reflect.Type).IsVariadic()") -} - -func (t *rawType) NumIn() int { - panic("unimplemented: (reflect.Type).NumIn()") -} - -func (t *rawType) NumOut() int { - panic("unimplemented: (reflect.Type).NumOut()") -} - -func (t *rawType) NumMethod() int { - - if t.isNamed() { - return int((*namedType)(unsafe.Pointer(t)).numMethod) - } - - switch t.Kind() { - case Pointer: - return int((*ptrType)(unsafe.Pointer(t)).numMethod) - case Struct: - return int((*structType)(unsafe.Pointer(t)).numMethod) - case Interface: - //FIXME: Use len(methods) - return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod() - } - - // Other types have no methods attached. Note we don't panic here. - return 0 -} - -// Read and return a null terminated string starting from data. -func readStringZ(data unsafe.Pointer) string { - start := data - var len uintptr - for *(*byte)(data) != 0 { - len++ - data = unsafe.Add(data, 1) // C: data++ - } - - return *(*string)(unsafe.Pointer(&stringHeader{ - data: start, - len: len, - })) -} - -func (t *rawType) name() string { - ntype := (*namedType)(unsafe.Pointer(t)) - return readStringZ(unsafe.Pointer(&ntype.name[0])) -} - -func (t *rawType) Name() string { - if t.isNamed() { - name := t.name() - for i := 0; i < len(name); i++ { - if name[i] == '.' { - return name[i+1:] - } - } - panic("corrupt name data") - } - - if kind := t.Kind(); kind < UnsafePointer { - return t.Kind().String() - } else if kind == UnsafePointer { - return "Pointer" - } - - return "" -} - -func (t *rawType) Key() Type { - return t.key() +func (t *rawType) Implements(u Type) bool { + return t.RawType.Implements(&(u.(*rawType).RawType)) } -func (t rawType) In(i int) Type { +func (t *rawType) In(i int) Type { panic("unimplemented: (reflect.Type).In()") } -func (t rawType) Out(i int) Type { - panic("unimplemented: (reflect.Type).Out()") -} - -// OverflowComplex reports whether the complex128 x cannot be represented by type t. -// It panics if t's Kind is not Complex64 or Complex128. -func (t rawType) OverflowComplex(x complex128) bool { - k := t.Kind() - switch k { - case Complex64: - return overflowFloat32(real(x)) || overflowFloat32(imag(x)) - case Complex128: - return false - } - panic("reflect: OverflowComplex of non-complex type") -} - -// OverflowFloat reports whether the float64 x cannot be represented by type t. -// It panics if t's Kind is not Float32 or Float64. -func (t rawType) OverflowFloat(x float64) bool { - k := t.Kind() - switch k { - case Float32: - return overflowFloat32(x) - case Float64: - return false - } - panic("reflect: OverflowFloat of non-float type") -} - -// OverflowInt reports whether the int64 x cannot be represented by type t. -// It panics if t's Kind is not Int, Int8, Int16, Int32, or Int64. -func (t rawType) OverflowInt(x int64) bool { - k := t.Kind() - switch k { - case Int, Int8, Int16, Int32, Int64: - bitSize := t.Size() * 8 - trunc := (x << (64 - bitSize)) >> (64 - bitSize) - return x != trunc - } - panic("reflect: OverflowInt of non-int type") +func (t *rawType) IsVariadic() bool { + panic("unimplemented: (reflect.Type).IsVariadic()") } -// OverflowUint reports whether the uint64 x cannot be represented by type t. -// It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. -func (t rawType) OverflowUint(x uint64) bool { - k := t.Kind() - switch k { - case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: - bitSize := t.Size() * 8 - trunc := (x << (64 - bitSize)) >> (64 - bitSize) - return x != trunc - } - panic("reflect: OverflowUint of non-uint type") +func (t *rawType) Key() Type { + return toType(t.RawType.Key()) } -func (t rawType) Method(i int) Method { +func (t *rawType) Method(i int) Method { panic("unimplemented: (reflect.Type).Method()") } -func (t rawType) MethodByName(name string) (Method, bool) { +func (t *rawType) MethodByName(name string) (Method, bool) { panic("unimplemented: (reflect.Type).MethodByName()") } -func (t *rawType) PkgPath() string { - if t.isNamed() { - ntype := (*namedType)(unsafe.Pointer(t)) - return readStringZ(unsafe.Pointer(ntype.pkg)) - } - - return "" -} - -func (t *rawType) FieldByName(name string) (StructField, bool) { - if t.Kind() != Struct { - panic(errTypeFieldByName) - } - - field, index, ok := t.rawFieldByNameFunc(func(n string) bool { return n == name }) - if !ok { - return StructField{}, false - } - - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: index, - }, true -} - -func (t *rawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { - if t.Kind() != Struct { - panic(TypeError{"FieldByNameFunc"}) - } - - field, index, ok := t.rawFieldByNameFunc(match) - if !ok { - return StructField{}, false - } - - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: index, - }, true -} - -func (t *rawType) FieldByIndex(index []int) StructField { - ftype := t - var field rawStructField - - for _, n := range index { - structOrPtrToStruct := ftype.Kind() == Struct || (ftype.Kind() == Pointer && ftype.elem().Kind() == Struct) - if !structOrPtrToStruct { - panic(errTypeFieldByIndex) - } - - if ftype.Kind() == Pointer { - ftype = ftype.elem() - } - - field = ftype.rawField(n) - ftype = field.Type - } - - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: index, - } -} - -// A StructField describes a single field in a struct. -type StructField struct { - // Name indicates the field name. - Name string - - // PkgPath is the package path where the struct containing this field is - // declared for unexported fields, or the empty string for exported fields. - PkgPath string - - Type Type - Tag StructTag // field tag string - Offset uintptr - Index []int // index sequence for Type.FieldByIndex - Anonymous bool -} - -// IsExported reports whether the field is exported. -func (f StructField) IsExported() bool { - return f.PkgPath == "" -} - -// rawStructField is the same as StructField but with the Type member replaced -// with rawType. For internal use only. Avoiding this conversion to the Type -// interface improves code size in many cases. -type rawStructField struct { - Name string - PkgPath string - Type *rawType - Tag StructTag - Offset uintptr - Anonymous bool -} - -// A StructTag is the tag string in a struct field. -type StructTag string - -// TODO: it would be feasible to do the key/value splitting at compile time, -// avoiding the code size cost of doing it at runtime - -// Get returns the value associated with key in the tag string. -func (tag StructTag) Get(key string) string { - v, _ := tag.Lookup(key) - return v -} - -// Lookup returns the value associated with key in the tag string. -func (tag StructTag) Lookup(key string) (value string, ok bool) { - for tag != "" { - // Skip leading space. - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // Scan to colon. A space, a quote or a control character is a syntax error. - // Strictly speaking, control chars include the range [0x7f, 0x9f], not just - // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters - // as it is simpler to inspect the tag's bytes than the tag's runes. - i = 0 - for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { - i++ - } - if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // Scan quoted string to find value. - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if key == name { - value, err := unquote(qvalue) - if err != nil { - break - } - return value, true - } - } - return "", false -} - -// TypeError is the error that is used in a panic when invoking a method on a -// type that is not applicable to that type. -type TypeError struct { - Method string -} - -func (e *TypeError) Error() string { - return "reflect: call of reflect.Type." + e.Method + " on invalid type" -} - -func align(offset uintptr, alignment uintptr) uintptr { - return (offset + alignment - 1) &^ (alignment - 1) -} - -func SliceOf(t Type) Type { - panic("unimplemented: reflect.SliceOf()") -} - -func ArrayOf(n int, t Type) Type { - panic("unimplemented: reflect.ArrayOf()") +func (t *rawType) NumIn() int { + panic("unimplemented: (reflect.Type).NumIn()") } -func StructOf([]StructField) Type { - panic("unimplemented: reflect.StructOf()") +func (t *rawType) NumOut() int { + panic("unimplemented: (reflect.Type).NumOut()") } -func MapOf(key, value Type) Type { - panic("unimplemented: reflect.MapOf()") +func (t *rawType) Out(i int) Type { + panic("unimplemented: (reflect.Type).Out()") } -func FuncOf(in, out []Type, variadic bool) Type { - panic("unimplemented: reflect.FuncOf()") +func (t *rawType) Elem() Type { + return toType(t.RawType.Elem()) } -const maxVarintLen32 = 5 - -// encoding/binary.Uvarint, specialized for uint32 -func uvarint32(buf []byte) (uint32, int) { - var x uint32 - var s uint - for i, b := range buf { - if b < 0x80 { - return x | uint32(b)< unsafe.Sizeof(uintptr(0)) { - return int64(*(*int)(v.value)) - } else { - return int64(int(uintptr(v.value))) - } - case Int8: - if v.isIndirect() { - return int64(*(*int8)(v.value)) - } else { - return int64(int8(uintptr(v.value))) - } - case Int16: - if v.isIndirect() { - return int64(*(*int16)(v.value)) - } else { - return int64(int16(uintptr(v.value))) - } - case Int32: - if v.isIndirect() || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) { - return int64(*(*int32)(v.value)) - } else { - return int64(int32(uintptr(v.value))) - } - case Int64: - if v.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) { - return int64(*(*int64)(v.value)) - } else { - return int64(int64(uintptr(v.value))) - } - default: - panic(&ValueError{Method: "Int", Kind: v.Kind()}) - } -} - -// CanUint reports whether Uint can be used without panicking. -func (v Value) CanUint() bool { - switch v.Kind() { - case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return true - default: - return false - } -} - -func (v Value) Uint() uint64 { - switch v.Kind() { - case Uintptr: - if v.isIndirect() { - return uint64(*(*uintptr)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint8: - if v.isIndirect() { - return uint64(*(*uint8)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint16: - if v.isIndirect() { - return uint64(*(*uint16)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint: - if v.isIndirect() || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint32: - if v.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint32)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint64: - if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint64)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - default: - panic(&ValueError{Method: "Uint", Kind: v.Kind()}) - } -} - -// CanFloat reports whether Float can be used without panicking. -func (v Value) CanFloat() bool { - switch v.Kind() { - case Float32, Float64: - return true - default: - return false - } -} - -func (v Value) Float32() float32 { - switch v.Kind() { - case Float32: - if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { - // The float is stored as an external value on systems with 16-bit - // pointers. - return *(*float32)(v.value) - } else { - // The float is directly stored in the interface value on systems - // with 32-bit and 64-bit pointers. - return *(*float32)(unsafe.Pointer(&v.value)) - } - - case Float64: - return float32(v.Float()) - - } - - panic(&ValueError{Method: "Float", Kind: v.Kind()}) -} - -func (v Value) Float() float64 { - switch v.Kind() { - case Float32: - if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { - // The float is stored as an external value on systems with 16-bit - // pointers. - return float64(*(*float32)(v.value)) - } else { - // The float is directly stored in the interface value on systems - // with 32-bit and 64-bit pointers. - return float64(*(*float32)(unsafe.Pointer(&v.value))) - } - case Float64: - if v.isIndirect() || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) { - // For systems with 16-bit and 32-bit pointers. - return *(*float64)(v.value) - } else { - // The float is directly stored in the interface value on systems - // with 64-bit pointers. - return *(*float64)(unsafe.Pointer(&v.value)) - } - default: - panic(&ValueError{Method: "Float", Kind: v.Kind()}) - } -} - -// CanComplex reports whether Complex can be used without panicking. -func (v Value) CanComplex() bool { - switch v.Kind() { - case Complex64, Complex128: - return true - default: - return false - } -} - -func (v Value) Complex() complex128 { - switch v.Kind() { - case Complex64: - if v.isIndirect() || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) { - // The complex number is stored as an external value on systems with - // 16-bit and 32-bit pointers. - return complex128(*(*complex64)(v.value)) - } else { - // The complex number is directly stored in the interface value on - // systems with 64-bit pointers. - return complex128(*(*complex64)(unsafe.Pointer(&v.value))) - } - case Complex128: - // This is a 128-bit value, which is always stored as an external value. - // It may be stored in the pointer directly on very uncommon - // architectures with 128-bit pointers, however. - return *(*complex128)(v.value) - default: - panic(&ValueError{Method: "Complex", Kind: v.Kind()}) - } -} - -func (v Value) String() string { - switch v.Kind() { - case String: - // A string value is always bigger than a pointer as it is made of a - // pointer and a length. - return *(*string)(v.value) - default: - // Special case because of the special treatment of .String() in Go. - return "<" + v.typecode.String() + " Value>" - } -} - -func (v Value) Bytes() []byte { - switch v.Kind() { - case Slice: - if v.typecode.elem().Kind() != Uint8 { - panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) - } - return *(*[]byte)(v.value) - - case Array: - v.checkAddressable() - - if v.typecode.elem().Kind() != Uint8 { - panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) - } - - // Small inline arrays are not addressable, so we only have to - // handle addressable arrays which will be stored as pointers - // in v.value - return unsafe.Slice((*byte)(v.value), v.Len()) - } - - panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) + return Value{v.Value.Addr()} } func (v Value) Slice(i, j int) Value { - switch v.Kind() { - case Slice: - hdr := *(*sliceHeader)(v.value) - i, j := uintptr(i), uintptr(j) - - if j < i || hdr.cap < j { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - hdr.len = j - i - hdr.cap = hdr.cap - i - hdr.data = unsafe.Add(hdr.data, i*elemSize) - - return Value{ - typecode: v.typecode, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - - case Array: - v.checkAddressable() - buf, length := buflen(v) - i, j := uintptr(i), uintptr(j) - if j < i || length < j { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - var hdr sliceHeader - hdr.len = j - i - hdr.cap = length - i - hdr.data = unsafe.Add(buf, i*elemSize) - - sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr - return Value{ - typecode: sliceType, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - - case String: - i, j := uintptr(i), uintptr(j) - str := *(*stringHeader)(v.value) - - if j < i || str.len < j { - slicePanic() - } - - hdr := stringHeader{ - data: unsafe.Add(str.data, i), - len: j - i, - } - - return Value{ - typecode: v.typecode, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - } - - panic(&ValueError{Method: "Slice", Kind: v.Kind()}) + return Value{v.Value.Slice(i, j)} } func (v Value) Slice3(i, j, k int) Value { - switch v.Kind() { - case Slice: - hdr := *(*sliceHeader)(v.value) - i, j, k := uintptr(i), uintptr(j), uintptr(k) - - if j < i || k < j || hdr.len < k { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - hdr.len = j - i - hdr.cap = k - i - hdr.data = unsafe.Add(hdr.data, i*elemSize) - - return Value{ - typecode: v.typecode, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - - case Array: - v.checkAddressable() - buf, length := buflen(v) - i, j, k := uintptr(i), uintptr(j), uintptr(k) - if j < i || k < j || length < k { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - var hdr sliceHeader - hdr.len = j - i - hdr.cap = k - i - hdr.data = unsafe.Add(buf, i*elemSize) - - sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr - return Value{ - typecode: sliceType, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - } - - panic("unimplemented: (reflect.Value).Slice3()") -} - -//go:linkname maplen runtime.hashmapLen -func maplen(p unsafe.Pointer) int - -//go:linkname chanlen runtime.chanLen -func chanlen(p unsafe.Pointer) int - -// Len returns the length of this value for slices, strings, arrays, channels, -// and maps. For other types, it panics. -func (v Value) Len() int { - switch v.typecode.Kind() { - case Array: - return v.typecode.Len() - case Chan: - return chanlen(v.pointer()) - case Map: - return maplen(v.pointer()) - case Slice: - return int((*sliceHeader)(v.value).len) - case String: - return int((*stringHeader)(v.value).len) - default: - panic(&ValueError{Method: "Len", Kind: v.Kind()}) - } -} - -//go:linkname chancap runtime.chanCap -func chancap(p unsafe.Pointer) int - -// Cap returns the capacity of this value for arrays, channels and slices. -// For other types, it panics. -func (v Value) Cap() int { - switch v.typecode.Kind() { - case Array: - return v.typecode.Len() - case Chan: - return chancap(v.pointer()) - case Slice: - return int((*sliceHeader)(v.value).cap) - default: - panic(&ValueError{Method: "Cap", Kind: v.Kind()}) - } -} - -//go:linkname mapclear runtime.hashmapClear -func mapclear(p unsafe.Pointer) - -// Clear clears the contents of a map or zeros the contents of a slice -// -// It panics if v's Kind is not Map or Slice. -func (v Value) Clear() { - switch v.typecode.Kind() { - case Map: - mapclear(v.pointer()) - case Slice: - hdr := (*sliceHeader)(v.value) - elemSize := v.typecode.underlying().elem().Size() - memzero(hdr.data, elemSize*hdr.len) - default: - panic(&ValueError{Method: "Clear", Kind: v.Kind()}) - } -} - -// NumField returns the number of fields of this struct. It panics for other -// value types. -func (v Value) NumField() int { - return v.typecode.NumField() + return Value{v.Value.Slice3(i, j, k)} } func (v Value) Elem() Value { - switch v.Kind() { - case Ptr: - ptr := v.pointer() - if ptr == nil { - return Value{} - } - // Don't copy RO flags - flags := (v.flags & (valueFlagIndirect | valueFlagExported)) | valueFlagIndirect - return Value{ - typecode: v.typecode.elem(), - value: ptr, - flags: flags, - } - case Interface: - typecode, value := decomposeInterface(*(*interface{})(v.value)) - return Value{ - typecode: (*rawType)(typecode), - value: value, - flags: v.flags &^ valueFlagIndirect, - } - default: - panic(&ValueError{Method: "Elem", Kind: v.Kind()}) - } + return Value{v.Value.Elem()} } // Field returns the value of the i'th field of this struct. func (v Value) Field(i int) Value { - if v.Kind() != Struct { - panic(&ValueError{Method: "Field", Kind: v.Kind()}) - } - structField := v.typecode.rawField(i) - - // Copy flags but clear EmbedRO; we're not an embedded field anymore - flags := v.flags & ^valueFlagEmbedRO - if structField.PkgPath != "" { - // No PkgPath => not exported. - // Clear exported flag even if the parent was exported. - flags &^= valueFlagExported - - // Update the RO flag - if structField.Anonymous { - // Embedded field - flags |= valueFlagEmbedRO - } else { - flags |= valueFlagStickyRO - } - } else { - // Parent field may not have been exported but we are - flags |= valueFlagExported - } - - size := v.typecode.Size() - fieldType := structField.Type - fieldSize := fieldType.Size() - if v.isIndirect() || fieldSize > unsafe.Sizeof(uintptr(0)) { - // v.value was already a pointer to the value and it should stay that - // way. - return Value{ - flags: flags, - typecode: fieldType, - value: unsafe.Add(v.value, structField.Offset), - } - } - - // The fieldSize is smaller than uintptr, which means that the value will - // have to be stored directly in the interface value. - - if fieldSize == 0 { - // The struct field is zero sized. - // This is a rare situation, but because it's undefined behavior - // to shift the size of the value (zeroing the value), handle this - // situation explicitly. - return Value{ - flags: flags, - typecode: fieldType, - value: unsafe.Pointer(nil), - } - } - - if size > unsafe.Sizeof(uintptr(0)) { - // The value was not stored in the interface before but will be - // afterwards, so load the value (from the correct offset) and return - // it. - ptr := unsafe.Add(v.value, structField.Offset) - value := unsafe.Pointer(loadValue(ptr, fieldSize)) - return Value{ - flags: flags &^ valueFlagIndirect, - typecode: fieldType, - value: value, - } - } - - // The value was already stored directly in the interface and it still - // is. Cut out the part of the value that we need. - value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize) - return Value{ - flags: flags, - typecode: fieldType, - value: unsafe.Pointer(value), - } + return Value{v.Value.Field(i)} } -var uint8Type = TypeOf(uint8(0)).(*rawType) - func (v Value) Index(i int) Value { - switch v.Kind() { - case Slice: - // Extract an element from the slice. - slice := *(*sliceHeader)(v.value) - if uint(i) >= uint(slice.len) { - panic("reflect: slice index out of range") - } - flags := (v.flags & (valueFlagExported | valueFlagIndirect)) | valueFlagIndirect | v.flags.ro() - elem := Value{ - typecode: v.typecode.elem(), - flags: flags, - } - elem.value = unsafe.Add(slice.data, elem.typecode.Size()*uintptr(i)) // pointer to new value - return elem - case String: - // Extract a character from a string. - // A string is never stored directly in the interface, but always as a - // pointer to the string value. - // Keeping valueFlagExported if set, but don't set valueFlagIndirect - // otherwise CanSet will return true for string elements (which is bad, - // strings are read-only). - s := *(*stringHeader)(v.value) - if uint(i) >= uint(s.len) { - panic("reflect: string index out of range") - } - return Value{ - typecode: uint8Type, - value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Add(s.data, i)))), - flags: v.flags & valueFlagExported, - } - case Array: - // Extract an element from the array. - elemType := v.typecode.elem() - elemSize := elemType.Size() - size := v.typecode.Size() - if size == 0 { - // The element size is 0 and/or the length of the array is 0. - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - } - } - if elemSize > unsafe.Sizeof(uintptr(0)) { - // The resulting value doesn't fit in a pointer so must be - // indirect. Also, because size != 0 this implies that the array - // length must be != 0, and thus that the total size is at least - // elemSize. - addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: addr, - } - } - - if size > unsafe.Sizeof(uintptr(0)) || v.isIndirect() { - // The element fits in a pointer, but the array is not stored in the pointer directly. - // Load the value from the pointer. - addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value - value := addr - if !v.isIndirect() { - // Use a pointer to the value (don't load the value) if the - // 'indirect' flag is set. - value = unsafe.Pointer(loadValue(addr, elemSize)) - } - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: value, - } - } - - // The value fits in a pointer, so extract it with some shifting and - // masking. - offset := elemSize * uintptr(i) - value := maskAndShift(uintptr(v.value), offset, elemSize) - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: unsafe.Pointer(value), - } - default: - panic(&ValueError{Method: "Index", Kind: v.Kind()}) - } -} - -func (v Value) NumMethod() int { - if v.typecode == nil { - panic(&ValueError{Method: "reflect.Value.NumMethod", Kind: Invalid}) - } - return v.typecode.NumMethod() -} - -// OverflowFloat reports whether the float64 x cannot be represented by v's type. -// It panics if v's Kind is not Float32 or Float64. -func (v Value) OverflowFloat(x float64) bool { - k := v.Kind() - switch k { - case Float32: - return overflowFloat32(x) - case Float64: - return false - } - panic(&ValueError{Method: "reflect.Value.OverflowFloat", Kind: v.Kind()}) -} - -func overflowFloat32(x float64) bool { - if x < 0 { - x = -x - } - return math.MaxFloat32 < x && x <= math.MaxFloat64 + return Value{v.Value.Index(i)} } func (v Value) MapKeys() []Value { - if v.Kind() != Map { - panic(&ValueError{Method: "MapKeys", Kind: v.Kind()}) - } - - // empty map - if v.Len() == 0 { - return nil - } - - keys := make([]Value, 0, v.Len()) - - it := hashmapNewIterator() - k := New(v.typecode.Key()) - e := New(v.typecode.Elem()) - - keyType := v.typecode.key() - keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 - shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() - - for hashmapNext(v.pointer(), it, k.value, e.value) { - if shouldUnpackInterface { - intf := *(*interface{})(k.value) - v := ValueOf(intf) - keys = append(keys, v) - } else { - keys = append(keys, k.Elem()) - } - k = New(v.typecode.Key()) - } - - return keys + keys := v.Value.MapKeys() + return *(*[]Value)(unsafe.Pointer(&keys)) } -//go:linkname hashmapStringGet runtime.hashmapStringGet -func hashmapStringGet(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool - -//go:linkname hashmapBinaryGet runtime.hashmapBinaryGet -func hashmapBinaryGet(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool - -//go:linkname hashmapInterfaceGet runtime.hashmapInterfaceGet -func hashmapInterfaceGet(m unsafe.Pointer, key interface{}, value unsafe.Pointer, valueSize uintptr) bool - func (v Value) MapIndex(key Value) Value { - if v.Kind() != Map { - panic(&ValueError{Method: "MapIndex", Kind: v.Kind()}) - } - - vkey := v.typecode.key() - - // compare key type with actual key type of map - if !key.typecode.AssignableTo(vkey) { - // type error? - panic("reflect.Value.MapIndex: incompatible types for key") - } - - elemType := v.typecode.Elem() - elem := New(elemType) - - if vkey.Kind() == String { - if ok := hashmapStringGet(v.pointer(), *(*string)(key.value), elem.value, elemType.Size()); !ok { - return Value{} - } - return elem.Elem() - } else if vkey.isBinary() { - var keyptr unsafe.Pointer - if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - keyptr = key.value - } else { - keyptr = unsafe.Pointer(&key.value) - } - //TODO(dgryski): zero out padding bytes in key, if any - if ok := hashmapBinaryGet(v.pointer(), keyptr, elem.value, elemType.Size()); !ok { - return Value{} - } - return elem.Elem() - } else { - if ok := hashmapInterfaceGet(v.pointer(), key.Interface(), elem.value, elemType.Size()); !ok { - return Value{} - } - return elem.Elem() - } + return Value{v.Value.MapIndex(key.Value)} } //go:linkname hashmapNewIterator runtime.hashmapNewIterator @@ -1102,988 +68,99 @@ func hashmapNewIterator() unsafe.Pointer func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool func (v Value) MapRange() *MapIter { - if v.Kind() != Map { - panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) - } - - keyType := v.typecode.key() - - keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 - shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() - - return &MapIter{ - m: v, - it: hashmapNewIterator(), - unpackKeyInterface: shouldUnpackInterface, - } + return (*MapIter)(v.Value.MapRange()) } -type MapIter struct { - m Value - it unsafe.Pointer - key Value - val Value - - valid bool - unpackKeyInterface bool -} +type MapIter reflectlite.MapIter func (it *MapIter) Key() Value { - if !it.valid { - panic("reflect.MapIter.Key called on invalid iterator") - } - - if it.unpackKeyInterface { - intf := *(*interface{})(it.key.value) - v := ValueOf(intf) - return v - } - - return it.key.Elem() + return Value{((*reflectlite.MapIter)(it)).Key()} } func (it *MapIter) Value() Value { - if !it.valid { - panic("reflect.MapIter.Value called on invalid iterator") - } - - return it.val.Elem() + return Value{((*reflectlite.MapIter)(it)).Value()} } func (it *MapIter) Next() bool { - it.key = New(it.m.typecode.Key()) - it.val = New(it.m.typecode.Elem()) - - it.valid = hashmapNext(it.m.pointer(), it.it, it.key.value, it.val.value) - return it.valid + return ((*reflectlite.MapIter)(it)).Next() } func (v Value) Set(x Value) { - v.checkAddressable() - v.checkRO() - if !x.typecode.AssignableTo(v.typecode) { - panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String()) - } - - if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { - // move the value of x back into the interface, if possible - if x.isIndirect() && x.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { - x.value = unsafe.Pointer(loadValue(x.value, x.typecode.Size())) - } - - intf := composeInterface(unsafe.Pointer(x.typecode), x.value) - x = Value{ - typecode: v.typecode, - value: unsafe.Pointer(&intf), - } - } - - size := v.typecode.Size() - if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() { - storeValue(v.value, size, uintptr(x.value)) - } else { - memcpy(v.value, x.value, size) - } -} - -func (v Value) SetZero() { - v.checkAddressable() - v.checkRO() - size := v.typecode.Size() - memzero(v.value, size) -} - -func (v Value) SetBool(x bool) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Bool: - *(*bool)(v.value) = x - default: - panic(&ValueError{Method: "SetBool", Kind: v.Kind()}) - } -} - -func (v Value) SetInt(x int64) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Int: - *(*int)(v.value) = int(x) - case Int8: - *(*int8)(v.value) = int8(x) - case Int16: - *(*int16)(v.value) = int16(x) - case Int32: - *(*int32)(v.value) = int32(x) - case Int64: - *(*int64)(v.value) = x - default: - panic(&ValueError{Method: "SetInt", Kind: v.Kind()}) - } -} - -func (v Value) SetUint(x uint64) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Uint: - *(*uint)(v.value) = uint(x) - case Uint8: - *(*uint8)(v.value) = uint8(x) - case Uint16: - *(*uint16)(v.value) = uint16(x) - case Uint32: - *(*uint32)(v.value) = uint32(x) - case Uint64: - *(*uint64)(v.value) = x - case Uintptr: - *(*uintptr)(v.value) = uintptr(x) - default: - panic(&ValueError{Method: "SetUint", Kind: v.Kind()}) - } -} - -func (v Value) SetFloat(x float64) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Float32: - *(*float32)(v.value) = float32(x) - case Float64: - *(*float64)(v.value) = x - default: - panic(&ValueError{Method: "SetFloat", Kind: v.Kind()}) - } -} - -func (v Value) SetComplex(x complex128) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Complex64: - *(*complex64)(v.value) = complex64(x) - case Complex128: - *(*complex128)(v.value) = x - default: - panic(&ValueError{Method: "SetComplex", Kind: v.Kind()}) - } -} - -func (v Value) SetString(x string) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case String: - *(*string)(v.value) = x - default: - panic(&ValueError{Method: "SetString", Kind: v.Kind()}) - } -} - -func (v Value) SetBytes(x []byte) { - v.checkAddressable() - v.checkRO() - if v.typecode.Kind() != Slice || v.typecode.elem().Kind() != Uint8 { - panic("reflect.Value.SetBytes called on not []byte") - } - - // copy the header contents over - *(*[]byte)(v.value) = x -} - -func (v Value) SetCap(n int) { - panic("unimplemented: (reflect.Value).SetCap()") -} - -func (v Value) SetLen(n int) { - if v.typecode.Kind() != Slice { - panic(&ValueError{Method: "reflect.Value.SetLen", Kind: v.Kind()}) - } - v.checkAddressable() - hdr := (*sliceHeader)(v.value) - if int(uintptr(n)) != n || uintptr(n) > hdr.cap { - panic("reflect.Value.SetLen: slice length out of range") - } - hdr.len = uintptr(n) -} - -func (v Value) checkAddressable() { - if !v.isIndirect() { - panic("reflect: value is not addressable") - } -} - -// OverflowInt reports whether the int64 x cannot be represented by v's type. -// It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64. -func (v Value) OverflowInt(x int64) bool { - switch v.Kind() { - case Int, Int8, Int16, Int32, Int64: - bitSize := v.typecode.Size() * 8 - trunc := (x << (64 - bitSize)) >> (64 - bitSize) - return x != trunc - } - panic(&ValueError{Method: "reflect.Value.OverflowInt", Kind: v.Kind()}) -} - -// OverflowUint reports whether the uint64 x cannot be represented by v's type. -// It panics if v's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. -func (v Value) OverflowUint(x uint64) bool { - k := v.Kind() - switch k { - case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: - bitSize := v.typecode.Size() * 8 - trunc := (x << (64 - bitSize)) >> (64 - bitSize) - return x != trunc - } - panic(&ValueError{Method: "reflect.Value.OverflowUint", Kind: v.Kind()}) + v.Value.Set(x.Value) } func (v Value) CanConvert(t Type) bool { - // TODO: Optimize this to not actually perform a conversion - _, ok := convertOp(v, t) - return ok + return v.Value.CanConvert(toRawType(t)) } func (v Value) Convert(t Type) Value { - if v, ok := convertOp(v, t); ok { - return v - } - - panic("reflect.Value.Convert: value of type " + v.typecode.String() + " cannot be converted to type " + t.String()) -} - -func convertOp(src Value, typ Type) (Value, bool) { - - // Easy check first. Do we even need to do anything? - if src.typecode.underlying() == typ.(*rawType).underlying() { - return Value{ - typecode: typ.(*rawType), - value: src.value, - flags: src.flags, - }, true - } - - if rtype := typ.(*rawType); rtype.Kind() == Interface && rtype.NumMethod() == 0 { - iface := composeInterface(unsafe.Pointer(src.typecode), src.value) - return Value{ - typecode: rtype, - value: unsafe.Pointer(&iface), - flags: valueFlagExported, - }, true - } - - switch src.Kind() { - case Int, Int8, Int16, Int32, Int64: - switch rtype := typ.(*rawType); rtype.Kind() { - case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return cvtInt(src, rtype), true - case Float32, Float64: - return cvtIntFloat(src, rtype), true - case String: - return cvtIntString(src, rtype), true - } - - case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - switch rtype := typ.(*rawType); rtype.Kind() { - case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return cvtUint(src, rtype), true - case Float32, Float64: - return cvtUintFloat(src, rtype), true - case String: - return cvtUintString(src, rtype), true - } - - case Float32, Float64: - switch rtype := typ.(*rawType); rtype.Kind() { - case Int, Int8, Int16, Int32, Int64: - return cvtFloatInt(src, rtype), true - case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return cvtFloatUint(src, rtype), true - case Float32, Float64: - return cvtFloat(src, rtype), true - } - - /* - case Complex64, Complex128: - switch src.Kind() { - case Complex64, Complex128: - return cvtComplex - } - */ - - case Slice: - switch rtype := typ.(*rawType); rtype.Kind() { - case Array: - if src.typecode.elem() == rtype.elem() && rtype.Len() <= src.Len() { - return Value{ - typecode: rtype, - value: (*sliceHeader)(src.value).data, - flags: src.flags | valueFlagIndirect, - }, true - } - case Pointer: - if rtype.Elem().Kind() == Array { - if src.typecode.elem() == rtype.elem().elem() && rtype.elem().Len() <= src.Len() { - return Value{ - typecode: rtype, - value: (*sliceHeader)(src.value).data, - flags: src.flags & (valueFlagExported | valueFlagRO), - }, true - } - } - case String: - if !src.typecode.elem().isNamed() { - switch src.Type().Elem().Kind() { - case Uint8: - return cvtBytesString(src, rtype), true - case Int32: - return cvtRunesString(src, rtype), true - } - } - } - - case String: - rtype := typ.(*rawType) - if typ.Kind() == Slice && !rtype.elem().isNamed() { - switch typ.Elem().Kind() { - case Uint8: - return cvtStringBytes(src, rtype), true - case Int32: - return cvtStringRunes(src, rtype), true - } - } - } - - // TODO(dgryski): Unimplemented: - // Chan - // Non-defined pointers types with same underlying base type - // Interface <-> Type conversions - - return Value{}, false -} - -func cvtInt(v Value, t *rawType) Value { - return makeInt(v.flags, uint64(v.Int()), t) -} - -func cvtUint(v Value, t *rawType) Value { - return makeInt(v.flags, v.Uint(), t) -} - -func cvtIntFloat(v Value, t *rawType) Value { - return makeFloat(v.flags, float64(v.Int()), t) -} - -func cvtUintFloat(v Value, t *rawType) Value { - return makeFloat(v.flags, float64(v.Uint()), t) -} - -func cvtFloatInt(v Value, t *rawType) Value { - return makeInt(v.flags, uint64(int64(v.Float())), t) -} - -func cvtFloatUint(v Value, t *rawType) Value { - return makeInt(v.flags, uint64(v.Float()), t) -} - -func cvtFloat(v Value, t *rawType) Value { - if v.Type().Kind() == Float32 && t.Kind() == Float32 { - // Don't do any conversion if both types have underlying type float32. - // This avoids converting to float64 and back, which will - // convert a signaling NaN to a quiet NaN. See issue 36400. - return makeFloat32(v.flags, v.Float32(), t) - } - return makeFloat(v.flags, v.Float(), t) -} - -//go:linkname stringToBytes runtime.stringToBytes -func stringToBytes(x string) []byte - -func cvtStringBytes(v Value, t *rawType) Value { - b := stringToBytes(*(*string)(v.value)) - return Value{ - typecode: t, - value: unsafe.Pointer(&b), - flags: v.flags, - } -} - -//go:linkname stringFromBytes runtime.stringFromBytes -func stringFromBytes(x []byte) string - -func cvtBytesString(v Value, t *rawType) Value { - s := stringFromBytes(*(*[]byte)(v.value)) - return Value{ - typecode: t, - value: unsafe.Pointer(&s), - flags: v.flags, - } -} - -func makeInt(flags valueFlags, bits uint64, 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 1: - *(*uint8)(ptr) = uint8(bits) - case 2: - *(*uint16)(ptr) = uint16(bits) - case 4: - *(*uint32)(ptr) = uint32(bits) - case 8: - *(*uint64)(ptr) = bits - } - return v -} - -func makeFloat(flags valueFlags, f float64, 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 4: - *(*float32)(ptr) = float32(f) - case 8: - *(*float64)(ptr) = f - } - return v -} - -func makeFloat32(flags valueFlags, f float32, t *rawType) Value { - v := Value{ - typecode: t, - flags: flags, - } - *(*float32)(unsafe.Pointer(&v.value)) = float32(f) - return v -} - -func cvtIntString(src Value, t *rawType) Value { - panic("cvtUintString: unimplemented") -} - -func cvtUintString(src Value, t *rawType) Value { - panic("cvtUintString: unimplemented") -} - -func cvtStringRunes(src Value, t *rawType) Value { - panic("cvsStringRunes: unimplemented") -} - -func cvtRunesString(src Value, t *rawType) Value { - panic("cvsRunesString: unimplemented") + return Value{v.Value.Convert(toRawType(t))} } //go:linkname slicePanic runtime.slicePanic func slicePanic() func MakeSlice(typ Type, len, cap int) Value { - if typ.Kind() != Slice { - panic("reflect.MakeSlice of non-slice type") - } - - rtype := typ.(*rawType) - - ulen := uint(len) - ucap := uint(cap) - maxSize := (^uintptr(0)) / 2 - elem := rtype.elem() - elementSize := elem.Size() - if elementSize > 1 { - maxSize /= uintptr(elementSize) - } - if ulen > ucap || ucap > uint(maxSize) { - slicePanic() - } - - // This can't overflow because of the above checks. - size := uintptr(ucap) * elementSize - - var slice sliceHeader - slice.cap = uintptr(ucap) - slice.len = uintptr(ulen) - layout := elem.gcLayout() - - slice.data = alloc(size, layout) - - return Value{ - typecode: rtype, - value: unsafe.Pointer(&slice), - flags: valueFlagExported, - } -} - -var zerobuffer unsafe.Pointer - -const zerobufferLen = 32 - -func init() { - // 32 characters of zero bytes - zerobufferStr := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - s := (*stringHeader)(unsafe.Pointer(&zerobufferStr)) - zerobuffer = s.data + return Value{reflectlite.MakeSlice(toRawType(typ), len, cap)} } func Zero(typ Type) Value { - size := typ.Size() - if size <= unsafe.Sizeof(uintptr(0)) { - return Value{ - typecode: typ.(*rawType), - value: nil, - flags: valueFlagExported | valueFlagRO, - } - } - - if size <= zerobufferLen { - return Value{ - typecode: typ.(*rawType), - value: unsafe.Pointer(zerobuffer), - flags: valueFlagExported | valueFlagRO, - } - } - - return Value{ - typecode: typ.(*rawType), - value: alloc(size, nil), - flags: valueFlagExported | valueFlagRO, - } + return Value{reflectlite.Zero(toRawType(typ))} } // New is the reflect equivalent of the new(T) keyword, returning a pointer to a // new value of the given type. func New(typ Type) Value { - return Value{ - typecode: pointerTo(typ.(*rawType)), - value: alloc(typ.Size(), nil), - flags: valueFlagExported, - } + return Value{reflectlite.New(toRawType(typ))} } -type funcHeader struct { - Context unsafe.Pointer - Code unsafe.Pointer -} - -type SliceHeader struct { - Data uintptr - Len intw - Cap intw -} - -// Slice header that matches the underlying structure. Used for when we switch -// to a precise GC, which needs to know exactly where pointers live. -type sliceHeader struct { - data unsafe.Pointer - len uintptr - cap uintptr -} - -type StringHeader struct { - Data uintptr - Len intw -} - -// Like sliceHeader, this type is used internally to make sure pointer and -// non-pointer fields match those of actual strings. -type stringHeader struct { - data unsafe.Pointer - len uintptr -} - -// Verify SliceHeader and StringHeader sizes. -// See https://github.com/tinygo-org/tinygo/pull/4156 -// and https://github.com/tinygo-org/tinygo/issues/1284. -var ( - _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} - _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(sliceHeader{})]byte{} - _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} - _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(stringHeader{})]byte{} -) - -type ValueError struct { - Method string - Kind Kind -} - -func (e *ValueError) Error() string { - if e.Kind == 0 { - return "reflect: call of " + e.Method + " on zero Value" - } - return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value" -} - -//go:linkname memcpy runtime.memcpy -func memcpy(dst, src unsafe.Pointer, size uintptr) - -//go:linkname memzero runtime.memzero -func memzero(ptr unsafe.Pointer, size uintptr) - -//go:linkname alloc runtime.alloc -func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer - -//go:linkname sliceAppend runtime.sliceAppend -func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) - -//go:linkname sliceCopy runtime.sliceCopy -func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int +type ValueError = reflectlite.ValueError // Copy copies the contents of src into dst until either // dst has been filled or src has been exhausted. func Copy(dst, src Value) int { - compatibleTypes := false || - // dst and src are both slices or arrays with equal types - ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && - (src.typecode.Kind() == Slice || src.typecode.Kind() == Array) && - (dst.typecode.elem() == src.typecode.elem())) || - // dst is array or slice of uint8 and src is string - ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && - dst.typecode.elem().Kind() == Uint8 && - src.typecode.Kind() == String) - - if !compatibleTypes { - panic("Copy: type mismatch: " + dst.typecode.String() + "/" + src.typecode.String()) - } - - // Can read from an unaddressable array but not write to one. - if dst.typecode.Kind() == Array && !dst.isIndirect() { - panic("reflect.Copy: unaddressable array value") - } - - dstbuf, dstlen := buflen(dst) - srcbuf, srclen := buflen(src) - - if srclen > 0 { - dst.checkRO() - } - - return sliceCopy(dstbuf, srcbuf, dstlen, srclen, dst.typecode.elem().Size()) -} - -func buflen(v Value) (unsafe.Pointer, uintptr) { - var buf unsafe.Pointer - var len uintptr - switch v.typecode.Kind() { - case Slice: - hdr := (*sliceHeader)(v.value) - buf = hdr.data - len = hdr.len - case Array: - if v.isIndirect() || v.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - buf = v.value - } else { - buf = unsafe.Pointer(&v.value) - } - len = uintptr(v.Len()) - case String: - hdr := (*stringHeader)(v.value) - buf = hdr.data - len = hdr.len - default: - // This shouldn't happen - panic("reflect.Copy: not slice or array or string") - } - - return buf, len -} - -//go:linkname sliceGrow runtime.sliceGrow -func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) - -// extend slice to hold n new elements -func extendSlice(v Value, n int) sliceHeader { - if v.Kind() != Slice { - panic(&ValueError{Method: "extendSlice", Kind: v.Kind()}) - } - - var old sliceHeader - if v.value != nil { - old = *(*sliceHeader)(v.value) - } - - nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), v.typecode.elem().Size()) - - return sliceHeader{ - data: nbuf, - len: nlen + uintptr(n), - cap: ncap, - } + return reflectlite.Copy(dst.Value, src.Value) } // Append appends the values x to a slice s and returns the resulting slice. // As in Go, each x's value must be assignable to the slice's element type. func Append(v Value, x ...Value) Value { - if v.Kind() != Slice { - panic(&ValueError{Method: "Append", Kind: v.Kind()}) - } - oldLen := v.Len() - newslice := extendSlice(v, len(x)) - v.flags = valueFlagExported - v.value = (unsafe.Pointer)(&newslice) - for i, xx := range x { - v.Index(oldLen + i).Set(xx) - } - return v + y := *(*[]reflectlite.Value)(unsafe.Pointer(&x)) + return Value{reflectlite.Append(v.Value, y...)} } // AppendSlice appends a slice t to a slice s and returns the resulting slice. // The slices s and t must have the same element type. func AppendSlice(s, t Value) Value { - if s.typecode.Kind() != Slice || t.typecode.Kind() != Slice || s.typecode != t.typecode { - // Not a very helpful error message, but shortened to just one error to - // keep code size down. - panic("reflect.AppendSlice: invalid types") - } - if !s.isExported() || !t.isExported() { - // One of the sides was not exported, so can't access the data. - panic("reflect.AppendSlice: unexported") - } - sSlice := (*sliceHeader)(s.value) - tSlice := (*sliceHeader)(t.value) - elemSize := s.typecode.elem().Size() - ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize) - result := &sliceHeader{ - data: ptr, - len: len, - cap: cap, - } - return Value{ - typecode: s.typecode, - value: unsafe.Pointer(result), - flags: valueFlagExported, - } + return Value{reflectlite.AppendSlice(s.Value, t.Value)} } -// Grow increases the slice's capacity, if necessary, to guarantee space for -// another n elements. After Grow(n), at least n elements can be appended -// to the slice without another allocation. -// -// It panics if v's Kind is not a Slice or if n is negative or too large to -// allocate the memory. -func (v Value) Grow(n int) { - v.checkAddressable() - if n < 0 { - panic("reflect.Grow: negative length") - } - if v.Kind() != Slice { - panic(&ValueError{Method: "Grow", Kind: v.Kind()}) - } - slice := (*sliceHeader)(v.value) - newslice := extendSlice(v, n) - // Only copy the new data and cap: the len remains unchanged. - slice.data = newslice.data - slice.cap = newslice.cap -} - -//go:linkname hashmapStringSet runtime.hashmapStringSet -func hashmapStringSet(m unsafe.Pointer, key string, value unsafe.Pointer) - -//go:linkname hashmapBinarySet runtime.hashmapBinarySet -func hashmapBinarySet(m unsafe.Pointer, key, value unsafe.Pointer) - -//go:linkname hashmapInterfaceSet runtime.hashmapInterfaceSet -func hashmapInterfaceSet(m unsafe.Pointer, key interface{}, value unsafe.Pointer) - -//go:linkname hashmapStringDelete runtime.hashmapStringDelete -func hashmapStringDelete(m unsafe.Pointer, key string) - -//go:linkname hashmapBinaryDelete runtime.hashmapBinaryDelete -func hashmapBinaryDelete(m unsafe.Pointer, key unsafe.Pointer) - -//go:linkname hashmapInterfaceDelete runtime.hashmapInterfaceDelete -func hashmapInterfaceDelete(m unsafe.Pointer, key interface{}) - func (v Value) SetMapIndex(key, elem Value) { - v.checkRO() - if v.Kind() != Map { - panic(&ValueError{Method: "SetMapIndex", Kind: v.Kind()}) - } - - vkey := v.typecode.key() - - // compare key type with actual key type of map - if !key.typecode.AssignableTo(vkey) { - panic("reflect.Value.SetMapIndex: incompatible types for key") - } - - // if elem is the zero Value, it means delete - del := elem == Value{} - - if !del && !elem.typecode.AssignableTo(v.typecode.elem()) { - panic("reflect.Value.SetMapIndex: incompatible types for value") - } - - // make elem an interface if it needs to be converted - if v.typecode.elem().Kind() == Interface && elem.typecode.Kind() != Interface { - intf := composeInterface(unsafe.Pointer(elem.typecode), elem.value) - elem = Value{ - typecode: v.typecode.elem(), - value: unsafe.Pointer(&intf), - } - } - - if key.Kind() == String { - if del { - hashmapStringDelete(v.pointer(), *(*string)(key.value)) - } else { - var elemptr unsafe.Pointer - if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - elemptr = elem.value - } else { - elemptr = unsafe.Pointer(&elem.value) - } - hashmapStringSet(v.pointer(), *(*string)(key.value), elemptr) - } - - } else if key.typecode.isBinary() { - var keyptr unsafe.Pointer - if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - keyptr = key.value - } else { - keyptr = unsafe.Pointer(&key.value) - } - - if del { - hashmapBinaryDelete(v.pointer(), keyptr) - } else { - var elemptr unsafe.Pointer - if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - elemptr = elem.value - } else { - elemptr = unsafe.Pointer(&elem.value) - } - hashmapBinarySet(v.pointer(), keyptr, elemptr) - } - } else { - if del { - hashmapInterfaceDelete(v.pointer(), key.Interface()) - } else { - var elemptr unsafe.Pointer - if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - elemptr = elem.value - } else { - elemptr = unsafe.Pointer(&elem.value) - } - - hashmapInterfaceSet(v.pointer(), key.Interface(), elemptr) - } - } + v.Value.SetMapIndex(key.Value, elem.Value) } // FieldByIndex returns the nested field corresponding to index. func (v Value) FieldByIndex(index []int) Value { - if len(index) == 1 { - return v.Field(index[0]) - } - if v.Kind() != Struct { - panic(&ValueError{"FieldByIndex", v.Kind()}) - } - for i, x := range index { - if i > 0 { - if v.Kind() == Pointer && v.typecode.elem().Kind() == Struct { - if v.IsNil() { - panic("reflect: indirection through nil pointer to embedded struct") - } - v = v.Elem() - } - } - v = v.Field(x) - } - return v + return Value{v.Value.FieldByIndex(index)} } // FieldByIndexErr returns the nested field corresponding to index. func (v Value) FieldByIndexErr(index []int) (Value, error) { - return Value{}, &ValueError{Method: "FieldByIndexErr"} + out, err := v.Value.FieldByIndexErr(index) + return Value{out}, err } func (v Value) FieldByName(name string) Value { - if v.Kind() != Struct { - panic(&ValueError{"FieldByName", v.Kind()}) - } - - if field, ok := v.typecode.FieldByName(name); ok { - return v.FieldByIndex(field.Index) - } - return Value{} + return Value{v.Value.FieldByName(name)} } func (v Value) FieldByNameFunc(match func(string) bool) Value { - if v.Kind() != Struct { - panic(&ValueError{"FieldByName", v.Kind()}) - } - - if field, ok := v.typecode.FieldByNameFunc(match); ok { - return v.FieldByIndex(field.Index) - } - return Value{} + return Value{v.Value.FieldByNameFunc(match)} } //go:linkname hashmapMake runtime.hashmapMake func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer -// MakeMapWithSize creates a new map with the specified type and initial space -// for approximately n elements. -func MakeMapWithSize(typ Type, n int) Value { - - // TODO(dgryski): deduplicate these? runtime and reflect both need them. - const ( - hashmapAlgorithmBinary uint8 = iota - hashmapAlgorithmString - hashmapAlgorithmInterface - ) - - if typ.Kind() != Map { - panic(&ValueError{Method: "MakeMap", Kind: typ.Kind()}) - } - - if n < 0 { - panic("reflect.MakeMapWithSize: negative size hint") - } - - key := typ.Key().(*rawType) - val := typ.Elem().(*rawType) - - var alg uint8 - - if key.Kind() == String { - alg = hashmapAlgorithmString - } else if key.isBinary() { - alg = hashmapAlgorithmBinary - } else { - alg = hashmapAlgorithmInterface - } - - m := hashmapMake(key.Size(), val.Size(), uintptr(n), alg) - - return Value{ - typecode: typ.(*rawType), - value: m, - flags: valueFlagExported, - } -} - type SelectDir int const ( @@ -2113,7 +190,13 @@ func (v Value) Close() { // MakeMap creates a new map with the specified type. func MakeMap(typ Type) Value { - return MakeMapWithSize(typ, 8) + return Value{reflectlite.MakeMap(toRawType(typ))} +} + +// MakeMapWithSize creates a new map with the specified type and initial space +// for approximately n elements. +func MakeMapWithSize(typ Type, n int) Value { + return Value{reflectlite.MakeMapWithSize(toRawType(typ), n)} } func (v Value) Call(in []Value) []Value { @@ -2124,6 +207,10 @@ func (v Value) CallSlice(in []Value) []Value { panic("unimplemented: (reflect.Value).CallSlice()") } +func (v Value) Equal(u Value) bool { + return v.Value.Equal(u.Value) +} + func (v Value) Method(i int) Value { panic("unimplemented: (reflect.Value).Method()") } diff --git a/src/reflect/visiblefields.go b/src/reflect/visiblefields.go index 9375faa110..ea7a0fd008 100644 --- a/src/reflect/visiblefields.go +++ b/src/reflect/visiblefields.go @@ -1,105 +1,7 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - package reflect -// VisibleFields returns all the visible fields in t, which must be a -// struct type. A field is defined as visible if it's accessible -// directly with a FieldByName call. The returned fields include fields -// inside anonymous struct members and unexported fields. They follow -// the same order found in the struct, with anonymous fields followed -// immediately by their promoted fields. -// -// For each element e of the returned slice, the corresponding field -// can be retrieved from a value v of type t by calling v.FieldByIndex(e.Index). -func VisibleFields(t Type) []StructField { - if t == nil { - panic("reflect: VisibleFields(nil)") - } - if t.Kind() != Struct { - panic("reflect.VisibleFields of non-struct type") - } - w := &visibleFieldsWalker{ - byName: make(map[string]int), - visiting: make(map[Type]bool), - fields: make([]StructField, 0, t.NumField()), - index: make([]int, 0, 2), - } - w.walk(t) - // Remove all the fields that have been hidden. - // Use an in-place removal that avoids copying in - // the common case that there are no hidden fields. - j := 0 - for i := range w.fields { - f := &w.fields[i] - if f.Name == "" { - continue - } - if i != j { - // A field has been removed. We need to shuffle - // all the subsequent elements up. - w.fields[j] = *f - } - j++ - } - return w.fields[:j] -} - -type visibleFieldsWalker struct { - byName map[string]int - visiting map[Type]bool - fields []StructField - index []int -} +import "internal/reflectlite" -// walk walks all the fields in the struct type t, visiting -// fields in index preorder and appending them to w.fields -// (this maintains the required ordering). -// Fields that have been overridden have their -// Name field cleared. -func (w *visibleFieldsWalker) walk(t Type) { - if w.visiting[t] { - return - } - w.visiting[t] = true - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - w.index = append(w.index, i) - add := true - if oldIndex, ok := w.byName[f.Name]; ok { - old := &w.fields[oldIndex] - if len(w.index) == len(old.Index) { - // Fields with the same name at the same depth - // cancel one another out. Set the field name - // to empty to signify that has happened, and - // there's no need to add this field. - old.Name = "" - add = false - } else if len(w.index) < len(old.Index) { - // The old field loses because it's deeper than the new one. - old.Name = "" - } else { - // The old field wins because it's shallower than the new one. - add = false - } - } - if add { - // Copy the index so that it's not overwritten - // by the other appends. - f.Index = append([]int(nil), w.index...) - w.byName[f.Name] = len(w.fields) - w.fields = append(w.fields, f) - } - if f.Anonymous { - if f.Type.Kind() == Pointer { - f.Type = f.Type.Elem() - } - if f.Type.Kind() == Struct { - w.walk(f.Type) - } - } - w.index = w.index[:len(w.index)-1] - } - delete(w.visiting, t) +func VisibleFields(t Type) []StructField { + return reflectlite.VisibleFields(toRawType(t)) } From 9e143efd73284d23b42fb8f4661e6b01c7ac6b2d Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 12:49:23 -0800 Subject: [PATCH 19/45] reflect: add Go 1.24 iter.Seq[2] methods --- src/reflect/iter.go | 166 ++++++++++++++++++++++++++++++++++++++++++++ src/reflect/type.go | 32 +++++++++ 2 files changed, 198 insertions(+) create mode 100644 src/reflect/iter.go diff --git a/src/reflect/iter.go b/src/reflect/iter.go new file mode 100644 index 0000000000..90a9469bb5 --- /dev/null +++ b/src/reflect/iter.go @@ -0,0 +1,166 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflect + +import "iter" + +func rangeNum[T int8 | int16 | int32 | int64 | int | + uint8 | uint16 | uint32 | uint64 | uint | + uintptr, N int64 | uint64](v N) iter.Seq[Value] { + return func(yield func(v Value) bool) { + // cannot use range T(v) because no core type. + for i := T(0); i < T(v); i++ { + if !yield(ValueOf(i)) { + return + } + } + } +} + +// Seq returns an iter.Seq[Value] that loops over the elements of v. +// If v's kind is Func, it must be a function that has no results and +// that takes a single argument of type func(T) bool for some type T. +// If v's kind is Pointer, the pointer element type must have kind Array. +// Otherwise v's kind must be Int, Int8, Int16, Int32, Int64, +// Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, +// Array, Chan, Map, Slice, or String. +func (v Value) Seq() iter.Seq[Value] { + // TODO: canRangeFunc + // if canRangeFunc(v.typ()) { + // return func(yield func(Value) bool) { + // rf := MakeFunc(v.Type().In(0), func(in []Value) []Value { + // return []Value{ValueOf(yield(in[0]))} + // }) + // v.Call([]Value{rf}) + // } + // } + switch v.Kind() { + case Int: + return rangeNum[int](v.Int()) + case Int8: + return rangeNum[int8](v.Int()) + case Int16: + return rangeNum[int16](v.Int()) + case Int32: + return rangeNum[int32](v.Int()) + case Int64: + return rangeNum[int64](v.Int()) + case Uint: + return rangeNum[uint](v.Uint()) + case Uint8: + return rangeNum[uint8](v.Uint()) + case Uint16: + return rangeNum[uint16](v.Uint()) + case Uint32: + return rangeNum[uint32](v.Uint()) + case Uint64: + return rangeNum[uint64](v.Uint()) + case Uintptr: + return rangeNum[uintptr](v.Uint()) + case Pointer: + if v.Elem().Kind() != Array { + break + } + return func(yield func(Value) bool) { + v = v.Elem() + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i)) { + return + } + } + } + case Array, Slice: + return func(yield func(Value) bool) { + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i)) { + return + } + } + } + case String: + return func(yield func(Value) bool) { + for i := range v.String() { + if !yield(ValueOf(i)) { + return + } + } + } + case Map: + return func(yield func(Value) bool) { + i := v.MapRange() + for i.Next() { + if !yield(i.Key()) { + return + } + } + } + case Chan: + return func(yield func(Value) bool) { + for value, ok := v.Recv(); ok; value, ok = v.Recv() { + if !yield(value) { + return + } + } + } + } + panic("reflect: " + v.Type().String() + " cannot produce iter.Seq[Value]") +} + +// Seq2 returns an iter.Seq2[Value, Value] that loops over the elements of v. +// If v's kind is Func, it must be a function that has no results and +// that takes a single argument of type func(K, V) bool for some type K, V. +// If v's kind is Pointer, the pointer element type must have kind Array. +// Otherwise v's kind must be Array, Map, Slice, or String. +func (v Value) Seq2() iter.Seq2[Value, Value] { + // TODO: canRangeFunc2 + // if canRangeFunc2(v.typ()) { + // return func(yield func(Value, Value) bool) { + // rf := MakeFunc(v.Type().In(0), func(in []Value) []Value { + // return []Value{ValueOf(yield(in[0], in[1]))} + // }) + // v.Call([]Value{rf}) + // } + // } + switch v.Kind() { + case Pointer: + if v.Elem().Kind() != Array { + break + } + return func(yield func(Value, Value) bool) { + v = v.Elem() + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i), v.Index(i)) { + return + } + } + } + case Array, Slice: + return func(yield func(Value, Value) bool) { + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i), v.Index(i)) { + return + } + } + } + case String: + return func(yield func(Value, Value) bool) { + for i, v := range v.String() { + if !yield(ValueOf(i), ValueOf(v)) { + return + } + } + } + case Map: + return func(yield func(Value, Value) bool) { + i := v.MapRange() + for i.Next() { + if !yield(i.Key(), i.Value()) { + return + } + } + } + } + panic("reflect: " + v.Type().String() + " cannot produce iter.Seq2[Value, Value]") +} diff --git a/src/reflect/type.go b/src/reflect/type.go index e5417f8e8f..08c73bb2ea 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -329,6 +329,12 @@ type Type interface { // OverflowUint reports whether the uint64 x cannot be represented by type t. // It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. OverflowUint(x uint64) bool + + // CanSeq reports whether a [Value] with this type can be iterated over using [Value.Seq]. + CanSeq() bool + + // CanSeq2 reports whether a [Value] with this type can be iterated over using [Value.Seq2]. + CanSeq2() bool } type rawType struct { @@ -359,6 +365,32 @@ func (t *rawType) AssignableTo(u Type) bool { return t.RawType.AssignableTo(&(u.(*rawType).RawType)) } +func (t *rawType) CanSeq() bool { + switch t.Kind() { + case Int8, Int16, Int32, Int64, Int, Uint8, Uint16, Uint32, Uint64, Uint, Uintptr, Array, Slice, Chan, String, Map: + return true + case Func: + return false // TODO: implement canRangeFunc + // return canRangeFunc(&t.) + case Pointer: + return t.Elem().Kind() == Array + } + return false +} + +func (t *rawType) CanSeq2() bool { + switch t.Kind() { + case Array, Slice, String, Map: + return true + case Func: + return false // TODO: implement canRangeFunc2 + // return canRangeFunc2(&t.t) + case Pointer: + return t.Elem().Kind() == Array + } + return false +} + func (t *rawType) ConvertibleTo(u Type) bool { panic("unimplemented: (reflect.Type).ConvertibleTo()") } From 99a618385b796c3960033c795c886e61b1c09461 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 12:53:15 -0800 Subject: [PATCH 20/45] runtime: use package reflectlite --- src/runtime/hashmap.go | 28 ++++++++++++++-------------- src/runtime/interface.go | 28 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index ad42fda753..894d92a1ba 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -6,7 +6,7 @@ package runtime // https://golang.org/src/runtime/map.go import ( - "reflect" + "internal/reflectlite" "tinygo" "unsafe" ) @@ -539,8 +539,8 @@ func hashmapStringDelete(m *hashmap, key string) { // a field is exported and thus allows circumventing the type system. // The hash function needs it as it also needs to hash unexported struct fields. // -//go:linkname valueInterfaceUnsafe reflect.valueInterfaceUnsafe -func valueInterfaceUnsafe(v reflect.Value) interface{} +//go:linkname valueInterfaceUnsafe internal/reflectlite.valueInterfaceUnsafe +func valueInterfaceUnsafe(v reflectlite.Value) interface{} func hashmapFloat32Hash(ptr unsafe.Pointer, seed uintptr) uint32 { f := *(*uint32)(ptr) @@ -561,7 +561,7 @@ func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 { } func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 { - x := reflect.ValueOf(itf) + x := reflectlite.ValueOf(itf) if x.RawType() == nil { return 0 // nil interface } @@ -574,39 +574,39 @@ func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 { } switch x.RawType().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + case reflectlite.Int, reflectlite.Int8, reflectlite.Int16, reflectlite.Int32, reflectlite.Int64: return hash32(ptr, x.RawType().Size(), seed) - case reflect.Bool, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + case reflectlite.Bool, reflectlite.Uint, reflectlite.Uint8, reflectlite.Uint16, reflectlite.Uint32, reflectlite.Uint64, reflectlite.Uintptr: return hash32(ptr, x.RawType().Size(), seed) - case reflect.Float32: + case reflectlite.Float32: // It should be possible to just has the contents. However, NaN != NaN // so if you're using lots of NaNs as map keys (you shouldn't) then hash // time may become exponential. To fix that, it would be better to // return a random number instead: // https://research.swtch.com/randhash return hashmapFloat32Hash(ptr, seed) - case reflect.Float64: + case reflectlite.Float64: return hashmapFloat64Hash(ptr, seed) - case reflect.Complex64: + case reflectlite.Complex64: rptr, iptr := ptr, unsafe.Add(ptr, 4) return hashmapFloat32Hash(rptr, seed) ^ hashmapFloat32Hash(iptr, seed) - case reflect.Complex128: + case reflectlite.Complex128: rptr, iptr := ptr, unsafe.Add(ptr, 8) return hashmapFloat64Hash(rptr, seed) ^ hashmapFloat64Hash(iptr, seed) - case reflect.String: + case reflectlite.String: return hashmapStringHash(x.String(), seed) - case reflect.Chan, reflect.Ptr, reflect.UnsafePointer: + case reflectlite.Chan, reflectlite.Ptr, reflectlite.UnsafePointer: // It might seem better to just return the pointer, but that won't // result in an evenly distributed hashmap. Instead, hash the pointer // like most other types. return hash32(ptr, x.RawType().Size(), seed) - case reflect.Array: + case reflectlite.Array: var hash uint32 for i := 0; i < x.Len(); i++ { hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)), seed) } return hash - case reflect.Struct: + case reflectlite.Struct: var hash uint32 for i := 0; i < x.NumField(); i++ { hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)), seed) diff --git a/src/runtime/interface.go b/src/runtime/interface.go index b9813225f2..e1d263a7e3 100644 --- a/src/runtime/interface.go +++ b/src/runtime/interface.go @@ -6,7 +6,7 @@ package runtime // anything (including non-pointers). import ( - "reflect" + "internal/reflectlite" "unsafe" ) @@ -27,12 +27,12 @@ func decomposeInterface(i _interface) (unsafe.Pointer, unsafe.Pointer) { // Return true iff both interfaces are equal. func interfaceEqual(x, y interface{}) bool { - return reflectValueEqual(reflect.ValueOf(x), reflect.ValueOf(y)) + return reflectValueEqual(reflectlite.ValueOf(x), reflectlite.ValueOf(y)) } -func reflectValueEqual(x, y reflect.Value) bool { +func reflectValueEqual(x, y reflectlite.Value) bool { // Note: doing a x.Type() == y.Type() comparison would not work here as that - // would introduce an infinite recursion: comparing two reflect.Type values + // would introduce an infinite recursion: comparing two reflectlite.Type values // is done with this reflectValueEqual runtime call. if x.RawType() == nil || y.RawType() == nil { // One of them is nil. @@ -46,35 +46,35 @@ func reflectValueEqual(x, y reflect.Value) bool { } switch x.RawType().Kind() { - case reflect.Bool: + case reflectlite.Bool: return x.Bool() == y.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + case reflectlite.Int, reflectlite.Int8, reflectlite.Int16, reflectlite.Int32, reflectlite.Int64: return x.Int() == y.Int() - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + case reflectlite.Uint, reflectlite.Uint8, reflectlite.Uint16, reflectlite.Uint32, reflectlite.Uint64, reflectlite.Uintptr: return x.Uint() == y.Uint() - case reflect.Float32, reflect.Float64: + case reflectlite.Float32, reflectlite.Float64: return x.Float() == y.Float() - case reflect.Complex64, reflect.Complex128: + case reflectlite.Complex64, reflectlite.Complex128: return x.Complex() == y.Complex() - case reflect.String: + case reflectlite.String: return x.String() == y.String() - case reflect.Chan, reflect.Ptr, reflect.UnsafePointer: + case reflectlite.Chan, reflectlite.Ptr, reflectlite.UnsafePointer: return x.UnsafePointer() == y.UnsafePointer() - case reflect.Array: + case reflectlite.Array: for i := 0; i < x.Len(); i++ { if !reflectValueEqual(x.Index(i), y.Index(i)) { return false } } return true - case reflect.Struct: + case reflectlite.Struct: for i := 0; i < x.NumField(); i++ { if !reflectValueEqual(x.Field(i), y.Field(i)) { return false } } return true - case reflect.Interface: + case reflectlite.Interface: return reflectValueEqual(x.Elem(), y.Elem()) default: runtimePanic("comparing un-comparable type") From c8d0b87a678bcb772b4a36f5911eb3e4b10d0c64 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 15:45:37 -0800 Subject: [PATCH 21/45] internal/reflectlite, reflect: handle different StructField --- src/internal/reflectlite/type.go | 2 +- src/reflect/deepequal.go | 7 ++++ src/reflect/type.go | 63 ++++++++++++++++++++++++++++++-- src/reflect/visiblefields.go | 8 +++- 4 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/reflect/deepequal.go diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go index b81468de40..10350676c6 100644 --- a/src/internal/reflectlite/type.go +++ b/src/internal/reflectlite/type.go @@ -294,7 +294,6 @@ func (t *RawType) String() string { } return s } - switch t.Kind() { case Chan: elem := t.elem().String() @@ -977,6 +976,7 @@ func (t *RawType) FieldByIndex(index []int) StructField { } // A StructField describes a single field in a struct. +// This must be kept in sync with [reflect.StructField]. type StructField struct { // Name indicates the field name. Name string diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go new file mode 100644 index 0000000000..362385aed8 --- /dev/null +++ b/src/reflect/deepequal.go @@ -0,0 +1,7 @@ +package reflect + +import "internal/reflectlite" + +func DeepEqual(x, y interface{}) bool { + return reflectlite.DeepEqual(x, y) +} diff --git a/src/reflect/type.go b/src/reflect/type.go index 08c73bb2ea..5116108dc7 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -342,6 +342,9 @@ type rawType struct { } func toType(t reflectlite.Type) Type { + if t == nil { + return nil + } return (*rawType)(unsafe.Pointer(t.(*reflectlite.RawType))) } @@ -395,6 +398,30 @@ func (t *rawType) ConvertibleTo(u Type) bool { panic("unimplemented: (reflect.Type).ConvertibleTo()") } +func (t *rawType) Elem() Type { + return toType(t.RawType.Elem()) +} + +func (t *rawType) Field(i int) StructField { + f := t.RawType.Field(i) + return toStructField(f) +} + +func (t *rawType) FieldByIndex(index []int) StructField { + f := t.RawType.FieldByIndex(index) + return toStructField(f) +} + +func (t *rawType) FieldByName(name string) (StructField, bool) { + f, ok := t.RawType.FieldByName(name) + return toStructField(f), ok +} + +func (t *rawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { + f, ok := t.RawType.FieldByNameFunc(match) + return toStructField(f), ok +} + func (t *rawType) Implements(u Type) bool { return t.RawType.Implements(&(u.(*rawType).RawType)) } @@ -431,11 +458,41 @@ func (t *rawType) Out(i int) Type { panic("unimplemented: (reflect.Type).Out()") } -func (t *rawType) Elem() Type { - return toType(t.RawType.Elem()) +// A StructField describes a single field in a struct. +// This must be kept in sync with [reflectlite.StructField]. +type StructField struct { + // Name indicates the field name. + Name string + + // PkgPath is the package path where the struct containing this field is + // declared for unexported fields, or the empty string for exported fields. + PkgPath string + + Type Type + Tag StructTag // field tag string + Offset uintptr + Index []int // index sequence for Type.FieldByIndex + Anonymous bool +} + +func toStructField(f reflectlite.StructField) StructField { + return StructField{ + Name: f.Name, + PkgPath: f.PkgPath, + Type: toType(f.Type), + Tag: f.Tag, + Offset: f.Offset, + Index: f.Index, + Anonymous: f.Anonymous, + } +} + +// IsExported reports whether the field is exported. +func (f StructField) IsExported() bool { + return f.PkgPath == "" } -type StructField = reflectlite.StructField +type StructTag = reflectlite.StructTag func TypeFor[T any]() Type { return toType(reflectlite.TypeFor[T]()) diff --git a/src/reflect/visiblefields.go b/src/reflect/visiblefields.go index ea7a0fd008..ac722da070 100644 --- a/src/reflect/visiblefields.go +++ b/src/reflect/visiblefields.go @@ -1,7 +1,11 @@ package reflect -import "internal/reflectlite" +import ( + "internal/reflectlite" + "unsafe" +) func VisibleFields(t Type) []StructField { - return reflectlite.VisibleFields(toRawType(t)) + fields := reflectlite.VisibleFields(toRawType(t)) + return *(*[]StructField)(unsafe.Pointer(&fields)) } From bf88cdea904f63c3d7061201fad6c2b6c2fdc62c Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 20:12:14 -0800 Subject: [PATCH 22/45] transform: cherry-pick from #4774 --- transform/rtcalls.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/transform/rtcalls.go b/transform/rtcalls.go index 8310fc9f19..3abc1d3952 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -117,8 +117,9 @@ func OptimizeStringEqual(mod llvm.Module) { // As of this writing, the (reflect.Type).Interface method has not yet been // implemented so this optimization is critical for the encoding/json package. func OptimizeReflectImplements(mod llvm.Module) { - implementsSignature := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool") - if implementsSignature.IsNil() { + implementsSignature1 := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool") + implementsSignature2 := mod.NamedGlobal("reflect/methods.Implements(internal/reflectlite.Type) bool") + if implementsSignature1.IsNil() && implementsSignature2.IsNil() { return } @@ -132,7 +133,8 @@ func OptimizeReflectImplements(mod llvm.Module) { if attr.IsNil() { continue } - if attr.GetStringValue() == "reflect/methods.Implements(reflect.Type) bool" { + val := attr.GetStringValue() + if val == "reflect/methods.Implements(reflect.Type) bool" || val == "reflect/methods.Implements(internal/reflectlite.Type) bool" { implementsFunc = fn break } From 417aa4312ebc39c6d5647675be19a898787d7451 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 20:35:36 -0800 Subject: [PATCH 23/45] internal/reflectlite, reflect: move StringHeader and SliceHeader back to package reflect --- src/internal/reflectlite/value.go | 13 ------------ src/{internal/reflectlite => reflect}/intw.go | 2 +- .../reflectlite => reflect}/intw_avr.go | 2 +- .../reflectlite => reflect}/intw_test.go | 2 +- src/reflect/value.go | 21 +++++++++++++++++++ 5 files changed, 24 insertions(+), 16 deletions(-) rename src/{internal/reflectlite => reflect}/intw.go (92%) rename src/{internal/reflectlite => reflect}/intw_avr.go (92%) rename src/{internal/reflectlite => reflect}/intw_test.go (97%) diff --git a/src/internal/reflectlite/value.go b/src/internal/reflectlite/value.go index 0cccfe9d87..31015cbd27 100644 --- a/src/internal/reflectlite/value.go +++ b/src/internal/reflectlite/value.go @@ -1686,12 +1686,6 @@ type funcHeader struct { Code unsafe.Pointer } -type SliceHeader struct { - Data uintptr - Len intw - Cap intw -} - // Slice header that matches the underlying structure. Used for when we switch // to a precise GC, which needs to know exactly where pointers live. type sliceHeader struct { @@ -1700,11 +1694,6 @@ type sliceHeader struct { cap uintptr } -type StringHeader struct { - Data uintptr - Len intw -} - // Like sliceHeader, this type is used internally to make sure pointer and // non-pointer fields match those of actual strings. type stringHeader struct { @@ -1716,9 +1705,7 @@ type stringHeader struct { // See https://github.com/tinygo-org/tinygo/pull/4156 // and https://github.com/tinygo-org/tinygo/issues/1284. var ( - _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(sliceHeader{})]byte{} - _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(stringHeader{})]byte{} ) diff --git a/src/internal/reflectlite/intw.go b/src/reflect/intw.go similarity index 92% rename from src/internal/reflectlite/intw.go rename to src/reflect/intw.go index 4dd197cefc..20fbd4341e 100644 --- a/src/internal/reflectlite/intw.go +++ b/src/reflect/intw.go @@ -1,6 +1,6 @@ //go:build !avr -package reflectlite +package reflect // intw is an integer type, used in places where an int is typically required, // except architectures where the size of an int != word size. diff --git a/src/internal/reflectlite/intw_avr.go b/src/reflect/intw_avr.go similarity index 92% rename from src/internal/reflectlite/intw_avr.go rename to src/reflect/intw_avr.go index 24b4377777..8f294eeee2 100644 --- a/src/internal/reflectlite/intw_avr.go +++ b/src/reflect/intw_avr.go @@ -1,6 +1,6 @@ //go:build avr -package reflectlite +package reflect // intw is an integer type, used in places where an int is typically required, // except architectures where the size of an int != word size. diff --git a/src/internal/reflectlite/intw_test.go b/src/reflect/intw_test.go similarity index 97% rename from src/internal/reflectlite/intw_test.go rename to src/reflect/intw_test.go index b235b88f4c..1014a9ae4e 100644 --- a/src/internal/reflectlite/intw_test.go +++ b/src/reflect/intw_test.go @@ -1,6 +1,6 @@ //go:build !avr -package reflectlite_test +package reflect_test import ( "reflect" diff --git a/src/reflect/value.go b/src/reflect/value.go index 26f3657cd0..fe41f2d184 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -226,3 +226,24 @@ func (v Value) Recv() (x Value, ok bool) { func NewAt(typ Type, p unsafe.Pointer) Value { panic("unimplemented: reflect.New()") } + +// Deprecated: Use unsafe.Slice or unsafe.SliceData instead. +type SliceHeader struct { + Data uintptr + Len intw + Cap intw +} + +// Deprecated: Use unsafe.String or unsafe.StringData instead. +type StringHeader struct { + Data uintptr + Len intw +} + +// Verify SliceHeader and StringHeader sizes. +// See https://github.com/tinygo-org/tinygo/pull/4156 +// and https://github.com/tinygo-org/tinygo/issues/1284. +var ( + _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} + _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} +) From 05fc49a0cf9f04075d1dfa520f1b4096c5753c78 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 20:52:17 -0800 Subject: [PATCH 24/45] internal/reflectlite: remove old reflect.go --- src/internal/reflectlite/reflect.go | 53 ----------------------------- 1 file changed, 53 deletions(-) delete mode 100644 src/internal/reflectlite/reflect.go diff --git a/src/internal/reflectlite/reflect.go b/src/internal/reflectlite/reflect.go deleted file mode 100644 index df6abd3aa7..0000000000 --- a/src/internal/reflectlite/reflect.go +++ /dev/null @@ -1,53 +0,0 @@ -//go:build never - -package reflectlite - -import "reflect" - -func Swapper(slice interface{}) func(i, j int) { - return reflect.Swapper(slice) -} - -type Kind = reflect.Kind -type Type = reflect.Type -type Value = reflect.Value - -const ( - Invalid Kind = reflect.Invalid - Bool Kind = reflect.Bool - Int Kind = reflect.Int - Int8 Kind = reflect.Int8 - Int16 Kind = reflect.Int16 - Int32 Kind = reflect.Int32 - Int64 Kind = reflect.Int64 - Uint Kind = reflect.Uint - Uint8 Kind = reflect.Uint8 - Uint16 Kind = reflect.Uint16 - Uint32 Kind = reflect.Uint32 - Uint64 Kind = reflect.Uint64 - Uintptr Kind = reflect.Uintptr - Float32 Kind = reflect.Float32 - Float64 Kind = reflect.Float64 - Complex64 Kind = reflect.Complex64 - Complex128 Kind = reflect.Complex128 - Array Kind = reflect.Array - Chan Kind = reflect.Chan - Func Kind = reflect.Func - Interface Kind = reflect.Interface - Map Kind = reflect.Map - Ptr Kind = reflect.Ptr - Slice Kind = reflect.Slice - String Kind = reflect.String - Struct Kind = reflect.Struct - UnsafePointer Kind = reflect.UnsafePointer -) - -func ValueOf(i interface{}) reflect.Value { - return reflect.ValueOf(i) -} - -func TypeOf(i interface{}) reflect.Type { - return reflect.TypeOf(i) -} - -type ValueError = reflect.ValueError From a2be2f33309b2a73b81e5e9fc93f752d84c755e7 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 21:11:08 -0800 Subject: [PATCH 25/45] loader, iter: add shim for go1.22 and earlier --- loader/goroot.go | 4 ++++ src/iter/iter.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 src/iter/iter.go diff --git a/loader/goroot.go b/loader/goroot.go index 00a7124d80..3d3dee0d12 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -261,6 +261,10 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "unique/": false, } + if goMinor <= 22 { + paths["iter]"] = false + } + if goMinor >= 19 { paths["crypto/internal/"] = true paths["crypto/internal/boring/"] = true diff --git a/src/iter/iter.go b/src/iter/iter.go new file mode 100644 index 0000000000..bbeb4e1d12 --- /dev/null +++ b/src/iter/iter.go @@ -0,0 +1,17 @@ +//go:build !go1.23 + +// Delete this file when TinyGo drops support for Go 1.22. + +package iter + +// Seq is an iterator over sequences of individual values. +// When called as seq(yield), seq calls yield(v) for each value v in the sequence, +// stopping early if yield returns false. +// See the [iter] package documentation for more details. +type Seq[V any] func(yield func(V) bool) + +// Seq2 is an iterator over sequences of pairs of values, most commonly key-value pairs. +// When called as seq(yield), seq calls yield(k, v) for each pair (k, v) in the sequence, +// stopping early if yield returns false. +// See the [iter] package documentation for more details. +type Seq2[K, V any] func(yield func(K, V) bool) From fafe80704f629a2cfe95a90c6bc9e2331045240f Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 9 Mar 2025 08:51:28 -0700 Subject: [PATCH 26/45] loader, iter, reflect: use build tags for package iter and iter methods on reflect.Value --- loader/goroot.go | 4 ---- src/iter/iter.go | 17 ----------------- src/reflect/iter.go | 2 ++ 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 src/iter/iter.go diff --git a/loader/goroot.go b/loader/goroot.go index 3d3dee0d12..00a7124d80 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -261,10 +261,6 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "unique/": false, } - if goMinor <= 22 { - paths["iter]"] = false - } - if goMinor >= 19 { paths["crypto/internal/"] = true paths["crypto/internal/boring/"] = true diff --git a/src/iter/iter.go b/src/iter/iter.go deleted file mode 100644 index bbeb4e1d12..0000000000 --- a/src/iter/iter.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build !go1.23 - -// Delete this file when TinyGo drops support for Go 1.22. - -package iter - -// Seq is an iterator over sequences of individual values. -// When called as seq(yield), seq calls yield(v) for each value v in the sequence, -// stopping early if yield returns false. -// See the [iter] package documentation for more details. -type Seq[V any] func(yield func(V) bool) - -// Seq2 is an iterator over sequences of pairs of values, most commonly key-value pairs. -// When called as seq(yield), seq calls yield(k, v) for each pair (k, v) in the sequence, -// stopping early if yield returns false. -// See the [iter] package documentation for more details. -type Seq2[K, V any] func(yield func(K, V) bool) diff --git a/src/reflect/iter.go b/src/reflect/iter.go index 90a9469bb5..dd0b8de209 100644 --- a/src/reflect/iter.go +++ b/src/reflect/iter.go @@ -1,3 +1,5 @@ +//go:build go1.23 + // Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. From b6c3d142db79704ff1e8cf3dd8f661379436f899 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 9 Mar 2025 08:51:51 -0700 Subject: [PATCH 27/45] reflect: copy reflect iter tests from upstream Go --- src/reflect/iter_test.go | 415 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 src/reflect/iter_test.go diff --git a/src/reflect/iter_test.go b/src/reflect/iter_test.go new file mode 100644 index 0000000000..88a6b83a0a --- /dev/null +++ b/src/reflect/iter_test.go @@ -0,0 +1,415 @@ +//go:build go1.23 + +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflect_test + +import ( + "iter" + "maps" + "reflect" + . "reflect" + "testing" +) + +type N int8 + +func TestValueSeq(t *testing.T) { + m := map[string]int{ + "1": 1, + "2": 2, + "3": 3, + "4": 4, + } + c := make(chan int, 3) + for i := range 3 { + c <- i + } + close(c) + tests := []struct { + name string + val Value + check func(*testing.T, iter.Seq[Value]) + }{ + {"int", ValueOf(4), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"int8", ValueOf(int8(4)), func(t *testing.T, s iter.Seq[Value]) { + i := int8(0) + for v := range s { + if v.Interface().(int8) != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"uint", ValueOf(uint64(4)), func(t *testing.T, s iter.Seq[Value]) { + i := uint64(0) + for v := range s { + if v.Uint() != i { + t.Fatalf("got %d, want %d", v.Uint(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"uint8", ValueOf(uint8(4)), func(t *testing.T, s iter.Seq[Value]) { + i := uint8(0) + for v := range s { + if v.Interface().(uint8) != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"*[4]int", ValueOf(&[4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[4]int", ValueOf([4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[]int", ValueOf([]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"string", ValueOf("12语言"), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + indexes := []int64{0, 1, 2, 5} + for v := range s { + if v.Int() != indexes[i] { + t.Fatalf("got %d, want %d", v.Int(), indexes[i]) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"map[string]int", ValueOf(m), func(t *testing.T, s iter.Seq[Value]) { + copy := maps.Clone(m) + for v := range s { + if _, ok := copy[v.String()]; !ok { + t.Fatalf("unexpected %v", v.Interface()) + } + delete(copy, v.String()) + } + if len(copy) != 0 { + t.Fatalf("should loop four times") + } + }}, + // {"chan int", ValueOf(c), func(t *testing.T, s iter.Seq[Value]) { + // i := 0 + // m := map[int64]bool{ + // 0: false, + // 1: false, + // 2: false, + // } + // for v := range s { + // if b, ok := m[v.Int()]; !ok || b { + // t.Fatalf("unexpected %v", v.Interface()) + // } + // m[v.Int()] = true + // i++ + // } + // if i != 3 { + // t.Fatalf("should loop three times") + // } + // }}, + // {"func", ValueOf(func(yield func(int) bool) { + // for i := range 4 { + // if !yield(i) { + // return + // } + // } + // }), func(t *testing.T, s iter.Seq[Value]) { + // i := int64(0) + // for v := range s { + // if v.Int() != i { + // t.Fatalf("got %d, want %d", v.Int(), i) + // } + // i++ + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + // {"method", ValueOf(methodIter{}).MethodByName("Seq"), func(t *testing.T, s iter.Seq[Value]) { + // i := int64(0) + // for v := range s { + // if v.Int() != i { + // t.Fatalf("got %d, want %d", v.Int(), i) + // } + // i++ + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + {"type N int8", ValueOf(N(4)), func(t *testing.T, s iter.Seq[Value]) { + i := N(0) + for v := range s { + if v.Int() != int64(i) { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + // TODO: iteration should produce the same type + // if v.Type() != reflect.TypeOf(i) { + // t.Fatalf("got %s, want %s", v.Type(), reflect.TypeOf(i)) + // } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + } + for _, tc := range tests { + seq := tc.val.Seq() + tc.check(t, seq) + } +} + +func TestValueSeq2(t *testing.T) { + m := map[string]int{ + "1": 1, + "2": 2, + "3": 3, + "4": 4, + } + tests := []struct { + name string + val Value + check func(*testing.T, iter.Seq2[Value, Value]) + }{ + {"*[4]int", ValueOf(&[4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := int64(0) + for v1, v2 := range s { + if v1.Int() != i { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != i { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[4]int", ValueOf([4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := int64(0) + for v1, v2 := range s { + if v1.Int() != i { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != i { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[]int", ValueOf([]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := int64(0) + for v1, v2 := range s { + if v1.Int() != i { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != i { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"string", ValueOf("12语言"), func(t *testing.T, s iter.Seq2[Value, Value]) { + next, stop := iter.Pull2(s) + defer stop() + i := int64(0) + for j, s := range "12语言" { + v1, v2, ok := next() + if !ok { + t.Fatalf("should loop four times") + } + if v1.Int() != int64(j) { + t.Fatalf("got %d, want %d", v1.Int(), j) + } + if v2.Interface() != s { + t.Fatalf("got %v, want %v", v2.Interface(), s) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"map[string]int", ValueOf(m), func(t *testing.T, s iter.Seq2[Value, Value]) { + copy := maps.Clone(m) + for v1, v2 := range s { + v, ok := copy[v1.String()] + if !ok { + t.Fatalf("unexpected %v", v1.String()) + } + if v != v2.Interface() { + t.Fatalf("got %v, want %d", v2.Interface(), v) + } + delete(copy, v1.String()) + } + if len(copy) != 0 { + t.Fatalf("should loop four times") + } + }}, + // {"func", ValueOf(func(f func(int, int) bool) { + // for i := range 4 { + // f(i, i+1) + // } + // }), func(t *testing.T, s iter.Seq2[Value, Value]) { + // i := int64(0) + // for v1, v2 := range s { + // if v1.Int() != i { + // t.Fatalf("got %d, want %d", v1.Int(), i) + // } + // i++ + // if v2.Int() != i { + // t.Fatalf("got %d, want %d", v2.Int(), i) + // } + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + // {"method", ValueOf(methodIter2{}).MethodByName("Seq2"), func(t *testing.T, s iter.Seq2[Value, Value]) { + // i := int64(0) + // for v1, v2 := range s { + // if v1.Int() != i { + // t.Fatalf("got %d, want %d", v1.Int(), i) + // } + // i++ + // if v2.Int() != i { + // t.Fatalf("got %d, want %d", v2.Int(), i) + // } + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + {"[4]N", ValueOf([4]N{0, 1, 2, 3}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := N(0) + for v1, v2 := range s { + if v1.Int() != int64(i) { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + if v2.Int() != int64(i) { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + i++ + if v2.Type() != reflect.TypeOf(i) { + t.Fatalf("got %s, want %s", v2.Type(), reflect.TypeOf(i)) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[]N", ValueOf([]N{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := N(0) + for v1, v2 := range s { + if v1.Int() != int64(i) { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != int64(i) { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + if v2.Type() != reflect.TypeOf(i) { + t.Fatalf("got %s, want %s", v2.Type(), reflect.TypeOf(i)) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + } + for _, tc := range tests { + seq := tc.val.Seq2() + tc.check(t, seq) + } +} + +// methodIter is a type from which we can derive a method +// value that is an iter.Seq. +type methodIter struct{} + +func (methodIter) Seq(yield func(int) bool) { + for i := range 4 { + if !yield(i) { + return + } + } +} + +// For Type.CanSeq test. +func (methodIter) NonSeq(yield func(int)) {} + +// methodIter2 is a type from which we can derive a method +// value that is an iter.Seq2. +type methodIter2 struct{} + +func (methodIter2) Seq2(yield func(int, int) bool) { + for i := range 4 { + if !yield(i, i+1) { + return + } + } +} + +// For Type.CanSeq2 test. +func (methodIter2) NonSeq2(yield func(int, int)) {} From 64651115c2f9ded9a2bd0d082d035a003c1a55a2 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 9 Mar 2025 09:04:47 -0700 Subject: [PATCH 28/45] reflect: Value.Seq iteration value types should match Implementation of https://github.com/golang/go/issues/71905 194696f1d1f6e5609f96d0fb0192595e7e0f5b90 --- src/reflect/iter.go | 39 ++++++++++++++++++++++++--------------- src/reflect/iter_test.go | 9 +++++---- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/reflect/iter.go b/src/reflect/iter.go index dd0b8de209..4cc2df8fd9 100644 --- a/src/reflect/iter.go +++ b/src/reflect/iter.go @@ -6,15 +6,24 @@ package reflect -import "iter" +import ( + "iter" +) func rangeNum[T int8 | int16 | int32 | int64 | int | uint8 | uint16 | uint32 | uint64 | uint | - uintptr, N int64 | uint64](v N) iter.Seq[Value] { + uintptr, N int64 | uint64](num N, t Type) iter.Seq[Value] { return func(yield func(v Value) bool) { + convert := t.PkgPath() != "" // cannot use range T(v) because no core type. - for i := T(0); i < T(v); i++ { - if !yield(ValueOf(i)) { + for i := T(0); i < T(num); i++ { + tmp := ValueOf(i) + // if the iteration value type is define by + // type T built-in type. + if convert { + tmp = tmp.Convert(t) + } + if !yield(tmp) { return } } @@ -40,27 +49,27 @@ func (v Value) Seq() iter.Seq[Value] { // } switch v.Kind() { case Int: - return rangeNum[int](v.Int()) + return rangeNum[int](v.Int(), v.Type()) case Int8: - return rangeNum[int8](v.Int()) + return rangeNum[int8](v.Int(), v.Type()) case Int16: - return rangeNum[int16](v.Int()) + return rangeNum[int16](v.Int(), v.Type()) case Int32: - return rangeNum[int32](v.Int()) + return rangeNum[int32](v.Int(), v.Type()) case Int64: - return rangeNum[int64](v.Int()) + return rangeNum[int64](v.Int(), v.Type()) case Uint: - return rangeNum[uint](v.Uint()) + return rangeNum[uint](v.Uint(), v.Type()) case Uint8: - return rangeNum[uint8](v.Uint()) + return rangeNum[uint8](v.Uint(), v.Type()) case Uint16: - return rangeNum[uint16](v.Uint()) + return rangeNum[uint16](v.Uint(), v.Type()) case Uint32: - return rangeNum[uint32](v.Uint()) + return rangeNum[uint32](v.Uint(), v.Type()) case Uint64: - return rangeNum[uint64](v.Uint()) + return rangeNum[uint64](v.Uint(), v.Type()) case Uintptr: - return rangeNum[uintptr](v.Uint()) + return rangeNum[uintptr](v.Uint(), v.Type()) case Pointer: if v.Elem().Kind() != Array { break diff --git a/src/reflect/iter_test.go b/src/reflect/iter_test.go index 88a6b83a0a..48c62c477a 100644 --- a/src/reflect/iter_test.go +++ b/src/reflect/iter_test.go @@ -197,10 +197,11 @@ func TestValueSeq(t *testing.T) { t.Fatalf("got %d, want %d", v.Int(), i) } i++ - // TODO: iteration should produce the same type - // if v.Type() != reflect.TypeOf(i) { - // t.Fatalf("got %s, want %s", v.Type(), reflect.TypeOf(i)) - // } + if v.Type() != reflect.TypeOf(i) { + j := ValueOf(i) + t.Logf("ValueOf(j): %s", j.Type()) + t.Fatalf("got %s, want %s", v.Type(), reflect.TypeOf(i)) + } } if i != 4 { t.Fatalf("should loop four times") From c2af1d183ea3b614bba293c82bdd1234f56bac00 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 10 Mar 2025 12:22:16 -0700 Subject: [PATCH 29/45] reflect: panic on Type.CanSeq[2] instead of returning false PR feedback. --- src/reflect/type.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/reflect/type.go b/src/reflect/type.go index 5116108dc7..a162aa7973 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -373,8 +373,9 @@ func (t *rawType) CanSeq() bool { case Int8, Int16, Int32, Int64, Int, Uint8, Uint16, Uint32, Uint64, Uint, Uintptr, Array, Slice, Chan, String, Map: return true case Func: - return false // TODO: implement canRangeFunc - // return canRangeFunc(&t.) + // TODO: implement canRangeFunc + // return canRangeFunc(t) + panic("unimplemented: (reflect.Type).CanSeq() for functions") case Pointer: return t.Elem().Kind() == Array } @@ -386,8 +387,9 @@ func (t *rawType) CanSeq2() bool { case Array, Slice, String, Map: return true case Func: - return false // TODO: implement canRangeFunc2 - // return canRangeFunc2(&t.t) + // TODO: implement canRangeFunc2 + // return canRangeFunc2(t) + panic("unimplemented: (reflect.Type).CanSeq2() for functions") case Pointer: return t.Elem().Kind() == Array } From 24eed9e376a7f49928e8d3c968c5610550d1890c Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 17 Mar 2025 07:42:09 -0700 Subject: [PATCH 30/45] reflect: remove strconv.go --- src/reflect/strconv.go | 347 ----------------------------------------- 1 file changed, 347 deletions(-) delete mode 100644 src/reflect/strconv.go diff --git a/src/reflect/strconv.go b/src/reflect/strconv.go deleted file mode 100644 index 2d8131c9df..0000000000 --- a/src/reflect/strconv.go +++ /dev/null @@ -1,347 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package reflect - -import ( - "unicode/utf8" -) - -// errSyntax indicates that a value does not have the right syntax for the target type. -var errSyntax = badSyntax{} - -type badSyntax struct{} - -func (badSyntax) Error() string { - return "invalid syntax" -} - -func unhex(b byte) (v rune, ok bool) { - c := rune(b) - switch { - case '0' <= c && c <= '9': - return c - '0', true - case 'a' <= c && c <= 'f': - return c - 'a' + 10, true - case 'A' <= c && c <= 'F': - return c - 'A' + 10, true - } - return -} - -const ( - lowerhex = "0123456789abcef" -) - -// unquoteChar decodes the first character or byte in the escaped string -// or character literal represented by the string s. -// It returns four values: -// -// 1. value, the decoded Unicode code point or byte value; -// 2. multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation; -// 3. tail, the remainder of the string after the character; and -// 4. an error that will be nil if the character is syntactically valid. -// -// The second argument, quote, specifies the type of literal being parsed -// and therefore which escaped quote character is permitted. -// If set to a single quote, it permits the sequence \' and disallows unescaped '. -// If set to a double quote, it permits \" and disallows unescaped ". -// If set to zero, it does not permit either escape and allows both quote characters to appear unescaped. -func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) { - // easy cases - if len(s) == 0 { - err = errSyntax - return - } - switch c := s[0]; { - case c == quote && (quote == '\'' || quote == '"'): - err = errSyntax - return - case c >= utf8.RuneSelf: - r, size := utf8.DecodeRuneInString(s) - return r, true, s[size:], nil - case c != '\\': - return rune(s[0]), false, s[1:], nil - } - - // hard case: c is backslash - if len(s) <= 1 { - err = errSyntax - return - } - c := s[1] - s = s[2:] - - switch c { - case 'a': - value = '\a' - case 'b': - value = '\b' - case 'f': - value = '\f' - case 'n': - value = '\n' - case 'r': - value = '\r' - case 't': - value = '\t' - case 'v': - value = '\v' - case 'x', 'u', 'U': - n := 0 - switch c { - case 'x': - n = 2 - case 'u': - n = 4 - case 'U': - n = 8 - } - var v rune - if len(s) < n { - err = errSyntax - return - } - for j := 0; j < n; j++ { - x, ok := unhex(s[j]) - if !ok { - err = errSyntax - return - } - v = v<<4 | x - } - s = s[n:] - if c == 'x' { - // single-byte string, possibly not UTF-8 - value = v - break - } - if v > utf8.MaxRune { - err = errSyntax - return - } - value = v - multibyte = true - case '0', '1', '2', '3', '4', '5', '6', '7': - v := rune(c) - '0' - if len(s) < 2 { - err = errSyntax - return - } - for j := 0; j < 2; j++ { // one digit already; two more - x := rune(s[j]) - '0' - if x < 0 || x > 7 { - err = errSyntax - return - } - v = (v << 3) | x - } - s = s[2:] - if v > 255 { - err = errSyntax - return - } - value = v - case '\\': - value = '\\' - case '\'', '"': - if c != quote { - err = errSyntax - return - } - value = rune(c) - default: - err = errSyntax - return - } - tail = s - return -} - -// unquote interprets s as a single-quoted, double-quoted, -// or backquoted Go string literal, returning the string value -// that s quotes. (If s is single-quoted, it would be a Go -// character literal; unquote returns the corresponding -// one-character string.) -func unquote(s string) (string, error) { - n := len(s) - if n < 2 { - return "", errSyntax - } - quote := s[0] - if quote != s[n-1] { - return "", errSyntax - } - s = s[1 : n-1] - - if quote == '`' { - if contains(s, '`') { - return "", errSyntax - } - if contains(s, '\r') { - // -1 because we know there is at least one \r to remove. - buf := make([]byte, 0, len(s)-1) - for i := 0; i < len(s); i++ { - if s[i] != '\r' { - buf = append(buf, s[i]) - } - } - return string(buf), nil - } - return s, nil - } - if quote != '"' && quote != '\'' { - return "", errSyntax - } - if contains(s, '\n') { - return "", errSyntax - } - - // Is it trivial? Avoid allocation. - if !contains(s, '\\') && !contains(s, quote) { - switch quote { - case '"': - if utf8.ValidString(s) { - return s, nil - } - case '\'': - r, size := utf8.DecodeRuneInString(s) - if size == len(s) && (r != utf8.RuneError || size != 1) { - return s, nil - } - } - } - - var runeTmp [utf8.UTFMax]byte - buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations. - for len(s) > 0 { - c, multibyte, ss, err := unquoteChar(s, quote) - if err != nil { - return "", err - } - s = ss - if c < utf8.RuneSelf || !multibyte { - buf = append(buf, byte(c)) - } else { - n := utf8.EncodeRune(runeTmp[:], c) - buf = append(buf, runeTmp[:n]...) - } - if quote == '\'' && len(s) != 0 { - // single-quoted must be single character - return "", errSyntax - } - } - return string(buf), nil -} - -func quote(s string) string { - buf := make([]byte, 0, 3*len(s)/2) - const quote = '"' - - buf = append(buf, quote) - for width := 0; len(s) > 0; s = s[width:] { - r := rune(s[0]) - width = 1 - if r >= utf8.RuneSelf { - r, width = utf8.DecodeRuneInString(s) - } - if width == 1 && r == utf8.RuneError { - buf = append(buf, `\x`...) - buf = append(buf, lowerhex[s[0]>>4]) - buf = append(buf, lowerhex[s[0]&0xF]) - continue - } - buf = appendEscapedRune(buf, r) - } - buf = append(buf, quote) - return string(buf) -} - -func appendEscapedRune(buf []byte, r rune) []byte { - - const quote = '"' - - var runeTmp [utf8.UTFMax]byte - if r == rune(quote) || r == '\\' { // always backslashed - buf = append(buf, '\\') - buf = append(buf, byte(r)) - return buf - } - if isPrint(r) { - n := utf8.EncodeRune(runeTmp[:], r) - buf = append(buf, runeTmp[:n]...) - return buf - } - switch r { - case '\a': - buf = append(buf, `\a`...) - case '\b': - buf = append(buf, `\b`...) - case '\f': - buf = append(buf, `\f`...) - case '\n': - buf = append(buf, `\n`...) - case '\r': - buf = append(buf, `\r`...) - case '\t': - buf = append(buf, `\t`...) - case '\v': - buf = append(buf, `\v`...) - default: - switch { - case r < ' ' || r == 0x7f: - buf = append(buf, `\x`...) - buf = append(buf, lowerhex[byte(r)>>4]) - buf = append(buf, lowerhex[byte(r)&0xF]) - case !utf8.ValidRune(r): - r = 0xFFFD - fallthrough - case r < 0x10000: - buf = append(buf, `\u`...) - for s := 12; s >= 0; s -= 4 { - buf = append(buf, lowerhex[r>>uint(s)&0xF]) - } - default: - buf = append(buf, `\U`...) - for s := 28; s >= 0; s -= 4 { - buf = append(buf, lowerhex[r>>uint(s)&0xF]) - } - } - } - return buf -} - -// This is only used for struct tags. Assume -func isPrint(r rune) bool { - if r <= 0xFF { - if 0x20 <= r && r <= 0x7E { - // All the ASCII is printable from space through DEL-1. - return true - } - if 0xA1 <= r && r <= 0xFF { - // Similarly for ¡ through ÿ... - return r != 0xAD // ...except for the bizarre soft hyphen. - } - return false - } - - // TinyGo: Skip all other unicode processing - return false -} - -// contains reports whether the string contains the byte c. -func contains(s string, c byte) bool { - return indexByteString(s, c) != -1 -} - -// Index finds the index of the first instance of the specified byte in the string. -// If the byte is not found, this returns -1. -func indexByteString(s string, c byte) int { - for i := 0; i < len(s); i++ { - if s[i] == c { - return i - } - } - return -1 -} From d95c1b59826c84f001b733fc4535a6200fd6f1b0 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 17 Mar 2025 08:03:00 -0700 Subject: [PATCH 31/45] reflect: remove unused go:linkname functions --- src/reflect/value.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index fe41f2d184..026ea02060 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -13,12 +13,6 @@ func Indirect(v Value) Value { return Value{reflectlite.Indirect(v.Value)} } -//go:linkname composeInterface runtime.composeInterface -func composeInterface(unsafe.Pointer, unsafe.Pointer) interface{} - -//go:linkname decomposeInterface runtime.decomposeInterface -func decomposeInterface(i interface{}) (unsafe.Pointer, unsafe.Pointer) - func ValueOf(i interface{}) Value { return Value{reflectlite.ValueOf(i)} } @@ -61,12 +55,6 @@ func (v Value) MapIndex(key Value) Value { return Value{v.Value.MapIndex(key.Value)} } -//go:linkname hashmapNewIterator runtime.hashmapNewIterator -func hashmapNewIterator() unsafe.Pointer - -//go:linkname hashmapNext runtime.hashmapNext -func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool - func (v Value) MapRange() *MapIter { return (*MapIter)(v.Value.MapRange()) } @@ -97,9 +85,6 @@ func (v Value) Convert(t Type) Value { return Value{v.Value.Convert(toRawType(t))} } -//go:linkname slicePanic runtime.slicePanic -func slicePanic() - func MakeSlice(typ Type, len, cap int) Value { return Value{reflectlite.MakeSlice(toRawType(typ), len, cap)} } @@ -158,9 +143,6 @@ func (v Value) FieldByNameFunc(match func(string) bool) Value { return Value{v.Value.FieldByNameFunc(match)} } -//go:linkname hashmapMake runtime.hashmapMake -func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer - type SelectDir int const ( From 6a25fd494e5c25d365e203cc2a2d6ead55ef50c0 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 17 Mar 2025 08:34:49 -0700 Subject: [PATCH 32/45] reflect, internal/reflectlite: add Value.SetIter{Key,Value} and MapIter.Reset Fixes #4790. Depends on #4787 (merge that first). --- src/internal/reflectlite/value.go | 42 ++++++++++++++++++++----------- src/reflect/all_test.go | 8 +++--- src/reflect/value.go | 12 +++++++++ 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/internal/reflectlite/value.go b/src/internal/reflectlite/value.go index 31015cbd27..22a6628562 100644 --- a/src/internal/reflectlite/value.go +++ b/src/internal/reflectlite/value.go @@ -1102,20 +1102,9 @@ func hashmapNewIterator() unsafe.Pointer func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool func (v Value) MapRange() *MapIter { - if v.Kind() != Map { - panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) - } - - keyType := v.typecode.key() - - keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 - shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() - - return &MapIter{ - m: v, - it: hashmapNewIterator(), - unpackKeyInterface: shouldUnpackInterface, - } + iter := &MapIter{} + iter.Reset(v) + return iter } type MapIter struct { @@ -1142,6 +1131,10 @@ func (it *MapIter) Key() Value { return it.key.Elem() } +func (v Value) SetIterKey(iter *MapIter) { + v.Set(iter.Key()) +} + func (it *MapIter) Value() Value { if !it.valid { panic("reflect.MapIter.Value called on invalid iterator") @@ -1150,6 +1143,10 @@ func (it *MapIter) Value() Value { return it.val.Elem() } +func (v Value) SetIterValue(iter *MapIter) { + v.Set(iter.Value()) +} + func (it *MapIter) Next() bool { it.key = New(it.m.typecode.Key()) it.val = New(it.m.typecode.Elem()) @@ -1158,6 +1155,23 @@ func (it *MapIter) Next() bool { return it.valid } +func (iter *MapIter) Reset(v Value) { + if v.Kind() != Map { + panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) + } + + keyType := v.typecode.key() + + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() + + *iter = MapIter{ + m: v, + it: hashmapNewIterator(), + unpackKeyInterface: shouldUnpackInterface, + } +} + func (v Value) Set(x Value) { v.checkAddressable() v.checkRO() diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 436bc00341..b009c325a8 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -381,8 +381,6 @@ func TestSetValue(t *testing.T) { } } -/* - func TestMapIterSet(t *testing.T) { m := make(map[string]any, len(valueTests)) for _, tt := range valueTests { @@ -430,8 +428,6 @@ func TestMapIterSet(t *testing.T) { } } -*/ - func TestCanIntUintFloatComplex(t *testing.T) { type integer int type uinteger uint @@ -7955,6 +7951,8 @@ func TestConvertibleTo(t *testing.T) { } } +*/ + func TestSetIter(t *testing.T) { data := map[string]int{ "foo": 1, @@ -8044,6 +8042,8 @@ func TestSetIter(t *testing.T) { } } +/* + func TestMethodCallValueCodePtr(t *testing.T) { m := ValueOf(Point{}).Method(1) want := MethodValueCallCodePtr() diff --git a/src/reflect/value.go b/src/reflect/value.go index 026ea02060..5d8c25f2d1 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -65,14 +65,26 @@ func (it *MapIter) Key() Value { return Value{((*reflectlite.MapIter)(it)).Key()} } +func (v Value) SetIterKey(iter *MapIter) { + v.Value.SetIterKey((*reflectlite.MapIter)(iter)) +} + func (it *MapIter) Value() Value { return Value{((*reflectlite.MapIter)(it)).Value()} } +func (v Value) SetIterValue(iter *MapIter) { + v.Value.SetIterValue((*reflectlite.MapIter)(iter)) +} + func (it *MapIter) Next() bool { return ((*reflectlite.MapIter)(it)).Next() } +func (iter *MapIter) Reset(v Value) { + (*reflectlite.MapIter)(iter).Reset(v.Value) +} + func (v Value) Set(x Value) { v.Value.Set(x.Value) } From 5282b4efac70466bf372676ae2f99fa065978490 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 17 Mar 2025 14:26:48 +0100 Subject: [PATCH 33/45] runtime: remove unused file func.go This was used in the past, but we don't use it anymore. --- compiler/llvm.go | 7 ------- interp/memory.go | 13 ------------- src/runtime/func.go | 28 ---------------------------- 3 files changed, 48 deletions(-) delete mode 100644 src/runtime/func.go diff --git a/compiler/llvm.go b/compiler/llvm.go index 59aaee8fdd..139c5a1cd8 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -371,13 +371,6 @@ func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.In return big.NewInt(1) case llvm.StructTypeKind: ptrs := big.NewInt(0) - if typ.StructName() == "runtime.funcValue" { - // Hack: the type runtime.funcValue contains an 'id' field which is - // of type uintptr, but before the LowerFuncValues pass it actually - // contains a pointer (ptrtoint) to a global. This trips up the - // interp package. Therefore, make the id field a pointer for now. - typ = c.ctx.StructType([]llvm.Type{c.dataPtrType, c.dataPtrType}, false) - } for i, subtyp := range typ.StructElementTypes() { subptrs := c.getPointerBitmap(subtyp, pos) if subptrs.BitLen() == 0 { diff --git a/interp/memory.go b/interp/memory.go index ba224f9d59..2812cd01c2 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -824,19 +824,6 @@ func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) { if err != nil { return llvm.Value{}, err } - if !field.IsAGlobalVariable().IsNil() { - elementType := field.GlobalValueType() - if elementType.TypeKind() == llvm.StructTypeKind { - // There are some special pointer types that should be used - // as a ptrtoint, so that they can be used in certain - // optimizations. - name := elementType.StructName() - if name == "runtime.funcValueWithSignature" { - uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8) - field = llvm.ConstPtrToInt(field, uintptrType) - } - } - } structFields = append(structFields, field) i += mem.r.pointerSize continue diff --git a/src/runtime/func.go b/src/runtime/func.go deleted file mode 100644 index 879424b5d7..0000000000 --- a/src/runtime/func.go +++ /dev/null @@ -1,28 +0,0 @@ -package runtime - -// This file implements some data types that may be useful for some -// implementations of func values. - -import ( - "unsafe" -) - -// funcValue is the underlying type of func values, depending on which func -// value representation was used. -type funcValue struct { - context unsafe.Pointer // function context, for closures and bound methods - id uintptr // ptrtoint of *funcValueWithSignature before lowering, opaque index (non-0) after lowering -} - -// funcValueWithSignature is used before the func lowering pass. -type funcValueWithSignature struct { - funcPtr uintptr // ptrtoint of the actual function pointer - signature *uint8 // external *i8 with a name identifying the function signature -} - -// getFuncPtr is a dummy function that may be used if the func lowering pass is -// not used. It is generally too slow but may be a useful fallback to debug the -// func lowering pass. -func getFuncPtr(val funcValue, signature *uint8) uintptr { - return (*funcValueWithSignature)(unsafe.Pointer(val.id)).funcPtr -} From a4cbe3326ec37313bfd92c01c3dbf313cae4e2b8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 5 Aug 2024 16:12:48 +0200 Subject: [PATCH 34/45] runtime: only allocate heap memory when needed For example, with -gc=none and -gc=leaking, no heap needs to be allocated when initializing the runtime. And some GCs (like -gc=custom) are responsible for allocating the heap themselves. --- src/runtime/gc_blocks.go | 1 + src/runtime/gc_custom.go | 2 ++ src/runtime/gc_leaking.go | 2 ++ src/runtime/gc_none.go | 2 ++ src/runtime/runtime_unix.go | 7 +++++-- 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index d58bfd92a2..6d32863649 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -37,6 +37,7 @@ import ( ) const gcDebug = false +const needsStaticHeap = true // Some globals + constants for the entire GC. diff --git a/src/runtime/gc_custom.go b/src/runtime/gc_custom.go index a34b7dce69..0125f1688b 100644 --- a/src/runtime/gc_custom.go +++ b/src/runtime/gc_custom.go @@ -36,6 +36,8 @@ import ( "unsafe" ) +const needsStaticHeap = false + // initHeap is called when the heap is first initialized at program start. func initHeap() diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index 9e84dc2a53..bdc7efe810 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -11,6 +11,8 @@ import ( "unsafe" ) +const needsStaticHeap = true + // Ever-incrementing pointer: no memory is freed. var heapptr uintptr diff --git a/src/runtime/gc_none.go b/src/runtime/gc_none.go index 43d3c4904b..173c1f6439 100644 --- a/src/runtime/gc_none.go +++ b/src/runtime/gc_none.go @@ -10,6 +10,8 @@ import ( "unsafe" ) +const needsStaticHeap = false + var gcTotalAlloc uint64 // for runtime.MemStats var gcMallocs uint64 var gcFrees uint64 diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 08e3e74269..6add9157d3 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -79,7 +79,10 @@ var stackTop uintptr // //export main func main(argc int32, argv *unsafe.Pointer) int { - preinit() + if needsStaticHeap { + // Allocate area for the heap if the GC needs it. + allocateHeap() + } // Store argc and argv for later use. main_argc = argc @@ -298,7 +301,7 @@ var heapMaxSize uintptr var heapStart, heapEnd uintptr -func preinit() { +func allocateHeap() { // Allocate a large chunk of virtual memory. Because it is virtual, it won't // really be allocated in RAM. Memory will only be allocated when it is // first touched. From 3a7c25f9878723beb927225523905596a7bf5af9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 13 Aug 2023 23:32:45 +0200 Subject: [PATCH 35/45] all: add the Boehm-Demers-Weiser GC on Linux This adds support for the well-known Boehm GC. It's significantly faster than our own naive GC and could be used as an alternative on bigger systems. In the future, this GC might also be supported on WebAssembly with some extra work. Right now it's Linux only (though Windows/MacOS shouldn't be too difficult to add). --- .github/workflows/nix.yml | 4 +- .gitmodules | 3 + GNUmakefile | 5 ++ builder/bdwgc.go | 71 +++++++++++++++ builder/build.go | 29 ++++-- builder/library.go | 25 ++++-- builder/musl.go | 6 ++ compileopts/config.go | 82 ++++++++++------- compileopts/options.go | 2 +- compileopts/options_test.go | 2 +- compileopts/target.go | 4 +- lib/bdwgc | 1 + src/runtime/gc_blocks.go | 6 ++ src/runtime/gc_boehm.go | 172 ++++++++++++++++++++++++++++++++++++ src/runtime/gc_globals.go | 2 +- src/runtime/gc_stack_raw.go | 5 +- 16 files changed, 364 insertions(+), 55 deletions(-) create mode 100644 builder/bdwgc.go create mode 160000 lib/bdwgc create mode 100644 src/runtime/gc_boehm.go diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 2f24df54a8..b19538c4d9 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -22,9 +22,9 @@ jobs: run: sudo apt-get remove llvm-18 - name: Checkout uses: actions/checkout@v4 - - name: Pull musl + - name: Pull musl, bdwgc run: | - git submodule update --init lib/musl + git submodule update --init lib/musl lib/bdwgc - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source diff --git a/.gitmodules b/.gitmodules index 91bd14a7d7..97689eff82 100644 --- a/.gitmodules +++ b/.gitmodules @@ -39,3 +39,6 @@ [submodule "lib/wasi-cli"] path = lib/wasi-cli url = https://github.com/WebAssembly/wasi-cli +[submodule "lib/bdwgc"] + path = lib/bdwgc + url = https://github.com/ivmai/bdwgc.git diff --git a/GNUmakefile b/GNUmakefile index bcbc21b263..8a25e1df99 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -940,6 +940,7 @@ wasmtest: build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen) @mkdir -p build/release/tinygo/bin + @mkdir -p build/release/tinygo/lib/bdwgc @mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/macos-minimal-sdk @@ -961,6 +962,7 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN ifneq ($(USE_SYSTEM_BINARYEN),1) @cp -p build/wasm-opt$(EXE) build/release/tinygo/bin endif + @cp -rp lib/bdwgc/* build/release/tinygo/lib/bdwgc @cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include @cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS @cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS @@ -974,6 +976,7 @@ endif @cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt @cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl @cp -rp lib/musl/include build/release/tinygo/lib/musl + @cp -rp lib/musl/src/ctype build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src @@ -988,8 +991,10 @@ endif @cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/sched build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/stdlib build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src diff --git a/builder/bdwgc.go b/builder/bdwgc.go new file mode 100644 index 0000000000..c7c797636e --- /dev/null +++ b/builder/bdwgc.go @@ -0,0 +1,71 @@ +package builder + +// The well-known conservative Boehm-Demers-Weiser GC. +// This file provides a way to compile this GC for use with TinyGo. + +import ( + "path/filepath" + + "github.com/tinygo-org/tinygo/goenv" +) + +var BoehmGC = Library{ + name: "bdwgc", + cflags: func(target, headerPath string) []string { + libdir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc") + return []string{ + // use a modern environment + "-DUSE_MMAP", // mmap is available + "-DUSE_MUNMAP", // return memory to the OS using munmap + "-DGC_BUILTIN_ATOMIC", // use compiler intrinsics for atomic operations + "-DNO_EXECUTE_PERMISSION", // don't make the heap executable + + // specific flags for TinyGo + "-DALL_INTERIOR_POINTERS", // scan interior pointers (needed for Go) + "-DIGNORE_DYNAMIC_LOADING", // we don't support dynamic loading at the moment + "-DNO_GETCONTEXT", // musl doesn't support getcontext() + + // Special flag to work around the lack of __data_start in ld.lld. + // TODO: try to fix this in LLVM/lld directly so we don't have to + // work around it anymore. + "-DGC_DONT_REGISTER_MAIN_STATIC_DATA", + + // Do not scan the stack. We have our own mechanism to do this. + "-DSTACK_NOT_SCANNED", + + // Assertions can be enabled while debugging GC issues. + //"-DGC_ASSERTIONS", + + // Threading is not yet supported, so these are disabled. + //"-DGC_THREADS", + //"-DTHREAD_LOCAL_ALLOC", + + "-I" + libdir + "/include", + } + }, + sourceDir: func() string { + return filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc") + }, + librarySources: func(target string) ([]string, error) { + return []string{ + "allchblk.c", + "alloc.c", + "blacklst.c", + "dbg_mlc.c", + "dyn_load.c", + "finalize.c", + "headers.c", + "mach_dep.c", + "malloc.c", + "mark.c", + "mark_rts.c", + "misc.c", + "new_hblk.c", + "obj_map.c", + "os_dep.c", + "pthread_stop_world.c", + "pthread_support.c", + "reclaim.c", + }, nil + }, +} diff --git a/builder/build.go b/builder/build.go index c2c712456e..9d73a03664 100644 --- a/builder/build.go +++ b/builder/build.go @@ -147,20 +147,22 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // the libc needs them. root := goenv.Get("TINYGOROOT") var libcDependencies []*compileJob + var libcJob *compileJob switch config.Target.Libc { case "darwin-libSystem": job := makeDarwinLibSystemJob(config, tmpdir) libcDependencies = append(libcDependencies, job) case "musl": - job, unlock, err := libMusl.load(config, tmpdir) + var unlock func() + libcJob, unlock, err = libMusl.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } defer unlock() - libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o"))) - libcDependencies = append(libcDependencies, job) + libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(libcJob.result), "crt1.o"))) + libcDependencies = append(libcDependencies, libcJob) case "picolibc": - libcJob, unlock, err := libPicolibc.load(config, tmpdir) + libcJob, unlock, err := libPicolibc.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } @@ -173,14 +175,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } libcDependencies = append(libcDependencies, dummyCompileJob(path)) case "wasmbuiltins": - libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir) + libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } defer unlock() libcDependencies = append(libcDependencies, libcJob) case "mingw-w64": - job, unlock, err := libMinGW.load(config, tmpdir) + job, unlock, err := libMinGW.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } @@ -701,7 +703,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. if config.Target.RTLib == "compiler-rt" { - job, unlock, err := libCompilerRT.load(config, tmpdir) + job, unlock, err := libCompilerRT.load(config, tmpdir, nil) if err != nil { return result, err } @@ -709,6 +711,19 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe linkerDependencies = append(linkerDependencies, job) } + // The Boehm collector is stored in a separate C library. + if config.GC() == "boehm" { + if libcJob == nil { + return BuildResult{}, fmt.Errorf("boehm GC isn't supported with libc %s", config.Target.Libc) + } + job, unlock, err := BoehmGC.load(config, tmpdir, libcJob) + if err != nil { + return BuildResult{}, err + } + defer unlock() + linkerDependencies = append(linkerDependencies, job) + } + // Add jobs to compile extra files. These files are in C or assembly and // contain things like the interrupt vector table and low level operations // such as stack switching. diff --git a/builder/library.go b/builder/library.go index c79f7ce3f3..1b6afe2fcd 100644 --- a/builder/library.go +++ b/builder/library.go @@ -43,7 +43,11 @@ type Library struct { // output archive file, it is expected to be removed after use. // As a side effect, this call creates the library header files if they didn't // exist yet. -func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) { +// The provided libc job (if not null) will cause this libc to be added as a +// dependency for all C compiler jobs, and adds libc headers for the given +// target config. In other words, pass this libc if the library needs a libc to +// compile. +func (l *Library) load(config *compileopts.Config, tmpdir string, libc *compileJob) (job *compileJob, abortLock func(), err error) { outdir, precompiled := config.LibcPath(l.name) archiveFilePath := filepath.Join(outdir, "lib.a") if precompiled { @@ -181,6 +185,9 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ args = append(args, "-mfpu=vfpv2") } } + if libc != nil { + args = append(args, config.LibcCFlags()...) + } var once sync.Once @@ -233,7 +240,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ objpath := filepath.Join(dir, cleanpath+".o") os.MkdirAll(filepath.Dir(objpath), 0o777) objs = append(objs, objpath) - job.dependencies = append(job.dependencies, &compileJob{ + objfile := &compileJob{ description: "compile " + srcpath, run: func(*compileJob) error { var compileArgs []string @@ -248,7 +255,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ } return nil }, - }) + } + if libc != nil { + objfile.dependencies = append(objfile.dependencies, libc) + } + job.dependencies = append(job.dependencies, objfile) } // Create crt1.o job, if needed. @@ -257,7 +268,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ // won't make much of a difference in speed). if l.crt1Source != "" { srcpath := filepath.Join(sourceDir, l.crt1Source) - job.dependencies = append(job.dependencies, &compileJob{ + crt1Job := &compileJob{ description: "compile " + srcpath, run: func(*compileJob) error { var compileArgs []string @@ -277,7 +288,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ } return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o")) }, - }) + } + if libc != nil { + crt1Job.dependencies = append(crt1Job.dependencies, libc) + } + job.dependencies = append(job.dependencies, crt1Job) } ok = true diff --git a/builder/musl.go b/builder/musl.go index 3c79c7c43a..dc03be46f7 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -113,26 +113,32 @@ var libMusl = Library{ librarySources: func(target string) ([]string, error) { arch := compileopts.MuslArchitecture(target) globs := []string{ + "ctype/*.c", "env/*.c", "errno/*.c", "exit/*.c", "fcntl/*.c", "internal/defsysinfo.c", + "internal/intscan.c", "internal/libc.c", + "internal/shgetc.c", "internal/syscall_ret.c", "internal/vdso.c", "legacy/*.c", "locale/*.c", "linux/*.c", + "locale/*.c", "malloc/*.c", "malloc/mallocng/*.c", "mman/*.c", "math/*.c", "misc/*.c", "multibyte/*.c", + "sched/*.c", "signal/" + arch + "/*.s", "signal/*.c", "stdio/*.c", + "stdlib/*.c", "string/*.c", "thread/" + arch + "/*.s", "thread/*.c", diff --git a/compileopts/config.go b/compileopts/config.go index ee5c34537c..a9eb235ad1 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -261,6 +261,10 @@ func (c *Config) LibcPath(name string) (path string, precompiled bool) { if c.Target.SoftFloat { archname += "-softfloat" } + if name == "bdwgc" { + // Boehm GC is compiled against a particular libc. + archname += "-" + c.Target.Libc + } // Try to load a precompiled library. precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name) @@ -315,83 +319,93 @@ func (c *Config) CFlags(libclang bool) []string { "-resource-dir="+resourceDir, ) } + cflags = append(cflags, c.LibcCFlags()...) + // Always emit debug information. It is optionally stripped at link time. + cflags = append(cflags, "-gdwarf-4") + // Use the same optimization level as TinyGo. + cflags = append(cflags, "-O"+c.Options.Opt) + // Set the LLVM target triple. + cflags = append(cflags, "--target="+c.Triple()) + // Set the -mcpu (or similar) flag. + if c.Target.CPU != "" { + if c.GOARCH() == "amd64" || c.GOARCH() == "386" { + // x86 prefers the -march flag (-mcpu is deprecated there). + cflags = append(cflags, "-march="+c.Target.CPU) + } else if strings.HasPrefix(c.Triple(), "avr") { + // AVR MCUs use -mmcu instead of -mcpu. + cflags = append(cflags, "-mmcu="+c.Target.CPU) + } else { + // The rest just uses -mcpu. + cflags = append(cflags, "-mcpu="+c.Target.CPU) + } + } + // Set the -mabi flag, if needed. + if c.ABI() != "" { + cflags = append(cflags, "-mabi="+c.ABI()) + } + return cflags +} + +// LibcCFlags returns the C compiler flags for the configured libc. +// It only uses flags that are part of the libc path (triple, cpu, abi, libc +// name) so it can safely be used to compile another C library. +func (c *Config) LibcCFlags() []string { switch c.Target.Libc { case "darwin-libSystem": root := goenv.Get("TINYGOROOT") - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"), - ) + } case "picolibc": root := goenv.Get("TINYGOROOT") picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc") path, _ := c.LibcPath("picolibc") - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(picolibcDir, "include"), "-isystem", filepath.Join(picolibcDir, "tinystdio"), "-D__PICOLIBC_ERRNO_FUNCTION=__errno_location", - ) + } case "musl": root := goenv.Get("TINYGOROOT") path, _ := c.LibcPath("musl") arch := MuslArchitecture(c.Triple()) - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "musl", "arch", arch), "-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"), "-isystem", filepath.Join(root, "lib", "musl", "include"), - ) + } case "wasi-libc": root := goenv.Get("TINYGOROOT") - cflags = append(cflags, + return []string{ "-nostdlibinc", - "-isystem", root+"/lib/wasi-libc/sysroot/include") + "-isystem", root + "/lib/wasi-libc/sysroot/include", + } case "wasmbuiltins": // nothing to add (library is purely for builtins) + return nil case "mingw-w64": root := goenv.Get("TINYGOROOT") path, _ := c.LibcPath("mingw-w64") - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"), "-D_UCRT", - ) + } case "": // No libc specified, nothing to add. + return nil default: // Incorrect configuration. This could be handled in a better way, but // usually this will be found by developers (not by TinyGo users). panic("unknown libc: " + c.Target.Libc) } - // Always emit debug information. It is optionally stripped at link time. - cflags = append(cflags, "-gdwarf-4") - // Use the same optimization level as TinyGo. - cflags = append(cflags, "-O"+c.Options.Opt) - // Set the LLVM target triple. - cflags = append(cflags, "--target="+c.Triple()) - // Set the -mcpu (or similar) flag. - if c.Target.CPU != "" { - if c.GOARCH() == "amd64" || c.GOARCH() == "386" { - // x86 prefers the -march flag (-mcpu is deprecated there). - cflags = append(cflags, "-march="+c.Target.CPU) - } else if strings.HasPrefix(c.Triple(), "avr") { - // AVR MCUs use -mmcu instead of -mcpu. - cflags = append(cflags, "-mmcu="+c.Target.CPU) - } else { - // The rest just uses -mcpu. - cflags = append(cflags, "-mcpu="+c.Target.CPU) - } - } - // Set the -mabi flag, if needed. - if c.ABI() != "" { - cflags = append(cflags, "-mabi="+c.ABI()) - } - return cflags } // LDFlags returns the flags to pass to the linker. A few more flags are needed diff --git a/compileopts/options.go b/compileopts/options.go index e698cb3cb7..ed248c8020 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -9,7 +9,7 @@ import ( var ( validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"} - validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"} + validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"} validSchedulerOptions = []string{"none", "tasks", "asyncify"} validSerialOptions = []string{"none", "uart", "usb", "rtt"} validPrintSizeOptions = []string{"none", "short", "full", "html"} diff --git a/compileopts/options_test.go b/compileopts/options_test.go index ee63c4c46d..280ff9b467 100644 --- a/compileopts/options_test.go +++ b/compileopts/options_test.go @@ -9,7 +9,7 @@ import ( func TestVerifyOptions(t *testing.T) { - expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`) + expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise, boehm`) expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`) expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`) expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`) diff --git a/compileopts/target.go b/compileopts/target.go index 64cbc2daa9..6917a944bd 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -247,7 +247,6 @@ func defaultTarget(options *Options) (*TargetSpec, error) { GOOS: options.GOOS, GOARCH: options.GOARCH, BuildTags: []string{options.GOOS, options.GOARCH}, - GC: "precise", Scheduler: "tasks", Linker: "cc", DefaultStackSize: 1024 * 64, // 64kB @@ -366,6 +365,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { llvmvendor := "unknown" switch options.GOOS { case "darwin": + spec.GC = "precise" platformVersion := "10.12.0" if options.GOARCH == "arm64" { platformVersion = "11.0.0" // first macosx platform with arm64 support @@ -388,6 +388,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { "src/runtime/runtime_unix.c", "src/runtime/signal.c") case "linux": + spec.GC = "boehm" spec.Linker = "ld.lld" spec.RTLib = "compiler-rt" spec.Libc = "musl" @@ -411,6 +412,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { "src/runtime/runtime_unix.c", "src/runtime/signal.c") case "windows": + spec.GC = "precise" spec.Linker = "ld.lld" spec.Libc = "mingw-w64" // Note: using a medium code model, low image base and no ASLR diff --git a/lib/bdwgc b/lib/bdwgc new file mode 160000 index 0000000000..d1ff06cc50 --- /dev/null +++ b/lib/bdwgc @@ -0,0 +1 @@ +Subproject commit d1ff06cc503a74dca0150d5e988f2c93158b0cdf diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 6d32863649..62e38dcd7a 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -534,6 +534,12 @@ func markRoots(start, end uintptr) { } } +func markCurrentGoroutineStack(sp uintptr) { + // This could be optimized by only marking the stack area that's currently + // in use. + markRoot(0, sp) +} + // stackOverflow is a flag which is set when the GC scans too deep while marking. // After it is set, all marked allocations must be re-scanned. var stackOverflow bool diff --git a/src/runtime/gc_boehm.go b/src/runtime/gc_boehm.go new file mode 100644 index 0000000000..0955f3c224 --- /dev/null +++ b/src/runtime/gc_boehm.go @@ -0,0 +1,172 @@ +//go:build gc.boehm + +package runtime + +import ( + "internal/gclayout" + "internal/reflectlite" + "internal/task" + "unsafe" +) + +const needsStaticHeap = false + +// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes. +var zeroSizedAlloc uint8 + +var gcLock task.PMutex + +func initHeap() { + libgc_init() + + libgc_set_push_other_roots(gcCallbackPtr) +} + +var gcCallbackPtr = reflectlite.ValueOf(gcCallback).UnsafePointer() + +func gcCallback() { + // Mark the system stack and (if we're on a goroutine stack) also the + // current goroutine stack. + markStack() + + findGlobals(func(start, end uintptr) { + libgc_push_all(start, end) + }) +} + +func markRoots(start, end uintptr) { + libgc_push_all(start, end) +} + +func markCurrentGoroutineStack(sp uintptr) { + // Only mark the area of the stack that is currently in use. + // (This doesn't work for other goroutines, but at least it doesn't keep + // more pointers alive than needed on the current stack). + base := libgc_base(sp) + if base == 0 { // && asserts + runtimePanic("goroutine stack not in a heap allocation?") + } + stackBottom := base + libgc_size(base) + libgc_push_all_stack(sp, stackBottom) +} + +//go:noinline +func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { + if size == 0 { + return unsafe.Pointer(&zeroSizedAlloc) + } + + gcLock.Lock() + var ptr unsafe.Pointer + if layout == gclayout.NoPtrs { + // This object is entirely pointer free, for example make([]int, ...). + // Make sure the GC knows this so it doesn't scan the object + // unnecessarily to improve performance. + ptr = libgc_malloc_atomic(size) + // Memory returned from libgc_malloc_atomic has not been zeroed so we + // have to do that manually. + memzero(ptr, size) + } else { + // TODO: bdwgc supports typed allocations, which could be useful to + // implement a mostly-precise GC. + ptr = libgc_malloc(size) + // Memory returned from libgc_malloc has already been zeroed, so nothing + // to do here. + } + gcLock.Unlock() + if ptr == nil { + runtimePanic("gc: out of memory") + } + + return ptr +} + +func free(ptr unsafe.Pointer) { + libgc_free(ptr) +} + +func GC() { + libgc_gcollect() +} + +// This should be stack-allocated, but we don't currently have a good way of +// ensuring that happens. +var gcMemStats libgc_prof_stats + +func ReadMemStats(m *MemStats) { + gcLock.Lock() + + libgc_get_prof_stats(&gcMemStats, unsafe.Sizeof(gcMemStats)) + + // Fill in MemStats as well as we can, given the information that bdwgc + // provides to us. + m.HeapIdle = uint64(gcMemStats.free_bytes_full - gcMemStats.unmapped_bytes) + m.HeapInuse = uint64(gcMemStats.heapsize_full - gcMemStats.unmapped_bytes) + m.HeapReleased = uint64(gcMemStats.unmapped_bytes) + m.HeapSys = uint64(m.HeapInuse + m.HeapIdle) + m.GCSys = 0 // not provided by bdwgc + m.TotalAlloc = uint64(gcMemStats.allocd_bytes_before_gc + gcMemStats.bytes_allocd_since_gc) + m.Mallocs = 0 // not provided by bdwgc + m.Frees = 0 // not provided by bdwgc + m.Sys = uint64(gcMemStats.obtained_from_os_bytes) + + gcLock.Unlock() +} + +func setHeapEnd(newHeapEnd uintptr) { + runtimePanic("gc: did not expect setHeapEnd call") +} + +func SetFinalizer(obj interface{}, finalizer interface{}) { + // Unimplemented. + // The GC *does* support finalization, so this could be added relatively + // easily I think. +} + +//export GC_init +func libgc_init() + +//export GC_malloc +func libgc_malloc(uintptr) unsafe.Pointer + +//export GC_malloc_atomic +func libgc_malloc_atomic(uintptr) unsafe.Pointer + +//export GC_free +func libgc_free(unsafe.Pointer) + +//export GC_base +func libgc_base(ptr uintptr) uintptr + +//export GC_size +func libgc_size(ptr uintptr) uintptr + +//export GC_push_all +func libgc_push_all(bottom, top uintptr) + +//export GC_push_all_stack +func libgc_push_all_stack(bottom, top uintptr) + +//export GC_gcollect +func libgc_gcollect() + +//export GC_get_prof_stats +func libgc_get_prof_stats(*libgc_prof_stats, uintptr) uintptr + +//export GC_set_push_other_roots +func libgc_set_push_other_roots(unsafe.Pointer) + +type libgc_prof_stats struct { + heapsize_full uintptr + free_bytes_full uintptr + unmapped_bytes uintptr + bytes_allocd_since_gc uintptr + allocd_bytes_before_gc uintptr + non_gc_bytes uintptr + gc_no uintptr + markers_m1 uintptr + bytes_reclaimed_since_gc uintptr + reclaimed_bytes_before_gc uintptr + expl_freed_bytes_since_gc uintptr + obtained_from_os_bytes uintptr +} diff --git a/src/runtime/gc_globals.go b/src/runtime/gc_globals.go index f27911ec51..3e8f857618 100644 --- a/src/runtime/gc_globals.go +++ b/src/runtime/gc_globals.go @@ -1,4 +1,4 @@ -//go:build (gc.conservative || gc.precise) && (baremetal || tinygo.wasm) +//go:build baremetal || tinygo.wasm package runtime diff --git a/src/runtime/gc_stack_raw.go b/src/runtime/gc_stack_raw.go index 5ee18622db..01ec0208c2 100644 --- a/src/runtime/gc_stack_raw.go +++ b/src/runtime/gc_stack_raw.go @@ -1,4 +1,4 @@ -//go:build (gc.conservative || gc.precise) && !tinygo.wasm +//go:build (gc.conservative || gc.precise || gc.boehm) && !tinygo.wasm package runtime @@ -33,7 +33,6 @@ func scanstack(sp uintptr) { markRoots(sp, stackTop) } else { // This is a goroutine stack. - // It is an allocation, so scan it as if it were a value in a global. - markRoot(0, sp) + markCurrentGoroutineStack(sp) } } From 7755780a498b11865c3517ddb3c34577b4a7b9f5 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 15 Mar 2025 22:00:57 -0700 Subject: [PATCH 36/45] GNUmakefile, internal/wasm-tools: update to go.bytecodealliance.org@v0.6.0 --- GNUmakefile | 7 ++++--- internal/wasm-tools/go.mod | 20 ++++++++++-------- internal/wasm-tools/go.sum | 40 ++++++++++++++++++++---------------- internal/wasm-tools/tools.go | 5 +++-- 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 8a25e1df99..4e630c6a3f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -267,16 +267,17 @@ lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC="$(CLANG)" AR=$(LLVM_AR) NM=$(LLVM_NM) # Generate WASI syscall bindings -WASM_TOOLS_MODULE=github.com/bytecodealliance/wasm-tools-go +WASM_TOOLS_MODULE=go.bytecodealliance.org .PHONY: wasi-syscall wasi-syscall: wasi-cm + rm -rf ./src/internal/wasi/* go run -modfile ./internal/wasm-tools/go.mod $(WASM_TOOLS_MODULE)/cmd/wit-bindgen-go generate --versioned -o ./src/internal -p internal --cm internal/cm ./lib/wasi-cli/wit # Copy package cm into src/internal/cm .PHONY: wasi-cm wasi-cm: - # rm -rf ./src/internal/cm - rsync -rv --delete --exclude '*_test.go' $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE))/cm ./src/internal/ + rm -rf ./src/internal/cm/* + rsync -rv --delete --exclude '*_test.go' $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod index 3404ab78c7..47ad4889c7 100644 --- a/internal/wasm-tools/go.mod +++ b/internal/wasm-tools/go.mod @@ -1,18 +1,22 @@ -module github.com/tinygo-org/tinygo/internal/tools +module github.com/tinygo-org/tinygo/internal/wasm-tools -go 1.22.4 +go 1.23.0 -require github.com/bytecodealliance/wasm-tools-go v0.3.1 +require ( + go.bytecodealliance.org v0.6.0 + go.bytecodealliance.org/cm v0.2.0 +) require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/regclient/regclient v0.7.1 // indirect + github.com/regclient/regclient v0.8.2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tetratelabs/wazero v1.9.0 // indirect github.com/ulikunitz/xz v0.5.12 // indirect - github.com/urfave/cli/v3 v3.0.0-alpha9.2 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/sys v0.26.0 // indirect + github.com/urfave/cli/v3 v3.0.0-beta1 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/sys v0.31.0 // indirect ) diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum index 2f4bef21b6..45b74397d1 100644 --- a/internal/wasm-tools/go.sum +++ b/internal/wasm-tools/go.sum @@ -1,5 +1,3 @@ -github.com/bytecodealliance/wasm-tools-go v0.3.1 h1:9Q9PjSzkbiVmkUvZ7nYCfJ02mcQDBalxycA3s8g7kR4= -github.com/bytecodealliance/wasm-tools-go v0.3.1/go.mod h1:vNAQ8DAEp6xvvk+TUHah5DslLEa76f4H6e737OeaxuY= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,16 +5,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/olareg/olareg v0.1.0 h1:1dXBOgPrig5N7zoXyIZVQqU0QBo6sD9pbL6UYjY75CA= -github.com/olareg/olareg v0.1.0/go.mod h1:RBuU7JW7SoIIxZKzLRhq8sVtQeAHzCAtRrXEBx2KlM4= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/olareg/olareg v0.1.1 h1:Ui7q93zjcoF+U9U71sgqgZWByDoZOpqHitUXEu2xV+g= +github.com/olareg/olareg v0.1.1/go.mod h1:w8NP4SWrHHtxsFaUiv1lnCnYPm4sN1seCd2h7FK/dc0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/regclient/regclient v0.7.1 h1:qEsJrTmZd98fZKjueAbrZCSNGU+ifnr6xjlSAs3WOPs= -github.com/regclient/regclient v0.7.1/go.mod h1:+w/BFtJuw0h0nzIw/z2+1FuA2/dVXBzDq4rYmziJpMc= +github.com/regclient/regclient v0.8.2 h1:23BQ3jWgKYHHIXUhp/S9laVJDHDoOQaQCzXMJ4undVE= +github.com/regclient/regclient v0.8.2/go.mod h1:uGyetv0o6VLyRDjtfeBqp/QBwRLJ3Hcn07/+8QbhNcM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -25,19 +23,25 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli/v3 v3.0.0-alpha9.2 h1:CL8llQj3dGRLVQQzHxS+ZYRLanOuhyK1fXgLKD+qV+Y= -github.com/urfave/cli/v3 v3.0.0-alpha9.2/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= +github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= +go.bytecodealliance.org v0.6.0 h1:9ziqj963aEKqOiu5cl87Hb78KImNP4zEABl+OWwBwxk= +go.bytecodealliance.org v0.6.0/go.mod h1:j0lprXhqeVSE8kYN2EjcN9gm+fxUyNWtl//WA+3ypFg= +go.bytecodealliance.org/cm v0.2.0 h1:HMkj1x1LZWU/Ghu2TtUk3VTyS7gyDHLyIfIut0Cpc5M= +go.bytecodealliance.org/cm v0.2.0/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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= diff --git a/internal/wasm-tools/tools.go b/internal/wasm-tools/tools.go index 0876ea7331..baf54a4a33 100644 --- a/internal/wasm-tools/tools.go +++ b/internal/wasm-tools/tools.go @@ -5,7 +5,8 @@ package tools import ( - _ "github.com/bytecodealliance/wasm-tools-go/cmd/wit-bindgen-go" + _ "go.bytecodealliance.org/cm" + _ "go.bytecodealliance.org/cmd/wit-bindgen-go" ) -//go:generate go install github.com/bytecodealliance/wasm-tools-go/cmd/wit-bindgen-go +//go:generate go install go.bytecodealliance.org/cmd/wit-bindgen-go From 429aea8e74ae06b3fec6e20022eb64b851f3fa59 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 15 Mar 2025 22:01:26 -0700 Subject: [PATCH 37/45] internal/cm: update to go.bytecodealliance.org/cm@v0.2.0 --- src/internal/cm/CHANGELOG.md | 23 ++++ src/internal/cm/LICENSE | 220 ++++++++++++++++++++++++++++++++++ src/internal/cm/README.md | 15 +++ src/internal/cm/RELEASE.md | 34 ++++++ src/internal/cm/case.go | 51 ++++++++ src/internal/cm/docs.go | 2 +- src/internal/cm/empty.s | 3 + src/internal/cm/error.go | 40 +++++++ src/internal/cm/error.wasm.go | 13 ++ src/internal/cm/future.go | 15 +++ src/internal/cm/go.mod | 3 + src/internal/cm/list.go | 60 +++++++++- src/internal/cm/stream.go | 15 +++ src/internal/cm/variant.go | 5 +- 14 files changed, 494 insertions(+), 5 deletions(-) create mode 100644 src/internal/cm/CHANGELOG.md create mode 100644 src/internal/cm/LICENSE create mode 100644 src/internal/cm/README.md create mode 100644 src/internal/cm/RELEASE.md create mode 100644 src/internal/cm/case.go create mode 100644 src/internal/cm/empty.s create mode 100644 src/internal/cm/error.go create mode 100644 src/internal/cm/error.wasm.go create mode 100644 src/internal/cm/future.go create mode 100644 src/internal/cm/go.mod create mode 100644 src/internal/cm/stream.go diff --git a/src/internal/cm/CHANGELOG.md b/src/internal/cm/CHANGELOG.md new file mode 100644 index 0000000000..35efaf2f57 --- /dev/null +++ b/src/internal/cm/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v0.2.0] — 2025-03-15 + +### Added + +- Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`. +- Initial support for JSON serialization of WIT `list`, `enum`, and `record` types. +- Added `cm.CaseUnmarshaler` helper for text and JSON unmarshaling of `enum` and `variant` types. + +### Changed + +- Breaking: package `cm`: removed `bool` from `Discriminant` type constraint. It was not used by code generation. + +## [v0.1.0] — 2024-12-14 + +Initial version, extracted into module [`go.bytecodealliance.org/cm`](https://pkg.go.dev/go.bytecodealliance.org/cm). + +[Unreleased]: +[v0.2.0]: +[v0.1.0]: diff --git a/src/internal/cm/LICENSE b/src/internal/cm/LICENSE new file mode 100644 index 0000000000..f9d81955f4 --- /dev/null +++ b/src/internal/cm/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/src/internal/cm/README.md b/src/internal/cm/README.md new file mode 100644 index 0000000000..d6c5fd4fd5 --- /dev/null +++ b/src/internal/cm/README.md @@ -0,0 +1,15 @@ +# go.bytecodealliance.org/cm + +[![pkg.go.dev](https://img.shields.io/badge/docs-pkg.go.dev-blue.svg)](https://pkg.go.dev/go.bytecodealliance.org/cm) [![build status](https://img.shields.io/github/actions/workflow/status/bytecodealliance/go-modules/test.yaml?branch=main)](https://github.com/bytecodealliance/go-modules/actions) + +## About + +Package `cm` contains helper types and functions used by generated packages, such as `option`, `result`, `variant`, `list`, and `resource`. These are intended for use by generated [Component Model](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#type-definitions) bindings, where the caller converts to a Go equivalent. It attempts to map WIT semantics to their equivalent in Go where possible. + +### Note on Memory Safety + +Package `cm` and generated bindings from `wit-bindgen-go` may have compatibility issues with the Go garbage collector, as they directly represent `variant` and `result` types as tagged unions where a pointer shape may be occupied by a non-pointer value. The GC may detect and throw an error if it detects a non-pointer value in an area it expects to see a pointer. This is an area of active development. + +## License + +This project is licensed under the Apache 2.0 license with the LLVM exception. See [LICENSE](../LICENSE) for more details. diff --git a/src/internal/cm/RELEASE.md b/src/internal/cm/RELEASE.md new file mode 100644 index 0000000000..5a6283a606 --- /dev/null +++ b/src/internal/cm/RELEASE.md @@ -0,0 +1,34 @@ +# Release + +This document describes the steps to release a new version of module `go.bytecodealliance.org/cm`. + +## 1. Update [CHANGELOG.md](./CHANGELOG.md) + +1. Add the latest changes to [CHANGELOG.md](./CHANGELOG.md). +1. Rename the Unreleased section to reflect the new version number. + 1. Update the links to new version tag in the footer of CHANGELOG.md +1. Add today’s date (YYYY-MM-DD) after an em dash (—). +1. Submit a [GitHub Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) with these updates. + +## 2. Create a new release + +Once the PR is merged, tag the new version in Git and push the tag to GitHub. + +**Note:** the tag **must** start with the prefix `cm/` in order to correctly tag this module. + +For example, to tag version `cm/v0.2.0`: + +```console +git tag cm/v0.2.0 +git push origin cm/v0.2.0 +``` + +## 3. Update the root module + +Once the tag is pushed, you can update the root module to depend on the newly created version of package `cm` by running the following: + +```console +go get -u go.bytecodealliance.org/cm@latest +``` + +Then follow the instructions in [RELEASE.md](../RELEASE.md) to release a new version of the root module. diff --git a/src/internal/cm/case.go b/src/internal/cm/case.go new file mode 100644 index 0000000000..65ade4949a --- /dev/null +++ b/src/internal/cm/case.go @@ -0,0 +1,51 @@ +package cm + +import "errors" + +// CaseUnmarshaler returns an function that can unmarshal text into +// [variant] or [enum] case T. +// +// [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums +// [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants +func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, text []byte) error { + if len(cases) <= linearScanThreshold { + return func(v *T, text []byte) error { + if len(text) == 0 { + return errEmpty + } + s := string(text) + for i := 0; i < len(cases); i++ { + if cases[i] == s { + *v = T(i) + return nil + } + } + return errNoMatchingCase + } + } + + m := make(map[string]T, len(cases)) + for i, v := range cases { + m[v] = T(i) + } + + return func(v *T, text []byte) error { + if len(text) == 0 { + return errEmpty + } + s := string(text) + c, ok := m[s] + if !ok { + return errNoMatchingCase + } + *v = c + return nil + } +} + +const linearScanThreshold = 16 + +var ( + errEmpty = errors.New("empty text") + errNoMatchingCase = errors.New("no matching case") +) diff --git a/src/internal/cm/docs.go b/src/internal/cm/docs.go index 5fc48fb759..5522cf9424 100644 --- a/src/internal/cm/docs.go +++ b/src/internal/cm/docs.go @@ -1,4 +1,4 @@ -// Package cm contains types and functions for interfacing with the WebAssembly Component Model. +// Package cm provides types and functions for interfacing with the WebAssembly Component Model. // // The types in this package (such as [List], [Option], [Result], and [Variant]) are designed to match the memory layout // of [Component Model] types as specified in the [Canonical ABI]. diff --git a/src/internal/cm/empty.s b/src/internal/cm/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/cm/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/cm/error.go b/src/internal/cm/error.go new file mode 100644 index 0000000000..857d5ce7c1 --- /dev/null +++ b/src/internal/cm/error.go @@ -0,0 +1,40 @@ +package cm + +import "unsafe" + +// ErrorContext represents the Component Model [error-context] type, +// an immutable, non-deterministic, host-defined value meant to aid in debugging. +// +// [error-context]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-context-type +type ErrorContext struct { + _ HostLayout + errorContext +} + +type errorContext uint32 + +// Error implements the [error] interface. It returns the debug message associated with err. +func (err errorContext) Error() string { + return err.DebugMessage() +} + +// String implements [fmt.Stringer]. +func (err errorContext) String() string { + return err.DebugMessage() +} + +// DebugMessage represents the Canonical ABI [error-context.debug-message] function. +// +// [error-context.debug-message]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-contextdebug-message +func (err errorContext) DebugMessage() string { + var s string + wasmimport_errorContextDebugMessage(err, unsafe.Pointer(&s)) + return s +} + +// Drop represents the Canonical ABI [error-context.drop] function. +// +// [error-context.drop]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-contextdrop +func (err errorContext) Drop() { + wasmimport_errorContextDrop(err) +} diff --git a/src/internal/cm/error.wasm.go b/src/internal/cm/error.wasm.go new file mode 100644 index 0000000000..112eb6d815 --- /dev/null +++ b/src/internal/cm/error.wasm.go @@ -0,0 +1,13 @@ +package cm + +import "unsafe" + +// msg uses unsafe.Pointer for compatibility with go1.23 and lower. +// +//go:wasmimport canon error-context.debug-message +//go:noescape +func wasmimport_errorContextDebugMessage(err errorContext, msg unsafe.Pointer) + +//go:wasmimport canon error-context.drop +//go:noescape +func wasmimport_errorContextDrop(err errorContext) diff --git a/src/internal/cm/future.go b/src/internal/cm/future.go new file mode 100644 index 0000000000..e82183f655 --- /dev/null +++ b/src/internal/cm/future.go @@ -0,0 +1,15 @@ +package cm + +// Future represents the Component Model [future] type. +// A future is a special case of stream. In non-error cases, +// a future delivers exactly one value before being automatically closed. +// +// [future]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#asynchronous-value-types +type Future[T any] struct { + _ HostLayout + future[T] +} + +type future[T any] uint32 + +// TODO: implement methods on type future diff --git a/src/internal/cm/go.mod b/src/internal/cm/go.mod new file mode 100644 index 0000000000..f8e24af713 --- /dev/null +++ b/src/internal/cm/go.mod @@ -0,0 +1,3 @@ +module go.bytecodealliance.org/cm + +go 1.23.0 diff --git a/src/internal/cm/list.go b/src/internal/cm/list.go index 5c896d044b..0a171dbd37 100644 --- a/src/internal/cm/list.go +++ b/src/internal/cm/list.go @@ -1,6 +1,10 @@ package cm -import "unsafe" +import ( + "bytes" + "encoding/json" + "unsafe" +) // List represents a Component Model list. // The binary representation of list is similar to a Go slice minus the cap field. @@ -58,3 +62,57 @@ func (l list[T]) Data() *T { func (l list[T]) Len() uintptr { return l.len } + +// MarshalJSON implements json.Marshaler. +func (l list[T]) MarshalJSON() ([]byte, error) { + if l.len == 0 { + return []byte("[]"), nil + } + + s := l.Slice() + var zero T + if unsafe.Sizeof(zero) == 1 { + // The default Go json.Encoder will marshal []byte as base64. + // We override that behavior so all int types have the same serialization format. + // []uint8{1,2,3} -> [1,2,3] + // []uint32{1,2,3} -> [1,2,3] + return json.Marshal(sliceOf(s)) + } + return json.Marshal(s) +} + +type slice[T any] []entry[T] + +func sliceOf[S ~[]E, E any](s S) slice[E] { + return *(*slice[E])(unsafe.Pointer(&s)) +} + +type entry[T any] [1]T + +func (v entry[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(v[0]) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (l *list[T]) UnmarshalJSON(data []byte) error { + if bytes.Equal(data, nullLiteral) { + return nil + } + + var s []T + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + + l.data = unsafe.SliceData([]T(s)) + l.len = uintptr(len(s)) + + return nil +} + +// nullLiteral is the JSON representation of a null literal. +// By convention, to approximate the behavior of Unmarshal itself, +// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op. +// See https://pkg.go.dev/encoding/json#Unmarshaler for more information. +var nullLiteral = []byte("null") diff --git a/src/internal/cm/stream.go b/src/internal/cm/stream.go new file mode 100644 index 0000000000..80ef062e97 --- /dev/null +++ b/src/internal/cm/stream.go @@ -0,0 +1,15 @@ +package cm + +// Stream represents the Component Model [stream] type. +// A stream is a special case of stream. In non-error cases, +// a stream delivers exactly one value before being automatically closed. +// +// [stream]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#asynchronous-value-types +type Stream[T any] struct { + _ HostLayout + stream[T] +} + +type stream[T any] uint32 + +// TODO: implement methods on type stream diff --git a/src/internal/cm/variant.go b/src/internal/cm/variant.go index 24703641df..d0def34bb3 100644 --- a/src/internal/cm/variant.go +++ b/src/internal/cm/variant.go @@ -3,10 +3,9 @@ package cm import "unsafe" // Discriminant is the set of types that can represent the tag or discriminator of a variant. -// Use bool for 2-case variant types, result, or option types, uint8 where there are 256 or -// fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater. +// Use uint8 where there are 256 or fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater. type Discriminant interface { - bool | uint8 | uint16 | uint32 + uint8 | uint16 | uint32 } // Variant represents a loosely-typed Component Model variant. From 211425c3e9967b9a74b95ba8fb9f7ce4be38c4e7 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 15 Mar 2025 22:02:04 -0700 Subject: [PATCH 38/45] internal/wasi: regenerate WASI 0.2 bindings Use go.bytecodealliance.org/cmd/wit-bindgen-go@v0.6.0 --- .../wasi/cli/v0.2.0/command/command.wit | 2099 ----------------- .../wasi/cli/v0.2.0/command/command.wit.go | 0 .../wasi/cli/v0.2.0/environment/empty.s | 0 .../v0.2.0/environment/environment.wasm.go | 0 .../cli/v0.2.0/environment/environment.wit.go | 0 src/internal/wasi/cli/v0.2.0/exit/empty.s | 0 .../wasi/cli/v0.2.0/exit/exit.wasm.go | 0 src/internal/wasi/cli/v0.2.0/exit/exit.wit.go | 2 +- src/internal/wasi/cli/v0.2.0/run/empty.s | 0 .../wasi/cli/v0.2.0/run/run.exports.go | 0 src/internal/wasi/cli/v0.2.0/run/run.wasm.go | 2 +- src/internal/wasi/cli/v0.2.0/run/run.wit.go | 0 src/internal/wasi/cli/v0.2.0/stderr/empty.s | 0 .../wasi/cli/v0.2.0/stderr/stderr.wasm.go | 0 .../wasi/cli/v0.2.0/stderr/stderr.wit.go | 0 src/internal/wasi/cli/v0.2.0/stdin/empty.s | 0 .../wasi/cli/v0.2.0/stdin/stdin.wasm.go | 0 .../wasi/cli/v0.2.0/stdin/stdin.wit.go | 0 src/internal/wasi/cli/v0.2.0/stdout/empty.s | 0 .../wasi/cli/v0.2.0/stdout/stdout.wasm.go | 0 .../wasi/cli/v0.2.0/stdout/stdout.wit.go | 0 .../wasi/cli/v0.2.0/terminal-input/empty.s | 0 ...alinput.wasm.go => terminal-input.wasm.go} | 0 .../terminal-input/terminal-input.wit.go | 0 .../wasi/cli/v0.2.0/terminal-output/empty.s | 0 ...output.wasm.go => terminal-output.wasm.go} | 0 .../terminal-output/terminal-output.wit.go | 0 .../wasi/cli/v0.2.0/terminal-stderr/empty.s | 0 ...stderr.wasm.go => terminal-stderr.wasm.go} | 0 .../terminal-stderr/terminal-stderr.wit.go | 0 .../wasi/cli/v0.2.0/terminal-stdin/empty.s | 0 ...alstdin.wasm.go => terminal-stdin.wasm.go} | 0 .../terminal-stdin/terminal-stdin.wit.go | 0 .../wasi/cli/v0.2.0/terminal-stdout/empty.s | 0 ...stdout.wasm.go => terminal-stdout.wasm.go} | 0 .../terminal-stdout/terminal-stdout.wit.go | 0 .../clocks/v0.2.0/monotonic-clock/empty.s | 0 ...cclock.wasm.go => monotonic-clock.wasm.go} | 0 .../monotonic-clock/monotonic-clock.wit.go | 0 .../wasi/clocks/v0.2.0/wall-clock/empty.s | 0 .../{wallclock.wasm.go => wall-clock.wasm.go} | 0 .../v0.2.0/wall-clock/wall-clock.wit.go | 6 +- .../wasi/filesystem/v0.2.0/preopens/empty.s | 0 .../v0.2.0/preopens/preopens.wasm.go | 0 .../v0.2.0/preopens/preopens.wit.go | 0 .../wasi/filesystem/v0.2.0/types/abi.go | 2 +- .../wasi/filesystem/v0.2.0/types/empty.s | 0 .../filesystem/v0.2.0/types/types.wasm.go | 0 .../wasi/filesystem/v0.2.0/types/types.wit.go | 83 +- src/internal/wasi/io/v0.2.0/error/empty.s | 0 .../error/{ioerror.wasm.go => error.wasm.go} | 0 .../wasi/io/v0.2.0/error/error.wit.go | 0 src/internal/wasi/io/v0.2.0/poll/empty.s | 0 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go | 0 src/internal/wasi/io/v0.2.0/poll/poll.wit.go | 2 +- src/internal/wasi/io/v0.2.0/streams/abi.go | 12 - src/internal/wasi/io/v0.2.0/streams/empty.s | 0 .../wasi/io/v0.2.0/streams/streams.wasm.go | 0 .../wasi/io/v0.2.0/streams/streams.wit.go | 30 +- .../wasi/random/v0.2.0/insecure-seed/empty.s | 0 ...cureseed.wasm.go => insecure-seed.wasm.go} | 0 .../v0.2.0/insecure-seed/insecure-seed.wit.go | 0 .../wasi/random/v0.2.0/insecure/empty.s | 0 .../random/v0.2.0/insecure/insecure.wasm.go | 0 .../random/v0.2.0/insecure/insecure.wit.go | 0 .../wasi/random/v0.2.0/random/empty.s | 0 .../wasi/random/v0.2.0/random/random.wasm.go | 0 .../wasi/random/v0.2.0/random/random.wit.go | 0 .../sockets/v0.2.0/instance-network/empty.s | 0 ...twork.wasm.go => instance-network.wasm.go} | 0 .../instance-network/instance-network.wit.go | 0 .../wasi/sockets/v0.2.0/ip-name-lookup/abi.go | 0 .../sockets/v0.2.0/ip-name-lookup/empty.s | 0 ...elookup.wasm.go => ip-name-lookup.wasm.go} | 0 .../ip-name-lookup/ip-name-lookup.wit.go | 0 .../wasi/sockets/v0.2.0/network/abi.go | 0 .../wasi/sockets/v0.2.0/network/empty.s | 0 .../sockets/v0.2.0/network/network.wasm.go | 0 .../sockets/v0.2.0/network/network.wit.go | 58 +- .../sockets/v0.2.0/tcp-create-socket/empty.s | 0 ...cket.wasm.go => tcp-create-socket.wasm.go} | 0 .../tcp-create-socket.wit.go | 0 src/internal/wasi/sockets/v0.2.0/tcp/abi.go | 6 +- src/internal/wasi/sockets/v0.2.0/tcp/empty.s | 0 .../wasi/sockets/v0.2.0/tcp/tcp.wasm.go | 2 +- .../wasi/sockets/v0.2.0/tcp/tcp.wit.go | 23 +- .../sockets/v0.2.0/udp-create-socket/empty.s | 0 ...cket.wasm.go => udp-create-socket.wasm.go} | 0 .../udp-create-socket.wit.go | 0 src/internal/wasi/sockets/v0.2.0/udp/abi.go | 6 +- src/internal/wasi/sockets/v0.2.0/udp/empty.s | 0 .../wasi/sockets/v0.2.0/udp/udp.wasm.go | 0 .../wasi/sockets/v0.2.0/udp/udp.wit.go | 16 +- 93 files changed, 158 insertions(+), 2191 deletions(-) delete mode 100644 src/internal/wasi/cli/v0.2.0/command/command.wit mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/command/command.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/environment/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/environment/environment.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/exit/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/exit/exit.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/run/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/run/run.exports.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/run/run.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/run/run.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stderr/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdin/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdout/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-input/empty.s rename src/internal/wasi/cli/v0.2.0/terminal-input/{terminalinput.wasm.go => terminal-input.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-output/empty.s rename src/internal/wasi/cli/v0.2.0/terminal-output/{terminaloutput.wasm.go => terminal-output.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s rename src/internal/wasi/cli/v0.2.0/terminal-stderr/{terminalstderr.wasm.go => terminal-stderr.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s rename src/internal/wasi/cli/v0.2.0/terminal-stdin/{terminalstdin.wasm.go => terminal-stdin.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s rename src/internal/wasi/cli/v0.2.0/terminal-stdout/{terminalstdout.wasm.go => terminal-stdout.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go mode change 100644 => 100755 src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s rename src/internal/wasi/clocks/v0.2.0/monotonic-clock/{monotonicclock.wasm.go => monotonic-clock.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go mode change 100644 => 100755 src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s rename src/internal/wasi/clocks/v0.2.0/wall-clock/{wallclock.wasm.go => wall-clock.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/preopens/empty.s mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/types/abi.go mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/types/empty.s mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/types/types.wit.go mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/error/empty.s rename src/internal/wasi/io/v0.2.0/error/{ioerror.wasm.go => error.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/error/error.wit.go mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/poll/empty.s mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/poll/poll.wit.go delete mode 100644 src/internal/wasi/io/v0.2.0/streams/abi.go mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/streams/empty.s mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/streams/streams.wasm.go mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/streams/streams.wit.go mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/insecure-seed/empty.s rename src/internal/wasi/random/v0.2.0/insecure-seed/{insecureseed.wasm.go => insecure-seed.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/insecure/empty.s mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/random/empty.s mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/random/random.wasm.go mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/random/random.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/instance-network/empty.s rename src/internal/wasi/sockets/v0.2.0/instance-network/{instancenetwork.wasm.go => instance-network.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s rename src/internal/wasi/sockets/v0.2.0/ip-name-lookup/{ipnamelookup.wasm.go => ip-name-lookup.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/network/abi.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/network/empty.s mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/network/network.wasm.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/network/network.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s rename src/internal/wasi/sockets/v0.2.0/tcp-create-socket/{tcpcreatesocket.wasm.go => tcp-create-socket.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp/abi.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp/empty.s mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s rename src/internal/wasi/sockets/v0.2.0/udp-create-socket/{udpcreatesocket.wasm.go => udp-create-socket.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp/abi.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp/empty.s mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit b/src/internal/wasi/cli/v0.2.0/command/command.wit deleted file mode 100644 index 7b2af2ae9e..0000000000 --- a/src/internal/wasi/cli/v0.2.0/command/command.wit +++ /dev/null @@ -1,2099 +0,0 @@ -package wasi:cli@0.2.0; - -interface environment { - /// Get the POSIX-style environment variables. - /// - /// Each environment variable is provided as a pair of string variable names - /// and string value. - /// - /// Morally, these are a value import, but until value imports are available - /// in the component model, this import function should return the same - /// values each time it is called. - get-environment: func() -> list>; - - /// Get the POSIX-style arguments to the program. - get-arguments: func() -> list; - - /// Return a path that programs should use as their initial current working - /// directory, interpreting `.` as shorthand for this. - initial-cwd: func() -> option; -} - -interface exit { - /// Exit the current instance and any linked instances. - exit: func(status: result); -} - -interface run { - /// Run the program. - run: func() -> result; -} - -interface stdin { - use wasi:io/streams@0.2.0.{input-stream}; - get-stdin: func() -> input-stream; -} - -interface stdout { - use wasi:io/streams@0.2.0.{output-stream}; - get-stdout: func() -> output-stream; -} - -interface stderr { - use wasi:io/streams@0.2.0.{output-stream}; - get-stderr: func() -> output-stream; -} - -/// Terminal input. -/// -/// In the future, this may include functions for disabling echoing, -/// disabling input buffering so that keyboard events are sent through -/// immediately, querying supported features, and so on. -interface terminal-input { - /// The input side of a terminal. - resource terminal-input; -} - -/// Terminal output. -/// -/// In the future, this may include functions for querying the terminal -/// size, being notified of terminal size changes, querying supported -/// features, and so on. -interface terminal-output { - /// The output side of a terminal. - resource terminal-output; -} - -/// An interface providing an optional `terminal-input` for stdin as a -/// link-time authority. -interface terminal-stdin { - use terminal-input.{terminal-input}; - - /// If stdin is connected to a terminal, return a `terminal-input` handle - /// allowing further interaction with it. - get-terminal-stdin: func() -> option; -} - -/// An interface providing an optional `terminal-output` for stdout as a -/// link-time authority. -interface terminal-stdout { - use terminal-output.{terminal-output}; - - /// If stdout is connected to a terminal, return a `terminal-output` handle - /// allowing further interaction with it. - get-terminal-stdout: func() -> option; -} - -/// An interface providing an optional `terminal-output` for stderr as a -/// link-time authority. -interface terminal-stderr { - use terminal-output.{terminal-output}; - - /// If stderr is connected to a terminal, return a `terminal-output` handle - /// allowing further interaction with it. - get-terminal-stderr: func() -> option; -} - -world command { - import environment; - import exit; - import wasi:io/error@0.2.0; - import wasi:io/poll@0.2.0; - import wasi:io/streams@0.2.0; - import stdin; - import stdout; - import stderr; - import terminal-input; - import terminal-output; - import terminal-stdin; - import terminal-stdout; - import terminal-stderr; - import wasi:clocks/monotonic-clock@0.2.0; - import wasi:clocks/wall-clock@0.2.0; - import wasi:filesystem/types@0.2.0; - import wasi:filesystem/preopens@0.2.0; - import wasi:sockets/network@0.2.0; - import wasi:sockets/instance-network@0.2.0; - import wasi:sockets/udp@0.2.0; - import wasi:sockets/udp-create-socket@0.2.0; - import wasi:sockets/tcp@0.2.0; - import wasi:sockets/tcp-create-socket@0.2.0; - import wasi:sockets/ip-name-lookup@0.2.0; - import wasi:random/random@0.2.0; - import wasi:random/insecure@0.2.0; - import wasi:random/insecure-seed@0.2.0; - export run; -} - -package wasi:filesystem@0.2.0 { - /// WASI filesystem is a filesystem API primarily intended to let users run WASI - /// programs that access their files on their existing filesystems, without - /// significant overhead. - /// - /// It is intended to be roughly portable between Unix-family platforms and - /// Windows, though it does not hide many of the major differences. - /// - /// Paths are passed as interface-type `string`s, meaning they must consist of - /// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain - /// paths which are not accessible by this API. - /// - /// The directory separator in WASI is always the forward-slash (`/`). - /// - /// All paths in WASI are relative paths, and are interpreted relative to a - /// `descriptor` referring to a base directory. If a `path` argument to any WASI - /// function starts with `/`, or if any step of resolving a `path`, including - /// `..` and symbolic link steps, reaches a directory outside of the base - /// directory, or reaches a symlink to an absolute or rooted path in the - /// underlying filesystem, the function fails with `error-code::not-permitted`. - /// - /// For more information about WASI path resolution and sandboxing, see - /// [WASI filesystem path resolution]. - /// - /// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md - interface types { - use wasi:io/streams@0.2.0.{input-stream}; - use wasi:io/streams@0.2.0.{output-stream}; - use wasi:io/streams@0.2.0.{error}; - use wasi:clocks/wall-clock@0.2.0.{datetime}; - - /// File size or length of a region within a file. - type filesize = u64; - - /// The type of a filesystem object referenced by a descriptor. - /// - /// Note: This was called `filetype` in earlier versions of WASI. - enum descriptor-type { - /// The type of the descriptor or file is unknown or is different from - /// any of the other types specified. - unknown, - /// The descriptor refers to a block device inode. - block-device, - /// The descriptor refers to a character device inode. - character-device, - /// The descriptor refers to a directory inode. - directory, - /// The descriptor refers to a named pipe. - fifo, - /// The file refers to a symbolic link inode. - symbolic-link, - /// The descriptor refers to a regular file inode. - regular-file, - /// The descriptor refers to a socket. - socket - } - - /// Descriptor flags. - /// - /// Note: This was called `fdflags` in earlier versions of WASI. - flags descriptor-flags { - /// Read mode: Data can be read. - read, - /// Write mode: Data can be written to. - write, - /// Request that writes be performed according to synchronized I/O file - /// integrity completion. The data stored in the file and the file's - /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. - /// - /// The precise semantics of this operation have not yet been defined for - /// WASI. At this time, it should be interpreted as a request, and not a - /// requirement. - file-integrity-sync, - /// Request that writes be performed according to synchronized I/O data - /// integrity completion. Only the data stored in the file is - /// synchronized. This is similar to `O_DSYNC` in POSIX. - /// - /// The precise semantics of this operation have not yet been defined for - /// WASI. At this time, it should be interpreted as a request, and not a - /// requirement. - data-integrity-sync, - /// Requests that reads be performed at the same level of integrety - /// requested for writes. This is similar to `O_RSYNC` in POSIX. - /// - /// The precise semantics of this operation have not yet been defined for - /// WASI. At this time, it should be interpreted as a request, and not a - /// requirement. - requested-write-sync, - /// Mutating directories mode: Directory contents may be mutated. - /// - /// When this flag is unset on a descriptor, operations using the - /// descriptor which would create, rename, delete, modify the data or - /// metadata of filesystem objects, or obtain another handle which - /// would permit any of those, shall fail with `error-code::read-only` if - /// they would otherwise succeed. - /// - /// This may only be set on directories. - mutate-directory, - } - - /// Flags determining the method of how paths are resolved. - flags path-flags { - /// As long as the resolved path corresponds to a symbolic link, it is - /// expanded. - symlink-follow, - } - - /// Open flags used by `open-at`. - flags open-flags { - /// Create file if it does not exist, similar to `O_CREAT` in POSIX. - create, - /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. - directory, - /// Fail if file already exists, similar to `O_EXCL` in POSIX. - exclusive, - /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. - truncate, - } - - /// Number of hard links to an inode. - type link-count = u64; - - /// File attributes. - /// - /// Note: This was called `filestat` in earlier versions of WASI. - record descriptor-stat { - /// File type. - %type: descriptor-type, - /// Number of hard links to the file. - link-count: link-count, - /// For regular files, the file size in bytes. For symbolic links, the - /// length in bytes of the pathname contained in the symbolic link. - size: filesize, - /// Last data access timestamp. - /// - /// If the `option` is none, the platform doesn't maintain an access - /// timestamp for this file. - data-access-timestamp: option, - /// Last data modification timestamp. - /// - /// If the `option` is none, the platform doesn't maintain a - /// modification timestamp for this file. - data-modification-timestamp: option, - /// Last file status-change timestamp. - /// - /// If the `option` is none, the platform doesn't maintain a - /// status-change timestamp for this file. - status-change-timestamp: option, - } - - /// When setting a timestamp, this gives the value to set it to. - variant new-timestamp { - /// Leave the timestamp set to its previous value. - no-change, - /// Set the timestamp to the current time of the system clock associated - /// with the filesystem. - now, - /// Set the timestamp to the given value. - timestamp(datetime), - } - - /// A directory entry. - record directory-entry { - /// The type of the file referred to by this directory entry. - %type: descriptor-type, - /// The name of the object. - name: string, - } - - /// Error codes returned by functions, similar to `errno` in POSIX. - /// Not all of these error codes are returned by the functions provided by this - /// API; some are used in higher-level library layers, and others are provided - /// merely for alignment with POSIX. - enum error-code { - /// Permission denied, similar to `EACCES` in POSIX. - access, - /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` - /// in POSIX. - would-block, - /// Connection already in progress, similar to `EALREADY` in POSIX. - already, - /// Bad descriptor, similar to `EBADF` in POSIX. - bad-descriptor, - /// Device or resource busy, similar to `EBUSY` in POSIX. - busy, - /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. - deadlock, - /// Storage quota exceeded, similar to `EDQUOT` in POSIX. - quota, - /// File exists, similar to `EEXIST` in POSIX. - exist, - /// File too large, similar to `EFBIG` in POSIX. - file-too-large, - /// Illegal byte sequence, similar to `EILSEQ` in POSIX. - illegal-byte-sequence, - /// Operation in progress, similar to `EINPROGRESS` in POSIX. - in-progress, - /// Interrupted function, similar to `EINTR` in POSIX. - interrupted, - /// Invalid argument, similar to `EINVAL` in POSIX. - invalid, - /// I/O error, similar to `EIO` in POSIX. - io, - /// Is a directory, similar to `EISDIR` in POSIX. - is-directory, - /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. - loop, - /// Too many links, similar to `EMLINK` in POSIX. - too-many-links, - /// Message too large, similar to `EMSGSIZE` in POSIX. - message-size, - /// Filename too long, similar to `ENAMETOOLONG` in POSIX. - name-too-long, - /// No such device, similar to `ENODEV` in POSIX. - no-device, - /// No such file or directory, similar to `ENOENT` in POSIX. - no-entry, - /// No locks available, similar to `ENOLCK` in POSIX. - no-lock, - /// Not enough space, similar to `ENOMEM` in POSIX. - insufficient-memory, - /// No space left on device, similar to `ENOSPC` in POSIX. - insufficient-space, - /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. - not-directory, - /// Directory not empty, similar to `ENOTEMPTY` in POSIX. - not-empty, - /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. - not-recoverable, - /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. - unsupported, - /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. - no-tty, - /// No such device or address, similar to `ENXIO` in POSIX. - no-such-device, - /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. - overflow, - /// Operation not permitted, similar to `EPERM` in POSIX. - not-permitted, - /// Broken pipe, similar to `EPIPE` in POSIX. - pipe, - /// Read-only file system, similar to `EROFS` in POSIX. - read-only, - /// Invalid seek, similar to `ESPIPE` in POSIX. - invalid-seek, - /// Text file busy, similar to `ETXTBSY` in POSIX. - text-file-busy, - /// Cross-device link, similar to `EXDEV` in POSIX. - cross-device - } - - /// File or memory access pattern advisory information. - enum advice { - /// The application has no advice to give on its behavior with respect - /// to the specified data. - normal, - /// The application expects to access the specified data sequentially - /// from lower offsets to higher offsets. - sequential, - /// The application expects to access the specified data in a random - /// order. - random, - /// The application expects to access the specified data in the near - /// future. - will-need, - /// The application expects that it will not access the specified data - /// in the near future. - dont-need, - /// The application expects to access the specified data once and then - /// not reuse it thereafter. - no-reuse - } - - /// A 128-bit hash value, split into parts because wasm doesn't have a - /// 128-bit integer type. - record metadata-hash-value { - /// 64 bits of a 128-bit hash value. - lower: u64, - /// Another 64 bits of a 128-bit hash value. - upper: u64, - } - - /// A descriptor is a reference to a filesystem object, which may be a file, - /// directory, named pipe, special file, or other object on which filesystem - /// calls may be made. - resource descriptor { - - /// Provide file advisory information on a descriptor. - /// - /// This is similar to `posix_fadvise` in POSIX. - advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code>; - - /// Return a stream for appending to a file, if available. - /// - /// May fail with an error-code describing why the file cannot be appended. - /// - /// Note: This allows using `write-stream`, which is similar to `write` with - /// `O_APPEND` in in POSIX. - append-via-stream: func() -> result; - - /// Create a directory. - /// - /// Note: This is similar to `mkdirat` in POSIX. - create-directory-at: func(path: string) -> result<_, error-code>; - - /// Get flags associated with a descriptor. - /// - /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. - /// - /// Note: This returns the value that was the `fs_flags` value returned - /// from `fdstat_get` in earlier versions of WASI. - get-flags: func() -> result; - - /// Get the dynamic type of a descriptor. - /// - /// Note: This returns the same value as the `type` field of the `fd-stat` - /// returned by `stat`, `stat-at` and similar. - /// - /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided - /// by `fstat` in POSIX. - /// - /// Note: This returns the value that was the `fs_filetype` value returned - /// from `fdstat_get` in earlier versions of WASI. - get-type: func() -> result; - - /// Test whether two descriptors refer to the same filesystem object. - /// - /// In POSIX, this corresponds to testing whether the two descriptors have the - /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. - /// wasi-filesystem does not expose device and inode numbers, so this function - /// may be used instead. - is-same-object: func(other: borrow) -> bool; - - /// Create a hard link. - /// - /// Note: This is similar to `linkat` in POSIX. - link-at: func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; - - /// Return a hash of the metadata associated with a filesystem object referred - /// to by a descriptor. - /// - /// This returns a hash of the last-modification timestamp and file size, and - /// may also include the inode number, device number, birth timestamp, and - /// other metadata fields that may change when the file is modified or - /// replaced. It may also include a secret value chosen by the - /// implementation and not otherwise exposed. - /// - /// Implementations are encourated to provide the following properties: - /// - /// - If the file is not modified or replaced, the computed hash value should - /// usually not change. - /// - If the object is modified or replaced, the computed hash value should - /// usually change. - /// - The inputs to the hash should not be easily computable from the - /// computed hash. - /// - /// However, none of these is required. - metadata-hash: func() -> result; - - /// Return a hash of the metadata associated with a filesystem object referred - /// to by a directory descriptor and a relative path. - /// - /// This performs the same hash computation as `metadata-hash`. - metadata-hash-at: func(path-flags: path-flags, path: string) -> result; - - /// Open a file or directory. - /// - /// The returned descriptor is not guaranteed to be the lowest-numbered - /// descriptor not currently open/ it is randomized to prevent applications - /// from depending on making assumptions about indexes, since this is - /// error-prone in multi-threaded contexts. The returned descriptor is - /// guaranteed to be less than 2**31. - /// - /// If `flags` contains `descriptor-flags::mutate-directory`, and the base - /// descriptor doesn't have `descriptor-flags::mutate-directory` set, - /// `open-at` fails with `error-code::read-only`. - /// - /// If `flags` contains `write` or `mutate-directory`, or `open-flags` - /// contains `truncate` or `create`, and the base descriptor doesn't have - /// `descriptor-flags::mutate-directory` set, `open-at` fails with - /// `error-code::read-only`. - /// - /// Note: This is similar to `openat` in POSIX. - open-at: func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: descriptor-flags) -> result; - - /// Read from a descriptor, without using and updating the descriptor's offset. - /// - /// This function returns a list of bytes containing the data that was - /// read, along with a bool which, when true, indicates that the end of the - /// file was reached. The returned list will contain up to `length` bytes; it - /// may return fewer than requested, if the end of the file is reached or - /// if the I/O operation is interrupted. - /// - /// In the future, this may change to return a `stream`. - /// - /// Note: This is similar to `pread` in POSIX. - read: func(length: filesize, offset: filesize) -> result, bool>, error-code>; - - /// Read directory entries from a directory. - /// - /// On filesystems where directories contain entries referring to themselves - /// and their parents, often named `.` and `..` respectively, these entries - /// are omitted. - /// - /// This always returns a new stream which starts at the beginning of the - /// directory. Multiple streams may be active on the same directory, and they - /// do not interfere with each other. - read-directory: func() -> result; - - /// Return a stream for reading from a file, if available. - /// - /// May fail with an error-code describing why the file cannot be read. - /// - /// Multiple read, write, and append streams may be active on the same open - /// file and they do not interfere with each other. - /// - /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. - read-via-stream: func(offset: filesize) -> result; - - /// Read the contents of a symbolic link. - /// - /// If the contents contain an absolute or rooted path in the underlying - /// filesystem, this function fails with `error-code::not-permitted`. - /// - /// Note: This is similar to `readlinkat` in POSIX. - readlink-at: func(path: string) -> result; - - /// Remove a directory. - /// - /// Return `error-code::not-empty` if the directory is not empty. - /// - /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. - remove-directory-at: func(path: string) -> result<_, error-code>; - - /// Rename a filesystem object. - /// - /// Note: This is similar to `renameat` in POSIX. - rename-at: func(old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; - - /// Adjust the size of an open file. If this increases the file's size, the - /// extra bytes are filled with zeros. - /// - /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. - set-size: func(size: filesize) -> result<_, error-code>; - - /// Adjust the timestamps of an open file or directory. - /// - /// Note: This is similar to `futimens` in POSIX. - /// - /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. - set-times: func(data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; - - /// Adjust the timestamps of a file or directory. - /// - /// Note: This is similar to `utimensat` in POSIX. - /// - /// Note: This was called `path_filestat_set_times` in earlier versions of - /// WASI. - set-times-at: func(path-flags: path-flags, path: string, data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; - - /// Return the attributes of an open file or directory. - /// - /// Note: This is similar to `fstat` in POSIX, except that it does not return - /// device and inode information. For testing whether two descriptors refer to - /// the same underlying filesystem object, use `is-same-object`. To obtain - /// additional data that can be used do determine whether a file has been - /// modified, use `metadata-hash`. - /// - /// Note: This was called `fd_filestat_get` in earlier versions of WASI. - stat: func() -> result; - - /// Return the attributes of a file or directory. - /// - /// Note: This is similar to `fstatat` in POSIX, except that it does not - /// return device and inode information. See the `stat` description for a - /// discussion of alternatives. - /// - /// Note: This was called `path_filestat_get` in earlier versions of WASI. - stat-at: func(path-flags: path-flags, path: string) -> result; - - /// Create a symbolic link (also known as a "symlink"). - /// - /// If `old-path` starts with `/`, the function fails with - /// `error-code::not-permitted`. - /// - /// Note: This is similar to `symlinkat` in POSIX. - symlink-at: func(old-path: string, new-path: string) -> result<_, error-code>; - - /// Synchronize the data and metadata of a file to disk. - /// - /// This function succeeds with no effect if the file descriptor is not - /// opened for writing. - /// - /// Note: This is similar to `fsync` in POSIX. - sync: func() -> result<_, error-code>; - - /// Synchronize the data of a file to disk. - /// - /// This function succeeds with no effect if the file descriptor is not - /// opened for writing. - /// - /// Note: This is similar to `fdatasync` in POSIX. - sync-data: func() -> result<_, error-code>; - - /// Unlink a filesystem object that is not a directory. - /// - /// Return `error-code::is-directory` if the path refers to a directory. - /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. - unlink-file-at: func(path: string) -> result<_, error-code>; - - /// Write to a descriptor, without using and updating the descriptor's offset. - /// - /// It is valid to write past the end of a file; the file is extended to the - /// extent of the write, with bytes between the previous end and the start of - /// the write set to zero. - /// - /// In the future, this may change to take a `stream`. - /// - /// Note: This is similar to `pwrite` in POSIX. - write: func(buffer: list, offset: filesize) -> result; - - /// Return a stream for writing to a file, if available. - /// - /// May fail with an error-code describing why the file cannot be written. - /// - /// Note: This allows using `write-stream`, which is similar to `write` in - /// POSIX. - write-via-stream: func(offset: filesize) -> result; - } - - /// A stream of directory entries. - resource directory-entry-stream { - - /// Read a single directory entry from a `directory-entry-stream`. - read-directory-entry: func() -> result, error-code>; - } - - /// Attempts to extract a filesystem-related `error-code` from the stream - /// `error` provided. - /// - /// Stream operations which return `stream-error::last-operation-failed` - /// have a payload with more information about the operation that failed. - /// This payload can be passed through to this function to see if there's - /// filesystem-related information about the error to return. - /// - /// Note that this function is fallible because not all stream-related - /// errors are filesystem-related errors. - filesystem-error-code: func(err: borrow) -> option; - } - - interface preopens { - use types.{descriptor}; - - /// Return the set of preopened directories, and their path. - get-directories: func() -> list>; - } -} - -package wasi:sockets@0.2.0 { - interface network { - /// An opaque resource that represents access to (a subset of) the network. - /// This enables context-based security for networking. - /// There is no need for this to map 1:1 to a physical network interface. - resource network; - - /// Error codes. - /// - /// In theory, every API can return any error code. - /// In practice, API's typically only return the errors documented per API - /// combined with a couple of errors that are always possible: - /// - `unknown` - /// - `access-denied` - /// - `not-supported` - /// - `out-of-memory` - /// - `concurrency-conflict` - /// - /// See each individual API for what the POSIX equivalents are. They sometimes differ - /// per API. - enum error-code { - /// Unknown error - unknown, - /// Access denied. - /// - /// POSIX equivalent: EACCES, EPERM - access-denied, - /// The operation is not supported. - /// - /// POSIX equivalent: EOPNOTSUPP - not-supported, - /// One of the arguments is invalid. - /// - /// POSIX equivalent: EINVAL - invalid-argument, - /// Not enough memory to complete the operation. - /// - /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY - out-of-memory, - /// The operation timed out before it could finish completely. - timeout, - /// This operation is incompatible with another asynchronous operation that is already - /// in progress. - /// - /// POSIX equivalent: EALREADY - concurrency-conflict, - /// Trying to finish an asynchronous operation that: - /// - has not been started yet, or: - /// - was already finished by a previous `finish-*` call. - /// - /// Note: this is scheduled to be removed when `future`s are natively supported. - not-in-progress, - /// The operation has been aborted because it could not be completed immediately. - /// - /// Note: this is scheduled to be removed when `future`s are natively supported. - would-block, - /// The operation is not valid in the socket's current state. - invalid-state, - /// A new socket resource could not be created because of a system limit. - new-socket-limit, - /// A bind operation failed because the provided address is not an address that the - /// `network` can bind to. - address-not-bindable, - /// A bind operation failed because the provided address is already in use or because - /// there are no ephemeral ports available. - address-in-use, - /// The remote address is not reachable - remote-unreachable, - /// The TCP connection was forcefully rejected - connection-refused, - /// The TCP connection was reset. - connection-reset, - /// A TCP connection was aborted. - connection-aborted, - /// The size of a datagram sent to a UDP socket exceeded the maximum - /// supported size. - datagram-too-large, - /// Name does not exist or has no suitable associated IP addresses. - name-unresolvable, - /// A temporary failure in name resolution occurred. - temporary-resolver-failure, - /// A permanent failure in name resolution occurred. - permanent-resolver-failure - } - enum ip-address-family { - /// Similar to `AF_INET` in POSIX. - ipv4, - /// Similar to `AF_INET6` in POSIX. - ipv6 - } - type ipv4-address = tuple; - type ipv6-address = tuple; - variant ip-address { - ipv4(ipv4-address), - ipv6(ipv6-address), - } - record ipv4-socket-address { - /// sin_port - port: u16, - /// sin_addr - address: ipv4-address, - } - record ipv6-socket-address { - /// sin6_port - port: u16, - /// sin6_flowinfo - flow-info: u32, - /// sin6_addr - address: ipv6-address, - /// sin6_scope_id - scope-id: u32, - } - variant ip-socket-address { - ipv4(ipv4-socket-address), - ipv6(ipv6-socket-address), - } - } - - /// This interface provides a value-export of the default network handle.. - interface instance-network { - use network.{network}; - - /// Get a handle to the default network. - instance-network: func() -> network; - } - - interface ip-name-lookup { - use wasi:io/poll@0.2.0.{pollable}; - use network.{network}; - use network.{error-code}; - use network.{ip-address}; - resource resolve-address-stream { - - /// Returns the next address from the resolver. - /// - /// This function should be called multiple times. On each call, it will - /// return the next address in connection order preference. If all - /// addresses have been exhausted, this function returns `none`. - /// - /// This function never returns IPv4-mapped IPv6 addresses. - /// - /// # Typical errors - /// - `name-unresolvable`: Name does not exist or has no suitable associated - /// IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) - /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. - /// (EAI_AGAIN) - /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. - /// (EAI_FAIL) - /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) - resolve-next-address: func() -> result, error-code>; - - /// Create a `pollable` which will resolve once the stream is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - } - - /// Resolve an internet host name to a list of IP addresses. - /// - /// Unicode domain names are automatically converted to ASCII using IDNA encoding. - /// If the input is an IP address string, the address is parsed and returned - /// as-is without making any external requests. - /// - /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. - /// - /// This function never blocks. It either immediately fails or immediately - /// returns successfully with a `resolve-address-stream` that can be used - /// to (asynchronously) fetch the results. - /// - /// # Typical errors - /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. - /// - /// # References: - /// - - /// - - /// - - /// - - resolve-addresses: func(network: borrow, name: string) -> result; - } - - interface tcp { - use wasi:io/streams@0.2.0.{input-stream}; - use wasi:io/streams@0.2.0.{output-stream}; - use wasi:io/poll@0.2.0.{pollable}; - use wasi:clocks/monotonic-clock@0.2.0.{duration}; - use network.{network}; - use network.{error-code}; - use network.{ip-socket-address}; - use network.{ip-address-family}; - enum shutdown-type { - /// Similar to `SHUT_RD` in POSIX. - receive, - /// Similar to `SHUT_WR` in POSIX. - send, - /// Similar to `SHUT_RDWR` in POSIX. - both - } - - /// A TCP socket resource. - /// - /// The socket can be in one of the following states: - /// - `unbound` - /// - `bind-in-progress` - /// - `bound` (See note below) - /// - `listen-in-progress` - /// - `listening` - /// - `connect-in-progress` - /// - `connected` - /// - `closed` - /// See - /// for a more information. - /// - /// Note: Except where explicitly mentioned, whenever this documentation uses - /// the term "bound" without backticks it actually means: in the `bound` state *or - /// higher*. - /// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) - /// - /// In addition to the general error codes documented on the - /// `network::error-code` type, TCP socket methods may always return - /// `error(invalid-state)` when in the `closed` state. - resource tcp-socket { - - /// Accept a new client socket. - /// - /// The returned socket is bound and in the `connected` state. The following properties - /// are inherited from the listener socket: - /// - `address-family` - /// - `keep-alive-enabled` - /// - `keep-alive-idle-time` - /// - `keep-alive-interval` - /// - `keep-alive-count` - /// - `hop-limit` - /// - `receive-buffer-size` - /// - `send-buffer-size` - /// - /// On success, this function returns the newly accepted client socket along with - /// a pair of streams that can be used to read & write to the connection. - /// - /// # Typical errors - /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) - /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) - /// - `connection-aborted`: An incoming connection was pending, but was terminated - /// by the client before this listener could accept it. (ECONNABORTED) - /// - `new-socket-limit`: The new socket resource could not be created because of - /// a system limit. (EMFILE, ENFILE) - /// - /// # References - /// - - /// - - /// - - /// - - accept: func() -> result, error-code>; - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - address-family: func() -> ip-address-family; - finish-bind: func() -> result<_, error-code>; - finish-connect: func() -> result, error-code>; - finish-listen: func() -> result<_, error-code>; - - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - hop-limit: func() -> result; - - /// Whether the socket is in the `listening` state. - /// - /// Equivalent to the SO_ACCEPTCONN socket option. - is-listening: func() -> bool; - - /// The maximum amount of keepalive packets TCP should send before aborting the connection. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// I.e. after setting a value, reading the same setting back may return a different - /// value. - /// - /// Equivalent to the TCP_KEEPCNT socket option. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - keep-alive-count: func() -> result; - - /// Enables or disables keepalive. - /// - /// The keepalive behavior can be adjusted using: - /// - `keep-alive-idle-time` - /// - `keep-alive-interval` - /// - `keep-alive-count` - /// These properties can be configured while `keep-alive-enabled` is false, but only - /// come into effect when `keep-alive-enabled` is true. - /// - /// Equivalent to the SO_KEEPALIVE socket option. - keep-alive-enabled: func() -> result; - - /// Amount of time the connection has to be idle before TCP starts sending keepalive - /// packets. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// I.e. after setting a value, reading the same setting back may return a different - /// value. - /// - /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - keep-alive-idle-time: func() -> result; - - /// The time between keepalive packets. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// I.e. after setting a value, reading the same setting back may return a different - /// value. - /// - /// Equivalent to the TCP_KEEPINTVL socket option. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - keep-alive-interval: func() -> result; - - /// Get the bound local address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `local-address` to return `invalid-state` when the - /// socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - local-address: func() -> result; - - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// I.e. after setting a value, reading the same setting back may return a different - /// value. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - receive-buffer-size: func() -> result; - - /// Get the remote address. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - remote-address: func() -> result; - send-buffer-size: func() -> result; - set-hop-limit: func(value: u8) -> result<_, error-code>; - set-keep-alive-count: func(value: u32) -> result<_, error-code>; - set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; - set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; - set-keep-alive-interval: func(value: duration) -> result<_, error-code>; - - /// Hints the desired listen queue size. Implementations are free to ignore this. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// - /// # Typical errors - /// - `not-supported`: (set) The platform does not support changing the backlog - /// size after the initial listen. - /// - `invalid-argument`: (set) The provided value was 0. - /// - `invalid-state`: (set) The socket is in the `connect-in-progress` or - /// `connected` state. - set-listen-backlog-size: func(value: u64) -> result<_, error-code>; - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - - /// Initiate a graceful shutdown. - /// - /// - `receive`: The socket is not expecting to receive any data from - /// the peer. The `input-stream` associated with this socket will be - /// closed. Any data still in the receive queue at time of calling - /// this method will be discarded. - /// - `send`: The socket has no more data to send to the peer. The `output-stream` - /// associated with this socket will be closed and a FIN packet will be sent. - /// - `both`: Same effect as `receive` & `send` combined. - /// - /// This function is idempotent. Shutting a down a direction more than once - /// has no effect and returns `ok`. - /// - /// The shutdown function does not close (drop) the socket. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; - - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the - /// implementation to decide which - /// network interface(s) to bind to. - /// If the TCP/UDP port is zero, the socket will be bound to a random free port. - /// - /// Bind can be attempted multiple times on the same socket, even with - /// different arguments on each iteration. But never concurrently and - /// only as long as the previous bind failed. Once a bind succeeds, the - /// binding can't be changed anymore. - /// - /// # Typical errors - /// - `invalid-argument`: The `local-address` has the wrong address family. - /// (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) - /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. - /// (EINVAL) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS - /// on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` - /// can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. - /// (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// When binding to a non-zero port, this bind operation shouldn't be affected by - /// the TIME_WAIT - /// state of a recently closed socket on the same local address. In practice this - /// means that the SO_REUSEADDR - /// socket option should be set implicitly on all platforms, except on Windows where - /// this is the default behavior - /// and SO_REUSEADDR performs something different entirely. - /// - /// Unlike in POSIX, in WASI the bind operation is async. This enables - /// interactive WASI hosts to inject permission prompts. Runtimes that - /// don't want to make use of this ability can simply call the native - /// `bind` as part of either `start-bind` or `finish-bind`. - /// - /// # References - /// - - /// - - /// - - /// - - start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; - - /// Connect to a remote endpoint. - /// - /// On success: - /// - the socket is transitioned into the `connection` state. - /// - a pair of streams is returned that can be used to read & write to the connection - /// - /// After a failed connection attempt, the socket will be in the `closed` - /// state and the only valid action left is to `drop` the socket. A single - /// socket can not be used to connect more than once. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. - /// (EAFNOSUPPORT) - /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, - /// ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) - /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. - /// (EINVAL, EADDRNOTAVAIL on Illumos) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY - /// (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL - /// on Windows) - /// - `invalid-argument`: The socket is already attached to a different network. - /// The `network` passed to `connect` must be identical to the one passed to `bind`. - /// - `invalid-state`: The socket is already in the `connected` state. - /// (EISCONN) - /// - `invalid-state`: The socket is already in the `listening` state. - /// (EOPNOTSUPP, EINVAL on Windows) - /// - `timeout`: Connection timed out. (ETIMEDOUT) - /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, - /// EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `address-in-use`: Tried to perform an implicit bind, but there were - /// no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `not-in-progress`: A connect operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. - /// (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// The POSIX equivalent of `start-connect` is the regular `connect` syscall. - /// Because all WASI sockets are non-blocking this is expected to return - /// EINPROGRESS, which should be translated to `ok()` in WASI. - /// - /// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` - /// with a timeout of 0 on the socket descriptor. Followed by a check for - /// the `SO_ERROR` socket option, in case the poll signaled readiness. - /// - /// # References - /// - - /// - - /// - - /// - - start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; - - /// Start listening for new connections. - /// - /// Transitions the socket into the `listening` state. - /// - /// Unlike POSIX, the socket must already be explicitly bound. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) - /// - `invalid-state`: The socket is already in the `connected` state. - /// (EISCONN, EINVAL on BSD) - /// - `invalid-state`: The socket is already in the `listening` state. - /// - `address-in-use`: Tried to perform an implicit bind, but there were - /// no ephemeral ports available. (EADDRINUSE) - /// - `not-in-progress`: A listen operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. - /// (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// Unlike in POSIX, in WASI the listen operation is async. This enables - /// interactive WASI hosts to inject permission prompts. Runtimes that - /// don't want to make use of this ability can simply call the native - /// `listen` as part of either `start-listen` or `finish-listen`. - /// - /// # References - /// - - /// - - /// - - /// - - start-listen: func() -> result<_, error-code>; - - /// Create a `pollable` which can be used to poll for, or block on, - /// completion of any of the asynchronous operations of this socket. - /// - /// When `finish-bind`, `finish-listen`, `finish-connect` or `accept` - /// return `error(would-block)`, this pollable can be used to wait for - /// their success or failure, after which the method can be retried. - /// - /// The pollable is not limited to the async operation that happens to be - /// in progress at the time of calling `subscribe` (if any). Theoretically, - /// `subscribe` only has to be called once per socket and can then be - /// (re)used for the remainder of the socket's lifetime. - /// - /// See - /// for a more information. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - } - } - - interface tcp-create-socket { - use network.{network}; - use network.{error-code}; - use network.{ip-address-family}; - use tcp.{tcp-socket}; - - /// Create a new TCP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. - /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. - /// - /// This function does not require a network capability handle. This is considered - /// to be safe because - /// at time of creation, the socket is not bound to any `network` yet. Up to the moment - /// `bind`/`connect` - /// is called, the socket is effectively an in-memory configuration object, unable - /// to communicate with the outside world. - /// - /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous - /// operations. - /// - /// # Typical errors - /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) - /// - `new-socket-limit`: The new socket resource could not be created because of - /// a system limit. (EMFILE, ENFILE) - /// - /// # References - /// - - /// - - /// - - /// - - create-tcp-socket: func(address-family: ip-address-family) -> result; - } - - interface udp { - use wasi:io/poll@0.2.0.{pollable}; - use network.{network}; - use network.{error-code}; - use network.{ip-socket-address}; - use network.{ip-address-family}; - - /// A received datagram. - record incoming-datagram { - /// The payload. - /// - /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. - data: list, - /// The source address. - /// - /// This field is guaranteed to match the remote address the stream was initialized - /// with, if any. - /// - /// Equivalent to the `src_addr` out parameter of `recvfrom`. - remote-address: ip-socket-address, - } - - /// A datagram to be sent out. - record outgoing-datagram { - /// The payload. - data: list, - /// The destination address. - /// - /// The requirements on this field depend on how the stream was initialized: - /// - with a remote address: this field must be None or match the stream's remote - /// address exactly. - /// - without a remote address: this field is required. - /// - /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise - /// it is equivalent to `sendto`. - remote-address: option, - } - - /// A UDP socket handle. - resource udp-socket { - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - address-family: func() -> ip-address-family; - finish-bind: func() -> result<_, error-code>; - - /// Get the current bound address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `local-address` to return `invalid-state` when the - /// socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - local-address: func() -> result; - - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// I.e. after setting a value, reading the same setting back may return a different - /// value. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - receive-buffer-size: func() -> result; - - /// Get the address the socket is currently streaming to. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - remote-address: func() -> result; - send-buffer-size: func() -> result; - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; - - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the - /// implementation to decide which - /// network interface(s) to bind to. - /// If the port is zero, the socket will be bound to a random free port. - /// - /// # Typical errors - /// - `invalid-argument`: The `local-address` has the wrong address family. - /// (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS - /// on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` - /// can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. - /// (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// Unlike in POSIX, in WASI the bind operation is async. This enables - /// interactive WASI hosts to inject permission prompts. Runtimes that - /// don't want to make use of this ability can simply call the native - /// `bind` as part of either `start-bind` or `finish-bind`. - /// - /// # References - /// - - /// - - /// - - /// - - start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; - - /// Set up inbound & outbound communication channels, optionally to a specific peer. - /// - /// This function only changes the local socket configuration and does not generate - /// any network traffic. - /// On success, the `remote-address` of the socket is updated. The `local-address` - /// may be updated as well, - /// based on the best network path to `remote-address`. - /// - /// When a `remote-address` is provided, the returned streams are limited to communicating - /// with that specific peer: - /// - `send` can only be used to send to this destination. - /// - `receive` will only return datagrams sent from the provided `remote-address`. - /// - /// This method may be called multiple times on the same socket to change its association, - /// but - /// only the most recently returned pair of streams will be operational. Implementations - /// may trap if - /// the streams returned by a previous invocation haven't been dropped yet before - /// calling `stream` again. - /// - /// The POSIX equivalent in pseudo-code is: - /// ```text - /// if (was previously connected) { - /// connect(s, AF_UNSPEC) - /// } - /// if (remote_address is Some) { - /// connect(s, remote_address) - /// } - /// ``` - /// - /// Unlike in POSIX, the socket must already be explicitly bound. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. - /// (EAFNOSUPPORT) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY - /// (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, - /// EADDRNOTAVAIL) - /// - `invalid-state`: The socket is not bound. - /// - `address-in-use`: Tried to perform an implicit bind, but there were - /// no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, - /// ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - /// # References - /// - - /// - - /// - - /// - - %stream: func(remote-address: option) -> result, error-code>; - - /// Create a `pollable` which will resolve once the socket is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - unicast-hop-limit: func() -> result; - } - resource incoming-datagram-stream { - - /// Receive messages on the socket. - /// - /// This function attempts to receive up to `max-results` datagrams on the socket - /// without blocking. - /// The returned list may contain fewer elements than requested, but never more. - /// - /// This function returns successfully with an empty list when either: - /// - `max-results` is 0, or: - /// - `max-results` is greater than 0, but no results are immediately available. - /// This function never returns `error(would-block)`. - /// - /// # Typical errors - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET - /// on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - receive: func(max-results: u64) -> result, error-code>; - - /// Create a `pollable` which will resolve once the stream is ready to receive again. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - } - resource outgoing-datagram-stream { - - /// Check readiness for sending. This function never blocks. - /// - /// Returns the number of datagrams permitted for the next call to `send`, - /// or an error. Calling `send` with more datagrams than this function has - /// permitted will trap. - /// - /// When this function returns ok(0), the `subscribe` pollable will - /// become ready when this function will report at least ok(1), or an - /// error. - /// - /// Never returns `would-block`. - check-send: func() -> result; - - /// Send messages on the socket. - /// - /// This function attempts to send all provided `datagrams` on the socket without - /// blocking and - /// returns how many messages were actually sent (or queued for sending). This function - /// never - /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` - /// is returned. - /// - /// This function semantically behaves the same as iterating the `datagrams` list - /// and sequentially - /// sending each individual datagram until either the end of the list has been reached - /// or the first error occurred. - /// If at least one datagram has been sent successfully, this function never returns - /// an error. - /// - /// If the input list is empty, the function returns `ok(0)`. - /// - /// Each call to `send` must be permitted by a preceding `check-send`. Implementations - /// must trap if - /// either `check-send` was not called or `datagrams` contains more items than `check-send` - /// permitted. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. - /// (EAFNOSUPPORT) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY - /// (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, - /// EADDRNOTAVAIL) - /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` - /// is `some` value that does not match the address passed to `stream`. (EISCONN) - /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` - /// was provided. (EDESTADDRREQ) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, - /// ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - send: func(datagrams: list) -> result; - - /// Create a `pollable` which will resolve once the stream is ready to send again. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - } - } - - interface udp-create-socket { - use network.{network}; - use network.{error-code}; - use network.{ip-address-family}; - use udp.{udp-socket}; - - /// Create a new UDP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. - /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. - /// - /// This function does not require a network capability handle. This is considered - /// to be safe because - /// at time of creation, the socket is not bound to any `network` yet. Up to the moment - /// `bind` is called, - /// the socket is effectively an in-memory configuration object, unable to communicate - /// with the outside world. - /// - /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous - /// operations. - /// - /// # Typical errors - /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) - /// - `new-socket-limit`: The new socket resource could not be created because of - /// a system limit. (EMFILE, ENFILE) - /// - /// # References: - /// - - /// - - /// - - /// - - create-udp-socket: func(address-family: ip-address-family) -> result; - } -} - -package wasi:clocks@0.2.0 { - /// WASI Monotonic Clock is a clock API intended to let users measure elapsed - /// time. - /// - /// It is intended to be portable at least between Unix-family platforms and - /// Windows. - /// - /// A monotonic clock is a clock which has an unspecified initial value, and - /// successive reads of the clock will produce non-decreasing values. - /// - /// It is intended for measuring elapsed time. - interface monotonic-clock { - use wasi:io/poll@0.2.0.{pollable}; - - /// An instant in time, in nanoseconds. An instant is relative to an - /// unspecified initial value, and can only be compared to instances from - /// the same monotonic-clock. - type instant = u64; - - /// A duration of time, in nanoseconds. - type duration = u64; - - /// Read the current value of the clock. - /// - /// The clock is monotonic, therefore calling this function repeatedly will - /// produce a sequence of non-decreasing values. - now: func() -> instant; - - /// Query the resolution of the clock. Returns the duration of time - /// corresponding to a clock tick. - resolution: func() -> duration; - - /// Create a `pollable` which will resolve once the specified instant - /// occured. - subscribe-instant: func(when: instant) -> pollable; - - /// Create a `pollable` which will resolve once the given duration has - /// elapsed, starting at the time at which this function was called. - /// occured. - subscribe-duration: func(when: duration) -> pollable; - } - - /// WASI Wall Clock is a clock API intended to let users query the current - /// time. The name "wall" makes an analogy to a "clock on the wall", which - /// is not necessarily monotonic as it may be reset. - /// - /// It is intended to be portable at least between Unix-family platforms and - /// Windows. - /// - /// A wall clock is a clock which measures the date and time according to - /// some external reference. - /// - /// External references may be reset, so this clock is not necessarily - /// monotonic, making it unsuitable for measuring elapsed time. - /// - /// It is intended for reporting the current date and time for humans. - interface wall-clock { - /// A time and date in seconds plus nanoseconds. - record datetime { - seconds: u64, - nanoseconds: u32, - } - - /// Read the current value of the clock. - /// - /// This clock is not monotonic, therefore calling this function repeatedly - /// will not necessarily produce a sequence of non-decreasing values. - /// - /// The returned timestamps represent the number of seconds since - /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], - /// also known as [Unix Time]. - /// - /// The nanoseconds field of the output is always less than 1000000000. - /// - /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 - /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time - now: func() -> datetime; - - /// Query the resolution of the clock. - /// - /// The nanoseconds field of the output is always less than 1000000000. - resolution: func() -> datetime; - } -} - -package wasi:io@0.2.0 { - interface error { - /// A resource which represents some error information. - /// - /// The only method provided by this resource is `to-debug-string`, - /// which provides some human-readable information about the error. - /// - /// In the `wasi:io` package, this resource is returned through the - /// `wasi:io/streams/stream-error` type. - /// - /// To provide more specific error information, other interfaces may - /// provide functions to further "downcast" this error into more specific - /// error information. For example, `error`s returned in streams derived - /// from filesystem types to be described using the filesystem's own - /// error-code type, using the function - /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter - /// `borrow` and returns - /// `option`. - /// - /// The set of functions which can "downcast" an `error` into a more - /// concrete type is open. - resource error { - - /// Returns a string that is suitable to assist humans in debugging - /// this error. - /// - /// WARNING: The returned string should not be consumed mechanically! - /// It may change across platforms, hosts, or other implementation - /// details. Parsing this string is a major platform-compatibility - /// hazard. - to-debug-string: func() -> string; - } - } - - /// A poll API intended to let users wait for I/O events on multiple handles - /// at once. - interface poll { - /// `pollable` represents a single I/O event which may be ready, or not. - resource pollable { - - /// `block` returns immediately if the pollable is ready, and otherwise - /// blocks until ready. - /// - /// This function is equivalent to calling `poll.poll` on a list - /// containing only this pollable. - block: func(); - - /// Return the readiness of a pollable. This function never blocks. - /// - /// Returns `true` when the pollable is ready, and `false` otherwise. - ready: func() -> bool; - } - - /// Poll for completion on a set of pollables. - /// - /// This function takes a list of pollables, which identify I/O sources of - /// interest, and waits until one or more of the events is ready for I/O. - /// - /// The result `list` contains one or more indices of handles in the - /// argument list that is ready for I/O. - /// - /// If the list contains more elements than can be indexed with a `u32` - /// value, this function traps. - /// - /// A timeout can be implemented by adding a pollable from the - /// wasi-clocks API to the list. - /// - /// This function does not return a `result`; polling in itself does not - /// do any I/O so it doesn't fail. If any of the I/O sources identified by - /// the pollables has an error, it is indicated by marking the source as - /// being reaedy for I/O. - poll: func(in: list>) -> list; - } - - /// WASI I/O is an I/O abstraction API which is currently focused on providing - /// stream types. - /// - /// In the future, the component model is expected to add built-in stream types; - /// when it does, they are expected to subsume this API. - interface streams { - use error.{error}; - use poll.{pollable}; - - /// An error for input-stream and output-stream operations. - variant stream-error { - /// The last operation (a write or flush) failed before completion. - /// - /// More information is available in the `error` payload. - last-operation-failed(error), - /// The stream is closed: no more input will be accepted by the - /// stream. A closed output-stream will return this error on all - /// future operations. - closed, - } - - /// An input bytestream. - /// - /// `input-stream`s are *non-blocking* to the extent practical on underlying - /// platforms. I/O operations always return promptly; if fewer bytes are - /// promptly available than requested, they return the number of bytes promptly - /// available, which could even be zero. To wait for data to be available, - /// use the `subscribe` function to obtain a `pollable` which can be polled - /// for using `wasi:io/poll`. - resource input-stream { - - /// Read bytes from a stream, after blocking until at least one byte can - /// be read. Except for blocking, behavior is identical to `read`. - blocking-read: func(len: u64) -> result, stream-error>; - - /// Skip bytes from a stream, after blocking until at least one byte - /// can be skipped. Except for blocking behavior, identical to `skip`. - blocking-skip: func(len: u64) -> result; - - /// Perform a non-blocking read from the stream. - /// - /// When the source of a `read` is binary data, the bytes from the source - /// are returned verbatim. When the source of a `read` is known to the - /// implementation to be text, bytes containing the UTF-8 encoding of the - /// text are returned. - /// - /// This function returns a list of bytes containing the read data, - /// when successful. The returned list will contain up to `len` bytes; - /// it may return fewer than requested, but not more. The list is - /// empty when no bytes are available for reading at this time. The - /// pollable given by `subscribe` will be ready when more bytes are - /// available. - /// - /// This function fails with a `stream-error` when the operation - /// encounters an error, giving `last-operation-failed`, or when the - /// stream is closed, giving `closed`. - /// - /// When the caller gives a `len` of 0, it represents a request to - /// read 0 bytes. If the stream is still open, this call should - /// succeed and return an empty list, or otherwise fail with `closed`. - /// - /// The `len` parameter is a `u64`, which could represent a list of u8 which - /// is not possible to allocate in wasm32, or not desirable to allocate as - /// as a return value by the callee. The callee may return a list of bytes - /// less than `len` in size while more bytes are available for reading. - read: func(len: u64) -> result, stream-error>; - - /// Skip bytes from a stream. Returns number of bytes skipped. - /// - /// Behaves identical to `read`, except instead of returning a list - /// of bytes, returns the number of bytes consumed from the stream. - skip: func(len: u64) -> result; - - /// Create a `pollable` which will resolve once either the specified stream - /// has bytes available to read or the other end of the stream has been - /// closed. - /// The created `pollable` is a child resource of the `input-stream`. - /// Implementations may trap if the `input-stream` is dropped before - /// all derived `pollable`s created with this function are dropped. - subscribe: func() -> pollable; - } - - /// An output bytestream. - /// - /// `output-stream`s are *non-blocking* to the extent practical on - /// underlying platforms. Except where specified otherwise, I/O operations also - /// always return promptly, after the number of bytes that can be written - /// promptly, which could even be zero. To wait for the stream to be ready to - /// accept data, the `subscribe` function to obtain a `pollable` which can be - /// polled for using `wasi:io/poll`. - resource output-stream { - - /// Request to flush buffered output, and block until flush completes - /// and stream is ready for writing again. - blocking-flush: func() -> result<_, stream-error>; - - /// Read from one stream and write to another, with blocking. - /// - /// This is similar to `splice`, except that it blocks until the - /// `output-stream` is ready for writing, and the `input-stream` - /// is ready for reading, before performing the `splice`. - blocking-splice: func(src: borrow, len: u64) -> result; - - /// Perform a write of up to 4096 bytes, and then flush the stream. Block - /// until all of these operations are complete, or an error occurs. - /// - /// This is a convenience wrapper around the use of `check-write`, - /// `subscribe`, `write`, and `flush`, and is implemented with the - /// following pseudo-code: - /// - /// ```text - /// let pollable = this.subscribe(); - /// while !contents.is_empty() { - /// // Wait for the stream to become writable - /// pollable.block(); - /// let Ok(n) = this.check-write(); // eliding error handling - /// let len = min(n, contents.len()); - /// let (chunk, rest) = contents.split_at(len); - /// this.write(chunk ); // eliding error handling - /// contents = rest; - /// } - /// this.flush(); - /// // Wait for completion of `flush` - /// pollable.block(); - /// // Check for any errors that arose during `flush` - /// let _ = this.check-write(); // eliding error handling - /// ``` - blocking-write-and-flush: func(contents: list) -> result<_, stream-error>; - - /// Perform a write of up to 4096 zeroes, and then flush the stream. - /// Block until all of these operations are complete, or an error - /// occurs. - /// - /// This is a convenience wrapper around the use of `check-write`, - /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with - /// the following pseudo-code: - /// - /// ```text - /// let pollable = this.subscribe(); - /// while num_zeroes != 0 { - /// // Wait for the stream to become writable - /// pollable.block(); - /// let Ok(n) = this.check-write(); // eliding error handling - /// let len = min(n, num_zeroes); - /// this.write-zeroes(len); // eliding error handling - /// num_zeroes -= len; - /// } - /// this.flush(); - /// // Wait for completion of `flush` - /// pollable.block(); - /// // Check for any errors that arose during `flush` - /// let _ = this.check-write(); // eliding error handling - /// ``` - blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error>; - - /// Check readiness for writing. This function never blocks. - /// - /// Returns the number of bytes permitted for the next call to `write`, - /// or an error. Calling `write` with more bytes than this function has - /// permitted will trap. - /// - /// When this function returns 0 bytes, the `subscribe` pollable will - /// become ready when this function will report at least 1 byte, or an - /// error. - check-write: func() -> result; - - /// Request to flush buffered output. This function never blocks. - /// - /// This tells the output-stream that the caller intends any buffered - /// output to be flushed. the output which is expected to be flushed - /// is all that has been passed to `write` prior to this call. - /// - /// Upon calling this function, the `output-stream` will not accept any - /// writes (`check-write` will return `ok(0)`) until the flush has - /// completed. The `subscribe` pollable will become ready when the - /// flush has completed and the stream can accept more writes. - flush: func() -> result<_, stream-error>; - - /// Read from one stream and write to another. - /// - /// The behavior of splice is equivelant to: - /// 1. calling `check-write` on the `output-stream` - /// 2. calling `read` on the `input-stream` with the smaller of the - /// `check-write` permitted length and the `len` provided to `splice` - /// 3. calling `write` on the `output-stream` with that read data. - /// - /// Any error reported by the call to `check-write`, `read`, or - /// `write` ends the splice and reports that error. - /// - /// This function returns the number of bytes transferred; it may be less - /// than `len`. - splice: func(src: borrow, len: u64) -> result; - - /// Create a `pollable` which will resolve once the output-stream - /// is ready for more writing, or an error has occured. When this - /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an - /// error. - /// - /// If the stream is closed, this pollable is always ready immediately. - /// - /// The created `pollable` is a child resource of the `output-stream`. - /// Implementations may trap if the `output-stream` is dropped before - /// all derived `pollable`s created with this function are dropped. - subscribe: func() -> pollable; - - /// Perform a write. This function never blocks. - /// - /// When the destination of a `write` is binary data, the bytes from - /// `contents` are written verbatim. When the destination of a `write` is - /// known to the implementation to be text, the bytes of `contents` are - /// transcoded from UTF-8 into the encoding of the destination and then - /// written. - /// - /// Precondition: check-write gave permit of Ok(n) and contents has a - /// length of less than or equal to n. Otherwise, this function will trap. - /// - /// returns Err(closed) without writing if the stream has closed since - /// the last call to check-write provided a permit. - write: func(contents: list) -> result<_, stream-error>; - - /// Write zeroes to a stream. - /// - /// This should be used precisely like `write` with the exact same - /// preconditions (must use check-write first), but instead of - /// passing a list of bytes, you simply pass the number of zero-bytes - /// that should be written. - write-zeroes: func(len: u64) -> result<_, stream-error>; - } - } -} - -package wasi:random@0.2.0 { - /// The insecure-seed interface for seeding hash-map DoS resistance. - /// - /// It is intended to be portable at least between Unix-family platforms and - /// Windows. - interface insecure-seed { - /// Return a 128-bit value that may contain a pseudo-random value. - /// - /// The returned value is not required to be computed from a CSPRNG, and may - /// even be entirely deterministic. Host implementations are encouraged to - /// provide pseudo-random values to any program exposed to - /// attacker-controlled content, to enable DoS protection built into many - /// languages' hash-map implementations. - /// - /// This function is intended to only be called once, by a source language - /// to initialize Denial Of Service (DoS) protection in its hash-map - /// implementation. - /// - /// # Expected future evolution - /// - /// This will likely be changed to a value import, to prevent it from being - /// called multiple times and potentially used for purposes other than DoS - /// protection. - insecure-seed: func() -> tuple; - } - - /// The insecure interface for insecure pseudo-random numbers. - /// - /// It is intended to be portable at least between Unix-family platforms and - /// Windows. - interface insecure { - /// Return `len` insecure pseudo-random bytes. - /// - /// This function is not cryptographically secure. Do not use it for - /// anything related to security. - /// - /// There are no requirements on the values of the returned bytes, however - /// implementations are encouraged to return evenly distributed values with - /// a long period. - get-insecure-random-bytes: func(len: u64) -> list; - - /// Return an insecure pseudo-random `u64` value. - /// - /// This function returns the same type of pseudo-random data as - /// `get-insecure-random-bytes`, represented as a `u64`. - get-insecure-random-u64: func() -> u64; - } - - /// WASI Random is a random data API. - /// - /// It is intended to be portable at least between Unix-family platforms and - /// Windows. - interface random { - /// Return `len` cryptographically-secure random or pseudo-random bytes. - /// - /// This function must produce data at least as cryptographically secure and - /// fast as an adequately seeded cryptographically-secure pseudo-random - /// number generator (CSPRNG). It must not block, from the perspective of - /// the calling program, under any circumstances, including on the first - /// request and on requests for numbers of bytes. The returned data must - /// always be unpredictable. - /// - /// This function must always return fresh data. Deterministic environments - /// must omit this function, rather than implementing it with deterministic - /// data. - get-random-bytes: func(len: u64) -> list; - - /// Return a cryptographically-secure random or pseudo-random `u64` value. - /// - /// This function returns the same type of data as `get-random-bytes`, - /// represented as a `u64`. - get-random-u64: func() -> u64; - } -} diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit.go b/src/internal/wasi/cli/v0.2.0/command/command.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/environment/empty.s b/src/internal/wasi/cli/v0.2.0/environment/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/exit/empty.s b/src/internal/wasi/cli/v0.2.0/exit/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go old mode 100644 new mode 100755 index f3f172e68b..caeeb269db --- a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go +++ b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go @@ -15,7 +15,7 @@ import ( // //go:nosplit func Exit(status cm.BoolResult) { - status0 := cm.BoolToU32(status) + status0 := (uint32)(cm.BoolToU32(status)) wasmimport_Exit((uint32)(status0)) return } diff --git a/src/internal/wasi/cli/v0.2.0/run/empty.s b/src/internal/wasi/cli/v0.2.0/run/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.exports.go b/src/internal/wasi/cli/v0.2.0/run/run.exports.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wasm.go b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go old mode 100644 new mode 100755 index d7e20574e7..12a14e763e --- a/src/internal/wasi/cli/v0.2.0/run/run.wasm.go +++ b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go @@ -12,6 +12,6 @@ import ( //export wasi:cli/run@0.2.0#run func wasmexport_Run() (result0 uint32) { result := Exports.Run() - result0 = cm.BoolToU32(result) + result0 = (uint32)(cm.BoolToU32(result)) return } diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wit.go b/src/internal/wasi/cli/v0.2.0/run/run.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/empty.s b/src/internal/wasi/cli/v0.2.0/stderr/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/empty.s b/src/internal/wasi/cli/v0.2.0/stdin/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/empty.s b/src/internal/wasi/cli/v0.2.0/stdout/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go rename to src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go rename to src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go rename to src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go rename to src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go rename to src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go rename to src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go rename to src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go old mode 100644 new mode 100755 index 7473c3e2c5..acf22248b8 --- a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go +++ b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go @@ -31,9 +31,9 @@ import ( // nanoseconds: u32, // } type DateTime struct { - _ cm.HostLayout - Seconds uint64 - Nanoseconds uint32 + _ cm.HostLayout `json:"-"` + Seconds uint64 `json:"seconds"` + Nanoseconds uint32 `json:"nanoseconds"` } // Now represents the imported function "now". diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s b/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/abi.go b/src/internal/wasi/filesystem/v0.2.0/types/abi.go old mode 100644 new mode 100755 index 40ee28aa8a..30f39af5fb --- a/src/internal/wasi/filesystem/v0.2.0/types/abi.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/abi.go @@ -30,7 +30,7 @@ func lower_NewTimestamp(v NewTimestamp) (f0 uint32, f1 uint64, f2 uint32) { f0 = (uint32)(v.Tag()) switch f0 { case 2: // timestamp - v1, v2 := lower_DateTime(*v.Timestamp()) + v1, v2 := lower_DateTime(*cm.Case[DateTime](&v, 2)) f1 = (uint64)(v1) f2 = (uint32)(v2) } diff --git a/src/internal/wasi/filesystem/v0.2.0/types/empty.s b/src/internal/wasi/filesystem/v0.2.0/types/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go old mode 100644 new mode 100755 index cac7de9580..46c94b0040 --- a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go @@ -106,7 +106,7 @@ const ( DescriptorTypeSocket ) -var stringsDescriptorType = [8]string{ +var _DescriptorTypeStrings = [8]string{ "unknown", "block-device", "character-device", @@ -119,9 +119,22 @@ var stringsDescriptorType = [8]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e DescriptorType) String() string { - return stringsDescriptorType[e] + return _DescriptorTypeStrings[e] } +// MarshalText implements [encoding.TextMarshaler]. +func (e DescriptorType) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *DescriptorType) UnmarshalText(text []byte) error { + return _DescriptorTypeUnmarshalCase(e, text) +} + +var _DescriptorTypeUnmarshalCase = cm.CaseUnmarshaler[DescriptorType](_DescriptorTypeStrings[:]) + // DescriptorFlags represents the flags "wasi:filesystem/types@0.2.0#descriptor-flags". // // Descriptor flags. @@ -246,34 +259,34 @@ type LinkCount uint64 // status-change-timestamp: option, // } type DescriptorStat struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // File type. - Type DescriptorType + Type DescriptorType `json:"type"` // Number of hard links to the file. - LinkCount LinkCount + LinkCount LinkCount `json:"link-count"` // For regular files, the file size in bytes. For symbolic links, the // length in bytes of the pathname contained in the symbolic link. - Size FileSize + Size FileSize `json:"size"` // Last data access timestamp. // // If the `option` is none, the platform doesn't maintain an access // timestamp for this file. - DataAccessTimestamp cm.Option[DateTime] + DataAccessTimestamp cm.Option[DateTime] `json:"data-access-timestamp"` // Last data modification timestamp. // // If the `option` is none, the platform doesn't maintain a // modification timestamp for this file. - DataModificationTimestamp cm.Option[DateTime] + DataModificationTimestamp cm.Option[DateTime] `json:"data-modification-timestamp"` // Last file status-change timestamp. // // If the `option` is none, the platform doesn't maintain a // status-change timestamp for this file. - StatusChangeTimestamp cm.Option[DateTime] + StatusChangeTimestamp cm.Option[DateTime] `json:"status-change-timestamp"` } // NewTimestamp represents the variant "wasi:filesystem/types@0.2.0#new-timestamp". @@ -326,7 +339,7 @@ func (self *NewTimestamp) Timestamp() *DateTime { return cm.Case[DateTime](self, 2) } -var stringsNewTimestamp = [3]string{ +var _NewTimestampStrings = [3]string{ "no-change", "now", "timestamp", @@ -334,7 +347,7 @@ var stringsNewTimestamp = [3]string{ // String implements [fmt.Stringer], returning the variant case name of v. func (v NewTimestamp) String() string { - return stringsNewTimestamp[v.Tag()] + return _NewTimestampStrings[v.Tag()] } // DirectoryEntry represents the record "wasi:filesystem/types@0.2.0#directory-entry". @@ -346,12 +359,12 @@ func (v NewTimestamp) String() string { // name: string, // } type DirectoryEntry struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // The type of the file referred to by this directory entry. - Type DescriptorType + Type DescriptorType `json:"type"` // The name of the object. - Name string + Name string `json:"name"` } // ErrorCode represents the enum "wasi:filesystem/types@0.2.0#error-code". @@ -516,7 +529,7 @@ const ( ErrorCodeCrossDevice ) -var stringsErrorCode = [37]string{ +var _ErrorCodeStrings = [37]string{ "access", "would-block", "already", @@ -558,9 +571,22 @@ var stringsErrorCode = [37]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e ErrorCode) String() string { - return stringsErrorCode[e] + return _ErrorCodeStrings[e] +} + +// MarshalText implements [encoding.TextMarshaler]. +func (e ErrorCode) MarshalText() ([]byte, error) { + return []byte(e.String()), nil } +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ErrorCode) UnmarshalText(text []byte) error { + return _ErrorCodeUnmarshalCase(e, text) +} + +var _ErrorCodeUnmarshalCase = cm.CaseUnmarshaler[ErrorCode](_ErrorCodeStrings[:]) + // Advice represents the enum "wasi:filesystem/types@0.2.0#advice". // // File or memory access pattern advisory information. @@ -601,7 +627,7 @@ const ( AdviceNoReuse ) -var stringsAdvice = [6]string{ +var _AdviceStrings = [6]string{ "normal", "sequential", "random", @@ -612,9 +638,22 @@ var stringsAdvice = [6]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e Advice) String() string { - return stringsAdvice[e] + return _AdviceStrings[e] +} + +// MarshalText implements [encoding.TextMarshaler]. +func (e Advice) MarshalText() ([]byte, error) { + return []byte(e.String()), nil } +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *Advice) UnmarshalText(text []byte) error { + return _AdviceUnmarshalCase(e, text) +} + +var _AdviceUnmarshalCase = cm.CaseUnmarshaler[Advice](_AdviceStrings[:]) + // MetadataHashValue represents the record "wasi:filesystem/types@0.2.0#metadata-hash-value". // // A 128-bit hash value, split into parts because wasm doesn't have a @@ -625,12 +664,12 @@ func (e Advice) String() string { // upper: u64, // } type MetadataHashValue struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // 64 bits of a 128-bit hash value. - Lower uint64 + Lower uint64 `json:"lower"` // Another 64 bits of a 128-bit hash value. - Upper uint64 + Upper uint64 `json:"upper"` } // Descriptor represents the imported resource "wasi:filesystem/types@0.2.0#descriptor". @@ -761,7 +800,7 @@ func (self Descriptor) IsSameObject(other Descriptor) (result bool) { self0 := cm.Reinterpret[uint32](self) other0 := cm.Reinterpret[uint32](other) result0 := wasmimport_DescriptorIsSameObject((uint32)(self0), (uint32)(other0)) - result = cm.U32ToBool((uint32)(result0)) + result = (bool)(cm.U32ToBool((uint32)(result0))) return } diff --git a/src/internal/wasi/io/v0.2.0/error/empty.s b/src/internal/wasi/io/v0.2.0/error/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go b/src/internal/wasi/io/v0.2.0/error/error.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go rename to src/internal/wasi/io/v0.2.0/error/error.wasm.go diff --git a/src/internal/wasi/io/v0.2.0/error/error.wit.go b/src/internal/wasi/io/v0.2.0/error/error.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/poll/empty.s b/src/internal/wasi/io/v0.2.0/poll/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go b/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go old mode 100644 new mode 100755 index a3d5163944..4261f161a2 --- a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go +++ b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go @@ -57,7 +57,7 @@ func (self Pollable) Block() { func (self Pollable) Ready() (result bool) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_PollableReady((uint32)(self0)) - result = cm.U32ToBool((uint32)(result0)) + result = (bool)(cm.U32ToBool((uint32)(result0))) return } diff --git a/src/internal/wasi/io/v0.2.0/streams/abi.go b/src/internal/wasi/io/v0.2.0/streams/abi.go deleted file mode 100644 index 5f1500288a..0000000000 --- a/src/internal/wasi/io/v0.2.0/streams/abi.go +++ /dev/null @@ -1,12 +0,0 @@ -// Code generated by wit-bindgen-go. DO NOT EDIT. - -package streams - -import ( - "unsafe" -) - -// StreamErrorShape is used for storage in variant or result types. -type StreamErrorShape struct { - shape [unsafe.Sizeof(StreamError{})]byte -} diff --git a/src/internal/wasi/io/v0.2.0/streams/empty.s b/src/internal/wasi/io/v0.2.0/streams/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go b/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go old mode 100644 new mode 100755 index a4dcc970e2..e67a512c66 --- a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go +++ b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go @@ -64,14 +64,14 @@ func (self *StreamError) Closed() bool { return self.Tag() == 1 } -var stringsStreamError = [2]string{ +var _StreamErrorStrings = [2]string{ "last-operation-failed", "closed", } // String implements [fmt.Stringer], returning the variant case name of v. func (v StreamError) String() string { - return stringsStreamError[v.Tag()] + return _StreamErrorStrings[v.Tag()] } // InputStream represents the imported resource "wasi:io/streams@0.2.0#input-stream". @@ -273,13 +273,13 @@ func (self OutputStream) BlockingSplice(src InputStream, len_ uint64) (result cm // // let pollable = this.subscribe(); // while !contents.is_empty() { -// // Wait for the stream to become writable -// pollable.block(); -// let Ok(n) = this.check-write(); // eliding error handling -// let len = min(n, contents.len()); -// let (chunk, rest) = contents.split_at(len); -// this.write(chunk ); // eliding error handling -// contents = rest; +// // Wait for the stream to become writable +// pollable.block(); +// let Ok(n) = this.check-write(); // eliding error handling +// let len = min(n, contents.len()); +// let (chunk, rest) = contents.split_at(len); +// this.write(chunk ); // eliding error handling +// contents = rest; // } // this.flush(); // // Wait for completion of `flush` @@ -309,12 +309,12 @@ func (self OutputStream) BlockingWriteAndFlush(contents cm.List[uint8]) (result // // let pollable = this.subscribe(); // while num_zeroes != 0 { -// // Wait for the stream to become writable -// pollable.block(); -// let Ok(n) = this.check-write(); // eliding error handling -// let len = min(n, num_zeroes); -// this.write-zeroes(len); // eliding error handling -// num_zeroes -= len; +// // Wait for the stream to become writable +// pollable.block(); +// let Ok(n) = this.check-write(); // eliding error handling +// let len = min(n, num_zeroes); +// this.write-zeroes(len); // eliding error handling +// num_zeroes -= len; // } // this.flush(); // // Wait for completion of `flush` diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s b/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go rename to src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/insecure/empty.s b/src/internal/wasi/random/v0.2.0/insecure/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/random/empty.s b/src/internal/wasi/random/v0.2.0/random/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/random/random.wasm.go b/src/internal/wasi/random/v0.2.0/random/random.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/random/random.wit.go b/src/internal/wasi/random/v0.2.0/random/random.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s b/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go rename to src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go rename to src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/network/abi.go b/src/internal/wasi/sockets/v0.2.0/network/abi.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/network/empty.s b/src/internal/wasi/sockets/v0.2.0/network/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go b/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go old mode 100644 new mode 100755 index 93a8e82002..987c4c4c35 --- a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go @@ -153,7 +153,7 @@ const ( ErrorCodePermanentResolverFailure ) -var stringsErrorCode = [21]string{ +var _ErrorCodeStrings = [21]string{ "unknown", "access-denied", "not-supported", @@ -179,9 +179,22 @@ var stringsErrorCode = [21]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e ErrorCode) String() string { - return stringsErrorCode[e] + return _ErrorCodeStrings[e] } +// MarshalText implements [encoding.TextMarshaler]. +func (e ErrorCode) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ErrorCode) UnmarshalText(text []byte) error { + return _ErrorCodeUnmarshalCase(e, text) +} + +var _ErrorCodeUnmarshalCase = cm.CaseUnmarshaler[ErrorCode](_ErrorCodeStrings[:]) + // IPAddressFamily represents the enum "wasi:sockets/network@0.2.0#ip-address-family". // // enum ip-address-family { @@ -198,16 +211,29 @@ const ( IPAddressFamilyIPv6 ) -var stringsIPAddressFamily = [2]string{ +var _IPAddressFamilyStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the enum case name of e. func (e IPAddressFamily) String() string { - return stringsIPAddressFamily[e] + return _IPAddressFamilyStrings[e] } +// MarshalText implements [encoding.TextMarshaler]. +func (e IPAddressFamily) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *IPAddressFamily) UnmarshalText(text []byte) error { + return _IPAddressFamilyUnmarshalCase(e, text) +} + +var _IPAddressFamilyUnmarshalCase = cm.CaseUnmarshaler[IPAddressFamily](_IPAddressFamilyStrings[:]) + // IPv4Address represents the tuple "wasi:sockets/network@0.2.0#ipv4-address". // // type ipv4-address = tuple @@ -246,14 +272,14 @@ func (self *IPAddress) IPv6() *IPv6Address { return cm.Case[IPv6Address](self, 1) } -var stringsIPAddress = [2]string{ +var _IPAddressStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the variant case name of v. func (v IPAddress) String() string { - return stringsIPAddress[v.Tag()] + return _IPAddressStrings[v.Tag()] } // IPv4SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv4-socket-address". @@ -263,12 +289,12 @@ func (v IPAddress) String() string { // address: ipv4-address, // } type IPv4SocketAddress struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // sin_port - Port uint16 + Port uint16 `json:"port"` // sin_addr - Address IPv4Address + Address IPv4Address `json:"address"` } // IPv6SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv6-socket-address". @@ -280,18 +306,18 @@ type IPv4SocketAddress struct { // scope-id: u32, // } type IPv6SocketAddress struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // sin6_port - Port uint16 + Port uint16 `json:"port"` // sin6_flowinfo - FlowInfo uint32 + FlowInfo uint32 `json:"flow-info"` // sin6_addr - Address IPv6Address + Address IPv6Address `json:"address"` // sin6_scope_id - ScopeID uint32 + ScopeID uint32 `json:"scope-id"` } // IPSocketAddress represents the variant "wasi:sockets/network@0.2.0#ip-socket-address". @@ -322,12 +348,12 @@ func (self *IPSocketAddress) IPv6() *IPv6SocketAddress { return cm.Case[IPv6SocketAddress](self, 1) } -var stringsIPSocketAddress = [2]string{ +var _IPSocketAddressStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the variant case name of v. func (v IPSocketAddress) String() string { - return stringsIPSocketAddress[v.Tag()] + return _IPSocketAddressStrings[v.Tag()] } diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go rename to src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go old mode 100644 new mode 100755 index 8174d298fd..8bfc6292e3 --- a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go @@ -23,7 +23,7 @@ type TupleInputStreamOutputStreamShape struct { // IPSocketAddressShape is used for storage in variant or result types. type IPSocketAddressShape struct { _ cm.HostLayout - shape [unsafe.Sizeof(network.IPSocketAddress{})]byte + shape [unsafe.Sizeof(IPSocketAddress{})]byte } func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { @@ -64,14 +64,14 @@ func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 f0 = (uint32)(v.Tag()) switch f0 { case 0: // ipv4 - v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*v.IPv4()) + v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*cm.Case[network.IPv4SocketAddress](&v, 0)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) f4 = (uint32)(v4) f5 = (uint32)(v5) case 1: // ipv6 - v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*v.IPv6()) + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*cm.Case[network.IPv6SocketAddress](&v, 1)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go old mode 100644 new mode 100755 index 1257fcb146..3ab1acde6d --- a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go @@ -46,7 +46,7 @@ func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.Result[uint32, //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-enabled //go:noescape -func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.Result[bool, bool, ErrorCode]) +func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.Result[ErrorCode, bool, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-idle-time //go:noescape diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go old mode 100644 new mode 100755 index 5eb102cd30..c82fd59337 --- a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -71,7 +71,7 @@ const ( ShutdownTypeBoth ) -var stringsShutdownType = [3]string{ +var _ShutdownTypeStrings = [3]string{ "receive", "send", "both", @@ -79,9 +79,22 @@ var stringsShutdownType = [3]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e ShutdownType) String() string { - return stringsShutdownType[e] + return _ShutdownTypeStrings[e] } +// MarshalText implements [encoding.TextMarshaler]. +func (e ShutdownType) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ShutdownType) UnmarshalText(text []byte) error { + return _ShutdownTypeUnmarshalCase(e, text) +} + +var _ShutdownTypeUnmarshalCase = cm.CaseUnmarshaler[ShutdownType](_ShutdownTypeStrings[:]) + // TCPSocket represents the imported resource "wasi:sockets/tcp@0.2.0#tcp-socket". // // A TCP socket resource. @@ -241,7 +254,7 @@ func (self TCPSocket) HopLimit() (result cm.Result[uint8, uint8, ErrorCode]) { func (self TCPSocket) IsListening() (result bool) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_TCPSocketIsListening((uint32)(self0)) - result = cm.U32ToBool((uint32)(result0)) + result = (bool)(cm.U32ToBool((uint32)(result0))) return } @@ -285,7 +298,7 @@ func (self TCPSocket) KeepAliveCount() (result cm.Result[uint32, uint32, ErrorCo // keep-alive-enabled: func() -> result // //go:nosplit -func (self TCPSocket) KeepAliveEnabled() (result cm.Result[bool, bool, ErrorCode]) { +func (self TCPSocket) KeepAliveEnabled() (result cm.Result[ErrorCode, bool, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveEnabled((uint32)(self0), &result) return @@ -457,7 +470,7 @@ func (self TCPSocket) SetKeepAliveCount(value uint32) (result cm.Result[ErrorCod //go:nosplit func (self TCPSocket) SetKeepAliveEnabled(value bool) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) - value0 := cm.BoolToU32(value) + value0 := (uint32)(cm.BoolToU32(value)) wasmimport_TCPSocketSetKeepAliveEnabled((uint32)(self0), (uint32)(value0), &result) return } diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go rename to src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/abi.go b/src/internal/wasi/sockets/v0.2.0/udp/abi.go old mode 100644 new mode 100755 index 71d00f9d6b..e535da64ad --- a/src/internal/wasi/sockets/v0.2.0/udp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/abi.go @@ -11,7 +11,7 @@ import ( // IPSocketAddressShape is used for storage in variant or result types. type IPSocketAddressShape struct { _ cm.HostLayout - shape [unsafe.Sizeof(network.IPSocketAddress{})]byte + shape [unsafe.Sizeof(IPSocketAddress{})]byte } func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { @@ -52,14 +52,14 @@ func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 f0 = (uint32)(v.Tag()) switch f0 { case 0: // ipv4 - v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*v.IPv4()) + v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*cm.Case[network.IPv4SocketAddress](&v, 0)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) f4 = (uint32)(v4) f5 = (uint32)(v5) case 1: // ipv6 - v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*v.IPv6()) + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*cm.Case[network.IPv6SocketAddress](&v, 1)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) diff --git a/src/internal/wasi/sockets/v0.2.0/udp/empty.s b/src/internal/wasi/sockets/v0.2.0/udp/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go old mode 100644 new mode 100755 index f773fa48d0..f10df3091d --- a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go @@ -43,11 +43,11 @@ type IPAddressFamily = network.IPAddressFamily // remote-address: ip-socket-address, // } type IncomingDatagram struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // The payload. // // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. - Data cm.List[uint8] + Data cm.List[uint8] `json:"data"` // The source address. // @@ -55,7 +55,7 @@ type IncomingDatagram struct { // with, if any. // // Equivalent to the `src_addr` out parameter of `recvfrom`. - RemoteAddress IPSocketAddress + RemoteAddress IPSocketAddress `json:"remote-address"` } // OutgoingDatagram represents the record "wasi:sockets/udp@0.2.0#outgoing-datagram". @@ -67,9 +67,9 @@ type IncomingDatagram struct { // remote-address: option, // } type OutgoingDatagram struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // The payload. - Data cm.List[uint8] + Data cm.List[uint8] `json:"data"` // The destination address. // @@ -80,7 +80,7 @@ type OutgoingDatagram struct { // // If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise // it is equivalent to `sendto`. - RemoteAddress cm.Option[IPSocketAddress] + RemoteAddress cm.Option[IPSocketAddress] `json:"remote-address"` } // UDPSocket represents the imported resource "wasi:sockets/udp@0.2.0#udp-socket". @@ -321,10 +321,10 @@ func (self UDPSocket) StartBind(network_ Network, localAddress IPSocketAddress) // The POSIX equivalent in pseudo-code is: // // if (was previously connected) { -// connect(s, AF_UNSPEC) +// connect(s, AF_UNSPEC) // } // if (remote_address is Some) { -// connect(s, remote_address) +// connect(s, remote_address) // } // // Unlike in POSIX, the socket must already be explicitly bound. From d6527ece0241adb1ea7b5ab684623175c2d47e7a Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 15 Mar 2025 22:05:45 -0700 Subject: [PATCH 39/45] internal/cm: exclude certain files when copying package --- GNUmakefile | 2 +- src/internal/cm/CHANGELOG.md | 23 ---- src/internal/cm/LICENSE | 220 ----------------------------------- src/internal/cm/README.md | 15 --- src/internal/cm/RELEASE.md | 34 ------ 5 files changed, 1 insertion(+), 293 deletions(-) delete mode 100644 src/internal/cm/CHANGELOG.md delete mode 100644 src/internal/cm/LICENSE delete mode 100644 src/internal/cm/README.md delete mode 100644 src/internal/cm/RELEASE.md diff --git a/GNUmakefile b/GNUmakefile index 4e630c6a3f..958d08b4e4 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -277,7 +277,7 @@ wasi-syscall: wasi-cm .PHONY: wasi-cm wasi-cm: rm -rf ./src/internal/cm/* - rsync -rv --delete --exclude '*_test.go' $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm + rsync -rv --delete --exclude '*_test.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) diff --git a/src/internal/cm/CHANGELOG.md b/src/internal/cm/CHANGELOG.md deleted file mode 100644 index 35efaf2f57..0000000000 --- a/src/internal/cm/CHANGELOG.md +++ /dev/null @@ -1,23 +0,0 @@ -# Changelog - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [v0.2.0] — 2025-03-15 - -### Added - -- Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`. -- Initial support for JSON serialization of WIT `list`, `enum`, and `record` types. -- Added `cm.CaseUnmarshaler` helper for text and JSON unmarshaling of `enum` and `variant` types. - -### Changed - -- Breaking: package `cm`: removed `bool` from `Discriminant` type constraint. It was not used by code generation. - -## [v0.1.0] — 2024-12-14 - -Initial version, extracted into module [`go.bytecodealliance.org/cm`](https://pkg.go.dev/go.bytecodealliance.org/cm). - -[Unreleased]: -[v0.2.0]: -[v0.1.0]: diff --git a/src/internal/cm/LICENSE b/src/internal/cm/LICENSE deleted file mode 100644 index f9d81955f4..0000000000 --- a/src/internal/cm/LICENSE +++ /dev/null @@ -1,220 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - diff --git a/src/internal/cm/README.md b/src/internal/cm/README.md deleted file mode 100644 index d6c5fd4fd5..0000000000 --- a/src/internal/cm/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# go.bytecodealliance.org/cm - -[![pkg.go.dev](https://img.shields.io/badge/docs-pkg.go.dev-blue.svg)](https://pkg.go.dev/go.bytecodealliance.org/cm) [![build status](https://img.shields.io/github/actions/workflow/status/bytecodealliance/go-modules/test.yaml?branch=main)](https://github.com/bytecodealliance/go-modules/actions) - -## About - -Package `cm` contains helper types and functions used by generated packages, such as `option`, `result`, `variant`, `list`, and `resource`. These are intended for use by generated [Component Model](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#type-definitions) bindings, where the caller converts to a Go equivalent. It attempts to map WIT semantics to their equivalent in Go where possible. - -### Note on Memory Safety - -Package `cm` and generated bindings from `wit-bindgen-go` may have compatibility issues with the Go garbage collector, as they directly represent `variant` and `result` types as tagged unions where a pointer shape may be occupied by a non-pointer value. The GC may detect and throw an error if it detects a non-pointer value in an area it expects to see a pointer. This is an area of active development. - -## License - -This project is licensed under the Apache 2.0 license with the LLVM exception. See [LICENSE](../LICENSE) for more details. diff --git a/src/internal/cm/RELEASE.md b/src/internal/cm/RELEASE.md deleted file mode 100644 index 5a6283a606..0000000000 --- a/src/internal/cm/RELEASE.md +++ /dev/null @@ -1,34 +0,0 @@ -# Release - -This document describes the steps to release a new version of module `go.bytecodealliance.org/cm`. - -## 1. Update [CHANGELOG.md](./CHANGELOG.md) - -1. Add the latest changes to [CHANGELOG.md](./CHANGELOG.md). -1. Rename the Unreleased section to reflect the new version number. - 1. Update the links to new version tag in the footer of CHANGELOG.md -1. Add today’s date (YYYY-MM-DD) after an em dash (—). -1. Submit a [GitHub Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) with these updates. - -## 2. Create a new release - -Once the PR is merged, tag the new version in Git and push the tag to GitHub. - -**Note:** the tag **must** start with the prefix `cm/` in order to correctly tag this module. - -For example, to tag version `cm/v0.2.0`: - -```console -git tag cm/v0.2.0 -git push origin cm/v0.2.0 -``` - -## 3. Update the root module - -Once the tag is pushed, you can update the root module to depend on the newly created version of package `cm` by running the following: - -```console -go get -u go.bytecodealliance.org/cm@latest -``` - -Then follow the instructions in [RELEASE.md](../RELEASE.md) to release a new version of the root module. From 8e009de8a82c9d6b89a96045454924641077882b Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 15 Mar 2025 22:09:30 -0700 Subject: [PATCH 40/45] internal/cm: remove go.mod file --- GNUmakefile | 2 +- src/internal/cm/go.mod | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 src/internal/cm/go.mod diff --git a/GNUmakefile b/GNUmakefile index 958d08b4e4..7ac712e0cf 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -277,7 +277,7 @@ wasi-syscall: wasi-cm .PHONY: wasi-cm wasi-cm: rm -rf ./src/internal/cm/* - rsync -rv --delete --exclude '*_test.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm + rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) diff --git a/src/internal/cm/go.mod b/src/internal/cm/go.mod deleted file mode 100644 index f8e24af713..0000000000 --- a/src/internal/cm/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module go.bytecodealliance.org/cm - -go 1.23.0 From d63d8e08991ac27721066ba39522e0272d9510e4 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 16 Mar 2025 13:35:44 -0700 Subject: [PATCH 41/45] internal/wasm-tools, internal/cm: udpate to go.bytecodealliance.org@v0.6.1 and cm@v0.2.1 --- GNUmakefile | 2 +- internal/wasm-tools/go.mod | 4 +-- internal/wasm-tools/go.sum | 8 +++--- src/internal/cm/case.go | 17 ++++++++---- src/internal/cm/list.go | 56 -------------------------------------- 5 files changed, 18 insertions(+), 69 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 7ac712e0cf..1a97269e9b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -277,7 +277,7 @@ wasi-syscall: wasi-cm .PHONY: wasi-cm wasi-cm: rm -rf ./src/internal/cm/* - rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm + rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*_json.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod index 47ad4889c7..c790a7ab05 100644 --- a/internal/wasm-tools/go.mod +++ b/internal/wasm-tools/go.mod @@ -3,8 +3,8 @@ module github.com/tinygo-org/tinygo/internal/wasm-tools go 1.23.0 require ( - go.bytecodealliance.org v0.6.0 - go.bytecodealliance.org/cm v0.2.0 + go.bytecodealliance.org v0.6.1 + go.bytecodealliance.org/cm v0.2.1 ) require ( diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum index 45b74397d1..3f2a32ded1 100644 --- a/internal/wasm-tools/go.sum +++ b/internal/wasm-tools/go.sum @@ -29,10 +29,10 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= -go.bytecodealliance.org v0.6.0 h1:9ziqj963aEKqOiu5cl87Hb78KImNP4zEABl+OWwBwxk= -go.bytecodealliance.org v0.6.0/go.mod h1:j0lprXhqeVSE8kYN2EjcN9gm+fxUyNWtl//WA+3ypFg= -go.bytecodealliance.org/cm v0.2.0 h1:HMkj1x1LZWU/Ghu2TtUk3VTyS7gyDHLyIfIut0Cpc5M= -go.bytecodealliance.org/cm v0.2.0/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= +go.bytecodealliance.org v0.6.1 h1:H3vVYeEZa8Gs9t9YyZ4ZjA8sr5c6Xfkz3p+cM3543aA= +go.bytecodealliance.org v0.6.1/go.mod h1:qPL6PksrsjAxl6o23HuX4pGO6n3UxiESPbvRn8ZVot0= +go.bytecodealliance.org/cm v0.2.1 h1:sFUQRPfM+ku7hKgFwxGdwjghv4QtTLukpqsAHgE1Tek= +go.bytecodealliance.org/cm v0.2.1/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= diff --git a/src/internal/cm/case.go b/src/internal/cm/case.go index 65ade4949a..fa212bb0c9 100644 --- a/src/internal/cm/case.go +++ b/src/internal/cm/case.go @@ -1,7 +1,5 @@ package cm -import "errors" - // CaseUnmarshaler returns an function that can unmarshal text into // [variant] or [enum] case T. // @@ -33,8 +31,7 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te if len(text) == 0 { return errEmpty } - s := string(text) - c, ok := m[s] + c, ok := m[string(text)] if !ok { return errNoMatchingCase } @@ -46,6 +43,14 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te const linearScanThreshold = 16 var ( - errEmpty = errors.New("empty text") - errNoMatchingCase = errors.New("no matching case") + errEmpty = &stringError{"empty text"} + errNoMatchingCase = &stringError{"no matching case"} ) + +type stringError struct { + err string +} + +func (err *stringError) Error() string { + return err.err +} diff --git a/src/internal/cm/list.go b/src/internal/cm/list.go index 0a171dbd37..22d9d31f2e 100644 --- a/src/internal/cm/list.go +++ b/src/internal/cm/list.go @@ -1,8 +1,6 @@ package cm import ( - "bytes" - "encoding/json" "unsafe" ) @@ -62,57 +60,3 @@ func (l list[T]) Data() *T { func (l list[T]) Len() uintptr { return l.len } - -// MarshalJSON implements json.Marshaler. -func (l list[T]) MarshalJSON() ([]byte, error) { - if l.len == 0 { - return []byte("[]"), nil - } - - s := l.Slice() - var zero T - if unsafe.Sizeof(zero) == 1 { - // The default Go json.Encoder will marshal []byte as base64. - // We override that behavior so all int types have the same serialization format. - // []uint8{1,2,3} -> [1,2,3] - // []uint32{1,2,3} -> [1,2,3] - return json.Marshal(sliceOf(s)) - } - return json.Marshal(s) -} - -type slice[T any] []entry[T] - -func sliceOf[S ~[]E, E any](s S) slice[E] { - return *(*slice[E])(unsafe.Pointer(&s)) -} - -type entry[T any] [1]T - -func (v entry[T]) MarshalJSON() ([]byte, error) { - return json.Marshal(v[0]) -} - -// UnmarshalJSON implements json.Unmarshaler. -func (l *list[T]) UnmarshalJSON(data []byte) error { - if bytes.Equal(data, nullLiteral) { - return nil - } - - var s []T - err := json.Unmarshal(data, &s) - if err != nil { - return err - } - - l.data = unsafe.SliceData([]T(s)) - l.len = uintptr(len(s)) - - return nil -} - -// nullLiteral is the JSON representation of a null literal. -// By convention, to approximate the behavior of Unmarshal itself, -// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op. -// See https://pkg.go.dev/encoding/json#Unmarshaler for more information. -var nullLiteral = []byte("null") From a7416e7300d0ae92faf580d6a4e4341a1d076b68 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 16 Mar 2025 14:18:33 -0700 Subject: [PATCH 42/45] internal/wasi: remove x bit from generated files --- src/internal/wasi/cli/v0.2.0/command/command.wit.go | 0 src/internal/wasi/cli/v0.2.0/environment/empty.s | 0 src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go | 0 src/internal/wasi/cli/v0.2.0/environment/environment.wit.go | 0 src/internal/wasi/cli/v0.2.0/exit/empty.s | 0 src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go | 0 src/internal/wasi/cli/v0.2.0/exit/exit.wit.go | 0 src/internal/wasi/cli/v0.2.0/run/empty.s | 0 src/internal/wasi/cli/v0.2.0/run/run.exports.go | 0 src/internal/wasi/cli/v0.2.0/run/run.wasm.go | 0 src/internal/wasi/cli/v0.2.0/run/run.wit.go | 0 src/internal/wasi/cli/v0.2.0/stderr/empty.s | 0 src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go | 0 src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go | 0 src/internal/wasi/cli/v0.2.0/stdin/empty.s | 0 src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go | 0 src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go | 0 src/internal/wasi/cli/v0.2.0/stdout/empty.s | 0 src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go | 0 src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go | 0 src/internal/wasi/cli/v0.2.0/terminal-input/empty.s | 0 .../wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go | 0 src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go | 0 src/internal/wasi/cli/v0.2.0/terminal-output/empty.s | 0 .../wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go | 0 .../wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go | 0 src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s | 0 .../wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go | 0 .../wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go | 0 src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s | 0 .../wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go | 0 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go | 0 src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s | 0 .../wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go | 0 .../wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go | 0 src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s | 0 .../wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go | 0 .../wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go | 0 src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s | 0 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go | 0 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go | 0 src/internal/wasi/filesystem/v0.2.0/preopens/empty.s | 0 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go | 0 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go | 0 src/internal/wasi/filesystem/v0.2.0/types/abi.go | 0 src/internal/wasi/filesystem/v0.2.0/types/empty.s | 0 src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go | 0 src/internal/wasi/filesystem/v0.2.0/types/types.wit.go | 0 src/internal/wasi/io/v0.2.0/error/empty.s | 0 src/internal/wasi/io/v0.2.0/error/error.wasm.go | 0 src/internal/wasi/io/v0.2.0/error/error.wit.go | 0 src/internal/wasi/io/v0.2.0/poll/empty.s | 0 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go | 0 src/internal/wasi/io/v0.2.0/poll/poll.wit.go | 0 src/internal/wasi/io/v0.2.0/streams/empty.s | 0 src/internal/wasi/io/v0.2.0/streams/streams.wasm.go | 0 src/internal/wasi/io/v0.2.0/streams/streams.wit.go | 0 src/internal/wasi/random/v0.2.0/insecure-seed/empty.s | 0 .../wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go | 0 .../wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go | 0 src/internal/wasi/random/v0.2.0/insecure/empty.s | 0 src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go | 0 src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go | 0 src/internal/wasi/random/v0.2.0/random/empty.s | 0 src/internal/wasi/random/v0.2.0/random/random.wasm.go | 0 src/internal/wasi/random/v0.2.0/random/random.wit.go | 0 src/internal/wasi/sockets/v0.2.0/instance-network/empty.s | 0 .../wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go | 0 .../wasi/sockets/v0.2.0/instance-network/instance-network.wit.go | 0 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go | 0 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s | 0 .../wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go | 0 .../wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go | 0 src/internal/wasi/sockets/v0.2.0/network/abi.go | 0 src/internal/wasi/sockets/v0.2.0/network/empty.s | 0 src/internal/wasi/sockets/v0.2.0/network/network.wasm.go | 0 src/internal/wasi/sockets/v0.2.0/network/network.wit.go | 0 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s | 0 .../sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go | 0 .../sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go | 0 src/internal/wasi/sockets/v0.2.0/tcp/abi.go | 0 src/internal/wasi/sockets/v0.2.0/tcp/empty.s | 0 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go | 0 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go | 0 src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s | 0 .../sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go | 0 .../sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go | 0 src/internal/wasi/sockets/v0.2.0/udp/abi.go | 0 src/internal/wasi/sockets/v0.2.0/udp/empty.s | 0 src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go | 0 src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go | 0 91 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/command/command.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/environment/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/environment/environment.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/exit/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/exit/exit.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/run/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/run/run.exports.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/run/run.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/run/run.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stderr/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdin/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdout/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-input/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-output/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/preopens/empty.s mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/types/abi.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/types/empty.s mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/types/types.wit.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/error/empty.s mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/error/error.wasm.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/error/error.wit.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/poll/empty.s mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/poll/poll.wit.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/streams/empty.s mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/streams/streams.wasm.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/streams/streams.wit.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure-seed/empty.s mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure/empty.s mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/random/empty.s mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/random/random.wasm.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/random/random.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/instance-network/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/network/abi.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/network/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/network/network.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/network/network.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp/abi.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp/abi.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit.go b/src/internal/wasi/cli/v0.2.0/command/command.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/environment/empty.s b/src/internal/wasi/cli/v0.2.0/environment/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/exit/empty.s b/src/internal/wasi/cli/v0.2.0/exit/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/run/empty.s b/src/internal/wasi/cli/v0.2.0/run/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.exports.go b/src/internal/wasi/cli/v0.2.0/run/run.exports.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wasm.go b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wit.go b/src/internal/wasi/cli/v0.2.0/run/run.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/empty.s b/src/internal/wasi/cli/v0.2.0/stderr/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/empty.s b/src/internal/wasi/cli/v0.2.0/stdin/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/empty.s b/src/internal/wasi/cli/v0.2.0/stdout/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s b/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/abi.go b/src/internal/wasi/filesystem/v0.2.0/types/abi.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/empty.s b/src/internal/wasi/filesystem/v0.2.0/types/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/error/empty.s b/src/internal/wasi/io/v0.2.0/error/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/error/error.wasm.go b/src/internal/wasi/io/v0.2.0/error/error.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/error/error.wit.go b/src/internal/wasi/io/v0.2.0/error/error.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/poll/empty.s b/src/internal/wasi/io/v0.2.0/poll/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go b/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/streams/empty.s b/src/internal/wasi/io/v0.2.0/streams/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go b/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s b/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure/empty.s b/src/internal/wasi/random/v0.2.0/insecure/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/random/empty.s b/src/internal/wasi/random/v0.2.0/random/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/random/random.wasm.go b/src/internal/wasi/random/v0.2.0/random/random.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/random/random.wit.go b/src/internal/wasi/random/v0.2.0/random/random.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s b/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/network/abi.go b/src/internal/wasi/sockets/v0.2.0/network/abi.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/network/empty.s b/src/internal/wasi/sockets/v0.2.0/network/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go b/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/abi.go b/src/internal/wasi/sockets/v0.2.0/udp/abi.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/empty.s b/src/internal/wasi/sockets/v0.2.0/udp/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go old mode 100755 new mode 100644 From 4f8672c9ab290c752ce97da4008b407b19111ff7 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 16 Mar 2025 17:44:33 -0700 Subject: [PATCH 43/45] internal/cm: change error type from struct{string} This is a temporary fix until cm@v0.2.2 is released, and package internal/cm and internal/wasi can be regenerated. See #4810 for more information. --- src/internal/cm/case.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/internal/cm/case.go b/src/internal/cm/case.go index fa212bb0c9..2ca7c28da9 100644 --- a/src/internal/cm/case.go +++ b/src/internal/cm/case.go @@ -9,7 +9,7 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te if len(cases) <= linearScanThreshold { return func(v *T, text []byte) error { if len(text) == 0 { - return errEmpty + return &emptyTextError{} } s := string(text) for i := 0; i < len(cases); i++ { @@ -18,7 +18,7 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te return nil } } - return errNoMatchingCase + return &noMatchingCaseError{} } } @@ -29,11 +29,11 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te return func(v *T, text []byte) error { if len(text) == 0 { - return errEmpty + return &emptyTextError{} } c, ok := m[string(text)] if !ok { - return errNoMatchingCase + return &noMatchingCaseError{} } *v = c return nil @@ -42,15 +42,10 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te const linearScanThreshold = 16 -var ( - errEmpty = &stringError{"empty text"} - errNoMatchingCase = &stringError{"no matching case"} -) +type emptyTextError struct{} -type stringError struct { - err string -} +func (*emptyTextError) Error() string { return "empty text" } -func (err *stringError) Error() string { - return err.err -} +type noMatchingCaseError struct{} + +func (*noMatchingCaseError) Error() string { return "no matching case" } From e5a8d693006cdaf6da471e5eb6fb53188122fa28 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 16 Mar 2025 20:08:14 -0700 Subject: [PATCH 44/45] internal/wasm-tools: update to go.bytecodealliance.org@v0.6.2 --- internal/wasm-tools/go.mod | 4 ++-- internal/wasm-tools/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod index c790a7ab05..d91b5475cd 100644 --- a/internal/wasm-tools/go.mod +++ b/internal/wasm-tools/go.mod @@ -3,8 +3,8 @@ module github.com/tinygo-org/tinygo/internal/wasm-tools go 1.23.0 require ( - go.bytecodealliance.org v0.6.1 - go.bytecodealliance.org/cm v0.2.1 + go.bytecodealliance.org v0.6.2 + go.bytecodealliance.org/cm v0.2.2 ) require ( diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum index 3f2a32ded1..374f54e169 100644 --- a/internal/wasm-tools/go.sum +++ b/internal/wasm-tools/go.sum @@ -29,10 +29,10 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= -go.bytecodealliance.org v0.6.1 h1:H3vVYeEZa8Gs9t9YyZ4ZjA8sr5c6Xfkz3p+cM3543aA= -go.bytecodealliance.org v0.6.1/go.mod h1:qPL6PksrsjAxl6o23HuX4pGO6n3UxiESPbvRn8ZVot0= -go.bytecodealliance.org/cm v0.2.1 h1:sFUQRPfM+ku7hKgFwxGdwjghv4QtTLukpqsAHgE1Tek= -go.bytecodealliance.org/cm v0.2.1/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= +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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= From 3e60eeb368f25f237a512e7553fd6d70f36dc74c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 18 Mar 2025 23:33:27 +0100 Subject: [PATCH 45/45] Prepare for release 0.37.0 Signed-off-by: deadprogram --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ goenv/version.go | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4415de438e..1c1d488934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,53 @@ +0.37.0 +--- +* **general** + - add the Boehm-Demers-Weiser GC on Linux +* **ci** + - add more tests for wasm and baremetal +* **compiler** + - crypto/internal/sysrand is allowed to use unsafe signatures +* **examples** + - add goroutine benchmark to examples +* **fixes** + - ensure use of pointers for SPI interface on atsam21/atsam51 and other machines/boards that were missing implementation (#4798) + - replace loop counter with hw timer for USB SetAddressReq on rp2040 (#4796) +* **internal** + - update to go.bytecodealliance.org@v0.6.2 in GNUmakefile and internal/wasm-tools + - exclude certain files when copying package in internal/cm + - update to go.bytecodealliance.org/cm@v0.2.2 in internal/cm + - remove old reflect.go in internal/reflectlite +* **loader** + - use build tags for package iter and iter methods on reflect.Value in loader, iter, reflect + - add shim for go1.22 and earlier in loader, iter +* **machine** + - bump rp2040 to 200MHz (#4768) + - correct register address for Pin.SetInterrupt for rp2350 (#4782) + - don't block the rp2xxx UART interrupt handler + - fix RP2040 Pico board on the playground + - add flash support for rp2350 (#4803) +* **os** + - add stub Symlink for wasm +* **refactor** + - use *SPI everywhere to make consistent for implementations. Fixes #4663 "in reverse" by making SPI a pointer everywhere, as discussed in the comments. +* **reflect** + - add Value.SetIter{Key,Value} and MapIter.Reset in reflect, internal/reflectlite + - embed reflectlite types into reflect types in reflect, internal/reflectlite + - add Go 1.24 iter.Seq[2] methods + - copy reflect iter tests from upstream Go + - panic on Type.CanSeq[2] instead of returning false + - remove strconv.go + - remove unused go:linkname functions +* **riscv-qemu** + - add VirtIO RNG device + - increase stack size +* **runtime** + - only allocate heap memory when needed + - remove unused file func.go + - use package reflectlite +* **transform** + - cherry-pick from #4774 + + 0.36.0 --- * **general** diff --git a/goenv/version.go b/goenv/version.go index 1f0f4c75ea..8b0aa07631 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -10,7 +10,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.37.0-dev" +const version = "0.37.0" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012).