Compare commits

...

90 Commits

Author SHA1 Message Date
Patricio Whittingslow 5c78cf05cd begin debugging gvisor crash 2026-07-06 13:27:24 -03:00
Jake Bailey e79edb58b2 all: clean up code with Go 1.24+ in mind (#5489)
* all: clean up code with Go 1.24+ in mind

* all: more old go cleanup

* all: even remove rand_fastrand64

* runtime: revert rand changes

* runtime: re-drop rand_fastrand64
2026-07-03 19:10:55 +02:00
rdon(あーるどん) bf80e9b446 runtime/rp2: handle RP2350 shared FIFO IRQ for GC (#5482)
* runtime/rp2: handle RP2350 shared FIFO IRQ for GC

---------

Co-authored-by: rdon <you@example.com>
2026-07-03 10:21:29 +02:00
rdon 30355cec9f machine/rp2: clear USB buffer status before endpoint handlers 2026-07-01 12:26:40 +02:00
deadprogram 9111927f85 builder: don't count non-writable SHT_NOBITS sections as RAM/bss
A SHT_NOBITS section was unconditionally treated as .bss (RAM) unless it
was the stack. The ESP linker scripts emit non-writable SHT_NOBITS
sections (.irom_dummy / .rodata_dummy) that merely reserve the
flash-mapped XIP virtual address ranges and occupy no RAM. Counting them
inflated the reported bss/RAM usage.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-06-30 19:52:47 +02:00
Damian Gryski 10224a4bf8 reflect,reflectlite: make IsRO/MakeRO package methods
Having them as methods on reflectlite.Value makes them
visible to the user.

Also move new functions out of all_test.go so it can stay
closer to upstream (except for comments).
2026-06-30 09:49:02 -07:00
Damian Gryski 33ddee76db reflect: fix pointer receiver for MakeRO 2026-06-30 09:49:02 -07:00
Damian Gryski 7fa50a55cf reflect: address Copilot review feedback 2026-06-30 09:49:02 -07:00
Damian Gryski 1c0c26b8b7 reflect: use TestConvert from all_test.go and make it pass
This PR removes convert_test.go which was a minimal version of this test.
2026-06-30 09:49:02 -07:00
Damian Gryski 3abe58448b refleect: more conversion tests 2026-06-30 09:49:02 -07:00
Damian Gryski 364ac2dcb2 reflect: fix (u)int -> string conversions for invalid code points 2026-06-30 09:49:02 -07:00
Damian Gryski e111b489ec reflect: add (u)int -> string conversions 2026-06-30 09:49:02 -07:00
Damian Gryski f31b39775f reflect: add string <-> []rune conversions 2026-06-30 09:49:02 -07:00
Damian Gryski 6c30583062 reflect: add some more complex conversion tests 2026-06-30 09:49:02 -07:00
Damian Gryski d29e8e5f22 reflect: add converting complex 2026-06-30 09:49:02 -07:00
Damian Gryski a93640662e reflect: add Type.ConvertibleTo 2026-06-30 09:49:02 -07:00
deadprogram 3fb0a90854 machine: add I2C support for ESP32-C6
Generalize the shared esp32xx I2C implementation to also cover the
ESP32-C6 and add the chip-specific code.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-06-26 01:20:21 +02:00
Ayke van Laethem df09dbdf0f transform: restore previous test behavior
I think it's much nicer to have the test output inline in the source
file, that way it's much easier to review any changes. For example, when
escape analysis is improved this is visible with removed `// OUT` lines.

This is similar to how LLVM writes its tests, and I like that style.
2026-06-25 23:26:06 +02:00
Ayke van Laethem 134de98ba5 compiler, runtime: optimize zero-sized allocations
Instead of referring to an unused global, use a constant value. This is
safe even when using `-gc=none` (since no actual memory gets allocated)
which wasn't the case before. It should also reduce binary size by a few
bytes for most programs.
2026-06-25 21:09:43 +02:00
Ayke van Laethem 221ea6a54c runtime: move alloc_noheap call (pure refactor)
See the next commit, where this makes more sense.
2026-06-25 21:09:43 +02:00
Ayke van Laethem 7ffbcbc666 compiler: refactor runtime.alloc call
This refactor makes no other changes.
2026-06-25 21:09:43 +02:00
deadprogram 5f66260c3a machine,targets: add minimal esp32c6 implementation
This adds a minimal esp32c6 implementation, currently only
supporting the examples/serial and examples/blinky1 programs.
It does correctly output the expected "Hello, World" via the
serial port, as well as blink the onboard LED.

In addition, it adds support for the PLIC based IRQ handling
as used on the ESP32C6 processor.

Some parts of this code are loosely based on PR #5252 and #5248

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-06-25 19:30:24 +02:00
Ayke van Laethem ba95eb3e1b compiler: use LLVM intrinsics for math trig operations
I think this failed in the past, but presumably those failures have been
fixed by now. So these intrinsics can now be used.

Using these intrinsics instead of the native Go implementations helps
LLVM to reason about them: it can for example evaluate the value at
compile time or do optimizations like convert
`float32(math.Sin(float64(x)))` into a 32-bit sin operation. If the math
operation is not available on the target platform the C library
implementation will be used instead.
2026-06-25 18:31:58 +02:00
iamrajiv f16697624d interp: defer out-of-bounds loads to runtime instead of crashing
When the interpreter encountered a load that was out of bounds of the
object, it panicked with "interp: load out of bounds", crashing the
compiler. This can happen for valid Go programs, for example when
dereferencing the pointer returned by unsafe.SliceData on a
zero-capacity slice, which points to a zero-sized object:

	package main

	import "unsafe"

	var p = unsafe.SliceData([]int{})
	var v = *p

	func main() {}

Return nil for an out-of-bounds load, the same as for an external
global, so the caller defers the load to runtime instead of crashing.
This matches what regular Go does, where the load reads from the
runtime zero-base at runtime.

Fixes #4214
2026-06-24 18:45:34 +02:00
Ayke van Laethem cb311ed25e machine: optimize RTT initialization
This converts the heap allocation to a statically allocated string.
This is useful for me, since it reduces binary size and makes it
possible to use `-gc=none` (which would previously result in a linker
error).
2026-06-24 12:43:55 +02:00
Damian Gryski 6f686d12af runtime: don't access timer queue outside of lock
This was causing a flake on testing `context`.

Fixes #5469
2026-06-19 11:55:32 -07:00
Jake Bailey 6a0acfc735 main: don't mutate compile options in Test 2026-06-19 17:48:12 +02:00
Jake Bailey 27f8ec17fc transform: follow aggregate returned alloc aliases
Track whether an allocation reaches a callee return separately from the
instruction where it escapes. The extra result state is needed because LLVM's
returned parameter attribute only covers scalar returns: a slice helper returns
its data pointer inside a {ptr, len, cap} aggregate, so the alias is only
visible by walking insertvalue and ret uses in the callee.

This lets OptimizeAllocs keep the backing array for returnIntSlice(s) on the
stack when the returned slice is ignored, matching gc's escape decision for the
same pattern. The golden output still keeps the escaping returned-slice case on
the heap.
2026-06-19 11:05:56 +02:00
Jake Bailey a683881eb7 transform: handle returned pointer alloc aliases
Follow returned pointer aliases when deciding whether runtime.alloc calls can
be lowered to stack allocations. A returned parameter is not the same as
nocapture: it still flows back to the caller and must be checked as an alias
of the original allocation.

Keep the analysis conservative for recursive returned-parameter chains and
unknown operands. The existing golden test now shows that the non-escaping
returned pointer cases no longer require heap allocation.
2026-06-19 11:05:56 +02:00
Jake Bailey 335b085fd2 transform: add allocation alias regression cases
Add allocation diagnostics coverage for pointer-returning helpers, aggregate
slice returns, and conditional pointer returns before changing the allocation
optimizer.

The golden files record the current heap-allocation behavior so later commits
show exactly which diagnostics each optimizer improvement removes.
2026-06-19 11:05:56 +02:00
Nia Waldvogel 11c2b76e29 runtime (gc.blocks): move objHeader to the end
This moves the objHeader from before an object body to after it.
On 32-bit systems with 16-byte alignment requirements (x86, ARM, RISC-V), we previously padded the header to a whole block.
This wastes up to 12 bytes, as on -gc=conservative the header is a single pointer.
With this change, no padding is required (beyond that from rounding the size up).

The "head" block in the metadata was moved to the end of the range to match the header location.
This changed the block loop directions throughout the GC logic.
The bit hacks used by sweep no longer work because there is no equivalent of addition that carries downwards.
However it is now possible to merge the sweep and free range list rebuild passes because their loop directions match.

There are two other places where we rebuilt the free ranges list: when initializing or growing the heap.
The former can be easily replaced with a single hardcoded range containing the entire heap.
In the latter case, I opted to only add the new space to the existing list.
These replacements allowed me to fully remove the buildFreeRanges function.
2026-06-16 13:37:35 -04:00
Marco Feltmann 6605b6e43f New Boards: Pimoroni Blinky 2350 and Badger 2350 (#5434)
* Prepares target for Piromoni Blinky 2350

* Adds target for Piromoni Badger 2350

* Updates submodules

* Removes redundant csv mention

* Revert "Updates submodules"

This reverts commit f05c2e1f2b.
2026-06-13 15:00:29 +02:00
rdon ec18e89365 machine: add GP aliases for waveshare-rp2040-zero 2026-06-13 13:51:46 +02:00
Jake Bailey 39cde9f9a3 builder: retry cached archive renames on Windows (#5462) 2026-06-12 16:33:13 -07:00
Konstantin Sharlaimov 2747027ef2 machine/rp2350: fix the ROM CS control for RP2350 ROM code
Remove support for A0 & A1 versions of RP2350
2026-06-12 21:07:20 +02:00
Piotr Bocheński 4465360f3d transform: add -print-allocs-cover and restore -print-allocs reason output
PR #5220 changed -print-allocs output to the go coverage tool format, which
replaced the original human-readable explanation of why each object had to be
heap allocated. That explanation is useful on its own, so this restores it as
the default behavior of -print-allocs and moves the coverage format behind a
-print-allocs-cover flag.

Signed-off-by: Piotr Bocheński <piotr@bochen.ski>
2026-06-12 19:57:08 +02:00
Konstantin Sharlaimov c33113ab5f fix(usb): implement endpoint stall for nRF52840.
Implement SetStallEPIn, SetStallEPOut, ClearStallEPIn, and ClearStallEPOut methods on the nRF52840 USBDevice struct using the hardware EPSTALL register to support MSC driver requirements. Also, correct the handleUSBIRQ loops to iterate over physical endpoint numbers rather than dynamic configuration entries to prevent missed or incorrectly routed endpoint interrupts.
2026-06-12 18:17:25 +02:00
Konstantin Sharlaimov eab43851ac fix(usb): support bidirectional endpoints on RP2 and SAMD21/51.
Remap CDC and MSC endpoints to share physical endpoint numbers. This change separates IN/OUT directional states for RP2 endpoints to prevent cross-talk, ensures SAMD21 and SAMD51 endpoint configuration merging does not overwrite bidirectionally shared registers, implements missing SAMD endpoint stall and clear methods, and corrects SAMD interrupt loop bounds to iterate over physical endpoint numbers rather than dynamic configuration entries.
2026-06-12 18:17:25 +02:00
Konstantin Sharlaimov c3f1832d9a refactor(usb): dynamic endpoint descriptor generation
Replace static endpoint variables with EndpointIN and EndpointOUT
functions. Allows flexible endpoint remapping across USB configs.

Map CDC, HID, MIDI and MSC USB devices to bidirectional endpoints consistently across different configurations.
2026-06-12 18:17:25 +02:00
Jake Bailey 2328d1e799 syscall: remove sliceHeader, use unsafe.SliceData more 2026-06-09 13:06:17 -04:00
Konstantin Sharlaimov 5fabc203db builder: increase stack size margin for automatic stack allocation
Adjust the stack size margin to account for the tinygo_swapTask overhead.
2026-06-08 23:41:39 +02:00
あーるどん d9d19e812e usb/cdc: fix RP2 USB CDC TX race with cores scheduler (#5391)
* usb/cdc: serialize TX pump across cores

* usb/cdc: apply review suggestion

* Clarify TX pump loop comment

* clarify txActive comments

* usb/cdc: document txActive ownership and lost-wakeup recheck

* builder: update wioterminal size expectation

---------

Co-authored-by: rdon <you@example.com>
2026-06-08 13:58:19 +02:00
deadprogram 45dd431f19 ci: add job to ensure that the go.sum is up to date with the go.mod file
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-06-08 10:23:07 +02:00
deadprogram 088a660c00 fix: update go.sum file which became out of sync somehow
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-06-08 10:23:07 +02:00
deadprogram 1ff348fb1f ci: update to latest Go version (1.26.4) for tests
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-06-08 10:23:07 +02:00
zowhoey cd364a9315 gba: add bios interrupt flags (#5445)
* gba: add bios interrupt flags

Adds a new GBA register for interrupt flags, the register is equivalent
to the IF register, but is intended for BIOS functions.

Acknowledging the interrupts with this register allows us to use BIOS
halt functions such as VBlankIntrWait and IntrWait, which we can use to
get rid of busy loops in the GBA code.

* fix formatting

---------

Co-authored-by: zoey <git@zoey.si>
2026-06-07 22:41:40 +02:00
sago35 ca584de84a machine/usb: support bidirectional endpoints by dynamic registration (#5447)
* machine/usb: support bidirectional endpoints by dynamic registration
2026-06-07 20:33:18 +02:00
deadprogram 594be6db63 CI: go back to free runners
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-06-07 16:40:43 +02:00
sago35 aef70a5839 machine/usb: fix truncated USB string descriptor when host requests short maxLen (#5449)
* machine/usb: fix truncated USB string descriptor when host requests short maxLen
* fix binary size
2026-06-07 13:32:52 +02:00
Matthew Hiles e6e7250a47 UEFI Target groundwork: runtime: extract Windows PE globals scan helper (#5361)
* runtime: extract Windows PE globals scan helper
* Update src/runtime/os_windows_pe.go
* run goimports
2026-06-07 08:45:16 +02:00
Matthew Hiles 7b3dc19445 UEFI target groundwork: runtime: split baremetal memory setup (#5360)
* runtime: split baremetal memory setup
* add missing unsafe import
2026-06-06 16:11:20 +02:00
Konstantin Sharlaimov 8e36d2758b runtime: improve hardfault handler and stack reporting on Cortex-M
- Add reference to the current goroutine stack (PSP) when showing the hardfault info on Cortex-M.
2026-06-05 21:05:36 +02:00
zoey 7cbbfd6fc5 gba: add support for mGBA debugging
Implements `putchar` when `mgbadebug` build tag is set which outputs
text through the mGBA debugging output interface.

mGBA allows the games running in the emulator to output debug text the
process to do so is:
1. set 0x4FFF780 to value of 0xC0DE
2. write text in memory 0x4FFF600 to 0x4FFF700
3. set 0x4FFF700 to the desired log level value from 0x100 (fatal) to
0x104 (debug)

Once this is done mGBA will output the text in its logs view as well as
through stdout. (Setting the log level to fatal also produces a dialog
box with the text)

The text output in the CLI is prefixed with `[DEBUG] GBA Debug: ` so I
modified the regex used for address matching in panic messages to be
able to read the address when the output line doesn't start with
`panic`.
2026-06-05 17:55:03 +02:00
あーるどん e8bb38f7c9 rp2: allow SPI transmit-only without SDI (#5437)
* rp2: allow SPI transmit-only without SDI

* ci: rerun

---------

Co-authored-by: rdon <you@example.com>
2026-06-05 12:10:16 +02:00
Jake Bailey 1b9bb143bf compiler: disambiguate function-local named types (#5336)
* compiler: make getTypeCodeName a method on compilerContext

* testdata: add regression tests for function-local named types

* compiler: disambiguate function-local named types

* Some PR feedback
2026-06-05 11:04:44 +02:00
Matthew Mets 41f666c670 Makefile: Use ${LLVM_PROJECTDIR} during copy phase 2026-06-04 22:14:16 +02:00
Jake Bailey 469e2434fd compiler: document defer frame field dependency 2026-06-04 19:25:47 +02:00
Jake Bailey 278aa09819 compiler: share recoverable fault blocks 2026-06-04 19:25:47 +02:00
Jake Bailey dded832238 GNUmakefile: update stdlib test package lists
Add newly passing packages to the appropriate native and Windows
standard-library test lists, and refresh comments for packages that
remain disabled.
2026-06-04 19:25:47 +02:00
Jake Bailey 8ffabbea64 runtime: add Windows vectored exception handler for recoverable panics
Register a vectored exception handler at startup so Windows hardware
exceptions can be translated into Go panics. Access violations become
nil pointer panics, and integer divide-by-zero exceptions become divide
by zero panics, allowing defer/recover to handle them like ordinary
runtime panics.
2026-06-04 19:25:47 +02:00
Jake Bailey 29b4c6723f runtime: make divide-by-zero and nil dereference panics recoverable
Modify the Unix signal handler to redirect execution to a Go sigpanic
function instead of printing an error and re-raising the signal.
The C signal handler modifies the ucontext to make the faulting
instruction appear to have called tinygo_sigpanic, which then calls
runtimePanic with the appropriate message.

Supported on all architectures TinyGo targets on Linux and Darwin:
x86_64, i386, aarch64, ARM, and MIPS. Also registers SIGFPE, which
was previously not handled at all.
2026-06-04 19:25:47 +02:00
Jake Bailey 039f48f3d2 compiler: make map and channel panics recoverable
Change hashmap set operations and channel send/close from
createRuntimeCall to createRuntimeInvoke. This places a setjmp
checkpoint before each call, allowing runtimePanicAt to safely
longjmp when these operations panic.
2026-06-04 19:25:47 +02:00
Jake Bailey eed4afda63 compiler, runtime: make runtime panics recoverable
Emit fault checkpoints around compiler-generated runtime assertions so
runtimePanicAt can unwind through the existing defer/recover machinery
instead of aborting. This lets panics from bounds checks, type checks,
and other compiler-inserted runtime checks be recovered by deferred
functions.

Mark functions that call recover as noinline. Inlining such a function
into a deferred closure can make recover observe the wrong call context
and report success when it should return nil.

Addresses tinygo-org/tinygo issues 2759 and 3510.
2026-06-04 19:25:47 +02:00
Jake Bailey ce888e1042 compiler: store defer list head in defer frame 2026-06-04 19:25:47 +02:00
Kenneth Bell f5d0ec93ef esp32: configure uarts to specified pins and yield waiting for buffer 2026-06-01 14:37:09 +02:00
Jake Bailey 3a0d72457b interp: fix partial store aliasing with previously loaded values
The optimization to skip cloning the destination object on partial
stores when the buffer is already owned by the current memory view
mutated obj.buffer.buf in place. The previous v.buf clone only handled
the case where the store value itself aliased that buffer; it did not
cover the more common case where an earlier partial load had returned
a slice into the same buffer. The in-place store would then corrupt
the previously loaded value, breaking code such as cloudflare/bn256
where the gfP arithmetic loads, computes, and stores within the same
global during package initialization.

Fix this in load instead: when the source object is owned by the
current memory view, copy the loaded slice so the returned value is
independent of the live buffer. The v.buf clone in store is no longer
needed and is removed.

Updates the regression test added in the previous commit to reflect
the corrected behavior.
2026-06-01 11:27:56 +02:00
Jake Bailey c980e48ad4 interp: add regression test for partial store aliasing
Adds a third init function to the store testdata: an initial partial
store puts the buffer into the current memory view, then a partial load
is followed by another partial store at the same offset, and finally the
loaded value is written to a separate global.

The expected output captures the current (incorrect) behavior of the
in-place partial store optimization: the loaded value gets corrupted by
the subsequent in-place store. The next commit fixes this.
2026-06-01 11:27:56 +02:00
Jake Bailey f5bd750a4d interp: avoid repeated object clones on partial stores
memoryView.store cloned the entire destination object on every partial
store. Compiling a program that pulls in golang.org/x/text/collate hit
this hard: its generated tables are initialized one element at a time,
and each element store copied the whole global, making interp quadratic
in the table size.

Switch to copy-on-first-write per memory view: clone the object the
first time this view writes to it, then mutate that private copy in
place on later partial stores. Rollback is unaffected because revert
still discards the view's objects wholesale.

Two extra safety tweaks fall out of this:

  - Full overwrites now clone the incoming value, so the stored buffer
    can never alias state held by the caller.
  - Partial in-place stores snapshot the source buffer, so a store
    whose source is a load from the same object (overlapping or not)
    still sees the pre-store bytes.

Add an interp golden test exercising both the overlapping load/store
case and a cross-object aliased load/store case.
2026-06-01 11:27:56 +02:00
deadprogram 0a5875ab2e gba: add some const values for sound register configs to avoid magic numbers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-05-29 16:02:52 +02:00
Ayke van Laethem 54ecf74261 sizediff: look for actual 'tinygo build' line 2026-05-29 10:32:27 +02:00
deadprogram 9b83acffe6 ci: increase runner size for compat build
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-05-29 09:10:14 +02:00
deadprogram 67aa15e92e fix: move esp32s3 based device to inside of XTENSA build section
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-05-29 09:10:14 +02:00
Juraj Michálek 0d6efc6429 Add new targets/esp32s3-box-3 (#5388)
* targets: add esp32s3-box-3

* add esp32s3-box3

* register esp32-s3-box-3 in tests
2026-05-28 22:47:08 +02:00
deadprogram 1bd3ade825 ci: increase size of runners for Linux, Windows, and macOS (Intel)
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-05-28 20:55:18 +02:00
Ayke van Laethem fb8ed8fde8 all: update golang.org/x/tools
This has the following effects:

  * It fixes issue #5414.
  * It bumps the minimum Go version to Go 1.24 (from 1.23).

I looked, and there doesn't seem to be a version of golang.org/x/tools
that supports the improved `new` builtin without also bumping the
minimum Go version.
2026-05-28 19:32:08 +02:00
Ayke van Laethem c698eeacba cli: add basic probe-rs support
OpenOCD can be somewhat outdated. `probe-rs` is a more modern tool
written in Rust with many interesting features, like built-in RTT
logging support.

This commit just adds basic support, to be able to flash devices with
probe-rs and debug them.
2026-05-28 18:05:24 +02:00
deadprogram 906a20d3bf stm32u5: configure system clock to 160 MHz
- Uses PLL1 to boost the system clock from the 4 MHz MSIS default to 160 MHz.
- Sets VOS to Range 1 (1.2V) and enables the EPOD booster for higher frequency support.
- Configures flash latency (4 wait states) and enables prefetch for 160 MHz operation.
- Updates CPU and APB timer frequencies in the machine package accordingly.
- Fixes LPUART baud rate divisor computation by using 64-bit arithmetic to prevent
overflow with the newly increased 160 MHz clock.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-05-28 15:18:31 +02:00
Ayke van Laethem 1be377c47d ci: add check for compatibility with older Go/LLVM versions 2026-05-28 11:46:25 +02:00
Ayke van Laethem 4dbb5dd4da nrf: avoid too high priority interrupt on SoftDevice
The highest priority interrupts are reserved by the SoftDevice, the
SoftDevice will refuse to start if any of them are used for other
purposes.
2026-05-28 11:36:38 +02:00
Ayke van Laethem 997974376d ci: re-add fmt-check in CI
This was removed in https://github.com/tinygo-org/tinygo/pull/5406.
2026-05-28 11:36:05 +02:00
deadprogram 7d51044892 machine/stm32: fix UART transmission, baud rate, and interrupt handling
This fixes several issues in the STM32 UART implementation:
- `writeByte` now waits for the transmit register to be empty before writing, preventing data loss by avoiding overwriting the shift register.
- `flush` now correctly waits for the Transmission Complete (TC) flag.
- The interrupt handler now clears all error flags (ORE, NE, FE, PE) to prevent interrupt storms, rather than just ORE.
- Extracted `SetBaudRate` so it can be cleanly overridden by specific MCU families.

It also introduces specific fixes for the STM32U5 family:
- Enforces a minimum BRR divisor of 16 in `getBaudRateDivisor` to prevent undefined hardware behavior and CPU starvation.
- Overrides `SetBaudRate` for STM32U585 to momentarily disable the USART (UE=0) before updating the BRR register, which is read-only when enabled.
- Adds a readback after enabling the USART1 clock to ensure the clock is active before register access.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-05-28 08:53:40 +02:00
Patricio Whittingslow 37c080083b upgrade espflasher 2026-05-28 00:30:46 +02:00
Ayke van Laethem 70a0dee8c8 all: use unsafe.SliceData where appropriate 2026-05-27 19:44:10 +02:00
Ayke van Laethem de83d67364 loader: skip object resolution
This is deprecated, see: https://pkg.go.dev/go/ast#Object
Removing it might speed up TinyGo a little bit.
2026-05-27 10:30:27 +02:00
Ayke van Laethem 7a5a50d949 runntime/interrupt.Checkpoint: add warning that this is hard to use
Realized this while looking through the code: there is no way to use
this safely when the scheduler is involved. It can only be used safely
with a `wfe` or similar (part of scheduler code).
2026-05-26 15:30:18 +02:00
Pat Whittingslow d348d8b4cf rp2040: fix -gc=leaking/none 2026-05-26 15:27:32 +02:00
Patricio Whittingslow 443de9fe1c skip flag on windows :( 2026-05-26 15:21:00 +02:00
Patricio Whittingslow c72f1d5e62 GNUmakefile: disable DWARF compression for CGo objects 2026-05-26 15:21:00 +02:00
Ayke van Laethem ca8fcc9479 cli: clarify runtime.alloc linker error with -gc=none
This should help people figure out the actual problem if they run into
this.
2026-05-26 14:28:52 +02:00
Achille d01a932201 runtime,syscall,internal/poll,os: wasip1 poll_oneoff scheduler integration + net.FileListener (#5386)
* runtime,syscall,internal/poll,os: wasip1 poll_oneoff scheduler integration + net.FileListener

On wasip1 today every syscall.Read/Write blocks the entire wasm module — the
cooperative scheduler invokes poll_oneoff only for sleep/timer wakeups, and
there's no path from the net package to a working TCP server. This change
fixes both: it threads poll_oneoff through the scheduler's idle path so a
goroutine doing FD I/O parks instead of blocking the module, and it provides
enough internal/poll / os / syscall surface that upstream Go's
net.FileListener / net.FileConn works on a host-pre-opened TCP socket.

* runtime: keep scheduler_cooperative idle-wait calls direct

TestBinarySize/hifive1b/examples/echo regressed by 32 bytes after the
previous commit routed the scheduler's idle wait through a
schedulerIdleWait helper. The extra call frame + branch landed on every
non-wasip1 cooperative target, where the original direct sleepTicks /
waitForEvents calls compile to a single inlined call.
2026-05-26 10:12:55 +02:00
204 changed files with 6921 additions and 1666 deletions
+2 -2
View File
@@ -40,7 +40,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.2'
go-version: '1.26.4'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
@@ -131,7 +131,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.2'
go-version: '1.26.4'
cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install -tags=llvm${{ matrix.version }}
+72
View File
@@ -0,0 +1,72 @@
# This CI job checks whether at least the smoke tests pass for the oldest
# Go/LLVM version we claim to support.
name: Version compatibility test
on:
pull_request:
push:
branches:
- dev
- release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test-compat:
runs-on: ubuntu-22.04 # this must be a specific version for the apt install below
env:
# Oldest versions currently supported by TinyGo
LLVM: "15"
Go: "1.24" # when updating this, also update minorMin in builder/config.go
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: ${{ env.Go }}
cache: true
- name: Install LLVM
run: |
echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-${{ env.LLVM }} main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
llvm-${{ env.LLVM }}-dev \
clang-${{ env.LLVM }} \
libclang-${{ env.LLVM }}-dev \
lld-${{ env.LLVM }} \
binaryen
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-20-linux-compat
path: llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v5
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
path: llvm-project/compiler-rt
- name: Go cache
uses: actions/cache@v5
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-build-${{ env.Go }}-llvm${{ env.LLVM }}-${{ hashFiles('go.sum') }}
- name: Build TinyGo
run: go install -tags=llvm${{ env.LLVM }}
- run: tinygo version
- run: make gen-device -j4
- run: go test -tags=llvm${{ env.LLVM }} -short -skip=TestErrors
- run: make smoketest XTENSA=0
+23 -3
View File
@@ -12,6 +12,24 @@ concurrency:
cancel-in-progress: true
jobs:
go-mod-tidy:
# Check that go.sum is up to date.
runs-on: ubuntu-slim
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.4'
cache: true
- name: Run go mod tidy
run: go mod tidy
- name: Check go.mod and go.sum are up to date
run: git diff --exit-code
build-linux:
# Build Linux binaries, ready for release.
# This runs inside an Alpine Linux container so we can more easily create a
@@ -142,7 +160,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.2'
go-version: '1.26.4'
cache: true
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
@@ -186,7 +204,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.2'
go-version: '1.26.4'
cache: true
- name: Install Node.js
uses: actions/setup-node@v6
@@ -266,6 +284,8 @@ jobs:
- run: make smoketest
- run: make wasmtest
- run: make tinygo-test-baremetal
- name: Check Go code formatting
run: make fmt-check lint
build-linux-cross:
# Build ARM Linux binaries, ready for release.
# This intentionally uses an older Linux image, so that we compile against
@@ -303,7 +323,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.2'
go-version: '1.26.4'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
+7 -31
View File
@@ -17,12 +17,6 @@ jobs:
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies
shell: bash
@@ -40,13 +34,13 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.2'
go-version: '1.26.4'
cache: true
- name: Restore cached LLVM source
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-20-windows-v1
key: llvm-source-20-windows-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -71,7 +65,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-20-windows-v2
key: llvm-build-20-windows-v4
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -93,7 +87,7 @@ jobs:
- name: Cache Go cache
uses: actions/cache@v5
with:
key: go-cache-windows-v2-${{ hashFiles('go.mod') }}
key: go-cache-windows-v3-${{ hashFiles('go.mod') }}
path: |
C:/Users/runneradmin/AppData/Local/go-build
C:/Users/runneradmin/go/pkg/mod
@@ -124,12 +118,6 @@ jobs:
runs-on: windows-2022
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies
shell: bash
@@ -141,7 +129,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.2'
go-version: '1.26.4'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
@@ -157,18 +145,12 @@ jobs:
runs-on: windows-2022
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- name: Checkout
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.2'
go-version: '1.26.4'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
@@ -183,12 +165,6 @@ jobs:
runs-on: windows-2022
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies
shell: bash
@@ -200,7 +176,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.2'
go-version: '1.26.4'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
+30 -10
View File
@@ -194,6 +194,12 @@ NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm lld $(addprefix lib/lib,
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
CGO_CPPFLAGS+=$(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++17
ifneq ($(uname),Windows_NT)
# Disable GCC DWARF compression: lld built without zlib cannot link
# object files with ELFCOMPRESS_ZLIB debug sections.
CGO_CFLAGS+=-gz=none
CGO_CXXFLAGS+=-gz=none
endif
CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
endif
@@ -310,7 +316,7 @@ check-nodejs-version:
tinygo: ## Build the TinyGo compiler
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" .
test: check-nodejs-version
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS)
@@ -379,22 +385,22 @@ TEST_PACKAGES_FAST = \
# archive/zip requires os.ReadAt, which is not yet supported on windows
# bytes requires mmap
# compress/flate appears to hang on wasi
# crypto/aes fails on wasi, needs panic()/recover()
# crypto/aes needs reflect.Type.Method(), not yet implemented
# crypto/des fails on wasi, needs panic()/recover()
# crypto/hmac fails on wasi, it exits with a "slice out of range" panic
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
# encoding/xml takes a minute on linux and gives a stack overflow on wasi
# image requires recover(), which is not yet supported on wasi
# image fails on wasi, needs panic()/recover()
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
# mime: fail on wasi; neds panic()/recover()
# mime: fails on wasi, needs panic()/recover()
# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK
# mime/quotedprintable requires syscall.Faccessat
# net/mail: needs wasip1 syscall.FDFLAG_NONBLOCK
# net/ntextproto: needs wasip1 syscall.FDFLAG_NONBLOCK
# regexp/syntax: fails on wasip1; needs panic()/recover()
# strconv requires recover() which is not yet supported on wasi
# text/tabwriter requires recover(), which is not yet supported on wasi
# text/template/parse requires recover(), which is not yet supported on wasi
# regexp/syntax: fails on wasip1, needs panic()/recover()
# strconv: fails on wasi, needs panic()/recover()
# text/tabwriter: fails on wasi, needs panic()/recover()
# text/template/parse: fails on wasi, needs panic()/recover()
# testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi
# Additional standard library packages that pass tests on individual platforms
@@ -419,6 +425,7 @@ TEST_PACKAGES_LINUX := \
os/user \
regexp/syntax \
strconv \
testing/fstest \
text/tabwriter \
text/template/parse
@@ -429,7 +436,11 @@ TEST_PACKAGES_WINDOWS := \
compress/flate \
crypto/des \
crypto/hmac \
image \
mime \
regexp/syntax \
strconv \
text/tabwriter \
text/template/parse \
$(nil)
@@ -778,6 +789,8 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico -gc=leaking examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
@@ -947,6 +960,11 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest
@$(MD5SUM) test.bin
# xiao-esp32c6
$(TINYGO) build -size short -o test.bin -target=xiao-esp32c6 examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32c6 examples/blinkm
@$(MD5SUM) test.bin
# xiao-esp32s3
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1
@$(MD5SUM) test.bin
@@ -967,6 +985,8 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/adc
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-box-3 examples/blinky1
@$(MD5SUM) test.bin
endif
# esp32c3-supermini
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1
@@ -1163,8 +1183,8 @@ endif
@cp -rp lib/wasi-libc/libc-top-half/musl/src/unistd build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/sources build/release/tinygo/lib/wasi-libc/libc-top-half
@cp -rp lib/wasi-cli/wit build/release/tinygo/lib/wasi-cli/wit
@cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins
@cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins
@cp -rp ${LLVM_PROJECTDIR}/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins
@cp -rp ${LLVM_PROJECTDIR}/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins
@cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets
+15 -1
View File
@@ -1076,7 +1076,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil {
return result, err
}
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266":
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp32c6", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
result.Binary = filepath.Join(tmpdir, "main"+outext)
@@ -1352,6 +1352,14 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
}
baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize()
// Account for the bytes that tinygo_swapTask pushes onto the goroutine stack
// on every context switch. The static analysis correctly traces Go calls,
// but it cannot see into the assembly-level register push.
var contextSwitchOverhead uint64
if swapFuncs, ok := functions["tinygo_swapTask"]; ok && len(swapFuncs) == 1 {
contextSwitchOverhead = swapFuncs[0].FrameSize
}
sizes := make(map[string]functionStackSize)
// Add the reset handler function, for convenience. The reset handler runs
@@ -1400,6 +1408,12 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
// overflow will occur even before the goroutine is started.
stackSize = baseStackSize
}
if stackSizeType == stacksize.Bounded {
// Add the overhead of context switching. This is needed because the
// context switch (tinygo_swapTask) pushes callee-saved registers
// onto the current stack, which is not seen by the static analysis.
stackSize += contextSwitchOverhead
}
sizes[name] = functionStackSize{
stackSize: stackSize,
stackSizeType: stackSizeType,
+1
View File
@@ -28,6 +28,7 @@ func TestClangAttributes(t *testing.T) {
"cortex-m4",
"cortex-m7",
"esp32c3",
"esp32c6",
"esp32s3",
"fe310",
"gameboy-advance",
+1 -1
View File
@@ -25,7 +25,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
}
// Version range supported by TinyGo.
const minorMin = 19
const minorMin = 24 // when updating the min version, also update .github/workflows/compat.yml
const minorMax = 26
// Check that we support this Go toolchain version.
+15 -3
View File
@@ -100,12 +100,24 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
chip_id := map[string]uint16{
"esp32": 0x0000,
"esp32c3": 0x0005,
"esp32c6": 0x000d,
"esp32s3": 0x0009,
}[chip]
// SPI flash speed/size byte (byte 3 of header):
// Upper nibble = flash size, lower nibble = flash frequency.
// The espflasher auto-detects and patches the flash size (upper nibble),
// but the frequency (lower nibble) must be correct per chip.
spiSpeedSize := map[string]uint8{
"esp32": 0x1f, // 80MHz=0x0F, 2MB=0x10
"esp32c3": 0x1f, // 80MHz=0x0F, 2MB=0x10
"esp32c6": 0x10, // 80MHz=0x00, 2MB=0x10 (C6 uses different freq encoding)
"esp32s3": 0x1f, // 80MHz=0x0F, 2MB=0x10
}[chip]
// Image header.
switch chip {
case "esp32", "esp32c3", "esp32s3":
case "esp32", "esp32c3", "esp32s3", "esp32c6":
// Header format:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
// Note: not adding a SHA256 hash as the binary is modified by
@@ -126,8 +138,8 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
}{
magic: 0xE9,
segment_count: byte(len(segments)),
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
spi_speed_size: spiSpeedSize,
entry_addr: uint32(inf.Entry),
wp_pin: 0xEE, // disable WP pin
chip_id: chip_id,
+1 -1
View File
@@ -218,7 +218,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
return err
}
// Store this archive in the cache.
return os.Rename(f.Name(), archiveFilePath)
return robustRename(f.Name(), archiveFilePath)
},
}
+9
View File
@@ -0,0 +1,9 @@
//go:build !windows
package builder
import "os"
func robustRename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}
+44
View File
@@ -0,0 +1,44 @@
package builder
import (
"errors"
"math/rand"
"os"
"syscall"
"time"
)
const robustRenameTimeout = 2 * time.Second
func robustRename(oldpath, newpath string) error {
var bestErr error
start := time.Now()
nextSleep := time.Millisecond
for {
err := os.Rename(oldpath, newpath)
if err == nil || !isEphemeralRenameError(err) {
return err
}
if bestErr == nil {
bestErr = err
}
if d := time.Since(start) + nextSleep; d >= robustRenameTimeout {
return bestErr
}
time.Sleep(nextSleep)
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
}
}
func isEphemeralRenameError(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
switch errno {
case syscall.Errno(2), // ERROR_FILE_NOT_FOUND
syscall.Errno(5), // ERROR_ACCESS_DENIED
syscall.Errno(32): // ERROR_SHARING_VIOLATION
return true
}
}
return false
}
+8 -2
View File
@@ -501,8 +501,9 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
Align: section.Addralign,
Type: memoryStack,
})
} else {
// Regular .bss section.
} else if section.Flags&elf.SHF_WRITE != 0 {
// Regular .bss section. Zero-initialized RAM is always
// writable, so require SHF_WRITE here.
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
@@ -510,6 +511,11 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
Type: memoryBSS,
})
}
// Other (non-writable) SHT_NOBITS sections are address-space
// placeholders that occupy no RAM, such as the ESP linker
// script's .irom_dummy / .rodata_dummy sections which reserve
// the flash-mapped XIP virtual address ranges. They must not be
// counted as bss/RAM usage.
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 {
// .text
sections = append(sections, memorySection{
+4 -4
View File
@@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 3680, 280, 0, 2252},
{"microbit", "examples/serial", 2694, 342, 8, 2248},
{"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248},
{"hifive1b", "examples/echo", 3699, 297, 0, 2252},
{"microbit", "examples/serial", 2736, 356, 8, 2248},
{"wioterminal", "examples/pininterrupt", 7960, 1652, 132, 7480},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
@@ -99,7 +99,7 @@ func TestSizeFull(t *testing.T) {
t.Fatal("could not read program size:", err)
}
for _, pkg := range sizes.sortedPackageNames() {
if pkg == "(padding)" || pkg == "(unknown)" {
if pkg == "(padding)" || pkg == "(unknown)" || pkg == "Go types" {
// TODO: correctly attribute all unknown binary size.
continue
}
+4 -2
View File
@@ -121,9 +121,11 @@ func parseLLDErrors(text string) error {
if matches != nil {
parsedError = true
line, _ := strconv.Atoi(matches[3])
// TODO: detect common mistakes like -gc=none?
msg := "linker could not find symbol " + symbolName
if symbolName == "runtime.alloc_noheap" {
switch symbolName {
case "runtime.alloc":
msg = "object allocated on the heap with -gc=none"
case "runtime.alloc_noheap":
msg = "object allocated on the heap in //go:noheap function"
}
linkErrors = append(linkErrors, scanner.Error{
-22
View File
@@ -26,10 +26,6 @@ import (
"golang.org/x/tools/go/ast/astutil"
)
// Function that's only defined in Go 1.22.
var setASTFileFields = func(f *ast.File, start, end token.Pos) {
}
// cgoPackage holds all CGo-related information of a package.
type cgoPackage struct {
generated *ast.File
@@ -654,7 +650,6 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
X: &ast.Ident{
NamePos: pos,
Name: "union",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: pos,
@@ -708,7 +703,6 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
X: &ast.Ident{
NamePos: pos,
Name: typeName,
Obj: nil,
},
},
},
@@ -764,7 +758,6 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
@@ -811,11 +804,6 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
},
},
Type: &ast.StarExpr{
@@ -823,7 +811,6 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
@@ -881,7 +868,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
@@ -964,11 +950,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
},
},
Type: &ast.StarExpr{
@@ -976,7 +957,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
@@ -997,7 +977,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
{
NamePos: bitfield.pos,
Name: "value",
Obj: nil,
},
},
Type: bitfield.field.Type,
@@ -1015,7 +994,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
-17
View File
@@ -1,17 +0,0 @@
//go:build go1.22
package cgo
// Code specifically for Go 1.22.
import (
"go/ast"
"go/token"
)
func init() {
setASTFileFields = func(f *ast.File, start, end token.Pos) {
f.FileStart = start
f.FileEnd = end
}
}
+2 -60
View File
@@ -217,10 +217,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
case C.CXCursor_FunctionDecl:
cursorType := C.tinygo_clang_getCursorType(c)
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
obj := &ast.Object{
Kind: ast.Fun,
Name: "_Cgo_" + name,
}
exportName := name
localName := name
var stringSignature string
@@ -258,7 +254,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Name: &ast.Ident{
NamePos: pos,
Name: "_Cgo_" + localName,
Obj: obj,
},
Type: &ast.FuncType{
Func: pos,
@@ -295,11 +290,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
{
NamePos: pos,
Name: argName,
Obj: &ast.Object{
Kind: ast.Var,
Name: argName,
Decl: decl,
},
},
},
Type: f.makeDecayingASTType(argType, pos),
@@ -315,7 +305,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
},
}
}
obj.Decl = decl
return decl, stringSignature
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
typ := f.makeASTRecordType(c, pos)
@@ -325,39 +314,27 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
// Convert to a single-field struct type.
typeExpr = f.makeUnionField(typ)
}
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: typ.pos,
Name: typeName,
Obj: obj,
},
Type: typeExpr,
}
obj.Decl = typeSpec
return typeSpec, typ
case C.CXCursor_TypedefDecl:
typeName := "_Cgo_" + name
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: pos,
Name: typeName,
Obj: obj,
},
Type: f.makeASTType(underlyingType, pos),
}
if underlyingType.kind != C.CXType_Enum {
typeSpec.Assign = pos
}
obj.Decl = typeSpec
return typeSpec, nil
case C.CXCursor_VarDecl:
cursorType := C.tinygo_clang_getCursorType(c)
@@ -376,19 +353,13 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
},
},
}
obj := &ast.Object{
Kind: ast.Var,
Name: "_Cgo_" + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "_Cgo_" + name,
Obj: obj,
}},
Type: typeExpr,
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
case C.CXCursor_MacroDefinition:
@@ -405,26 +376,16 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Lparen: token.NoPos,
Rparen: token.NoPos,
}
obj := &ast.Object{
Kind: ast.Con,
Name: "_Cgo_" + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "_Cgo_" + name,
Obj: obj,
}},
Values: []ast.Expr{expr},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
case C.CXCursor_EnumDecl:
obj := &ast.Object{
Kind: ast.Typ,
Name: "_Cgo_" + name,
}
underlying := C.tinygo_clang_getEnumDeclIntegerType(c)
// TODO: gc's CGo implementation uses types such as `uint32` for enums
// instead of types such as C.int, which are used here.
@@ -432,12 +393,10 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Name: &ast.Ident{
NamePos: pos,
Name: "_Cgo_" + name,
Obj: obj,
},
Assign: pos,
Type: f.makeASTType(underlying, pos),
}
obj.Decl = typeSpec
return typeSpec, nil
case C.CXCursor_EnumConstantDecl:
value := C.tinygo_clang_getEnumConstantDeclValue(c)
@@ -452,19 +411,13 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Lparen: token.NoPos,
Rparen: token.NoPos,
}
obj := &ast.Object{
Kind: ast.Con,
Name: "_Cgo_" + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "_Cgo_" + name,
Obj: obj,
}},
Values: []ast.Expr{expr},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
default:
@@ -639,7 +592,8 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
Package: f.Pos(0),
Name: ast.NewIdent(p.packageName),
}
setASTFileFields(astFile, f.Pos(0), f.Pos(int(size)))
astFile.FileStart = f.Pos(0)
astFile.FileEnd = f.Pos(int(size))
p.cgoFiles = append(p.cgoFiles, astFile)
}
positionFile := p.tokenFiles[filename]
@@ -956,22 +910,16 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
}
// Construct an *ast.TypeSpec for this type.
obj := &ast.Object{
Kind: ast.Typ,
Name: name,
}
spec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: pos,
Name: name,
Obj: obj,
},
Type: &ast.Ident{
NamePos: pos,
Name: goName,
},
}
obj.Decl = spec
return spec
}
@@ -1104,7 +1052,6 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
pos: prevField.Names[0].NamePos,
})
prevField.Names[0].Name = bitfieldName
prevField.Names[0].Obj.Name = bitfieldName
}
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
prevBitfield.endBit = bitfieldOffset
@@ -1121,11 +1068,6 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
{
NamePos: pos,
Name: name,
Obj: &ast.Object{
Kind: ast.Var,
Name: name,
Decl: field,
},
},
}
fieldList.List = append(fieldList.List, field)
+1 -1
View File
@@ -539,7 +539,7 @@ func (c *Config) Programmer() (method, openocdInterface string) {
case "openocd", "msd", "command", "adb":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface
case "bmp":
case "bmp", "probe-rs":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, ""
default:
+1
View File
@@ -47,6 +47,7 @@ type Options struct {
Nobounds bool
PrintSizes string
PrintAllocs *regexp.Regexp // regexp string
PrintAllocsCover bool // emit allocs in go coverage tool format
PrintStacks bool
Tags []string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
+3
View File
@@ -67,6 +67,7 @@ type TargetSpec struct {
ADBPreCommands []string `json:"adb-pre-commands,omitempty"`
ADBPushRemote string `json:"adb-push-remote,omitempty"`
ADBPostCommands []string `json:"adb-post-commands,omitempty"`
ProbeRSChip string `json:"probe-rs-chip,omitempty"`
CodeModel string `json:"code-model,omitempty"`
RelocationModel string `json:"relocation-model,omitempty"`
WITPackage string `json:"wit-package,omitempty"`
@@ -484,6 +485,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
"--no-insert-timestamp",
"--no-dynamicbase",
)
spec.ExtraFiles = append(spec.ExtraFiles,
"src/runtime/runtime_windows.c")
case "wasm", "wasip1", "wasip2":
return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS)
default:
+21 -8
View File
@@ -241,24 +241,37 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
}
}
// Put the fault block at the end of the function and the next block at the
// current insert position.
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
faultBlock := b.getRuntimeAssertBlock(blockPrefix, assertFunc)
nextBlock := b.insertBasicBlock(blockPrefix + ".next")
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block.
b.CreateCondBr(assert, faultBlock, nextBlock)
// Fail: the assert triggered so panic.
b.SetInsertPointAtEnd(faultBlock)
b.createRuntimeCall(assertFunc, nil, "")
b.CreateUnreachable()
// Ok: assert didn't trigger so continue normally.
b.SetInsertPointAtEnd(nextBlock)
}
func (b *builder) getRuntimeAssertBlock(blockPrefix, assertFunc string) llvm.BasicBlock {
if b.runtimeAssertBlocks == nil {
b.runtimeAssertBlocks = make(map[string]llvm.BasicBlock)
}
if block := b.runtimeAssertBlocks[assertFunc]; !block.IsNil() {
return block
}
savedBlock := b.GetInsertBlock()
block := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
b.runtimeAssertBlocks[assertFunc] = block
b.SetInsertPointAtEnd(block)
if b.hasDeferFrame() {
b.createFaultCheckpoint()
}
b.createRuntimeCall(assertFunc, nil, "")
b.CreateUnreachable()
b.SetInsertPointAtEnd(savedBlock)
return block
}
// extendInteger extends the value to at least targetType using a zero or sign
// extend. The resulting value is not truncated: it may still be bigger than
// targetType.
+2 -2
View File
@@ -48,7 +48,7 @@ func (b *builder) createChanSend(instr *ssa.Send) {
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
// End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8:
@@ -101,7 +101,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
// createChanClose closes the given channel.
func (b *builder) createChanClose(ch llvm.Value) {
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
b.createRuntimeInvoke("chanClose", []llvm.Value{ch}, "")
}
// createSelect emits all IR necessary for a select statements. That's a
+27 -15
View File
@@ -92,6 +92,7 @@ type compilerContext struct {
pkg *types.Package
packageDir string // directory for this package
runtimePkg *types.Package
localTypeNames typeutil.Map // *types.Named (synthetic local from generic instantiation) -> string
}
// newCompilerContext returns a new compiler context ready for use, most
@@ -176,6 +177,9 @@ type builder struct {
deferBuiltinFuncs map[ssa.Value]deferBuiltin
runDefersBlock []llvm.BasicBlock
afterDefersBlock []llvm.BasicBlock
runtimeAssertBlocks map[string]llvm.BasicBlock
interfaceAssertBlock llvm.BasicBlock
}
func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *builder {
@@ -192,16 +196,6 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu
}
}
// Return the runtime.alloc function variant.
// This is normally just "alloc", but is "alloc_noheap" if the //go:noheap
// pragma is used.
func (b *builder) allocFunc() string {
if b.info.noheap {
return "alloc_noheap"
}
return "alloc"
}
type blockInfo struct {
// entry is the LLVM basic block corresponding to the start of this *ssa.Block.
entry llvm.BasicBlock
@@ -310,6 +304,11 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
// Convert AST to SSA.
ssaPkg.Build()
// Assign names to function-local named types before compiling the
// package, so that types declared in different functions (or in
// different instantiations of a generic function) do not collide.
c.scanLocalTypes(ssaPkg)
// Initialize debug information.
if c.Debug {
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
@@ -869,10 +868,9 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
}
// Create the function definition.
b := newBuilder(c, irbuilder, member)
if _, ok := mathToLLVMMapping[member.RelString(nil)]; ok {
if ok := b.defineMathOp(); ok {
// The body of this function (if there is one) is ignored and
// replaced with a LLVM intrinsic call.
b.defineMathOp()
continue
}
if ok := b.defineMathBitsIntrinsic(); ok {
@@ -1356,6 +1354,15 @@ func (b *builder) createFunctionStart(intrinsic bool) {
// diagnostic.
func (b *builder) createFunction() {
b.createFunctionStart(false)
// createFunctionStart returns early (leaving blockInfo unset) when it hits
// an error such as a redeclared symbol. Don't plow into the block loop and
// panic on an empty blockInfo — the error was already recorded.
if b.blockInfo == nil {
// TINYGO-DEBUG: surface the redeclaration collision.
fmt.Printf("TINYGO-DEBUG skipping fn (redeclared/errored): %s pkg=%s\n",
b.fn.RelString(nil), b.fn.Pkg.Pkg.Path())
return
}
// Fill blocks with instructions.
for _, block := range b.fn.DomPreorder() {
@@ -1895,6 +1902,12 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
// not of the current function.
useParentFrame = 1
}
// Prevent inlining of functions that call recover(), matching the
// Go compiler's behavior. If this function were inlined into a
// deferred function, recover() would incorrectly succeed because
// the inlined code runs in the deferred function's context.
noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
b.llvmFn.AddFunctionAttr(noinline)
return b.createRuntimeCall("_recover", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), useParentFrame, false)}, ""), nil
case "ssa:wrapnilchk":
// TODO: do an actual nil check?
@@ -2183,9 +2196,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
}
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
layoutValue := b.createObjectLayout(typ, expr.Pos())
buf := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, layoutValue}, expr.Comment)
align := b.targetData.ABITypeAlignment(typ)
buf.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
buf := b.createAlloc(sizeValue, layoutValue, align, expr.Comment)
return buf, nil
} else {
buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment)
@@ -2415,7 +2427,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
}
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
slicePtr := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
slicePtr := b.createAlloc(sliceSize, layoutValue, 0, "makeslice.buf")
slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign)))
// Extend or truncate if necessary. This is safe as we've already done
+22 -5
View File
@@ -60,10 +60,6 @@ func (b *builder) deferInitFunc() {
b.deferExprFuncs = make(map[ssa.Value]int)
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
// Create defer list pointer.
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr)
if b.hasDeferFrame() {
// Set up the defer frame with the current stack pointer.
// This assumes that the stack pointer doesn't move outside of the
@@ -73,12 +69,22 @@ func (b *builder) deferInitFunc() {
// in the setjmp-like inline assembly.
deferFrameType := b.getLLVMRuntimeType("deferFrame")
b.deferFrame = b.CreateAlloca(deferFrameType, "deferframe.buf")
// The field index must match the DeferPtr field in runtime.deferFrame,
// defined in src/runtime/panic.go.
b.deferPtr = b.CreateInBoundsGEP(deferFrameType, b.deferFrame, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 6, false), // DeferPtr field
}, "deferPtr")
stackPointer := b.readStackPointer()
b.createRuntimeCall("setupDeferFrame", []llvm.Value{b.deferFrame, stackPointer}, "")
// Create the landing pad block, which is where control transfers after
// a panic.
b.landingpad = b.ctx.AddBasicBlock(b.llvmFn, "lpad")
} else {
// Create defer list pointer.
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr)
}
}
@@ -237,6 +243,17 @@ func (b *builder) createInvokeCheckpoint() {
b.currentBlockInfo.exit = continueBB
}
// createFaultCheckpoint is like createInvokeCheckpoint but for use in fault
// blocks (e.g., bounds check failures). Unlike createInvokeCheckpoint, it does
// not update currentBlockInfo.exit because the fault block is a dead-end that
// does not participate in phi node resolution.
func (b *builder) createFaultCheckpoint() {
isZero := b.createCheckpoint(b.deferFrame)
continueBB := b.insertBasicBlock("")
b.CreateCondBr(isZero, continueBB, b.landingpad)
b.SetInsertPointAtEnd(continueBB)
}
// isInLoop checks if there is a path from the current block to itself.
// Use Tarjan's strongly connected components algorithm to search for cycles.
// A one-node SCC is a cycle iff there is an edge from the node to itself.
@@ -488,7 +505,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
size := b.targetData.TypeAllocSize(deferredCallType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
nilPtr := llvm.ConstNull(b.dataPtrType)
alloca = b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
alloca = b.createAlloc(sizeValue, nilPtr, 0, "defer.alloc.call")
}
if b.NeedsStackObjects {
b.trackPointer(alloca)
+26
View File
@@ -10,6 +10,32 @@ import (
"tinygo.org/x/go-llvm"
)
// Heap-allocate a buffer of the given size. This will typically call
// runtime.alloc.
func (b *builder) createAlloc(sizeValue, layoutValue llvm.Value, align int, comment string) llvm.Value {
// Normally allocate using "runtime.alloc", but use "runtime.alloc_noheap"
// if the //go:noheap pragma is used.
allocFunc := "alloc"
if b.info.noheap {
allocFunc = "alloc_noheap"
}
// Allocs that don't allocate anything can return an architecture-specific
// sentinel value.
if !sizeValue.IsAConstantInt().IsNil() && sizeValue.ZExtValue() == 0 {
allocFunc = "alloc_zero"
}
// Make the runtime call.
call := b.createRuntimeCall(allocFunc, []llvm.Value{sizeValue, layoutValue}, comment)
if align != 0 {
// TODO: make sure all callsites set the correct alignment.
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
}
return call
}
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
// one of the expressions that need this.
func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) {
+328 -46
View File
@@ -10,6 +10,7 @@ import (
"fmt"
"go/token"
"go/types"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -165,7 +166,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}
}
typeCodeName, isLocal := getTypeCodeName(typ)
typeCodeName, isLocal := c.getTypeCodeName(typ)
globalName := "reflect/types.type:" + typeCodeName
var global llvm.Value
if isLocal {
@@ -580,20 +581,50 @@ var basicTypeNames = [...]string{
// getTypeCodeName returns a name for this type that can be used in the
// interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum.
func getTypeCodeName(t types.Type) (string, bool) {
//
// isLocal is true when the type is declared inside a function body.
// Such types need a per-declaration (or per instantiation) suffix
// because their printed names are not unique.
//
// Ordinary function-local types (TypeName.Parent() != nil) are
// disambiguated lazily from their declaration position: every
// declaration in the package has a distinct (file, line, column)
// triple, and the position is taken un-//line-adjusted so it is
// stable across builds. Such types are only nameable inside their
// declaring package, so the name does not need to agree with anything
// computed in another package.
//
// Synthetic locals (TypeName.Parent() == nil), produced by generic
// instantiation, are pre-registered by scanLocalTypes because their
// names must agree across packages that materialize the same instance.
func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bool) {
switch t := types.Unalias(t).(type) {
case *types.Named:
if t.Obj().Parent() != t.Obj().Pkg().Scope() {
return "named:" + t.String() + "$local", true
tn := t.Obj()
if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() {
// Package-scope or builtin: the printed name is unique.
return "named:" + t.String(), false
}
return "named:" + t.String(), false
if tn.Parent() != nil {
// Ordinary function-local type. Use the un-//line-adjusted
// declaration position as the disambiguator.
pos := c.program.Fset.PositionFor(tn.Pos(), false)
return fmt.Sprintf("named:%s$%s:%d:%d", t.String(), filepath.Base(pos.Filename), pos.Line, pos.Column), true
}
// Synthetic local from generic instantiation: must have been
// pre-registered by scanLocalTypes.
v := c.localTypeNames.At(t)
if v == nil {
panic("compiler: synthetic local type " + tn.Name() + " was not registered by scanLocalTypes")
}
return "named:" + v.(string), true
case *types.Array:
s, isLocal := getTypeCodeName(t.Elem())
s, isLocal := c.getTypeCodeName(t.Elem())
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal
case *types.Basic:
return "basic:" + basicTypeNames[t.Kind()], false
case *types.Chan:
s, isLocal := getTypeCodeName(t.Elem())
s, isLocal := c.getTypeCodeName(t.Elem())
var dir string
switch t.Dir() {
case types.SendOnly:
@@ -613,7 +644,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
if !token.IsExported(name) {
name = t.Method(i).Pkg().Path() + "." + name
}
s, local := getTypeCodeName(t.Method(i).Type())
s, local := c.getTypeCodeName(t.Method(i).Type())
if local {
isLocal = true
}
@@ -621,17 +652,17 @@ func getTypeCodeName(t types.Type) (string, bool) {
}
return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal
case *types.Map:
keyType, keyLocal := getTypeCodeName(t.Key())
elemType, elemLocal := getTypeCodeName(t.Elem())
keyType, keyLocal := c.getTypeCodeName(t.Key())
elemType, elemLocal := c.getTypeCodeName(t.Elem())
return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal
case *types.Pointer:
s, isLocal := getTypeCodeName(t.Elem())
s, isLocal := c.getTypeCodeName(t.Elem())
return "pointer:" + s, isLocal
case *types.Signature:
isLocal := false
params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ {
s, local := getTypeCodeName(t.Params().At(i).Type())
s, local := c.getTypeCodeName(t.Params().At(i).Type())
if local {
isLocal = true
}
@@ -639,7 +670,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
}
results := make([]string, t.Results().Len())
for i := 0; i < t.Results().Len(); i++ {
s, local := getTypeCodeName(t.Results().At(i).Type())
s, local := c.getTypeCodeName(t.Results().At(i).Type())
if local {
isLocal = true
}
@@ -647,7 +678,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
}
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}", isLocal
case *types.Slice:
s, isLocal := getTypeCodeName(t.Elem())
s, isLocal := c.getTypeCodeName(t.Elem())
return "slice:" + s, isLocal
case *types.Struct:
elems := make([]string, t.NumFields())
@@ -657,7 +688,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
if t.Field(i).Embedded() {
embedded = "#"
}
s, local := getTypeCodeName(t.Field(i).Type())
s, local := c.getTypeCodeName(t.Field(i).Type())
if local {
isLocal = true
}
@@ -672,6 +703,232 @@ func getTypeCodeName(t types.Type) (string, bool) {
}
}
// scanLocalTypes assigns names to every synthetic *types.TypeName
// (TypeName.Parent() == nil) reachable from this package and stores
// them in c.localTypeNames.
//
// Synthetic TypeNames are produced by generic instantiation: two
// instantiations of the same generic function (e.g. F[int] and
// F[string]) produce TypeNames with the same printed name and the
// same source position, so each is named with the enclosing
// instance's RelString as prefix. RelString encodes the type
// arguments, matching Go's runtime behavior, where F[int].Inner and
// F[string].Inner are distinct types even when Inner does not mention
// the type parameter.
//
// A given instance may be materialized by several packages (the body
// of F[int] is compiled in every package that calls F[int]); its
// reflect/types.type:* global has LinkOnceODRLinkage and is merged by
// name at link time. The chosen name therefore depends only on
// intrinsic SSA properties (RelString and the raw token.Pos used as a
// sort key), so any package compiling the same instance produces the
// same identifier.
//
// Ordinary function-local TypeNames (TypeName.Parent() != nil) are
// not handled here: they are nameable only inside their declaring
// package, and getTypeCodeName derives a stable per-declaration name
// for them directly from their source position.
func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
// Locate every generic instance reachable from this package
// (including instances declared in imported packages and any
// function reached through an instance subtree).
var instances []*ssa.Function
seen := map[*ssa.Function]struct{}{}
var walk func(fn *ssa.Function, inInstance bool)
walk = func(fn *ssa.Function, inInstance bool) {
if fn == nil {
return
}
if _, ok := seen[fn]; ok {
return
}
// fn belongs to an instance subtree if it is itself an
// instantiation or if we reached it from one.
//
// len(TypeArgs()) is used instead of fn.Origin() because
// Origin() may call Build() on fn's declaring package, which
// would defeat per-package compilation.
isInstanceRoot := len(fn.TypeArgs()) > 0
if !isInstanceRoot && !inInstance && fn.Pkg != ssaPkg {
return
}
if fn.Blocks == nil && fn.AnonFuncs == nil {
return
}
seen[fn] = struct{}{}
isInInstance := inInstance || isInstanceRoot
if isInInstance {
instances = append(instances, fn)
}
for _, anon := range fn.AnonFuncs {
walk(anon, isInInstance)
}
var ops [10]*ssa.Value
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
for _, op := range instr.Operands(ops[:0]) {
if op == nil || *op == nil {
continue
}
if callee, ok := (*op).(*ssa.Function); ok {
walk(callee, isInInstance)
}
}
}
}
}
for _, member := range ssaPkg.Members {
switch m := member.(type) {
case *ssa.Function:
walk(m, false)
case *ssa.Type:
mset := c.program.MethodSets.MethodSet(m.Type())
for i := 0; i < mset.Len(); i++ {
walk(c.program.MethodValue(mset.At(i)), false)
}
pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type()))
for i := 0; i < pmset.Len(); i++ {
walk(c.program.MethodValue(pmset.At(i)), false)
}
}
}
// Registration is first-writer-wins (a synthetic TypeName may be
// reachable from several instances), so visit instances in a
// deterministic order. Pos() is a defensive tiebreaker.
sort.Slice(instances, func(i, j int) bool {
ri, rj := instances[i].RelString(nil), instances[j].RelString(nil)
if ri != rj {
return ri < rj
}
return instances[i].Pos() < instances[j].Pos()
})
for _, fn := range instances {
c.registerSyntheticLocalTypes(fn)
}
}
// registerSyntheticLocalTypes walks every type reachable from fn's
// body and records each synthetic *types.Named (TypeName.Parent() ==
// nil) in c.localTypeNames. Each is named with fn.RelString as the
// owning function plus a per-function counter assigned in source
// order.
//
// First-writer-wins: a *types.Named already present in
// c.localTypeNames is left alone, so a synthetic type reachable from
// several instances keeps the name assigned by the first (in
// scanLocalTypes' deterministic order).
func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
var found []*types.Named
seen := map[types.Type]struct{}{}
var visit func(t types.Type)
visit = func(t types.Type) {
if t == nil {
return
}
if _, ok := seen[t]; ok {
return
}
seen[t] = struct{}{}
switch t := t.(type) {
case *types.Alias:
visit(types.Unalias(t))
case *types.Named:
tn := t.Obj()
if tn.Pkg() != nil && tn.Parent() == nil {
if c.localTypeNames.At(t) == nil {
// Reserve the slot so later calls within this
// scanLocalTypes invocation skip it; the final
// name is filled in after sorting, before any
// getTypeCodeName lookups happen.
c.localTypeNames.Set(t, "")
found = append(found, t)
}
}
targs := t.TypeArgs()
for i := 0; i < targs.Len(); i++ {
visit(targs.At(i))
}
visit(t.Underlying())
case *types.Pointer:
visit(t.Elem())
case *types.Slice:
visit(t.Elem())
case *types.Array:
visit(t.Elem())
case *types.Chan:
visit(t.Elem())
case *types.Map:
visit(t.Key())
visit(t.Elem())
case *types.Struct:
for i := 0; i < t.NumFields(); i++ {
visit(t.Field(i).Type())
}
case *types.Signature:
if p := t.Params(); p != nil {
for i := 0; i < p.Len(); i++ {
visit(p.At(i).Type())
}
}
if r := t.Results(); r != nil {
for i := 0; i < r.Len(); i++ {
visit(r.At(i).Type())
}
}
case *types.Tuple:
for i := 0; i < t.Len(); i++ {
visit(t.At(i).Type())
}
case *types.Interface:
// A synthetic local type can be reachable only through a
// local interface's method signature, so descend into
// them. getTypeCodeName encodes those signatures into
// the interface's identifier, and the seen map breaks
// cycles formed by methods that mention the interface
// itself.
for i := 0; i < t.NumMethods(); i++ {
visit(t.Method(i).Type())
}
}
}
for _, p := range fn.Params {
visit(p.Type())
}
for _, fv := range fn.FreeVars {
visit(fv.Type())
}
for _, l := range fn.Locals {
visit(l.Type())
}
var ops [10]*ssa.Value
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
if v, ok := instr.(ssa.Value); ok {
visit(v.Type())
}
for _, op := range instr.Operands(ops[:0]) {
if op != nil && *op != nil {
visit((*op).Type())
}
}
}
}
if len(found) == 0 {
return
}
// Sort by raw token.Pos: this gives a total order on declarations
// that is stable across builds and unaffected by //line directives
// (which only adjust the human-facing position from Fset.Position).
sort.Slice(found, func(i, j int) bool {
return found[i].Obj().Pos() < found[j].Obj().Pos()
})
enclosing := fn.RelString(nil)
for i, named := range found {
c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i))
}
}
// getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass.
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
@@ -769,7 +1026,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk = b.createInterfaceTypeAssert(intf, actualTypeNum)
}
} else {
name, _ := getTypeCodeName(expr.AssertedType)
name, _ := b.getTypeCodeName(expr.AssertedType)
globalName := "reflect/types.typeid:" + name
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() {
@@ -796,43 +1053,68 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
prevBlock := b.GetInsertBlock()
okBlock := b.insertBasicBlock("typeassert.ok")
nextBlock := b.insertBasicBlock("typeassert.next")
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was
// successful.
b.SetInsertPointAtEnd(okBlock)
var valueOk llvm.Value
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type. Easy: just return the same
// interface value.
valueOk = itf
} else {
// Type assert on concrete type. Extract the underlying type from
// the interface (but only after checking it matches).
valueOk = b.extractValueFromInterface(itf, assertedType)
}
b.CreateBr(nextBlock)
// Continue after the if statement.
b.SetInsertPointAtEnd(nextBlock)
phi := b.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
if expr.CommaOk {
nextBlock := b.insertBasicBlock("typeassert.next")
b.currentBlockInfo.exit = nextBlock
b.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was
// successful.
b.SetInsertPointAtEnd(okBlock)
var valueOk llvm.Value
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type. Easy: just return the same
// interface value.
valueOk = itf
} else {
// Type assert on concrete type. Extract the underlying type from
// the interface (but only after checking it matches).
valueOk = b.extractValueFromInterface(itf, assertedType)
}
b.CreateBr(nextBlock)
// Continue after the if statement.
b.SetInsertPointAtEnd(nextBlock)
phi := b.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple
tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
return tuple
} else {
// This is kind of dirty as the branch above becomes mostly useless,
// but hopefully this gets optimized away.
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
return phi
// Type assert without comma-ok. If it fails, panic.
faultBlock := b.getInterfaceAssertBlock()
b.currentBlockInfo.exit = okBlock
b.CreateCondBr(commaOk, okBlock, faultBlock)
// OK: extract the value from the interface.
b.SetInsertPointAtEnd(okBlock)
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
return itf
}
return b.extractValueFromInterface(itf, assertedType)
}
}
func (b *builder) getInterfaceAssertBlock() llvm.BasicBlock {
if !b.interfaceAssertBlock.IsNil() {
return b.interfaceAssertBlock
}
savedBlock := b.GetInsertBlock()
block := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.throw")
b.interfaceAssertBlock = block
b.SetInsertPointAtEnd(block)
if b.hasDeferFrame() {
b.createFaultCheckpoint()
}
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), 0, false)}, "")
b.CreateUnreachable()
b.SetInsertPointAtEnd(savedBlock)
return block
}
// getMethodsString returns a string to be used in the "tinygo-methods" string
// attribute for interface functions.
func (c *compilerContext) getMethodsString(itf *types.Interface) string {
@@ -857,7 +1139,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
if !token.IsExported(name) {
name = method.Pkg().Path() + "." + name
}
s, _ := getTypeCodeName(method.Type())
s, _ := c.getTypeCodeName(method.Type())
globalName := "reflect/types.signature:" + name + ":" + s
value := c.mod.NamedGlobal(globalName)
if value.IsNil() {
@@ -901,7 +1183,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
// thunk is declared, not defined: it will be defined by the interface lowering
// pass.
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
s, _ := getTypeCodeName(instr.Value.Type().Underlying())
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying())
fnName := s + "." + instr.Method.Name() + "$invoke"
llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
@@ -926,7 +1208,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
// by the interface lowering pass as a type-ID comparison chain, avoiding the
// need for runtime.typeImplementsMethodSet at compile time.
func (b *builder) createInterfaceTypeAssert(intf *types.Interface, actualType llvm.Value) llvm.Value {
s, _ := getTypeCodeName(intf)
s, _ := b.getTypeCodeName(intf)
fnName := s + ".$typeassert"
llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
+42 -8
View File
@@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@@ -166,12 +167,22 @@ func (b *builder) createMachineKeepAliveImpl() {
}
var mathToLLVMMapping = map[string]string{
"math.Acos": "llvm.acos.f64",
"math.Asin": "llvm.asin.f64",
"math.Atan": "llvm.atan.f64",
"math.Atan2": "llvm.atan2.f64",
"math.Ceil": "llvm.ceil.f64",
"math.Cos": "llvm.cos.f64",
"math.Cosh": "llvm.cosh.f64",
"math.Exp": "llvm.exp.f64",
"math.Exp2": "llvm.exp2.f64",
"math.Floor": "llvm.floor.f64",
"math.Log": "llvm.log.f64",
"math.Sin": "llvm.sin.f64",
"math.Sinh": "llvm.sinh.f64",
"math.Sqrt": "llvm.sqrt.f64",
"math.Tan": "llvm.tan.f64",
"math.Tanh": "llvm.tanh.f64",
"math.Trunc": "llvm.trunc.f64",
}
@@ -185,18 +196,40 @@ var mathToLLVMMapping = map[string]string{
// float32(math.Sqrt(float64(v))) to a 32-bit floating point operation, which is
// beneficial on architectures where 64-bit floating point operations are (much)
// more expensive than 32-bit ones.
func (b *builder) defineMathOp() {
b.createFunctionStart(true)
llvmName := mathToLLVMMapping[b.fn.RelString(nil)]
if llvmName == "" {
panic("unreachable: unknown math operation") // sanity check
func (b *builder) defineMathOp() bool {
llvmName, ok := mathToLLVMMapping[b.fn.RelString(nil)]
if !ok {
return false
}
if strings.HasSuffix(b.Triple, "-wasi") || llvmutil.Version() < 19 {
// We don't have a real libc for wasip2. Until that is fixed, we need to
// limit math intrinsics on WASI to a subset supported natively in
// WebAssembly.
// Also, since we don't know the specific libc we will target, disallow
// these for all WASI targets.
//
// We also need to limit ourselves to LLVM 19 and above for the extended
// set of math intrinsics, see:
// https://discourse.llvm.org/t/rfc-all-the-math-intrinsics/78294
switch b.fn.Name() {
case "Ceil", "Exp", "Exp2", "Floor", "Log", "Sqrt", "Trunc":
default:
return false
}
}
b.createFunctionStart(true)
llvmFn := b.mod.NamedFunction(llvmName)
if llvmFn.IsNil() {
// The intrinsic doesn't exist yet, so declare it.
// At the moment, all supported intrinsics have the form "double
// foo(double %x)" so we can hardcode the signature here.
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
var llvmType llvm.Type
switch b.fn.Name() {
case "Atan2":
// double atan2(double %y, double %x)
llvmType = llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false)
default:
// double foo(double %x)
llvmType = llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
}
llvmFn = llvm.AddFunction(b.mod, llvmName, llvmType)
}
// Create a call to the intrinsic.
@@ -206,6 +239,7 @@ func (b *builder) defineMathOp() {
}
result := b.CreateCall(llvmFn.GlobalValueType(), llvmFn, args, "")
b.CreateRet(result)
return true
}
func (b *builder) defineCryptoIntrinsic() bool {
+1 -5
View File
@@ -129,11 +129,7 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
align := b.targetData.ABITypeAlignment(packedType)
packedAlloc := b.createRuntimeCall(b.allocFunc(), []llvm.Value{
sizeValue,
llvm.ConstNull(b.dataPtrType),
}, "")
packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
packedAlloc := b.createAlloc(sizeValue, llvm.ConstNull(b.dataPtrType), align, "")
if b.NeedsStackObjects {
b.trackPointer(packedAlloc)
}
+2 -2
View File
@@ -133,7 +133,7 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, valueAlloca}
b.createRuntimeCall("hashmapStringSet", params, "")
b.createRuntimeInvoke("hashmapStringSet", params, "")
} else {
// Key stored at actual type.
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
@@ -143,7 +143,7 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
fnName = "hashmapGenericSet"
}
params := []llvm.Value{m, keyAlloca, valueAlloca}
b.createRuntimeCall(fnName, params, "")
b.createRuntimeInvoke(fnName, params, "")
b.emitLifetimeEnd(keyAlloca, keySize)
}
b.emitLifetimeEnd(valueAlloca, valueSize)
+1 -1
View File
@@ -162,7 +162,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.alloc", "runtime.alloc_noheap":
case "runtime.alloc", "runtime.alloc_noheap", "runtime.alloc_zero":
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
// returns values that are never null and never alias to an existing value.
for _, attrName := range []string{"noalias", "nonnull"} {
+14 -17
View File
@@ -3,7 +3,7 @@ source_filename = "defer.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-eabi"
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface }
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface, ptr }
%runtime._interface = type { ptr, ptr }
; Function Attrs: nounwind
@@ -18,14 +18,14 @@ declare void @main.external(ptr) #1
define hidden void @main.deferSimple(ptr %context) unnamed_addr #0 {
entry:
%defer.alloca = alloca { i32, ptr }, align 4
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
%0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
%defer.next = load ptr, ptr %deferPtr, align 4
store i32 0, ptr %defer.alloca, align 4
%defer.alloca.repack15 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
store ptr null, ptr %defer.alloca.repack15, align 4
store ptr %defer.next, ptr %defer.alloca.repack15, align 4
store ptr %defer.alloca, ptr %deferPtr, align 4
%setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5
%setjmp.result = icmp eq i32 %setjmp, 0
@@ -111,9 +111,9 @@ rundefers.end3: ; preds = %rundefers.loophead6
; Function Attrs: nocallback nofree nosync nounwind willreturn
declare ptr @llvm.stacksave.p0() #2
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(24), ptr, ptr) #1
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(28), ptr, ptr) #1
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #1
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(28), ptr) #1
; Function Attrs: nounwind
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #0 {
@@ -135,18 +135,18 @@ define hidden void @main.deferMultiple(ptr %context) unnamed_addr #0 {
entry:
%defer.alloca2 = alloca { i32, ptr }, align 4
%defer.alloca = alloca { i32, ptr }, align 4
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
%0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
%defer.next = load ptr, ptr %deferPtr, align 4
store i32 0, ptr %defer.alloca, align 4
%defer.alloca.repack22 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
store ptr null, ptr %defer.alloca.repack22, align 4
store ptr %defer.next, ptr %defer.alloca.repack22, align 4
store ptr %defer.alloca, ptr %deferPtr, align 4
store i32 1, ptr %defer.alloca2, align 4
%defer.alloca2.repack23 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4
store ptr %defer.alloca, ptr %defer.alloca2.repack23, align 4
%defer.alloca2.repack24 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4
store ptr %defer.alloca, ptr %defer.alloca2.repack24, align 4
store ptr %defer.alloca2, ptr %deferPtr, align 4
%setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5
%setjmp.result = icmp eq i32 %setjmp, 0
@@ -270,9 +270,8 @@ entry:
; Function Attrs: nounwind
define hidden void @main.deferInfiniteLoop(ptr %context) unnamed_addr #0 {
entry:
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
%0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
br label %for.body
@@ -318,9 +317,8 @@ declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #3
; Function Attrs: nounwind
define hidden void @main.deferLoop(ptr %context) unnamed_addr #0 {
entry:
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
%0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
br label %for.loop
@@ -408,9 +406,8 @@ rundefers.end1: ; preds = %rundefers.loophead4
define hidden void @main.deferBetweenLoops(ptr %context) unnamed_addr #0 {
entry:
%defer.alloca = alloca { i32, ptr, i32 }, align 4
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
%0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
br label %for.loop
+4 -1
View File
@@ -75,7 +75,7 @@ entry:
define hidden void @main.newStruct(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%new = call align 1 ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
%new = call align 1 ptr @runtime.alloc_zero(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3
store ptr %new, ptr @main.struct1, align 4
%new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
@@ -93,6 +93,9 @@ entry:
ret void
}
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc_zero(i32, ptr, ptr) #2
; Function Attrs: nounwind
define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #1 {
entry:
+30 -60
View File
@@ -29,13 +29,13 @@ entry:
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
store float %a.X, ptr %a, align 4
%a.repack9 = getelementptr inbounds nuw i8, ptr %a, i32 4
store float %a.Y, ptr %a.repack9, align 4
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4
store float %a.Y, ptr %a.repack5, align 4
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3
store float %b.X, ptr %b, align 4
%b.repack11 = getelementptr inbounds nuw i8, ptr %b, i32 4
store float %b.Y, ptr %b.repack11, align 4
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4
store float %b.Y, ptr %b.repack7, align 4
call void @main.checkSize(i32 4, ptr undef) #3
call void @main.checkSize(i32 8, ptr undef) #3
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
@@ -43,29 +43,29 @@ entry:
br i1 false, label %deref.throw, label %deref.next
deref.next: ; preds = %entry
br i1 false, label %deref.throw1, label %deref.next2
br i1 false, label %deref.throw, label %deref.next1
deref.next2: ; preds = %deref.next
deref.next1: ; preds = %deref.next
%0 = load float, ptr %a, align 4
%1 = load float, ptr %b, align 4
%2 = fadd float %0, %1
br i1 false, label %deref.throw3, label %deref.next4
br i1 false, label %deref.throw, label %deref.next2
deref.next4: ; preds = %deref.next2
br i1 false, label %deref.throw5, label %deref.next6
deref.next2: ; preds = %deref.next1
br i1 false, label %deref.throw, label %deref.next3
deref.next6: ; preds = %deref.next4
deref.next3: ; preds = %deref.next2
%3 = getelementptr inbounds nuw i8, ptr %b, i32 4
%4 = getelementptr inbounds nuw i8, ptr %a, i32 4
%5 = load float, ptr %4, align 4
%6 = load float, ptr %3, align 4
br i1 false, label %store.throw, label %store.next
br i1 false, label %deref.throw, label %store.next
store.next: ; preds = %deref.next6
store.next: ; preds = %deref.next3
store float %2, ptr %complit, align 4
br i1 false, label %store.throw7, label %store.next8
br i1 false, label %deref.throw, label %store.next4
store.next8: ; preds = %store.next
store.next4: ; preds = %store.next
%7 = getelementptr inbounds nuw i8, ptr %complit, i32 4
%8 = fadd float %5, %6
store float %8, ptr %7, align 4
@@ -74,22 +74,7 @@ store.next8: ; preds = %store.next
%10 = insertvalue %"main.Point[float32]" %9, float %8, 1
ret %"main.Point[float32]" %10
deref.throw: ; preds = %entry
unreachable
deref.throw1: ; preds = %deref.next
unreachable
deref.throw3: ; preds = %deref.next2
unreachable
deref.throw5: ; preds = %deref.next4
unreachable
store.throw: ; preds = %deref.next6
unreachable
store.throw7: ; preds = %store.next
deref.throw: ; preds = %store.next, %deref.next3, %deref.next2, %deref.next1, %deref.next, %entry
unreachable
}
@@ -107,13 +92,13 @@ entry:
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
store i32 %a.X, ptr %a, align 4
%a.repack9 = getelementptr inbounds nuw i8, ptr %a, i32 4
store i32 %a.Y, ptr %a.repack9, align 4
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4
store i32 %a.Y, ptr %a.repack5, align 4
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3
store i32 %b.X, ptr %b, align 4
%b.repack11 = getelementptr inbounds nuw i8, ptr %b, i32 4
store i32 %b.Y, ptr %b.repack11, align 4
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4
store i32 %b.Y, ptr %b.repack7, align 4
call void @main.checkSize(i32 4, ptr undef) #3
call void @main.checkSize(i32 8, ptr undef) #3
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
@@ -121,29 +106,29 @@ entry:
br i1 false, label %deref.throw, label %deref.next
deref.next: ; preds = %entry
br i1 false, label %deref.throw1, label %deref.next2
br i1 false, label %deref.throw, label %deref.next1
deref.next2: ; preds = %deref.next
deref.next1: ; preds = %deref.next
%0 = load i32, ptr %a, align 4
%1 = load i32, ptr %b, align 4
%2 = add i32 %0, %1
br i1 false, label %deref.throw3, label %deref.next4
br i1 false, label %deref.throw, label %deref.next2
deref.next4: ; preds = %deref.next2
br i1 false, label %deref.throw5, label %deref.next6
deref.next2: ; preds = %deref.next1
br i1 false, label %deref.throw, label %deref.next3
deref.next6: ; preds = %deref.next4
deref.next3: ; preds = %deref.next2
%3 = getelementptr inbounds nuw i8, ptr %b, i32 4
%4 = getelementptr inbounds nuw i8, ptr %a, i32 4
%5 = load i32, ptr %4, align 4
%6 = load i32, ptr %3, align 4
br i1 false, label %store.throw, label %store.next
br i1 false, label %deref.throw, label %store.next
store.next: ; preds = %deref.next6
store.next: ; preds = %deref.next3
store i32 %2, ptr %complit, align 4
br i1 false, label %store.throw7, label %store.next8
br i1 false, label %deref.throw, label %store.next4
store.next8: ; preds = %store.next
store.next4: ; preds = %store.next
%7 = getelementptr inbounds nuw i8, ptr %complit, i32 4
%8 = add i32 %5, %6
store i32 %8, ptr %7, align 4
@@ -152,22 +137,7 @@ store.next8: ; preds = %store.next
%10 = insertvalue %"main.Point[int]" %9, i32 %8, 1
ret %"main.Point[int]" %10
deref.throw: ; preds = %entry
unreachable
deref.throw1: ; preds = %deref.next
unreachable
deref.throw3: ; preds = %deref.next2
unreachable
deref.throw5: ; preds = %deref.next4
unreachable
store.throw: ; preds = %deref.next6
unreachable
store.throw7: ; preds = %store.next
deref.throw: ; preds = %store.next, %deref.next3, %deref.next2, %deref.next1, %deref.next, %entry
unreachable
}
+7 -7
View File
@@ -1,6 +1,6 @@
module github.com/tinygo-org/tinygo
go 1.23.0
go 1.24.0
require (
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139
@@ -18,11 +18,11 @@ require (
go.bug.st/serial v1.6.4
go.bytecodealliance.org v0.6.2
go.bytecodealliance.org/cm v0.2.2
golang.org/x/net v0.35.0
golang.org/x/sys v0.30.0
golang.org/x/tools v0.30.0
golang.org/x/net v0.50.0
golang.org/x/sys v0.41.0
golang.org/x/tools v0.42.0
gopkg.in/yaml.v2 v2.4.0
tinygo.org/x/espflasher v0.6.0
tinygo.org/x/espflasher v0.6.1
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6
)
@@ -48,6 +48,6 @@ require (
github.com/spf13/afero v1.11.0 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/urfave/cli/v3 v3.0.0-beta1 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/text v0.34.0 // indirect
)
+16 -14
View File
@@ -23,6 +23,8 @@ github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs=
github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
@@ -93,24 +95,24 @@ go.bytecodealliance.org v0.6.2 h1:Jy4u5DVmSkXgsnwojBhJ+AD/YsJsR3VzVnxF0xRCqTQ=
go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA=
go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA=
go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@@ -118,7 +120,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
tinygo.org/x/espflasher v0.6.0 h1:CHbGMHAIWq1tB8FKd/QwBpNFw1jvHHxpmvZiF+QOYUo=
tinygo.org/x/espflasher v0.6.0/go.mod h1:tr5u08HoE67WD5zxJesCiiVF/R1b6Akz3yXwh5zah8U=
tinygo.org/x/espflasher v0.6.1 h1:9jfyAP9jGjxF63FQUY2Bml9TFb5fCZYxVgbgR2IjUGs=
tinygo.org/x/espflasher v0.6.1/go.mod h1:tr5u08HoE67WD5zxJesCiiVF/R1b6Akz3yXwh5zah8U=
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6 h1:QSnqFgNV2Ij0T4hM2qKv53fcDAFElxClPjVUZXzYkWU=
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
+2
View File
@@ -18,7 +18,9 @@ func TestInterp(t *testing.T) {
"copy",
"interface",
"revert",
"store",
"alloc",
"slicedata",
} {
name := name // make local to this closure
t.Run(name, func(t *testing.T) {
+1 -1
View File
@@ -299,7 +299,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// means that monotonic time in the time package is counted from
// time.Time{}.Sub(1), which should be fine.
locals[inst.localIndex] = literalValue{uint64(0)}
case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap":
case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap" || callFn.name == "runtime.alloc_zero":
// Allocate heap memory. At compile time, this is instead done
// by creating a global variable.
+34 -13
View File
@@ -299,7 +299,8 @@ func (mv *memoryView) put(index uint32, obj object) {
}
// Load the value behind the given pointer. Returns nil if the pointer points to
// an external global.
// an external global or if the load is out of bounds of the object (in which
// case the caller defers the load to runtime).
func (mv *memoryView) load(p pointerValue, size uint32) value {
if checks && mv.hasExternalStore(p) {
panic("interp: load from object with external store")
@@ -312,14 +313,23 @@ func (mv *memoryView) load(p pointerValue, size uint32) value {
if p.offset() == 0 && size == obj.size {
return obj.buffer.clone()
}
if checks && p.offset()+size > obj.size {
panic("interp: load out of bounds")
if p.offset()+size > obj.size {
// The load is out of bounds of the object. This can happen for valid
// Go programs, for example when dereferencing the pointer returned by
// unsafe.SliceData on a zero-capacity slice (which points to a
// zero-sized object). Return nil so the caller defers this load to
// runtime instead of crashing the compiler.
return nil
}
v := obj.buffer.asRawValue(mv.r)
loadedValue := rawValue{
buf: v.buf[p.offset() : p.offset()+size],
loadedBuf := v.buf[p.offset() : p.offset()+size]
if _, writable := mv.objects[p.index()]; writable {
// This object's buffer is owned by this view, which means a later
// store may mutate it in place (see store below). Copy the loaded
// slice so the returned value is not aliased with the live buffer.
loadedBuf = append([]uint64(nil), loadedBuf...)
}
return loadedValue
return rawValue{buf: loadedBuf}
}
// Store to the value behind the given pointer. This overwrites the value in the
@@ -330,26 +340,37 @@ func (mv *memoryView) store(v value, p pointerValue) bool {
if checks && mv.hasExternalLoadOrStore(p) {
panic("interp: store to object with external load/store")
}
obj := mv.get(p.index())
index := p.index()
var obj object
writable := false
if mv.objects != nil {
obj, writable = mv.objects[index]
}
if !writable {
obj = mv.get(index)
}
if obj.buffer == nil {
// External global, return false (for a failure).
return false
}
if checks && p.offset()+v.len(mv.r) > obj.size {
valueLen := v.len(mv.r)
if checks && p.offset()+valueLen > obj.size {
panic("interp: store out of bounds")
}
if p.offset() == 0 && v.len(mv.r) == obj.buffer.len(mv.r) {
obj.buffer = v
if p.offset() == 0 && valueLen == obj.buffer.len(mv.r) {
obj.buffer = v.clone()
} else {
obj = obj.clone()
if !writable {
obj = obj.clone()
}
buffer := obj.buffer.asRawValue(mv.r)
obj.buffer = buffer
v := v.asRawValue(mv.r)
for i := uint32(0); i < v.len(mv.r); i++ {
for i := uint32(0); i < valueLen; i++ {
buffer.buf[p.offset()+i] = v.buf[i]
}
}
mv.put(p.index(), obj)
mv.put(index, obj)
return true // success
}
+23
View File
@@ -0,0 +1,23 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
; Reproduction of https://github.com/tinygo-org/tinygo/issues/4214.
; Dereferencing the pointer returned by unsafe.SliceData on a zero-capacity
; slice produces a load that is out of bounds of a zero-sized object. The
; interp must defer this load to runtime instead of crashing the compiler.
@main.zeroSized = global {} zeroinitializer
@main.v = global i64 0
define void @runtime.initAll() unnamed_addr {
entry:
call void @main.init(ptr undef)
ret void
}
define internal void @main.init(ptr %context) unnamed_addr {
entry:
%val = load i64, ptr @main.zeroSized
store i64 %val, ptr @main.v
ret void
}
+12
View File
@@ -0,0 +1,12 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.zeroSized = local_unnamed_addr global {} zeroinitializer
@main.v = local_unnamed_addr global i64 0
define void @runtime.initAll() unnamed_addr {
entry:
%val = load i64, ptr @main.zeroSized, align 8
store i64 %val, ptr @main.v, align 8
ret void
}
+49
View File
@@ -0,0 +1,49 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@overlap.buf = global [4 x i8] c"\01\02\03\04"
@alias.src = global [4 x i8] c"\05\06\07\08"
@alias.dst = global [2 x i8] zeroinitializer
@reload.buf = global [4 x i8] c"\01\02\03\04"
@reload.out = global [2 x i8] zeroinitializer
define void @runtime.initAll() unnamed_addr {
entry:
call void @overlap.init(ptr undef)
call void @alias.init(ptr undef)
call void @reload.init(ptr undef)
ret void
}
define internal void @overlap.init(ptr %context) unnamed_addr {
entry:
%tail = getelementptr [4 x i8], ptr @overlap.buf, i32 0, i32 3
store i8 9, ptr %tail
%val = load i16, ptr @overlap.buf
%dst = getelementptr [4 x i8], ptr @overlap.buf, i32 0, i32 1
store i16 %val, ptr %dst
ret void
}
define internal void @alias.init(ptr %context) unnamed_addr {
entry:
%src = getelementptr [4 x i8], ptr @alias.src, i32 0, i32 1
%val = load i16, ptr %src
store i16 %val, ptr @alias.dst
store i8 9, ptr @alias.dst
ret void
}
define internal void @reload.init(ptr %context) unnamed_addr {
entry:
; First store makes reload.buf writable in the current memory view.
%tail = getelementptr [4 x i8], ptr @reload.buf, i32 0, i32 3
store i8 9, ptr %tail
; Partial load whose result may share the underlying buffer.
%val = load i16, ptr @reload.buf
; Subsequent in-place partial store; this must not corrupt %val.
store i8 99, ptr @reload.buf
; Write the originally-loaded value to a separate global.
store i16 %val, ptr @reload.out
ret void
}
+13
View File
@@ -0,0 +1,13 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@overlap.buf = local_unnamed_addr global [4 x i8] c"\01\01\02\09"
@alias.src = local_unnamed_addr global [4 x i8] c"\05\06\07\08"
@alias.dst = local_unnamed_addr global [2 x i8] c"\09\07"
@reload.buf = local_unnamed_addr global [4 x i8] c"c\02\03\09"
@reload.out = local_unnamed_addr global [2 x i8] c"\01\02"
define void @runtime.initAll() unnamed_addr {
entry:
ret void
}
+1
View File
@@ -246,6 +246,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
"internal/futex/": false,
"internal/fuzz/": false,
"internal/itoa/": false,
"internal/poll/": false,
"internal/reflectlite/": false,
"internal/gclayout": false,
"internal/task/": false,
+2 -4
View File
@@ -27,8 +27,6 @@ import (
"github.com/tinygo-org/tinygo/goenv"
)
var initFileVersions = func(info *types.Info) {}
// Program holds all packages and some metadata about the program as a whole.
type Program struct {
config *compileopts.Config
@@ -435,7 +433,7 @@ func (p *Package) Check() error {
}
checker.GoVersion = fmt.Sprintf("go%d.%d", major, minor)
}
initFileVersions(&p.info)
p.info.FileVersions = make(map[*ast.File]string)
// Do typechecking of the package.
packageName := p.ImportPath
@@ -485,7 +483,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
if !filepath.IsAbs(file) {
file = filepath.Join(p.Dir, file)
}
f, err := p.parseFile(file, parser.ParseComments)
f, err := p.parseFile(file, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
fileErrs = append(fileErrs, err)
return
-17
View File
@@ -1,17 +0,0 @@
//go:build go1.22
// types.Info.FileVersions was added in Go 1.22, so we can only initialize it
// when built with Go 1.22.
package loader
import (
"go/ast"
"go/types"
)
func init() {
initFileVersions = func(info *types.Info) {
info.FileVersions = make(map[*ast.File]string)
}
}
+38 -3
View File
@@ -209,6 +209,8 @@ func Build(pkgName, outpath string, config *compileopts.Config) error {
// Test runs the tests in the given package. Returns whether the test passed and
// possibly an error if the test failed to run.
func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options, outpath string) (bool, error) {
optionsCopy := *options
options = &optionsCopy
options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options)
if err != nil {
@@ -395,7 +397,7 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error {
fileExt = filepath.Ext(config.Target.FlashFilename)
case "openocd":
fileExt = ".hex"
case "bmp":
case "bmp", "probe-rs":
fileExt = ".elf"
case "adb":
fileExt = ".hex"
@@ -535,6 +537,16 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error {
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
}
case "probe-rs":
// TODO: this halts the target after flashing.
// See: https://github.com/probe-rs/probe-rs/discussions/4005
cmd := executeCommand(config.Options, "probe-rs", "download", "--chip="+config.Target.ProbeRSChip, "--verify", result.Binary)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
}
case "adb":
// Run pre-flash adb shell commands.
for _, preCmd := range config.Target.ADBPreCommands {
@@ -697,6 +709,21 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
daemon.Stdout = w
daemon.Stderr = w
}
case "probe-rs":
port = ":1337"
gdbCommands = append(gdbCommands, "monitor halt", "load", "monitor reset halt")
daemon = executeCommand(config.Options, "probe-rs", "gdb", "--chip="+config.Target.ProbeRSChip)
if ocdOutput {
// Make it clear which output is from the daemon.
w := &ColorWriter{
Out: colorable.NewColorableStderr(),
Prefix: "probe-rs: ",
Color: TermColorYellow,
}
daemon.Stdout = w
daemon.Stderr = w
}
case "jlink":
port = ":2331"
gdbCommands = append(gdbCommands, "load", "monitor reset halt")
@@ -1753,6 +1780,7 @@ func main() {
printSize := flag.String("size", "", "print sizes (none, short, full, html)")
printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines")
printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed")
printAllocsCoverString := flag.String("print-allocs-cover", "", "like -print-allocs, but in go coverage tool format")
printCommands := flag.Bool("x", false, "Print commands")
flagJSON := flag.Bool("json", false, "print output in JSON format")
parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel")
@@ -1833,8 +1861,14 @@ func main() {
}
var printAllocs *regexp.Regexp
if *printAllocsString != "" {
printAllocs, err = regexp.Compile(*printAllocsString)
printAllocsCover := false
printAllocsPattern := *printAllocsString
if *printAllocsCoverString != "" {
printAllocsPattern = *printAllocsCoverString
printAllocsCover = true
}
if printAllocsPattern != "" {
printAllocs, err = regexp.Compile(printAllocsPattern)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
@@ -1882,6 +1916,7 @@ func main() {
PrintSizes: *printSize,
PrintStacks: *printStacks,
PrintAllocs: printAllocs,
PrintAllocsCover: printAllocsCover,
Tags: []string(tags),
TestConfig: testConfig,
GlobalValues: globalVarValues,
+2 -9
View File
@@ -46,15 +46,6 @@ var supportedLinuxArches = map[string]string{
"WASIp1": "wasip1/wasm",
}
func init() {
major, _, _ := goenv.GetGorootVersion()
if major < 21 {
// Go 1.20 backwards compatibility.
// Should be removed once we drop support for Go 1.20.
delete(supportedLinuxArches, "WASIp1")
}
}
var sema = make(chan struct{}, runtime.NumCPU())
func TestBuild(t *testing.T) {
@@ -76,6 +67,8 @@ func TestBuild(t *testing.T) {
"init_multi.go",
"interface.go",
"json.go",
"localtypes/",
"localtypes.go",
"map.go",
"map_bigkey.go",
"math.go",
+1 -1
View File
@@ -278,7 +278,7 @@ func ListSerialPorts() ([]SerialPortInfo, error) {
return serialPortInfo, nil
}
var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `)
var addressMatch = regexp.MustCompile(`panic: runtime error at 0x([0-9a-f]+): `)
// Extract the address from the "panic: runtime error at" message.
func extractPanicAddress(line []byte) uint64 {
+1 -1
View File
@@ -20,7 +20,7 @@ type reader struct {
func (r *reader) Read(b []byte) (n int, err error) {
if len(b) != 0 {
libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b)))
libc_arc4random_buf(unsafe.Pointer(unsafe.SliceData(b)), uint(len(b)))
}
return len(b), nil
}
+1 -1
View File
@@ -25,7 +25,7 @@ func (r *reader) Read(b []byte) (n int, err error) {
// See for example: https://github.com/golang/go/issues/33542
// For Windows 7 and newer, we might switch to ProcessPrng in the future
// (which is a documented function and might be a tiny bit faster).
ok := libc_RtlGenRandom(unsafe.Pointer(&b[0]), len(b))
ok := libc_RtlGenRandom(unsafe.Pointer(unsafe.SliceData(b)), len(b))
if !ok {
return 0, errRandom
}
+66
View File
@@ -0,0 +1,66 @@
// This is a very minimal bootloader for the ESP32-C6. It only initializes the
// flash and then continues with the generic RISC-V initialization code, which
// in turn will call runtime.main.
// It is written in assembly (and not in a higher level language) to make sure
// it is entirely loaded into IRAM and doesn't accidentally call functions
// stored in IROM.
//
// The ESP32-C6 has a unified IRAM/DRAM address space at 0x40800000, and
// separate DROM (0x42800000) / IROM (0x42000000) flash-mapped regions.
.section .init
.global call_start_cpu0
.type call_start_cpu0,@function
call_start_cpu0:
// At this point:
// - The ROM bootloader is finished and has jumped to here.
// - We're running from IRAM: both IRAM and DRAM segments have been loaded
// by the ROM bootloader.
// - We have a usable stack (but not the one we would like to use).
// - No flash mappings (MMU) are set up yet.
// Reset MMU, see bootloader_reset_mmu in the ESP-IDF.
call Cache_Suspend_ICache
mv s0, a0 // autoload value
call Cache_Invalidate_ICache_All
call Cache_MMU_Init
// Set up flash mapping (both IROM and DROM).
// On ESP32-C6, Cache_Dbus_MMU_Set is replaced by Cache_MSPI_MMU_Set
// which has an extra "sensitive" parameter.
// C equivalent:
// Cache_MSPI_MMU_Set(0, 0, 0x42000000, 0, 64, 256, 0)
// Maps 16MB starting at 0x42000000, covering both IROM and DROM.
li a0, 0 // sensitive: no flash encryption
li a1, 0 // ext_ram: MMU_ACCESS_FLASH
li a2, 0x42000000 // vaddr: start of flash-mapped region
li a3, 0 // paddr: physical address in the flash chip
li a4, 64 // psize: always 64 (kilobytes)
li a5, 256 // num: pages (16MB / 64K = 256, covers IROM+DROM)
li a6, 0 // fixed
call Cache_MSPI_MMU_Set
// Enable the flash cache.
mv a0, s0 // restore autoload value from Cache_Suspend_ICache call
call Cache_Resume_ICache
// Jump to generic RISC-V initialization, which initializes the stack
// pointer and globals register. It should not return.
j _start
.section .text.exception_vectors
.global _vector_table
.type _vector_table,@function
_vector_table:
.option push
.option norvc
.rept 32
j handleInterruptASM /* interrupt handler */
.endr
.option pop
.size _vector_table, .-_vector_table
+60
View File
@@ -85,6 +85,9 @@ var (
SIO = (*SIO_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0134)))
INTERRUPT = (*INTERRUPT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0200)))
// BIOS interrupt flags
BIOS_IF = (*volatile.Register16)(unsafe.Pointer(uintptr(0x03007FF8)))
)
// Main memory sections
@@ -825,3 +828,60 @@ const (
OAMOBJ_ATT2_PB_Pos = 0xC
OAMOBJ_ATT2_PB_Msk = 0xF
)
// Constants for SOUND: sound control
const (
SSW_INC = 0x0 // Increasing sweep rate
SSW_DEC = 0x0008 // Decreasing sweep rate
SSW_OFF = 0x0008 // Disable sweep altogether
SSQR_DUTY1_8 = 0x0 // 12.5% duty cycle (#-------)
SSQR_DUTY1_4 = 0x0040 // 25% duty cycle (##------)
SSQR_DUTY1_2 = 0x0080 // 50% duty cycle (####----)
SSQR_DUTY3_4 = 0x00C0 // 75% duty cycle (######--) Equivalent to 25%
SSQR_INC = 0x0 // Increasing volume
SSQR_DEC = 0x0800 // Decreasing volume
SFREQ_HOLD = 0x0 // Continuous play
SFREQ_TIMED = 0x4000 // Timed play
SFREQ_RESET = 0x8000 // Reset sound
SDMG_SQR1 = 0x01
SDMG_SQR2 = 0x02
SDMG_WAVE = 0x04
SDMG_NOISE = 0x08
SDMG_LSQR1 = 0x0100 // Enable channel 1 on left
SDMG_LSQR2 = 0x0200 // Enable channel 2 on left
SDMG_LWAVE = 0x0400 // Enable channel 3 on left
SDMG_LNOISE = 0x0800 // Enable channel 4 on left
SDMG_RSQR1 = 0x1000 // Enable channel 1 on right
SDMG_RSQR2 = 0x2000 // Enable channel 2 on right
SDMG_RWAVE = 0x4000 // Enable channel 3 on right
SDMG_RNOISE = 0x8000 // Enable channel 4 on right
SDS_DMG25 = 0x0 // Tone generators at 25% volume
SDS_DMG50 = 0x0001 // Tone generators at 50% volume
SDS_DMG100 = 0x0002 // Tone generators at 100% volume
SDS_A50 = 0x0 // Direct Sound A at 50% volume
SDS_A100 = 0x0004 // Direct Sound A at 100% volume
SDS_B50 = 0x0 // Direct Sound B at 50% volume
SDS_B100 = 0x0008 // Direct Sound B at 100% volume
SDS_AR = 0x0100 // Enable Direct Sound A on right
SDS_AL = 0x0200 // Enable Direct Sound A on left
SDS_ATMR0 = 0x0 // Direct Sound A to use timer 0
SDS_ATMR1 = 0x0400 // Direct Sound A to use timer 1
SDS_ARESET = 0x0800 // Reset FIFO of Direct Sound A
SDS_BR = 0x1000 // Enable Direct Sound B on right
SDS_BL = 0x2000 // Enable Direct Sound B on left
SDS_BTMR0 = 0x0 // Direct Sound B to use timer 0
SDS_BTMR1 = 0x4000 // Direct Sound B to use timer 1
SDS_BRESET = 0x8000 // Reset FIFO of Direct Sound B
SSTAT_SQR1 = 0x0001 // (R) Channel 1 status
SSTAT_SQR2 = 0x0002 // (R) Channel 2 status
SSTAT_WAVE = 0x0004 // (R) Channel 3 status
SSTAT_NOISE = 0x0008 // (R) Channel 4 status
SSTAT_DISABLE = 0 // Disable sound
SSTAT_ENABLE = 0x0080 // Enable sound. NOTE: enable before using any other sound regs
)
@@ -1,5 +1,3 @@
//go:build go1.23
package cm
import "structs"
-11
View File
@@ -1,11 +0,0 @@
//go:build !go1.23
package cm
// HostLayout marks a struct as using host memory layout.
// See [structs.HostLayout] in Go 1.23 or later.
type HostLayout struct {
_ hostLayout // prevent accidental conversion with plain struct{}
}
type hostLayout struct{}
+45
View File
@@ -0,0 +1,45 @@
//go:build wasip1
// Internal-test helpers exposed via go:linkname so user code can drive
// the deadline-aware Read/Write loop without becoming a stdlib package.
// Not part of the public API; the names are intentionally awkward to
// signal "for tests only".
package poll
import (
"syscall"
"time"
)
// pollTestReadWithDeadline opens a pollable FD wrapper for sysfd, sets
// a read deadline d into the future, calls Read once, and returns
// (n, err). Caller is responsible for closing sysfd.
//
//go:linkname pollTestReadWithDeadline
func pollTestReadWithDeadline(sysfd int, d time.Duration, p []byte) (int, error) {
fd := &FD{Sysfd: sysfd, IsStream: true}
// Best-effort init; ignore error so a caller using a not-fcntl-able FD
// (stdin under wazero, etc.) still gets to test the deadline path on
// whatever park behaviour the runtime gives.
_ = fd.Init("test", true)
if err := fd.SetReadDeadline(time.Now().Add(d)); err != nil {
return 0, err
}
return fd.Read(p)
}
// pollTestSetNonblock toggles O_NONBLOCK on a raw sysfd. Useful in
// tests when the caller wants to ensure the FD is in nonblocking mode
// before calling pollTestReadWithDeadline (Init is best-effort and may
// silently skip).
//
//go:linkname pollTestSetNonblock
func pollTestSetNonblock(sysfd int) error {
flags, err := syscall.Fcntl(sysfd, syscall.F_GETFL, 0)
if err != nil {
return err
}
_, err = syscall.Fcntl(sysfd, syscall.F_SETFL, flags|syscall.O_NONBLOCK)
return err
}
+549
View File
@@ -0,0 +1,549 @@
//go:build wasip1
// Package poll is a minimal subset of upstream Go's internal/poll, scoped
// to what is needed to back a wasip1 net implementation on top of
// TinyGo's cooperative-scheduler netpoll integration.
//
// On wasip1 the cooperative scheduler integrates poll_oneoff with FD
// waiters (see runtime/netpoll_wasip1.go and syscall/syscall_libc_wasip1.go).
// This package wraps the syscall layer to:
//
// - own the O_NONBLOCK policy decision (set on Init for pollable FDs),
// unblocking the EAGAIN→park retry loop that syscall.Read already has;
// - provide a Go-shaped FD type that net.* can use without reaching
// into syscall directly;
// - thread per-FD read/write deadlines through a runtime helper that
// lets a time.AfterFunc callback wake the parked goroutine;
// - dispatch socket FDs through wasi sock_recv / sock_send / sock_accept
// / sock_shutdown so net.Conn/net.Listener (via upstream Go's
// net/file_wasip1.go) work end-to-end.
package poll
import (
"errors"
"internal/task"
"syscall"
"time"
"unsafe"
)
// ErrFileClosing is returned when a Read or Write is started on a closed FD.
var ErrFileClosing = errors.New("use of closed file")
// ErrNetClosing is returned for network operations on a closed FD.
var ErrNetClosing = errors.New("use of closed network connection")
// ErrDeadlineExceeded is returned by Read/Write when a deadline expired.
// Matches the error returned by os.IsTimeout-style helpers.
var ErrDeadlineExceeded = errors.New("i/o timeout")
// ErrNoDeadline is returned if SetDeadline is called on an FD whose
// underlying type does not support deadlines.
var ErrNoDeadline = errors.New("file type does not support deadline")
// pollMode constants must mirror runtime/netpoll_wasip1.go's pollRead/
// pollWrite values.
const (
pollModeRead uint8 = 1
pollModeWrite uint8 = 2
)
//go:linkname runtime_netpoll_addwait runtime.runtime_netpoll_addwait
func runtime_netpoll_addwait(fd uint32, mode uint8) uintptr
//go:linkname runtime_netpoll_done runtime.runtime_netpoll_done
func runtime_netpoll_done(pd uintptr)
//go:linkname runtime_netpoll_pdfired runtime.runtime_netpoll_pdfired
func runtime_netpoll_pdfired(pd uintptr) bool
//go:linkname runtime_netpoll_wake runtime.runtime_netpoll_wake
func runtime_netpoll_wake(pd uintptr)
//go:linkname fd_fdstat_get_type syscall.fd_fdstat_get_type
func fd_fdstat_get_type(fd int) (syscall.Filetype, error)
// wasiIovec / wasiCiovec mirror wasi-snapshot-preview1's iovec / ciovec
// records: a buffer pointer plus a 32-bit length. Marshalled inline with
// each fd_read / fd_write / sock_recv / sock_send call.
type wasiIovec struct {
buf *byte
bufLen uint32
}
// fd_read / fd_write are bound directly here rather than going through
// wasi-libc so the deadline-aware Read/Write loop can observe EAGAIN
// cleanly. Outside package runtime, TinyGo's wasmimport binding
// restricts us to unsafe.Pointer for struct args and uint32 for the
// errno result; the wasi spec uses *iovec and uint16 respectively but
// the wire layout is identical.
//
//go:wasmimport wasi_snapshot_preview1 fd_read
func wasi_fd_read(fd int32, iovs unsafe.Pointer, iovsLen uint32, nread unsafe.Pointer) uint32
//go:wasmimport wasi_snapshot_preview1 fd_write
func wasi_fd_write(fd int32, iovs unsafe.Pointer, iovsLen uint32, nwritten unsafe.Pointer) uint32
// sock_recv / sock_send are the socket-data wasi syscalls used when the
// FD's Filetype indicates a socket. We bind them here for the same
// reason as fd_read / fd_write: the deadline-aware loop wants direct
// access to the EAGAIN signal without libc's translation layer.
//
//go:wasmimport wasi_snapshot_preview1 sock_recv
func wasi_sock_recv(fd int32, riData unsafe.Pointer, riDataLen uint32, riFlags uint32, roDatalen unsafe.Pointer, roFlags unsafe.Pointer) uint32
//go:wasmimport wasi_snapshot_preview1 sock_send
func wasi_sock_send(fd int32, siData unsafe.Pointer, siDataLen uint32, siFlags uint32, soDatalen unsafe.Pointer) uint32
// SysFile carries per-FD bookkeeping that upstream Go's poll.FD uses to
// share an underlying syscall FD between an os.File and a net.Conn (see
// Copy below). RefCountPtr / RefCount handle the shared-ownership case;
// Filetype caches the wasi filetype so socket-vs-file dispatch in Read /
// Write is a single integer compare on the hot path.
//
// wasip1 is single-threaded, so the refcount is a plain int — no atomics
// needed. Match upstream's field naming for source-level compatibility
// with code that constructs FDs via struct literal (e.g. upstream's
// net/fd_fake.go).
type SysFile struct {
RefCountPtr *int32
RefCount int32
Filetype uint32
}
// init lazily allocates the refcount the first time it's needed (i.e.
// the first Init or Copy on this FD). A zero SysFile starts at refcount
// 1 — the FD's sole owner is the caller.
func (s *SysFile) init() {
if s.RefCountPtr == nil {
s.RefCount = 1
s.RefCountPtr = &s.RefCount
}
}
// ref increments the shared refcount and returns a SysFile that points
// at the same counter. Used by FD.Copy.
func (s *SysFile) ref() SysFile {
s.init()
*s.RefCountPtr++
return SysFile{RefCountPtr: s.RefCountPtr, Filetype: s.Filetype}
}
// destroy decrements the refcount and reports whether the underlying
// syscall FD should now be closed (i.e. this was the last owner).
func (s *SysFile) destroy() bool {
if s.RefCountPtr == nil {
return true
}
*s.RefCountPtr--
return *s.RefCountPtr <= 0
}
// FD is the wasip1 file/socket descriptor wrapped with the bookkeeping
// that net and os rely on. It owns the lifecycle of the underlying
// syscall FD (modulo the Copy / refcount handoff between os.File and
// net.Conn).
//
// The struct mirrors upstream Go's internal/poll.FD field naming
// (Sysfd, IsStream, ZeroReadIsEOF, SysFile) so that upstream's
// net/file_wasip1.go and net/fd_fake.go can construct one via struct
// literal without modification.
type FD struct {
Sysfd int
SysFile SysFile
IsStream bool
ZeroReadIsEOF bool
closed bool
// Per-FD deadlines, zero means "no deadline". Subsequent Read/Write
// calls observe whatever the current value is at call time; an
// in-flight call uses the deadline it captured at its start.
rDeadline time.Time
wDeadline time.Time
}
// Init readies the FD for use. When pollable is true (i.e. the FD might
// block — sockets, pipes, FIFOs), Init sets O_NONBLOCK so that
// Read/Write enter the EAGAIN→park retry loop instead of blocking the
// entire wasm module.
//
// Init also caches the wasi filetype so the Read/Write hot path can
// dispatch socket vs file with a single integer compare.
//
// The net argument is currently ignored but kept for parity with
// upstream Go.
func (fd *FD) Init(net string, pollable bool) error {
_ = net
fd.SysFile.init()
if ft, err := fd_fdstat_get_type(fd.Sysfd); err == nil {
fd.SysFile.Filetype = uint32(ft)
}
if !pollable {
return nil
}
flags, err := syscall.Fcntl(fd.Sysfd, syscall.F_GETFL, 0)
if err != nil {
return err
}
if flags&syscall.O_NONBLOCK != 0 {
return nil
}
if _, err := syscall.Fcntl(fd.Sysfd, syscall.F_SETFL, flags|syscall.O_NONBLOCK); err != nil {
return err
}
return nil
}
// Copy returns a duplicate FD that shares the underlying Sysfd through
// the SysFile refcount. The original and the copy can independently
// call Close — only the last one actually issues the syscall. Used by
// upstream net/file_wasip1.go to hand a socket FD off from an os.File
// to a net.Listener / net.Conn.
func (fd *FD) Copy() FD {
return FD{
Sysfd: fd.Sysfd,
SysFile: fd.SysFile.ref(),
IsStream: fd.IsStream,
ZeroReadIsEOF: fd.ZeroReadIsEOF,
}
}
// Close marks the FD closed. The underlying syscall FD is only released
// when the refcount drops to zero — earlier Close calls (e.g. on the
// os.File side after a successful net.FileConn handoff) just decrement.
func (fd *FD) Close() error {
if fd.closed {
return ErrFileClosing
}
fd.closed = true
if !fd.SysFile.destroy() {
return nil
}
return syscall.Close(fd.Sysfd)
}
// Exist reports whether fd points to an actual FD wrapper (non-nil).
// Callers in package os hold a *FD via a per-target alias and need to
// nil-check without depending on the concrete type being a pointer.
func (fd *FD) Exist() bool { return fd != nil }
// CloseFunc is the hook upstream net's poll package exposes so tests
// can intercept Close. We just point at syscall.Close.
var CloseFunc func(int) error = syscall.Close
// String is an internal string definition for methods/functions that
// shouldn't be used outside the stdlib. Upstream net references it
// from rawconn.go to mark methods as not-for-external-use.
type String string
// RawControl / RawRead / RawWrite back syscall.RawConn's three callback
// methods. They invoke f with the underlying FD; the bool return of
// RawRead / RawWrite controls retry-on-EAGAIN, which we implement by
// parking the goroutine on the netpoll registry and retrying until f
// returns true (the same loop upstream uses).
func (fd *FD) RawControl(f func(uintptr)) error {
if fd.closed {
return ErrFileClosing
}
f(uintptr(fd.Sysfd))
return nil
}
func (fd *FD) RawRead(f func(uintptr) bool) error {
if fd.closed {
return ErrFileClosing
}
for {
if f(uintptr(fd.Sysfd)) {
return nil
}
wait(fd.Sysfd, pollModeRead)
}
}
func (fd *FD) RawWrite(f func(uintptr) bool) error {
if fd.closed {
return ErrFileClosing
}
for {
if f(uintptr(fd.Sysfd)) {
return nil
}
wait(fd.Sysfd, pollModeWrite)
}
}
// Shutdown calls wasi sock_shutdown. how is one of syscall.SHUT_RD,
// SHUT_WR, SHUT_RDWR.
func (fd *FD) Shutdown(how int) error {
return syscall.Shutdown(fd.Sysfd, how)
}
// Accept loops over wasi sock_accept, parking the goroutine on EAGAIN
// (waiting for a new connection) through the netpoll registry. Returns
// (newfd, sockaddr=nil, errcall, error) — sockaddr is always nil because
// wasi sock_accept doesn't return one.
func (fd *FD) Accept() (int, syscall.Sockaddr, string, error) {
deadline := fd.rDeadline
for {
if !deadline.IsZero() && !time.Now().Before(deadline) {
return -1, nil, "accept", ErrDeadlineExceeded
}
nfd, _, err := syscall.Accept(fd.Sysfd)
if err == nil {
return nfd, nil, "", nil
}
if err == syscall.EINTR {
continue
}
if err != syscall.EAGAIN {
return -1, nil, "accept", err
}
if deadline.IsZero() {
wait(fd.Sysfd, pollModeRead)
} else {
if perr := fd.parkUntil(pollModeRead, deadline); perr != nil {
return -1, nil, "accept", perr
}
}
}
}
// isSocket reports whether the cached Filetype is a stream or datagram
// socket. False if Init was never called (Filetype stays 0 = UNKNOWN).
func (fd *FD) isSocket() bool {
ft := syscall.Filetype(fd.SysFile.Filetype)
return ft == syscall.FILETYPE_SOCKET_STREAM || ft == syscall.FILETYPE_SOCKET_DGRAM
}
// Read reads from the FD into p. Sockets dispatch to sock_recv (which
// honours fd.rDeadline directly); regular files/pipes go through
// syscall.Read for the no-deadline fast path or readWithDeadline when
// a deadline is set.
func (fd *FD) Read(p []byte) (int, error) {
if fd.closed {
return 0, ErrFileClosing
}
if len(p) == 0 {
return 0, nil
}
if fd.isSocket() {
return fd.sockRecv(p)
}
if fd.rDeadline.IsZero() {
return syscall.Read(fd.Sysfd, p)
}
return fd.readWithDeadline(p)
}
// Write writes p to the FD. Sockets dispatch to sock_send. Regular
// files / pipes go through syscall.Write or writeWithDeadline.
func (fd *FD) Write(p []byte) (int, error) {
if fd.closed {
return 0, ErrFileClosing
}
if fd.isSocket() {
return fd.sockSend(p)
}
if fd.wDeadline.IsZero() {
return syscall.Write(fd.Sysfd, p)
}
return fd.writeWithDeadline(p)
}
// Pread reads from the FD at the given offset. Always file semantics —
// sockets aren't seekable so this never goes through sock_recv.
func (fd *FD) Pread(p []byte, off int64) (int, error) {
if fd.closed {
return 0, ErrFileClosing
}
return syscall.Pread(fd.Sysfd, p, off)
}
// Pwrite writes to the FD at the given offset.
func (fd *FD) Pwrite(p []byte, off int64) (int, error) {
if fd.closed {
return 0, ErrFileClosing
}
return syscall.Pwrite(fd.Sysfd, p, off)
}
// SetDeadline sets both the read and write deadlines.
func (fd *FD) SetDeadline(t time.Time) error {
fd.rDeadline = t
fd.wDeadline = t
return nil
}
// SetReadDeadline sets the deadline for future Read calls. A zero t
// clears the deadline.
func (fd *FD) SetReadDeadline(t time.Time) error {
fd.rDeadline = t
return nil
}
// SetWriteDeadline sets the deadline for future Write calls.
func (fd *FD) SetWriteDeadline(t time.Time) error {
fd.wDeadline = t
return nil
}
// readWithDeadline implements the EAGAIN→park retry loop with deadline
// cancellation for non-socket FDs. The deadline is captured at function
// entry; later SetReadDeadline calls don't affect this in-flight Read.
func (fd *FD) readWithDeadline(p []byte) (int, error) {
deadline := fd.rDeadline
iov := wasiIovec{buf: &p[0], bufLen: uint32(len(p))}
for {
if !time.Now().Before(deadline) {
return 0, ErrDeadlineExceeded
}
var n uint32
errno := wasi_fd_read(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, unsafe.Pointer(&n))
switch errno {
case 0:
return int(n), nil
case wasiErrnoIntr:
continue
case wasiErrnoAgain:
if err := fd.parkUntil(pollModeRead, deadline); err != nil {
return 0, err
}
default:
return 0, syscall.Errno(errno)
}
}
}
func (fd *FD) writeWithDeadline(p []byte) (int, error) {
deadline := fd.wDeadline
var nn int
for {
if !time.Now().Before(deadline) {
return nn, ErrDeadlineExceeded
}
if nn == len(p) {
return nn, nil
}
buf := p[nn:]
iov := wasiIovec{buf: &buf[0], bufLen: uint32(len(buf))}
var n uint32
errno := wasi_fd_write(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, unsafe.Pointer(&n))
switch errno {
case 0:
nn += int(n)
case wasiErrnoIntr:
// retry
case wasiErrnoAgain:
if err := fd.parkUntil(pollModeWrite, deadline); err != nil {
return nn, err
}
default:
return nn, syscall.Errno(errno)
}
}
}
// sockRecv is the socket sibling of readWithDeadline / syscall.Read.
// Always issues sock_recv, regardless of deadline state, so callers
// that don't want fd_read on a socket get a guaranteed sock_recv path.
func (fd *FD) sockRecv(p []byte) (int, error) {
deadline := fd.rDeadline
iov := wasiIovec{buf: &p[0], bufLen: uint32(len(p))}
for {
if !deadline.IsZero() && !time.Now().Before(deadline) {
return 0, ErrDeadlineExceeded
}
var n uint32
var roFlags uint32
errno := wasi_sock_recv(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, 0, unsafe.Pointer(&n), unsafe.Pointer(&roFlags))
switch errno {
case 0:
return int(n), nil
case wasiErrnoIntr:
continue
case wasiErrnoAgain:
if deadline.IsZero() {
wait(fd.Sysfd, pollModeRead)
} else if err := fd.parkUntil(pollModeRead, deadline); err != nil {
return 0, err
}
default:
return 0, syscall.Errno(errno)
}
}
}
func (fd *FD) sockSend(p []byte) (int, error) {
deadline := fd.wDeadline
var nn int
for {
if !deadline.IsZero() && !time.Now().Before(deadline) {
return nn, ErrDeadlineExceeded
}
if nn == len(p) {
return nn, nil
}
buf := p[nn:]
iov := wasiIovec{buf: &buf[0], bufLen: uint32(len(buf))}
var n uint32
errno := wasi_sock_send(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, 0, unsafe.Pointer(&n))
switch errno {
case 0:
nn += int(n)
case wasiErrnoIntr:
// retry
case wasiErrnoAgain:
if deadline.IsZero() {
wait(fd.Sysfd, pollModeWrite)
} else if err := fd.parkUntil(pollModeWrite, deadline); err != nil {
return nn, err
}
default:
return nn, syscall.Errno(errno)
}
}
}
// wasi snapshot preview1 errno values, kept here so the read/write loops
// can compare without dragging in the full syscall errno table for a
// switch on three constants. These match the wasi spec exactly; see also
// syscall_libc_wasi.go in package syscall.
const (
wasiErrnoAgain uint32 = 6
wasiErrnoIntr uint32 = 27
)
// wait parks the current goroutine until the FD becomes ready in the
// given direction. No deadline; mirrors the helper of the same name in
// package syscall (intentionally duplicated rather than linknamed
// across the package boundary — see project memory on shim avoidance).
func wait(fd int, mode uint8) {
pd := runtime_netpoll_addwait(uint32(fd), mode)
task.Pause()
runtime_netpoll_done(pd)
}
// parkUntil parks the current goroutine on (fd, mode) with a deadline.
// Returns nil if the FD became ready or the timer fired (caller's loop
// re-checks the deadline at top); ErrDeadlineExceeded if the deadline
// was already in the past.
//
// Race handling: the deadline timer's callback and pollIO's event walk
// can both target the same pollDesc. The pd.fired flag guards against
// double-pushing the task to the run queue; whichever arrives second
// is a no-op.
func (fd *FD) parkUntil(mode uint8, deadline time.Time) error {
d := time.Until(deadline)
if d <= 0 {
return ErrDeadlineExceeded
}
pd := runtime_netpoll_addwait(uint32(fd.Sysfd), mode)
timer := time.AfterFunc(d, func() {
runtime_netpoll_wake(pd)
})
task.Pause()
timer.Stop()
runtime_netpoll_done(pd)
return nil
}
+92
View File
@@ -754,6 +754,98 @@ func (t *RawType) FieldAlign() int {
return t.Align()
}
// ConvertibleTo returns whether a value of type t can be converted to a variable of type u
func (r *RawType) ConvertibleTo(u *RawType) bool {
// This logic is mostly copied from Value.CanConvert
switch r.Kind() {
case Int, Int8, Int16, Int32, Int64:
switch u.Kind() {
case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return true
case Float32, Float64:
return true
case String:
return true
}
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
switch u.Kind() {
case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return true
case Float32, Float64:
return true
case String:
return true
}
case Float32, Float64:
switch u.Kind() {
case Int, Int8, Int16, Int32, Int64:
return true
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return true
case Float32, Float64:
return true
}
case Complex64, Complex128:
switch u.Kind() {
case Complex64, Complex128:
return true
}
case Slice:
switch u.Kind() {
case Array:
// This may fail at runtime if there isn't room
if r.elem() == u.elem() {
return true
}
case Pointer:
// This may fail at runtime if there isn't room
if u.elem().Kind() == Array && r.elem() == u.elem().elem() {
return true
}
case String:
// bytes or runes
if !r.elem().isNamed() && (r.elem().Kind() == Uint8 || r.elem().Kind() == Int32) {
return true
}
}
case String:
// bytes or runes
if u.Kind() == Slice && !u.elem().isNamed() && (u.elem().Kind() == Uint8 || u.elem().Kind() == Int32) {
return true
}
case Pointer:
if !r.isNamed() && u.Kind() == Pointer && !u.isNamed() && r.elem().underlying() == u.elem().underlying() {
return true
}
}
if r.underlying() == u.underlying() {
return true
}
if u.Kind() == Interface && u.NumMethod() == 0 {
return true
}
// TODO(dgryski): Unimplemented
// struct types
// channels
return false
}
// AssignableTo returns whether a value of type t can be assigned to a variable
// of type u.
func (t *RawType) AssignableTo(u Type) bool {
+103 -15
View File
@@ -51,6 +51,22 @@ func (v Value) isRO() bool {
return v.flags&(valueFlagRO) != 0
}
// These are package methods, not methods on Value, since the reflect.Value embeds a reflectlite.Value, so any
// methods added to reflectlite.Value are visible to the user.
func IsRO(v Value) bool {
return v.isRO()
}
func MakeRO(v Value, ro bool) Value {
if ro {
v.flags |= valueFlagRO
} else {
v.flags &^= valueFlagRO
}
return v
}
func (v Value) checkRO() {
if v.isRO() {
panic("reflect: value is not settable")
@@ -1485,13 +1501,11 @@ func convertOp(src Value, typ Type) (Value, bool) {
return cvtFloat(src, rtype), true
}
/*
case Complex64, Complex128:
switch src.Kind() {
case Complex64, Complex128:
return cvtComplex
}
*/
case Complex64, Complex128:
switch rtype := typ.(*RawType); rtype.Kind() {
case Complex64, Complex128:
return cvtComplex(src, rtype), true
}
case Slice:
switch rtype := typ.(*RawType); rtype.Kind() {
@@ -1534,6 +1548,12 @@ func convertOp(src Value, typ Type) (Value, bool) {
return cvtStringRunes(src, rtype), true
}
}
case Pointer:
rtype := typ.(*RawType)
if rtype.Kind() == Pointer && !src.typecode.isNamed() && !rtype.isNamed() && src.typecode.elem().underlying() == rtype.elem().underlying() {
return cvtDirect(src, rtype), true
}
}
// TODO(dgryski): Unimplemented:
@@ -1578,6 +1598,18 @@ func cvtFloat(v Value, t *RawType) Value {
return makeFloat(v.flags, v.Float(), t)
}
func cvtDirect(v Value, t *RawType) Value {
return Value{
typecode: t,
value: v.value,
flags: v.flags,
}
}
func cvtComplex(v Value, t *RawType) Value {
return makeComplex(v.flags, v.Complex(), t)
}
//go:linkname stringToBytes runtime.stringToBytes
func stringToBytes(x string) []byte
@@ -1661,20 +1693,76 @@ func makeFloat32(flags valueFlags, f float32, t *RawType) Value {
return v
}
func cvtIntString(src Value, t *RawType) Value {
panic("cvtUintString: unimplemented")
func makeComplex(flags valueFlags, f complex128, t *RawType) Value {
size := t.Size()
v := Value{
typecode: t,
flags: flags,
}
ptr := unsafe.Pointer(&v.value)
if size > unsafe.Sizeof(uintptr(0)) {
ptr = alloc(size, nil)
v.value = ptr
}
switch size {
case 8:
*(*complex64)(ptr) = complex64(f)
case 16:
*(*complex128)(ptr) = f
}
return v
}
func cvtUintString(src Value, t *RawType) Value {
panic("cvtUintString: unimplemented")
func cvtIntString(v Value, t *RawType) Value {
s := "\uFFFD"
if x := v.Int(); int64(rune(x)) == x {
s = string(rune(x))
}
return Value{
typecode: t,
value: unsafe.Pointer(&s),
flags: v.flags,
}
}
func cvtStringRunes(src Value, t *RawType) Value {
panic("cvsStringRunes: unimplemented")
func cvtUintString(v Value, t *RawType) Value {
s := "\uFFFD"
if x := v.Uint(); uint64(rune(x)) == x {
s = string(rune(x))
}
return Value{
typecode: t,
value: unsafe.Pointer(&s),
flags: v.flags,
}
}
func cvtRunesString(src Value, t *RawType) Value {
panic("cvsRunesString: unimplemented")
//go:linkname stringToRunes runtime.stringToRunes
func stringToRunes(s string) []rune
func cvtStringRunes(v Value, t *RawType) Value {
b := stringToRunes(*(*string)(v.value))
return Value{
typecode: t,
value: unsafe.Pointer(&b),
flags: v.flags,
}
}
//go:linkname stringFromRunes runtime.stringFromRunes
func stringFromRunes(r []rune) string
func cvtRunesString(v Value, t *RawType) Value {
s := stringFromRunes(*(*[]rune)(v.value))
return Value{
typecode: t,
value: unsafe.Pointer(&s),
flags: v.flags,
}
}
//go:linkname slicePanic runtime.slicePanic
+1 -1
View File
@@ -15,7 +15,7 @@ func GetRandom(p []byte, flags GetRandomFlag) (n int, err error) {
if len(p) == 0 {
return 0, nil
}
libc_arc4random_buf(unsafe.Pointer(&p[0]), uint(len(p)))
libc_arc4random_buf(unsafe.Pointer(unsafe.SliceData(p)), uint(len(p)))
return len(p), nil
}
+5
View File
@@ -42,3 +42,8 @@ func SystemStack() uintptr {
runtimePanic("scheduler is disabled")
return 0 // unreachable
}
func GoroutineStack() uintptr {
// No separate goroutine stack without a scheduler.
return 0
}
+11
View File
@@ -11,3 +11,14 @@ uintptr_t SystemStack() {
);
return sp;
}
uintptr_t GoroutineStack() {
uintptr_t sp;
asm volatile(
"mrs %0, PSP"
: "=r"(sp)
:
: "memory"
);
return sp;
}
+7
View File
@@ -70,3 +70,10 @@ func (s *state) pause() {
//
//export SystemStack
func SystemStack() uintptr
// GoroutineStack returns the PSP (goroutine stack pointer). When a fault
// occurs in goroutine context, the hardware saves the exception frame to PSP;
// reading PSP+0x18 gives the actual faulting PC.
//
//export GoroutineStack
func GoroutineStack() uintptr
+113
View File
@@ -0,0 +1,113 @@
//go:build badger2350
// Chip: RP2350A (QFN-60, 30 GPIO) -> target inherits from "rp2350".
// Pin source: https://github.com/pimoroni/badger2350/blob/main/board/pins.csv
//
// NOTES (verify before first flash):
// - POWER_EN (GPIO27) likely switches peripheral/display power.
// Verify polarity against the schematic; pull HIGH early if needed,
// otherwise the display will remain blank.
// - CHARGE_STAT is connected to EXT_GPIO2 (I2C IO expander) according to pins.csv,
// NOT the RP2350 -> intentionally not defined as a pin here.
package machine
// Crystal oscillator frequency.
const xoscFreq = 12 // MHz
// User buttons.
const (
BUTTON_A Pin = GPIO7
BUTTON_B Pin = GPIO9
BUTTON_C Pin = GPIO10
BUTTON_UP Pin = GPIO11
BUTTON_DOWN Pin = GPIO6
BUTTON_HOME Pin = GPIO22
BUTTON_BOOT Pin = BUTTON_HOME
// System / RTC-related buttons.
BUTTON_RESET Pin = GPIO14
BUTTON_INT Pin = GPIO15
)
// Case LEDs (4-zone mono illumination), named CL0..CL3 in the docs.
const (
CL0 Pin = GPIO0
CL1 Pin = GPIO1
CL2 Pin = GPIO2
CL3 Pin = GPIO3
// Convention: first zone as default LED.
LED = CL0
)
// E-ink display (Inky), on SPI0.
const (
INKY_BUSY Pin = GPIO16
INKY_CS Pin = GPIO17
INKY_SCLK Pin = GPIO18
INKY_MOSI Pin = GPIO19
INKY_DC Pin = GPIO20
INKY_RES Pin = GPIO21
)
// SPI0 default pins == Inky bus. SDI is unused by the EPD,
// but GPIO16 is a valid SPI0 RX line on the RP2350.
const (
SPI0_SCK_PIN = GPIO18 // INKY_SCLK
SPI0_SDO_PIN = GPIO19 // INKY_MOSI
SPI0_SDI_PIN = GPIO16 // (unused by the EPD)
)
// SPI1 is not routed to any header; NoPin satisfies the machine package.
const (
SPI1_SCK_PIN Pin = NoPin
SPI1_SDO_PIN Pin = NoPin
SPI1_SDI_PIN Pin = NoPin
)
// UART pins - default RP2350 UART0 mapping.
// Note: GPIO0/GPIO1 are also CL0/CL1 (case LEDs)
// Do not use UART0 and LEDs simultaneously.
const (
UART0_TX_PIN = GPIO0
UART0_RX_PIN = GPIO1
UART_TX_PIN = UART0_TX_PIN
UART_RX_PIN = UART0_RX_PIN
)
// I2C0 (Qwiic/STEMMA QT + RTC PCF85063A).
const (
I2C0_SDA_PIN = GPIO4
I2C0_SCL_PIN = GPIO5
)
// I2C1 is not routed to any header; NoPin satisfies the machine package.
const (
I2C1_SDA_PIN Pin = NoPin
I2C1_SCL_PIN Pin = NoPin
)
// Power / Sensing.
const (
VBUS_DETECT Pin = GPIO12
RTC_ALARM Pin = GPIO13
VBAT_SENSE Pin = GPIO26
POWER_EN Pin = GPIO27
SENSE_1V1 Pin = GPIO28
BATTERY = VBAT_SENSE
)
var DefaultUART = UART0
// USB identifiers
const (
usb_STRING_PRODUCT = "Badger 2350"
usb_STRING_MANUFACTURER = "Pimoroni"
)
var (
usb_VID uint16 = 0x2E8A
usb_PID uint16 = 0x0005
)
+112
View File
@@ -0,0 +1,112 @@
//go:build blinky2350
// Chip: RP2350A (QFN-60, 30 GPIO) -> target inherits from "rp2350".
// Pin source: https://github.com/pimoroni/blinky2350/blob/main/board/pins.csv
//
// NOTES (verify before first flash):
// - POWER_EN (GPIO27) likely switches peripheral power.
// Verify polarity against the schematic; pull HIGH early if needed,
// otherwise the display will remain blank.
// - CHARGE_STAT is connected to EXT_GPIO2 (I2C IO expander) according to pins.csv,
// NOT the RP2350 -> intentionally not defined as a pin here.
// - The dot matrix is NOT driven via SPI (Latch/Blank/
// Row-Clock are not SPI signals) -> use PIO or bit-banging.
// SPI0 and SPI1 are therefore set to NoPin (required by the machine package).
package machine
// Crystal oscillator frequency.
const xoscFreq = 12 // MHz
// User buttons.
const (
BUTTON_A Pin = GPIO7
BUTTON_B Pin = GPIO9
BUTTON_C Pin = GPIO10
BUTTON_UP Pin = GPIO11
BUTTON_DOWN Pin = GPIO6
BUTTON_HOME Pin = GPIO22
BUTTON_BOOT Pin = BUTTON_HOME
BUTTON_RESET Pin = GPIO14
BUTTON_INT Pin = GPIO15
)
// Case LEDs (4-zone mono illumination), named CL0..CL3 in the docs.
const (
CL0 Pin = GPIO0
CL1 Pin = GPIO1
CL2 Pin = GPIO2
CL3 Pin = GPIO3
LED = CL0
)
// LED dot matrix: column shift register + row driver.
// NOT SPI -> drive via PIO or bit-banging.
const (
DISPLAY_COL_SCLK Pin = GPIO16
DISPLAY_COL_DATA Pin = GPIO17
DISPLAY_COL_LATCH Pin = GPIO18
DISPLAY_COL_BLANK Pin = GPIO19
DISPLAY_ROW_DATA Pin = GPIO20
DISPLAY_ROW_CLK Pin = GPIO21
)
// I2C0 (Qwiic/STEMMA QT + RTC).
const (
I2C0_SDA_PIN = GPIO4
I2C0_SCL_PIN = GPIO5
)
// I2C1 is not routed to any header; NoPin satisfies the machine package.
const (
I2C1_SDA_PIN Pin = NoPin
I2C1_SCL_PIN Pin = NoPin
)
// SPI pins - the LED matrix is driven via PIO/bit-banging, not SPI.
// NoPin satisfies the machine package requirements.
const (
SPI0_SCK_PIN Pin = NoPin
SPI0_SDO_PIN Pin = NoPin
SPI0_SDI_PIN Pin = NoPin
SPI1_SCK_PIN Pin = NoPin
SPI1_SDO_PIN Pin = NoPin
SPI1_SDI_PIN Pin = NoPin
)
// UART pins - default RP2350 UART0 mapping.
// Note: GPIO0/GPIO1 are also CL0/CL1 (case LEDs)
// Do not use UART0 and LEDs simultaneously.
const (
UART0_TX_PIN = GPIO0
UART0_RX_PIN = GPIO1
UART_TX_PIN = UART0_TX_PIN
UART_RX_PIN = UART0_RX_PIN
)
// Power / sensing.
const (
VBUS_DETECT Pin = GPIO12
RTC_ALARM Pin = GPIO13
VBAT_SENSE Pin = GPIO26
POWER_EN Pin = GPIO27
SENSE_1V1 Pin = GPIO28
BATTERY = VBAT_SENSE
)
var DefaultUART = UART0
// USB identifiers
const (
usb_STRING_PRODUCT = "Blinky 2350"
usb_STRING_MANUFACTURER = "Pimoroni"
)
var (
usb_VID uint16 = 0x2E8A
usb_PID uint16 = 0x0005
)
+144
View File
@@ -0,0 +1,144 @@
//go:build esp32s3_box3
// This file contains the pin mappings for the ESP32-S3-BOX-3 board.
//
// ESP32-S3-BOX-3 is an AI voice development kit with 2.4" display.
// - https://github.com/espressif/esp-box
//
// Based on ESP-BOX-3 BSP from Espressif.
package machine
// CPUFrequency returns the current CPU frequency for ESP32-S3-BOX-3.
// Returns 240MHz (max speed for ESP32-S3 with PSRAM)
func CPUFrequency() uint32 {
return 240_000_000 // 240MHz
}
const (
// GPIO pins available on ESP32-S3-BOX-3
// Based on ESP-BOX-3 BSP pin definitions
IO0 = GPIO0 // Button Config
IO1 = GPIO1 // Button Mute / Mute Status
IO2 = GPIO2 // I2S MCLK
IO3 = GPIO3 // LCD Touch Interrupt
IO4 = GPIO4 // LCD DC
IO5 = GPIO5 // LCD CS
IO6 = GPIO6 // LCD DATA0 (MOSI)
IO7 = GPIO7 // LCD PCLK (SCK)
IO8 = GPIO8 // I2C SDA
IO9 = GPIO9 // SD Card D0
IO10 = GPIO10
IO11 = GPIO11 // SD Card CLK
IO12 = GPIO12 // SD Card D3
IO13 = GPIO13 // SD Card D1
IO14 = GPIO14 // SD Card CMD
IO15 = GPIO15 // I2S DOUT (Speaker)
IO16 = GPIO16 // I2S DSIN (Microphone)
IO17 = GPIO17 // I2S SCLK
IO18 = GPIO18 // I2C SCL
IO19 = GPIO19 // USB_NEG
IO20 = GPIO20 // USB_POS
IO21 = GPIO21
IO38 = GPIO38
IO39 = GPIO39
IO40 = GPIO40 // I2C Dock SCL
IO41 = GPIO41 // I2C Dock SDA
IO42 = GPIO42 // SD Card D2
IO43 = GPIO43 // SD Card Power
IO44 = GPIO44 // (Unused)
IO45 = GPIO45 // I2S LCLK (LRCK)
IO46 = GPIO46 // Power Amp Control
IO47 = GPIO47 // LCD Backlight
IO48 = GPIO48 // LCD Reset
)
// SPI pins
const (
// SPI1 - used for LCD
SPI1_SCK_PIN = IO7 // LCD_PCLK
SPI1_MOSI_PIN = IO6 // LCD_DATA0 (MOSI)
SPI1_MISO_PIN = NoPin // Not used for LCD
SPI1_CS_PIN = IO5 // LCD_CS
// SPI2 (not used on this board)
SPI2_SCK_PIN = NoPin
SPI2_MOSI_PIN = NoPin
SPI2_MISO_PIN = NoPin
SPI2_CS_PIN = NoPin
)
// LCD pins (ST7789/ILI9341)
const (
LCD_SCK_PIN = SPI1_SCK_PIN
LCD_SDO_PIN = SPI1_MOSI_PIN
LCD_SDI_PIN = NoPin
LCD_SS_PIN = SPI1_CS_PIN
LCD_DC_PIN = IO4
LCD_RST_PIN = IO48
LCD_BL_PIN = IO47
)
// I2C pins (Internal I2C for sensors)
const (
SDA0_PIN = IO8
SCL0_PIN = IO18
SDA_PIN = SDA0_PIN
SCL_PIN = SCL0_PIN
)
// Dock I2C pins (for expansion dock)
const (
SDA1_PIN = IO41
SCL1_PIN = IO40
)
// UART pins (USB CDC - native USB on ESP32-S3)
// Note: Native USB doesn't require GPIO pins, these are kept for compatibility
const (
UART_TX_PIN = NoPin
UART_RX_PIN = NoPin
)
// Built-in LED
// Using LCD backlight as LED indicator
const LED = GPIO47
// Buttons
const (
BUTTON_CONFIG = GPIO0
BUTTON_MUTE = GPIO1
)
// Audio pins (I2S)
const (
I2S_SCLK_PIN = IO17 // Bit Clock
I2S_MCLK_PIN = IO2 // Master Clock
I2S_LRCK_PIN = IO45 // Left/Right Clock (Frame Sync)
I2S_DOUT_PIN = IO15 // Data Out to Speaker (ES8311)
I2S_DSIN_PIN = IO16 // Data In from Microphone (ES7210)
)
// SD Card pins (SD/MMC mode)
const (
SDCARD_CMD_PIN = IO14
SDCARD_CLK_PIN = IO11
SDCARD_D0_PIN = IO9
SDCARD_D1_PIN = IO13
SDCARD_D2_PIN = IO42
SDCARD_D3_PIN = IO12
SDCARD_PWR_PIN = IO43 // SD Card Power
)
// USB pins (native USB)
const (
USB_DPPIN = IO20 // USB D+
USB_DMPIN = IO19 // USB D-
)
// Touch interrupt
const TOUCH_INT_PIN = IO3
// Power amplifier control
const POWER_AMP_PIN = IO46
@@ -41,6 +41,40 @@ const (
D29 Pin = GPIO29
)
// GPIO pin aliases
const (
GP0 Pin = GPIO0
GP1 Pin = GPIO1
GP2 Pin = GPIO2
GP3 Pin = GPIO3
GP4 Pin = GPIO4
GP5 Pin = GPIO5
GP6 Pin = GPIO6
GP7 Pin = GPIO7
GP8 Pin = GPIO8
GP9 Pin = GPIO9
GP10 Pin = GPIO10
GP11 Pin = GPIO11
GP12 Pin = GPIO12
GP13 Pin = GPIO13
GP14 Pin = GPIO14
GP15 Pin = GPIO15
GP16 Pin = GPIO16
GP17 Pin = GPIO17
GP18 Pin = GPIO18
GP19 Pin = GPIO19
GP20 Pin = GPIO20
GP21 Pin = GPIO21
GP22 Pin = GPIO22
GP23 Pin = GPIO23
GP24 Pin = GPIO24
GP25 Pin = GPIO25
GP26 Pin = GPIO26
GP27 Pin = GPIO27
GP28 Pin = GPIO28
GP29 Pin = GPIO29
)
// Analog pins
const (
A0 Pin = D26
+57
View File
@@ -0,0 +1,57 @@
//go:build xiao_esp32c6
// This file contains the pin mappings for the Seeed XIAO ESP32C6 board.
//
// Seeed Studio XIAO ESP32C6 is an IoT mini development board based on
// the Espressif ESP32-C6 WiFi 6 / Bluetooth 5 / 802.15.4 chip.
//
// - https://www.seeedstudio.com/Seeed-Studio-XIAO-ESP32C6-p-5884.html
// - https://wiki.seeedstudio.com/xiao_esp32c6_getting_started/
package machine
// Digital Pins
const (
D0 = GPIO0
D1 = GPIO1
D2 = GPIO2
D3 = GPIO21
D4 = GPIO22
D5 = GPIO23
D6 = GPIO16
D7 = GPIO17
D8 = GPIO19
D9 = GPIO20
D10 = GPIO18
)
// Analog pins
const (
A0 = GPIO0
A1 = GPIO1
A2 = GPIO2
)
// UART pins
const (
UART_TX_PIN = GPIO16
UART_RX_PIN = GPIO17
)
// I2C pins
const (
SDA_PIN = GPIO22
SCL_PIN = GPIO23
)
// SPI pins
const (
SPI_SCK_PIN = GPIO19
SPI_SDI_PIN = GPIO20
SPI_SDO_PIN = GPIO18
)
// Onboard LEDs
const (
LED = GPIO15
)
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 || esp32s3
//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 || esp32c6 || esp32s3
package machine
+23 -18
View File
@@ -23,19 +23,6 @@ const (
NumberOfUSBEndpoints = 8
)
var (
endPoints = []uint32{
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
}
)
// Configure the USB peripheral. The config is here for compatibility with the UART interface.
func (dev *USBDevice) Configure(config UARTConfig) {
if dev.initcomplete {
@@ -188,7 +175,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
// Now the actual transfer handlers, ignore endpoint number 0 (setup)
var i uint32
for i = 1; i < uint32(len(endPoints)); i++ {
for i = 1; i < NumberOfUSBEndpoints; i++ {
// Check if endpoint has a pending interrupt
epFlags := getEPINTFLAG(i)
setEPINTFLAG(i, epFlags)
@@ -206,6 +193,8 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
func initEndpoint(ep, config uint32) {
// Note: Both IN (Bank 1) and OUT (Bank 0) configurations share the same EPCFG register.
// We must use getEPCFG(ep) | ... to avoid clearing/disabling the opposite direction.
switch config {
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
// set packet size
@@ -215,7 +204,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT1)
@@ -227,7 +216,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
// receive interrupts when current transfer complete
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0)
@@ -246,7 +235,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
// receive interrupts when current transfer complete
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0)
@@ -265,7 +254,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
// NAK on endpoint IN, the bank is not yet filled in.
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY)
@@ -663,3 +652,19 @@ func setEPINTENSET(ep uint32, val uint8) {
return
}
}
func (dev *USBDevice) SetStallEPIn(ep uint32) {
setEPSTATUSSET(ep, sam.USB_DEVICE_EPSTATUSSET_STALLRQ1)
}
func (dev *USBDevice) SetStallEPOut(ep uint32) {
setEPSTATUSSET(ep, sam.USB_DEVICE_EPSTATUSSET_STALLRQ0)
}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_STALLRQ1)
}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_STALLRQ0)
}
+23 -18
View File
@@ -23,19 +23,6 @@ const (
NumberOfUSBEndpoints = 8
)
var (
endPoints = []uint32{
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
}
)
// Configure the USB peripheral. The config is here for compatibility with the UART interface.
func (dev *USBDevice) Configure(config UARTConfig) {
if dev.initcomplete {
@@ -191,7 +178,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
// Now the actual transfer handlers, ignore endpoint number 0 (setup)
var i uint32
for i = 1; i < uint32(len(endPoints)); i++ {
for i = 1; i < NumberOfUSBEndpoints; i++ {
// Check if endpoint has a pending interrupt
epFlags := getEPINTFLAG(i)
setEPINTFLAG(i, epFlags)
@@ -209,6 +196,8 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
func initEndpoint(ep, config uint32) {
// Note: Both IN (Bank 1) and OUT (Bank 0) configurations share the same EPCFG register.
// We must use getEPCFG(ep) | ... to avoid clearing/disabling the opposite direction.
switch config {
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
// set packet size
@@ -218,7 +207,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT1)
@@ -230,7 +219,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
// receive interrupts when current transfer complete
setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT0)
@@ -249,7 +238,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
// receive interrupts when current transfer complete
setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT0)
@@ -268,7 +257,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
// NAK on endpoint IN, the bank is not yet filled in.
setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK1RDY)
@@ -494,3 +483,19 @@ func setEPINTENCLR(ep uint32, val uint8) {
func setEPINTENSET(ep uint32, val uint8) {
sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTENSET.Set(val)
}
func (dev *USBDevice) SetStallEPIn(ep uint32) {
setEPSTATUSSET(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSSET_STALLRQ1)
}
func (dev *USBDevice) SetStallEPOut(ep uint32) {
setEPSTATUSSET(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSSET_STALLRQ0)
}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_STALLRQ1)
}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_STALLRQ0)
}
+39 -5
View File
@@ -295,16 +295,33 @@ var DefaultUART = UART0
var (
UART0 = &_UART0
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
_UART0 = UART{
Bus: esp.UART0,
Buffer: NewRingBuffer(),
TXRXSignal: 14,
RTSCTSSignal: 15,
}
UART1 = &_UART1
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
_UART1 = UART{
Bus: esp.UART1,
Buffer: NewRingBuffer(),
TXRXSignal: 17,
RTSCTSSignal: 18,
}
UART2 = &_UART2
_UART2 = UART{Bus: esp.UART2, Buffer: NewRingBuffer()}
_UART2 = UART{
Bus: esp.UART2,
Buffer: NewRingBuffer(),
TXRXSignal: 198,
RTSCTSSignal: 199,
}
)
type UART struct {
Bus *esp.UART_Type
Buffer *RingBuffer
Bus *esp.UART_Type
Buffer *RingBuffer
TXRXSignal uint32
RTSCTSSignal uint32
}
func (uart *UART) Configure(config UARTConfig) {
@@ -312,6 +329,22 @@ func (uart *UART) Configure(config UARTConfig) {
config.BaudRate = 115200
}
uart.Bus.CLKDIV.Set(peripheralClock / config.BaudRate)
if config.RX != NoPin {
config.RX.configure(PinConfig{Mode: PinInputPullup}, uart.TXRXSignal)
}
if config.TX != NoPin {
config.TX.configure(PinConfig{Mode: PinOutput}, uart.TXRXSignal)
}
if config.RTS != NoPin {
config.RTS.configure(PinConfig{Mode: PinOutput}, uart.RTSCTSSignal)
}
if config.CTS != NoPin {
config.CTS.configure(PinConfig{Mode: PinInputPullup}, uart.RTSCTSSignal)
}
}
func (uart *UART) writeByte(b byte) error {
@@ -319,6 +352,7 @@ func (uart *UART) writeByte(b byte) error {
// Read UART_TXFIFO_CNT from the status register, which indicates how
// many bytes there are in the transmit buffer. Wait until there are
// less than 128 bytes in this buffer (the default buffer size).
gosched()
}
// Write to the TX_FIFO register.
(*volatile.Register8)(unsafe.Add(unsafe.Pointer(uart.Bus), 0x200C0000)).Set(b)
+7
View File
@@ -19,3 +19,10 @@ var (
useExt1: false,
}
)
// enableI2C0PeriphClock enables the I2C0 peripheral clock via SYSTEM.
func enableI2C0PeriphClock() {
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1)
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0)
}
+546
View File
@@ -0,0 +1,546 @@
//go:build esp32c6
package machine
import (
"device/esp"
"device/riscv"
"errors"
"runtime/interrupt"
"runtime/volatile"
"sync"
"unsafe"
)
const deviceName = esp.Device
const maxPin = 31
const cpuInterruptFromPin = 6
// CPUFrequency returns the current CPU frequency of the chip.
// Currently it is a fixed frequency but it may allow changing in the future.
func CPUFrequency() uint32 {
return 160e6 // 160MHz
}
const (
PinOutput PinMode = iota
PinInput
PinInputPullup
PinInputPulldown
PinAnalog
)
const (
GPIO0 Pin = 0
GPIO1 Pin = 1
GPIO2 Pin = 2
GPIO3 Pin = 3
GPIO4 Pin = 4
GPIO5 Pin = 5
GPIO6 Pin = 6
GPIO7 Pin = 7
GPIO8 Pin = 8
GPIO9 Pin = 9
GPIO10 Pin = 10
GPIO11 Pin = 11
GPIO12 Pin = 12
GPIO13 Pin = 13
GPIO14 Pin = 14
GPIO15 Pin = 15
GPIO16 Pin = 16
GPIO17 Pin = 17
GPIO18 Pin = 18
GPIO19 Pin = 19
GPIO20 Pin = 20
GPIO21 Pin = 21
GPIO22 Pin = 22
GPIO23 Pin = 23
GPIO24 Pin = 24
GPIO25 Pin = 25
GPIO26 Pin = 26
GPIO27 Pin = 27
GPIO28 Pin = 28
GPIO29 Pin = 29
GPIO30 Pin = 30
)
const (
ADC0 Pin = GPIO0
ADC1 Pin = GPIO1
ADC2 Pin = GPIO2
ADC3 Pin = GPIO3
ADC4 Pin = GPIO4
ADC5 Pin = GPIO5
ADC6 Pin = GPIO6
)
type PinChange uint8
// Pin change interrupt constants for SetInterrupt.
const (
PinRising PinChange = iota + 1
PinFalling
PinToggle
)
// Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) {
if p == NoPin {
// This simplifies pin configuration in peripherals such as SPI.
return
}
var muxConfig uint32
// Configure this pin as a GPIO pin.
const function = 1 // function 1 is GPIO for every pin
muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos
// FUN_IE: disable for PinAnalog (high-Z for ADC)
if config.Mode != PinAnalog {
muxConfig |= esp.IO_MUX_GPIO_FUN_IE
}
// Set drive strength: 0 is lowest, 3 is highest.
muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos
// Select pull mode (no pulls for PinAnalog).
if config.Mode == PinInputPullup {
muxConfig |= esp.IO_MUX_GPIO_FUN_WPU
} else if config.Mode == PinInputPulldown {
muxConfig |= esp.IO_MUX_GPIO_FUN_WPD
}
// Configure the pad with the given IO mux configuration.
p.mux().Set(muxConfig)
// Set the output signal to the simple GPIO output.
p.outFunc().Set(0x80)
switch config.Mode {
case PinOutput:
// Set the 'output enable' bit.
esp.GPIO.ENABLE_W1TS.Set(1 << p)
case PinInput, PinInputPullup, PinInputPulldown, PinAnalog:
// Clear the 'output enable' bit.
esp.GPIO.ENABLE_W1TC.Set(1 << p)
}
}
// configure is the same as Configure, but allows setting a specific GPIO matrix signal.
func (p Pin) configure(config PinConfig, signal uint32) {
p.Configure(config)
if signal == 256 {
return
}
if config.Mode == PinOutput {
p.outFunc().Set(signal)
} else {
inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p))
}
}
func initI2CExt1Clock() {}
// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the
// output function selection.
func (p Pin) outFunc() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4))
}
func (p Pin) pinReg() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4)))
}
// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input
// function selection.
func inFunc(signal uint32) *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*4))
}
// mux returns the I/O mux configuration register corresponding to the given
// GPIO pin.
func (p Pin) mux() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4))
}
// pin returns the PIN register corresponding to the given GPIO pin.
func (p Pin) pin() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), uintptr(p)*4))
}
// Set the pin to high or low.
// Warning: only use this on an output pin!
func (p Pin) Set(value bool) {
if value {
reg, mask := p.portMaskSet()
reg.Set(mask)
} else {
reg, mask := p.portMaskClear()
reg.Set(mask)
}
}
// Get returns the current value of a GPIO pin when configured as an input or as
// an output.
func (p Pin) Get() bool {
reg := &esp.GPIO.IN
return (reg.Get()>>p)&1 > 0
}
// Return the register and mask to enable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskSet() (*uint32, uint32) {
reg, mask := p.portMaskSet()
return &reg.Reg, mask
}
// Return the register and mask to disable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskClear() (*uint32, uint32) {
reg, mask := p.portMaskClear()
return &reg.Reg, mask
}
func (p Pin) portMaskSet() (*volatile.Register32, uint32) {
return &esp.GPIO.OUT_W1TS, 1 << p
}
func (p Pin) portMaskClear() (*volatile.Register32, uint32) {
return &esp.GPIO.OUT_W1TC, 1 << p
}
// SetInterrupt sets an interrupt to be executed when a particular pin changes
// state. The pin should already be configured as an input, including a pull up
// or down if no external pull is provided.
//
// You can pass a nil func to unset the pin change interrupt. If you do so,
// the change parameter is ignored and can be set to any value (such as 0).
// If the pin is already configured with a callback, you must first unset
// this pins interrupt before you can set a new callback.
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) {
if p >= maxPin {
return ErrInvalidInputPin
}
if callback == nil {
// Disable this pin interrupt
p.pin().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk)
if pinCallbacks[p] != nil {
pinCallbacks[p] = nil
}
return nil
}
if pinCallbacks[p] != nil {
// The pin was already configured.
// To properly re-configure a pin, unset it first and set a new
// configuration.
return ErrNoPinChangeChannel
}
pinCallbacks[p] = callback
onceSetupPinInterrupt.Do(func() {
err = setupPinInterrupt()
})
if err != nil {
return err
}
p.pin().Set(
(p.pin().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) |
uint32(change)<<esp.GPIO_PIN_INT_TYPE_Pos | uint32(1)<<esp.GPIO_PIN_INT_ENA_Pos)
return nil
}
var (
pinCallbacks [maxPin]func(Pin)
onceSetupPinInterrupt sync.Once
)
func setupPinInterrupt() error {
esp.INTERRUPT_CORE0.GPIO_INTERRUPT_PRO_MAP.Set(cpuInterruptFromPin)
return interrupt.New(cpuInterruptFromPin, func(interrupt.Interrupt) {
status := esp.GPIO.STATUS.Get()
// Clear before processing so new edges during callbacks are not lost.
esp.GPIO.STATUS_W1TC.Set(status)
for i, mask := 0, uint32(1); i < maxPin; i, mask = i+1, mask<<1 {
if (status&mask) != 0 && pinCallbacks[i] != nil {
pinCallbacks[i](Pin(i))
}
}
}).Enable()
}
var (
DefaultUART = UART0
UART0 = &_UART0
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
UART1 = &_UART1
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
onceUart = sync.Once{}
errSamePins = errors.New("UART: invalid pin combination")
errWrongUART = errors.New("UART: unsupported UARTn")
errWrongBitSize = errors.New("UART: invalid data size")
errWrongStopBitSize = errors.New("UART: invalid bit size")
)
type UART struct {
Bus *esp.UART_Type
Buffer *RingBuffer
ParityErrorDetected bool // set when parity error detected
DataErrorDetected bool // set when data corruption detected
DataOverflowDetected bool // set when data overflow detected in UART FIFO buffer or RingBuffer
}
const (
defaultDataBits = 8
defaultStopBit = 1
defaultParity = ParityNone
uartInterrupts = esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA |
esp.UART_INT_ENA_PARITY_ERR_INT_ENA |
esp.UART_INT_ENA_FRM_ERR_INT_ENA |
esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA |
esp.UART_INT_ENA_GLITCH_DET_INT_ENA
pplClockFreq = 80e6
)
type registerSet struct {
interruptMapReg *volatile.Register32
gpioMatrixSignal uint32
}
func (uart *UART) Configure(config UARTConfig) error {
if config.BaudRate == 0 {
config.BaudRate = 115200
}
if config.TX == config.RX {
return errSamePins
}
switch {
case uart.Bus == esp.UART0:
return uart.configure(config, registerSet{
interruptMapReg: &esp.INTERRUPT_CORE0.UART0_INTR_MAP,
gpioMatrixSignal: 6,
})
case uart.Bus == esp.UART1:
return uart.configure(config, registerSet{
interruptMapReg: &esp.INTERRUPT_CORE0.UART1_INTR_MAP,
gpioMatrixSignal: 9,
})
}
return errWrongUART
}
func (uart *UART) configure(config UARTConfig, regs registerSet) error {
initUARTClock(uart.Bus)
// - disable TX/RX clock to make sure the UART transmitter or receiver is not at work during configuration
uart.Bus.SetCLK_CONF_TX_SCLK_EN(0)
uart.Bus.SetCLK_CONF_RX_SCLK_EN(0)
// - default clock source: 1=XTAL_CLK, 2=RC_FAST_CLK, 3=FOSC_CLK
// On C6, SCLK_SEL=1 selects XTAL (40 MHz). Use FOSC (80 MHz) for
// better baud-rate accuracy.
uart.Bus.SetCLK_CONF_SCLK_SEL(3) // FOSC_CLK (80 MHz)
// reset divisor of the divider via UART_SCLK_DIV_NUM, UART_SCLK_DIV_A, and UART_SCLK_DIV_B
uart.Bus.SetCLK_CONF_SCLK_DIV_NUM(0)
uart.Bus.SetCLK_CONF_SCLK_DIV_A(0)
uart.Bus.SetCLK_CONF_SCLK_DIV_B(0)
uart.SetBaudRate(config.BaudRate)
uart.SetFormat(defaultDataBits, defaultStopBit, defaultParity)
// - set UART mode
uart.Bus.SetRS485_CONF_RS485_EN(0)
uart.Bus.SetRS485_CONF_RS485TX_RX_EN(0)
uart.Bus.SetRS485_CONF_RS485RXBY_TX_EN(0)
uart.Bus.SetCONF0_IRDA_EN(0)
// - disable hw-flow control
uart.Bus.SetCONF0_TX_FLOW_EN(0)
// synchronize values into Core Clock
uart.Bus.SetREG_UPDATE(1)
uart.setupPins(config, regs)
uart.configureInterrupt(regs.interruptMapReg)
uart.enableTransmitter()
uart.enableReceiver()
// Start TX/RX
uart.Bus.SetCLK_CONF_TX_SCLK_EN(1)
uart.Bus.SetCLK_CONF_RX_SCLK_EN(1)
return nil
}
func (uart *UART) SetFormat(dataBits, stopBits int, parity UARTParity) error {
if dataBits < 5 {
return errWrongBitSize
}
if stopBits > 1 {
return errWrongStopBitSize
}
// - data length
uart.Bus.SetCONF0_BIT_NUM(uint32(dataBits - 5))
// - stop bit
uart.Bus.SetCONF0_STOP_BIT_NUM(uint32(stopBits))
// - parity check
switch parity {
case ParityNone:
uart.Bus.SetCONF0_PARITY_EN(0)
case ParityEven:
uart.Bus.SetCONF0_PARITY_EN(1)
uart.Bus.SetCONF0_PARITY(0)
case ParityOdd:
uart.Bus.SetCONF0_PARITY_EN(1)
uart.Bus.SetCONF0_PARITY(1)
}
return nil
}
func initUARTClock(bus *esp.UART_Type) {
// On ESP32-C6, UART clock is controlled via PCR (Peripheral Clock Reset).
switch bus {
case esp.UART0:
esp.PCR.SetUART0_CONF_UART0_CLK_EN(1)
esp.PCR.SetUART0_CONF_UART0_RST_EN(0) // ensure not in reset
bus.SetCLK_CONF_RST_CORE(1)
esp.PCR.SetUART0_CONF_UART0_RST_EN(1)
esp.PCR.SetUART0_CONF_UART0_RST_EN(0)
bus.SetCLK_CONF_RST_CORE(0)
// Configure SCLK divisor in PCR
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(0)
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_A(0)
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_B(0)
case esp.UART1:
esp.PCR.SetUART1_CONF_UART1_CLK_EN(1)
esp.PCR.SetUART1_CONF_UART1_RST_EN(0)
bus.SetCLK_CONF_RST_CORE(1)
esp.PCR.SetUART1_CONF_UART1_RST_EN(1)
esp.PCR.SetUART1_CONF_UART1_RST_EN(0)
bus.SetCLK_CONF_RST_CORE(0)
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(0)
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_A(0)
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_B(0)
}
// wait for Core Clock to ready for configuration
for bus.GetREG_UPDATE() > 0 {
riscv.Asm("nop")
}
}
func (uart *UART) SetBaudRate(baudRate uint32) {
// based on esp-idf
max_div := uint32((1 << 12) - 1)
sclk_div := (pplClockFreq + (max_div * baudRate) - 1) / (max_div * baudRate)
clk_div := (pplClockFreq << 4) / (baudRate * sclk_div)
uart.Bus.SetCLKDIV(clk_div >> 4)
uart.Bus.SetCLKDIV_FRAG(clk_div & 0xf)
// On C6, the SCLK divisor is in the PCR register.
switch uart.Bus {
case esp.UART0:
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(sclk_div - 1)
case esp.UART1:
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(sclk_div - 1)
}
}
func (uart *UART) setupPins(config UARTConfig, regs registerSet) {
config.RX.Configure(PinConfig{Mode: PinInputPullup})
config.TX.Configure(PinConfig{Mode: PinInputPullup})
// link TX with GPIO signal X (technical reference manual, GPIO matrix)
config.TX.outFunc().Set(regs.gpioMatrixSignal)
// link RX with GPIO signal X and route signals via GPIO matrix
inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX))
}
func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) {
// Disable all UART interrupts
uart.Bus.INT_ENA.ClearBits(0x0ffff)
intrMapReg.Set(7)
onceUart.Do(func() {
_ = interrupt.New(7, func(i interrupt.Interrupt) {
UART0.serveInterrupt(0)
UART1.serveInterrupt(1)
}).Enable()
})
}
func (uart *UART) serveInterrupt(num int) {
// get interrupt status
interrutFlag := uart.Bus.INT_ST.Get()
if (interrutFlag & uartInterrupts) == 0 {
return
}
// block UART interrupts while processing
uart.Bus.INT_ENA.ClearBits(uartInterrupts)
if interrutFlag&esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA > 0 {
for uart.Bus.GetSTATUS_RXFIFO_CNT() > 0 {
b := uart.Bus.GetFIFO_RXFIFO_RD_BYTE()
if !uart.Buffer.Put(byte(b & 0xff)) {
uart.DataOverflowDetected = true
}
}
}
if interrutFlag&esp.UART_INT_ENA_PARITY_ERR_INT_ENA > 0 {
uart.ParityErrorDetected = true
}
if 0 != interrutFlag&esp.UART_INT_ENA_FRM_ERR_INT_ENA {
uart.DataErrorDetected = true
}
if 0 != interrutFlag&esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA {
uart.DataOverflowDetected = true
}
if 0 != interrutFlag&esp.UART_INT_ENA_GLITCH_DET_INT_ENA {
uart.DataErrorDetected = true
}
// Clear the UART interrupt status
uart.Bus.INT_CLR.SetBits(interrutFlag)
uart.Bus.INT_CLR.ClearBits(interrutFlag)
// Enable interrupts
uart.Bus.INT_ENA.Set(uartInterrupts)
}
const uart_empty_thresh_default = 10
func (uart *UART) enableTransmitter() {
uart.Bus.SetCONF0_TXFIFO_RST(1)
uart.Bus.SetCONF0_TXFIFO_RST(0)
uart.Bus.SetCONF1_TXFIFO_EMPTY_THRHD(uart_empty_thresh_default)
}
func (uart *UART) enableReceiver() {
uart.Bus.SetCONF0_RXFIFO_RST(1)
uart.Bus.SetCONF0_RXFIFO_RST(0)
uart.Bus.SetCONF1_RXFIFO_FULL_THRHD(1)
uart.Bus.SetINT_ENA_RXFIFO_FULL_INT_ENA(1)
uart.Bus.SetINT_ENA_FRM_ERR_INT_ENA(1)
uart.Bus.SetINT_ENA_PARITY_ERR_INT_ENA(1)
uart.Bus.SetINT_ENA_GLITCH_DET_INT_ENA(1)
uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1)
}
func (uart *UART) writeByte(b byte) error {
for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 {
// Wait until there is space in the transmit buffer.
}
uart.Bus.FIFO.Set(uint32(b))
return nil
}
func (uart *UART) flush() {}
+37
View File
@@ -0,0 +1,37 @@
//go:build esp32c6
package machine
import (
"device/esp"
)
// GPIO matrix signal indices for I2C0 on ESP32-C6.
const (
I2CEXT0_SCL_OUT_IDX = 45
I2CEXT0_SDA_OUT_IDX = 46
)
var (
I2C0 = &I2C{
Bus: esp.I2C0,
funcSCL: I2CEXT0_SCL_OUT_IDX,
funcSDA: I2CEXT0_SDA_OUT_IDX,
useExt1: false,
}
)
// enableI2C0PeriphClock enables the I2C0 peripheral clock via PCR.
func enableI2C0PeriphClock() {
// Enable the APB/bus clock for the I2C0 registers and pulse the reset.
esp.PCR.SetI2C0_CONF_I2C0_CLK_EN(1)
esp.PCR.SetI2C0_CONF_I2C0_RST_EN(1)
esp.PCR.SetI2C0_CONF_I2C0_RST_EN(0)
// On the ESP32-C6 the I2C functional (source) clock is gated and selected
// in PCR.
// Select the XTAL (40 MHz) source and enable the clock gate, otherwise SCL
// is never driven and no bytes are clocked onto the bus.
esp.PCR.SetI2C_SCLK_CONF_I2C_SCLK_SEL(i2cClkSource)
esp.PCR.SetI2C_SCLK_CONF_I2C_SCLK_EN(1)
}
+250
View File
@@ -0,0 +1,250 @@
//go:build esp32c6
package machine
import (
"device/esp"
"errors"
"machine/usb"
"machine/usb/descriptor"
"runtime/interrupt"
)
// USB Serial/JTAG Controller
// The ESP32-C6 has the same built-in USB Serial/JTAG hardware IP as the
// ESP32-C3. The only difference at the machine level is how the peripheral
// clock is enabled: C3 uses SYSTEM.PERIP_CLK_EN0, while C6 uses the PCR
// (Peripheral Clock Reset) block.
const cpuInterruptFromUSB = 10
type USB_DEVICE struct {
Bus *esp.USB_DEVICE_Type
Buffer *RingBuffer
txPending bool // unflushed data in the EP1 TX FIFO
txStalled bool // set when flushAndWait fails (no host reading); cleared when FIFO becomes writable
}
var (
_USBCDC = &USB_DEVICE{
Bus: esp.USB_DEVICE,
Buffer: NewRingBuffer(),
}
USBCDC Serialer = _USBCDC
)
var (
errUSBWrongSize = errors.New("USB: invalid write size")
errUSBCouldNotWriteAllData = errors.New("USB: could not write all data")
)
type Serialer interface {
WriteByte(c byte) error
Write(data []byte) (n int, err error)
Configure(config UARTConfig) error
Buffered() int
ReadByte() (byte, error)
DTR() bool
RTS() bool
}
var usbConfigured bool
// USBDevice provides a stub USB device for the ESP32-C6. The hardware
// only supports a fixed-function CDC-ACM serial port, so the programmable
// USB device features are no-ops.
type USBDevice struct {
initcomplete bool
InitEndpointComplete bool
}
var USBDev = &USBDevice{}
func (dev *USBDevice) SetStallEPIn(ep uint32) {}
func (dev *USBDevice) SetStallEPOut(ep uint32) {}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {}
// initUSB is intentionally empty — the interp phase evaluates init()
// functions at compile time and cannot access hardware registers.
// Actual hardware setup is deferred to the first Configure() call.
func initUSB() {}
// Configure initialises the USB Serial/JTAG controller clock, pads, and
// interrupt so that received data is buffered automatically.
func (usbdev *USB_DEVICE) Configure(config UARTConfig) error {
if usbConfigured {
return nil
}
usbConfigured = true
// Enable the USB_DEVICE peripheral clock via PCR (C6 uses PCR instead
// of SYSTEM.PERIP_CLK_EN0 that C3 used).
esp.PCR.SetUSB_DEVICE_CONF_USB_DEVICE_CLK_EN(1)
esp.PCR.SetUSB_DEVICE_CONF_USB_DEVICE_RST_EN(0)
// Ensure internal PHY is selected and USB pads are enabled.
usbdev.Bus.SetCONF0_PHY_SEL(0)
usbdev.Bus.SetCONF0_USB_PAD_ENABLE(1)
usbdev.Bus.SetCONF0_DP_PULLUP(1)
// Clear any pending interrupts.
usbdev.Bus.INT_CLR.Set(0xFFFFFFFF)
// Enable the RX-packet-received interrupt.
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1)
// Map the USB peripheral interrupt to CPU interrupt cpuInterruptFromUSB.
esp.INTERRUPT_CORE0.SetUSB_INTR_MAP(cpuInterruptFromUSB)
_ = interrupt.New(cpuInterruptFromUSB, func(interrupt.Interrupt) {
_USBCDC.handleInterrupt()
}).Enable()
return nil
}
// ensureConfigured triggers lazy initialization on first use.
func (usbdev *USB_DEVICE) ensureConfigured() {
if !usbConfigured {
usbdev.Configure(UARTConfig{})
}
}
// handleInterrupt drains the hardware RX FIFO into the software ring buffer.
func (usbdev *USB_DEVICE) handleInterrupt() {
intStatus := usbdev.Bus.INT_ST.Get()
// Disable the RX interrupt to prevent re-triggering while we drain.
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(0)
if intStatus&esp.USB_DEVICE_INT_ST_SERIAL_OUT_RECV_PKT_INT_ST != 0 {
for usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 {
b := byte(usbdev.Bus.EP1.Get())
usbdev.Buffer.Put(b)
}
usbdev.Bus.SetINT_CLR_SERIAL_OUT_RECV_PKT_INT_CLR(1)
}
// Re-enable the RX interrupt.
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1)
}
func (usbdev *USB_DEVICE) WriteByte(c byte) error {
usbdev.ensureConfigured()
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
if usbdev.txStalled {
return errUSBCouldNotWriteAllData
}
if !usbdev.flushAndWait() {
usbdev.txStalled = true
return errUSBCouldNotWriteAllData
}
}
usbdev.txStalled = false
usbdev.Bus.EP1.Set(uint32(c))
if c == '\n' {
usbdev.flush()
usbdev.txPending = false
} else {
usbdev.txPending = true
}
return nil
}
func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) {
usbdev.ensureConfigured()
if len(data) == 0 {
return 0, nil
}
for i, c := range data {
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
if usbdev.txStalled {
return i, errUSBCouldNotWriteAllData
}
if !usbdev.flushAndWait() {
usbdev.txStalled = true
return i, errUSBCouldNotWriteAllData
}
}
usbdev.txStalled = false
usbdev.Bus.EP1.Set(uint32(c))
}
usbdev.flush()
usbdev.txPending = false
return len(data), nil
}
// Buffered returns the number of bytes waiting in the receive ring buffer.
func (usbdev *USB_DEVICE) Buffered() int {
usbdev.ensureConfigured()
if usbdev.txPending {
usbdev.flush()
usbdev.txPending = false
}
return int(usbdev.Buffer.Used())
}
// ReadByte returns a byte from the receive ring buffer.
func (usbdev *USB_DEVICE) ReadByte() (byte, error) {
b, ok := usbdev.Buffer.Get()
if !ok {
return 0, nil
}
return b, nil
}
func (usbdev *USB_DEVICE) DTR() bool {
return false
}
func (usbdev *USB_DEVICE) RTS() bool {
return false
}
// flush signals WR_DONE to tell the hardware to send the data that has
// been written to the EP1 FIFO.
func (usbdev *USB_DEVICE) flush() {
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
}
// flushAndWait signals WR_DONE and waits for the EP1 FIFO to become
// writable again. Returns false if the FIFO is still locked after the
// timeout (no host reading).
func (usbdev *USB_DEVICE) flushAndWait() bool {
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
for i := 0; i < 50000; i++ {
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() != 0 {
return true
}
}
return false
}
// FlushSerial flushes any pending USB serial TX data.
func FlushSerial() {
if _USBCDC.txPending {
_USBCDC.flush()
_USBCDC.txPending = false
}
}
// ConfigureUSBEndpoint is a no-op on ESP32-C6 — the hardware does not
// support programmable USB endpoints.
func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) {
}
// SendZlp is a no-op on ESP32-C6.
func SendZlp() {
}
// SendUSBInPacket is a no-op on ESP32-C6.
func SendUSBInPacket(ep uint32, data []byte) bool {
return false
}
+7
View File
@@ -33,3 +33,10 @@ func initI2CExt1Clock() {
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT1_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT1_RST(0)
}
// enableI2C0PeriphClock enables the I2C0 peripheral clock via SYSTEM.
func enableI2C0PeriphClock() {
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1)
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0)
}
+2 -4
View File
@@ -1,4 +1,4 @@
//go:build (esp32c3 || esp32s3) && !m5stamp_c3
//go:build (esp32c3 || esp32c6 || esp32s3) && !m5stamp_c3
package machine
@@ -53,9 +53,7 @@ func (i2c *I2C) Configure(config I2CConfig) error {
//go:inline
func (i2c *I2C) initClock(config I2CConfig) {
if !i2c.useExt1 {
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1)
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0)
enableI2C0PeriphClock()
} else {
initI2CExt1Clock()
}
+4 -2
View File
@@ -145,7 +145,7 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
// Set and enable the GPIOTE interrupt. It's not a problem if this happens
// more than once.
interrupt.New(nrf.IRQ_GPIOTE, func(interrupt.Interrupt) {
intr := interrupt.New(nrf.IRQ_GPIOTE, func(interrupt.Interrupt) {
for i := range nrf.GPIOTE.EVENTS_IN {
if nrf.GPIOTE.EVENTS_IN[i].Get() != 0 {
nrf.GPIOTE.EVENTS_IN[i].Set(0)
@@ -153,7 +153,9 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
pinCallbacks[i](pin)
}
}
}).Enable()
})
intr.SetPriority(0x40) // interrupt priority 2 (highest priority not reserved by the SoftDevice)
intr.Enable()
// Everything was configured correctly.
return nil
+18 -13
View File
@@ -22,17 +22,6 @@ var (
epinen uint32
epouten uint32
easyDMABusy volatile.Register8
endPoints = []uint32{
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
}
)
// enterCriticalSection is used to protect access to easyDMA - only one thing
@@ -183,7 +172,7 @@ func handleUSBIRQ(interrupt.Interrupt) {
epDataStatus := nrf.USBD.EPDATASTATUS.Get()
nrf.USBD.EPDATASTATUS.Set(epDataStatus)
var i uint32
for i = 1; i < uint32(len(endPoints)); i++ {
for i = 1; i < NumberOfUSBEndpoints; i++ {
// Check if endpoint has a pending interrupt
inDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPIN1<<(i-1)) > 0
outDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPOUT1<<(i-1)) > 0
@@ -202,7 +191,7 @@ func handleUSBIRQ(interrupt.Interrupt) {
}
// ENDEPOUT[n] events
for i := 0; i < len(endPoints); i++ {
for i := 0; i < NumberOfUSBEndpoints; i++ {
if nrf.USBD.EVENTS_ENDEPOUT[i].Get() > 0 {
nrf.USBD.EVENTS_ENDEPOUT[i].Set(0)
buf := handleEndpointRx(uint32(i))
@@ -378,3 +367,19 @@ func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
return b, nil
}
func (dev *USBDevice) SetStallEPIn(ep uint32) {
nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_IO | nrf.USBD_EPSTALL_STALL)
}
func (dev *USBDevice) SetStallEPOut(ep uint32) {
nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_STALL)
}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_IO)
}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
nrf.USBD.EPSTALL.Set(ep)
}
+2 -2
View File
@@ -90,6 +90,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
s2 := rp.USBCTRL_REGS.BUFF_STATUS.Get()
rp.USBCTRL_REGS.BUFF_STATUS.Set(s2)
// OUT (PC -> rp2040)
for i := 0; i < 16; i++ {
@@ -110,7 +111,6 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
}
rp.USBCTRL_REGS.BUFF_STATUS.Set(s2)
}
// Bus is reset
@@ -128,7 +128,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
const ackTimeout = 570
rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC)
sendUSBPacket(0, []byte{})
sendUSBPacket(0, nil)
// Wait for transfer to complete with a timeout.
t := timer.timeElapsed()
+12 -15
View File
@@ -155,9 +155,7 @@ static bool pico_processor_state_is_nonsecure(void) {
#define BOOTROM_FUNC_TABLE_OFFSET 0x14
// todo remove this (or #ifdef it for A1/A2)
#define BOOTROM_IS_A2() ((*(volatile uint8_t *)0x13) == 2)
#define BOOTROM_WELL_KNOWN_PTR_SIZE (BOOTROM_IS_A2() ? 2 : 4)
#define BOOTROM_WELL_KNOWN_PTR_SIZE 2
#define BOOTROM_VTABLE_OFFSET 0x00
#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE)
@@ -229,11 +227,12 @@ 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
#define QMI_DIRECT_CSR_EN_BITS 0x00000001
#define QMI_DIRECT_CSR_ASSERT_CS0N_BITS 0x00000004
#define QMI_DIRECT_CSR_RXEMPTY_BITS 0x00010000
#define QMI_DIRECT_CSR_TXFULL_BITS 0x00000400
#define QMI_M1_WFMT_RESET 0x00001000
#define QMI_M1_WCMD_RESET 0x0000a002
// https://github.com/raspberrypi/pico-sdk
@@ -406,13 +405,11 @@ static ram_func void flash_rp2350_restore_qmi_cs1(const flash_rp2350_qmi_save_st
}
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
);
if (high) {
hw_clear_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_ASSERT_CS0N_BITS);
} else {
hw_set_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_ASSERT_CS0N_BITS);
}
}
// Adapted from flash_range_program()
+1 -1
View File
@@ -93,6 +93,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
s2 := rp.USB.BUFF_STATUS.Get()
rp.USB.BUFF_STATUS.Set(s2)
// OUT (PC -> rp2350)
for i := 0; i < 16; i++ {
@@ -113,7 +114,6 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
}
rp.USB.BUFF_STATUS.Set(s2)
}
// Bus is reset
+2 -2
View File
@@ -130,11 +130,11 @@ func (spi *SPI) validPins(config SPIConfig) error {
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDI = config.SDI == NoPin || config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDI = config.SDI == NoPin || config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
}
+2 -2
View File
@@ -21,11 +21,11 @@ func (spi *SPI) validPins(config SPIConfig) error {
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDI = config.SDI == NoPin || config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDI = config.SDI == NoPin || config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
}
+2 -2
View File
@@ -55,11 +55,11 @@ func (spi *SPI) validPins(config SPIConfig) error {
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 || config.SDI == 32 || config.SDI == 36
okSDI = config.SDI == NoPin || config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 || config.SDI == 32 || config.SDI == 36
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23 || config.SDO == 35 || config.SDO == 39
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22 || config.SCK == 34 || config.SCK == 38
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 || config.SDI == 40 || config.SDI == 44
okSDI = config.SDI == NoPin || config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 || config.SDI == 40 || config.SDI == 44
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27 || config.SDO == 31 || config.SDO == 43 || config.SDO == 47
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26 || config.SCK == 30 || config.SCK == 42 || config.SCK == 46
}
+3 -1
View File
@@ -174,7 +174,9 @@ func (spi *SPI) Configure(config SPIConfig) error {
// SPI pin configuration
config.SCK.setFunc(fnSPI)
config.SDO.setFunc(fnSPI)
config.SDI.setFunc(fnSPI)
if config.SDI != NoPin {
config.SDI.setFunc(fnSPI)
}
return spi.initSPI(config)
}
+58 -35
View File
@@ -16,53 +16,60 @@ var (
data []byte
pid uint32
}
endPoints = []uint32{
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
}
)
func initEndpoint(ep, config uint32) {
val := uint32(usbEpControlEnable) | uint32(usbEpControlInterruptPerBuff)
// Each endpoint has 128 bytes of DPRAM buffer space allocated (2 * usbBufferLen).
// To support bidirectional configurations using the same endpoint number,
// we allocate the first 64 bytes (Buffer0) to OUT transfers, and the remaining
// 64 bytes (Buffer1) to IN transfers by shifting the IN offset by usbBufferLen.
offset := ep*2*usbBufferLen + 0x100
val |= offset
// Bulk and interrupt endpoints must have their Packet ID reset to DATA0 when un-stalled.
epXPIDReset[ep] = false // Default to false in case an endpoint is re-initialized.
// Since both directions share the same ep, we reset their PID flags independently.
if (config & usb.EndpointIn) != 0 {
epXPIDResetIn[ep] = false
} else {
epXPIDResetOut[ep] = false
}
switch config {
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
epXPIDResetIn[ep] = true
epXdata0In[ep] = false
val |= offset + usbBufferLen
val |= usbEpControlEndpointTypeInterrupt
_usbDPSRAM.EPxControl[ep].In.Set(val)
epXPIDReset[ep] = true
case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut:
epXPIDResetOut[ep] = true
epXdata0Out[ep] = false
val |= offset
val |= usbEpControlEndpointTypeBulk
_usbDPSRAM.EPxControl[ep].Out.Set(val)
_usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask)
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
epXPIDReset[ep] = true
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut:
epXPIDResetOut[ep] = true
epXdata0Out[ep] = false
val |= offset
val |= usbEpControlEndpointTypeInterrupt
_usbDPSRAM.EPxControl[ep].Out.Set(val)
_usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask)
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
epXPIDReset[ep] = true
case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn:
epXPIDResetIn[ep] = true
epXdata0In[ep] = false
val |= offset + usbBufferLen
val |= usbEpControlEndpointTypeBulk
_usbDPSRAM.EPxControl[ep].In.Set(val)
epXPIDReset[ep] = true
case usb.ENDPOINT_TYPE_CONTROL:
val |= offset
val |= usbEpControlEndpointTypeControl
_usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid)
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
@@ -90,7 +97,7 @@ func sendUSBPacket(ep uint32, data []byte) {
} else {
sendOnEP0DATADONE.offset = 0
}
epXdata0[ep] = true
epXdata0In[ep] = true
}
sendViaEPIn(ep, data, count)
@@ -127,21 +134,31 @@ func handleEndpointRx(ep uint32) []byte {
// AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer.
func AckUsbOutTransfer(ep uint32) {
ep = ep & 0x7F
setEPDataPID(ep, !epXdata0[ep])
setEPDataPIDOut(ep, !epXdata0Out[ep])
}
// Set the USB endpoint Packet ID to DATA0 or DATA1.
func setEPDataPID(ep uint32, dataOne bool) {
epXdata0[ep] = dataOne
if epXdata0[ep] || ep == 0 {
// Set the USB endpoint Packet ID to DATA0 or DATA1 for OUT direction.
func setEPDataPIDOut(ep uint32, dataOne bool) {
epXdata0Out[ep] = dataOne
if epXdata0Out[ep] || ep == 0 {
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid)
}
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
}
// Set the USB endpoint Packet ID to DATA0 or DATA1 for IN direction.
func setEPDataPIDIn(ep uint32, dataOne bool) {
epXdata0In[ep] = dataOne
if epXdata0In[ep] || ep == 0 {
_usbDPSRAM.EPxBufferControl[ep].In.SetBits(usbBuf0CtrlData1Pid)
}
_usbDPSRAM.EPxBufferControl[ep].In.SetBits(usbBuf0CtrlAvail)
}
func SendZlp() {
sendUSBPacket(0, []byte{})
sendUSBPacket(0, nil)
}
func sendViaEPIn(ep uint32, data []byte, count int) {
@@ -149,15 +166,19 @@ func sendViaEPIn(ep uint32, data []byte, count int) {
val := uint32(count) | usbBuf0CtrlAvail
// DATA0 or DATA1
epXdata0[ep&0x7F] = !epXdata0[ep&0x7F]
if !epXdata0[ep&0x7F] {
epXdata0In[ep&0x7F] = !epXdata0In[ep&0x7F]
if !epXdata0In[ep&0x7F] {
val |= usbBuf0CtrlData1Pid
}
// Mark as full
val |= usbBuf0CtrlFull
copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count])
if (ep & 0x7F) == 0 {
copy(_usbDPSRAM.EPxBuffer[0].Buffer0[:], data[:count])
} else {
copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer1[:], data[:count])
}
_usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val)
}
@@ -189,9 +210,9 @@ func (dev *USBDevice) ClearStallEPIn(ep uint32) {
ep = ep & 0x7F
val := uint32(usbBuf0CtrlStall)
_usbDPSRAM.EPxBufferControl[ep].In.ClearBits(val)
if epXPIDReset[ep] {
if epXPIDResetIn[ep] {
// Reset the PID to DATA0
setEPDataPID(ep, false)
setEPDataPIDIn(ep, false)
}
}
@@ -200,9 +221,9 @@ func (dev *USBDevice) ClearStallEPOut(ep uint32) {
ep = ep & 0x7F
val := uint32(usbBuf0CtrlStall)
_usbDPSRAM.EPxBufferControl[ep].Out.ClearBits(val)
if epXPIDReset[ep] {
if epXPIDResetOut[ep] {
// Reset the PID to DATA0
setEPDataPID(ep, false)
setEPDataPIDOut(ep, false)
}
}
@@ -230,10 +251,12 @@ type usbBuffer struct {
}
var (
_usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000)))
epXdata0 [16]bool
epXPIDReset [16]bool
setupBytes [8]byte
_usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000)))
epXdata0In [16]bool
epXdata0Out [16]bool
epXPIDResetIn [16]bool
epXPIDResetOut [16]bool
setupBytes [8]byte
)
func (d *usbDPSRAM) setupBytes() []byte {
+17 -15
View File
@@ -76,13 +76,15 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
uart.Receive(byte((uart.rxReg.Get() & 0xFF)))
}
// Clear overrun error (ORE, bit 3) to prevent an interrupt storm.
if s&0x8 != 0 {
// Clear error flags (ORE=bit3, NE=bit2, FE=bit1, PE=bit0) to prevent
// an interrupt storm and ensure the USART can continue receiving.
if s&0xF != 0 {
if uart.errClearReg != nil {
// Newer USART peripherals: clear ORE via the ICR register.
uart.errClearReg.Set(0x8) // ORECF
// Newer USART peripherals (L0, L4, L5, G0, F7, U5, WL, etc.):
// clear all error flags via ICR (ORECF|NECF|FECF|PECF = bits 3:0).
uart.errClearReg.Set(s & 0xF)
} else if s&0x20 == 0 {
// Older USART (F1/F4): ORE is cleared by reading SR then DR.
// Older USART (F1/F4): errors are cleared by reading SR then DR.
// SR was already read above. If RXNE was set, DR was read in
// the Receive path. Otherwise do a dummy DR read to complete
// the clearing sequence.
@@ -91,20 +93,20 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
}
}
// SetBaudRate sets the communication speed for the UART. Defer to chip-specific
// routines for calculation
func (uart *UART) SetBaudRate(br uint32) {
divider := uart.getBaudRateDivisor(br)
uart.Bus.BRR.Set(divider)
}
// WriteByte writes a byte of data to the UART.
func (uart *UART) writeByte(c byte) error {
uart.txReg.Set(uint32(c))
// Wait for the transmit data register to be empty before writing, so we
// don't overwrite a byte that hasn't moved to the shift register yet.
for !uart.statusReg.HasBits(uart.txEmptyFlag) {
}
uart.txReg.Set(uint32(c))
return nil
}
func (uart *UART) flush() {}
// flush waits until the USART shift register has finished transmitting the
// last byte (TC = Transmission Complete, bit 6). Without this, Write() returns
// while the final byte is still clocking out on the wire.
func (uart *UART) flush() {
for !uart.statusReg.HasBits(1 << 6) { // TC bit
}
}
@@ -0,0 +1,13 @@
//go:build stm32 && !stm32u585
package machine
// SetBaudRate sets the communication speed for the UART. Defers to
// chip-specific getBaudRateDivisor for the divisor calculation.
//
// On STM32U585 this function is overridden in machine_stm32u585.go because
// the U5 family requires UE=0 to write BRR.
func (uart *UART) SetBaudRate(br uint32) {
divider := uart.getBaudRateDivisor(br)
uart.Bus.BRR.Set(divider)
}
+1
View File
@@ -256,6 +256,7 @@ func enableAltFuncClock(bus unsafe.Pointer) {
stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_I2C4EN)
case unsafe.Pointer(stm32.USART1): // USART1 clock enable
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN)
_ = stm32.RCC.APB2ENR.Get() // readback: ensure clock is active before accessing USART1 registers
case unsafe.Pointer(stm32.USART2): // USART2 clock enable
stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART2EN)
case unsafe.Pointer(stm32.USART3): // USART3 clock enable
+35 -6
View File
@@ -8,14 +8,14 @@ import (
)
func CPUFrequency() uint32 {
return 4_000_000
return 160_000_000
}
// Internal use: configured speed of the APB1 and APB2 timers, this should be kept
// in sync with any changes to runtime package which configures the oscillators
// and clock frequencies
const APB1_TIM_FREQ = 4e6 // 4MHz (MSI default)
const APB2_TIM_FREQ = 4e6 // 4MHz (MSI default)
const APB1_TIM_FREQ = 160e6 // 160MHz (PLL1: MSIS 4MHz × 80 / 1 / 2)
const APB2_TIM_FREQ = 160e6 // 160MHz (PLL1: MSIS 4MHz × 80 / 1 / 2)
//---------- UART related code
@@ -55,10 +55,21 @@ func (uart *UART) isLPUART1() bool {
// NOTE: keep this in sync with the runtime/runtime_stm32u5.go clock init code
func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 {
if uart.isLPUART1() {
// LPUART uses BRR = 256 * fclk / baud
return (256 * CPUFrequency()) / baudRate
// LPUART uses BRR = 256 * fclk / baud.
// Use 64-bit arithmetic to avoid overflow: at 160 MHz,
// 256 * 160_000_000 = 40_960_000_000 which exceeds uint32 max.
return uint32(uint64(256) * uint64(CPUFrequency()) / uint64(baudRate))
}
return CPUFrequency() / baudRate
// USART requires BRR >= 16 for 16x oversampling (OVER8=0).
// A divisor below 16 is invalid per the STM32 reference manual and causes
// undefined hardware behaviour — in practice the receiver fires ORE/RXNE
// interrupts at an impossible rate, completely starving the CPU.
const minBRR = 16
divisor := CPUFrequency() / baudRate
if divisor < minBRR {
divisor = minBRR
}
return divisor
}
// Register names vary by ST processor, these are for STM U5
@@ -70,6 +81,24 @@ func (uart *UART) setRegisters() {
uart.errClearReg = &uart.Bus.ICR
}
// SetBaudRate overrides the shared implementation for STM32U5. On this
// family the BRR register is read-only while UE=1 (USART enabled), so the
// USART must be briefly disabled to change the baud rate. This matters when
// the servo library (or any code) calls SetBaudRate after Configure has
// already enabled the USART.
func (uart *UART) SetBaudRate(br uint32) {
cr1 := uart.Bus.CR1.Get()
if cr1&stm32.USART_CR1_UE != 0 {
// Disable the USART so BRR becomes writable.
uart.Bus.CR1.Set(cr1 &^ stm32.USART_CR1_UE)
}
uart.Bus.BRR.Set(uart.getBaudRateDivisor(br))
if cr1&stm32.USART_CR1_UE != 0 {
// Restore CR1 exactly as it was (re-enables USART, TE, RE, etc.).
uart.Bus.CR1.Set(cr1)
}
}
//---------- SPI related types and code
// SPI on the STM32U5 using the new SPIv2 peripheral
+4 -2
View File
@@ -71,16 +71,18 @@ type rttSerial struct {
rttControlBlock
}
var terminalByteString = []byte("Terminal\x00")
func (s *rttSerial) Configure(config UARTConfig) error {
s.maxNumUpBuffers = rttMaxNumUpBuffers
s.maxNumDownBuffers = rttMaxNumDownBuffers
s.buffersUp[0].name = &[]byte("Terminal\x00")[0]
s.buffersUp[0].name = &terminalByteString[0]
s.buffersUp[0].buffer = &rttBufferUpData[0]
s.buffersUp[0].bufferSize = rttBufferSizeUp
s.buffersUp[0].flags = rttModeNoBlockSkip
s.buffersDown[0].name = &[]byte("Terminal\x00")[0]
s.buffersDown[0].name = &terminalByteString[0]
s.buffersDown[0].buffer = &rttBufferDownData[0]
s.buffersDown[0].bufferSize = rttBufferSizeDown
s.buffersDown[0].flags = rttModeNoBlockSkip
+31 -9
View File
@@ -14,9 +14,21 @@ type USBDevice struct {
InitEndpointComplete bool
}
type usbEndpointEntry struct {
Endpoint uint32
Config uint32
}
var (
USBDev = &USBDevice{}
USBCDC Serialer
endPoints = []usbEndpointEntry{
{
Endpoint: usb.CONTROL_ENDPOINT,
Config: usb.ENDPOINT_TYPE_CONTROL,
},
}
)
func initUSB() {
@@ -196,17 +208,18 @@ func sendDescriptorString(data string, maxLen uint16) {
// Clamp the length.
maxEncBytes := min(len(usb_trans_buffer), len(udd_ep_control_cache_buffer), int(maxLen))
data = data[:min(len(data), (maxEncBytes-2)/2)]
// Write the header.
buf := usb_trans_buffer[:2*len(data)+2]
buf := usb_trans_buffer[:min(2*len(data)+2, maxEncBytes)]
hdr, body := buf[:2], buf[2:]
hdr[0] = byte(len(buf))
// hdr[0] (bLength) should convey the "original total string length" before being limited by the host's maxLen.
hdr[0] = byte(2*len(data) + 2)
hdr[1] = descriptor.TypeString
// Convert the string to UTF16.
// NOTE: Using range here would cause the length to disagree when multibyte codepoints are present.
for i := 0; i < len(data); i++ {
limit := min(len(data), len(body)/2)
for i := 0; i < limit; i++ {
body[2*i] = byte(data[i])
body[2*i+1] = 0
}
@@ -276,8 +289,11 @@ func handleStandardSetup(setup usb.Setup) bool {
case usb.SET_CONFIGURATION:
if setup.BmRequestType&usb.REQUEST_RECIPIENT == usb.REQUEST_DEVICE {
for i := 1; i < len(endPoints); i++ {
initEndpoint(uint32(i), endPoints[i])
for _, entry := range endPoints {
if entry.Endpoint == usb.CONTROL_ENDPOINT {
continue
}
initEndpoint(uint32(entry.Endpoint), entry.Config)
}
usbConfiguration = setup.WValueL
@@ -358,12 +374,18 @@ func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointC
for _, ep := range epSettings {
if ep.IsIn {
endPoints[ep.Index] = uint32(ep.Type | usb.EndpointIn)
endPoints = append(endPoints, usbEndpointEntry{
Endpoint: uint32(ep.Index),
Config: uint32(ep.Type | usb.EndpointIn),
})
if ep.TxHandler != nil {
usbTxHandler[ep.Index] = ep.TxHandler
}
} else {
endPoints[ep.Index] = uint32(ep.Type | usb.EndpointOut)
endPoints = append(endPoints, usbEndpointEntry{
Endpoint: uint32(ep.Index),
Config: uint32(ep.Type | usb.EndpointOut),
})
if ep.RxHandler != nil {
usbRxHandler[ep.Index] = func(b []byte) bool {
ep.RxHandler(b)
+7 -3
View File
@@ -2,10 +2,14 @@
package cdc
import (
"machine/usb"
)
const (
cdcEndpointACM = 1
cdcEndpointOut = 2
cdcEndpointIn = 3
cdcEndpointACM = usb.CDC_ENDPOINT_ACM
cdcEndpointOut = usb.CDC_ENDPOINT_OUT
cdcEndpointIn = usb.CDC_ENDPOINT_IN
)
// New returns USBCDC struct.
+57 -19
View File
@@ -26,11 +26,23 @@ type cdcLineInfo struct {
// USBCDC is the USB CDC aka serial over USB interface.
type USBCDC struct {
tx ring512
rx ring512
tx ring512
rx ring512
// inflight is the number of bytes currently submitted to the USB IN endpoint.
inflight atomic.Uint32
rbuf [1]byte
wbuf [1]byte
// txActive is the TX-pump ownership flag: 0 = idle, 1 = a pump owns the TX
// path. Claimed once (kickTx, CAS 0->1), held across every in-flight packet
// and the TX-complete IRQ, and released only when the ring drains. While it
// is set, kickTx's CAS fails and no second pump starts, which serializes the
// pump against Write across cores. Same model as Linux NAPI_STATE_SCHED:
// held across completion, dropped only with a recheck
// (Documentation/networking/napi.rst).
txActive atomic.Uint32
rbuf [1]byte
wbuf [1]byte
}
var (
@@ -81,7 +93,7 @@ func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error {
// Flush flushes buffered data.
func (usbcdc *USBCDC) Flush() {
for usbcdc.tx.Used() > 0 {
for usbcdc.tx.Used() > 0 || usbcdc.txActive.Load() != 0 {
gosched()
}
}
@@ -105,33 +117,59 @@ func (usbcdc *USBCDC) Write(data []byte) (n int, err error) {
return n, nil
}
// kickTx starts a transfer if none is in flight. Called from main context only.
// kickTx claims the TX pump for a producer. This CAS is the only start-from-idle
// edge; if it fails, a pump already owns the path and will drain what we just
// enqueued -- see the recheck in sendFromRing.
func (usbcdc *USBCDC) kickTx() {
if usbcdc.inflight.Load() > 0 {
return // txhandler will chain the next packet.
if !usbcdc.txActive.CompareAndSwap(0, 1) {
return
}
usbcdc.sendFromRing()
}
func (usbcdc *USBCDC) txhandler() {
// TX-complete IRQ. The pump is still owned here (txActive stayed 1 across the
// in-flight packet), so continue WITHOUT re-claiming -- pairs with the CAS in
// kickTx. A CAS here would see the flag already set, bail, and stall the chain.
inflight := usbcdc.inflight.Load()
usbcdc.inflight.Store(0)
if inflight == 0 {
return
}
usbcdc.tx.Discard(inflight)
usbcdc.inflight.Store(0)
usbcdc.sendFromRing()
}
// sendFromRing sends one USB packet from the ring and sets inflight.
// Called from kickTx (main) or txhandler (ISR), but never concurrently
// because kickTx only runs when inflight==0 and txhandler only runs
// when inflight>0.
// sendFromRing runs one step of the TX pump: submit one IN packet, or release the
// pump if the ring is empty. Precondition: txActive == 1 (from kickTx's CAS, or
// still held from the previous packet when entered via txhandler).
func (usbcdc *USBCDC) sendFromRing() {
d1, _ := usbcdc.tx.Peek()
if len(d1) == 0 {
return
for {
d1, _ := usbcdc.tx.Peek()
if len(d1) == 0 {
// Release the pump, then re-scan the ring: closes the missed-wakeup
// race where Write Put()s data and kickTx's CAS then fails (txActive
// still set), leaving the data for this pump to drain. The Store(0)
// is ordered before the Used() load -- and, in the producer, Put()
// before its CAS -- by the sequential consistency of Go's atomics, so
// neither side misses the other (assumes the ring's accesses are
// atomic too). cf. napi_complete_done() clearing NAPI_STATE_SCHED
// then rechecking.
usbcdc.txActive.Store(0)
if usbcdc.tx.Used() == 0 {
return // ring empty and pump released; done
}
if !usbcdc.txActive.CompareAndSwap(0, 1) {
return // another producer re-claimed the pump; let it run
}
continue // re-claimed; re-peek and keep pumping
}
chunk := d1[:min(usb.EndpointPacketSize, len(d1))]
usbcdc.inflight.Store(uint32(len(chunk)))
machine.SendUSBInPacket(cdcEndpointIn, chunk)
return // in flight; txActive stays set, txhandler continues
}
chunk := d1[:min(usb.EndpointPacketSize, len(d1))]
usbcdc.inflight.Store(uint32(len(chunk)))
machine.SendUSBInPacket(cdcEndpointIn, chunk)
}
// WriteByte writes a byte of data to the USB CDC interface.
+6 -3
View File
@@ -150,6 +150,9 @@ var InterfaceCDCData = InterfaceType{
data: interfaceCDCData[:],
}
// EP1 IN : CDC Call Management
// EP2 OUT: CDC OUT
// EP2 IN : CDC IN
var CDC = Descriptor{
Device: DeviceCDC.Bytes(),
Configuration: Append([][]byte{
@@ -160,9 +163,9 @@ var CDC = Descriptor{
ClassSpecificCDCCallManagement.Bytes(),
ClassSpecificCDCACM.Bytes(),
ClassSpecificCDCUnion.Bytes(),
EndpointEP1IN.Bytes(),
EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(),
InterfaceCDCData.Bytes(),
EndpointEP2OUT.Bytes(),
EndpointEP3IN.Bytes(),
EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
}),
}
+36 -96
View File
@@ -15,104 +15,44 @@ const (
TransferTypeInterrupt
)
var endpointEP1IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x81, // EndpointAddress
0x03, // Attributes
0x10, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x10, // Interval
type EndpointNumber uint8
const (
EndpointEP1 EndpointNumber = iota
EndpointEP2
EndpointEP3
EndpointEP4
)
const (
maxEndpoints = 4
)
var (
endpointEPIn = [maxEndpoints][endpointTypeLen]byte{}
endpointEPOut = [maxEndpoints][endpointTypeLen]byte{}
)
func EndpointIN(ep EndpointNumber, transferType uint8, maxPacketSize uint16, interval uint8) EndpointType {
e := EndpointType{data: endpointEPIn[ep][:]}
e.Length(endpointTypeLen)
e.Type(TypeEndpoint)
e.EndpointAddress(uint8(ep+1) | 0x80) // EndpointNumber is 0-based, addresses are 1-based
e.Attributes(transferType)
e.MaxPacketSize(maxPacketSize)
e.Interval(interval)
return e
}
var EndpointEP1IN = EndpointType{
data: endpointEP1IN[:],
}
var endpointEP2OUT = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x02, // EndpointAddress
0x02, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointEP2OUT = EndpointType{
data: endpointEP2OUT[:],
}
var endpointEP3IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x83, // EndpointAddress
0x02, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointEP3IN = EndpointType{
data: endpointEP3IN[:],
}
var endpointEP4IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x84, // EndpointAddress
0x03, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x01, // Interval
}
var EndpointEP4IN = EndpointType{
data: endpointEP4IN[:],
}
var endpointEP5OUT = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x05, // EndpointAddress
0x03, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x01, // Interval
}
var EndpointEP5OUT = EndpointType{
data: endpointEP5OUT[:],
}
// Mass Storage Class bulk in endpoint
var endpointMSCIN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x86, // EndpointAddress
TransferTypeBulk, // Attributes
0x40, // MaxPacketSizeL (64 bytes)
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointMSCIN = EndpointType{
data: endpointMSCIN[:],
}
// Mass Storage Class bulk out endpoint
var endpointMSCOUT = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x07, // EndpointAddress
TransferTypeBulk, // Attributes
0x40, // MaxPacketSizeL (64 bytes)
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointMSCOUT = EndpointType{
data: endpointMSCOUT[:],
func EndpointOUT(ep EndpointNumber, transferType uint8, maxPacketSize uint16, interval uint8) EndpointType {
e := EndpointType{data: endpointEPOut[ep][:]}
e.Length(endpointTypeLen)
e.Type(TypeEndpoint)
e.EndpointAddress(uint8(ep + 1)) // EndpointNumber is 0-based, addresses are 1-based
e.Attributes(transferType)
e.MaxPacketSize(maxPacketSize)
e.Interval(interval)
return e
}
const (
+10 -5
View File
@@ -111,6 +111,11 @@ var ClassHID = ClassHIDType{
data: classHID[:],
}
// EP1 IN : CDC Call Management
// EP2 OUT: CDC OUT
// EP2 IN : CDC IN
// EP3 OUT: HID OUT
// EP3 IN : HID IN
var CDCHID = Descriptor{
Device: DeviceCDC.Bytes(),
Configuration: Append([][]byte{
@@ -121,14 +126,14 @@ var CDCHID = Descriptor{
ClassSpecificCDCACM.Bytes(),
ClassSpecificCDCUnion.Bytes(),
ClassSpecificCDCCallManagement.Bytes(),
EndpointEP1IN.Bytes(),
EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(),
InterfaceCDCData.Bytes(),
EndpointEP2OUT.Bytes(),
EndpointEP3IN.Bytes(),
EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
InterfaceHID.Bytes(),
ClassHID.Bytes(),
EndpointEP4IN.Bytes(),
EndpointEP5OUT.Bytes(),
EndpointIN(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
EndpointOUT(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
}),
HID: map[uint16][]byte{
2: Append([][]byte{ // Update ClassLength in classHID whenever the array length is modified!
+10 -5
View File
@@ -121,6 +121,11 @@ var JoystickDefaultHIDReport = Append([][]byte{
// CDCJoystick requires that you append the JoystickDescriptor
// to the Configuration before using. This is in order to support
// custom configurations.
// EP1 IN : CDC Call Management
// EP2 OUT: CDC OUT
// EP2 IN : CDC IN
// EP3 OUT: HID OUT
// EP3 IN : HID IN
var CDCJoystick = Descriptor{
Device: DeviceJoystick.Bytes(),
Configuration: Append([][]byte{
@@ -131,14 +136,14 @@ var CDCJoystick = Descriptor{
ClassSpecificCDCACM.Bytes(),
ClassSpecificCDCUnion.Bytes(),
ClassSpecificCDCCallManagement.Bytes(),
EndpointEP1IN.Bytes(),
EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(),
InterfaceCDCData.Bytes(),
EndpointEP2OUT.Bytes(),
EndpointEP3IN.Bytes(),
EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
InterfaceHIDJoystick.Bytes(),
ClassHIDJoystick.Bytes(),
EndpointEP4IN.Bytes(),
EndpointEP5OUT.Bytes(),
EndpointIN(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
EndpointOUT(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
}),
HID: map[uint16][]byte{},
}

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