This should make usb/msc a whole lot more efficient by pausing the
worker goroutine and waiting for an interrupt to unpause it instead of
waiting in a loop and sleeping for 0.1ms each cycle.
In other words, this should make it both faster (no unnecessary delay
due to the time.Sleep) and more efficient (no polling).
This provides an abstraction to allow goroutines to wait for an event
from an interrupt, and for interrupts to send such an event and know
whether the goroutine is still working on the previous event.
This means runtime.alloc isn't allocated every time but instead only
when it is actually needed. It also just cleans up the compiler code a
little bit, and makes the next commit a bit nicer.
* compiler, runtime, reflect: generate type-specific hash/equal for composite map keys
For map keys that are not trivially binary-comparable, the compiler now
generates type-specific hash and equal functions as LLVM IR instead of
going through the interface+reflection path. This covers comparable
types: strings, floats, complex numbers, interfaces, channels, and
composites containing any mix of these.
Previously, maps with composite keys containing strings or floats
converted the key to interface{}, hashed via reflection, and compared
through interface equality. Now the compiler walks struct fields and
array elements directly, dispatching to the right runtime helper for
each field type and storing keys at their actual type.
Struct keys are always handled field-by-field so padding bytes do not
affect equality or hashing. Blank fields are ignored, matching Go
equality. Generated hash/equal function names use canonical underlying
type structure so structurally identical key types can share generated
functions. Padding zeroing before map operations is no longer needed
because structs no longer use the binary key path.
Also fix reflect map iteration for interface-keyed maps: MapIter.Key
returns an interface Value for map[interface{}] keys instead of
unpacking to the concrete key kind.
* compiler: generate loops for array map key hash/equal
Previously, array key hash and equal functions were unrolled at compile
time, generating one block of IR per element. For large arrays like
[1000]int inside a struct with non-binary fields, this caused code
explosion.
Now, binary-element arrays dispatch directly to hash32/memequal for the
whole array. Non-binary-element arrays generate an LLVM IR loop. The
equal loop short-circuits on the first mismatch.
Small arrays are still unrolled instead of looping, keeping the simple
cases compact.
* reflect: fix at-runtime map issues from review, and more found locally
Maps created through reflect.MakeMap need hash/equal behavior that
matches compiler-created maps. Add hashmapMakeReflect for composite key
types, using runtime closures that reconstruct interface{} values from
raw key bytes and delegate to the interface hash and equality paths.
Interface-keyed maps are already stored as interface values, so use the
existing interface hash/equal helpers directly for those. This keeps
reflect insert, lookup, delete, and compiled lookup paths consistent.
Also fix addressable small values used as interface map keys or
interface map values. loadSmallValue puts small indirect values back in
the pointer-sized interface data field the same way valueInterfaceUnsafe
does.
* compiler, interp, reflect: fix pointer map literals; remove interface fallback
Package-level map literals with pointer keys (both *T and
unsafe.Pointer) crash the compiler: the interp pass panics when trying
to hash pointer data as raw bytes, because pointer values in the interp
memory model are symbolic identities that do not fit in a byte.
Fix this by setting a recoverable error flag instead of panicking. The
interp detects the error after each instruction and defers the map
insert to runtime init code, where real addresses are available for
hashing. This matches how the interp already handles other operations
it cannot evaluate at compile time.
With this fix, unsafe.Pointer can also be classified as a binary map
key, which was the last type requiring the interface-based fallback.
Since all comparable types now use either the binary or the
compiler-generated hash/equal path, remove the interface fallback from
the compiler and reflect packages.
* compiler, transform: always pass hash/equal function pointers to hashmapMakeGeneric
The compiler now always resolves the hash and equal functions at compile
time and passes them directly to hashmapMakeGeneric, instead of passing
an algorithm enum to hashmapMake and resolving at runtime. For string
keys, the runtime hashmapStringPtrHash/hashmapStringEqual functions are
referenced directly. For binary keys, hash32/memequal are referenced.
The old hashmapMake with alg enum is retained for reflect, which still
needs runtime resolution when creating maps dynamically.
The OptimizeMaps transform pass is updated to handle both hashmapMake
and hashmapMakeGeneric, and to recognize hashmapGenericSet in addition
to hashmapBinarySet and hashmapStringSet. The now-unused
hashmapCanGenerateHashEqual helper is removed.
* runtime: store large map keys and values indirectly
When a map key or value exceeds 128 bytes, the bucket now stores a
pointer to separately allocated memory instead of the data inline. This
matches Go's MapMaxKeyBytes/MapMaxElemBytes threshold and prevents
bucket sizes from exploding for large key/value types.
For example, map[[256]byte]int previously used 2128 bytes per bucket
(16 header + 256*8 keys + 8*8 values); now it uses 144 bytes per bucket
(16 header + 8*8 pointers + 8*8 values).
The indirection is fully encapsulated in the runtime via helper
functions. Store the computed key and value slot sizes on the hashmap so
all runtime and reflect paths use the same bucket layout, including
non-indirect keys and values.
Add big-key golden coverage and benchmarks. Make the benchmark vary
enough key bytes to exercise hashing.
* interp: bail out of loops that iterate too many times
The existing loop guard (errLoopUnrolled) only fires when a loop body
emits runtime instructions. Loops that are fully evaluable at compile
time, such as inserting thousands of entries into a map, were not
caught and could hang the compiler.
Add a per-basic-block iteration counter that triggers a recoverable
error (errLoopTooLong) when any block is entered more than 1000 times
in a single function call. This defers the init function to runtime,
which is the same behavior as other interp bailouts.
Profiling showed that 83% of CPU time was spent in GC, caused by
allocation pressure from the interp memory cloning on each map
mutation. The iteration limit avoids this entirely by bailing out
before the quadratic cost becomes significant.
Performance on the reproducer from #2090 (map init with strconv.Itoa):
entries before after
5,000 7.4s 2.1s
10,000 17.5s 2.1s
20,000 48.0s 2.8s
65,536 >180s (OOM) 3.2s
* Add -interp-loop-limit
I was seeing bad error messages... but only some of the time. So I went
digging. Eventually I traced it to this one bug, probably introduced in
https://github.com/tinygo-org/tinygo/pull/5290.
This is not just a visual issue, this is actually a bug in TinyGo that
will result in non-reproducible binaries and random behavior
differences.
These methods were added in Go 1.20 but were missing from TinyGo's
sync.Map implementation, causing compilation failures for code that
uses them.
The implementation follows the same lock-based approach as the rest
of TinyGo's sync.Map.
Several places used the (*[1 << N]T)(ptr)[:len:len] pattern to convert
C pointers to Go slices. This has a hardcoded size limit that can panic
if exceeded; RunTool hit this with >1024 linker arguments when many
files are embedded.
Replace all instances with unsafe.Slice, which handles any size.
When a string variable already had a source-level default value, the -ldflags
"-X" flag was silently ignored and the variable remained the default value
at runtime.
Fix by stripping the InitOrder entry for each -X variable before
LoadSSA() is called. With no entry in InitOrder, go/ssa emits no init
store, so the global stays zero-valued in the IR. makeGlobalsModule
still injects the actual -X values at final link time.
The -X values remain out of the per-package build cache with only the
variable names appearing in the cache key.
Signed-off-by: deadprogram <ron@hybridgroup.com>
Rename the "arduino" target to "arduino-uno" to better reflect the
board it represents. The "arduino" name is kept as an alias that
inherits from "arduino-uno" for backward compatibility.
- Rename targets/arduino.json to targets/arduino-uno.json
- Create targets/arduino.json as alias inheriting from arduino-uno
- Rename board_arduino.go to board_arduino_uno.go
- Update build tag from "arduino" to "arduino_uno"
- Rename corresponding example files
Signed-off-by: deadprogram <ron@hybridgroup.com>
Move STATUS_W1TC clear to before the callback loop so that new GPIO
events arriving during handler execution generate a fresh edge on the
CPU interrupt line and are not lost. Matches the same fix applied to
ESP32-S3.
Signed-off-by: deadprogram <ron@hybridgroup.com>
When SPI is configured via the GPIO Matrix, SPI signal transitions set
GPIO.STATUS bits on the routed pins. With a level-triggered CPU interrupt
(line 8), the ISR re-enters continuously as long as any STATUS bit is
asserted — causing user GPIO callbacks to fire spuriously.
Switch cpuInterruptFromPin to CPU interrupt 10, which is edge-triggered
(level 1) on the Xtensa LX7. This ensures the ISR fires once per GPIO
event rather than looping while SPI is active.
Also move STATUS_W1TC clears to before callback dispatch so that new
GPIO events arriving during handler execution generate a fresh edge, and
add writeINTCLEAR(active) in handleInterrupt to properly acknowledge
edge-triggered CPU interrupt pending bits via the INTCLEAR register.
Fixes GPIO interrupts firing constantly when SPI and pin interrupts are
used together.
Signed-off-by: deadprogram <ron@hybridgroup.com>
SPIConfig.CS has a zero value of Pin(0) (GPIO0), but NoPin is Pin(0xff).
When the user does not set CS, the SPI driver sees Pin(0) != NoPin and
configures GPIO0 as the chip select output, hijacking whatever function
that pin was serving such as a button interrupt.
Default config.CS to NoPin when it is the zero value, so an omitted CS
field correctly means "no hardware CS pin". Applied to both ESP32-S3 and
ESP32-C3 SPI drivers.
Signed-off-by: deadprogram <ron@hybridgroup.com>
Ticker.Stop does not drain already-buffered ticks from the channel,
so on slow CI runners a tick may already be queued before Stop takes
effect, causing a spurious "fail: ticker should have stopped!" failure.
Drain the channel immediately after Stop before checking that no new
ticks arrive.
Signed-off-by: deadprogram <ron@hybridgroup.com>
* reflect: implement method-set based AssignableTo and Implements
Based on the design from #4376 by aykevl.
Fixes#4277, fixes#3580.
Co-authored-by: Ayke van Laethem <aykevanlaethem@gmail.com>
* builder: update expected binary sizes for reflect changes
* Make interface checks similar to invoke, allowing typeImplementsMethodSet and method info to be dropped when reflect is not present
* Add more tests that BigGo reflect tests
* Even more pruning
* Add go/token and net/url to passing tests
* Prune even further, I am less happy with this, though
* Update size test now that we are smaller
* Skip some tests
* elide method lists
* format, oops
* fix tests
* Add a panic, pull out constant to keep in sync
* Add debug info
* Remove code that was leftover from a previous refactor
---------
Co-authored-by: Ayke van Laethem <aykevanlaethem@gmail.com>
This fixes issue #5037. A few more wasip1 syscalls needed to be present,
and in particular fd_prestat_get needed to return EBADF on invalid file
descriptors.
Add support for rising, falling, and toggle edge interrupts on all
ESP32-S3 GPIO pins (0-48). The GPIO peripheral interrupt is routed
to CPU interrupt 8 via the interrupt matrix. The ISR reads both
STATUS and STATUS1 registers to cover the full 49-pin range.
Signed-off-by: deadprogram <ron@hybridgroup.com>
The UART handleInterrupt handler unconditionally read RDR on every
interrupt without checking which flag triggered it. On newer STM32
USART peripherals (U5, L4, L5, L0, G0, F7, WL), RXNEIE enables
interrupts for both RXFNE (data ready) and ORE (overrun error).
Unlike older families (F1, F4), ORE is not cleared by reading the
data register, it must be explicitly cleared via the ICR register.
When an overrun occurred (e.g. serial data arriving while ADC
busy-waits in Get()), ORE would trigger the interrupt, the handler
would fire without clearing it, and the interrupt would re-trigger
immediately, causing an infinite interrupt storm that locks up the
CPU.
Fix by:
- Checking RXFNE/RXNE (bit 5) before reading data from RDR
- Clearing ORE (bit 3) via ICR on newer peripherals when set
- Adding errClearReg field to UART struct, set to &Bus.ICR in
setRegisters() for all ICR-capable families
- Preserving the SR+DR clearing sequence for older F1/F4 families
Signed-off-by: deadprogram <ron@hybridgroup.com>