Compare commits

..

769 Commits

Author SHA1 Message Date
deadprogram b0298c6a69 targets/esp32: add .rxring and .wifibss sections to DRAM1
Place espradio's RX ring buffer and WiFi-only BSS (ISR tables,
timer slots, ISR ring) in SRAM1 pool 7/6 ahead of the arena.
Frees ~14 KB of SRAM2 for the Go GC heap. The arena assertion
still guarantees ≥32 KB remains for WiFi DMA buffers.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-25 17:42:01 +02:00
rdon-key 0fe4c619eb runtime: prevent timer starvation in cores scheduler 2026-07-25 10:02:09 +02:00
Jake Bailey 16fc1ea2bb runtime: make fatal failures unrecoverable
Match the Go runtime by terminating for deadlocks, stack overflows,
runtime and GC invariants, invalid lock operations, and platform
initialization failures instead of routing them through panic/recover.

Keep language-level runtime errors and unsupported user operations
recoverable. Add crash coverage that verifies fatal errors bypass deferred
recover calls.
2026-07-25 07:14:00 +02:00
Jake Bailey 9105cce340 runtime: make out-of-memory failures fatal
Match the Go runtime by terminating instead of unwinding when an
allocator exhausts the heap. Recovering an OOM can leave allocator locks
held and cannot safely continue when the panic path itself needs memory.
2026-07-25 07:14:00 +02:00
Jake Bailey 7756941f39 main_test: skip flaky goroutines test on threaded schedulers 2026-07-24 20:50:26 +02:00
Matthew Hiles 259842dc2e UEFI: Add support for UEFI time and UEFI events; fix STOP \n -> \r\n conversion (#5549)
* add support for UEFI time and UEFI events; fix STOP \n -> \r\n conversion

* apply EFI timezone offset
2026-07-24 19:29:42 +02:00
Jake Bailey 528476f2b3 compiler, runtime: support recover on riscv64 2026-07-24 08:55:49 +02:00
rdon-key 0c68e1215e runtime: add critical-section fallbacks for atomic And and Or 2026-07-23 18:53:35 +02:00
Matthew Hiles 9c2eafce6f targets: minimal uefi target support (#5452)
* runtime: split baremetal memory setup

* runtime: extract Windows PE globals scan helper

* compileopts,builder: add target linker flavor override

* machine,runtime,targets: add minimal uefi-amd64 target

* runtime,examples: add clean UEFI exit path test

* machine,x86: fix amd64 ABI stack handling

* machine/uefi: add CHAR16 conversion helpers

* machine/uefi: add loaded image protocol support

* runtime: add UEFI PE globals support

* machine,runtime,x86: add UEFI time and text output support

* machine,runtime,x86: add UEFI text and time support

* machine,runtime,examples: add UEFI text input support

* machine,examples: add UEFI graphics output support

* add rest of STOP methods (direct UEFI ABI only)

* runtime: fix UEFI sleep tick conversion

* machine/uefi: make text input waits cooperative

* address TestClangAttributes/uefi-amd64 failure

* address TestConfigLinkerFlavor bug

* uefi: move raw bindings to device package

* strip down implementation to bare minimum

* amd64: rename x86 package

* do git restore upstream/dev for src/net

* add smoketest for uefi-amd64; add required zeroSizeAllocPtr constant

* address PR issues: remove unused files; make putchar() insert '\r' before '\n'

* swap lines around
2026-07-23 14:34:18 +02:00
Konstantin Sharlaimov 3797e89600 feat(machine/stm32): add STM32H7 and NUCLEO-H753ZI support 2026-07-23 12:29:44 +02:00
Konstantin Sharlaimov 40ed956d6c src/device/arm: add ARM v7-M MPU support 2026-07-23 12:29:44 +02:00
Damian Gryski 2a49216152 compiler: add regression test for generic methods in interfaces
Add compiler/testdata/go1.27.go, gated behind Go >= 1.27 (the version
that promoted generic methods out of the GenericMethods experiment),
covering both fixes from the previous two commits:

  - genericMethod has a regular method and a generic method (its own
    type parameter); boxing it into an interface must only include the
    regular method in the runtime method set instead of panicking in
    getTypeCodeName.
  - onlyGenericMethod's sole method is generic, so its type code must
    have hasMethodSet == false and no methodSet field at all, not an
    empty one.

Verified this test panics on the pre-fix compiler/interface.go and
passes after it.
2026-07-23 10:14:10 +02:00
Damian Gryski 1e3baff966 compiler: fix hasMethodSet to account for filtered generic methods
hasMethodSet was computed from the raw, unfiltered method set length
(ms.Len() != 0), before generic methods were excluded from numMethods
and the method set value. For a type whose only method is generic
(e.g. "func (t T) M[X int](n X) X"), this left hasMethodSet true even
though the actual (filtered) method set is empty, causing an
unnecessary empty method-set global and methodSet field to be emitted
for that type's type descriptor.

Compute hasMethodSet from the same filtered loop that produces
numMethods, so it's true only when at least one non-generic method
exists.
2026-07-23 10:14:10 +02:00
Damian Gryski 4fcf03414c compiler: exclude generic methods from runtime method sets
Go 1.27 promoted "generic methods" (methods with their own type
parameters, independent of any type parameters on the receiver) out
of the GenericMethods experiment, e.g.:

    func (r *Rand) N[Int intType](n Int) Int

Boxing a value of a type with such a method into an interface caused
tinygo to panic while building the runtime type's method table:

    getTypeCode -> getMethodSetValue -> getTypeCodeName

getTypeCodeName's type switch has no case for *types.TypeParam, and a
generic method's Signature carries the method's own type parameters
in its parameter/result types (e.g. "Int" above), so encoding it into
a type code name panicked with "unknown type: <param name>".

This affected any package using math/rand/v2.Rand.N, including much of
the math/rand/v2 test suite, since printing or otherwise boxing a
*Rand into an interface is common.

Upstream reflect deliberately excludes generic methods from
Type.NumMethod()/Type.Method() (they can't be instantiated implicitly,
and a generic method can never satisfy an interface method), so mirror
that behavior: add isGenericMethod, which checks whether a method's
Signature has its own type parameters, and skip such methods when
building the runtime method count, method set value, and method set
in compiler/interface.go.

Fixes a panic isolated to:

    package main

    type T struct{}

    func (t T) M[X int](n X) X { return n }

    func main() {
    	var t T
    	var i interface{} = t
    	_ = i
    }
2026-07-23 10:14:10 +02:00
deadprogram 888d0c0f97 machine/esp32: address review feedback for interrupt and UART code
- Fix SetInterrupt error capture: use a package-level variable instead
  of a named return captured by the sync.Once closure, avoiding a
  closure allocation on every call.
- Extract GPIO interrupt handler from inline closure to named function
  (handleGPIOInterrupt), matching the UART handler pattern.
- Unexport ESP32-specific UART fields (txrxSignal, rtsctsSignal,
  parityErrorDetected, dataErrorDetected, dataOverflowDetected).
- Change UART.Configure to return error, consistent with ESP32C3/C6.
- Clarify why UART0 does not return early when pins are already wired.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-22 23:52:21 +02:00
deadprogram fc17c3565e esp32: remove dead code and fix GC root scanning
Remove unused cpuIntUsed and cpuIntToPeripheral variables from
interrupt_esp32.go because these were declared but never read.

Fix _globals_end in the linker script to cover .data in addition to
.bss and .wifi_bss. Previously the GC scan range ended at
_wifi_bss_end, missing any heap pointers stored in initialized
globals (.data section). Extend to _edata so the conservative
collector sees all global root pointers.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-22 23:52:21 +02:00
deadprogram 6aa966af0a esp32: add PHY DRAM symbols to linker
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-22 23:52:21 +02:00
deadprogram aee0581a32 esp32: fix XIP boot crash caused by LLVM 22 / lld l32r relocation bug
Work around an lld (LLVM 22) bug where l32r PC-relative offsets are
miscalculated when auto-generated .literal.* sections are prepended to
.text.* sections by the linker script. The assembler emits .literal
entries for movi instructions whose constants exceed the 12-bit signed
range; lld then resolves the l32r relocations with incorrect offsets,
causing every l32r in the boot code to load from the wrong literal
pool entry.

The symptom was a TG0WDT_SYS_RESET boot loop: the watchdog disable
code loaded wrong register addresses via l32r and silently wrote to
the wrong peripheral registers, leaving the watchdog running.

Fix by constructing PS_WOE_MASK (0x40000) with movi+slli instead of a
single large-constant movi, eliminating all auto-generated .literal
section entries. This is compatible with both LLVM 20 and LLVM 22.

Also revert DRAM origin to 0x3FFAE000 (200K) and remove rom_phyFuns
symbols that belong in a separate WiFi commit.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-22 23:52:21 +02:00
deadprogram 2dea20b769 targets/esp32: adjust memory usage and export required symbols for wifi
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-22 23:52:21 +02:00
deadprogram 0c17b918fb esp32: implement flash XIP (execute-in-place) support
Add full flash XIP support for ESP32, enabling code and read-only data to
execute/load directly from flash via the MMU cache rather than consuming
precious SRAM. This increases available RAM from ~328KB to effectively
unlimited for code/rodata, while keeping ~121KB for the Go heap.

Changes:
- src/device/esp/esp32.S: Add MMU initialization in call_start_cpu0
  - Call ROM bootloader mmu_init() and cache_flash_mmu_set() to map DROM/IROM
  - Enable flash cache via ROM Cache_Read_Enable()
  - Fix tinygo_scanCurrentStack to spill all register windows for GC

- targets/esp32-interrupts.S: Add exception diagnostics

- targets/esp32.ld: Major linker script restructure for XIP
  - Add DROM (4MB @ 0x3F400000) and IROM (4MB @ 0x400D0000) regions
  - Move .rodata to DROM, main .text to IROM (both flash-mapped)
  - Keep boot code, vectors, and WiFi blob IRAM sections in SRAM0
  - Create WiFi arena in SRAM1 pool 7/6 (64KB @ 0x3FFF0000)
  - Move .bss and heap to SRAM2 (200KB @ 0x3FFAE000), avoiding ROM/MAC regions
  - Add _drom_flash_addr variable (patched by builder with flash offset)

- targets/esp32.json: Add linker wrap flags for malloc/free and WiFi functions

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-22 23:52:21 +02:00
deadprogram 8727b696a5 esp32: add interrupt-based UART RX and fix init order
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-22 23:52:21 +02:00
deadprogram 94736ac0e5 esp32: add interrupt support (vector table, timer alarm, GPIO SetInterrupt)
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-22 23:52:21 +02:00
Jake Bailey 9e9be09e9a testing: avoid Goexit without unwind support
Goexit cannot run deferred functions on architectures without panic
unwinding. Query runtime.supportsRecover before calling it so unsupported
targets report an error instead of leaving the test runner blocked forever.
2026-07-22 11:27:51 -07:00
Jake Bailey 9e7d89d4d5 compiler: pass large aggregates by pointer
LLVM ComputeValueVTs recursively expands arrays and structs into one
value type per scalar leaf. SelectionDAG call lowering allocates data
structures proportional to this count, which makes very large values
exhaust memory or crash LLVM.

Count scalar leaves and use pointers for internal parameters and results
when the count exceeds 1024. A result pointer is the first parameter,
and aggregate parameters point to read-only memory. Exported function
types are unchanged.

Keep these SSA values in memory and copy them with memcpy when needed.
Handle calls, interfaces, maps, channels, selects, defers, goroutines,
phis, and multiple results. Update the expected compiler IR and re-enable
the native compress/flate tests.
2026-07-22 11:22:03 -07:00
Jake Bailey 3b7c9e24f5 compiler: add large aggregate IR tests
Add compiler coverage for large aggregate parameters and results,
including function values, interfaces, maps, channels, selects, deferred
calls, goroutines, phis, and multiple results.
2026-07-22 11:22:03 -07:00
Jake Bailey bc35d087c2 compiler: move SSA result handling into helpers
createInstruction records each LLVM value in locals, emits stack object
tracking, and constructs function returns inline.

Move these operations to setValue and createReturn. This keeps the
instruction switch from having to know how an SSA value or function
result is emitted.
2026-07-22 11:22:03 -07:00
Jake Bailey deaf532d61 compiler: resolve callees before lowering arguments
createFunctionCall and createGo currently lower arguments before they
determine whether the call is direct, an interface invoke, or through a
function value.

Resolve the callee, function type, and context first, then append the
arguments in the same order as before. This does not change the
generated LLVM IR.
2026-07-22 11:22:03 -07:00
Jake Bailey 5ec2632461 compiler: share LLVM function type construction
getFunction and getLLVMFunctionType duplicate the construction of LLVM
result types for functions with zero, one, or multiple results.

Move this code to getLLVMResultType. Also split the existing parameter
expansion code into expandDirectFormalParamType so callers can request
the current flattened parameter types directly.
2026-07-22 11:22:03 -07:00
Jake Bailey 39a105dab9 compiler: centralize loads and stores of SSA values
Map, channel, index, and goroutine lowering each create allocas, store
SSA values into them, load results, and emit lifetime intrinsics.

Add helpers for these operations and use runtimeValueResult for runtime
calls that write a value and an optional comma-ok result. The generated
LLVM IR is unchanged.
2026-07-22 11:22:03 -07:00
Jake Bailey aa914bea5c compiler: centralize deferred call record types
createDefer builds an LLVM struct containing the deferred function and
its arguments. createRunDefers separately reconstructs the same struct
type before loading the fields.

Keep each LLVM value together with its type while building a deferred
call record, and load all argument fields through one helper. This
removes the duplicate lists of field types and field loads.
2026-07-22 11:22:03 -07:00
Jake Bailey 3071e339cd compiler: key deferInvokeFuncs on distinct name 2026-07-22 12:30:34 +02:00
Pat Whittingslow b536dd6f79 compiler: handle nested unsigned shift being untyped after type resolution (#5497)
* apply compiler patch for fix of #5496

* add compiler/testdata smoketest

* make diff error more explicit on difference

* apply golden fix
2026-07-22 07:35:18 +02:00
deadprogram 3ad913acb8 runtime/esp32c6: select 80MHz PLL as TIMG0 timer clock source
The shared timekeeping code assumes the TIMG0 timer counts at 40MHz,
but on the ESP32-C6 the timer clock defaults to the 40MHz XTAL, giving
a 20MHz tick with the /2 prescaler and making all delays run twice as
long. Select PLL_F80M (80MHz) and enable the timer group clock so the
25ns/tick assumption holds.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-21 21:20:41 +02:00
deadprogram 9e9f1d0b96 go.mod: update to go-llvm with changes to use context-aware wrappers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-21 19:16:37 +02:00
Nia Waldvogel cd4e479dd5 compiler: consistently pass layout and alignment to createAlloc 2026-07-21 07:15:43 +02:00
Faye Amacker cf5bed8227 sync: fix deadlock in Map.Range callback
Currently, calling any sync.Map method from inside the sync.Map.Range
callback f deadlocks. Moreover, Go's sync.Map explicitly permits the
Range callback to call other methods on the map ("Range does not
block other methods on the receiver; even f itself may call any
method on m").

This commit prevents the deadlock by changing sync.Map.Range to:
- copy the map's keys under the lock
- release the lock
- iterate over the key snapshot

A snapshot satisfies Go's sync.Map.Range contract, which only
requires that no key is visited more than once and may reflect any
mapping from any point during the call.

Using a snapshot keeps the implementation simple, in line with this
file's stated scope ("no more efficient than a map with a lock").

Also added TestMapRangeAndDelete regression test, which deletes map
entries from inside the map's Range callback.
2026-07-20 13:38:44 -07:00
deadprogram babdfc9e10 esp32s3: fix boot crash caused by LLVM 22 / lld l32r relocation bug
Replace movi instructions with constants outside the 12-bit signed
range (-2048..2047) with movi+slli sequences. Large constants cause
the assembler to emit auto-generated .literal section entries, which
triggers an lld bug where l32r PC-relative offsets are miscalculated
when .literal.* sections are merged with .text.* sections.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-20 16:45:34 +02:00
deadprogram 2602d4c25d main: update to espflasher 0.7.1 and add flash erase progress tracker
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-20 14:12:53 +02:00
deadprogram 0a9b3f91bc builder: improvements needed for esp32 XIP to allow for correctly flashing large programs
This fixes the builder for ESP32 (original) by separating the RAM segments loadable by the
ROM bootloader from flash-mapped segments (DROM/IROM) which require MMU setup by startup code.

These changes are needed for to allow for ESP32 to correctly flashing large programs which
as a result require XIP support.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-20 14:12:53 +02:00
deadprogram 3b137c3053 ci: fix sizediff check by updating to go 1.25, which is the new minimum version
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-19 17:24:30 +02:00
Konstantin Sharlaimov 5f02d6b659 feat(machine/stm32): make HSE crystal frequency selectable.
Define the board's external crystal (HSE) frequency `xtalHz` in the
respective `board_*.go` files. Per-topology PLL tables (F1, F4, F7)
will compute the register dividers from it.

Moving this parameter to the board definition level cleanly isolates
board hardware characteristics from general MCU chip configurations,
eliminating the need for custom target build tags. Targets using
HSE-clocked STM32 chips must define `xtalHz` or fail to build.
2026-07-19 10:56:14 +02:00
rdon(あーるどん) b8adb803c4 runtime: wake channel waiters after releasing locks (#5513)
* runtime: wake channel waiters after releasing locks

* runtime: add comment explaining next field reuse in chanClose
2026-07-18 08:42:46 +02:00
deadprogram 36d3b83e9c builder: add chkstk2.S to windows/386 builtins for __alloca symbol
Clang on i386 mingw emits calls to _alloca (decorated as __alloca)
for stack probing. This was provided by chkstk2.S, not chkstk.S
which only provides __chkstk_ms.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-17 19:06:57 +02:00
deadprogram a2a638c198 all: fix LLVM version compatibility for targets and interp
cc1as: change AssemblerInvocation.Triple from std::string to
llvm::Triple, matching upstream LLVM 22 and fixing deprecation warnings
for lookupTarget, createMCRegInfo, createMCAsmInfo, and
createMCSubtargetInfo.

interp: always use ConstNamedStruct in rawValue.toLLVMValue instead of
falling back to ConstStruct for unnamed structs. LLVM 22 uses anonymous
identified struct types where previous versions used literal structs;
ConstStruct creates a literal type that doesn't match, causing an
"initializer type mismatch" panic for globals like fmt.ppFree.

compileopts: add build-tag-guarded feature patching for pre-LLVM 20
(strip +bulk-memory-opt and +call-indirect-overlong from wasm features)
and restructure into three files covering all LLVM version ranges.

targets: strip redundant negative features from RISC-V target JSONs
(esp32c3, esp32c6, fe310, k210, riscv-qemu, tkey). These ~190 negative
features per target listed every extension LLVM knows about that the
target doesn't use, but LLVM disables them by default. They became stale
across LLVM versions, causing "not a recognized feature" warnings. Keep
only positive features and -relax.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-17 19:06:57 +02:00
deadprogram 0ffa3eb748 all: add LLVM 22 support
Builder:
- Update clang.cpp for LLVM 22 API changes: DiagnosticOptions is no
  longer ref-counted, TextDiagnosticPrinter and DiagnosticsEngine take
  references instead of pointers, createDiagnostics() signature changed.
- Update cc1as.cpp: clang/Driver/Options.h moved to
  clang/Options/Options.h, namespace changed from clang::driver::options
  to clang::options, MCInstPrinter now passed as unique_ptr.
- Add new LLVM 22 libraries to GNUmakefile: clangAnalysisLifetimeSafety,
  clangOptions, LLVMDTLTO, and dtlto component.
- Add llvm22 build tag to all build/test commands.

CGo:
- Handle CXType_Unexposed in libclang.go by resolving via canonical
  type. LLVM 22 reports builtin type aliases (e.g. __size_t) as
  Unexposed instead of Typedef.
- Always make C typedefs into Go type aliases. LLVM 22 changed
  getTypedefDeclUnderlyingType to return CXType_Enum directly instead
  of wrapping in an elaborated type.

Compileopts:
- Add build-tag-guarded ClangTriple() to substitute wasm32-unknown-wasi
  with wasm32-unknown-wasip1 for LLVM 22 (deprecated triple).
- Add build-tag-guarded patchFeatures() to map renamed Xtensa features
  (atomctl, memctl, timerint, esp32s3) for LLVM 22.

Targets:
- Remove -zca from RISC-V target feature strings (esp32c3, esp32c6,
  fe310, k210, riscv-qemu, tkey). LLVM 22 now implies +zca from +c.
- Remove -zcd from k210. LLVM 22 now implies +zcd from +c,+d.

Tests:
- Change TestClangAttributes to check individual feature flags instead
  of exact string match, allowing new LLVM features without failures.
- Update TestBinarySize expected values for LLVM 22 codegen.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-17 19:06:57 +02:00
deadprogram 922f583dd4 ci: modify builds to use llvm 22
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-17 19:06:57 +02:00
Damian Gryski 91a56b28a2 go.mod: update to go-llvm with needed changes 2026-07-17 13:09:00 +02:00
Damian Gryski 9c864f0d79 interp: fix switch-instruction handling for LLVM 22's new case-value API
Written entirely by Claude (Anthropic's Claude Code), at the request of
and under the direction of dgryski, on the dgryski/llvm23 branch (LLVM
22 support, in preparation for the eventual LLVM 23 release; the
corresponding go-llvm branch of the same name adds the matching
binding support this depends on).

Found while running real-world Go test suites through a tinygo built
against LLVM 22 (as a smoke test of the earlier captures(none)/
nocapture and lifetime-intrinsic fixes on this branch): `tinygo test`
on github.com/dgryski/go-rc5 panicked at compile time with "unknown
value" inside interp.(*runner).getValue, while compiling
(*sync.Once).Do. Bisecting against LLVM 21 (unaffected) narrowed it to
a change introduced in LLVM 22 specifically, unrelated to the earlier
captures/lifetime work.

Root cause: LLVM 22 stopped exposing switch-instruction case values as
regular instruction operands -- only the condition and
destination-block operands remain. interp/compiler.go's `case
llvm.Switch:` handling walked raw operands assuming the old
alternating (value, label) layout, so under LLVM 22 it read a
destination *block* where it expected an integer case value,
eventually panicking deep inside a getValue call it couldn't resolve.
This only manifested on functions actually compiled by the interp
package (TinyGo's compile-time evaluator for global initializers) that
happen to reach a switch-based dispatch, such as sync.Once's
defer-based Do method -- hence it needed a real, moderately complex
package (crypto/cipher via go-rc5) to surface, and never showed up in
smaller smoke tests.

Fix: use the new version-independent Value.SuccessorsCount()/
Value.Successor(i) and Value.GetSwitchCaseValue(i) helpers just added
to go-llvm, instead of raw Operand() indexing.

Also applies the same captures(none)/nocapture golden-IR normalization
already used in transform/transform_test.go and compiler/compiler_test.go
to interp/interp_test.go's separate copy of the same comparator helper,
which was missed in the earlier pass and started failing two of its
subtests once actually run against LLVM 22.

Verified: `tinygo test` on go-rc5 now passes reliably (repeated runs)
against both LLVM 20 (default) and LLVM 22; full transform/compiler/
interp/cgo/goenv test suites pass on both versions. The `builder`
package has pre-existing, environment-related test failures (stale
GOROOT cache, local clang/target-feature drift) confirmed identical
against an unmodified LLVM 20 build, so unrelated to this change.
2026-07-17 13:09:00 +02:00
Damian Gryski 6203b624c5 transform, compiler: support LLVM 21's captures(none) attribute; add LLVM 22 build support
Written entirely by Claude (Anthropic's Claude Code), at the request of
and under the direction of dgryski, as part of an effort to get TinyGo
building against upcoming LLVM releases (this branch currently targets
LLVM 22, verified against real LLVM 22.1.8; a corresponding go-llvm
branch of the same name adds the matching binding support).

LLVM 21 replaced the boolean 'nocapture' enum attribute with the more
expressive 'captures' int attribute, where captures(none) (value 0) is
the equivalent of the old nocapture. This matters for
transform.OptimizeAllocs, which relies on reading this attribute for
its interprocedural escape analysis, and for compiler/symbol.go, which
emits it on a number of runtime/generated functions. Confirmed
empirically (via `opt -passes=function-attrs`) that the cutoff is
LLVM 20 emits/expects nocapture, LLVM 21+ emits/expects
captures(none). Since TinyGo must keep working with LLVM 20, both
sites now go through new version-gated helpers in
compiler/llvmutil (NoCaptureAttrName/IsNoCapture) rather than switching
unconditionally.

Also fixes a second, unrelated but load-bearing break found while
testing against LLVM 22: llvm.lifetime.start/end dropped their i64
size argument (confirmed via `opt -passes=verify`, the cutoff here is
one version later, at LLVM 22). compiler/llvmutil now builds the
right call signature based on version.

Adds llvm21 and llvm22 build-tag config files to the cgo package,
which parses cgo fragments via libclang and had never been updated
past LLVM 20 even though the compiler package itself already gained
LLVM 21 support previously -- a latent gap that would have caused a
version mismatch between the cgo preprocessor and the rest of the
compiler when building with -tags llvm21 or llvm22.

Finally, updates the golden-IR test comparators in
transform/transform_test.go and compiler/compiler_test.go to
normalize a few cosmetic LLVM 21/22 output differences (the
captures(none) rename/reordering, a new 'nocreateundeforpoison'
intrinsic attribute, and the lifetime intrinsic arity change) so a
single golden file continues to match output from either LLVM
version.

Verified by building a full (non-byollvm) tinygo binary against real
LLVM 22.1.8 and running a compiled Go program end-to-end (exercising
OptimizeAllocs' stack-allocation path), and by running the
transform/compiler/cgo test suites against both LLVM 20 (default) and
LLVM 22.

Not yet addressed: the byollvm embedded-clang/lld build path hits
separate, unrelated Clang C++ API breakage against LLVM 22
(DiagnosticOptions reference-to-pointer change, missing headers) --
that is a larger follow-up effort.
2026-07-17 13:09:00 +02:00
Konstantin Sharlaimov ea003da13f Add UART line inversion support (#5522)
* feat(machine): add UART line inversion support.

Add InvertTX and InvertRX to UARTConfig to allow enabling hardware
line inversion on supported targets. Added hardware implementation for
RP2 (RP2040, RP2350), STM32 (newer families), SAM (SAMD51, SAME5x),
and ESP (ESP32, ESP32-C3, ESP32-C6).

* refactor(machine): Refactor UART inversion with pin setter helpers.

RP2: Extract setOutOver/setInOver methods on Pin, removing inline
IO control register manipulation from UART configure.

SAM: Switch to SetCTRLA_TXINV/SetCTRLA_RXINV methods, dropping
the unused device/sam import and raw SetBits/ClearBits calls.

---------

Co-authored-by: Konstantin Sharlaimov <ksharlaimov@inavflight.com>
2026-07-17 10:45:50 +02:00
felipegenef 7994d2e912 runtime: implement SetFinalizer to fix syscall/js finalizeRef leak 2026-07-17 08:12:46 +02:00
rdon(あーるどん) f1c39e8356 targets: add M5Stack Stamp-S3A (#5524)
* targets: add M5Stack Stamp-S3A

* targets: add Stamp-S3A smoke test

* targets: rename Stamp-S3A target

* targets: rename Stamp-S3A target
2026-07-16 14:31:47 +02:00
deadprogram 801bd484ab test: skip crypto/ecdsa on wasi for now, since it never finishes
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-14 11:52:59 +02:00
deadprogram be7b6b316e test: skip compress/flate for now due to Go 1.27 changes
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-14 11:52:59 +02:00
Jake Bailey ec184e90c1 main_test: skip json on Cortex-M QEMU with Go 1.27
Go 1.27 jsonv2 pushes testdata/json.go beyond the LM3S6965 flash
budget. Keep coverage on larger emulated targets while skipping only the
too-small Cortex-M QEMU configuration.
2026-07-14 11:52:59 +02:00
Jake Bailey 07c9cdcbc4 builder: link stack probes on Windows amd64 and arm64
Go 1.27 packages can emit stack probes on Windows amd64 and arm64.
Link the compiler-rt stack probe builtins and use compiler-rt for those
targets so these packages build.
2026-07-14 11:52:59 +02:00
deadprogram bd1d11d166 compiler: support Go 1.27 generic methods via x/tools upgrade
Go 1.27 adds generic methods (golang/go#77273). golang.org/x/tools
go/ssa v0.42.0 does not instantiate them: objectMethod only applies
receiverTypeArgs and ignores method-level type parameters, so a call
such as (*math/rand/v2.Rand).N resolves to the abstract generic method
and its body reaches createConst with a type-parameter-typed zero
constant, triggering "panic: expected nil interface constant".

Upgrade golang.org/x/tools to v0.47.0, whose go/ssa returns nil from
MethodValue for generic methods and instantiates method-level type
parameters. No compiler changes are required.

Since x/tools v0.47.0 requires Go 1.25, raise the minimum supported Go
version from 1.24 to 1.25.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-14 11:52:59 +02:00
deadprogram 919c998355 builder: update max supported Go version to 1.27
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-14 11:52:59 +02:00
deadprogram a3375fb2c4 all: build/test using Go 1.27-rc2
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-14 11:52:59 +02:00
rdon-key 09bda77b78 runtime: fix leaking GC build with cores scheduler 2026-07-13 12:58:46 +02:00
Jake Bailey 047ed57005 compiler: canonicalize method signature identities
The interface lowering pass used a separate formatter for method
signatures. Same-named local aliases could therefore make distinct generic
methods compare equal. Reuse getTypeCodeName so interface checks preserve
type identity.
2026-07-13 07:33:32 +02:00
Jake Bailey c7923d1c64 compiler: canonicalize generic instance identities
x/tools SSA names and go/types strings can preserve the source spelling of
type arguments, so aliases can make distinct instances collide. Use the
canonical type encoding for function names, synthetic local type owners,
instantiated named types, and method sets.
2026-07-13 07:33:32 +02:00
Jake Bailey 73db9776b1 testdata: add generic alias identity regressions
Add cases where same-named local aliases instantiate generic functions,
local types, and methods with float32 and float64. Record the current
collisions and incorrect results.
2026-07-13 07:33:32 +02:00
deadprogram 6d49f0177b compiler: support //go:linknamestd pragma
Go 1.27 introduced the //go:linknamestd directive, a standard-library
variant of //go:linkname that does not require importing "unsafe". The
iter package switched to it for referencing runtime.newcoro and
runtime.coroswitch, which caused "linker could not find symbol
iter.newcoro / iter.coroswitch" errors when building with Go 1.27.

Handle //go:linknamestd the same as //go:linkname, bypassing the unsafe
import requirement, and add test coverage in the pragma compiler test.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-12 14:05:34 +02:00
Evan Wies 35a61ac8c5 compiler: support file-level //go:linkname directives
Modern golang.org/x/sys/unix (v0.36+) declares its linknames
detached from function declarations, e.g.:

    func syscall_syscall(...)
    //go:linkname syscall_syscall syscall.syscall

TinyGo's pragma parser only inspected function doc comments and
therefore missed these, producing link errors like:

    undefined symbol: _golang.org/x/sys/unix.syscall_syscall

Extend parsePragmas to also walk the enclosing *ast.File's
free-standing comments for //go:linkname directives matching the
function's name. Function-attached directives still take
precedence. The existing 'unsafe' import gate is preserved.

Fixes #4395, #5365
2026-07-12 11:54:38 +02:00
Konstantin Sharlaimov c1a4ed1489 machine/stm32: add OTG FS USB driver for F4/F7
STM32F4 and F7 share the same OTG FS IP but have no USB driver in
TinyGo. This adds a full device-mode driver covering CDC, HID, and
MSC with working examples.

- OTG FS has 4 physical EPs (0–3); virtual indices 4–7 fold onto
  them via physEP() so the existing machine/usb API is unchanged
- F4 bypasses VBUS sensing via GCCFG.NOVBUSSENS; F7 uses the USB
  voltage regulator + GOTGCTL B-valid override instead
- STM32F7 PLL_Q changed 2→9 to produce the 48 MHz clock required
  by USB/RNG/SDMMC; CK48MSEL cleared to select main PLL as source
- HID and MSC descriptors remapped at init() to physical endpoint
  addresses (EP2/EP1 for HID, EP2/EP3 for MSC)
- usb-storage example replaced machine.Flash with a FAT12 RAM disk
  so the host mounts without reformatting
- MSC sendCSW sets queuedBytes before state transition to fix a
  missed byte-count on the status phase
2026-07-12 10:45:49 +02:00
Jake Bailey 140c82e012 compiler: disambiguate generic instance link names with local type args 2026-07-11 16:20:08 +02:00
Jake Bailey dc23ce73d6 testdata: add regression test for generic instances with local type args 2026-07-11 16:20:08 +02:00
Jake Bailey e039ce131c runtime: encode pending Goexit in panic state
Treat panicState as a bitmask so a recovered panic during Goexit
can clear the panic bit while preserving the pending Goexit bit.
This removes the separate deferFrame Goexit field and restores the
previous defer frame size.
2026-07-11 11:53:33 +02:00
Jake Bailey d59c706e60 GNUmakefile: increase encoding/xml test stack
Top-level tests now run in goroutines, which puts stdlib tests on
TinyGo's default host goroutine stack. encoding/xml's depth-limit
test needs a larger stack on Linux and Darwin, so run just that
package with a larger stack while leaving the rest of the host stdlib
tests on the default stack.
2026-07-11 11:53:33 +02:00
Jake Bailey 26d3645317 GNUMakefile: change skip reason note 2026-07-11 11:53:33 +02:00
Jake Bailey 432ebe13ed runtime: make Goexit exit host threads
The threads scheduler handled runtime.Goexit using the generic deadlock
path, which parked the current pthread forever. Add a scheduler-specific
goexit hook so it can remove the current task and exit the pthread while
ordinary blocking deadlocks still block.

Track main goroutine Goexit so the last remaining goroutine reports the
expected deadlock instead of letting the process exit successfully.
Expand the crash coverage with the Goexit cases covered by Big Go's
runtime tests.
2026-07-11 11:53:33 +02:00
Jake Bailey 16eefc59eb runtime, testing: support Goexit, SkipNow, FailNow
Keep a pending Goexit separate from the active panic state so recovered
deferred panics do not cancel the original Goexit unwind. Goexit should
also ignore -panic=trap, since it is not a panic.

Wire testing.FailNow and SkipNow through runtime.Goexit, and run tests
and benchmarks in goroutines. This lets Goexit terminate the user
function without skipping test bookkeeping. Add Goexit/recover coverage
and enable crypto/ecdh in the Linux stdlib test list.
2026-07-11 11:53:33 +02:00
deadprogram fb1bf2e6bf build: statically link MinGW runtime in LLVM tools on Windows
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-10 23:54:46 +02:00
Matthew Hiles 0922e3e956 add device/uefi and device/amd64 2026-07-08 14:19:59 +02:00
Victor Costa bd61913973 wasm: patch wasm_exec.js and wasm_exec_node.js from Go 1.18+ (#5483)
* wasm: patch wasm_exec.js and wasm_exec_node.js from Go 1.18+

Port this commit https://github.com/golang/go/commit/680caf15355057ca84857a2a291b6f5c44e73329 removing polyfills from `wasm_exec.js`, now the environment is expected to provide all the required polyfills.

`wasm_exec.js` now only provides stub fallbacks for globalThis.fs and globalThis.process.

All NodeJS specific code is now in a separate file `wasm_exec_node.js` with its required polyfills.

* feat: bump minimum node version to 22

Drops official support for NodeJS 18 for WASM by removing the provided polyfills in the `wasm_exec.js`.

Now the environment is expected to provide the polyfill if it is running on older NodeJS versions

The new minimum required version for WASM is NodeJS 22+.

* chore: removed wrong comment

`wasm_exec_node.js` no longer contains the polyfill for NodeJS 18
2026-07-08 12:32:00 +02:00
Matthew Hiles 2dbfdbae10 UEFI Target Groundwork: Add LinkerFlavor compile option (#5465)
* Add LinkerFlavor so the odd pairing of COFF+Linux is possible (necessary for UEFI)

* But back bits of builder.go that were mistakenly removed

* Update compileopts/config.go

Co-authored-by: Ayke <aykevanlaethem@gmail.com>

* update LinkerFlavor test

---------

Co-authored-by: Ayke <aykevanlaethem@gmail.com>
2026-07-08 10:33:59 +02:00
Ayke van Laethem 05029c4fb5 machine: add support for the STM32U031 chip 2026-07-08 08:54:06 +02:00
Bryan Souza a7b30639d8 Adding support for the 'Plus' board variant of SeeedStudio XIAO nrf52840, works with Sense variants as well (#5479)
* adding support for SeeedStudio XIAO BLE Plus and Sense Plus boards;

* adjusting definitions for UARTs on XIAO BLE Plus;

* fixed buid tag on XIAO BLE Plus;

* tested by building some examples; fixed UART nomenclature;

* added xiao-ble-plus to GNUmakefile smoketests;
2026-07-07 13:36:18 +02:00
Pat Whittingslow 4adc661197 upgrade net (#5490)
* upgrade net

* add some missing crypto and os API (#5488)

* add missing crypto API

* gofmt crypto/tls/common.go

* pull new net with unixsock formatted

* update net to latest main
2026-07-07 10:40:36 +02:00
Pat Whittingslow 311c071ae6 add runtime.fastrandn (#5502) 2026-07-06 22:35:41 -03:00
Jake Bailey f1fdfe2313 reflect: add missing iterator methods 2026-07-06 23:47:28 +02:00
Jake Bailey 7c3dc05117 all: modernize (#5498)
* modernize string cut usage

* modernize string cut prefix usage

* modernize slices helper usage

* modernize min and max usage

* modernize loop variable copies

* modernize integer range loops

* modernize map copy loops

* modernize go types iterator usage

* modernize empty interface usage

* modernize atomic type usage

* modernize string builders

* modernize string split iteration

* modernize remaining loop variable copies

* modernize usb cdc min usage

* modernize src integer range loops

* modernize example empty interface usage

* modernize src min and max usage

* modernize src integer range loops

* modernize src empty interface usage

* modernize src atomic type usage

* modernize reflect type lookups

* modernize review nits
2026-07-06 21:58:48 +02:00
Ron Evans e569dcfe38 runtime: fix ticker not stopping when Stop races with its callback (#5487)
* runtime: fix ticker not stopping when Stop races with its callback

On the threads and cores schedulers, timer callbacks run concurrently
with user goroutines.

If Stop (or Reset) was called in the window after the node was popped
but before its callback re-added it, removeTimer would not find the
timer in the queue, so it would be re-added to timer anyway.

Track timers whose callback is currently running in a firing list, and
have removeTimer mark a firing timer as stopped so its callback does not
re-add it.

Also make the timers test drain robust: allow a possible in-flight tick
delivered concurrently with Stop to settle before draining the channel.

Signed-off-by: deadprogram <ron@hybridgroup.com>

* runtime: fix Stop/Reset semantics when racing a firing timer callback

Address review feedback on the ticker Stop-race fix. On the threads and
cores schedulers a timer callback runs concurrently with user goroutines,
which left several problems:

- removeTimer reported a firing timer as successfully removed, so
  Stop/Reset could return true even though the callback had already
  started (wrong semantics, notably for AfterFunc). firingTimerStop now
  returns a bool and removeTimer no longer hands the still-firing node
  back to resetTimer.

- The periodic advance (when += period) ran in timerCallback outside the
  scheduler timer lock. Move it into each scheduler's reAddTimer, under
  the lock and after the stopped check, so a concurrent Reset can't have
  its freshly-queued deadline corrupted.

- resetTimer now sets when/period after removeTimer for the same reason.

Add testdata/timer_stop_reset_race.go and TestTimerStopResetRace, which
reproduce the stop-while-firing and reset-while-firing races via the
runtime timer linkname hooks.

Signed-off-by: deadprogram <ron@hybridgroup.com>

* runtime: fix timer Stop/Reset race with firing periodic timers

Signed-off-by: deadprogram <ron@hybridgroup.com>

---------

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-06 20:14:53 +02:00
wikto 20038817ae [feature] moved ReadTemeprature to machine_esp32s3.go 2026-07-05 14:27:31 +02:00
Wikwoj0512 9ce73cfafa [feature] added temperature reading to esp32s3 2026-07-05 14:27:31 +02:00
deadprogram 0033c23848 machine/esp32c6: add ADC driver
Adds machine_esp32c6_adc.go implementing the machine.ADC interface for
ESP32-C6 (ADC1 only, GPIO0–GPIO6, channels 0–6; there is no ADC2).

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-05 14:22:58 +02:00
Jake Bailey 5c15a68d18 tests: support simavr 1.8+ 2026-07-05 13:57:05 +02:00
Hector Chu 02384c9414 add CGO_CFLAGS support (#5453)
* add CGO_CFLAGS support

* the environment variable must be split

* use shlex to handle double quotes

* wrap error
2026-07-05 10:17:26 +02: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
Ayke van Laethem 85d223c5e2 compiler: add //go:noheap pragma 2026-05-25 16:58:01 +02:00
Ayke van Laethem 7b486e398a compiler: clean up pointer pack code
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.
2026-05-25 16:58:01 +02:00
felipegenef da69f18e91 fix targets/wasm_exec.js: move unsigned coercions from helpers to top-level import functions 2026-05-21 14:39:17 -07:00
felipegenef 6971b8a69e fix: coerce memory offsets to unsigned before typed array and DataView access 2026-05-21 14:39:17 -07:00
Ayke van Laethem 6682e41ab4 reflectlite: remove stringHeader
It's usually a better idea to use unsafe.String and such instead.
2026-05-20 14:33:41 +02:00
Jake Bailey 18033ebc36 compiler, runtime, reflect: generate type-specific hash/equal (#5359)
* 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.
2026-05-18 13:31:27 +02:00
Jake Bailey 89d9e33bca interp: bail out of loops that iterate too many times (#5395)
* 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
2026-05-17 20:42:27 +02:00
deadprogram e80e7e5c10 ci: remove CircleCI configuration
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-05-17 14:36:32 +01:00
pedramktb 6233915ecf targets/wasm_exec: add runtime.getRandomData to gojs imports 2026-05-17 10:25:05 +01:00
Joel Wetzell fd8e0bd70b monitor flag on flash now respects port 2026-05-17 07:14:56 +01:00
Ayke van Laethem 45e2863e9f loader: avoid race condition when loading package list
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.
2026-05-16 17:24:58 +01:00
Jake Bailey 4204f3d065 sync: add Map.CompareAndSwap and Map.CompareAndDelete
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.
2026-05-11 14:13:10 -07:00
Jake Bailey ddbd65beec builder, cgo, syscall: replace fixed-size array casts with unsafe.Slice
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.
2026-05-11 09:35:04 +02:00
deadprogram 1a1506ef79 builder,loader: fix -ldflags -X not overriding variables with default values
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>
2026-04-28 11:06:48 +01:00
deadprogram 8793dc37fa modules: update to 'net' package with roundtrip fix for js/wasm
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-27 12:32:43 +01:00
Ayke van Laethem e9d78a78ea compiler: re-enable generics test
This seems to have been removed by accident, but it still seems useful
to me.
2026-04-24 17:48:48 +01:00
Jake Bailey 7ec9cfc61c testdata: re-add reflect.DeepEqual test case 2026-04-23 12:02:44 +01:00
Ayke van Laethem 294cf28e43 all: support LLVM 19 and LLVM 20 on Fedora 43
The system LLVM paths on Fedora 43 are slightly different from other
systems like Debian, so adding those paths in this patch.
2026-04-23 10:22:56 +01:00
deadprogram 2f7a66e6d3 version: update to 0.42.0-dev for next dev cycle
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-23 09:22:47 +01:00
deadprogram a4f9c9d1b6 Release 0.41.1
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-22 18:22:49 +01:00
deadprogram aa10b682e4 modules: update net to version that is backwards compatible with Go 1.25.x to fix #5332
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-22 16:45:20 +01:00
deadprogram eafbe4ee69 machine/esp32c3: correct pin interrupt setup call that was overlooked from #5320
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-22 15:53:17 +01:00
Joel Wetzell cf5912412d runtime/esp32s3: wait for TIMG0 update register to clear before reading timer registers 2026-04-22 14:12:33 +01:00
deadprogram 1f114b2acf Release 0.41.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-21 02:42:54 +01:00
deadprogram 94ebddbdfe espflasher: update to espflasher 0.6.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-20 06:41:54 +01:00
deadprogram aa1ca27789 machine_atmega328p_simulator: add correct build tag for arduino_uno after renaming
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-19 10:48:04 +01:00
deadprogram 9999320cd0 build: actually use the version of llvm we are supposed to be testing by using brew link command
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-19 08:04:45 +01:00
deadprogram 74429b4a14 targets: rename arduino target to arduino-uno
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>
2026-04-18 23:58:39 +01:00
Jake Bailey bcb191c01e runtime: handle GODEBUG on wasip2 (#5312)
* runtime: handle GODEBUG on wasip2
* Support env on more platforms to get GODEBUG working
2026-04-18 23:15:47 +02:00
deadprogram ddb848816f machine/esp32xx: add WASM simulator support for ESP32-C3/ESP32-S3 targets
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-18 19:40:04 +01:00
deadprogram 889911d0a6 machine/esp32c3: clear GPIO STATUS before dispatching pin callbacks
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>
2026-04-18 18:18:42 +01:00
deadprogram a4dd38de81 machine/esp32s3: use edge-triggered CPU interrupt for GPIO pin interrupts
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>
2026-04-18 18:18:42 +01:00
deadprogram 486ee37a40 machine/esp32: default SPI CS pin to NoPin when unset
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>
2026-04-18 17:12:21 +01:00
deadprogram 944176beef testdata: fix flaky timers test by draining ticker channel after Stop
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>
2026-04-18 15:38:13 +01:00
Ayke van Laethem c396418187 ci: don't double zip release artifacts
See: https://github.blog/changelog/2026-02-26-github-actions-now-supports-uploading-and-downloading-non-zipped-artifacts/
2026-04-18 12:54:54 +01:00
deadprogram 1b4ab772d2 test: increase default timeout from 1 to 2 minutes to prevent spurious fails in CI from slow runners
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-18 10:20:25 +01:00
Jake Bailey aca0fe0412 Add errors package to passing list 2026-04-18 08:19:32 +01:00
deadprogram af511c1118 net: update to latest net submodule with UDP and JS improvements
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-18 07:17:08 +01:00
Jake Bailey 5ba8766cbc compiler: implement method-set based AssignableTo and Implements (#5304)
* 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>
2026-04-17 21:57:03 +02:00
Jake Bailey b792c10680 internal/tools: move into root 2026-04-17 19:18:17 +01:00
Jake Bailey 12d41ba0c1 go.mod, compiler: bump minimum Go version to 1.23
This enables gotypesalias=1 by default, which is needed for
generic type aliases to work correctly in go/types.
2026-04-17 19:18:17 +01:00
Ayke van Laethem 020cca8c3c wasm: add more stubbed file operations
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.
2026-04-17 16:02:45 +01:00
deadprogram 4cbd34d32f machine/esp32s3: implement Pin.SetInterrupt for GPIO pin change interrupts
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>
2026-04-17 14:48:01 +01:00
Damian Gryski 6426bf79b4 make spellfix (+ manual tweak) 2026-04-17 10:33:07 +02:00
Damian Gryski aed8116b22 GNUmakefile: add context and expvar to stdlib tests 2026-04-17 09:12:32 +01:00
Damian Gryski f6c759ccbf GNUmakefile: add encoding/xml to stdlib tests on Linux 2026-04-15 22:43:04 +01:00
deadprogram 4b389b90e8 targets: add pins/setup for Arduino UNO Q board QWIIC connector
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-15 16:52:32 +01:00
deadprogram 4c3680635e machine/stm32: fix UART interrupt storm caused by uncleared overrun error
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>
2026-04-15 15:42:06 +01:00
Joel Wetzell 0755145824 create generic targets for esp32 boards 2026-04-15 10:28:33 +01:00
Jake Bailey eabd1ddce0 os: add UserCacheDir and UserConfigDir 2026-04-15 08:19:08 +01:00
deadprogram 0e84235f9a machine/esp32s3,esp32c3: add txStalled flag to skip USB serial spin when no host
When no USB host is reading, flushAndWait() spins 50K iterations per
FIFO-full event. With putchar calling WriteByte per byte, the cumulative
delay starves I2C and other peripherals, freezing displays.

Add a txStalled flag: the first FIFO-full triggers one flushAndWait
attempt. If it fails (no host), txStalled is set and all subsequent
writes return immediately with no spin — just a register read and a
bool check. When a host reconnects, SERIAL_IN_EP_DATA_FREE goes back
to 1, bypassing the stall path and clearing the flag automatically.
2026-04-14 18:26:30 +01:00
deadprogram 894839484c net: update to Go 1.26.2 net package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-14 07:57:09 +01:00
deadprogram 1fadd0e772 builder: use target-specific -march for RISC-V library compilation
The -march flag for compiling C libraries (compiler-rt, picolibc) was
hardcoded to rv32imac for all riscv32 targets. This is incorrect for
targets that lack the atomic extension, such as ESP32-C3 (rv32imc) and
TKey (rv32iczmmul).

Extract the -march value from the target's cflags when available,
falling back to the previous defaults (rv32imac, rv64gc) for targets
that don't override it.

Fixes #5114

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-13 08:09:26 +01:00
deadprogram eba6e7a428 machine/esp32s3,esp32c3: make USB Serial/JTAG writes non-blocking when FIFO is full
When no USB host is reading (e.g. board not connected to a serial
monitor), WriteByte and Write would spin for up to 200k iterations
per byte waiting for the FIFO to drain. This stalled the entire
application, freezing unrelated peripherals like I2C displays.

Reduce flushTimeout from 200,000 to 50,000 iterations (~3ms) which
is enough for 2-3 USB frames when a host is connected, but short
enough that serial output won't freeze the application when no host
is reading. Serial output is best-effort; callers like putchar
already ignore write errors.

Applies to both ESP32-S3 and ESP32-C3 which share the same USB
Serial/JTAG controller design.
2026-04-13 06:23:23 +01:00
deadprogram f4a6e0373b main: update espflasher and auto-use best available flashing options on esp32
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-11 20:31:21 +01:00
deadprogram d274545e63 targets: add "adb" flash method using Android Debug Bridge
Add a new flash method that uses adb to flash boards over Android Debug
Bridge. The method supports running pre-flash shell commands via
"adb shell", pushing the firmware binary via "adb push", and running
post-flash shell commands with a {remote} token for the remote path.

New target JSON fields:
- adb-pre-commands: shell commands to run before pushing firmware
- adb-push-remote: remote device path for adb push
- adb-post-commands: shell commands to run after pushing firmware

Switch arduino-uno-q target to use the new adb flash method with
openocd running on the remote device.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-11 17:19:46 +01:00
deadprogram f74d25c882 build: use Golang 1.26 for all builds
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-11 12:38:36 +01:00
deadprogram 618c689246 runtime: fix Darwin syscall return value truncation and lost arguments
Go 1.26 replaced the individual Darwin syscall entry points (syscall,
syscallX, syscallPtr, syscall6, syscall6X) with two variadic functions:
syscalln and rawsyscalln. The old wrappers now have Go bodies that call
these, then use errno/errnoX/errnoPtr to interpret the result.

This caused two bugs in our rawsyscalln implementation:

1. Return value truncation: We used call_syscall/call_syscall6 which
   return int32, truncating 64-bit results (pointers from fdopendir,
   offsets from lseek, addresses from mmap). For example, a DIR* pointer
   returned by fdopendir would lose its upper 32 bits on arm64, causing
   SIGSEGV when later accessed.

2. Lost 4th argument: The case 4/5/6 fallthrough had 'a3 = args[3]'
   instead of 'a4 = args[3]', and a3 was immediately overwritten by
   'a1, a2, a3 = args[0], args[1], args[2]'. This lost the 4th syscall
   argument entirely, breaking pread (offset=0) and causing wrong data
   to be read from files.

Fix by using call_syscallX/call_syscall6X (returning full uintptr),
always reading errno (letting the Go wrappers decide what to do with
it), and correcting the argument assignment.
2026-04-11 12:38:36 +01:00
deadprogram 9d29971755 interp: make ptrtoint size mismatch a recoverable error
Go 1.26 changed Darwin's rawSyscall/Syscall from assembly stubs to Go
wrappers that call variadic rawsyscalln/syscalln. When the interp tries
to evaluate syscall.init() at compile time (which calls Getrlimit →
rawSyscall → rawsyscalln), it encounters a ptrtoint of a function
pointer and fails with 'ptrtoint integer size does not equal pointer
size'.

Make this a recoverable error so the interp reverts the init
interpretation and defers it to runtime, matching the behavior for other
uninterpretable init functions.
2026-04-11 12:38:36 +01:00
deadprogram 451c57af92 runtime: add syscall.runtimeClearenv for Go 1.26
Go 1.26 added syscall.runtimeClearenv (called by syscall.Clearenv) which
must be provided by the runtime via go:linkname. Without it, the os
package fails to link.
2026-04-11 12:38:36 +01:00
deadprogram 59feac28b9 internal/syscall/unix: implement GetRandom for WASI targets
Go 1.26's crypto/internal/sysrand uses internal/syscall/unix.GetRandom
on Linux-like targets (including wasip2 which sets GOOS=linux). The
existing stub panicked with 'todo: unix.GetRandom', causing crypto/ecdsa
tests to fail on wasip2.

Implement GetRandom on WASI targets (wasip1, wasip2) by calling the
arc4random_buf libc function that TinyGo's runtime already provides.
For other TinyGo targets, return ENOSYS so sysrand can fall back to
/dev/urandom.
2026-04-11 12:38:36 +01:00
deadprogram c1f48fc79a loader, crypto/internal/entropy: fix 32 MiB RAM overflow on microcontrollers
Go 1.26 added a CPU jitter-based SP 800-90B entropy source for FIPS 140-3
compliance (crypto/internal/entropy/v1.0.0). It declares a 32 MiB global
ScratchBuffer ([1<<25]byte) used for memory access timing noise. On systems
with virtual memory this stays in .noptrbss and is lazily paged, but on
baremetal targets it becomes static RAM, causing fatal overflow (e.g.
pybadge with 192 KB SRAM reports 33 MB overflow).

Since FIPS jitter entropy is never used on TinyGo targets (fips140.Enabled
is always false, so drbg.Read falls through to sysrand.Read), provide a
TinyGo overlay that replaces ScratchBuffer with [0]byte and stubs out all
entropy functions with panics.
2026-04-11 12:38:36 +01:00
deadprogram 3c07a36e95 loader, unicode/utf8: fix hiBits overflow on 16-bit AVR targets
Go 1.26 added SWAR optimizations to unicode/utf8 that use:

  const ptrSize = 4 << (^uintptr(0) >> 63)
  const hiBits = 0x8080808080808080 >> (64 - 8*ptrSize)

This formula only distinguishes 32-bit and 64-bit architectures.
On AVR (16-bit uintptr), ptrSize computes as 4, and hiBits becomes
0x80808080 (2155905152) which overflows the 16-bit uintptr type.

Fix by providing a patched unicode/utf8 overlay for Go 1.26+ that
uses a ptrSize formula handling all three sizes (16/32/64-bit):

  const ptrSize = 1 << (^uintptr(0)>>15&1 + ^uintptr(0)>>31&1 + ^uintptr(0)>>63&1)

This evaluates to 2 on AVR, 4 on 32-bit, and 8 on 64-bit. The
word() helper also gains a ptrSize==2 case for 16-bit loads.
2026-04-11 12:38:36 +01:00
deadprogram 98b3c27c76 builder: fix SIGSEGV when stripping duplicate function definitions
The previous approach of erasing basic blocks one-by-one from
duplicate function definitions could crash with a segfault. When
a function has complex control flow (branches, switches, PHI nodes),
deleting a basic block that is still referenced by instructions in
other blocks of the same function causes use-after-free in LLVM.

LLVM's C++ Function::deleteBody() avoids this by calling
dropAllReferences() on all blocks first, but that API is not
exposed in the Go LLVM bindings.

Fix this by using a completely different approach: instead of
manually deleting the function body, set the duplicate function's
linkage to LinkOnceODRLinkage. This tells the LLVM linker that the
definition can be merged with another copy, causing it to prefer
the already-linked ExternalLinkage definition in the destination
module. The result is the same (the runtime's definition wins) but
without any unsafe block manipulation.
2026-04-11 12:38:36 +01:00
deadprogram ddacdfacd5 compiler, runtime: fix SyscallN handling for Windows on Go 1.26+
Go 1.26 changed all Windows syscall wrappers in zsyscall_windows.go
to use SyscallN instead of fixed-argument Syscall/Syscall6/etc. The
SyscallN function now has a body that calls an unexported syscalln
function (provided by runtime via //go:linkname).

TinyGo's existing createSyscall compiler builtin used call.Args[2:]
to extract syscall arguments, but for variadic SyscallN the SSA
representation passes args as a slice value (not individual args),
causing call.Args[2:] to be empty -- resulting in zero arguments
being passed to Windows API calls and 0xc0000005 access violations.

Fix this by:
1. Excluding syscall.SyscallN from builtin interception, letting
   Go 1.26's function body compile normally (it calls syscalln)
2. Adding a new createSyscalln compiler builtin that intercepts
   syscall.syscalln and correctly handles the variadic slice:
   - Generates a switch on the arg count n (0-18 cases)
   - Each case loads args from the slice via GEP/Load
   - Wraps calls with SetLastError(0)/GetLastError() as before
   - Handles i386 stdcall conventions
3. Adding runtime stubs for both Go versions:
   - go1.26: syscall.syscalln stub (body intercepted by compiler)
   - pre-go1.26: syscall.SyscallN stub (linker satisfaction)
2026-04-11 12:38:36 +01:00
deadprogram 2d477e069d builder, runtime: fix duplicate symbol error with Go 1.26+ on Windows
Go 1.26 changed syscall.loadlibrary, syscall.loadsystemlibrary, and
syscall.getprocaddress from declarations to definitions in
syscall/dll_windows.go. TinyGo's runtime also defines these via
//go:linkname, causing "symbol multiply defined!" during LLVM module
linking.

Resolve this by detecting duplicate function definitions before calling
llvm.LinkModules and turning the incoming duplicate into a declaration,
so the runtime's version wins. TinyGo's implementations must take
precedence because Go 1.26's versions depend on //go:cgo_import_dynamic,
which TinyGo does not support.

Also fix the signature of syscall_loadsystemlibrary to match the
standard library (remove unused absoluteFilepath parameter).

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-11 12:38:36 +01:00
Damian Gryski ed1a56785a internal, runtime: add internal fips bool 2026-04-11 12:38:36 +01:00
Damian Gryski f496571747 compiler: add instrinsic for crypto/internal/constanttime.boolToUint8 2026-04-11 12:38:36 +01:00
Damian Gryski 17fed5980b internal/abi: add EscapeNonString, EscapeToResultNonString 2026-04-11 12:38:36 +01:00
Damian Gryski 2ff04f1a87 runtime: split syscall functions for darwin changes for 1.26 2026-04-11 12:38:36 +01:00
Damian Gryski f763865c5c src/internal/itoa: resurrect from Go stdlib
We use this a bunch. Upstream switched to `internal/strconv` which is much heavier.
2026-04-11 12:38:36 +01:00
Damian Gryski 52edf52245 builder: claim we support 1.26 now 2026-04-11 12:38:36 +01:00
Patricio Whittingslow 8161b766d3 add soypat/lneto to corpus 2026-04-09 16:19:33 +01:00
deadprogram 709349e24b device/esp: fix tinygo_scanCurrentStack to spill register windows
The previous implementation was a bare tail-jump to tinygo_scanstack
without spilling any registers or passing an sp argument.  On Xtensa
windowed ABI, heap pointers held in physical registers were invisible
to the conservative GC, causing it to collect live objects and leading
to nil pointer dereferences under allocation pressure.

Flush all register windows to the stack using recursive call4 (15
levels for NAREG=64), then pass the current sp to tinygo_scanstack so
the GC scan from sp to stackTop covers every live value.  Interrupts
are briefly masked during the spill to prevent window-overflow
exceptions from interfering.

Fixes crashes on ESP32-S3 observed when serving concurrent HTTP
requests.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-08 18:51:42 +01:00
MaximAL 5fd4878574 Fix grammar in quote 2026-04-07 10:39:41 -07:00
deadprogram a24dc8c543 esp32s3: improve exception handlers, add procPin/procUnpin, and linker wrap flags
Rewrite kernel and double exception handlers to save EXCCAUSE/EPC1 to
RTC STORE registers before triggering a software reset, replacing the
LED-blink diagnostic with post-mortem debug info that survives reset.

Add user exception dispatch in the level-1 handler with a weak
espradio_user_exception symbol so programs without espradio still link.

Implement procPin/procUnpin for Xtensa using RSIL/WSR PS to properly
disable interrupts during atomic operations. Fix abort() to use a
waiti loop instead of bare spin.

Add --wrap ldflags for malloc/calloc/free/realloc/ppCheckTxConnTrafficIdle
to support espradio WiFi blob integration.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-07 16:27:02 +02:00
deadprogram c3d514a751 esp32s3: fix Xtensa register window spill using recursive call4 in task switching
Replace the ROTW-based register flush with a recursive call4 approach
that properly triggers hardware window-overflow exceptions. ROTW only
modifies WindowBase without saving registers, causing corruption when
switching goroutines. The recursive call4 correctly spills all 15 window
panes. Also clear WindowStart after the stack switch to prevent stale
overflow of garbage register values.

Add tinygo_task_current export for C interop.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-07 16:27:02 +02:00
deadprogram 5b7a2c73db esp32s3: update linker script and boot assembly for multi-page flash XIP mapping
Extend the linker script with proper IROM/DROM section layout for flash
execute-in-place. Update the boot assembly MMU init to dynamically map
all required flash pages based on _irom_end/_drom_end symbols instead
of hardcoding a single page.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-07 16:27:02 +02:00
deadprogram aaf6a36c55 esp32s3: add flash XIP boot assembly with cache/MMU init
The ESP32-S3 ROM bootloader loads IRAM/DRAM into SRAM but does not
configure the flash cache or MMU. Previously the target incorrectly
reused the ESP32 boot assembly (esp32.S) which lacks flash XIP support.

Add a dedicated esp32s3.S boot assembly that:
- Sets up windowed-ABI registers, stack, and FPU
- Disables all watchdog timers (RTC, TIMG0, TIMG1, Super WDT)
- Configures VECBASE and clears PS.EXCM before any callx4
- Calls ROM functions to configure cache modes:
  rom_config_instruction_cache_mode (16KB, 8-way, 32B line)
  rom_config_data_cache_mode (32KB, 8-way, 32B line)
- Initializes MMU, maps flash page 0 for IROM and DROM,
  clears bus-shut bits, and enables both caches
- Jumps to runtime.main in IROM (flash)

Update the linker script (esp32s3.ld) to place .text and .rodata in
flash-mapped regions (IROM/DROM) with proper alignment for the MMU
page size. Update esp32s3-interrupts.S with proper exception vector
handlers. Point esp32s3.json at the new esp32s3.S instead of esp32.S.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-07 16:27:02 +02:00
deadprogram dc6e0e23b9 cgo: allow for changes to 'short-enums' flag
This allows for changes to the 'short-enums' flag when
using CGo since TinyGo already does this, and it is
needed for the esp32s3 processor.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-07 16:27:02 +02:00
Abhishek Goyal 44b0c79eaf loader: support "all:" prefix in //go:embed patterns
The "all:" prefix in //go:embed directives instructs the compiler to
  include hidden files (starting with "." or "_") when embedding a
  directory. Previously, the prefix was passed literally to path.Match,
  which would never match.

  Introduce an embedPattern struct that parses and validates the pattern
  at construction time via newEmbedPattern(). The "all:" prefix is
  stripped once and stored along with the glob pattern, avoiding repeated
  string prefix checks. Pattern validation is absorbed into the
  constructor, guaranteeing that any embedPattern value is valid and
  eliminating the need for separate validation loops.

  Fixes embedding with patterns like "//go:embed all:static".
2026-04-05 14:15:40 +02:00
deadprogram 27e5baeb57 esp32s3: use TIMG0 alarm interrupt for sleepTicks
Replace the busy-wait sleepTicks with an interrupt-driven version that
sets a TIMG0 timer alarm and waits for the interrupt to fire. The timer
alarm handler disables INT_ENA at the peripheral level to prevent
level-triggered re-assertion; sleepTicks re-enables it after each wake.

This avoids burning CPU cycles during time.Sleep and similar delays.
2026-04-05 14:15:40 +02:00
deadprogram a6a4f85e61 esp32s3: replace inline ISR with full interrupt vector handler
Replace the minimal inline ISR (which only disabled INTENABLE) with a
full level-1 interrupt handler that saves/restores the interrupted
context and dispatches to Go's handleInterrupt.

The handler uses callx4 (not callx0) to call into Go code because:
- callx0 does not set PS.CALLINC, so the Go function's entry
  instruction uses stale CALLINC from the interrupted code, causing
  wrong window rotation and a garbage stack pointer.
- callx4 explicitly sets CALLINC=1, and our frame pointer (a1) is
  outside the callee's register window so it is preserved.

Also updates the USB Serial/JTAG ISR to disable INT_ENA (peripheral
level) instead of relying on INTENABLE, and adds signalInterrupt to
the dispatcher so sleepTicks can be woken by any interrupt.
2026-04-05 14:15:40 +02:00
Carlos Henrique Guardão Gandarez 3386316b9f targets: add esp32s3-supermini 2026-04-05 14:15:40 +02:00
Carlos Henrique Guardão Gandarez 410bb9c103 feat: add inheritable-only field to filter processor-level targets (#5270)
* feat: add inheritable-only field to hide processor-level targets from listing

Processor-level targets like esp32, rp2040, etc. have flash-method set
so they leak through the existing heuristic filter in GetTargetSpecs and
appear in `tinygo targets` output. Add an "inheritable-only" JSON field
that is checked on raw JSON before inheritance resolution, preventing
these non-board targets from being listed while keeping them loadable
for direct builds and inheritance.

Ref: tinygo-org/tinygo#5178

* fix: prevent inheritable-only from propagating to child targets

The InheritableOnly bool field was propagating from parent to child
targets through overrideProperties, which would hide board targets
from GetTargetSpecs. Fix by preserving/restoring the field across
resolveInherits. Also simplify GetTargetSpecs by removing the raw
JSON pre-check workaround and remove nonexistent esp32c6 from tests.

* prevent build commands to use inheritable only targets
2026-04-05 14:15:40 +02:00
deadprogram f176b3a480 esp32s3: switch USB implementation to use interrupts instead of polling
The previous implementation for USB was using polling instead of using
interrupts. This changes that.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
Ron Evans 9b1a3a2236 esp32s3: add interrupt support (#5244)
* esp32s3: add interrupt support

This finally adds the long awaited support for interrupts on the
Xtensa arch. Initially just for the ESP32-S3 but then others.

Signed-off-by: deadprogram <ron@hybridgroup.com>

* esp32s3: get interrupts working correctly

There were a number of needed changes in order to get interrupts correctly working
on the esp32s3 processor:

- PS.UM=1 in interruptInit() - routed interrupts to user exception vector (0x340)
instead of kernel (0x300)
- Inline ISR in the vector slot - external handlers via j/call0 crashed (likely
clang Xtensa literal pool issue with large movi constants in separate sections)
- Disable INTENABLE (not just INT_CLR) - the USB RX interrupt is level-triggered;
clearing INT_CLR alone causes infinite re-entry since data is still in the FIFO
- Buffered() re-enables INTENABLE after draining the hardware FIFO

Signed-off-by: deadprogram <ron@hybridgroup.com>

---------

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
Damian Gryski b29edadf48 reflect: fix reflect.TyepAssert for structs 2026-04-05 14:15:40 +02:00
deadprogram d04332d688 fix: use external 7zip for scoop installs on Windows
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 547288529c chore: update all github actions to latest versions
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram bd8de8719f interrupt/esp32c3: add Enable stub for QEMU test target
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 2fd44ac341 build: let scoop run updates to resolve binaryen install failures on Windows
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram dc30aef687 builder: fix panic in findPackagePath when using -size short
This fixes an "index out of range" panic that occurs when calculating
binary sizes. The strings.SplitN operation in findPackagePath can sometimes
return a slice with only one element, so we now verify the length of the
slice before attempting to access the second element.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 29cadaa23b main: add extra second of delay when flashing esp32 boards before reset
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram a7c149f8dc fix(esp32c3): map missing IRAM sections in linker script
Adds `.coexiram*`, `.wifiorslpiram*`, and `.iram1*` to the main `.iram`
segment in `esp32c3.ld`. This resolves the "Invalid image block, can't
boot" error on the ESP32-C3 by ensuring these precompiled ESP-IDF blob
sections are correctly aligned in memory.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram c2a04e1a45 machine/esp32c3: implement BlockDevice for esp32c3 flash
- Add `machine_esp32c3_flash.go` to implement the `BlockDevice` interface for ESP32-C3.
- Map internal ESP ROM SPI flash and cache invalidation functions via CGo.
- Update `targets/esp32c3.ld` to expose `__flash_data_start` and `__flash_data_end` linker variables.
- Add `esp32c3` to build tags in `src/machine/flash.go`.
- Ensure atomic flash operations by disabling interrupts and invalidating cache to prevent stale reads.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
rdon f748dc6fd8 fix: replace ! with ~ for register mask 2026-04-05 14:15:40 +02:00
deadprogram bfc21ad906 targets/esp32c3: fix to use PROVIDE() for WiFi/BLE ROM function addresses to allow blob overrides
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 41726fab9d target: add PWM constant values for Arduino UNO Q
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 82f4fcf2a3 machine/stm32: fix PWM problem due to register shifting.
The SVD-generated TIM constants in the STM32 device files have all per-channel
fields shifted by one channel position (e.g., CC1E is at the hardware position
of CC2E, OC1M_Pos is at the OC2M hardware position). This caused Set(), Unset(),
SetInverting(), and interrupt handlers to write to wrong bit positions.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 3a00f00f62 targets: change default stack size for esp32c3 and esp32s3 to 8196.
This changes the default stack size for the esp32-c3 and esp32-s3
to 8kb. Since they have more onboard memory and a larger stack size is
needed for any networking etc this default seems like a more sensible
one, in similar way to the rp2040/rp2350 defaults.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram a71f776203 runtime/esp32c3: use TIMG0 alarm interrupt for sleepTicks instead of busy-waiting
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram d2f60c02bb fix: correctly handle interrupt disable on esp32-c3
Priority 0 disables an interrupt on ESP32-C3. This corrects
the code to actually use that.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 7ca53138bc machine/stm32u585: implement ADC
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram a9c0501826 targets: add stm32u585 openocd commands for flashing on Arduino UNO Q
This adds the specific OpenOCD commands needed for flashing the onboard
STM32U585 MCU directly from the Arduino UNO Q board.

Thanks to https://github.com/jesdev-io/arduino-uno-q-pio-toolchain
for the infor on how to do this.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 60fb4b95fa targets: correct pin mapping for Arduino UNO Q STM32 MCU
Signed-off-by: deadprogram <ron@hybridgroup.com>

machine/stm32u585: fix PWR peripheral clock was never enabled

On STM32U5, PWR is on AHB3 and requires RCC.AHB3ENR.PWREN -
unlike some other STM32 families where PWR is always clocked.
Without this, all writes to PWR registers (including IO2SV for
VDDIO2) were silently dropped, so GPIOG pins could never drive.

Signed-off-by: deadprogram <ron@hybridgroup.com>

target: correct I2C pin mapping for Arduino UNO Q

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 323b62b405 fix: set stm32u5x clock rate to default
The complex 160MHz PLL initialization was hanging the MCU.

The fix: Replaced the entire PLL-based clock init with the MSI 4MHz default

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 40fb3466c1 machine/stm32: add Arduino UNO Q to smoketests
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 2333ef2454 machine/stm32: add STM32U5 SPI support
Add SPIv2 driver for STM32U5 using TXDR/RXDR and CFG1/CFG2 registers,
which differ from the classic SPI peripheral on older STM32 families.
Implements Configure() and single-byte Transfer().

Add SPI1 peripheral and pin definitions to arduino-uno-q board.
Exclude stm32u5 from the shared classic SPI build tag.
2026-04-05 14:15:40 +02:00
deadprogram 0ac7bf8c8b machine/stm32: add STM32U5 I2C support
Add I2C timing values for STM32U585 at 160MHz PCLK1 (10/100/400/500 KHz).
Add I2C1 peripheral and pin definitions to arduino-uno-q board.
Update i2c_revb build tag to include stm32u5.
2026-04-05 14:15:40 +02:00
deadprogram 38a3ccee52 machine/stm32: add STM32U5 GPIO, pin interrupts, and UART support
Add machine-level support for STM32U5 family: GPIO ports A-I with clock
enables via AHB2ENR1, EXTI pin interrupts (individual EXTI0-15 IRQs),
timer definitions (TIM1-8, TIM15-17), RNG, and alternate function clock
enables for all peripherals.

Add UART support with STM32U585 baud rate and register configuration.
Add arduino-uno-q board pin definitions and USART2 serial.

Update shared build tags: gpio_revb, gpio_reva, exti_exti, exti_syscfg.
2026-04-05 14:15:40 +02:00
deadprogram 47115f0380 machine/stm32: add STM32U585 target definition and runtime
Add processor target (cortex-m33), linker script (2048K flash, 768K RAM),
and arduino-uno-q board target. Runtime initializes 160MHz clock via PLL1
from HSI16, with PWR Range 1 and EPOD booster.

Also add psctype abstraction for timer PSC register width, needed because
the U5 SVD defines PSC as a 16-bit register.
2026-04-05 14:15:40 +02:00
Dima Jolkin 1f00425eb3 WiFi/BLE ROM 2026-04-05 14:15:40 +02:00
deadprogram 71245845c8 esp32c3: implement USBDevice using interrupts for more proper operation.
This improves the ESP32-C3 USBDevice implementation by using interrupts
to properly handle data RX/TX.

It also stubs out USB interfaces for HID functions, since those cannot
be implemented on ESP32-C3 due to using a JTAG-USB interface.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram d9f6f3b81f chore: update espflash to espflasher
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram b81a7c2bca fix: ESP32-C3 needs to clear MCAUSE after handling interrupt
MCAUSE was never being cleared after handling an interrupt.
On RISC-V, mret does NOT zero MCAUSE — it retains the last
trap cause. Every other TinyGo RISC-V target (FE310, K210,
QEMU) explicitly does riscv.MCAUSE.Set(0) after handling.
The ESP32-C3 was missing this.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 0ec2f0818a flashing: introduce flash method 'esp32jtag' for esp32c3/esp32s3 targets
This adds a new flash method 'esp32jtag' to explicitly define when this
reset method is needed. When flashing esp32c3/esp32s3 boards on Windows
this method of board reset is required, otherwise the reset does not
take place and the board cannot be flashed.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 4a6a82ae79 modules: update to latest serial and espflash packages
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram b14ee51ff6 esp32c3/esp32s3: refactoring and corrections for SPI implementation
This refactors and corrects the SPI implentation for the
ESP32C3 and ESP32S3 processors. There was a lot of duplicated
code, as well as some errors such as incorrectly calculating
speed on the esp32c3 implementation.

This will also be helpful when adding additional processors
that use very similar peripheral registers.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 394116e998 targets: switch rp2040/rp2350 to default to tasks scheduler
This switches the rp2040 and rp2350 to use the tasks scheduler
by default instead of using the cores scheduler. Too many race
conditions at present, we need to look into exactly why. In
the meantime, going back to the tasks as default will address
a lot of these intermittent problems.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram f902437848 esp32c3/esp32s3: refactor ADC implementation to reduce code duplication.
This refactoring reduces code duplication from the esp32c3/esp32s3 ADC
implementation, by reusing the register/efuse calibration code since the
same basic procedures are used by both processors.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
Pat Whittingslow 86cc3b6c12 usb/cdc: Better ring buffer implementation (#5209)
* begin adding ring512 implementation

* refactor to make operation driven fuzz test

* refactor USBCDC.Read to use ring512

* try txhandler separate to flush

* working USBCDC with large packets

* fix binary size

* remove comment

* documentation improvements
2026-04-05 14:15:40 +02:00
Dima 6bb6ea2432 esp32s3 + c3 ADC (#5231)
* save

* esp32s3: save

* adc

* worker on esp32s3

* worker after flash arduino

* save

* fix

* simple adc

* added adc

* esp32s3-adc: rm debug

* esp32s3-adc: clear

* last refactor

* linters

* esp32s3-adc: recover example

* esp32s3-adc: reuse fuse for esp32c3

* esp32s3-adc: refactor bugs

* esp32s3-adc: fix adc2 for esp32c3

* esp32s3-adc: group to adc files

* esp32s3-adc: revert changing board

* esp32s3-adc: recover example adc

* esp32s3-adc: fix edge values adc & added smoketests

* esp32s3-adc: rename methods

* esp32s3-adc: extends adc tests

* esp32s3-adc: drop debug

* esp32s3-adc: added ADCX const

* esp32s3-adc: change adc tests

* esp32s3-adc: added comment for esp32c3

* esp32s3-adc: drop debug empty loops

* esp32s3-adc: drop duplicate gpio

* esp32s3-adc: change return values to 0..65520
2026-04-05 14:15:40 +02:00
deadprogram 3c590e36ad machine: implement RNG for esp32s3 based on the onboard hardware random number generator.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 69a4dda8c8 modules: update espflash to 0.2.0 for esp8266 fixes
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 8fbaffe56e targets: modify esp32, esp32s3, esp32c3, & esp8266 targets to use built-in esp32flash
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram bfafef8df1 feature: add esp32flash flash method for esp32s3/esp32c3/esp32/esp8266
This adds usage of the new espflash package to perform flashing on
ESP32, ESP32S3, ESP32C3, & ESP8266 boards. This means that you no longer
have to install esptool.py in order to flash ESP32 based boards.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 26ff622148 targets: add support for Seeedstudio Xiao-RP2350 board.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
Michael Teichgräber 88a68eaf7e machine: stm32l0x1,l0x2: TIM: adapt to 32-bit register access
Recent changes in stm32-svd result in a change from previous
16-bit register access to 32-bit access.
Both access types are allowed, according to the register manuals.
2026-04-05 14:15:40 +02:00
Michael Teichgräber 46a2a177c1 machine: sync stm32g0 with updated stm32 device files
USART.ISR_FIFO_ENABLED => USART.ISR
          IRQ_TIM6_DAC => IRQ_TIM6_DAC_LPTIM1
            ADC.CHSELR => ADC.CHSELR0 (alternate registers 0/1)
2026-04-05 14:15:40 +02:00
Michael Teichgräber b0ea05a853 targets/swan: rename group build tag stm32l4x5 => stm32l4y5 to avoid clash with stm32 device name
Previously, there was no specific stm32f4r5.svd in lib/stm32-svd,
just stm32f4x5.svd was used; now, both files are present. This means
that the existing build-tag stm32f4r5 will include the
device/stm32/stm32f4r5.go file, and the additional build-tag stm32f4x5
would include the device/stm32/stm32f4x5.go file as well, resulting
in build conflicts. Renaming just the tag, which is used in src/machine,
and src/runtime, into stm32f4y5 solves this issue.
2026-04-05 14:15:40 +02:00
Michael Teichgräber f3b79c412f targets/nucleo-f722ze.json: add build-tag stm32f722, change stm32f7x2.s to ..722.s
In stm32-rs, stm32f7x2.svd got replaced by stm32f722.svd and stm32f732.svd.
This change adjusts the target definition where stm32f7x2 is used,
2026-04-05 14:15:40 +02:00
Michael Teichgräber f4f3ad5051 lib/stm32-svd: update submodule 2026-04-05 14:15:40 +02:00
knieriem 0757ccc657 update tools/gen-device-svd w.r.t. recent changes to the structure of stm32-svd files (#5212)
* tools/gen-device-svd: orderPeripherals: prevent skipping base peripherals derived by name

Recent SVDs from stm32-rs, like the one for stm32u595, define derivedFrom
attributes that do not refer to a peripheral group name, but to a
peripheral name. For instance, the peripheral I2C5 may be derived from
"I2C1", not from "I2C". The previous algorithm records, in case the
group name is non-empty, only the group name in the
knownBasePeripherals map, not the name of the peripheral itself.
So in case of the base peripheral I2C1 with group name I2C: although
the peripheral gets added to the sorted list, it would be added to
knownBasePeripherals with the group name "I2C" as key, not with "I2C1".
A following peripheral, I2C5, derived from I2C1, with an empty group
name, would be recorded as known with key "I2C5", but omitted from the
sorted list, because "I2C1" is not recognized as known.
The following peripheral SEC_I2C5, derived from I2C5, with empty group
name, would be added to both the knownPeripherals map and the sorted list.
So if, later, the sorted list is examined, it would find SEC_I2C5
earlier than its base peripheral I2C5, which would be missing from
"peripheralDict", resulting in a nil pointer access.
This patch makes sure that, to stay with the example, that
"I2C1" is recorded as known too (not only the group name "I2C"),
so that "I2C5" won't be skipped anymore, preventing the program from
crashing.

* tools/gen-device-svd: orderPeripherals: ensure ordered content of missingBasePeripherals

After the first run, missingBasePeripherals may contain peripherals
with dependencies that are not guaranteed to be in proper order.

This change implements additional loop runs that try to reduce the
size of the missingBasePeripherals as far as possible.

[With recent SVDs from stm32-rs this change will not produce different
results, though (since these source files contain already properly
ordered peripherals).]

* tools/gen-device-svd: Register: move dim array decoding to utility type dimArray

This allows encoding of dim increment and array indices to be re-used
by other elements supporting dim arrays.

This change just restructures parts of register specific code,
it does not change the output of the program.

* tools/gen-device-svd: parseBitfields: support field dim arrays

Patched SVD files from stm32-rs recently contain many fields with
dim array parameters and names containing %s (like "CC%sIF").
This change adjusts parseBitfields so that these field elements
get resolved.

* tools/gen-device-svd: SVDField: allow multiple enumeratedValues

In recent patched SVD files from stm32-rs there may be two
enumeratedValues elements per SVDField, not just one.
The SVD specification allows up to two entries (they may be used
to define different enums for read and write access).

This change extends SVDField and parseBitfields so that
two enumeratedValues are processed like a single one.

* tools/gen-device-svd: orderPeripherals: sort peripherals of same group with larger number of registers/bitfields first

In group "TIM" there may be general purpose timers like TIM16 and
advanced timers like TIM1. The advanced peripheral may contain a larger
number of registers than the general purpose ones.
TIM1 may contain CCR1..CCR4, SMCR and OR1, while TIM16 only knows
about CCR1.
Unfortunately in some SVDs, like the one for stm32g031, TIM16 is defined
before TIM1. Since register and bitfield constants are generated taking
only the first peripheral of a group into account, the resulting .go
file may lack definitions for e.g. CCR2..CCR4, SMCR, and OR1.

This change adjusts orderPeripherals so that, to stay with the example,
a peripheral like TIM1 will be moved in front of TIM16, resulting in an
output file containing the larger set of definitions.

* tools/gen-device-svd: SVDEnumeration: support isDefault

Recent SVD files created by stm32-rs use "isDefault" in enumeratedValue
elements for purposes like the Div1 enum for clock prescaler registers
without specifying a specific value.
Previously, these enumeratedValues would be skipped because of the
enumEl.Value == 0 condition, and the corresponding const definitions
like "RCC_CFGR2_PPRE2_Div1 = 0x0" would be missing from the
resulting .go files, so existing code relying on these constants would
not compile anymore.
This change adds a utility type enumDefaultResolver that helps
finding an actual value that is unused by the enumeratedValues that
are defined for the field. More examples for values marked
as "isDefault", along with their resolved values:

  DAC_CR_WAVE1_Triangle => 2
  IWDG_PR_PR_DivideBy256 => 6
  DAC_CR_MAMP2_Amp4095 => 0xb

* tools/gen-device-svd: support derivedFrom attribute at field level

This ensures that some more constants are included in the .go files
that would otherwise be skipped (like e.g. ADC_SMPR2_SMP1_Cycles*
of some STM32 devices), which prevented compilation of some programs.

To avoid extending a lot of func argument lists, and since there
is no context.Context in use yet, this change introduces a
global derivationContext.

* tools/gen-device-svd: tweak: stm32: ensure USART_ISR_TXE/TXFNF are present

* tools/gen-device-svd: stm32: ensure CCMR*_Output alternate registers are sorted first

* tools/gen-device-svd: stm32: add IWDG peripheral alias if SVD defines IWDG1
2026-04-05 14:15:40 +02:00
Dima 47282be619 esp3s3 & esp32c3 PWM implements (#5215)
* esp3s3 pwm

* esp32s3-pwm: added spi alias for esp32s3 for tests

* esp32s3-pwm: fix linters

* esp32s3-pwm: fix rename

* esp32s3-pwm: replace if to switch

* esp32s3-pwm: chip specific registry

* esp32s3-pwm: fix linters
2026-04-05 14:15:40 +02:00
deadprogram 9bdbe943ce transform: modify output format from the -print-allocs flag to be the same as expected by the go coverage tool
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
Dima 180220a2e3 Esp32s3 i2c interface (#5210)
* esp32s3-i2c implement interface

* esp32s3-i2c: disable m5stamp_c3

* added simple tests without Listen

* replace smoke tests

* esp32s3-i2c: fix allocation in tx
2026-04-05 14:15:40 +02:00
Micah Cowell 8ef4af45d7 export UART0 and pins 2026-04-05 14:15:40 +02:00
Nia Waldvogel 9215aa7ece runtime: implement fminimum/fmaximum
The compiler may generate calls to fminimum/fmaximum on some platforms.
Neither of the libm implementations we statically link against have these functions yet.
Implement them ourselves.
2026-04-05 14:15:40 +02:00
Dima Jolkin b497c5da71 esp32s3-usbserial: move InitSerial to init method 2026-04-05 14:15:40 +02:00
Dima Jolkin bf4d2fef4a esp32s3-usbserial: common usbserial for both esp32c3 & esp32s3 2026-04-05 14:15:40 +02:00
Dima Jolkin c053b8fc8a esp32s3-usbserial: split usb 2026-04-05 14:15:40 +02:00
Dima Jolkin d364ac6024 esp32s3-usbserial: added usbserial printing 2026-04-05 14:15:40 +02:00
deadprogram d78be5112b make: remove machine without board from smoketest
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram b5233eea56 targets: correct name/tag use for esp32s3-wroom1 board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
deadprogram 09a6612742 fix: init heap before random number seed on wasm platforms
This changes the order for initialization of the random number
seed generation on wasm platforms until after the heap has been
initialized. Should fix #5198

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
Dima 1778ad620f Esp32s3 implement spi (#5169)
* esp32s3 spi

* stabilization freq cpu

* cheange clacl freq for spi

* fix linters

* esp32s3-spi: change default pins for esp32s3 xiao

* set default configuration

* esp32s3-spi: extends smoketests for esp32s3
2026-04-05 14:15:40 +02:00
deadprogram 458b50bc07 sponsorship: add explicit callout/link in README to help out TinyGo
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:40 +02:00
Elias Naur fca8784dc3 builder: order embedded files deterministically 2026-04-05 14:15:40 +02:00
Elias Naur a846ee2321 flake.*: bump to nixpkgs 25.11
Bump the GitHub Actions Nix install as well; nixpkgs 25.11 requires a
newer nix command.
2026-04-05 14:15:40 +02:00
deadprogram df087b592f build: update CI builds to use latest Go 1.25.7 release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:15:39 +02:00
Matthew Hiles eade931650 Add per-byte timeout budget for rp2 I2C (#5189)
* Add per-byte timeout budget for rp2 I2C

* run goimports
2026-04-05 14:15:39 +02:00
Yaj a7649f5b42 targets: Add Shrike Lite board (#5170)
* feat: Add Vicharak Shrike Lite

* Add shrike-lite to smoketest
2026-04-05 14:15:39 +02:00
robo f0b953dec9 Fix syntax for building with TinyGo 2026-04-05 14:15:39 +02:00
Jesús Espino bb8196653f machine/attiny85: add USI-based SPI support (#5181)
* machine/attiny85: add USI-based SPI support

Implement SPI communication for ATTiny85 using the USI (Universal Serial
Interface) hardware in three-wire mode. The ATTiny85 lacks dedicated SPI
hardware but can emulate SPI using the USI module with software clock
strobing.

Implementation details:
- Configure USI in three-wire mode for SPI operation
- Use clock strobing technique to shift data in/out
- Pin mapping: PB2 (SCK), PB1 (MOSI/DO), PB0 (MISO/DI)
- Support both Transfer() and Tx() methods

The implementation uses the USI control register (USICR) to toggle the
clock pin, which triggers automatic bit shifting in hardware. This is
more efficient than pure software bit-banging.

Current limitations:
- Frequency configuration not yet implemented (runs at max software speed)
- Only SPI Mode 0 (CPOL=0, CPHA=0) supported
- Only MSB-first bit order supported

* machine/attiny85: add SPI frequency configuration support

Add software-based frequency control for USI SPI. The ATtiny85 USI lacks
hardware prescalers, so frequency is controlled via delay loops between
clock toggles.

- Calculate delay cycles based on requested frequency and CPU clock
- Fast path (no delay) when frequency is 0 or max speed requested
- Delay loop uses nop instructions for timing control

* machine/attiny85: add SPI mode configuration support

Add support for all 4 SPI modes (Mode 0-3) using USI hardware:
- Mode 0 (CPOL=0, CPHA=0): Clock idle low, sample on rising edge
- Mode 1 (CPOL=0, CPHA=1): Clock idle low, sample on falling edge
- Mode 2 (CPOL=1, CPHA=0): Clock idle high, sample on falling edge
- Mode 3 (CPOL=1, CPHA=1): Clock idle high, sample on rising edge

CPOL is controlled by setting the clock pin idle state.
CPHA is controlled via the USICS0 bit in USICR.

* machine/attiny85: add LSB-first bit order support

Add software-based LSB-first support for USI SPI. The USI hardware only
supports MSB-first, so bit reversal is done in software before sending
and after receiving.

Uses an efficient parallel bit swap algorithm (3 operations) to reverse
the byte.

* GNUmakefile: add mcp3008 SPI example to digispark smoketest

Test the USI-based SPI implementation for ATtiny85/digispark.

* machine/attiny85: minimize SPI RAM footprint

Reduce SPI struct from ~14 bytes to 1 byte to fit in ATtiny85's limited
512 bytes of RAM.

Changes:
- Remove register pointers (use avr.USIDR/USISR/USICR directly)
- Remove pin fields (USI pins are fixed: PB0/PB1/PB2)
- Remove CS pin management (user must handle CS)
- Remove frequency control (runs at max speed)
- Remove LSBFirst support

The SPI struct now only stores the USICR configuration byte.

* Revert "machine/attiny85: minimize SPI RAM footprint"

This reverts commit 387ccad494.

* machine/attiny85: reduce SPI RAM usage by 10 bytes

Remove unnecessary fields from SPI struct while keeping all functionality:
- Remove register pointers (use avr.USIDR/USISR/USICR directly)
- Remove pin fields (USI pins are fixed: PB0/PB1/PB2)
- Remove CS pin (user must manage it, standard practice)

Kept functional fields:
- delayCycles for frequency control
- usicrValue for SPI mode support
- lsbFirst for bit order support

SPI struct reduced from 14 bytes to 4 bytes.

---------
2026-04-05 14:14:55 +02:00
Damian Gryski a1b44ddd58 testdata: more corpus entries (#5182)
* testdata: more corpus entries

* testdata: remove skipwasi for dchest/siphash build issues
2026-04-05 14:14:55 +02:00
deadprogram 48b0d7f434 chore: update version to 0.41.0-dev
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:14:55 +02:00
Nia Waldvogel d8b6b257f4 compiler: simplify createObjectLayout
This simplifies the process of constructing and encoding layout bitmaps.
Instead of creating big integers and merging them, we can create a pre-sized bitmap and set positions within it.

This also changes the encoding logic to allow larger layouts to be encoded inline.
We would previously not encode a layout inline unless the size was less than the width of the data field.
This is overly conservative.
A layout can be encoded inline as long as:
1. The size fits within the size field.
2. All set bits in the bitmap fit into the data field.
2026-04-05 14:14:55 +02:00
deadprogram c98972a3e0 machine/rp: add Close function to UART to allow for removing all system resources/power usage
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:14:55 +02:00
deadprogram b93759936c machine/rp: use the blockReset() and unresetBlockWait() helper functions for all peripheral reset/unreset operations
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-05 14:14:55 +02:00
Jesús Espino d62dda8140 machine: add attiny85 pwm support (#5171)
* machine/attiny85: add PWM support for Timer0 and Timer1

Add complete PWM implementation for ATtiny85, supporting both Timer0
and Timer1 with their respective output channels:
- Timer0: 8-bit timer for pins PB0 (OC0A) and PB1 (OC0B)
- Timer1: 8-bit high-speed timer for pins PB1 (OC1A) and PB4 (OC1B)

Timer1 provides more flexible period control with configurable top value
(OCR1C) and extended prescaler options (1-16384), making it well-suited
for LED PWM control and other applications requiring variable frequencies.

Implements full PWM interface including Configure, SetPeriod, Channel,
Set, SetInverting, Top, Counter, and Period methods.

* machine/digispark: document PWM support on pins

Add documentation to the Digispark board file indicating which pins
support PWM output:
- P0 (PB0): Timer0 channel A
- P1 (PB1): Timer0 channel B or Timer1 channel A
- P4 (PB4): Timer1 channel B

Includes package comment explaining Timer0 vs Timer1 capabilities,
with Timer1 recommended for more flexible frequency control.

* machine/attiny85: optimize PWM prescaler lookups

Replace verbose switch statements with more efficient implementations:

- SetPeriod: Use bit shift (top >>= prescaler-1) instead of 15-case
  switch for dividing uint64 by power-of-2 prescaler values

- Period: Replace switch statements with compact uint16 lookup tables
  for both Timer0 and Timer1, casting to uint64 only when needed

This addresses review feedback about inefficient switch-based lookups.
On AVR, this approach is significantly smaller:
- Bit shifts for uint64 division: ~34 bytes vs ~140 bytes
- uint16 tables: 22 bytes code + 32/16 bytes data vs ~140 bytes
- Total savings: ~190 bytes (68% reduction)

* examples/pwm: add digispark support and smoketest

Add digispark.go configuration for PWM example using Timer1 with pins P1 (LED) and P4. Also add digispark PWM example to GNUmakefile smoketests.

---------
2026-04-05 14:14:15 +02:00
Itxaka ca36fba7e4 feat: Implement Lchown function for changing file ownership (#5161)
* feat: Implement Lchown function for changing file ownership

- Added Lchown function to change the numeric uid and gid of a file or symbolic link.
- Included error handling to return *PathError for failed operations.
- Added unit tests for Lchown to verify behavior on different error scenarios.

* fix: fix chmod tests
2026-01-10 09:31:22 +01:00
deadprogram 743bf45e00 runtime: make timeoffset atomic
Make timeoffset atomic to be able to handle changing the system
time. Otherwise the scheduler can gets rather confused if you call
AdjustTimeOffset when there are multiple goroutines already running.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-01-09 23:20:47 +00:00
Damian Gryski 9bc5d21185 src/runtime: add MemStats.HeapObjects 2026-01-09 13:46:28 +00:00
Damian Gryski c8b56bd161 src/testing: add b.ReportMetric() 2026-01-09 11:17:53 +00:00
soypat fe20079300 rp2: prevent pwm Period method overflow 2026-01-09 09:36:32 +00:00
Jake Bailey 068d7c1b44 reflect: add TypeAssert 2026-01-08 21:40:42 +00:00
Ayke van Laethem 42fed60e61 stm32l0: add flash support
Flash on the STM32L0 series of chips works a bit different and needs a
slightly different implementation compared to other STM32 chips.
2026-01-08 16:09:11 +00:00
Amken USA 53c1ccaa42 STM32G0B1 Watchdog + ADC Support (#5158)
* - Fix SPI initialization by disabling interface before configuration.
- Ensure proper 8-bit configuration for STM32G0 SPI data register access.
- Add default pin initialization for SPI.

* Add Window Watchdog (WWDG) and ADC support for STM32G0

* Comment cleanup

* Refactored STM32G0 ADC sampling time configuration to use `switch` for improved readability
2026-01-08 14:52:14 +01:00
Nia Waldvogel 72f667cac9 main (test): skips mipsle test if the emulator is missing
This one specific case was missing a call to emucheck.
2026-01-08 10:52:05 +00:00
Jake Bailey 4fff472808 sync: add WaitGroup.Go 2026-01-07 20:01:42 +00:00
Amken USA cfd74c2954 Add stm32g0b1 support (#5150)
* Add STM32G0B1 target support

Introduce support for STM32G0B1 microcontrollers, including target-specific JSON files, linker scripts, and runtime initialization. This update adds hardware support for GPIO, UART, SPI, I2C, timers, and additional board-specific configurations like Nucleo-G0B1RE.

* Update STM32G0 clock initialization to 64MHz and adjust related configurations

Reconfigure STM32G0 to use a 64MHz system clock via PLL with HSI16 as the source. Update flash latency, prescaler settings, and I2C timing values to reflect the new frequency.

* Cleanup

* Cleanup

* Add STM32G0-specific UART implementation

Introduce a new UART implementation for the STM32G0 series with chip-specific setup and configuration methods. Update the generic STM32 UART code to exclude STM32G0.

* Refactor STM32G0 runtime and machine code to utilize chip-specific register access functions

Simplify and standardize register operations with dedicated setter methods in the STM32G0 runtime and machine code and cleanup redundant syntax.

* Remove redundant commented-out APBENR1 register operations in STM32G0 machine code

* Introduce FDCAN support for STM32G0B1 series

Add FDCAN peripheral implementation targeting STM32G0B1, including support for standard, extended identifiers, and bit rate configuration. Update board files to include FDCAN pins, instances, and clock configuration for Nucleo-G0B1RE and Amken Trio boards.
2026-01-06 23:12:02 +01:00
Nia Waldvogel 9bcca974ba internal/gclayout: use correct lengths
The GC bitmap length is measured in multiples of the pointer alignment.
This is equal to the pointer size on all architectures except AVR.
Replace the hardcoded lengths with lengths that are computed naturally.
2026-01-05 21:12:31 +00:00
Nia Waldvogel a40586d092 compiler: implement copy directly
The compiler now implements the copy builtin directly instead of calling sliceCopy.
The length is calculated with the llvm.umax.* intrinsics, and the move is performed by llvm.memmove.*.
Both of these operations are easily understood by LLVM's optimization passes.
The type's alignment is also provided to llvm.memmove.*, which is useful when rewriting the move.

Interp no longer needs to reimplement sliceCopy.
Some edge case handling was implemented by sliceCopy but not llvm.memmove.*/llvm.memcpy.*.
I copied this over, so copies of external slices should work now.
Volatile moves/copies are now run at runtime by interp.

There is a 4-byte size increase due to some confusing length logic in sendUSBPacket.
I will look at sendUSBPacket in a future PR.
2026-01-05 15:10:31 -05:00
Nia Waldvogel bc9708f51a compiler: fix min/max on floats by using intrinsics
This fixes two edge cases for min/max:
min/max(+0.0, -0.0) = -0.0, +0.0
min/max(number, NaN) = NaN, NaN

The compare and select method does not work here.
I switched to the llvm.minimum/llvm.maximum intrinsics which match the intended behavior.

The integer min/max were also swapped over to using intrinsics.
The compare and select path is now only used by strings.
2026-01-05 19:46:06 +00:00
Nia Waldvogel 8ef36ed939 machine: fix usb truncation?
Remove the sendUSBPacket maxLen param because this greatly confused the compiler.
It also fixes a bug where the length provided to the hardware may not match the length of the packet.
sendUSBPacket now panics if the sent packet is too big.

I also fixed some of the string descriptor logic where we could create a packet without fully populating it.

RP2* systems might require some more work since they are implemented very differently?
I don't have any of those to test with yet, so maybe someone can deal with them in a seperate PR?
2026-01-03 10:59:50 +00:00
gram c7f62bae9b make GO copyright verbatim 2026-01-02 20:40:17 +00:00
gram 1b741bf2b7 Update copyright year in LICENSE 2026-01-02 20:40:17 +00:00
Nia Waldvogel 266d70816f compiler/runtime: add element layout to sliceAppend
Attach the memory layout of the element type to newly created slices via append when using the precise collector.
2025-12-26 21:27:24 -05:00
Damian Gryski 27cedf09e8 testdata: more corpus repos 2025-12-22 13:15:06 +00:00
deadprogram db9f1182f5 Release 0.40.1
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-18 21:59:18 +00:00
MakKi (makki_d) f7fd5cf2d8 runtime: use rand_hwrng hardwareRand for RP2040/RP2350 (#5135)
* runtime: use rand_hwrng hardwareRand for RP2040/RP2350

* add a comment

* insert 'implementations'
2025-12-18 20:52:12 +01:00
deadprogram 8d9c9a16e3 picolibc: use updated location for git repo
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-18 18:20:22 +00:00
deadprogram 82deccac40 runtime: call initRand() before initHeap() during initialization.
The reason is that allocating without a heap is usually more visible
than using an uninitialized random generator which is subtle and may
lead to security vulnerabilities.

Based on suggestion from @eliasnaur

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-18 16:42:46 +00:00
deadprogram 0e540dc3da runtime: add calls to initRand() during run() for all schedulers.
Intended to ensure that the random number seed used is always initialized on startup.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-18 16:42:46 +00:00
MakKi (makki_d) 897f2bc5fd runtime: avoid fixed math/rand sequence on RP2040/RP2350 (#5124)
* runtime: avoid fixed math/rand sequence on RP2040/RP2350

* fix typo
2025-12-17 20:05:43 +01:00
Ayke van Laethem 750b0e5c18 nrf: fix flash writes when SoftDevice is enabled
The data to write is specififed in words, not in bytes.

This fix is needed for correctness. Without it, a
`machine.Flash.WriteAt` call can result in **data loss** since it will
overwrite more data than it should.
2025-12-17 14:58:40 +00:00
deadprogram a65ac105cc build: remove post-build triggers for associated repos like drivers.
Now that the drivers repo and friends are intended to target the most
recent release version of TinyGo instead of the development version,
this trigger does not serve any puppose to trigger a build that will have
already been tested.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-17 13:34:19 +00:00
deadprogram 6970c80d34 Release 0.40.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-15 09:33:17 +00:00
Damian Gryski dfbc42f833 testdata: some more packages for the test corpus
ahocorasick no longer times out during interp.  I don't believe
this is because we fixed a bug in interp but rather because "something"
(garbage collector fixes?) has moved the tree building to runtime.
2025-12-13 11:17:41 +00:00
Damian Gryski e356daa229 internal/task: prevent semaphore resource leak for threads scheduler 2025-12-13 00:40:54 -08:00
deadprogram 1c27ecb66f build: go back to normal scheduler instead of tasks scheduler for macos CI
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-12 07:58:30 +00:00
Nia Waldvogel d01d0bb358 runtime (gc_blocks.go): make sweep branchless
Instead of looping over each block, we can use bit hacks to operate on an entire state byte.
I deinterleaved the state bits in order to enable these tricks.

Sweep used to count free/freed allocations/blocks.
I managed to move/remove all of these counters:
- The free space is now calculated in buildFreeRanges by adding the range lengths.
- ReadMemStats counts freed objects by subtracting live objects from allocated objects.
- gcFreedBlocks was never necessary because MemStats.HeapAlloc is the same as MemStats.HeapInUse.
2025-12-10 18:39:50 -05:00
Nia Waldvogel 8fcf3658b1 runtime (gc_blocks.go): use best-fit allocation
The allocator originally just looped through the blocks until it found a sufficiently-long range.
This is simple, but it fragments very easily and can degrade to a full heap scan for long requests.

Instead, we now maintain a sorted nested list of free ranges by size.
The allocator will select the shortest sufficient-length range, generally reducing fragmentation.
This data structure can find a range in time directly proportional to the requested length.
2025-12-10 06:58:07 +00:00
Nia Waldvogel b37535bcfb transform (gc): create stack slots in callers of external functions
This updates the stack slot pass to include callers of external functions which may access non-argument memory.
Wiithout this change, a use-after-free could occur on WASM when calling a reentrant function or switching to another goroutine.
2025-12-09 21:12:55 +00:00
Nia Waldvogel ee0a10ec51 runtime (wasm): scan the system stack
This saves the system stack pointer into a global and uses it to scan.
It should fix some use-after-free issues?
2025-12-08 17:27:20 +00:00
Michael Smith 39e0333663 fix: don't hardcode success return state
fix: validate bmRequestType\nfix: mimic tinyusb behavior in mscStateNeedReset\nfix: CLEAR_FEATURE requests to iface addr are against spec
2025-12-06 06:34:32 +00:00
Michael Smith 3afd2c3597 fix: update m.queuedBytes when clamping output to avoid corrupting sentBytes 2025-12-06 06:34:32 +00:00
Michael Smith bd19f3baa3 fix: separate host expected bytes from device intended bytes 2025-12-06 06:34:32 +00:00
Ayke van Laethem 908fa84d49 runtime: remove copied code for nrf52840
We really shouldn't have copies around like that, instead the code
should be factored out.
In this case, it's a single import that's needed.
2025-12-05 20:50:06 +00:00
Ayke van Laethem 571f34e0e6 machine: only enable USB-CDC when needed
Enabling USB consumes a lot of power. So it's better to keep it disabled
by default when the serial port is set to something other than "usb"
(USB-CDC).

On my nice!nano clone, it reduces current consumption from 1mA to
0.13mA (and most of the rest is probably consumed by a connected SCD41
sensor).

This change might break some expectations, when a board uses USB but not
USB-CDC (I think this is rare, but I didn't check specifically).
2025-12-05 20:50:06 +00:00
Nia Waldvogel 01723025d7 runtime (avr): fix infinite longjmp loop if stack is aligned to 256 bytes
The setjmp code sets r24 to 0 and checks if it is still 0 when it finishes.
This usually works because tinygo_longjmp stores the lower half of the stack pointer into r24.
However, r24 will be set to 0 if the stack is aligned to 256 bytes when entering the setjmp.

Manually set r24 to 1 in tinygo_longjmp to fix this issue.
2025-12-05 13:22:37 -05:00
deadprogram 505b5ee565 build: update CI to use Go 1.25.5
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-05 11:49:11 +00:00
Nia Waldvogel 26ac03a3f6 runtime (gc_blocks.go): simplify scanning logic
Loop over valid pointer locations in heap objects instead of checking if each location is valid.
The conservative scanning code is now shared between markRoots and the heap scan.

This also removes the ending alignment requirement from markRoots, since the new scan* functions do not require an aligned length.
This requirement was occasionally violated by the linux global marking code.

This saves some code space and has negligible impact on performance.
2025-12-05 10:16:38 +00:00
Nia Waldvogel c9aa88b8ef runtime (gc_blocks.go): use a linked stack to scan marked objects
The blocks GC originally used a fixed-size stack to hold objects to scan.
When this stack overflowed, the GC would fully rescan all marked objects.
This could cause the GC to degrade to O(n^2) when scanning large linked data structures.

Instead of using a fixed-size stack, we now add a pointer field to the start of each object.
This pointer field is used to implement an unbounded linked stack.
This also consolidates the heap object scanning into one place, which simplifies the process.

This comes at the cost of introducing a pointer field to the start of the object, plus the cost of aligning the result.
This translates to:
- 16 bytes of overhead on x86/arm64 with the conservative collector
- 0 bytes of overhead on x86/arm64 with the precise collector (the layout field cost gets aligned up to 16 bytes anyway)
- 8 bytes of overhead on other 64-bit systems
- 4 bytes of overhead on 32-bit systems
- 2 bytes of overhead on AVR
2025-12-05 10:16:38 +00:00
Ayke van Laethem 887ac286ff nrf: add ADC_VDDH which is an ADC pin for VDDH
VDDH is typically connected to USB (~5V) or directly to a battery.
Measuring the voltage on VDDH is a way to measure the current battery
voltage, which in turn can be useful to determine how much power is left
in the battery.

I've special cased the nrf52840 in a somewhat unusual way, maybe there
is a better way, feel free to comment!
2025-12-04 18:48:22 +00:00
Ayke van Laethem b1083d78c7 nrf: rename pwmPin to adcPin
Just a small refactor, to fix the naming.
2025-12-04 18:48:22 +00:00
Ayke van Laethem 7863bdb484 nrf: don't set PSELN, it's ignored in single ended mode anyway
We use single ended mode, so according to the datasheet PSELN is
ignored.
2025-12-04 18:48:22 +00:00
Ayke van Laethem 5c3363bf25 nrf: fix typo in ADC configuration
This shouldn't change behavior (both values are 0), but it's a bit more
correct.
2025-12-04 18:48:22 +00:00
Nia Waldvogel 9404bb8712 internal/task (threads): save stack bounds instead of scanning under a lock
In order to scan stacks, the GC preempts all other threads and has them scan their own stack.
This is somewhat expensive since all of these threads have to fight over a single lock.
Instead, save the stack bounds and let the GC thread perform the scan.

This also fixes a few other bugs I ran into:
1. The GC starts scanning before the world stops. This can cause it to miss some objects (and mistakenly free them) if memory is modified while stopping.
2. The GC does not wait for threads to resume. This can cause notifications to be misinterpreted due to signal nesting if the GC is re-run before all threads wake.
2025-12-04 11:22:46 +00:00
Nia Waldvogel 20e22d4507 runtime (gc_blocks.go): clear full size of allocation
This fixes a bug where runtime.alloc would not clear the padding from rounding the allocation up to a multiple of the block size.
The GC still has to scan this, and this could result in permanent false positives (and therefore memory leaks).

It also handles overflow in the size calculation.
2025-11-30 08:46:20 +00:00
deadprogram 9118e91b72 target: add xiao-esp32s3 board target
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-11-24 14:30:50 +00:00
cjpeterson fbbaa5e580 Add ESP32-S3 support (#5091)
* feat: add initial support for ESP32-S3 (#3442)

* feat: add initial support for esp32-s3

* esp32s3: fix merge errors

* esp32s3: Fix Watchdog registers bad names

* esp32s3: fix linker relocation errors and support for ESP binary

* esp32s3: fix memory section overlap

* esp32s3: correct clock frequencies

* esp32s3: more stable cpu

* esp32s3: enable basic gpio support

* esp32s3: simplify loading and check extensions

* esp32s3: synchronize cpu features with clang

* esp32s3: correct iram origin

---------

Co-authored-by: Denys Vitali <denys@denv.it>
Co-authored-by: Olivier Fauchon <ofauchon2204@gmail.com>
2025-11-24 12:11:47 +01:00
deadprogram 97a2cb4a37 build: update macOS GH actions builds to handle sunset of macOS 13
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-11-19 16:15:12 +01:00
Ayke van Laethem 6dee98c3a1 nrf: support flash operations while the SoftDevice is enabled
Flash operations must go through the SoftDevice if it is enabled,
otherwise they will crash the chip. (And even then the SoftDevice
doesn't guarantee they'll succeed, depending on advertisement frequency
etc). But this patch makes sure to call the appropriate APIs so that
flash is usable while using Bluetooth.
2025-11-16 19:25:14 +01:00
Ayke van Laethem 484ccf830a nrf: refactor SoftDevice enabled check
Share the code with the runtime.
2025-11-16 19:25:14 +01:00
Ayke van Laethem 5690204224 nrf: don't block SPI transfer
Ideally we'd use something like
https://github.com/tinygo-org/tinygo/pull/5016 but that's a bit more
involved. As a quick improvement, call gosched() instead.

Example where I use this: a custom WS2812 driver that uses SPI to
transfer the data. It's useful to be able to switch back to the main
goroutine during the transfer to render the next LED update.
2025-11-16 11:24:32 +01:00
Ayke van Laethem b203314c2f machine: make sure DMA buffers do not escape unnecessarily
Writing the pointer of a buffer to memory-mapped I/O will normally cause
it to escape, which forces the compiler to heap-allocate the buffer. But
we do know how long the value stays alive, so we can tell the compiler
to keep it alive exactly until it is not needed anymore - and tell it to
not treat the pointer-to-uintptr cast as escaping.
2025-11-12 18:07:53 +01:00
Ayke van Laethem 5ae8fd1f6f machine: clarify WriteAt semantics of BlockDevice
Flash can generally only be written after it has been erased, which
wasn't spelled out explicitly in the interface.
2025-11-12 11:24:49 +01:00
Nia Waldvogel c80da7eb0d runtime (gc_boehm.go): fix world already stopped check
This fixes the "gc: world already stopped" check by moving it before gcMarkReachable.
Previously the check did not work because gcMarkReachable would hang while waiting for activeTaskLock.
2025-11-10 11:19:31 +01:00
Nia Waldvogel 326ca4202e internal/task: create detatched threads and fix error handling
Previously, we created joinable threads.
As a result, all threads would be kept alive after completion so that pthread_join could wait for them.
We never call pthread_join, so threads would never get cleaned up.

Additionally, the thread creation error check was performed after waiting for the thread to start.
If pthread_create failed, the caller would get stuck waiting for a thread that was never created.
2025-11-10 10:07:19 +01:00
Nia Waldvogel 852dde671e compiler: use Tarjan's SCC algorithm to detect loops for defer
The compiler needs to know whether a defer is in a loop to determine whether to allocate stack or heap memory.
Previously, this performed a DFS of the CFG every time a defer was found.
This resulted in time complexity jointly proportional to the number of defers and the number of blocks in the function.

Now, the compiler will instead use Tarjan's strongly connected components algorithm to find cycles in linear time.
The search is performed lazily, so this has minimal performance impact on functions without defers.

In order to implement Tarjan's SCC algorithm, additional state needed to be attached to the blocks.
I chose to merge all of the per-block state into a single slice to simplify memory management.
2025-11-10 07:27:40 +01:00
deadprogram bf613175e7 ci: update GH actions builds to use Go 1.25.4
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-11-07 10:04:33 +01:00
sivchari 4d62cca267 feature: Add flag to ignore go compatibility matrix (#5078)
* add go-compatibility feature

Signed-off-by: sivchari <shibuuuu5@gmail.com>

* rename env

Signed-off-by: sivchari <shibuuuu5@gmail.com>

* fix description

Signed-off-by: sivchari <shibuuuu5@gmail.com>

---------

Signed-off-by: sivchari <shibuuuu5@gmail.com>
2025-11-06 21:28:06 +01:00
deadprogram a63aa143bb build: use task scheduler on macOS builds to avoid test race condition lockups.
Fixes #4995

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-11-06 19:19:20 +01:00
Ben Krieger ed0fb774b9 Add testing.T.Context() and testing.B.Context() 2025-11-05 10:33:15 +01:00
Damian Gryski 9be956f4bc src/syscall: update src buffer after write
Fixes #5012
2025-10-28 12:43:23 -07:00
Ayke van Laethem 48d741f68a compiler: emit an error when the actual arch doesn't match GOARCH
See: https://github.com/tinygo-org/tinygo/issues/4959
This makes sure to emit an error instead of generating inline assembly,
which leads to hard-to-debug linker errors. Instead, it will now produce
errors like the following:

    # golang.org/x/sys/unix
    ../../../../pkg/mod/golang.org/x/sys@v0.0.0-20220722155257-8c9f86f7a55f/unix/affinity_linux.go:20:23: system calls are not supported: target emulates a linux/arm system on wasm32
    ../../../../pkg/mod/golang.org/x/sys@v0.0.0-20220722155257-8c9f86f7a55f/unix/fcntl.go:17:29: system calls are not supported: target emulates a linux/arm system on wasm32
    ../../../../pkg/mod/golang.org/x/sys@v0.0.0-20220722155257-8c9f86f7a55f/unix/fcntl.go:32:24: system calls are not supported: target emulates a linux/arm system on wasm32
2025-10-23 10:13:29 +02:00
Elias Naur 35e6b8a67d compileopts: lower "large stack" limit to 16kb
I'm running with `-stack-size 16kb` and running into excessive allocations
because the otherwise non-escaping buffer in

https://github.com/golang/go/blob/c53cb642deea152e28281133bc0053f5ec0ce358/src/crypto/internal/fips140/sha512/sha512block.go#L97

is moved to the heap.
2025-10-22 13:04:24 +02:00
Ayke van Laethem 8a31ae14a0 machine: use larger SPI MAXCNT on nrf52833 and nrf52840
These chips have a larger upper limit for the DMA transfer than the
nrf52832. For best performance, we should be splitting the transfer in
as large blocks as possible on the given hardware.
2025-10-21 13:33:13 +02:00
Ayke van Laethem 26ee3fb93e wasm: fix C realloc and optimize it a bit
- Do not use make([]byte, ...) to allocate, instead call the allocator
    directly with a nil (undefined) layout. This makes sure the precise
    GC will scan the contents of the allocation, since C could very well
    put pointers in there.
  - Simplify the map to use the pointer as the key and the size as the
    value, instead of storing the slices directly in the map.
2025-10-20 12:58:11 +02:00
Ayke van Laethem 8872d5ebfc runtime: fix sleep duration for long sleeps
Long sleep durations weren't implemented correctly, by simply casting to
a smaller number of bits. What we want is a saturating downcast, which
is what I've used here instead.

This should fix https://github.com/tinygo-org/tinygo/issues/3356.
2025-10-16 17:20:50 +02:00
deadprogram faf455283d chore: update all CI builds to use latest stable Go release. Also update some of the actions to their latest releases.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-10-16 09:04:00 +02:00
tinkerator 562b4c5020 Create "pico2-ice" target board (#5062)
* Create "pico2-ice" target board

This board has an rp2350b chip on it, as well as a Lattice Semiconductor
iCE40UP5K FPGA. More details of this open hardware board here:

  https://pico2-ice.tinyvision.ai/

Tested on a pico2-ice board with:

  ~/go/bin/tinygo flash -target=pico2-ice src/examples/blinky1/blinky1.go

which blinks the GREEN LED (connected to GPIO0) on this board.

Signed-off-by: Tinkerer <tinkerer@zappem.net>

* More silkscreen labels for pico2-ice board

Reading the schematic and the rev2 board viewer:

https://raw.githubusercontent.com/tinyvision-ai-inc/pico2-ice/refs/heads/main/Board/Rev2/bom/ibom.html

concluded that the RP2350B GPIO pins are not labeled with these
pin numbers in the silkscreen. Instead, the silkscreen refers to
uses of the GPIOs or the ICE numbered pins. RP2340B devoted pins
map to the A1..4 B1..4 pins on the RP PMOD connector. Also
silkscreen "~0".."~6" are labeled as pins N0..N6.

Signed-off-by: Tinkerer <tinkerer@zappem.net>

* Added a smoketest for pico2-ice board and tidied up GPIO defs

Addresses review comments from aykevl.

Signed-off-by: Tinkerer <tinkerer@zappem.net>
2025-10-13 13:19:04 +02:00
Ayke van Laethem 7460734522 compiler: mark string parameters as readonly
Strings are readonly, but the compiler doesn't always know this. Marking
them as readonly in the frontend allows the compiler to optimize based
on this knowledge.

This provides some small code size benefits. I didn't measure running
speed.
2025-10-11 23:03:18 +02:00
Olaf Flebbe 3be7100e89 fix(rp2): possible integer overflow while computing factors for SPI baudrate
Incorrect factors are calculated for baudrates which are a bit
larger than integer multiples of 4194304.
For example for baudrates of 8_400_000 or 58_800_000.
Fixed the same way in newer versions of the RPI SDK.
2025-10-09 11:48:27 +02:00
张之阳 aedaf7d925 machine: fix deprecated AsmFull comment (#5005) 2025-10-04 11:19:01 +02:00
Ayke van Laethem 256e84f912 all: shrink bdwgc library
By disabling some configuration options (and updating the library), the
library becomes a lot smaller. With `-no-debug`, binaries become ~18kB
smaller on Linux, ~4kB smaller on MacOS, ~9kB smaller on Windows, and
~12kB smaller on WebAssembly.
2025-10-03 15:46:25 +02:00
Ayke van Laethem 0a87846bd8 cortexm: optimize code size for the HardFault_Handler
This function is called when a hard fault occurs. Hard faults happen
when something really bad happens - like writing to unwritable memory or
an unaligned memory access on Cortex-M0. It is not generally possible to
recover from these.

This commit optimizes the code size overhead of hard fault handling:

  * It removes the stack overflow checking code.
    This may seem like a bad thing, but the only thing this could check
    were stack overflows outside goroutines. In practice, this could
    only really happen on a stack overflow in the scheduler (unlikely),
    or in interrupt code (possible, but interrupts are small so still
    unlikely). Most stack overflows happen in regular goroutines, and
    weren't caught in the HardFault.
  * It makes the panic message similar to a regular panic. This has two
    advantages:
      * It reduces code size, because the string can be reused between
        the HardFault handler and the runtime panic function.
      * Using the same pattern automatically makes `-monitor` print the
        source address for the hard fault. Not a big benefit as we could
        trivially add any other pattern but a nice benefit nonetheless.

Result:

    $ tinygo flash -target=microbit -size=short -programmer=openocd -monitor examples/serial
       code    data     bss |   flash     ram
       3036       8    2256 |    3044    2264
    [...snip]
    Connected to /dev/ttyACM0. Press Ctrl-C to exit.
    panic: runtime error at 0x00000344: HardFault with sp=0x200007d0
    [tinygo: panic at /home/ayke/src/tinygo/tinygo/src/internal/task/task_stack_cortexm.go:48:4]

(This is with https://github.com/tinygo-org/tinygo/pull/3680 not yet
fixed and some local changes to configure the UART so I can actually see
the panic).

For atsamd21/nrf51 chips this results in a binary size reduction of
around 100 bytes. For other Cortex-M chips it's around 24 bytes but I
hope to change this in the future because a lot of the fault decoding in
runtime_cortexm_hardfault_debug.go should IMHO be done by the TinyGo
monitor instead (I estimate that this would save around 800 bytes on
these chips).
2025-10-03 13:20:18 +02:00
Ayke van Laethem 073862ee92 fe310: add I2C pins for the HiFive1b 2025-10-02 20:26:25 +02:00
Ayke van Laethem c820d83ae2 interp: better errors when debugging interp
When debugging is enabled for interp, print better errors in a specific
case.

Before:

    !! revert because of error: interp: unsupported instruction (to be emitted at runtime)

After:

    !! revert because of error: /usr/local/go1.24.0/src/regexp/syntax/parse.go:927:27: interp: unsupported instruction (to be emitted at runtime)

So this adds error location information, which can be quite useful.
2025-10-02 05:42:08 +02:00
Daniel Esteban 109e0767e7 Added Gopher ARCADE board 2025-09-20 08:35:34 +02:00
Ben Krieger 821f2aeae0 Fix linker errors for runtime.vgetrandom and crypto/internal/sysrand.fatal 2025-09-19 15:32:01 +02:00
Piotr Bocheński 93a11e266f net: update submodule to latest commits
Signed-off-by: Piotr Bocheński <piotr@bochen.ski>
2025-09-19 13:14:12 +02:00
rdon e29e583919 fix: use int64 in ReadTemperature to avoid overflow 2025-09-19 11:30:50 +02:00
Michael Smith 310df7acb5 fix(rp2): reset spinlocks at startup 2025-09-19 02:22:36 -04:00
Michael Smith b9387febe0 fix(rp2): use side-effect-free spinlocks 2025-09-19 02:22:36 -04:00
Michael Smith 3dce224183 fix(rp2): switch spinlock busy loop to wfe 2025-09-19 02:22:36 -04:00
Michael Smith 5d8afa25b8 fix(rp2): disable DBGPAUSE on startup 2025-09-19 02:22:36 -04:00
deadprogram 0dca016749 fix: remove macOS 15 from CI build matrix. It was already being tested by
the test-macos-homebrew job, and it conflicts with the actual build
that we want, which is macOS 14 for backwards-compatibility.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-09-19 01:01:58 -04:00
deadprogram 28a911bcce fix: point the submodule for musl-lib to a mirror in the TinyGo
GitHub org. The git server for git.musl-libc.org is having troubles,
and it also seems like a safer bet to have our own mirror just in case.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-09-19 01:01:58 -04:00
deadprogram eb36120c6e ci: several improvements to the macOS GH actions build including:
- do not install cmake, instead use the version already installed
- add macOS 15 to the CI builds
- update a could of GH actions to latest release
- update Go version being use to Go 1.25.1

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-09-18 08:24:29 -04:00
Ayke van Laethem dfbb133ea6 all: add full LLVM 20 support
This switches the Espressif fork from LLVM 19 to LLVM 20, so we can use
the improvements made between those LLVM versions. It also better aligns
with the system-LLVM build method, which currently also defaults to LLVM
20.

Note that this disables the machine outliner for RISC-V. It appears
there's a bug in there somewhere, with the machine outliner enabled the
crypto/elliptic package tests fail with -target=riscv-qemu.
This should ideally be investigated and reported upstream.
2025-09-17 05:28:06 -04:00
deadprogram 79ab77facd fix: correct linter issues exposed by the fix in #4679
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-09-14 08:03:25 -04:00
Matt Mets 2c99692019 Move the directory list into a variable 2025-09-14 08:03:25 -04:00
Matt Mets 0ca0aad7ce Fix for #4678: top-level 'make lint' wasn't working
The revive command seems to have had a syntax error in the file
input glob. It appears to have been broken in a way that did not
result in a return code being set. This change uses 'find' to
build the input to the linter.

Note that it is expected to fail the CI script, because it is
uncovering some existing lint issues that were not being caught.
2025-09-14 08:03:25 -04:00
Michael Smith 3ddba4e620 fix: add TryLock to sync.RWMutex 2025-09-13 11:04:07 -04:00
luoliwoshang eab3dceb48 fix/typo:makeESPFirmwareImage 2025-09-12 14:04:22 -04:00
Michael Smith 663a94fcab feat: enable //go:linkname pragma for globals 2025-09-12 12:53:34 -04:00
Michael Smith 373ca88f14 fix: expand RTT debugger compatibility 2025-09-12 12:53:34 -04:00
Ayke van Laethem be33de1086 GNUmakefile: shrink TinyGo binaries on Linux
With these flags, the TinyGo binary gets 18.8MB (11.6%) smaller. That
seems like a quite useful win for such a small change!

This is only for Linux for now. MacOS and Windows can be tested later,
the flags for those probably need to be modified.

Originally inspired by:
https://discourse.llvm.org/t/state-of-the-art-for-reducing-executable-size-with-heavily-optimized-program/87952/18
There are some other flags like -Wl,--pack-dyn-relocs=relr that did not
shrink binary size in my testing, so I've left them out.

This also switches the linker to prefer mold or lld over the default
linker, since the system linker is usually ld.bfd which is very slow.
(Also, for some reason mold produces smaller binaries than lld).
2025-09-12 05:29:08 -04:00
deadprogram e5bdfb0962 fix: increase the timeout for chromedp to connect to the headless browser used for running
the wasm tests.

Also add favicon link to avoid extra fetches during test runs.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-09-11 08:34:38 -04:00
deadprogram e80c6c48a1 chore: create separate go.mod file for testing dependencies for wasm tests
that use Chromium headless browser to avoid use of older incompatible version.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-09-11 08:34:38 -04:00
Thomas Kohler 381644a0c0 machine,nrf528: stop the bus only once on I2C bus error and ensures the first error is returned
In some cases, e.g nothing connected on the bus, repeated resume-stop sequences can lead to the bus never reaching the stop state, hanging Tx.
This change ensures the resume-stop sequence is submitted once on error. It also moves the error code read to before the sequence to ensure it's valid.

Fixes: #4998
2025-09-09 12:49:29 -04:00
luoliwoshang 13bb59c334 ci:uninstall cmake before install 2025-09-09 06:39:26 -04:00
Ayke van Laethem 632e5f9872 rp2040: allow writing to the UART inside interrupts
Writing to the UART takes time and that may not be a good idea inside an
interrupt, but it is essential for debugging sometimes (especially since
USB-CDC typically doesn't work inside an interrupt).

This fixes UART support in interrupts for the RP2040 at least. You can
test it with `-serial=uart` and connecting a USB-UART adapter to the
right pins.
2025-09-03 10:50:10 -04:00
sago35 37531930b8 chore: update version for 0.40 development cycle 2025-09-02 07:14:45 -04:00
deadprogram 3869f76887 all: prepare for release 0.39.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-19 14:03:57 +02:00
deadprogram 3e76703ff5 targets: set default stanck size to 8k for rp2040/rp2350 based boards where this was not already the case.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-18 12:23:51 +02:00
deadprogram deb48dd5f0 fix: update version of clang to 17 to accomodate latest Go 1.25 docker base image
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-14 15:01:07 +02:00
deadprogram 73fa5cd7bc fix: disable test-newest since CircleCI seems unable to download due to rate-limits on Dockerhub
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-14 11:30:27 +02:00
deadprogram 1be7582106 chore: update all CI builds to test Go 1.25 release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-14 11:30:27 +02:00
deadprogram d5b7cdbca3 net: update to latest tinygo net package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-12 18:34:27 +02:00
Michael Smith c769262398 feat: add metro-rp2350 board definition (#4989)
* feat: add metro-rp2350 board definition
* chore: add smoke test
2025-08-10 09:32:16 +02:00
Michael Smith 64caab1ade feat: enable multi-core scheduler for rp2350 2025-08-08 13:09:38 +02:00
Elias Naur 78914382c3 runtime: ensure time.Sleep(d) sleeps at least d
Account for the sleep queue base time in the computation of the wakeup
time.

Tested with the following program on pico2.

  func main() {
  	go func() {
  		for i := range 60 {
  			const delay = 20 * time.Millisecond
  			before := time.Now()
  			time.Sleep(delay)
  			if d := time.Since(before); true || d < delay {
  				log.Println(i, "actual", d, "delay", delay)
  			}
  		}
  	}()
  	time.Sleep(500 * time.Millisecond)
  	log.Println("******** done sleeping ********")
  	select {}
  }

Without this change, the program would print lines such as:

  17 actual 15.494ms delay 20ms
  18 actual 15.49ms delay 20ms
  19 actual 15.585ms delay 20ms
  20 actual 15.493ms delay 20ms
  21 actual 15.494ms delay 20ms
  22 actual 15.487ms delay 20ms
  23 actual 15.498ms delay 20ms
  ******** done sleeping ********
  24 actual 15.548ms delay 20ms
  25 actual 20.011ms delay 20ms
  26 actual 20.01ms delay 20ms
  27 actual 20.011ms delay 20ms
  28 actual 20.015ms delay 20ms

Note that while more than one sleeping goroutine is in the timer queue,
the sleep duration is 5ms short.
2025-08-08 01:32:42 +02:00
あーるどん 15e37ff927 flash: add -o flag support to save built binary (Fixes #4937) (#4942)
* Add -o flag support to flash command. Fixes #4937
* Remove empty outpath check in validateOutputFormat
---------
Co-authored-by: rdon <you@example.com>
2025-08-07 23:38:15 +02:00
deadprogram 91234060be chore: update all CI builds to test Go 1.25rc3
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-07 12:31:23 +02:00
Ayke van Laethem b33b6ce293 all: add Go 1.25 support 2025-08-06 17:08:26 +02:00
Ayke van Laethem 8911abbc6d runtime: stub out weak pointer support 2025-08-06 17:08:26 +02:00
Ayke van Laethem bc77922d47 testing: stub out testing.B.Loop
This gets the path package tests to pass, so we can move ahead with Go
1.25. It should be implemented in the future at some point (that, or
we'll use the upstream testing package instead).
2025-08-06 17:08:26 +02:00
Ayke van Laethem 34efc3a381 compiler: implement internal/abi.Escape 2025-08-06 17:08:26 +02:00
Ayke van Laethem 5c46efb4d1 runtime: implement dummy AddCleanup 2025-08-06 17:08:26 +02:00
Ayke van Laethem d6343d9d5c sync: implement sync.Swap 2025-08-06 17:08:26 +02:00
Ayke van Laethem e368551919 reflect: implement Method.IsExported 2025-08-06 17:08:26 +02:00
Ayke van Laethem 8b4624a420 ci: rename some jobs to avoid churn on every Go/LLVM version bump 2025-08-06 17:08:26 +02:00
Ayke van Laethem 9fd1b7b1a8 ci: make the goroutines test less racy
Since we switched to OS threads for goroutines, the
testdata/goroutines.go test has been flaky. Not surprising, if threads
need to be scheduled within 1ms on a busy CI system. This PR makes the
test a bit less flaky (hopefully) by increasing the time to 100ms.
2025-08-06 16:13:04 +02:00
Ayke van Laethem a38829c692 internal/task: use -stack-size flag when starting a new thread
Found this bug while trying to use the upstream testing package instead
of our own. The io/fs package wasn't passing, because the test was run
in a separate goroutine (and therefore a separate thread, with its own
stack) instead of all in the same thread with our own stack
creation/switching implementation.
2025-08-05 20:32:59 +02:00
Ayke van Laethem 1dbc6c83c4 internal/task: add SA_RESTART flag to GC interrupts
This makes sure system calls like read don't return EINTR but instead
restart the call on an interrupt. This is by far the more sensible
option, the default POSIX behavior of returning EINTR is extremely
error-prone.

Found this bug while trying to use the upstream testing package instead
of our own.
2025-08-05 14:09:03 +02:00
Ayke van Laethem 9a6071920f main: show the compiler erro (if any) for tinygo test -c
This fixes the bug that if there was a compiler error, it was silently
ignored.
2025-08-05 07:33:26 +02:00
Dmitrii Sharshakov 0964176b4d chore: correct GOOS=js name in error messages for WASM 2025-08-03 11:47:57 +02:00
Elliott Sales de Andrade 1ffa9a4adf Makefile: Install missing dlmalloc files
Running tests against an installed TinyGoRoot fails with:
```
=== CONT  TestBuild/WebAssembly/gc.go-boehm
    main_test.go:441: wasi-libc: did not find any files for pattern builder.filePattern{glob:"dlmalloc/src/dlmalloc.c", exclude:[]string(nil)}
```
because these files are not there, and are required with Boehm GC.
2025-07-31 12:26:56 +02:00
Damian Gryski 8c5886060f internal/gclayout: make gclayout values constants
The previous versions calculated at init() prevented `interp` from running
in many cases, increasing compile times due to the increased need to revert
the partially interpreted results and also increasing binary runtime because
fewer optimizations had happened during interp.
2025-07-22 18:57:55 +02:00
Elliott Sales de Andrade 3bb092d4d4 Add flag to skip Renesas SVD builds
Much like the STMicro SVDs, Renesas SVDs are fully proprietary and not
under an FOSS license.
2025-07-20 12:15:38 +02:00
Ayke van Laethem 20e62dea6f stm32: add support for the STM32L031G6U6 2025-07-17 16:35:05 +02:00
Elias Naur 1b5d312c68 tests: de-flake goroutines test 2025-07-17 15:40:01 +02:00
diwamoto 435ddc5f2d machine: add international keys 2025-07-16 17:30:54 +02:00
张之阳 1de1f87c6b Docs: Clarify build verification step for macOS users 2025-07-12 09:23:08 +02:00
Ayke van Laethem 778164c98e machine: remove some unnecessary "// peripherals:" comments
These things are specified in the shared chip Go file, not in the board
pin aliases.
2025-07-11 10:28:38 +02:00
Ayke van Laethem b3da00ac1f machine: add I2C pin comments
Similar to PWM, I2C can only be used on some pins. To automatically
generate this information per board, we need to add extra comments that
can then be interpreted by doc-gen for the tinygo.org website.
2025-07-11 10:28:38 +02:00
Ayke van Laethem e454ad1b61 machine: standardize I2C errors with "i2c:" prefix
This probably looks better anyway.
2025-07-11 09:25:48 +02:00
Ayke van Laethem f845b469b1 machine: make I2C usable in the simulator
This fixes/improves a few issues with I2C support:

  * Validate I2C pins, so only pins that are supported by the hardware
    can be used (similar to how it's done with PWM).
  * Add address to Tx API (without it, the simulator can't really
    simulate I2C).
  * Add frequency when configuring. Not currently used, but might be
    useful in the future and adding it now avoids possibly breaking
    changes.

This is a breaking change, but since the simulator doesn't support I2C
yet that seems fine to me. (It does in my local changes, but those need
to be cleaned up before I can push them).
2025-07-11 09:25:48 +02:00
deadprogram 35adbffa54 fix: additional params to create chromium browser for testing
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-07-10 11:47:37 +02:00
emricks 01f3d3eb59 fix: add SPI and I2C to teensy 4.1 (#4943)
* fix: add SPI and I2C to teensy 4.1
* fix: add SPI3 variables back
* fix: formatting

Signed-off-by: Emrick Sorensen <emrickishere@gmail.com>
2025-07-04 08:46:48 +02:00
Ayke van Laethem 0e43146d32 darwin: add threading support and use it by default 2025-06-30 09:07:54 +02:00
Ayke van Laethem 93f40992c1 internal/task: a few small correctness fixes
This shouldn't affect Linux or MacOS, but it's good to have them fixed.
2025-06-30 09:07:54 +02:00
Elias Naur c6b47fe6a0 rp2: use the correct channel mask for rp2350 ADC; hold lock during read (#4938) 2025-06-29 15:20:58 -03:00
Elias Naur 536deaa00a rp2: disable digital input for analog inputs
This is what the Pico SDK does[0] and may fix #4936.

[0] https://github.com/raspberrypi/pico-sdk/blob/ee68c78d0afae2b69c03ae1a72bf5cc267a2d94c/src/rp2_common/hardware_adc/include/hardware/adc.h#L101
2025-06-28 09:34:06 +02:00
deadprogram df1d639deb chore: update version for 0.39 development cycle
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-06-21 19:13:13 +02:00
deadprogram 733a17d470 all: prepare for release 0.38.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-06-14 07:51:48 +02:00
micchie 330f8c7c50 Use diskutil on macOS to extract volume name and path for FAT mounts (#4928)
* Use diskutil on macOS to extract volume name and path for FAT mounts
* Improve FAT mount detection on macOS by parsing diskutil output into a map
* Simplify conditional check
2025-06-14 06:27:38 +02:00
Ayke van Laethem e238eb2242 rp2040: add multicore support 2025-06-13 16:19:10 +02:00
Ayke van Laethem 5625f68d51 runtime: don't lock the print output inside interrupts
This should avoid a deadlock when trying to print inside an interrupt,
if the interrupted code is also printing (and therefore has the print
lock taken).
2025-06-13 16:19:10 +02:00
Ayke van Laethem f7d8502572 runtime: don't try to interrupt other cores before they are started
The GC shouldn't try to interrupt other cores before they are started.
For example, it would be possible for the GC to run in a package
initializer (which is currently run on a single core). That would
suggest questionable program design, but it is something that should
work. So this commit makes sure the GC only tries to scan the stack of
other cores when those other cores have in fact started.
2025-06-13 16:19:10 +02:00
Ayke van Laethem e395c94e5f runtime: implement NumCPU for the multicore scheduler
I forgot to add it in the first PR, here:
https://github.com/tinygo-org/tinygo/pull/4851
2025-06-13 16:19:10 +02:00
Ayke van Laethem c08b2dfe06 wasm: add Boehm GC support
This adds support for `-gc=boehm` on `-target=wasip1` and `-target=wasm`
(in a browser or NodeJS). Notably it does *not* add Boehm GC support for
`-target=wasip2`, since that target doesn't have a real libc.
2025-06-13 14:42:23 +02:00
Ayke van Laethem 9c3b706533 main: add "cores" and "threads" schedulers to help text
This was missing before.
2025-06-13 11:33:05 +02:00
Ayke van Laethem 60f8a62978 all: add support for multicore scheduler
This commit adds support for a scheduler that runs a scheduler on all
available cores. It is meant to be used on baremetal systems with a
fixed number of cores, such as the RP2040.

The initial implementation adds support for multicore scheduling to the
riscv-qemu target as a convenient testing target. This means that this
new multicore scheduler is tested in CI, including a bunch of standard
library tests (`make tinygo-test-baremetal`). This should ensure the new
scheduler is reasonably well tested before trying to use it on
harder-to-debug targets like the RP2040.
2025-06-12 21:04:36 +02:00
Ayke van Laethem 0c7c2926f9 runtime: refactor obtaining the system stack
The system stack is only needed when we're not on it. So we can directly
call task.SystemStack() without problems.

This also saves a tiny bit of binary size.
2025-06-12 21:04:36 +02:00
Ayke van Laethem 3a2188fc7b runtime/interrupt: add Checkpoint type
This type can be used to jump back to a previous position in a program
from inside an interrupt. This is useful for baremetal systems that
implement wfi but not wfe, and therefore have no easy (race-free) way to
wait until a flag gets changed inside an interrupt. This is an issue on
RISC-V, where this is racy (the interrupt might happen after the check
but before the wfi instruction):

    configureInterrupt()
    for flag.Load() != 0 {
        riscv.Asm("wfi")
    }
2025-06-12 21:04:36 +02:00
Ayke van Laethem 64a30424da runtime: add exportedFuncPtr
This function isn't used yet, but will be used for the "cores" scheduler
for RISC-V.
2025-06-12 21:04:36 +02:00
Ayke van Laethem 525c41bac9 sync: fix TestMutexConcurrent test
Accessing the same variable from multiple goroutines is unsafe, and will
fail with parallelism. A lightweight way to avoid issues is by using
atomic variables.
2025-06-12 21:04:36 +02:00
Piotr Bocheński 83de8ff77d net: update submodule to latest commits
And fix module definition pointing to `dev` branch by default.

Signed-off-by: Piotr Bocheński <piotr@bochen.ski>
2025-06-12 14:02:44 +02:00
Ayke van Laethem 170a069132 riscv32: use gdb binary as a fallback
At least on my system (Fedora 42) the standard gdb binary is capable of
debugging riscv-qemu binaries (running inside qemu-system-riscv32).
2025-06-11 14:29:20 +02:00
Ömer Faruk IRMAK f19b288717 runtime/debug: add GC related stubs 2025-06-11 09:57:46 +02:00
Ömer Faruk IRMAK 7c54dbc61f metrics: flesh out some of the metric types 2025-06-11 09:57:46 +02:00
Ömer Faruk IRMAK 1976d9e3fe reflect: Chan related stubs 2025-06-11 09:57:46 +02:00
Michael Smith 87153e9a02 usb: add USB mass storage class support 2025-06-11 07:46:02 +02:00
Michael Smith ba274008d5 machine: implement usb receive message throttling 2025-06-11 07:46:02 +02:00
Michael Smith b059de857c machine: declare usb endpoints per-platform 2025-06-11 07:46:02 +02:00
deadprogram 1880e82da6 build: go back to using MinoruSekine/setup-scoop for Windows CI builds
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-06-10 19:08:00 +02:00
Ella Fox 888d957f60 machine: Add board support for BigTreeTech SKR Pico (#4842)
* machine: add support for BTT SKR Pico
Adds support for the BigTreeTech SKR Pico 3D-printer mainboard.
This board uses the RP2040.
* Fix build tag
* Add I2C defaults
* Run UART test instead of blinky1
* Use NoPin for I2C and SPI on BTT SKR Pico
* Cleanup comments
* Don't use ADC pin names
2025-06-01 18:15:57 +02:00
Mateusz Nowak f69c579005 machine/samd21: implement watchdog 2025-05-25 10:01:43 +02:00
Ayke van Laethem 2e043c7fbc compiler: add support for GODEBUG=gotypesalias=1
Since we now require Go 1.22, this became a lot simpler (since we can
now refer to `types.Alias` and `types.Unalias`).
2025-05-24 19:49:49 +02:00
Elias Naur 3f4f4e0ead flake.*: upgrade to nixpkgs 25.05, LLVM 20
Add a work around for #4819.
2025-05-24 13:56:01 +02:00
Mateusz Nowak 5c35bad590 machine/samd51: write to flash memory in 512 byte long chunks 2025-05-24 11:45:29 +02:00
Rolf Sommerhalder c1bff3dad0 Fix DMA to SPI transmits on RP2350 (#4903)
* Fix DMA to SPI transfers on RP2350

DMA DREQ line numbers for "flow control" between SPI bus and DMA channels on RP2350 differ from RP2040.
Tested with st7789 driver for Pico-1.14-LCD from Waveshare on Pico 2W. Without this fix func st7789.tx() blocks indefinitely while attempting to use DMA to SPI transfers.

* Add definitions for DMA DREQ "handshake" lines

Specific for RP2350, missing in generated src/device/rp/rp2350.go

* Add definitions for DMA DREQ "handshake" lines

Specific for RP2040, missing in generated src/device/rp/rp2040.go

* Complete table

* Complete table

* Remove redundant DMA_ prefix

* Correct name of Datasheet

* Correct name of Datasheet

* Refactor

Move global definitions to device/rp/

* Refactor

* Refactor

* Refacture

* Refacture

* Fix comments

* go fmt

* rename new non-generated files
2025-05-24 10:16:58 +02:00
Mateusz Nowak 3eeffca841 machine/samd21: write to flash memory in 64 byte long chunks 2025-05-23 11:29:53 +02:00
Ayke van Laethem cb5f7ad3fd interp: fix copy() from/to external buffers
This includes copying from a //go:embed slice for example. The slice
with data is not available at interp time.

See: https://github.com/tinygo-org/tinygo/issues/4895
2025-05-22 14:46:41 +02:00
Elias Naur 704489ed6d runtime: update comment
I forgot to change the comment in PR #4890.
2025-05-22 09:29:50 +02:00
Ayke van Laethem 9ca6f72583 wasm: refactor/modify stub signal handling
This commit changes signal handling in a few ways:

  * It stubs signals for all wasm targets (not just wasi) and baremetal,
    since none of those have traditional POSIX signals. And moves the
    code for that into a single file, instead of duplicating it.
  * It removes the stub for signal_ignored since the value `false` might
    be wrong in some cases and it doesn't usually seem to be called (it
    is not called in tsgo). Should be trivial to re-add if it is shown
    to be needed.
  * It adds a stub for `os/signal.signalWaitUntilIdle` which _is_ called
    by tsgo.
2025-05-21 20:04:05 +02:00
Ayke van Laethem 584098dfda machine: don't inline RTT WriteByte everywhere
With `-opt=2`, WriteByte gets inlined everywhere a println statement
exists. This blows up binary size for very little gain. In my case, the
binary size roughly doubled. Instead, don't inline it so that the binary
size remains somewhat reasonable. This might slow down WriteByte a tiny
bit, but likely not by any significant amount.
2025-05-21 16:49:21 +02:00
Elias Naur 180662f038 runtime: avoid an allocation in (*time.Timer).Reset 2025-05-16 12:36:14 +02:00
Elias Naur 2da9d26e21 machine/rp2: unexport machine-specific errors
Errors are part of API, and the exported rp2 errors seemed arbitrary.
For example, the very particular ErrRP2040I2CDisable was exported, but
errI2CWriteTimeout (which is defined on all platforms) is not.

While here, remove "RP2040" from an error name and make the messages
consistent and idiomatic.
2025-05-12 12:11:01 +02:00
Elias Naur d840971c42 machine: [rp2] discount scheduling delays in I2C timeouts (#4876)
The `gosched` call introduce arbitrary long delays in general, and in
TinyGo particular because the goroutine scheduler is cooperative and
doesn't preempt busy (e.g. compute-heavy) goroutines.

Before this change, the timeout logic would read, simplified:

  deadline := now() + timeout
  startTX()
	for !txDone() {
	   if now() > deadline { return timeoutError }
		 gosched() // (1)
	}
  startRx() // (2)
	for !rxDone() {
	   // (3)
	   if now() > deadline { return timeoutError }
		 gosched()
	}

What could happen in a busy system is:

- The gosched marked (1) would push now() to be > than deadline.
- startRx is called (2), but the call to rxDone immediately after would
report it not yet done.
- The check marked (3) would fail, even though only a miniscule amount
of time has passed between startRx and the check.

This change ensures that the timeout clock discounts time spent in
`gosched`. The logic now reads, around every call to `gosched`:

  deadline := now() + timeout
  startTX()
	for !txDone() {
	   if now() > deadline { return timeoutError }
		 before := now()
		 gosched()
		 deadline += now() - before
	}

I tested this change by simulating a busy goroutine:

	go func() {
		for {
      // Busy.
			before := time.Now()
			for time.Since(before) < 100*time.Millisecond {
			}
      // Sleep.
			time.Sleep(100 * time.Millisecond)
		}
	}()

and testing that I2C transfers would no longer time out.
2025-05-06 21:42:16 -03:00
Damian Gryski ba3b3e8938 runtime: stub runtime signal functions for os/signal on wasip1 2025-05-06 13:20:30 +02:00
Elias Naur c4ef38fbf9 go.*: upgrade golang.org/x/tools to v0.30.0, Go to 1.22
Fixes #4884 by upgrading the ssa package.

The fix is in v0.26.0, which also bumps the minimum Go to 1.22. The
latest x/tools module still depending on 1.22 is v0.30.0.
2025-05-06 10:05:20 +02:00
Ayke van Laethem edaf877056 machine: use pointer receiver in simulated PWM peripherals
I only discovered this issue after a while. All the baremetal PWM
implementations use pointers to a PWM instance, instead of the PWM
instance itself. For consistency (and because it's a better idea in
general), the simulated PWMs need to work the same.
2025-05-01 15:08:08 +02:00
Damian Gryski 45743406d0 os: handle relative and abs paths in Executable() 2025-05-01 10:53:49 +02:00
Damian Gryski 5428bfd270 runtime,os: add os.Executable() for Darwin 2025-05-01 10:53:49 +02:00
Ayke van Laethem 72b19555dc machine: add simulated PWM/timer peripherals
I will soon use these as part of the TinyGo tour, to explain the PWM
peripherals on common chips.
2025-04-30 19:44:34 +02:00
Ayke van Laethem 612a38e363 wasm: don't block //go:wasmexport because of running goroutines
This fixes bug https://github.com/tinygo-org/tinygo/issues/4874.
2025-04-29 14:19:17 +02:00
deadprogram 0c2ab895b7 targets: add target for Microbit v2 with SoftDevice S140 support for both peripheral and central
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-04-26 15:39:50 +00:00
deadprogram 8d5b7710cf fix: use OpenOCD flash method on microbit v2 when using Nordic Semi SoftDevice
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-04-26 15:39:39 +00:00
Ayke van Laethem de532643b1 main: add StartPos and EndPos to -json build output
This is useful for the TinyGo Playground: with this change it can show
the error span like in an IDE, instead of just the (single) error
position. Errors become a bit more readable as a result.
2025-04-24 09:24:52 +02:00
Ayke van Laethem c2765e9eca main: change -json flag to match upstream Go
This changes the -json flag for build/run/flash/gdb etc commands to not
print `compileopts.Config` but instead match upstream Go and print build
errors in a structured way.

For more details, see the proposal:
https://github.com/golang/go/issues/62067
2025-04-23 12:39:23 +02:00
Ayke van Laethem aa63f26f36 windows: use MSVCRT.DLL instead of UCRT on i386
This allows the binaries to run on Windows XP, without needing any extra
DLLs. Tested in an x86 Windows XP SP3 virtual machine.
2025-04-22 17:45:29 +02:00
Ayke van Laethem f542717992 windows: add windows/386 support 2025-04-22 17:45:29 +02:00
Ayke van Laethem 922ba6a4a3 all: add support for LLVM 20
This adds support for the 'go install' case, where we link against the
system LLVM (Homebrew, Linux distro version, etc). This does not have an
effect on `make`, which still uses LLVM 19 for now.

The main reason for doing this is because newer distros like Fedora 42
and Homebrew have switched to LLVM 20, so switching to this newer
version helps those people. (I'm one of those people, I'm on Fedora 42).

There are two small changes for WebAssembly included:

  * nontrapping-fptoint was enabled in the wasmbuiltin library used for
    wasm-unknown. This matches wasm-unknown which already had this
    enabled, so it doesn't add any new instructions.
  * bulk-memory was enabled in wasi-libc. This was previously enabled in
    all the other WebAssembly targets (wasm, wasip1, wasip2) so this
    does't add any new instructions. I think it was also enabled in the
    wasi-libc build before
    https://github.com/tinygo-org/tinygo/pull/4820 but I don't know for
    sure.
2025-04-22 16:31:19 +02:00
Michael Smith febb3906db machine/rp2: expose usb endpoint stall handling 2025-04-17 10:15:44 +02:00
Elias Naur 0f06f59c6e device/arm: clear pending interrupts before enabling them
Without this change, a pending interrupt would spuriously trigger
immediately after enabling. This happens if an interrupt is triggered
during flashing (e.g. by DMA), which survives the subsequent reset.

This behaviour matches e.g. `machine.irqSet` in machine_rp2_rp2350.go.

See 7f970a45, whose symptoms were likely caused by spurious interrupts.
2025-04-16 09:43:23 +02:00
Damian Gryski 0fdf08d14f add -nobounds (similar to -gcflags=-B) 2025-04-15 12:55:44 +02:00
Michael Smith 133e32c1c4 machine/rp2: merge common usb code (#4856)
* machine/rp2: merge common usb code
* chore: add note on noinline reason
2025-04-15 11:07:47 +02:00
Ayke van Laethem 41e501aaf4 runtime: move timeUnit to a single place
The timeUnit is now the same type everywhere. Move it to a single place
and add some documentation to it.
2025-04-15 09:23:23 +02:00
Ayke van Laethem abc373dc73 wasm: use int64 instead of float64 for the timeUnit
This makes wasm consistent with all the other targets, where timeUnit is
already int64.
2025-04-15 09:23:23 +02:00
Ayke van Laethem 02c5c4213c runtime: implement NumCPU for -scheduler=threads
For the threads scheduler, it makes sense to have NumCPU available.
For all other schedulers, the number of available CPUs is practically
limited to one by the scheduler (even though the system might have more
CPUs).
2025-04-13 14:59:19 +02:00
Ayke van Laethem 25b83920b6 ci: try to fix race condition in testdata/goroutines.go
A race condition was possible because the 'acquire' goroutine might not
have started in 4 milliseconds which changed the ordering of the test.

This patch fixes it by making sure the goroutine has started (and locked
the mutex) before continuing with the test.
2025-04-12 18:35:56 +02:00
Ayke van Laethem 3f8a110e4a runtime: move mainExited boolean
This variable is only necessary on the cooperative and none scheduler.
It is not used on the threads scheduler.

The reason for moving is that the upcoming multicore baremetal scheduler
also needs mainExited but of a different type: an atomic variable
instead of a plain boolean.
2025-04-12 13:34:57 +02:00
Ayke van Laethem 95ee572b4d internal/task: rename tinygo_pause to tinygo_task_exit
This is more descriptive: the call is to exit a task, not to pause it.
This also makes it more obvious that there's an optimization
opportunity: to free the stack explicitly after the goroutine returns
(or to keep it as a cache for the next stack allocation).
2025-04-12 13:34:57 +02:00
Ayke van Laethem 120d17c124 runtime: map every goroutine to a new OS thread
This is not a scheduler in the runtime, instead every goroutine is
mapped to a single OS thread - meaning 1:1 scheduling.

While this may not perform well (or at all) for large numbers of
threads, it greatly simplifies many things in the runtime. For example,
blocking syscalls can be called directly instead of having to use epoll
or similar. Also, we don't need to do anything special to call C code -
the default stack is all we need.
2025-04-11 15:18:41 +02:00
Ayke van Laethem 193f91b870 sync: implement RWMutex using futexes
Somewhat surprisingly, this results in smaller code than the old code
with the cooperative (tasks) scheduler. Probably because the new RWMutex
is also simpler.
2025-04-11 15:18:41 +02:00
Ayke van Laethem f9ed15f857 runtime: refactor timerQueue
Move common functions to scheduler.go. They will be used both from the
cooperative and from the threads scheduler.
2025-04-11 15:18:41 +02:00
Ayke van Laethem 870f00222d runtime: make conservative and precise GC MT-safe
Using a global lock may be slow, but it is certainly simple and safe.
If this global lock becomes a bottleneck, we can of course look into
making the GC truly support multithreading.
2025-04-11 15:18:41 +02:00
Ayke van Laethem 5b243f652c internal/task: implement atomic primitives for preemptive scheduling 2025-04-11 15:18:41 +02:00
Ayke van Laethem 789b5c6b78 compileopts: add library version to cached library path
This may be a bit of a weird place to put the library path, but
otherwise it's difficult to get the pathname for the Config.CFlags
function. So I've put it here.

This should help to avoid stale library caches. The idea is to increment
it every time something changes to a library that means it needs to be
recompiled. It's a manual process.
2025-04-10 11:59:46 +02:00
Ayke van Laethem dcf609defb riscv-qemu: actually sleep in time.Sleep()
Instead of just incrementing the timestamp, this causes the system to
actually sleep when calling time.Sleep. The direct effect is that this
works as expected:

    $ tinygo run -target=riscv-qemu examples/serial
    hello world!
    hello world!
    hello world!
    [..etc]

This commit also adds a bare bones handler for exceptions (such as
invalid memory writes), since we're adding an interrupt handler anyway.

While this patch doesn't add that much functionality, having interrupt
support is going to be needed for multicore support on riscv-qemu. My
plan is to first add this support to riscv-qemu (based on the earlier
work I did for the RP2040 and demoed at FOSDEM 2025) and once the basics
are in place and fully tested we can extend this support to the RP2040.
Writing for QEMU first makes it much easier to debug any issues that
will come up.
2025-04-05 08:57:26 +02:00
Ayke van Laethem 4bce85dc09 riscv: define CSR constants and use them where possible 2025-04-05 08:57:26 +02:00
Caleb Champlin 8a73502f11 reflect: Add SliceOf, ArrayOf, StructOf, MapOf, FuncOf 2025-04-04 15:23:07 +02:00
Ayke van Laethem 632357ffb9 builder: build wasi-libc inside TinyGo
Instead of relying on a build when TinyGo is being built, do it like all
other libraries when it is needed.

This brings a few benefits:

  * No more running `make wasi-libc` on the command line as a special
    case for WebAssembly. The generic `git submodule update --init` step
    is now enough for wasi-libc.
  * It becomes much easier to customize the build per system. For
    example: include/exclude malloc as needed, disable/enable bulk
    memory operations per target, etc.
2025-04-04 13:14:04 +02:00
Ayke van Laethem 4c54aa20da builder: simplify bdwgc libc dependency
Header files are built immediately, not in a separate job, so no
dependency is needed here. All we need is a flag whether to add flags
for that given libc.
2025-04-04 13:14:04 +02:00
Ayke van Laethem 8486e07377 builder: don't use precompiled libraries
We used these libraries in the past, but stopped doing so a while ago.
This is a small cleanup to remove support for these entirely.
2025-04-04 13:14:04 +02:00
deadprogram 91e20a53e0 fix: display all of the current GC options for the -gc flag
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-04-01 13:54:37 +02:00
Ayke van Laethem 9e42c8459e Makefile: only detect ccache command when needed 2025-04-01 10:10:11 +02:00
Ayke van Laethem 627743432a Makefile: create random filename inside rule
Don't create the temporary name in advance (which needs a subprocess
call), only do that when needed inside the report-stdlib-tests-pass
rule.
2025-04-01 10:10:11 +02:00
Ayke van Laethem 5d92d4dc98 Makefile: don't set GOROOT
This *should* not be needed. Using the right `go` binary should do the
job. It might break if users have explicitly set GOROOT (in which case
they probably need to fix that).

This avoids a subprocess call inside `make`.
2025-04-01 10:10:11 +02:00
Ayke van Laethem 682b3a9523 Makefile: call uname at most once
Instead of calling it 5 times, call it only once on Unix-like systems
(Linux, MacOS) and avoid it entirely on Windows.

The main benefit of this is that it avoids a ton of overhead doing
subprocess calls, which are very slow on Windows (~0.2s on my system).
2025-04-01 10:10:11 +02:00
Ayke van Laethem fdf075a7f9 Makefile: only read NodeJS version when it is needed
Most make commands don't need to check for the NodeJS version, so only
do it when needed.

This avoids the following error on Windows for example when NodeJS is
not installed:

    /usr/bin/sh: line 1: node: command not found
    which: no node in (/c/Users/Ayke/bin:/clangarm64/bin:/usr/local/bin:/usr/bin:/usr/bin:/mingw64/bin:/usr/bin:/c/Users/Ayke/bin:/c/Users/Ayke/.vscode-server/cli/servers/Stable-e54c774e0add60467559eb0d1e229c6452cf8447/server/bin/remote-cli:/c/WINDOWS/system32:/c/WINDOWS:/c/WINDOWS/System32/Wbem:/c/WINDOWS/System32/WindowsPowerShell/v1.0:/c/WINDOWS/System32/OpenSSH:/cmd:/c/Users/Ayke/scoop/apps/mingw-mstorsjo-llvm-ucrt/current/bin:/c/Users/Ayke/scoop/apps/python/current/Scripts:/c/Users/Ayke/scoop/apps/python/current:/c/Users/Ayke/go/bin:/c/Users/Ayke/scoop/shims:/c/Users/Ayke/AppData/Local/Microsoft/WindowsApps:/usr/bin/vendor_perl:/usr/bin/core_perl)

Also, some users have been confused by the error message even though in
most cases it is harmless and can be ignored.
2025-03-25 18:58:08 +01:00
Ayke van Laethem 95a7d065ca darwin: support Boehm GC (and use by default)
This mostly required some updates to macos-minimal-sdk to add the needed
header files and symbols.
2025-03-25 12:33:13 +01:00
deadprogram c1c074f170 version: update to 0.38.0-dev
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-24 09:25:32 +01:00
Ayke van Laethem 429c01556b cgo: *actually* fix build warnings on Windows ARM
See: https://github.com/tinygo-org/tinygo/pull/4628

This removes the comment, which actually fixes the issue. #4628 was
incorrect.
2025-03-23 08:13:57 +01:00
Ayke van Laethem ee76822fe6 arm64: remove unnecessary .section directive
This directive caused the code to be put in a non-executable area on
Windows which caused a segmentation fault. This patch fixes the issue by
removing `.section` directives, fixing windows/arm64 support.
2025-03-23 07:23:24 +01:00
Ayke van Laethem b9bf0aa0da windows: add support for the Boehm-Demers-Weiser GC
A few small changes were needed to make this work. In particular, I
found a critical bug (see the previous commit) that needed to be fixed
to make this work on Windows.
2025-03-22 19:40:56 +01:00
Ayke van Laethem fd3976b5bb windows: fix wrong register for first parameter
I'm surprised this worked as long as it did, since it looks like the
goroutine stack did not get scanned. Or maybe the RCX register contained
the stack pointer by accident. In any case, it now uses the correct
register (RCX instead of RDI on Windows) for passing the stack pointer
as the first parameter.
2025-03-22 19:40:56 +01:00
Ayke van Laethem 48f145c4df compileopts: enable support for GOARCH=wasm in tinygo test
Support for GOARCH=wasm was only available for `tinygo build`, not for
any of the other subcommands (`test`, `run`, etc). With this PR, these
subcommands should work again for supported values of GOOS.

This fixes the following error for example:

    $ make tinygo-test-wasip1
    GOOS=wasip1 GOARCH=wasm /home/ayke/bin/tinygo test cmp compress/lzw compress/zlib [...etc]
    cannot resolve packages: GOARCH=wasm but GOOS is unset. Please set GOOS to wasm, wasip1, or wasip2.
    make: *** [GNUmakefile:489: tinygo-test-wasip1] Error 1
2025-03-22 08:54:30 +01:00
Carlos Henrique Guardão Gandarez 5a34c64fbe Remove duplicated error handling 2025-03-20 07:04:42 -07:00
deadprogram 3e60eeb368 Prepare for release 0.37.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-19 07:32:48 +01:00
Randy Reddig e5a8d69300 internal/wasm-tools: update to go.bytecodealliance.org@v0.6.2 2025-03-18 15:40:46 +01:00
Randy Reddig 4f8672c9ab internal/cm: change error type from struct{string}
This is a temporary fix until cm@v0.2.2 is released, and package internal/cm and internal/wasi can be regenerated.

See #4810 for more information.
2025-03-18 15:40:46 +01:00
Randy Reddig a7416e7300 internal/wasi: remove x bit from generated files 2025-03-18 15:40:46 +01:00
Randy Reddig d63d8e0899 internal/wasm-tools, internal/cm: udpate to go.bytecodealliance.org@v0.6.1 and cm@v0.2.1 2025-03-18 15:40:46 +01:00
Randy Reddig 8e009de8a8 internal/cm: remove go.mod file 2025-03-18 15:40:46 +01:00
Randy Reddig d6527ece02 internal/cm: exclude certain files when copying package 2025-03-18 15:40:46 +01:00
Randy Reddig 211425c3e9 internal/wasi: regenerate WASI 0.2 bindings
Use go.bytecodealliance.org/cmd/wit-bindgen-go@v0.6.0
2025-03-18 15:40:46 +01:00
Randy Reddig 429aea8e74 internal/cm: update to go.bytecodealliance.org/cm@v0.2.0 2025-03-18 15:40:46 +01:00
Randy Reddig 7755780a49 GNUmakefile, internal/wasm-tools: update to go.bytecodealliance.org@v0.6.0 2025-03-18 15:40:46 +01:00
Ayke van Laethem 3a7c25f987 all: add the Boehm-Demers-Weiser GC on Linux
This adds support for the well-known Boehm GC. It's significantly faster
than our own naive GC and could be used as an alternative on bigger
systems.

In the future, this GC might also be supported on WebAssembly with some
extra work. Right now it's Linux only (though Windows/MacOS shouldn't be
too difficult to add).
2025-03-18 10:31:38 +01:00
Ayke van Laethem a4cbe3326e runtime: only allocate heap memory when needed
For example, with -gc=none and -gc=leaking, no heap needs to be
allocated when initializing the runtime. And some GCs (like -gc=custom)
are responsible for allocating the heap themselves.
2025-03-18 10:31:38 +01:00
Ayke van Laethem 5282b4efac runtime: remove unused file func.go
This was used in the past, but we don't use it anymore.
2025-03-17 22:13:17 +01:00
Randy Reddig 6a25fd494e reflect, internal/reflectlite: add Value.SetIter{Key,Value} and MapIter.Reset
Fixes #4790. Depends on #4787 (merge that first).
2025-03-17 20:44:20 +01:00
Randy Reddig d95c1b5982 reflect: remove unused go:linkname functions 2025-03-17 18:23:28 +01:00
Randy Reddig 24eed9e376 reflect: remove strconv.go 2025-03-17 18:23:28 +01:00
Randy Reddig c2af1d183e reflect: panic on Type.CanSeq[2] instead of returning false
PR feedback.
2025-03-17 18:23:28 +01:00
Randy Reddig 64651115c2 reflect: Value.Seq iteration value types should match
Implementation of https://github.com/golang/go/issues/71905

194696f1d1f6e5609f96d0fb0192595e7e0f5b90
2025-03-17 18:23:28 +01:00
Randy Reddig b6c3d142db reflect: copy reflect iter tests from upstream Go 2025-03-17 18:23:28 +01:00
Randy Reddig fafe80704f loader, iter, reflect: use build tags for package iter and iter methods on reflect.Value 2025-03-17 18:23:28 +01:00
Randy Reddig a2be2f3330 loader, iter: add shim for go1.22 and earlier 2025-03-17 18:23:28 +01:00
Randy Reddig 05fc49a0cf internal/reflectlite: remove old reflect.go 2025-03-17 18:23:28 +01:00
Randy Reddig 417aa4312e internal/reflectlite, reflect: move StringHeader and SliceHeader back to package reflect 2025-03-17 18:23:28 +01:00
Randy Reddig bf88cdea90 transform: cherry-pick from #4774 2025-03-17 18:23:28 +01:00
Randy Reddig c8d0b87a67 internal/reflectlite, reflect: handle different StructField 2025-03-17 18:23:28 +01:00
Randy Reddig 99a618385b runtime: use package reflectlite 2025-03-17 18:23:28 +01:00
Randy Reddig 9e143efd73 reflect: add Go 1.24 iter.Seq[2] methods 2025-03-17 18:23:28 +01:00
Randy Reddig d5c70a1cd3 reflect, internal/reflectlite: embed reflectlite types into reflect types 2025-03-17 18:23:28 +01:00
Ayke van Laethem bbb2b0c95b builder: use a separate module for command-line set strings
Strings that are set via the command line (as in -ldflags="-X ...") are
now created in a separate module to avoid type renaming issues. LLVM
sometimes renames types or merges types that are structurally the same,
and putting these strings in a separate module avoids this issue (and
lets llvm.LinkModules deal with the difference).

This fixes https://github.com/tinygo-org/tinygo/issues/4810.
2025-03-17 07:13:32 -07:00
Michael Smith 4768c7d431 machine/rp2350: add flash support for rp2350 (#4803)
* machine/rp2350: add flash support for rp2350
* combine duplicate files
* clean things up and group by source file
* add stubbed out xip cache clean func if needed in the future
* update flash_enable_xip_via_boot2
* remove unused macros and fix inconsistent formatting
* make flash size configurable like rp2040
* add missing flash size configs
* retain big Go CGo compatibility per #4103
* clarify CS0_SIZE source and remove single-use typedef
2025-03-16 09:45:53 +01:00
あーるどん 4f7c64cb24 fix(rp2040): replace loop counter with hw timer for USB SetAddressReq… (#4796)
* fix(rp2040/rp2350): replace loop counter with hw timer for USB SetAddressRequest timeout.

* fix code format.

---------

Co-authored-by: rdon <you@example.com>
2025-03-13 16:39:43 +01:00
Ayke van Laethem ff2a79de7c riscv-qemu: increase stack size
Bumping the stack size to 8kb (previous was 4kb) gets most remaining
tests to pass on baremetal.
2025-03-13 06:43:39 -07:00
soypat 04c7057ea6 add goroutine benchmark to examples 2025-03-11 13:10:41 -07:00
Ron Evans f76e8d2812 fix: ensure use of pointers for SPI interface on atsam21/atsam51 and other machines/boards that were missing implementation. (#4798)
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-11 12:50:31 -03:00
deadprogram bcdc5e0097 chore: update version to 0.37.0-dev
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-11 07:24:17 -07:00
deadprogram 2bee29959b refactor: use *SPI everywhere to make consistant for implementations.
Fixes #4663 "in reverse" by making SPI a pointer everywhere, as discussed
in the comments.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-11 06:06:31 -07:00
Ayke van Laethem cab3834bc9 riscv-qemu: add VirtIO RNG device
This implements machine.GetRNG() using VirtIO. This gets the tests to
pass for crypto/md5 and crypto/sha1 that use crypto/rand in their tests.
2025-03-11 04:22:16 -07:00
Elias Naur 7f970a45c2 machine: don't block the rp2xxx UART interrupt handler
Don't block forever if there's nothing to receive. On the other hand,
process the entire FIFO, not just a single byte.

This fixes an issue where the rp2350 would hang after programming through
openocd, where the UART0 interrupt would be spuriously pending.
2025-03-11 02:01:18 -07:00
Ayke van Laethem ebf70ab18e ci: add more tests for wasm and baremetal
Run a range of tests in CI, to make sure browser wasm and baremetal
don't regress too badly.

I have intentionally filtered out tests, so that newly added tests to
TEST_PACKAGES_FAST will be added here as well (and can be excluded if
needed).
2025-03-10 03:26:30 -07:00
Elias Naur 226744a538 machine: correct register address for Pin.SetInterrupt for rp2350 (#4782)
Fixes #4689
2025-03-09 10:26:38 -03:00
Ayke van Laethem dc876c6ed4 ci: add single test for wasm
It looks like we didn't have any tests for wasm. Having one tests is not
much, but it proves that the infrastructure works and it actually
verifies a fix to https://github.com/tinygo-org/tinygo/issues/4777.

We should add more packages to this list in the future.
2025-03-07 09:09:13 -08:00
Ayke van Laethem 5a09084c73 os: add stub Symlink for wasm
This doesn't do anything, but it makes tests work.
2025-03-07 09:09:13 -08:00
Ayke van Laethem 6c9074772c compiler: crypto/internal/sysrand is allowed to use unsafe signatures
Apparently this package imports `runtime.getRandomData` from the gojs
module. This is not yet implemented, but simply allowing this package to
do such imports gets crypto/sha256 to compile.
2025-03-07 09:09:13 -08:00
Ayke van Laethem e49663809b machine: fix RP2040 Pico board on the playground
Right now it doesn't compile, with errors like the following:

    # machine
    /app/tinygo/src/machine/board_pico.go:7:13: undefined: GPIO0
    /app/tinygo/src/machine/board_pico.go:8:13: undefined: GPIO1
    /app/tinygo/src/machine/board_pico.go:9:13: undefined: GPIO2
    /app/tinygo/src/machine/board_pico.go:10:13: undefined: GPIO3
    [...etc...]

This patch should fix that.
2025-03-07 07:20:40 -08:00
Elias Naur 7d6d93f7aa machine: bump rp2040 to 200MHz (#4768)
* machine: add support for core voltage adjustments to rp2040

In preparation for bumping the core frequency of the rp2040, this
change implements the required core voltage adjustment logic.

* machine: bump rp2040 to 200MHz
2025-03-04 08:57:59 -03:00
deadprogram 8c54e3dd88 release: update version to 0.36.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-03 13:02:59 -08:00
deadprogram 47c0debe4b docs: update CHANGELOG for 0.36.0 release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-03 13:02:59 -08:00
Randy Reddig 9a1cde40a8 src/reflect: implement Value.Equal
Implementation copied from Go.
2025-03-03 12:14:30 -08:00
Elias Naur 20fc814635 machine: replace hard-coded cpu frequencies on rp2xxx (#4767)
Missed from the earlier change that bumped the rp2350 frequency.
2025-03-02 08:38:29 -03:00
Egawa Takashi 64d8a04308 convert offset as signed int into unsigned int in syscall/js.stringVal in wasm_exec.js 2025-03-01 09:14:57 -08:00
Elias Naur b95effbdd7 machine: bump rp2350 CPUFrequency to 150 MHz (#4766)
Leave rp2040 speed bump to 200 MHz for a future change, because it
requires bumping the core voltage as well[0].

[0] https://github.com/raspberrypi/pico-sdk/releases/tag/2.1.1
2025-03-01 10:09:53 -03:00
Elias Naur 92c130c7be machine: compute rp2 clock dividers from crystal and target frequency (#4747)
Follow-up to #4728 which implemented the algorithm for finding the
dividers.

The calculation is computed at compile time by interp, as verified by
building example/blinky1 for -target pico.
2025-02-28 19:05:27 -03:00
deadprogram 31c4bc1151 license: update for 2025
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-02-28 20:56:22 +01:00
deadprogram 42f5e55ed5 docs: small corrections for README regarding wasm
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-02-28 20:56:22 +01:00
deadprogram 81da7bb4c1 targets: add target for pico2-w board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-02-28 20:02:35 +01:00
deadprogram 209754493b make: use GOOS and GOARCH for building wasm simulated boards
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-02-28 18:02:21 +01:00
deadprogram b0aef96340 fix: only infer target for wasm when GOOS and GOARCH are set correctly, not just based on file extension
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-02-28 18:02:21 +01:00
Ayke van Laethem 056394e24b make: add test-corpus-wasip2
I wanted to run the test corpus with WASIp2 so I added these lines.
2025-02-28 16:16:12 +01:00
Ayke van Laethem 1545659817 internal/syscall/unix: use our own version of this package
The upstream one assumes it's running on a Unix system (which makes
sense), but this package is also used on baremetal. So replace it on
systems that need a replaced syscall package.
2025-02-27 15:45:23 +01:00
Ayke van Laethem 38e3d55e64 all: add Go 1.24 support 2025-02-25 14:41:42 +01:00
Ayke van Laethem 66da29e89f wasip2: add stubs to get internal/syscall/unix to work
This fixes lots of broken tests in stdlib packages in Go 1.24.
2025-02-25 14:41:42 +01:00
Ayke van Laethem c180d6fe2f testing: add Chdir
This method was added in Go 1.24.
2025-02-25 14:41:42 +01:00
Ayke van Laethem cdea833b5c os: add File.Chdir support
We should really be using syscall.Fchdir here, but this is a fix to get
Go 1.24 working.
2025-02-25 14:41:42 +01:00
Ayke van Laethem c2eaa495df os: implement stub Chdir for non-OS systems 2025-02-25 14:41:42 +01:00
Ayke van Laethem 3476100dd0 syscall: add wasip1 RandomGet
This function is needed starting with Go 1.24.
2025-02-25 14:41:42 +01:00
Ayke van Laethem 07c7eff3c3 runtime: add FIPS helper functions
Not entirely sure what they're for, but the Go runtime also stores this
information per goroutine so let's go with that.
2025-02-25 14:41:42 +01:00
Ayke van Laethem a1be8cd9fe machine/usb/descriptor: avoid bytes package
We can't use the bytes package in Go 1.24 since it would result in an
import cycle. Therefore, use bytealg.Index instead.

(I'm not sure this code is correct - just searching for a range of bytes
seems brittle. But at least this commit shouldn't change the code).
2025-02-25 14:41:42 +01:00
Ayke van Laethem 52b9fcd57f machine: remove bytes package dependency in flash code
This also moves flash padding code to a single place, since it was
copied 5 times.

This change is necessary in Go 1.24 to avoid an import cycle.
2025-02-25 14:41:42 +01:00
Ayke van Laethem ea53ace270 cgo: mangle identifier names
This mangles CGo identifier names to something like "_Cgo_foo" instead
of using literal identifiers like "C.foo". This works around
https://github.com/golang/go/issues/71777.

I don't like this solution, but I hope we'll find a better solution in
the future. In that case we can revert this commit.
2025-02-25 14:41:42 +01:00
Ayke van Laethem a6cd072fa1 Revert "fix: implement testing {Skip,Fail}Now"
This reverts commit c5879c682c.

It doesn't look like this is working (see
https://github.com/tinygo-org/tinygo/pull/4736#issuecomment-2679670226),
and it doesn't have any tests anyway to prove that it does work. So I
think it's best to revert it for now and add a working implementation
with tests in the future.
2025-02-25 12:38:52 +01:00
Ayke van Laethem 4372dbdd41 ci: use older image for cross-compiling builds
This ensures that the resulting binaries are compatible with a wide
range of Linux systems, not just the most recent ones.
2025-02-24 19:22:27 +01:00
deadprogram 17bb1fec44 feature: add buildmode=wasi-legacy to support existing base of users who expected the
older behavior for wasi modules to not return an exit code as if they were reactors.

See #4726 for some details on what this is intended to address.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-02-21 13:06:14 +01:00
Ayke van Laethem f4fd79f7be runtime: manually initialize xorshift state
This ensures:

 1. The xorshift state is initialized during interp.
 2. The xorshift state gets initialized to a real random number on
    hardware that supports it at runtime.

This fixes a big binary size regression from the previous commit. It's
still not perfect: most programs increase binary size by a few bytes.
But it's not nearly as bad as before.
2025-02-21 11:36:17 +01:00
Ayke van Laethem 811b13a9d3 interp: correctly mark functions as modifying memory
Previously, if a function couldn't be interpreted in the interp pass, it
would just be run at runtime and the parameters would be marked as
potentially being modified. However, this is incorrect: the function
itself can also modify memory. So the function itself also needs to be
marked (recursively).

This fixes a number of regressions while upgrading to Go 1.24.

This results in a significant size regression that is mostly worked
around in the next commit.
2025-02-21 11:36:17 +01:00
Ron Evans 6e97079367 target: add Pimoroni Pico Plus2 (#4735)
* machine: separate definitions for ADC pins on rp2350/rp2350b

Signed-off-by: deadprogram <ron@hybridgroup.com>

* targets: add support for Pimoroni Pico Plus2

Signed-off-by: deadprogram <ron@hybridgroup.com>

---------

Signed-off-by: deadprogram <ron@hybridgroup.com>
Co-authored-by: Patricio Whittingslow <graded.sp@gmail.com>
2025-02-21 09:56:52 +01:00
soypat 9c7bbce029 rp2350: add pll generalized solution; fix ADC handles; pwm period fix 2025-02-21 09:56:52 +01:00
soypat 0ec1cb1e19 machine/rp2350: extending support to include the rp2350b 2025-02-21 09:56:52 +01:00
Scott Feldman e7118e5bda add comboat_fw tag for elecrow W5 boards with Combo-AT Wifi firmware 2025-02-21 07:22:17 +01:00
Ron Evans 150d9d1c6b fix: correctly handle id lookup for finalizeRef call
Modify the ID used for looking up the reference, based on suggestion made by @prochac

Also use console.error in the caase that the reference is not found, since it is now actually
known to be an error.
2025-02-20 22:17:38 +01:00
Laurent Demailly 8fe039156b fix: Avoid total failure on wasm finalizer call 2025-02-20 22:17:38 +01:00
leongross 2d17dec7bf os/file: add file.Chmod
Signed-off-by: leongross <leon.gross@9elements.com>
2025-02-19 22:12:51 +01:00
JP Hastings-Spital c1b267a208 Ensure build output directory is created
`tinygo build -o /path/to/out .` expected `/path/to/out` to already exist, which is different behaviour to `go build`. This change ensures tinygo creates any directories needed to be able to build to the specified output dir.
2025-02-19 19:55:48 +01:00
deadprogram f02c56c9e0 net: update to latest submodule with httptest subpackage and
ResolveIPAddress implementation.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-02-17 13:16:56 +01:00
leongross c5879c682c fix: implement testing {Skip,Fail}Now
PR https://github.com/tinygo-org/tinygo/pull/4623 implements
runtime.GoExit() with which we can improve the testing package
and align it more to upstream testing behavour https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/testing/testing.go;l=1150

Signed-off-by: leongross <leon.gross@9elements.com>
2025-02-15 21:30:22 +01:00
deadprogram 190c20825f targets: turn on GC for TKey1 device, since it does in fact work
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-02-14 12:49:20 +01:00
deadprogram d04eea7185 fix: add NoSandbox flag to chrome headless that is run during WASM tests, since
this is now required for Ubuntu 23+ and we are using Ubuntu 24+ when running
Github Actions.

See https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-02-12 17:13:56 +01:00
deadprogram f24cd31895 build: update Linux builds to run on ubuntu-latest since 20.04 is being retired
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-02-12 17:13:56 +01:00
m00874241 2044f6fbcc Added VersionTLS constants and VersionName(version uint16) method that turns it into a string, copied from big go 2025-01-30 07:30:57 +01:00
HattoriHanzo031 d55a89f96a board: support for NRF51 HW-651 (#4712)
NRF51: Support for NRF51 HW-651 board
* smoketest
* removed redundant flash-method
* smoketest fix
* description and links
* link fix
2025-01-30 05:46:52 +01:00
Elias Naur 080a6648e9 targets: match Pico2 stack size to Pico
Took me a while to debug weird crashes after switching from Pico to
Pico2.
2025-01-26 17:04:04 +08:00
deadprogram ec0a142190 build: update wasmtime used for CI to 29.0.1 to fix issue with install during CI tests
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-01-23 17:56:02 +08:00
Yurii Soldak b7fcf6ad68 nrf: fix adc read near zero 2025-01-23 13:03:25 +08:00
milkpirate 7417c14c2d nrf: make ADC resolution changeable (#4701)
Signed-off-by: Paul Schroeder <milkpirate@users.noreply.github.com>
2025-01-20 21:51:05 +01:00
vaaski 4f39be9c3b targets: esp32c3-supermini (#4518) 2025-01-20 21:44:58 +01:00
Scott Feldman 7305a44f0c targets: add support for Elecrow Pico rp2350 W5 boards (#4706)
https://www.elecrow.com/pico-w5-microcontroller-development-boards-rp2350-microcontroller-board.html

Just the basics to get several test to work (blinky1, flash).

This board has an rtl8720d Wifi/bluetooth chip wired to UART1, but the
rtl8720d firmware installed is the AT cmd set, not the RPC interface
used by the rtl8720dn driver, so no wifi support at the moment.

---------

Co-authored-by: Yurii Soldak <ysoldak@gmail.com>
2025-01-20 15:36:03 +01:00
Scott Feldman 74effd2fa9 targets: add support for Elecrow Pico rp2040 W5 boards (#4705)
https://www.elecrow.com/pico-w5-microcontroller-development-boards-rp2040-microcontroller-board-support-wifi-2-4ghz-5ghz-bluetooth5.html

Just the basics to get several test to work (blinky1, flash).

This board has an rtl8720d Wifi/bluetooth chip wired to UART1, but the
rtl8720d firmware installed is the AT cmd set, not the RPC interface
used by the rtl8720dn driver, so no wifi support at the moment.
2025-01-20 13:48:51 +01:00
Ayke van Laethem 9e9768b51d all: add support for LLVM 19 2025-01-20 06:15:33 +01:00
Roman Grudzinski 127557d27e Fix ADC channel selecting and ADC value reading 2025-01-17 09:18:30 +01:00
Roman Grudzinski fd89e9b83a Fix stm32f103 ADC (#4702)
fix: Fix stm32f103 ADC
2025-01-16 10:06:22 +01:00
Ayke van Laethem 5a1b885024 sync: move Mutex to internal/task
The mutex implementation needs a different implementation once support
for threading lands. This implementation just moves code to the
internal/task package to centralize these algorithms.
2025-01-14 11:11:15 +01:00
Volodymyr Pobochii b15adf23f8 machine: add support for waveshare-rp2040-tiny (#4683) 2025-01-14 01:53:40 +01:00
Yurii Soldak 29d2719bad example: naive debouncing for pininterrupt example (#2233) 2025-01-13 13:21:23 -03:00
deadprogram 194438cdd6 fix: correctly handle calls for GetRNG() when being made from nrf devices with SoftDevice enabled.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-01-12 16:44:35 +01:00
Scott Feldman 217f677bbe crypto/tls: add Dialer.DialContext() to fix websocket client
The latest golang.org/x/net websocket package v0.33.0 needs
Dialer.DailContext in crypto/tls.  This PR adds it.

Apps using golang.org/x/net are encouraged to use v0.33.0 to address:

CVE-2024-45338: Non-linear parsing of case-insensitive content in golang.org/x/net/html
CVE-2023-45288: net/http, x/net/http2: close connections when receiving too many headers

Tested with examples/net/websocket/dial.
2025-01-06 12:24:28 +01:00
Elliott Sales de Andrade 16b0f4cc9a builder: Fix parsing of external ld.lld error messages
If `ld.lld` is a version-specific binary (e.g., `ld.lld-18`), then its
error messages include the version. The parsing previously incorrectly
assumed it would be unversioned.
2025-01-05 13:44:56 +01:00
Elliott Sales de Andrade ff15b474fd Remove unnecessary executable permissions
These files are plain text and have no shebang.
2025-01-04 10:08:49 +01:00
sago35 3efc6340ad main: update to use Get-CimInstance as wmic is being deprecated 2025-01-04 08:27:58 +01:00
sago35 0426a5f902 goenv: update to new v0.36.0 development version 2024-12-26 12:25:20 +01:00
soypat b898916d52 rp2350 cleanup: unexport internal USB and clock package variable, consts and types 2024-12-26 10:49:48 +01:00
Ayke van Laethem 52983794d7 all: version 0.35.0 2024-12-20 12:09:22 +01:00
Ayke van Laethem 9d2f52805b builder: show files in size report table
Show which files cause a binary size increase. This makes it easier to
see where the size is going: for example, this makes it easy to see how
much the GC contributes to code size compared to other runtime parts.
2024-12-19 15:08:37 +01:00
Ayke van Laethem b18213805a builder: write HTML size report
This is not a big change over the existing size report with -size=full,
but it is a bit more readable.

More information will be added in subsequent commits.
2024-12-19 15:08:37 +01:00
Thomas Legris eeba90fd5b properly handle unix read on directory 2024-12-19 13:44:11 +01:00
deadprogram 6507765883 feature: make RNG implementation shared for rp2040/rp2350
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-19 12:54:25 +01:00
Ayke van Laethem a98e35ebab reflect: fix incorrect comment on elemType
PR that introduced this: https://github.com/tinygo-org/tinygo/pull/4543
2024-12-19 11:17:26 +01:00
deadprogram c4cfc01ba3 targets: add support for Pimoroni Tiny2350 board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-19 10:12:57 +01:00
deadprogram f64e70e659 feature: make SPI implementation shared for rp2040/rp2350
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-19 08:01:36 +01:00
deadprogram 64fa7853a7 feature: make i2c implementation shared for rp2040/rp2350
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-18 21:16:07 +01:00
Patricio Whittingslow 37f35f8c91 Add RP2350 support (#4459)
machine/rp2350: add support

* add linker scripts for rp2350
* add bootloader
* begin melding rp2040 and rp2350 APIs
* add UART
* add rp2350 boot patching
* Fix RP2350 memory layout (#4626)
* Remove rp2040-style second stage bootloader.
* Add 'minimum viable' IMAGE_DEF embedded block
* Create a pico2 specific target
* Implement rp2350 init, clock, and uart support
* Merge rp2 reset code back together
* Separate chip-specific clock definitions
* Clear pad isolation bit on rp2350
* Init UART in rp2350 runtime
* Correct usb/serial initialization order
* Implement jump-to-bootloader
* test: add pico2 to smoketests

---------

Signed-off-by: deadprogram <ron@hybridgroup.com>
Co-authored-by: Matthew Mets <matt.mets@cibomahto.com>
Co-authored-by: Matt Mets <matt@blinkinlabs.com>
Co-authored-by: deadprogram <ron@hybridgroup.com>
2024-12-18 19:36:30 +01:00
deadprogram 0d13e61d0c fix: add build tags to ensure that tkey target has stubs for runtime/interrupt package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-18 17:48:55 +01:00
deadprogram 5701bf81f3 feature: modify i2s interface/implementation to better match specification
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-18 15:23:00 +01:00
Ayke van Laethem 6110f0bc1b runtime: make channels parallelism-safe 2024-12-16 17:58:00 +01:00
Ron Evans 17302ca762 targets: add implementation for Tillitis TKey device (#4631)
* initial implementation for Tillitis TKey device
* add UART implementation for TKey
* add Pin interface implementation for TKey touch sensor
* add RNG interface implementation for TKey
* add helpful machine package functions to return identifiers such as name and version for TKey
* use built-in timer for sleep timing on TKey
* modify UART implementation for TKey to implement Serialer interface
* implement BLAKE2s ROM function call for TKey device
* handle abort by triggering TKey device fault using illegal instruction to halt CPU
* simplify TKey implementation by inheriting from existing riscv32 target
* return error for trying to configure invalid baudrates on UART
* add tkey to builder test
* be very specific for features passed to LLVM for specific config in use for TKey
* handle feedback items from TKey device code review

Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-14 14:26:03 +01:00
Ayke van Laethem f246599253 compiler: report error instead of crashing on missing function body
This can happen with generic functions, see:
https://github.com/tinygo-org/tinygo/issues/4486
2024-12-14 13:34:58 +01:00
deadprogram ec3f387f21 fix: specify ubuntu-22.04 during GH actions transition period to ubuntu-24.04
See https://github.com/actions/runner-images/issues/10636

Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-09 20:26:06 +01:00
Ayke van Laethem 31f7214156 test: make tests deterministic with -scheduler=threads 2024-12-06 11:55:34 +01:00
Ayke van Laethem 6faf36fc64 sync: make Cond MT-safe
This actually simplifies the code and avoids a heap allocation in the
call to Wait. Instead, it uses the Data field of the task to store
information on whether a task was signalled early.
2024-12-06 11:04:14 +01:00
deadprogram edb2f2a417 make: modify smoketest for nintendoswitch target to build something that includes the 'os' package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-04 15:53:49 +01:00
deadprogram 3eee686932 fix: allow nintendoswitch target to compile
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-04 15:53:49 +01:00
Ayke van Laethem 4aac3cd7b1 sync: implement WaitGroup using a futex
This prepares sync.WaitGroup for multithreading.
Code size for the cooperative scheduler is nearly unchanged.
2024-12-04 11:13:35 +01:00
Ayke van Laethem 2588bf7fa0 internal/task: add cooperative implementation of Futex
See the code comments for details. But in short, this implements a futex
for the cooperative scheduler (that is single threaded and
non-reentrant). Similar implementations can be made for basically every
other operating system, and even WebAssembly with the threading
(actually: atomics) proposal.
Using this basic futex implementation means we can use the same
implementation for synchronisation primitives on cooperative and
multicore systems.

For more information on futex across operating systems:
https://outerproduct.net/futex-dictionary.html
2024-12-04 10:12:55 +01:00
Ayke van Laethem 72f564555e internal/task: add non-atomic atomic operations
This adds some non-atomic types that have the same interface as the ones
in sync/atomic.

We currently don't need these to be atomic (because the scheduler is
entirely cooperative), but once we add support for a scheduler with
multiple threads and/or preemptive scheduling we can trivially add some
type aliases under a different build tag in the future when real
atomicity is needed for threading.
2024-12-04 10:12:52 +01:00
Ayke van Laethem b5a8931fb5 runtime: remove Cond
I don't think this is used anywhere right now, and it would need to be
updated to work with multithreading. So instead of fixing it, I think we
can remove it.

My original intention was to have something like this that could be used
in the machine package, but since this is in the runtime package (and
the runtime package imports the machine package on baremetal) it can't
actually be used that way.

I checked the TinyGo repo and the drivers repo, and `runtime.Cond` isn't
used anywhere except in that one test.
2024-12-04 09:43:10 +01:00
Ayke van Laethem d3810ecd48 ci: cache the Go cache across builds
This should hopefully make the build slightly faster.
2024-12-04 06:58:04 +01:00
Ayke van Laethem 3b8062170c mips: fix a bug when scanning the stack
Previously the assembler was reordering this code:

    jal tinygo_scanstack
    move $a0, $sp

Into this:

    jal tinygo_scanstack
    nop
    move $a0, $sp

So it was "helpfully" inserting a branch delay slot, even though this
was already being taken care of.
Somehow this didn't break, but it does break in the WIP threading branch
(https://github.com/tinygo-org/tinygo/pull/4559) where this bug leads to
a crash.
2024-12-01 11:23:27 +01:00
Ayke van Laethem 26c36d0a2e runtime: lock output in print/println
This ensures that calls to print/println happening in different threads
are not interleaved. It's a task.PMutex, so this should only change
things when threading is used.

This matches the Go compiler, which does the same thing:
https://godbolt.org/z/na5KzE7en

The locks are not recursive, which means that we need to be careful to
not call `print` or `println` inside a runtime.print* implementation,
inside putchar (recursively), and inside signal handlers. Making them
recursive might be useful to do in the future, but it's not really
necessary.
2024-12-01 11:12:00 +01:00
Ayke van Laethem 09a22ac4b4 runtime: make signals parallelism-safe 2024-12-01 07:40:22 +01:00
Ayke van Laethem aed555d858 runtime: implement Goexit
This is needed for full support for the testing package
2024-11-30 11:55:22 +01:00
deadprogram 65b085a5d5 examples: use default UART settings in echo example
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-11-29 11:33:45 +01:00
Ayke van Laethem 392a709b77 runtime: use uint32 for the channel state and select index
This uses uint32 instead of uint64. The reason for this is that uint64
atomic operations aren't universally available (especially on 32-bit
architectures). We could also use uintptr, but that seems needlessly
complicated: it's unlikely real-world programs will use more than a
billion select states (2^30).
2024-11-27 12:16:10 +01:00
Ayke van Laethem ee6fcd76f4 builder: add testing for -size=full
This helps to make sure this feature continues to work and we won't
accidentally introduce regressions.
2024-11-26 12:41:12 +01:00
Ayke van Laethem ca80c52df1 builder: fix wasi-libc path names on Windows with -size=full
Without this fix, wasi-libc is listed as follows:

     65       0       0       0 |      65       0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-bottom-half\sources
     14       0       0       0 |      14       0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-top-half\musl\src\exit
   1398       0       0       0 |    1398       0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-top-half\musl\src\string
   1525       0       0       0 |    1525       0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-top-half\sources

With this fix, it's identified as the wasi-libc C library:

   3002       0       0       0 |    3002       0 | C wasi-libc
2024-11-26 12:41:12 +01:00
Ayke van Laethem b76ea29520 builder: work around bug in DWARF paths in Clang
See bug: https://github.com/llvm/llvm-project/issues/117317
2024-11-26 12:41:12 +01:00
Ayke van Laethem 9172cc15d2 builder: fix cache paths in -size=full output
This fixes long paths from the TinyGo cached GOROOT, as can be seen
here:

   code  rodata    data     bss |   flash     ram | package
------------------------------- | --------------- | -------
      0       5       0       5 |       5       5 | (padding)
    148       0       0       5 |     148       5 | (unknown)
     76       0       0       0 |      76       0 | /home/ayke/.cache/tinygo/goroot-ce8827882be9dc201bed279a631881177ae124ea064510684a3cf4bb66436e1a/src/device/arm
      4       0       0       0 |       4       0 | /home/ayke/.cache/tinygo/goroot-ce8827882be9dc201bed279a631881177ae124ea064510684a3cf4bb66436e1a/src/internal/task

They're now attributed to the correct package instead (device/arm and
internal/task).
2024-11-26 12:41:12 +01:00
Ayke van Laethem 1b83d43cfa cgo: fix build warnings on Windows ARM
Fix the warning, and also remove tinygo_clang_enum_visitor which was
unused.

See: https://github.com/golang/go/issues/49721
2024-11-26 09:30:13 +01:00
Matt Mets 7847f4ea8e Fix invalid assembler syntax from gen-device-svd
This addresses #4608
2024-11-22 14:44:27 +01:00
Matt Mets 19736e5be2 Update cmsis-svd library
This updates the version of the cmsis-svd library, to include a version
that supports rp2350. This is in support of #4452
2024-11-22 14:44:27 +01:00
Ayke van Laethem 51504bfd2e sync: make Pool thread-safe
Make sure the object is locked when trying to modify it.
Binary size seems unaffected when not using threading.
2024-11-22 11:55:52 +01:00
Ayke van Laethem 79164dae71 sync: only use a lock in the Map implementation when needed 2024-11-22 09:42:39 +01:00
Ayke van Laethem 8d04821639 runtime: prepare the leaking GC for concurrent operations
This uses the task.PMutex parallel-only-mutex type to make the leaking
GC parallelism safe. The task.PMutex type is currently a no-op but will
become a real mutex once we add true parallelism.
2024-11-22 08:17:35 +01:00
Ayke van Laethem f75187392d internal/task: implement PMutex
PMutex is a mutex when threading is possible, and a dummy mutex-like
object (that doesn't do anything) otherwise.
2024-11-22 08:17:35 +01:00
Ayke van Laethem ecc6d16f22 interp: align created globals
Use the alignment from the align attribute of the runtime.alloc call.
This is going to be a more accurate alignment, and is typically smaller
than the default.
2024-11-21 10:19:19 +01:00
Ayke van Laethem 5f252b3e16 runtime: fix regression introduced by merging conflicting PRs 2024-11-21 09:37:13 +01:00
Ayke van Laethem dd1ebbd31b runtime: implement race-free signals using futexes
This requires an API introduced in MacOS 11. I think that's fine, since
the version before that (MacOS 10.15) is EOL since 2022. Though if
needed, we could certainly work around it by using an older and slightly
less nice API.
2024-11-20 18:50:34 +01:00
Ayke van Laethem 95671469c7 runtime: use SA_RESTART when registering a signal
This really is the only sane way to register a signal. If this flag is
not set, many syscalls will return EINTR (and not complete their
operation) which will be a massive source of hard-to-debug bugs.
2024-11-20 18:50:34 +01:00
deadprogram 6601c1b1a6 fix: add updated test output to match recent changes
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-11-20 17:55:04 +01:00
Ayke van Laethem 5ebda89d78 runtime: rewrite channel implementation
This rewrite simplifies the channel implementation considerably, with
34% less LOC. Perhaps the most important change is the removal of the
channel state, which made sense when we had only send and receive
operations but only makes things more compliated when multiple select
operations can be pending on a single channel.

I did this rewrite originally to make it possible to make channels
parallelism-safe. The current implementation is not parallelism-safe,
but it will be easy to make it so (the main additions will be a channel
lock, a global select lock, and an atomic compare-and-swap in
chanQueue.pop).
2024-11-20 14:57:30 +01:00
Ayke van Laethem b7a3fd8d2f cgo: add support for #cgo noescape lines
Here is the proposal:
https://github.com/golang/go/issues/56378

They are documented here:
https://pkg.go.dev/cmd/cgo@master#hdr-Optimizing_calls_of_C_code

This would have been very useful to fix
https://github.com/tinygo-org/bluetooth/issues/176 in a nice way. That
bug is now fixed in a different way using a wrapper function, but once
this new noescape pragma gets included in TinyGo we could remove the
workaround and use `#cgo noescape` instead.
2024-11-20 14:07:55 +01:00
Ayke van Laethem fd625f7265 compiler: add //go:noescape pragma
This only works on declarations, not definitions. This is intentional:
it follows the upstream Go implemetation.

However, we might want to loosen this requirement at some point: TinyGo
sometimes stores pointers in memory mapped I/O knowing they won't
actually escape, but the compiler doesn't know about this.
2024-11-20 14:07:55 +01:00
Ayke van Laethem d1fe02df23 runtime: move scheduler code around
This moves all scheduler code into a separate file that is only compiled
when there's a scheduler in use (the tasks or asyncify scheduler, which
are both cooperative). The main goal of this change is to make it easier
to add a new "scheduler" based on OS threads.

It also fixes a few subtle issues with `-gc=none`:

  - Gosched() panicked. This is now fixed to just return immediately
    (the only logical thing to do when there's only one goroutine).
  - Timers aren't supported without a scheduler, but the relevant code
    was still present and would happily add a timer to the queue. It
    just never ran. So now it exits with a runtime error, similar to any
    blocking operation.
2024-11-20 12:45:09 +01:00
Ayke van Laethem 6593cf22fa cgo: support errno value as second return parameter
Making this work on all targets was interesting but there's now a test
in place to make sure this works on all targets that have the CGo test
enabled (which is almost all targets).
2024-11-20 07:53:59 +01:00
Damian Gryski 548fba82e6 compiler: Fix wasmimport -> wasmexport in error message
Fixes #4615
2024-11-20 07:53:02 +01:00
Ayke van Laethem 2d26b6cc8d windows: don't return, exit via exit(0) instead
This fixes a bug where output would not actually be written to stdout
before exiting the process, leading to a flaky Windows CI. Exiting using
`exit(0)` appears to fix this.

For some background, see: https://github.com/tinygo-org/tinygo/pull/4589
2024-11-19 11:58:07 +01:00
Ayke van Laethem 0087d4c6bf syscall: use wasi-libc tables for wasm/js target
Instead of using fake tables for errno and others, use the ones that
correspond to wasi-libc.
2024-11-19 07:51:58 +01:00
Ayke van Laethem 289fceb3ea syscall: refactor environment handling
* Move environment functions to their own files.
  * Rewrite the WASIp2 version of environment variables to be much
    simpler (don't go through C functions).
2024-11-19 07:51:58 +01:00
Ayke van Laethem e12da15f7d cgo: support function-like macros
This is needed for code like this:

    #define __WASI_ERRNO_INVAL (UINT16_C(28))
    #define EINVAL __WASI_ERRNO_INVAL
2024-11-18 18:35:20 +01:00
Ayke van Laethem c4867c8743 cgo: define idents referenced only from macros 2024-11-18 18:35:20 +01:00
Ayke van Laethem 8068419854 runtime: heapptr only needs to be initialized once
There is no need to initialize it twice.
2024-11-18 17:14:55 +01:00
Ayke van Laethem 9b3eb3fe59 ci: run at least some tests on older Go/LLVM versions
These should make sure basic functionality is still working.
Using the `-short` flag to avoid taking too long to run all tests (and
to install all the necessary emulators), and because some targets might
not work in older Go/LLVM versions (such as WASI).

This does _not_ run tests and checks against expected IR, because LLVM
IR changes a lot across versions.
2024-11-18 16:42:14 +01:00
leongross 1dcccf87b5 linux: add runtime.fcntl function
This is needed for the internal/syscall/unix package.

Signed-off-by: leongross <leon.gross@9elements.com>
2024-11-18 14:44:06 +01:00
Ayke van Laethem d51ef253a9 builder: whitelist temporary directory env var for Clang invocation
It looks like this breaks on Windows:
https://github.com/tinygo-org/tinygo/issues/4557
I haven't confirmed this is indeed the problem, but it would make sense.
And passing through the temporary directory seems like a good idea
regardless, there's not much that could break due to that.
2024-11-15 18:27:48 +01:00
Ayke van Laethem 6d4dfcf72f compiler, runtime: move constants into shared package
Use a single package for certain constants that must be the same between
the compiler and the runtime.

While just using the same values in both places works, this is much more
obvious and harder to mess up. It also avoids the need for comments
pointing to the other location the constant is defined. And having it in
code makes it possible for IDEs to analyze the source.

In the future, more such constants and maybe algorithms can be added.
2024-11-15 10:11:57 +01:00
sivchari ac9f72be61 support to parse devl version
Signed-off-by: sivchari <shibuuuu5@gmail.com>
2024-11-15 09:21:47 +01:00
Ayke van Laethem 258dac2324 wasm: tidy up wasm_exec.js a bit 2024-11-14 11:02:54 -08:00
Ayke van Laethem b7d91e2f33 ci: use TinyGo version in artifact files
This avoids needing to rename them ourselves (which is kinda annoying)
and also avoids mistakes in the process.
2024-11-14 10:38:56 +01:00
Ayke van Laethem 860697257b runtime: optimize findHead
This is similar to https://github.com/tinygo-org/tinygo/pull/3899, but
smaller and hopefully just as efficient.

Thanks to @HattoriHanzo031 for starting this work, benchmarking, and for
improving the performance of the code even further.
2024-11-14 09:28:25 +01:00
Damian Gryski 91563cff1f targets/wasm_exec: call process.exit() when go.run() returns 2024-11-14 09:16:25 +01:00
Ben Krieger 4f96a50fd0 reflect: fix Copy of non-pointer array with size > 64bits 2024-11-13 10:28:33 -07:00
Ben Krieger e98fea0bba reflect: add Value.Clear; support anytype->interface{}, Slice->(*)Array in Value.Convert 2024-11-13 10:28:33 -07:00
Ayke van Laethem c728e031df goenv: read git hash embedded in the binary
The git hash that's part of `tinygo version` is now read from the binary
itself instead of embedding it with a `-ldflags` flag. This means it is
also present when building TinyGo using `go build` or `go install`.
2024-11-10 08:59:22 +01:00
Ayke van Laethem ceb7891986 wasm: correctly return from run() in wasm_exec.js
Instead of hanging forever, it should return the exit code from os.Exit.
2024-11-08 11:55:38 +01:00
Ayke van Laethem 04a7baec3e wasm: support //go:wasmexport functions after a call to time.Sleep
This fixes a bug where `//go:wasmexport` functions would not be allowed
anymore after a call to `time.Sleep` (when using `-buildmode=default`).
2024-11-08 11:55:38 +01:00
Ayke van Laethem 8ff97bdedd runtime: remove unnecessary check for negative sleepTicks duration
This is now fixed for every target in the previous commit.

Also see: https://github.com/tinygo-org/tinygo/pull/4239
2024-11-07 15:37:18 +01:00
Ayke van Laethem a6c4287b4d runtime: don't call sleepTicks with a negative duration
There are rare cases where this can happen, see for example
https://github.com/tinygo-org/tinygo/issues/4568
2024-11-07 15:37:18 +01:00
leongross f9f439ad49 os: implement StartProcess
Signed-off-by: leongross <leon.gross@9elements.com>
2024-11-07 09:45:47 +01:00
Randy Reddig c02a8141c7 internal/wasm-tools, syscall: update to wasm-tools-go@v0.3.1 (#4577)
* internal/wasm-tools, internal/cm: update wasm-tools-go to v0.3.1 and regenerate bindings

* syscall: use new (cm.Result).Result() method instead of OK() and Err()
2024-11-04 14:32:17 -08:00
sago35 5862b481a4 goenv: update to new v0.35.0 development version 2024-11-01 09:14:09 +01:00
Joonas Bergius 1648fe8ef2 runtime/trace: stub all public methods
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-01 09:12:59 +01:00
Ayke van Laethem 449eefdb19 compiler: allow deferred panic
This is rare, but apparently some programs do this:

    defer panic("...")

This is emitted in the IR as a builtin function.
2024-11-01 09:11:38 +01:00
Ayke van Laethem 058f62ac08 main: parse extldflags early so we can report the error message
This avoids some weird behavior when the -extldflags flag cannot be
parsed by TinyGo.
2024-11-01 09:10:49 +01:00
Ayke van Laethem 4e49ba597d interrupt: fix bug in interrupt lowering
The alignment wasn't set, so defaulted to 4 (for a 32-bit int). LLVM saw
this, and therefore assumed that a ptrtoint of the pointer would have
had the lowest bits unset. That's an entirely valid optimization, except
that we are using these globals for arbitrary values (and aren't
actually using these globals).

Fixed by setting alignment to 1. It works, though long-term we should
maybe find a different solution for this.
2024-11-01 09:10:25 +01:00
Ayke van Laethem 4b706ae25c test: show output even when a test binary didn't exit cleanly
This was a problem on wasm, where node would exit with a non-zero exit
code when there was a panic.
2024-11-01 08:49:00 +01:00
Ayke van Laethem 1ac26d3d2f test: run TestWasmExportJS tests in parallel 2024-11-01 08:48:19 +01:00
Randy Reddig 0edeaf657f tinygo: revise and simplify wasmtime argument handling (#4555) 2024-10-28 17:57:24 +01:00
Ayke van Laethem 76d5b3d786 sync: don't use volatile in Mutex
Volatile loads/stors are only useful for communication with interrupts
or for memory-mapped I/O. They do not provide any sort of safety for
sync.Mutex, while making it *appear* as if it is more safe.

  * `sync.Mutex` cannot be used safely inside interrupts, because any
    blocking calls (including `Lock`) will cause a runtime panic.
  * For multithreading, `volatile` is also the wrong choice. Atomic
    operations should be used instead, and the current code would not
    work for multithreaded programs anyway.
2024-10-28 16:43:28 +01:00
Ayke van Laethem 915132645e ci: remove 'shell: bash' lines from MacOS build
Unlike Windows, we can just use the default shell here.
2024-10-28 10:22:05 +01:00
1041 changed files with 63886 additions and 17357 deletions
-125
View File
@@ -1,125 +0,0 @@
version: 2.1
commands:
submodules:
steps:
- run:
name: "Pull submodules"
command: git submodule update --init
llvm-source-linux:
steps:
- restore_cache:
keys:
- llvm-source-18-v1
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-18-v1
paths:
- llvm-project/clang/lib/Headers
- llvm-project/clang/include
- llvm-project/compiler-rt
- llvm-project/lld/include
- llvm-project/llvm/include
hack-ninja-jobs:
steps:
- run:
name: "Hack Ninja to use less jobs"
command: |
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
build-binaryen-linux:
steps:
- restore_cache:
keys:
- binaryen-linux-v3
- run:
name: "Build Binaryen"
command: |
make binaryen
- save_cache:
key: binaryen-linux-v3
paths:
- build/wasm-opt
test-linux:
parameters:
llvm:
type: string
fmt-check:
type: boolean
default: true
steps:
- checkout
- submodules
- run:
name: "Install apt dependencies"
command: |
echo 'deb https://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-<<parameters.llvm>> main' > /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
apt-get update
apt-get install --no-install-recommends -y \
llvm-<<parameters.llvm>>-dev \
clang-<<parameters.llvm>> \
libclang-<<parameters.llvm>>-dev \
lld-<<parameters.llvm>> \
cmake \
ninja-build
- hack-ninja-jobs
- build-binaryen-linux
- restore_cache:
keys:
- go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v4-{{ checksum "go.mod" }}
- llvm-source-linux
- run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache:
keys:
- wasi-libc-sysroot-systemclang-v7
- run: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-systemclang-v7
paths:
- lib/wasi-libc/sysroot
- when:
condition: <<parameters.fmt-check>>
steps:
- run:
# Do this before gen-device so that it doesn't check the
# formatting of generated files.
name: Check Go code formatting
command: make fmt-check lint
- run: make gen-device -j4
- run: make smoketest XTENSA=0
- save_cache:
key: go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- /go/pkg/mod
jobs:
test-llvm15-go119:
docker:
- image: golang:1.19-bullseye
steps:
- test-linux:
llvm: "15"
# "make lint" fails before go 1.21 because internal/tools/go.mod specifies packages that require go 1.21
fmt-check: false
resource_class: large
test-llvm18-go123:
docker:
- image: golang:1.23-bullseye
steps:
- test-linux:
llvm: "18"
resource_class: large
workflows:
test-all:
jobs:
# This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass.
- test-llvm15-go119
# This tests LLVM 18 support when linking against system libraries.
- test-llvm18-go123
-1
View File
@@ -1,5 +1,4 @@
build/
llvm-*/
.github
.circleci
+33 -47
View File
@@ -16,34 +16,37 @@ jobs:
name: build-macos
strategy:
matrix:
# macos-13: amd64 (oldest supported version as of 18-10-2024)
# macos-14: arm64 (oldest arm64 version)
os: [macos-13, macos-14]
# macos-14: arm64 (oldest supported version as of 18-11-2025)
# macos-15-intel: amd64 (last intel version to be supported by github runners)
# See https://github.com/actions/runner-images/issues/13046
os: [macos-14, macos-15-intel]
include:
- os: macos-13
goarch: amd64
- os: macos-14
goarch: arm64
- os: macos-15-intel
goarch: amd64
runs-on: ${{ matrix.os }}
steps:
- name: Install Dependencies
shell: bash
run: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: true
- name: Extract TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.23'
go-version: '1.27.0-rc.2'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-18-${{ matrix.os }}-v2
key: llvm-source-22-${{ matrix.os }}-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -54,7 +57,7 @@ jobs:
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v4
uses: actions/cache/save@v5
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
@@ -65,73 +68,55 @@ jobs:
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-18-${{ matrix.os }}-v3
key: llvm-build-22-${{ matrix.os }}-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
shell: bash
run: |
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
HOMEBREW_NO_AUTO_UPDATE=1 brew install ninja
# build!
make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v4
uses: actions/cache/save@v5
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache wasi-libc sysroot
uses: actions/cache@v4
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-${{ matrix.os }}-v1
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- name: make gen-device
run: make -j3 gen-device
- name: Test TinyGo
shell: bash
run: make test GOTESTFLAGS="-short"
run: make test GOTESTFLAGS="-only-current-os"
- name: Build TinyGo release tarball
run: make release -j3
- name: Test stdlib packages
run: make tinygo-test
- name: Make release artifact
shell: bash
run: cp -p build/release.tar.gz build/tinygo.darwin-${{ matrix.goarch }}.tar.gz
run: cp -p build/release.tar.gz build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
- name: Publish release artifact
# Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
# We can essentially pick one of these:
# - have a double-zipped artifact when downloaded from the UI
# - have a very slow artifact upload
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: darwin-${{ matrix.goarch }}-double-zipped
path: build/tinygo.darwin-${{ matrix.goarch }}.tar.gz
name: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
archive: false
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo
test-macos-homebrew:
name: homebrew-install
runs-on: macos-latest
strategy:
matrix:
version: [16, 17, 18]
version: [16, 17, 18, 19, 20]
steps:
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
- name: Fix Python symlinks
run: |
# Github runners have broken symlinks, so relink
@@ -140,20 +125,21 @@ jobs:
- name: Install LLVM
run: |
brew install llvm@${{ matrix.version }}
brew link llvm@${{ matrix.version }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.23'
go-version: '1.27.0-rc.2'
cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install -tags=llvm${{ matrix.version }}
- name: Check binary
run: tinygo version
- name: Build TinyGo (default LLVM)
if: matrix.version == 18
if: matrix.version == 20
run: go install
- name: Check binary
if: matrix.version == 18
if: matrix.version == 20
run: tinygo 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.25" # 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-22-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
+6 -48
View File
@@ -31,14 +31,14 @@ jobs:
sudo rm -rf /usr/local/share/boost
df -h
- name: Check out the repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: |
tinygo/tinygo-dev
@@ -47,18 +47,18 @@ jobs:
type=sha,format=long
type=raw,value=latest
- name: Log in to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
uses: docker/build-push-action@v7
with:
context: .
push: true
@@ -66,45 +66,3 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Trigger Drivers repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/drivers/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger Bluetooth repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/bluetooth/actions/workflows/linux.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyFS repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinyfs/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyFont repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinyfont/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyDraw repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinydraw/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyTerm repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinyterm/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
+105 -84
View File
@@ -12,38 +12,61 @@ 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.27.0-rc.2'
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
# statically linked binary.
runs-on: ubuntu-latest
container:
image: golang:1.23-alpine
image: golang:1.27rc2-alpine
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Install apk dependencies
# tar: needed for actions/cache@v4
# tar: needed for actions/cache@v5
# git+openssh: needed for checkout (I think?)
# ruby: needed to install fpm
run: apk add tar git openssh make g++ ruby-dev
run: apk add tar git openssh make g++ ruby-dev mold
- name: Work around CVE-2022-24765
# We're not on a multi-user machine, so this is safe.
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: true
- name: Extract TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Cache Go
uses: actions/cache@v4
uses: actions/cache@v5
with:
key: go-cache-linux-alpine-v1-${{ hashFiles('go.mod') }}
key: go-cache-linux-alpine-v2-${{ hashFiles('go.mod') }}
path: |
~/.cache/go-build
~/go/pkg/mod
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-18-linux-alpine-v1
key: llvm-source-22-linux-alpine-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -54,7 +77,7 @@ jobs:
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v4
uses: actions/cache/save@v5
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
@@ -65,10 +88,10 @@ jobs:
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-18-linux-alpine-v2
key: llvm-build-22-linux-alpine-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -83,31 +106,22 @@ jobs:
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v4
uses: actions/cache/save@v5
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache Binaryen
uses: actions/cache@v4
uses: actions/cache@v5
id: cache-binaryen
with:
key: binaryen-linux-alpine-v1
key: binaryen-linux-alpine-v3
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: |
apk add cmake samurai python3
make binaryen STATIC=1
- name: Cache wasi-libc
uses: actions/cache@v4
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-linux-alpine-v2
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- name: Install fpm
run: |
gem install --version 4.0.7 public_suffix
@@ -120,46 +134,52 @@ jobs:
- name: Build TinyGo release
run: |
make release deb -j3 STATIC=1
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_amd64.deb
- name: Publish release artifact
uses: actions/upload-artifact@v4
cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
- name: "Publish release artifact: tarball"
uses: actions/upload-artifact@v7
with:
name: linux-amd64-double-zipped
path: |
/tmp/tinygo.linux-amd64.tar.gz
/tmp/tinygo_amd64.deb
name: tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
path: /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
archive: false
- name: "Publish release artifact: Debian package"
uses: actions/upload-artifact@v7
with:
name: tinygo_${{ steps.version.outputs.version }}_amd64.deb
path: /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
archive: false
test-linux-build:
# Test the binaries built in the build-linux job by running the smoke tests.
runs-on: ubuntu-latest
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.23'
go-version: '1.27.0-rc.2'
cache: true
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
version: "19.0.1"
version: "29.0.1"
- name: Install wasm-tools
uses: bytecodealliance/actions/wasm-tools/setup@v1
- name: Download release artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-amd64-double-zipped
name: tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
- name: Extract release tarball
run: |
mkdir -p ~/lib
tar -C ~/lib -xf tinygo.linux-amd64.tar.gz
tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
- run: make tinygo-test-wasip1-fast
- run: make tinygo-test-wasip2-fast
- run: make tinygo-test-wasm
- run: make smoketest
assert-test-linux:
# Run all tests that can run on Linux, with LLVM assertions enabled to catch
@@ -167,7 +187,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: true
- name: Install apt dependencies
@@ -182,25 +202,25 @@ jobs:
simavr \
ninja-build
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.23'
go-version: '1.27.0-rc.2'
cache: true
- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '18'
node-version: '22'
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
version: "19.0.1"
version: "29.0.1"
- name: Setup `wasm-tools`
uses: bytecodealliance/actions/wasm-tools/setup@v1
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-18-linux-asserts-v1
key: llvm-source-22-linux-asserts-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -211,7 +231,7 @@ jobs:
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v4
uses: actions/cache/save@v5
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
@@ -222,10 +242,10 @@ jobs:
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-18-linux-asserts-v2
key: llvm-build-22-linux-asserts-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -238,13 +258,13 @@ jobs:
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v4
uses: actions/cache/save@v5
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache Binaryen
uses: actions/cache@v4
uses: actions/cache@v5
id: cache-binaryen
with:
key: binaryen-linux-asserts-v1
@@ -252,15 +272,6 @@ jobs:
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: make binaryen
- name: Cache wasi-libc
uses: actions/cache@v4
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-linux-asserts-v6
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- run: make gen-device -j4
- name: Test TinyGo
run: make ASSERT=1 test
@@ -272,7 +283,9 @@ jobs:
run: make tinygo-test
- run: make smoketest
- run: make wasmtest
- run: make tinygo-baremetal
- 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
@@ -292,11 +305,14 @@ jobs:
- goarch: arm
toolchain: arm-linux-gnueabihf
libc: armhf
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04 # note: use the oldest image available! (see above)
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Get TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install apt dependencies
run: |
sudo apt-get update
@@ -305,15 +321,15 @@ jobs:
g++-${{ matrix.toolchain }} \
libc6-dev-${{ matrix.libc }}-cross
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.23'
go-version: '1.27.0-rc.2'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-18-linux-v1
key: llvm-source-22-linux-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -324,7 +340,7 @@ jobs:
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v4
uses: actions/cache/save@v5
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
@@ -335,10 +351,10 @@ jobs:
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-18-linux-${{ matrix.goarch }}-v2
key: llvm-build-22-linux-${{ matrix.goarch }}-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -353,16 +369,16 @@ jobs:
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v4
uses: actions/cache/save@v5
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache Binaryen
uses: actions/cache@v4
uses: actions/cache@v5
id: cache-binaryen
with:
key: binaryen-linux-${{ matrix.goarch }}-v4
key: binaryen-linux-${{ matrix.goarch }}-v5
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
@@ -379,13 +395,13 @@ jobs:
run: |
make CROSS=${{ matrix.toolchain }}
- name: Download amd64 release
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: linux-amd64-double-zipped
name: tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
- name: Extract amd64 release
run: |
mkdir -p build/release
tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
tar -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz -C build/release tinygo
- name: Modify release
run: |
cp -p build/tinygo build/release/tinygo/bin
@@ -393,12 +409,17 @@ jobs:
- name: Create ${{ matrix.goarch }} release
run: |
make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }}
cp -p build/release.tar.gz /tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ matrix.libc }}.deb
- name: Publish release artifact
uses: actions/upload-artifact@v4
cp -p build/release.tar.gz /tmp/tinygo${{ needs.build-linux.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ needs.build-linux.outputs.version }}_${{ matrix.libc }}.deb
- name: "Publish release artifact: tarball"
uses: actions/upload-artifact@v7
with:
name: linux-${{ matrix.goarch }}-double-zipped
path: |
/tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
/tmp/tinygo_${{ matrix.libc }}.deb
name: linux-${{ matrix.goarch }}-double-zipped-${{ needs.build-linux.outputs.version }}
path: /tmp/tinygo${{ needs.build-linux.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
archive: false
- name: "Publish release artifact: Debian package"
uses: actions/upload-artifact@v7
with:
name: linux-${{ matrix.goarch }}-double-zipped-${{ needs.build-linux.outputs.version }}
path: /tmp/tinygo_${{ needs.build-linux.outputs.version }}_${{ matrix.libc }}.deb
archive: false
+7 -7
View File
@@ -25,18 +25,18 @@ jobs:
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: |
tinygo/llvm-18
ghcr.io/${{ github.repository_owner }}/llvm-18
tinygo/llvm-20
ghcr.io/${{ github.repository_owner }}/llvm-20
tags: |
type=sha,format=long
type=raw,value=latest
@@ -46,13 +46,13 @@ jobs:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
uses: docker/build-push-action@v7
with:
target: tinygo-llvm-build
context: .
+7 -7
View File
@@ -21,28 +21,28 @@ jobs:
# See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668
run: sudo apt-get remove llvm-18
- name: Checkout
uses: actions/checkout@v4
- name: Pull musl
uses: actions/checkout@v6
- name: Pull musl, bdwgc
run: |
git submodule update --init lib/musl
git submodule update --init lib/musl lib/bdwgc
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-18-linux-nix-v1
key: llvm-source-22-linux-nix-v1
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@v4
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
- uses: cachix/install-nix-action@v22
- uses: cachix/install-nix-action@v31
- name: Test
run: |
nix develop --ignore-environment --keep HOME --command bash -c "go install && ~/go/bin/tinygo version && ~/go/bin/tinygo build -o test ./testdata/cgo"
+5 -5
View File
@@ -2,11 +2,11 @@
# still works after checking out the dev branch (that is, when going from LLVM
# 16 to LLVM 17 for example, both Clang 16 and Clang 17 are installed).
echo 'deb https://apt.llvm.org/noble/ llvm-toolchain-noble-18 main' | sudo tee /etc/apt/sources.list.d/llvm.list
echo 'deb https://apt.llvm.org/noble/ llvm-toolchain-noble-20 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-18-dev \
clang-18 \
libclang-18-dev \
lld-18
llvm-20-dev \
clang-20 \
libclang-20-dev \
lld-20
+8 -4
View File
@@ -19,25 +19,29 @@ jobs:
- name: Add GOBIN to $PATH
run: |
echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '~1.25'
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0 # fetch all history (no sparse checkout)
submodules: true
- name: Install apt dependencies
run: ./.github/workflows/sizediff-install-pkgs.sh
- name: Restore LLVM source cache
uses: actions/cache@v4
uses: actions/cache@v5
id: cache-llvm-source
with:
key: llvm-source-18-sizediff-v1
key: llvm-source-22-sizediff-v1
path: |
llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache Go
uses: actions/cache@v4
uses: actions/cache@v5
with:
key: go-cache-linux-sizediff-v2-${{ hashFiles('go.mod') }}
path: |
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
# Extract the version string from the source code, to be stored in a variable.
grep 'const version' goenv/version.go | sed 's/^const version = "\(.*\)"$/version=\1/g'
+54 -90
View File
@@ -14,34 +14,33 @@ concurrency:
jobs:
build-windows:
runs-on: windows-2022
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: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies
shell: bash
run: |
scoop config use_external_7zip true
scoop install ninja binaryen
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: true
- name: Extract TinyGo version
id: version
shell: bash
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.23'
go-version: '1.27.0-rc.2'
cache: true
- name: Restore cached LLVM source
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-18-windows-v1
key: llvm-source-22-windows-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -52,7 +51,7 @@ jobs:
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save cached LLVM source
uses: actions/cache/save@v4
uses: actions/cache/save@v5
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
@@ -63,10 +62,10 @@ jobs:
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore cached LLVM build
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-18-windows-v2
key: llvm-build-22-windows-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -80,80 +79,64 @@ jobs:
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save cached LLVM build
uses: actions/cache/save@v4
uses: actions/cache/save@v5
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache wasi-libc sysroot
uses: actions/cache@v4
id: cache-wasi-libc
- name: Cache Go cache
uses: actions/cache@v5
with:
key: wasi-libc-sysroot-v5
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
key: go-cache-windows-v3-${{ hashFiles('go.mod') }}
path: |
C:/Users/runneradmin/AppData/Local/go-build
C:/Users/runneradmin/go/pkg/mod
- name: Install wasmtime
run: |
scoop install wasmtime@14.0.4
scoop config use_external_7zip true
scoop install wasmtime@29.0.1
- name: make gen-device
run: make -j3 gen-device
- name: Test TinyGo
shell: bash
run: make test GOTESTFLAGS="-short"
run: make test GOTESTFLAGS="-only-current-os"
- name: Build TinyGo release tarball
shell: bash
run: make build/release -j4
- name: Make release artifact
shell: bash
working-directory: build/release
run: 7z -tzip a release.zip tinygo
run: 7z -tzip a tinygo${{ steps.version.outputs.version }}.windows-amd64.zip tinygo
- name: Publish release artifact
# Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
# We can essentially pick one of these:
# - have a dobule-zipped artifact when downloaded from the UI
# - have a very slow artifact upload
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: windows-amd64-double-zipped
path: build/release/release.zip
name: tinygo${{ steps.version.outputs.version }}.windows-amd64.zip
path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip
archive: false
smoke-test-windows:
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: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies
shell: bash
run: |
scoop config use_external_7zip true
scoop install binaryen
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.23'
go-version: '1.27.0-rc.2'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-amd64-double-zipped
name: tinygo${{ needs.build-windows.outputs.version }}.windows-amd64.zip
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
# This build is already unzipped.
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo
@@ -162,28 +145,19 @@ 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@v4
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.23'
go-version: '1.27.0-rc.2'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-amd64-double-zipped
name: tinygo${{ needs.build-windows.outputs.version }}.windows-amd64.zip
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
# This build is already unzipped.
- name: Test stdlib packages
run: make tinygo-test TINYGO=$(PWD)/build/tinygo/bin/tinygo
@@ -191,34 +165,24 @@ 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: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies
shell: bash
run: |
scoop install binaryen && scoop install wasmtime@14.0.4
scoop config use_external_7zip true
scoop install binaryen wasmtime@29.0.1
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.23'
go-version: '1.27.0-rc.2'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: windows-amd64-double-zipped
name: tinygo${{ needs.build-windows.outputs.version }}.windows-amd64.zip
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
# This build is already unzipped.
- name: Test stdlib packages on wasip1
run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo
+4
View File
@@ -37,5 +37,9 @@ test.exe
test.gba
test.hex
test.nro
test.uf2
test.wasm
wasm.wasm
*.uf2
*.elf
+5 -3
View File
@@ -16,13 +16,13 @@
url = https://github.com/WebAssembly/wasi-libc
[submodule "lib/picolibc"]
path = lib/picolibc
url = https://github.com/keith-packard/picolibc.git
url = https://github.com/picolibc/picolibc.git
[submodule "lib/stm32-svd"]
path = lib/stm32-svd
url = https://github.com/tinygo-org/stm32-svd
[submodule "lib/musl"]
path = lib/musl
url = git://git.musl-libc.org/musl
url = https://github.com/tinygo-org/musl-libc.git
[submodule "lib/binaryen"]
path = lib/binaryen
url = https://github.com/WebAssembly/binaryen.git
@@ -35,7 +35,9 @@
[submodule "src/net"]
path = src/net
url = https://github.com/tinygo-org/net.git
branch = dev
[submodule "lib/wasi-cli"]
path = lib/wasi-cli
url = https://github.com/WebAssembly/wasi-cli
[submodule "lib/bdwgc"]
path = lib/bdwgc
url = https://github.com/ivmai/bdwgc.git
+8 -2
View File
@@ -85,11 +85,17 @@ Try running TinyGo:
./build/tinygo help
Also, make sure the `tinygo` binary really is statically linked. Check this
using `ldd` (not to be confused with `lld`):
Also, make sure the `tinygo` binary really is statically linked. The command to check for
dynamic dependencies differs depending on your operating system.
On Linux, use `ldd` (not to be confused with `lld`):
ldd ./build/tinygo
On macOS, use otool -L:
otool -L ./build/tinygo
The result should not contain libclang or libLLVM.
## Make a release tarball
+595
View File
@@ -1,3 +1,598 @@
0.41.1
---
* **machine**
- esp32c3: correct pin interrupt setup call that was overlooked from #5320
* **runtime**
- esp32s3: wait for TIMG0 update register to clear before reading timer registers
* **net**
- update net module to a version that is backwards compatible with Go 1.25.x to fix #5332
0.41.0
---
* **general**
- go.mod, compiler: bump minimum Go version to 1.23
- builder: support Go 1.26 now
- feat: add inheritable-only field to filter processor-level targets (#5270)
- feature: add esp32flash flash method for esp32s3/esp32c3/esp32/esp8266
- flashing: introduce flash method 'esp32jtag' for esp32c3/esp32s3 targets
- fashing: add "adb" flash method using Android Debug Bridge
- main: auto-use best available flashing options on esp32
- espflasher: update to espflasher 0.6.0
* **compiler**
- implement method-set based AssignableTo and Implements (#5304)
- simplify createObjectLayout
- implement copy directly
- fix min/max on floats by using intrinsics
- add element layout to sliceAppend
- add intrinsic for crypto/internal/constanttime.boolToUint8
- fix SyscallN handling for Windows on Go 1.26+
* **core**
- reflect: add TypeAssert
- reflect: fix TypeAssert for structs
- sync: add WaitGroup.Go
- os: add UserCacheDir and UserConfigDir
- os: implement Lchown function for changing file ownership (#5161)
- testing: add b.ReportMetric()
- errors: add errors package to passing list
- internal/gclayout: use correct lengths
- internal/abi: add EscapeNonString, EscapeToResultNonString
- internal, runtime: add internal fips bool
- internal/itoa: resurrect from Go stdlib
- internal/syscall/unix: implement GetRandom for WASI targets
- interp: make ptrtoint size mismatch a recoverable error
- loader: support "all:" prefix in //go:embed patterns
- loader, crypto/internal/entropy: fix 32 MiB RAM overflow on microcontrollers
- loader, unicode/utf8: fix hiBits overflow on 16-bit AVR targets
- builder: order embedded files deterministically
- builder: fix panic in findPackagePath when using -size short
- builder: fix SIGSEGV when stripping duplicate function definitions
- builder, runtime: fix duplicate symbol error with Go 1.26+ on Windows
- builder: use target-specific -march for RISC-V library compilation
- transform: modify output format from the -print-allocs flag to match the go coverage tool format
- cgo: allow for changes to 'short-enums' flag
- wasm: add more stubbed file operations
* **machine**
- esp32c3: implement BlockDevice for esp32c3 flash
- esp32c3: clear GPIO STATUS before dispatching pin callbacks
- esp32c3: implement USBDevice using interrupts
- esp32c3: add Enable stub for QEMU test target
- esp32c3: fix interrupt disable handling
- esp32c3: clear MCAUSE after handling interrupt
- esp32c3: map missing IRAM sections in linker script
- esp32c3: fix to use PROVIDE() for WiFi/BLE ROM function addresses to allow blob overrides
- esp32c3: add WiFi/BLE ROM linker support
- esp32c3: use TIMG0 alarm interrupt for sleepTicks instead of busy-waiting
- esp32s3: implement Pin.SetInterrupt for GPIO pin change interrupts
- esp32s3: use edge-triggered CPU interrupt for GPIO pin interrupts
- esp32s3: add interrupt support (#5244)
- esp32s3: switch USB implementation to use interrupts instead of polling
- esp32s3: replace inline ISR with full interrupt vector handler
- esp32s3: use TIMG0 alarm interrupt for sleepTicks
- esp32s3: improve exception handlers, add procPin/procUnpin, and linker wrap flags
- esp32s3: fix Xtensa register window spill using recursive call4 in task switching
- esp32s3: update linker script and boot assembly for multi-page flash XIP mapping
- esp32s3: add flash XIP boot assembly with cache/MMU init
- esp32s3: implement SPI (#5169)
- esp32s3: implement I2C interface (#5210)
- esp32s3: implement RNG based on onboard hardware random number generator
- esp32s3,esp32c3: add USB serial support
- esp32s3,esp32c3: add txStalled flag to skip USB serial spin when no host
- esp32s3,esp32c3: make USB Serial/JTAG writes non-blocking when FIFO is full
- esp32c3/esp32s3: refactor ADC implementation to reduce code duplication
- esp32s3,esp32c3: add ADC support (#5231)
- esp32s3,esp32c3: add PWM support (#5215)
- esp32c3/esp32s3: refactoring and corrections for SPI implementation
- esp32xx: add WASM simulator support for ESP32-C3/ESP32-S3 targets
- esp32: default SPI CS pin to NoPin when unset
- esp: fix tinygo_scanCurrentStack to spill register windows
- stm32: fix UART interrupt storm caused by uncleared overrun error
- stm32: fix PWM problem due to register shifting
- stm32: add STM32U585 target definition and runtime
- stm32: add STM32U5 GPIO, pin interrupts, and UART support
- stm32: add STM32U5 I2C support
- stm32: add STM32U5 SPI support
- stm32u585: implement ADC
- stm32g0b1: add support (#5150)
- stm32g0b1: add Watchdog + ADC support (#5158)
- stm32l0: add flash support
- stm32l0x1,l0x2: TIM: adapt to 32-bit register access
- stm32g0: sync with updated stm32 device files
- attiny85: add USI-based SPI support (#5181)
- attiny85: add PWM support (#5171)
- rp: add Close function to UART to allow for removing all system resources/power usage
- rp: use blockReset() and unresetBlockWait() helper functions for peripheral reset/unreset
- rp2: prevent PWM Period method overflow
- rp2: add per-byte timeout budget for I2C (#5189)
- feather-m0: export UART0 and pins
- usb/cdc: better ring buffer implementation (#5209)
- fix: replace ! with ~ for register mask
- atmega328p_simulator: add correct build tag for arduino_uno after renaming
* **net**
- update to Go 1.26.2 net package with UDP and JS improvements
* **runtime**
- implement fminimum/fmaximum
- make timeoffset atomic
- add MemStats.HeapObjects
- handle GODEBUG on wasip2 (#5312)
- fix Darwin syscall return value truncation and lost arguments
- add syscall.runtimeClearenv for Go 1.26
- split syscall functions for darwin changes for 1.26
- fix: init heap before random number seed on wasm platforms
* **targets**
- add esp32s3-supermini board target
- add support for Seeedstudio Xiao-RP2350 board
- add Shrike Lite board (#5170)
- rename arduino target to arduino-uno
- add pins/setup for Arduino UNO Q board QWIIC connector
- correct pin mapping for Arduino UNO Q STM32 MCU
- add stm32u585 openocd commands for flashing on Arduino UNO Q
- correct name/tag use for esp32s3-wroom1 board
- modify esp32, esp32s3, esp32c3, & esp8266 targets to use built-in esp32flash
- switch rp2040/rp2350 to default to tasks scheduler
- change default stack size for esp32c3 and esp32s3 to 8196
- swan: rename group build tag stm32l4x5 => stm32l4y5 to avoid clash with stm32 device name
- nucleo-f722ze: add build-tag stm32f722
- fix: set stm32u5x clock rate to default
- create generic targets for esp32 boards
* **libs**
- stm32-svd: update submodule
- update tools/gen-device-svd for recent changes to stm32-svd file structure (#5212)
* **build/test**
- build: use Golang 1.26 for all builds
- build: actually use the version of llvm we are supposed to be testing by using brew link command
- build: let scoop run updates to resolve binaryen install failures on Windows
- ci: don't double zip release artifacts
- test: increase default timeout from 1 to 2 minutes to prevent spurious CI fails
- testdata: fix flaky timers test by draining ticker channel after Stop
- testdata: more corpus entries (#5182)
- add soypat/lneto to corpus
- GNUmakefile: add context and expvar to stdlib tests
- GNUmakefile: add encoding/xml to stdlib tests on Linux
- main (test): skip mipsle test if the emulator is missing
- make: remove machine without board from smoketest
- stm32: add Arduino UNO Q to smoketests
- flake: bump to nixpkgs 25.11
- chore: update all github actions to latest versions
- fix: use external 7zip for scoop installs on Windows
0.40.1
---
* **machine**
- nrf: fix flash writes when SoftDevice is enabled
* **runtime**
- runtime: avoid fixed math/rand sequence on RP2040/RP2350 (#5124)
- runtime: add calls to initRand() during run() for all schedulers
- runtime: call initRand() before initHeap() during initialization
- runtime: use rand_hwrng hardwareRand for RP2040/RP2350 (#5135)
* **libs**
- picolibc: use updated location for git repo
0.40.0
---
* **general**
- all: add full LLVM 20 support
- core: feat: enable //go:linkname pragma for globals
- core: feature: Add flag to ignore go compatibility matrix (#5078)
- chore: update version for 0.40 development cycle
* **compiler**
- emit an error when the actual arch doesn't match GOARCH
- mark string parameters as readonly
- use Tarjan's SCC algorithm to detect loops for defer
- lower "large stack" limit to 16kb
* **core**
- shrink bdwgc library
- Fix linker errors for runtime.vgetrandom and crypto/internal/sysrand.fatal
- fix: add TryLock to sync.RWMutex
- fix: correct linter issues exposed by the fix in #4679
- fix: don't hardcode success return state
- fix: expand RTT debugger compatibility
- internal/task (threads): save stack bounds instead of scanning under a lock
- internal/task: create detached threads and fix error handling
- interp: better errors when debugging interp
- transform (gc): create stack slots in callers of external functions
- internal/task: prevent semaphore resource leak for threads scheduler
* **machine**
- cortexm: optimize code size for the HardFault_Handler
- fe310: add I2C pins for the HiFive1b
- clarify WriteAt semantics of BlockDevice
- fix deprecated AsmFull comment (#5005)
- make sure DMA buffers do not escape unnecessarily
- only enable USB-CDC when needed
- use larger SPI MAXCNT on nrf52833 and nrf52840
- fix: update m.queuedBytes when clamping output to avoid corrupting sentBytes
- fix: use int64 in ReadTemperature to avoid overflow
- fix(rp2): disable DBGPAUSE on startup
- fix(rp2): possible integer overflow while computing factors for SPI baudrate
- fix(rp2): reset spinlocks at startup
- fix(rp2): switch spinlock busy loop to wfe
- fix(rp2): use side-effect-free spinlocks
- nrf: add ADC_VDDH which is an ADC pin for VDDH
- nrf: don't block SPI transfer
- nrf: don't set PSELN, it's ignored in single ended mode anyway
- nrf: fix typo in ADC configuration
- nrf: refactor SoftDevice enabled check
- nrf: rename pwmPin to adcPin
- nrf: support flash operations while the SoftDevice is enabled
- rp2040: allow writing to the UART inside interrupts
- machine,nrf528: stop the bus only once on I2C bus error and ensures the first error is returned
* **net**
- update submodule to latest commits
* **runtime**
- (avr): fix infinite longjmp loop if stack is aligned to 256 bytes
- (gc_blocks.go): clear full size of allocation
- (gc_blocks.go): make sweep branchless
- (gc_blocks.go): simplify scanning logic
- (gc_blocks.go): use a linked stack to scan marked objects
- (gc_blocks.go): use best-fit allocation
- (gc_boehm.go): fix world already stopped check
- (wasm): scan the system stack
- fix sleep duration for long sleeps
- remove copied code for nrf52840
- src/syscall: update src buffer after write
- wasm: fix C realloc and optimize it a bit
* **targets**
- add xiao-esp32s3 board target
- Add ESP32-S3 support (#5091)
- Added Gopher ARCADE board
- Create "pico2-ice" target board (#5062)
* **build/test**
- Add testing.T.Context() and testing.B.Context()
- create separate go.mod file for testing dependencies for wasm tests that use Chromium headless browser to avoid use of older incompatible version.
- go back to normal scheduler instead of tasks scheduler for macos CI
- update CI to use Go 1.25.5
- update macOS GH actions builds to handle sunset of macOS 13
- use task scheduler on macOS builds to avoid test race condition lockups
- update all CI builds to use latest stable Go release. Also update some of the actions to their latest releases.
- update GH actions builds to use Go 1.25.4
- uninstall cmake before install
- fix: point the submodule for musl-lib to a mirror in the TinyGo GitHub org
- fix: remove macOS 15 from CI build matrix (conflicts with macOS 14 build)
- fix: separate host expected bytes from device intended bytes
- fix/typo: makeESPFirmwareImage
- make: GNUmakefile: shrink TinyGo binaries on Linux
- move the directory list into a variable
- several improvements to the macOS GH actions build
- Fix for #4678: top-level 'make lint' wasn't working
- fix: increase the timeout for chromedp to connect to the headless browser used for running the wasm tests.
- testdata: some more packages for the test corpus
0.39.0
---
* **general**
- all: add Go 1.25 support
- net: update to latest tinygo net package
- docs: clarify build verification step for macOS users
- Add flag to skip Renesas SVD builds
* **build**
- Makefile: install missing dlmalloc files
- flash: add -o flag support to save built binary (Fixes #4937) (#4942)
- fix: update version of clang to 17 to accommodate latest Go 1.25 docker base image
* **ci**
- chore: update all CI builds to test Go 1.25 release
- fix: disable test-newest since CircleCI seems unable to download due to rate-limits on Dockerhub
- ci: rename some jobs to avoid churn on every Go/LLVM version bump
- ci: make the goroutines test less racy
- tests: de-flake goroutines test
* **compiler**
- compiler: implement internal/abi.Escape
* **main**
- main: show the compiler error (if any) for `tinygo test -c`
- chore: correct GOOS=js name in error messages for WASM
* **machine**
- machine: add international keys
- machine: remove some unnecessary "// peripherals:" comments
- machine: add I2C pin comments
- machine: standardize I2C errors with "i2c:" prefix
- machine: make I2C usable in the simulator
- fix: add SPI and I2C to teensy 4.1 (#4943)
- `rp2`: use the correct channel mask for rp2350 ADC; hold lock during read (#4938)
- `rp2`: disable digital input for analog inputs
* **runtime**
- runtime: ensure time.Sleep(d) sleeps at least d
- runtime: stub out weak pointer support
- runtime: implement dummy AddCleanup
- runtime: enable multi-core scheduler for rp2350
- `internal/task`: use -stack-size flag when starting a new thread
- `internal/task`: add SA_RESTART flag to GC interrupts
- `internal/task`: a few small correctness fixes
- `internal/gclayout`: make gclayout values constants
- darwin: add threading support and use it by default
* **standard library**
- `sync`: implement sync.Swap
- `reflect`: implement Method.IsExported
* **testing**
- testing: stub out testing.B.Loop
* **targets**
- `stm32`: add support for the STM32L031G6U6
- add metro-rp2350 board definition (#4989)
- `rp2040/rp2350`: set the default stack size to 8k for rp2040/rp2350 based boards where this was not already the case
0.38.0
---
* **general**
- `go.*`: upgrade `golang.org/x/tools` to v0.30.0
- `all`: add support for LLVM 20
* **build**
- go back to using MinoruSekine/setup-scoop for Windows CI builds
- `flake.*`: upgrade to nixpkgs 25.05, LLVM 20
- `Makefile`: only detect ccache command when needed
- `Makefile`: create random filename inside rule
- `Makefile`: don't set GOROOT
- `Makefile`: call uname at most once
- `Makefile`: only read NodeJS version when it is needed
* **compiler**
- add support for `GODEBUG=gotypesalias=1`
- `interp`: fix `copy()` from/to external buffers
- add `-nobounds` (similar to `-gcflags=-B`)
- `compileopts`: add library version to cached library path
- `builder`: build wasi-libc inside TinyGo
- `builder`: simplify bdwgc libc dependency
- `builder`: don't use precompiled libraries
- `compileopts`: enable support for `GOARCH=wasm` in `tinygo test`
* **fixes**
- `rp2350`: Fix DMA to SPI transmits on RP2350 (#4903)
- `microbit v2`: use OpenOCD flash method on microbit v2 when using Nordic Semi SoftDevice
- `main`: display all of the current GC options for the `-gc` flag
- Remove duplicated error handling
- `sync`: fix `TestMutexConcurrent` test
- fix race condition in `testdata/goroutines.go`
- fix build warnings on Windows ARM
* **machine**
- `usb`: add USB mass storage class support
- implement usb receive message throttling
- declare usb endpoints per-platform
- `samd21`: implement watchdog
- `samd51`: write to flash memory in 512 byte long chunks
- `samd21`: write to flash memory in 64 byte long chunks
- don't inline RTT `WriteByte` everywhere
- `rp2`: unexport machine-specific errors
- `rp2`: discount scheduling delays in I2C timeouts (#4876)
- use pointer receiver in simulated PWM peripherals
- add simulated PWM/timer peripherals
- `rp2`: expose usb endpoint stall handling
- `arm`: clear pending interrupts before enabling them
- `rp2`: merge common usb code (#4856)
* **main**
- add "cores" and "threads" schedulers to help text
- add `StartPos` and `EndPos` to `-json` build output
- change `-json` flag to match upstream Go
* **runtime**
- don't lock the print output inside interrupts
- don't try to interrupt other cores before they are started
- implement `NumCPU` for the multicore scheduler
- add support for multicore scheduler
- refactor obtaining the system stack
- `interrupt`: add `Checkpoint` type
- add `exportedFuncPtr`
- avoid an allocation in `(*time.Timer).Reset`
- stub runtime signal functions for `os/signal` on wasip1
- move `timeUnit` to a single place
- implement `NumCPU` for `-scheduler=threads`
- move `mainExited` boolean
- `internal/task`: rename `tinygo_pause` to `tinygo_task_exit`
- map every goroutine to a new OS thread
- refactor `timerQueue`
- make conservative and precise GC MT-safe
- `internal/task`: implement atomic primitives for preemptive scheduling
- Use diskutil on macOS to extract volume name and path for FAT mounts #4928
* **standard library**
- `net`: update submodule to latest commits
- `runtime/debug`: add GC related stubs
- `metrics`: flesh out some of the metric types
- `reflect`: Chan related stubs
- `os`: handle relative and abs paths in `Executable()`
- `os`: add `os.Executable()` for Darwin
- `sync`: implement `RWMutex` using futexes
- `reflect`: Add `SliceOf`, `ArrayOf`, `StructOf`, `MapOf`, `FuncOf`
* **targets**
- `rp2040`: add multicore support
- `riscv32`: use `gdb` binary as a fallback
- add target for Microbit v2 with SoftDevice S140 support for both peripheral and central
- `windows`: use MSVCRT.DLL instead of UCRT on i386
- `windows`: add windows/386 support
- `arm64`: remove unnecessary `.section` directive
- `riscv-qemu`: actually sleep in `time.Sleep()`
- `riscv`: define CSR constants and use them where possible
- `darwin`: support Boehm GC (and use by default)
- `windows`: add support for the Boehm-Demers-Weiser GC
- `windows`: fix wrong register for first parameter
* **wasm**
- add Boehm GC support
- refactor/modify stub signal handling
- don't block `//go:wasmexport` because of running goroutines
- use `int64` instead of `float64` for the `timeUnit`
* **boards**
- Add board support for BigTreeTech SKR Pico (#4842)
0.37.0
---
* **general**
- add the Boehm-Demers-Weiser GC on Linux
* **ci**
- add more tests for wasm and baremetal
* **compiler**
- crypto/internal/sysrand is allowed to use unsafe signatures
* **examples**
- add goroutine benchmark to examples
* **fixes**
- ensure use of pointers for SPI interface on atsam21/atsam51 and other machines/boards that were missing implementation (#4798)
- replace loop counter with hw timer for USB SetAddressReq on rp2040 (#4796)
* **internal**
- update to go.bytecodealliance.org@v0.6.2 in GNUmakefile and internal/wasm-tools
- exclude certain files when copying package in internal/cm
- update to go.bytecodealliance.org/cm@v0.2.2 in internal/cm
- remove old reflect.go in internal/reflectlite
* **loader**
- use build tags for package iter and iter methods on reflect.Value in loader, iter, reflect
- add shim for go1.22 and earlier in loader, iter
* **machine**
- bump rp2040 to 200MHz (#4768)
- correct register address for Pin.SetInterrupt for rp2350 (#4782)
- don't block the rp2xxx UART interrupt handler
- fix RP2040 Pico board on the playground
- add flash support for rp2350 (#4803)
* **os**
- add stub Symlink for wasm
* **refactor**
- use *SPI everywhere to make consistent for implementations. Fixes #4663 "in reverse" by making SPI a pointer everywhere, as discussed in the comments.
* **reflect**
- add Value.SetIter{Key,Value} and MapIter.Reset in reflect, internal/reflectlite
- embed reflectlite types into reflect types in reflect, internal/reflectlite
- add Go 1.24 iter.Seq[2] methods
- copy reflect iter tests from upstream Go
- panic on Type.CanSeq[2] instead of returning false
- remove strconv.go
- remove unused go:linkname functions
* **riscv-qemu**
- add VirtIO RNG device
- increase stack size
* **runtime**
- only allocate heap memory when needed
- remove unused file func.go
- use package reflectlite
* **transform**
- cherry-pick from #4774
0.36.0
---
* **general**
- add initial Go 1.24 support
- add support for LLVM 19
- update license for 2025
- make small corrections for README regarding wasm
- use GOOS and GOARCH for building wasm simulated boards
- only infer target for wasm when GOOS and GOARCH are set correctly, not just based on file extension
- add test-corpus-wasip2
- use older image for cross-compiling builds
- update Linux builds to run on ubuntu-latest since 20.04 is being retired
- ensure build output directory is created
- add NoSandbox flag to chrome headless that is run during WASM tests, since this is now required for Ubuntu 23+ and we are using Ubuntu 24+ when running Github Actions
- update wasmtime used for CI to 29.0.1 to fix issue with install during CI tests
- update to use `Get-CimInstance` as `wmic` is being deprecated on WIndows
- remove unnecessary executable permissions
- `goenv`: update to new v0.36.0 development version
* **compiler**
- `builder`: fix parsing of external ld.lld error messages
- `cgo`: mangle identifier names
- `interp`: correctly mark functions as modifying memory
- add buildmode=wasi-legacy to support existing base of users who expected the older behavior for wasi modules to not return an exit code as if they were reactors
* **standard library**
- `crypto/tls`: add Dialer.DialContext() to fix websocket client
- `crypto/tls`: add VersionTLS constants and VersionName(version uint16) method that turns it into a string, copied from big go
- `internal/syscall/unix`: use our own version of this package
- `machine`: replace hard-coded cpu frequencies on rp2xxx
- `machine`: bump rp2350 CPUFrequency to 150 MHz
- `machine`: compute rp2 clock dividers from crystal and target frequency
- `machine`: remove bytes package dependency in flash code
- `machine/usb/descriptor`: avoid bytes package
- `net`: update to latest submodule with httptest subpackage and ResolveIPAddress implementation
- `os`: add File.Chdir support
- `os`: implement stub Chdir for non-OS systems
- `os/file`: add file.Chmod
- `reflect`: implement Value.Equal
- `runtime`: add FIPS helper functions
- `runtime`: manually initialize xorshift state
- `sync`: move Mutex to internal/task
- `syscall`: add wasip1 RandomGet
- `testing`: add Chdir
- `wasip2`: add stubs to get internal/syscall/unix to work
* **fixes**
- correctly handle calls for GetRNG() when being made from nrf devices with SoftDevice enabled
- fix stm32f103 ADC
- `wasm`: correctly handle id lookup for finalizeRef call
- `wasm`: avoid total failure on wasm finalizer call
- `wasm`: convert offset as signed int into unsigned int in syscall/js.stringVal in wasm_exec.js
* **targets**
- rp2350: add pll generalized solution; fix ADC handles; pwm period fix
- rp2350: extending support to include the rp2350b
- rp2350: cleanup: unexport internal USB and clock package variable, consts and types
- nrf: make ADC resolution changeable
- turn on GC for TKey1 device, since it does in fact work
- match Pico2 stack size to Pico
* **boards**
- add support for Pimoroni Pico Plus2
- add target for pico2-w board
- add comboat_fw tag for elecrow W5 boards with Combo-AT Wifi firmware
- add support for Elecrow Pico rp2350 W5 boards
- add support for Elecrow Pico rp2040 W5 boards
- add support for NRF51 HW-651
- add support for esp32c3-supermini
- add support for waveshare-rp2040-tiny
* **examples**
- add naive debouncing for pininterrupt example
0.35.0
---
* **general**
- update cmsis-svd library
- use default UART settings in the echo example
- `goenv`: also show git hash with custom build of TinyGo
- `goenv`: support parsing development versions of Go
- `main`: parse extldflags early so we can report the error message
* **compiler**
- `builder`: whitelist temporary directory env var for Clang invocation to fix Windows bug
- `builder`: fix cache paths in `-size=full` output
- `builder`: work around incorrectly escaped DWARF paths on Windows (Clang bug)
- `builder`: fix wasi-libc path names on Windows with `-size=full`
- `builder`: write HTML size report
- `cgo`: support C identifiers only referred to from within macros
- `cgo`: support function-like macros
- `cgo`: support errno value as second return parameter
- `cgo`: add support for `#cgo noescape` lines
- `compiler`: fix bug in interrupt lowering
- `compiler`: allow panic directly in `defer`
- `compiler`: fix wasmimport -> wasmexport in error message
- `compiler`: support `//go:noescape` pragma
- `compiler`: report error instead of crashing when instantiating a generic function without body
- `interp`: align created globals
* **standard library**
- `machine`: modify i2s interface/implementation to better match specification
- `os`: implement `StartProcess`
- `reflect`: add `Value.Clear`
- `reflect`: add interface support to `NumMethods`
- `reflect`: fix `AssignableTo` for named + non-named types
- `reflect`: implement `CanConvert`
- `reflect`: handle more cases in `Convert`
- `reflect`: fix Copy of non-pointer array with size > 64bits
- `runtime`: don't call sleepTicks with a negative duration
- `runtime`: optimize GC scanning (findHead)
- `runtime`: move constants into shared package
- `runtime`: add `runtime.fcntl` function for internal/syscall/unix
- `runtime`: heapptr only needs to be initialized once
- `runtime`: refactor scheduler (this fixes a few bugs with `-scheduler=none`)
- `runtime`: rewrite channel implementation to be smaller and more flexible
- `runtime`: use `SA_RESTART` when registering a signal for os/signal
- `runtime`: implement race-free signals using futexes
- `runtime`: run deferred functions in `Goexit`
- `runtime`: remove `Cond` which seems to be unused
- `runtime`: properly handle unix read on directory
- `runtime/trace`: stub all public methods
- `sync`: don't use volatile in `Mutex`
- `sync`: implement `WaitGroup` using a (pseudo)futex
- `sync`: make `Cond` parallelism-safe
- `syscall`: use wasi-libc tables for wasm/js target
* **targets**
- `mips`: fix a bug when scanning the stack
- `nintendoswitch`: get this target to compile again
- `rp2350`: add support for the new RP2350
- `rp2040/rp2350` : make I2C implementation shared for rp2040/rp2350
- `rp2040/rp2350` : make SPI implementation shared for rp2040/rp2350
- `rp2040/rp2350` : make RNG implementation shared for rp2040/rp2350
- `wasm`: revise and simplify wasmtime argument handling
- `wasm`: support `//go:wasmexport` functions after a call to `time.Sleep`
- `wasm`: correctly return from run() in wasm_exec.js
- `wasm`: call process.exit() when go.run() returns
- `windows`: don't return, exit via exit(0) instead to flush stdout buffer
* **boards**
- add support for the Tillitis TKey
- add support for the Raspberry Pi Pico2 (based on the RP2040)
- add support for Pimoroni Tiny2350
0.34.0
---
* **general**
+3 -3
View File
@@ -1,8 +1,8 @@
# tinygo-llvm stage obtains the llvm source for TinyGo
FROM golang:1.23 AS tinygo-llvm
FROM golang:1.27rc2 AS tinygo-llvm
RUN apt-get update && \
apt-get install -y apt-utils make cmake clang-15 ninja-build && \
apt-get install -y apt-utils make cmake clang-17 ninja-build && \
rm -rf \
/var/lib/apt/lists/* \
/var/log/* \
@@ -33,7 +33,7 @@ RUN cd /tinygo/ && \
# tinygo-compiler copies the compiler build over to a base Go container (without
# all the build tools etc).
FROM golang:1.23 AS tinygo-compiler
FROM golang:1.27rc2 AS tinygo-compiler
# Copy tinygo build.
COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo
+350 -97
View File
@@ -8,13 +8,20 @@ LLVM_PROJECTDIR ?= llvm-project
CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
ifeq ($(OS),Windows_NT)
# avoid calling uname on Windows
uname := Windows_NT
else
uname := $(shell uname -s)
endif
# Try to autodetect LLVM build tools.
# Versions are listed here in descending priority order.
LLVM_VERSIONS = 18 17 16 15
LLVM_VERSIONS = 19 18 17 16 15
errifempty = $(if $(1),$(1),$(error $(2)))
detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2)))
toolSearchPathsVersion = $(1)-$(2)
ifeq ($(shell uname -s),Darwin)
ifeq ($(uname),Darwin)
# Also explicitly search Brew's copy, which is not in PATH by default.
BREW_PREFIX := $(shell brew --prefix)
toolSearchPathsVersion += $(BREW_PREFIX)/opt/llvm@$(2)/bin/$(1)-$(2) $(BREW_PREFIX)/opt/llvm@$(2)/bin/$(1)
@@ -27,7 +34,6 @@ LLVM_NM ?= $(call findLLVMTool,llvm-nm)
# Go binary and GOROOT to select
GO ?= go
export GOROOT = $(shell $(GO) env GOROOT)
# Flags to pass to go test.
GOTESTFLAGS ?=
@@ -38,14 +44,10 @@ TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo)
# Check for ccache if the user hasn't set it to on or off.
ifeq (, $(CCACHE))
# Use CCACHE for LLVM if possible
ifneq (, $(shell command -v ccache 2> /dev/null))
CCACHE := ON
else
CCACHE := OFF
endif
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=$(if $(shell command -v ccache 2> /dev/null),ON,OFF)'
else
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=$(CCACHE)'
endif
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=$(CCACHE)'
# Allow enabling LLVM assertions
ifeq (1, $(ASSERT))
@@ -77,6 +79,26 @@ ifeq (1, $(STATIC))
BINARYEN_OPTION += -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static"
endif
# Optimize the binary size for Linux.
# These flags may work on other platforms, but have only been tested on Linux.
ifeq ($(uname),Linux)
HAS_MOLD := $(shell command -v ld.mold 2> /dev/null)
HAS_LLD := $(shell command -v ld.lld 2> /dev/null)
LLVM_CFLAGS := -ffunction-sections -fdata-sections -fvisibility=hidden
LLVM_LDFLAGS := -Wl,--gc-sections
ifneq ($(HAS_MOLD),)
# Mold might be slightly faster.
LLVM_LDFLAGS += -fuse-ld=mold -Wl,--icf=all
else ifneq ($(HAS_LLD),)
# LLD is more commonly available.
LLVM_LDFLAGS += -fuse-ld=lld -Wl,--icf=all
endif
LLVM_OPTION += \
-DCMAKE_C_FLAGS="$(LLVM_CFLAGS)" \
-DCMAKE_CXX_FLAGS="$(LLVM_CFLAGS)"
CGO_LDFLAGS += $(LLVM_LDFLAGS)
endif
# Cross compiling support.
ifneq ($(CROSS),)
CC = $(CROSS)-gcc
@@ -111,7 +133,7 @@ endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontenddriver frontendhlsl frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsdriver windowsmanifest
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb dtlto executionengine frontenddriver frontendhlsl frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsdriver windowsmanifest
ifeq ($(OS),Windows_NT)
EXE = .exe
@@ -120,6 +142,9 @@ ifeq ($(OS),Windows_NT)
# PIC needs to be disabled for libclang to work.
LLVM_OPTION += -DLLVM_ENABLE_PIC=OFF
# Statically link the C++ and GCC runtime into LLVM tools so they don't
# depend on MinGW DLLs that may not be on PATH when executed during the build.
LLVM_OPTION += '-DCMAKE_EXE_LINKER_FLAGS=-static-libgcc -static-libstdc++'
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
@@ -127,14 +152,14 @@ ifeq ($(OS),Windows_NT)
USE_SYSTEM_BINARYEN ?= 1
else ifeq ($(shell uname -s),Darwin)
else ifeq ($(uname),Darwin)
MD5SUM ?= md5
CGO_LDFLAGS += -lxar
USE_SYSTEM_BINARYEN ?= 1
else ifeq ($(shell uname -s),FreeBSD)
else ifeq ($(uname),FreeBSD)
MD5SUM ?= md5
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
@@ -147,7 +172,7 @@ endif
MD5SUM ?= md5sum
# Libraries that should be linked in for the statically linked Clang.
CLANG_LIB_NAMES = clangAnalysis clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIB_NAMES = clangAnalysis clangAnalysisLifetimeSafety clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangInstallAPI clangLex clangOptions clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
# Libraries that should be linked in for the statically linked LLD.
@@ -155,7 +180,7 @@ LLD_LIB_NAMES = lldCOFF lldCommon lldELF lldMachO lldMinGW lldWasm
LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP)
# Other libraries that are needed to link TinyGo.
EXTRA_LIB_NAMES = LLVMInterpreter LLVMMCA LLVMRISCVTargetMCA LLVMX86TargetMCA
EXTRA_LIB_NAMES = LLVMDTLTO LLVMInterpreter LLVMMCA LLVMRISCVTargetMCA LLVMX86TargetMCA
# All libraries to be built and linked with the tinygo binary (lib/lib*.a).
LIB_NAMES = clang $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)
@@ -172,6 +197,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
@@ -185,7 +216,10 @@ fmt-check: ## Warn if any source needs reformatting
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp gen-device-renesas ## Generate microcontroller-specific sources
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp ## Generate microcontroller-specific sources
ifneq ($(RENESAS), 0)
gen-device: gen-device-renesas
endif
ifneq ($(STM32), 0)
gen-device: gen-device-stm32
endif
@@ -238,7 +272,7 @@ gen-device-renesas: build/gen-device-svd
GO111MODULE=off $(GO) fmt ./src/device/renesas
$(LLVM_PROJECTDIR)/llvm:
git clone -b tinygo_xtensa_release_18.1.2 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
git clone -b tinygo_22.x --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/llvm ## Get LLVM sources
# Configure LLVM.
@@ -259,41 +293,35 @@ build/wasm-opt$(EXE):
cp lib/binaryen/bin/wasm-opt$(EXE) build/wasm-opt$(EXE)
endif
# Build wasi-libc sysroot
.PHONY: wasi-libc
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC="$(CLANG)" AR=$(LLVM_AR) NM=$(LLVM_NM)
# Generate WASI syscall bindings
WASM_TOOLS_MODULE=github.com/bytecodealliance/wasm-tools-go
WASM_TOOLS_MODULE=go.bytecodealliance.org
.PHONY: wasi-syscall
wasi-syscall: wasi-cm
go run -modfile ./internal/wasm-tools/go.mod $(WASM_TOOLS_MODULE)/cmd/wit-bindgen-go generate --versioned -o ./src/internal -p internal --cm internal/cm ./lib/wasi-cli/wit
rm -rf ./src/internal/wasi/*
go run $(WASM_TOOLS_MODULE)/cmd/wit-bindgen-go generate --versioned -o ./src/internal -p internal --cm internal/cm ./lib/wasi-cli/wit
# Copy package cm into src/internal/cm
.PHONY: wasi-cm
wasi-cm:
# rm -rf ./src/internal/cm
rsync -rv --delete --exclude '*_test.go' $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE))/cm ./src/internal/
rm -rf ./src/internal/cm/*
rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*_json.go' --exclude '*.md' --exclude LICENSE $(shell go list -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm
# Check for Node.js used during WASM tests.
NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-)))
MIN_NODEJS_VERSION=18
MIN_NODEJS_VERSION=22
.PHONY: check-nodejs-version
check-nodejs-version:
ifeq (, $(shell which node))
@echo "Install NodeJS version 18+ to run tests."; exit 1;
endif
@if [ $(NODEJS_VERSION) -lt $(MIN_NODEJS_VERSION) ]; then echo "Install NodeJS version 18+ to run tests."; exit 1; fi
@# Check whether NodeJS is available.
@if ! command -v node 2>&1 >/dev/null; then echo "Install NodeJS version ${MIN_NODEJS_VERSION}+ to run tests."; exit 1; fi
@# Check whether the version is high enough.
@if [ "`node -v | sed 's/v\([0-9]\+\).*/\\1/g'`" -lt $(MIN_NODEJS_VERSION) ]; then echo "Install NodeJS version $(MIN_NODEJS_VERSION)+ to run tests."; exit 1; fi
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" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" .
test: wasi-libc 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)
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 llvm22 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 llvm22 osusergo" $(GOTESTPKGS)
# Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi
TEST_PACKAGES_SLOW = \
@@ -309,11 +337,9 @@ TEST_PACKAGES_FAST = \
container/heap \
container/list \
container/ring \
crypto/des \
crypto/ecdsa \
crypto/elliptic \
crypto/md5 \
crypto/rc4 \
crypto/sha1 \
crypto/sha256 \
crypto/sha512 \
@@ -322,14 +348,17 @@ TEST_PACKAGES_FAST = \
embed/internal/embedtest \
encoding \
encoding/ascii85 \
errors \
encoding/asn1 \
encoding/base32 \
encoding/base64 \
encoding/csv \
encoding/hex \
expvar \
go/ast \
go/format \
go/scanner \
go/token \
go/version \
hash \
hash/adler32 \
@@ -342,6 +371,7 @@ TEST_PACKAGES_FAST = \
math/cmplx \
net/http/internal/ascii \
net/mail \
net/url \
os \
path \
reflect \
@@ -355,40 +385,39 @@ TEST_PACKAGES_FAST = \
unique \
$(nil)
# Assume this will go away before Go2, so only check minor version.
ifeq ($(filter $(shell $(GO) env GOVERSION | cut -f 2 -d.), 16 17 18), )
TEST_PACKAGES_FAST += crypto/internal/nistec/fiat
else
TEST_PACKAGES_FAST += crypto/elliptic/internal/fiat
endif
# 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
# image requires recover(), which is not yet supported on wasi
# encoding/xml takes a minute on linux and gives a stack overflow 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
TEST_PACKAGES_LINUX := \
archive/zip \
compress/flate \
context \
crypto/aes \
crypto/des \
crypto/ecdh \
crypto/hmac \
debug/dwarf \
debug/plan9obj \
encoding/xml \
image \
io/ioutil \
mime \
@@ -400,22 +429,64 @@ TEST_PACKAGES_LINUX := \
os/user \
regexp/syntax \
strconv \
testing/fstest \
text/tabwriter \
text/template/parse
TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX)
# os/user requires t.Skip() support
TEST_PACKAGES_WINDOWS := \
compress/flate \
crypto/des \
crypto/hmac \
os/user \
image \
mime \
regexp/syntax \
strconv \
text/tabwriter \
text/template/parse \
$(nil)
# These packages cannot be tested on wasm, mostly because these tests assume a
# working filesystem. This could perhaps be fixed, by supporting filesystem
# access when running inside Node.js.
TEST_PACKAGES_WASM = $(filter-out $(TEST_PACKAGES_NONWASM), $(TEST_PACKAGES_FAST))
TEST_PACKAGES_NONWASM = \
compress/lzw \
compress/zlib \
crypto/ecdsa \
debug/macho \
embed/internal/embedtest \
expvar \
go/format \
os \
testing \
$(nil)
# These packages cannot be tested on baremetal.
#
# Some reasons why the tests don't pass on baremetal:
#
# * No filesystem is available, so packages like compress/zlib can't be tested
# (just like wasm).
# * picolibc math functions apparently are less precise, the math package
# fails on baremetal.
TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST))
TEST_PACKAGES_NONBAREMETAL = \
$(TEST_PACKAGES_NONWASM) \
math \
$(nil)
TEST_PACKAGES_FAST_WASI = $(filter-out $(TEST_PACKAGES_NOWASI), $(TEST_PACKAGES_FAST))
TEST_PACKAGES_NOWASI = \
crypto/ecdsa \
$(nil)
# Report platforms on which each standard library package is known to pass tests
jointmp := $(shell echo /tmp/join.$$$$)
report-stdlib-tests-pass:
$(eval jointmp := $(shell echo /tmp/join.$$$$))
@for t in $(TEST_PACKAGES_DARWIN); do echo "$$t darwin"; done | sort > $(jointmp).darwin
@for t in $(TEST_PACKAGES_LINUX); do echo "$$t linux"; done | sort > $(jointmp).linux
@for t in $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW); do echo "$$t darwin linux wasi windows"; done | sort > $(jointmp).portable
@@ -424,24 +495,34 @@ report-stdlib-tests-pass:
@rm $(jointmp).*
# Standard library packages that pass tests quickly on the current platform
ifeq ($(shell uname),Darwin)
ifeq ($(uname),Darwin)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_DARWIN)
TEST_IOFS := true
TEST_ENCODING_XML := true
endif
ifeq ($(shell uname),Linux)
ifeq ($(uname),Linux)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_LINUX)
TEST_IOFS := true
TEST_ENCODING_XML := true
endif
ifeq ($(OS),Windows_NT)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
TEST_IOFS := false
endif
TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic|TestAsValidation'
TEST_ADDITIONAL_FLAGS ?=
# Test known-working standard library packages.
# TODO: parallelize, and only show failing tests (no implied -v flag).
.PHONY: tinygo-test
tinygo-test:
$(TINYGO) test $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
@# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented.
@# TestParseAndBytesRoundTrip/P256/Generic: needs Goexit to run defers on wasm.
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(filter-out encoding/xml,$(TEST_PACKAGES_HOST)) $(TEST_PACKAGES_SLOW)
ifeq ($(TEST_ENCODING_XML),true)
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) -stack-size=16MB encoding/xml
endif
@# io/fs requires os.ReadDir, not yet supported on windows or wasi. It also
@# requires a large stack-size. Hence, io/fs is only run conditionally.
@# For more details, see the comments on issue #3143.
@@ -449,24 +530,26 @@ ifeq ($(TEST_IOFS),true)
$(TINYGO) test -stack-size=6MB io/fs
endif
tinygo-test-fast:
$(TINYGO) test $(TEST_PACKAGES_HOST)
$(TINYGO) test $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST)
tinygo-bench:
$(TINYGO) test -bench . $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
tinygo-bench-fast:
$(TINYGO) test -bench . $(TEST_PACKAGES_HOST)
# Same thing, except for wasi rather than the current platform.
tinygo-test-wasm:
$(TINYGO) test -target wasm $(TEST_SKIP_FLAG) $(TEST_PACKAGES_WASM)
tinygo-test-wasi:
$(TINYGO) test -target wasip1 $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
$(TINYGO) test -target wasip1 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
tinygo-test-wasip1:
GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
tinygo-test-wasip1-fast:
$(TINYGO) test -target=wasip1 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi
$(TINYGO) test -target=wasip1 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST_WASI) ./tests/runtime_wasi
tinygo-test-wasip2-slow:
$(TINYGO) test -target=wasip2 $(TEST_PACKAGES_SLOW)
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_SLOW)
tinygo-test-wasip2-fast:
$(TINYGO) test -target=wasip2 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST_WASI) ./tests/runtime_wasi
tinygo-test-wasip2-sum-slow:
TINYGO=$(TINYGO) \
@@ -490,18 +573,19 @@ tinygo-bench-wasip2:
tinygo-bench-wasip2-fast:
$(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST)
# Run tests on riscv-qemu since that one provides a large amount of memory.
tinygo-test-baremetal:
$(TINYGO) test -target riscv-qemu $(TEST_SKIP_FLAG) $(TEST_PACKAGES_BAREMETAL)
# Test external packages in a large corpus.
test-corpus:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm llvm22" -run TestCorpus . -corpus=testdata/corpus.yaml
test-corpus-fast:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus -short . -corpus=testdata/corpus.yaml
test-corpus-wasi: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip1
tinygo-baremetal:
# Regression tests that run on a baremetal target and don't fit in either main_test.go or smoketest.
# regression test for #2666: e.g. encoding/hex must pass on baremetal
$(TINYGO) test -target cortex-m-qemu encoding/hex
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm llvm22" -run TestCorpus -short . -corpus=testdata/corpus.yaml
test-corpus-wasi:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm llvm22" -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip1
test-corpus-wasip2:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm llvm22" -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip2
.PHONY: testchdir
testchdir:
@@ -523,6 +607,8 @@ smoketest: testchdir
# regression test for #2563
cd tests/os/smoke && $(TINYGO) test -c -target=pybadge && rm smoke.test
# test all examples (except pwm)
$(TINYGO) build -size short -o test.hex -target=pga2350 examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
@@ -569,23 +655,31 @@ smoketest: testchdir
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/device-id
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2-ice examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o test.efi -target=uefi-amd64 examples/test
@$(MD5SUM) test.efi
# test simulated boards on play.tinygo.org
ifneq ($(WASM), 0)
$(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=arduino_uno examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=microbit examples/microbit-blink
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=microbit examples/microbit-blink
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/machinetest
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/machinetest
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1
@$(MD5SUM) test.wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=pico examples/blinky1
@$(MD5SUM) test.wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=xiao_esp32s3 examples/blinky1
@$(MD5SUM) test.wasm
endif
# test all targets/boards
@@ -599,8 +693,12 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s140v7 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=btt-skr-pico examples/uart
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
@@ -671,6 +769,8 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao-ble-plus examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=rak4631 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac
@@ -707,6 +807,10 @@ 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=pico-w 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
@@ -737,10 +841,32 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=gopher-badge examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=gopher-arcade examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=ae-rp2040 examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=thumby examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2-w examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=badger2350 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=blinky2350 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico-plus2 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=metro-rp2350 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-tiny examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=vicharak_shrike-lite examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao-rp2350 examples/blinky1
@$(MD5SUM) test.hex
# test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex
@@ -755,6 +881,10 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/usb-midi
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico examples/usb-storage
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2 examples/usb-storage
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-s140v6-uf2-generic examples/machinetest
@$(MD5SUM) test.hex
ifneq ($(STM32), 0)
@@ -768,6 +898,8 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-h753zi examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
@@ -794,18 +926,28 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=mksnanov3 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32l0x1 examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32u031 examples/empty
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/blinkm
@$(MD5SUM) test.hex
endif
$(TINYGO) build -size short -o test.hex -target=atmega328pb examples/blinkm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/machinetest
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
$(TINYGO) build -size short -o test.hex -target=arduino-uno examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-leonardo examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/pwm
$(TINYGO) build -size short -o test.hex -target=arduino-uno examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
$(TINYGO) build -size short -o test.hex -target=arduino-uno -scheduler=tasks examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/blinky1
@$(MD5SUM) test.hex
@@ -817,24 +959,79 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/mcp3008
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
@$(MD5SUM) test.hex
ifneq ($(XTENSA), 0)
$(TINYGO) build -size short -o test.bin -target=esp32-generic examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32c3-generic examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-generic examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinkm
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stack examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stamp-s3a examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stick-c examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5paper examples/machinetest
@$(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
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinkm
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/mcp3008
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/pwm
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/adc
@$(MD5SUM) test.bin
# esp32s3-supermini
$(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/blinkm
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/mcp3008
@$(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
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinkm
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/mcp3008
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/pwm
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/adc
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/machinetest
@@ -847,12 +1044,23 @@ endif
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32c3-12f examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=makerfabs-esp32c3spi35 examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=tkey examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=elecrow-rp2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=elecrow-rp2350 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=hw-651 examples/machinetest
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=hw-651-s110v8 examples/machinetest
@$(MD5SUM) test.hex
ifneq ($(WASM), 0)
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main
@@ -867,7 +1075,7 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=rtt examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
$(TINYGO) build -o test.nro -target=nintendoswitch examples/echo2
@$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex
@@ -885,15 +1093,17 @@ endif
wasmtest:
$(GO) test ./tests/wasm
cd ./tests/wasm && $(GO) test .
build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen)
build/release: tinygo gen-device $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen)
@mkdir -p build/release/tinygo/bin
@mkdir -p build/release/tinygo/lib/bdwgc
@mkdir -p build/release/tinygo/lib/clang/include
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/macos-minimal-sdk
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/crt
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/math
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
@mkdir -p build/release/tinygo/lib/musl/arch
@mkdir -p build/release/tinygo/lib/musl/crt
@@ -901,7 +1111,8 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN
@mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libm
@mkdir -p build/release/tinygo/lib/wasi-libc/libc-bottom-half/headers
@mkdir -p build/release/tinygo/lib/wasi-libc/dlmalloc
@mkdir -p build/release/tinygo/lib/wasi-libc/libc-bottom-half
@mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch
@mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@mkdir -p build/release/tinygo/lib/wasi-cli/
@@ -910,6 +1121,7 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN
ifneq ($(USE_SYSTEM_BINARYEN),1)
@cp -p build/wasm-opt$(EXE) build/release/tinygo/bin
endif
@cp -rp lib/bdwgc/* build/release/tinygo/lib/bdwgc
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
@@ -923,9 +1135,12 @@ endif
@cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt
@cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl
@cp -rp lib/musl/include build/release/tinygo/lib/musl
@cp -rp lib/musl/src/conf build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/ctype build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/fcntl build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/legacy build/release/tinygo/lib/musl/src
@@ -934,19 +1149,31 @@ endif
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/sched build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/stdlib build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/process build/release/tinygo/lib/musl/src
@cp -rp lib/mingw-w64/mingw-w64-crt/crt/pseudo-reloc.c build/release/tinygo/lib/mingw-w64/mingw-w64-crt/crt
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/gdtoa build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/advapi32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/stdio/ucrt_* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/msvcrt.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/math/x86 build/release/tinygo/lib/mingw-w64/mingw-w64-crt/math
@cp -rp lib/mingw-w64/mingw-w64-crt/misc build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/stdio build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers
@cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
@cp -rp lib/mingw-w64/mingw-w64-headers/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
@cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc
@@ -956,18 +1183,40 @@ endif
@cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm
@cp -rp lib/picolibc/newlib/libm/math build/release/tinygo/lib/picolibc/newlib/libm
@cp -rp lib/picolibc-stdio.c build/release/tinygo/lib
@cp -rp lib/wasi-libc/libc-bottom-half/headers/public build/release/tinygo/lib/wasi-libc/libc-bottom-half/headers
@cp -rp lib/wasi-libc/dlmalloc/src build/release/tinygo/lib/wasi-libc/dlmalloc
@cp -rp lib/wasi-libc/libc-bottom-half/cloudlibc build/release/tinygo/lib/wasi-libc/libc-bottom-half
@cp -rp lib/wasi-libc/libc-bottom-half/headers build/release/tinygo/lib/wasi-libc/libc-bottom-half
@cp -rp lib/wasi-libc/libc-bottom-half/sources build/release/tinygo/lib/wasi-libc/libc-bottom-half
@cp -rp lib/wasi-libc/libc-top-half/headers build/release/tinygo/lib/wasi-libc/libc-top-half
@cp -rp lib/wasi-libc/libc-top-half/musl/arch/generic build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch
@cp -rp lib/wasi-libc/libc-top-half/musl/arch/wasm32 build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch
@cp -rp lib/wasi-libc/libc-top-half/musl/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl
@cp -rp lib/wasi-libc/libc-top-half/musl/src/conf build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/dirent build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/env build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/errno build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/exit build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/fcntl build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/fenv build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/internal build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/legacy build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/locale build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/math build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/misc build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/multibyte build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/network build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/stat build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/stdio build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/stdlib build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/string build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
@cp -rp lib/wasi-libc/libc-top-half/musl/src/thread build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/time build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@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
@@ -989,8 +1238,9 @@ endif
.PHONY: tools
tools:
cd internal/tools && go generate -tags tools ./
go generate -tags tools ./
LINTDIRS=src/os/ src/reflect/
.PHONY: lint
lint: tools ## Lint source tree
revive -version
@@ -998,7 +1248,10 @@ lint: tools ## Lint source tree
# revive.toml isn't flexible enough to filter out just one kind of error from a checker, so do it with grep here.
# Can't use grep with friendly formatter. Plain output isn't too bad, though.
# Use 'grep .' to get rid of stray blank line
revive -config revive.toml compiler/... src/{os,reflect}/*.go | grep -v "should have comment or be unexported" | grep '.' | awk '{print}; END {exit NR>0}'
revive -config revive.toml compiler/... $$( find $(LINTDIRS) -type f -name '*.go' ) \
| grep -v "should have comment or be unexported" \
| grep '.' \
| awk '{print}; END {exit NR>0}'
SPELLDIRSCMD=find . -depth 1 -type d | egrep -wv '.git|lib|llvm|src'; find src -depth 1 | egrep -wv 'device|internal|net|vendor'; find src/internal -depth 1 -type d | egrep -wv src/internal/wasi
.PHONY: spell
+3 -2
View File
@@ -1,7 +1,8 @@
Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved.
Copyright (c) 2018-2026 The TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2023 The Go Authors. All rights reserved.
Copyright 2009 The Go Authors. All rights reserved.
See https://github.com/golang/go/blob/master/LICENSE for license information.
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
+17 -12
View File
@@ -1,11 +1,14 @@
# TinyGo - Go compiler for small places
[![Linux](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [![Windows](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [![Docker](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [![Nix](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml) [![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
[![Linux](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [![Windows](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [![Docker](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [![Nix](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (wasm/wasi), and command-line tools.
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
> [!IMPORTANT]
> You can help TinyGo with a financial contribution using OpenCollective. Please see https://opencollective.com/tinygo for more information. Thank you!
## Embedded
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
@@ -31,10 +34,10 @@ func main() {
}
```
The above program can be compiled and run without modification on an Arduino Uno, an Adafruit ItsyBitsy M0, or any of the supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
The above program can be compiled and run without modification on an [Arduino Uno](https://tinygo.org/docs/reference/microcontrollers/boards/arduino-uno), an [Adafruit Circuit Playground Express](https://tinygo.org/docs/reference/microcontrollers/featured/circuitplay-express), a [Seeed Studio XIAO-ESP32S3](https://tinygo.org/docs/reference/microcontrollers/featured/xiao-esp32s3) or any of the many supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
```shell
tinygo flash -target arduino examples/blinky1
tinygo flash -target arduino-uno examples/blinky1
```
## WebAssembly
@@ -48,20 +51,22 @@ Here is a small TinyGo program for use by a WASI host application:
```go
package main
//go:wasm-module yourmodulename
//export add
//go:wasmexport add
func add(x, y uint32) uint32 {
return x + y
}
// main is required for the `wasip1` target, even if it isn't used.
func main() {}
```
This compiles the above TinyGo program for use on any WASI runtime:
This compiles the above TinyGo program for use on any WASI Preview 1 runtime:
```shell
tinygo build -o main.wasm -target=wasip1 main.go
tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.go
```
You can also use the same syntax as Go 1.24+:
```shell
GOOS=wasip1 GOARCH=wasm tinygo build -buildmode=c-shared -o add.wasm add.go
```
## Installation
@@ -72,7 +77,7 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
### Embedded
You can compile TinyGo programs for over 94 different microcontroller boards.
You can compile TinyGo programs for over 150 different microcontroller boards.
For more information, please see https://tinygo.org/docs/reference/microcontrollers/
@@ -137,7 +142,7 @@ Non-goals:
## Why this project exists
> We never expected Go to be an embedded language and so its got serious problems...
> We never expected Go to be an embedded language, and so its got serious problems...
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
+15
View File
@@ -3,6 +3,7 @@ package builder
import (
"bytes"
"debug/elf"
"debug/macho"
"debug/pe"
"encoding/binary"
"errors"
@@ -62,6 +63,20 @@ func makeArchive(arfile *os.File, objs []string) error {
fileIndex int
}{symbol.Name, i})
}
} else if dbg, err := macho.NewFile(objfile); err == nil {
for _, symbol := range dbg.Symtab.Syms {
// See mach-o/nlist.h
if symbol.Type&0x0e != 0xe { // (symbol.Type & N_TYPE) != N_SECT
continue // undefined symbol
}
if symbol.Type&0x01 == 0 { // (symbol.Type & N_EXT) == 0
continue // internal symbol (static, etc)
}
symbolTable = append(symbolTable, struct {
name string
fileIndex int
}{symbol.Name, i})
}
} else if dbg, err := pe.NewFile(objfile); err == nil {
for _, symbol := range dbg.Symbols {
if symbol.StorageClass != 2 {
+91
View File
@@ -0,0 +1,91 @@
package builder
// The well-known conservative Boehm-Demers-Weiser GC.
// This file provides a way to compile this GC for use with TinyGo.
import (
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
var BoehmGC = Library{
name: "bdwgc",
cflags: func(target, headerPath string) []string {
libdir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc")
flags := []string{
// use a modern environment
"-DUSE_MMAP", // mmap is available
"-DUSE_MUNMAP", // return memory to the OS using munmap
"-DGC_BUILTIN_ATOMIC", // use compiler intrinsics for atomic operations
"-DNO_EXECUTE_PERMISSION", // don't make the heap executable
// specific flags for TinyGo
"-DALL_INTERIOR_POINTERS", // scan interior pointers (needed for Go)
"-DIGNORE_DYNAMIC_LOADING", // we don't support dynamic loading at the moment
"-DNO_GETCONTEXT", // musl doesn't support getcontext()
"-DGC_DISABLE_INCREMENTAL", // don't mess with SIGSEGV and such
// Use a minimal environment.
"-DNO_MSGBOX_ON_ERROR", // don't call MessageBoxA on Windows
"-DDONT_USE_ATEXIT",
"-DNO_GETENV", // smaller binary, more predictable configuration
"-DNO_CLOCK", // don't use system clock
"-DNO_DEBUGGING", // reduce code size
"-DGC_NO_FINALIZATION", // finalization is not used at the moment
// Special flag to work around the lack of __data_start in ld.lld.
// TODO: try to fix this in LLVM/lld directly so we don't have to
// work around it anymore.
"-DGC_DONT_REGISTER_MAIN_STATIC_DATA",
// Do not scan the stack. We have our own mechanism to do this.
"-DSTACK_NOT_SCANNED",
"-DNO_PROC_STAT", // we scan the stack manually (don't read /proc/self/stat on Linux)
"-DSTACKBOTTOM=0", // dummy value, we scan the stack manually
// Assertions can be enabled while debugging GC issues.
//"-DGC_ASSERTIONS",
// We use our own way of dealing with threads (that is a bit hacky).
// See src/runtime/gc_boehm.go.
//"-DGC_THREADS",
//"-DTHREAD_LOCAL_ALLOC",
"-I" + libdir + "/include",
}
return flags
},
needsLibc: true,
sourceDir: func() string {
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc")
},
librarySources: func(target string, _ bool) ([]string, error) {
sources := []string{
"allchblk.c",
"alloc.c",
"blacklst.c",
"dbg_mlc.c",
"dyn_load.c",
"headers.c",
"mach_dep.c",
"malloc.c",
"mark.c",
"mark_rts.c",
"misc.c",
"new_hblk.c",
"os_dep.c",
"reclaim.c",
}
if strings.Split(target, "-")[2] == "windows" {
// Due to how the linker on Windows works (that doesn't allow
// undefined functions), we need to include these extra files.
sources = append(sources,
"mallocx.c",
"ptr_chck.c",
)
}
return sources, nil
},
}
+199 -81
View File
@@ -14,12 +14,13 @@ import (
"fmt"
"go/types"
"hash/crc32"
"io/fs"
"maps"
"math/bits"
"os"
"os/exec"
"path/filepath"
"runtime"
"slices"
"sort"
"strconv"
"strings"
@@ -61,6 +62,10 @@ type BuildResult struct {
// correctly printing test results: the import path isn't always the same as
// the path listed on the command line.
ImportPath string
// Map from path to package name. It is needed to attribute binary size to
// the right Go package.
PackagePathMap map[string]string
}
// packageAction is the struct that is serialized to JSON and hashed, to work as
@@ -133,9 +138,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if _, ok := globalValues[pkgPath]; !ok {
globalValues[pkgPath] = map[string]string{}
}
for k, v := range vals {
globalValues[pkgPath][k] = v
}
maps.Copy(globalValues[pkgPath], vals)
}
// Check for a libc dependency.
@@ -145,16 +148,17 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
var libcDependencies []*compileJob
switch config.Target.Libc {
case "darwin-libSystem":
job := makeDarwinLibSystemJob(config, tmpdir)
libcDependencies = append(libcDependencies, job)
libcJob := makeDarwinLibSystemJob(config, tmpdir)
libcDependencies = append(libcDependencies, libcJob)
case "musl":
job, unlock, err := libMusl.load(config, tmpdir)
var unlock func()
libcJob, unlock, err := libMusl.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
}
defer unlock()
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o")))
libcDependencies = append(libcDependencies, job)
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(libcJob.result), "crt1.o")))
libcDependencies = append(libcDependencies, libcJob)
case "picolibc":
libcJob, unlock, err := libPicolibc.load(config, tmpdir)
if err != nil {
@@ -163,11 +167,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
defer unlock()
libcDependencies = append(libcDependencies, libcJob)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
return BuildResult{}, errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
libcJob, unlock, err := libWasiLibc.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
}
libcDependencies = append(libcDependencies, dummyCompileJob(path))
defer unlock()
libcDependencies = append(libcDependencies, libcJob)
case "wasmbuiltins":
libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir)
if err != nil {
@@ -176,12 +181,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
defer unlock()
libcDependencies = append(libcDependencies, libcJob)
case "mingw-w64":
job, unlock, err := libMinGW.load(config, tmpdir)
libcJob, unlock, err := libMinGW.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
}
defer unlock()
libcDependencies = append(libcDependencies, job)
libcDependencies = append(libcDependencies, libcJob)
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir, config.GOARCH())...)
case "":
// no library specified, so nothing to do
@@ -209,6 +214,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
MaxStackAlloc: config.MaxStackAlloc(),
NeedsStackObjects: config.NeedsStackObjects(),
Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed
Nobounds: config.Options.Nobounds,
PanicStrategy: config.PanicStrategy(),
}
@@ -242,6 +248,24 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return result, err
}
// Store which filesystem paths map to which package name.
result.PackagePathMap = make(map[string]string, len(lprogram.Packages))
for _, pkg := range lprogram.Sorted() {
result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
}
// Strip default initializers for -X globals from the type info before
// building SSA. This prevents go/ssa from emitting init stores for them,
// so that makeGlobalsModule can supply the correct values at final link
// time without any runtime init overwriting them. The -X values themselves
// are kept out of the per-package build cache; only the variable names
// appear in the cache key.
for _, pkg := range lprogram.Sorted() {
for name := range globalValues[pkg.Pkg.Path()] {
pkg.StripVarInitializer(name)
}
}
// Create the *ssa.Program. This does not yet build the entire SSA of the
// program so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA()
@@ -253,7 +277,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
var embedFileObjects []*compileJob
for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition
var undefinedGlobals []string
for name := range globalValues[pkg.Pkg.Path()] {
@@ -269,9 +292,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
allFiles[file.Name] = append(allFiles[file.Name], file)
}
}
for name, files := range allFiles {
name := name
files := files
// Sort embedded files by name to maintain output determinism.
embedNames := make([]string, 0, len(allFiles))
for _, files := range allFiles {
embedNames = append(embedNames, files[0].Name)
}
slices.Sort(embedNames)
for _, name := range embedNames {
job := &compileJob{
description: "make object file for " + name,
run: func(job *compileJob) error {
@@ -286,7 +313,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
sum := sha256.Sum256(data)
hexSum := hex.EncodeToString(sum[:16])
for _, file := range files {
for _, file := range allFiles[name] {
file.Size = uint64(len(data))
file.Hash = hexSum
if file.NeedsData {
@@ -439,8 +466,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if global.IsNil() {
return errors.New("global not found: " + globalName)
}
globalType := global.GlobalValueType()
if globalType.TypeKind() != llvm.StructTypeKind || globalType.StructName() != "runtime._string" {
// Verify this is indeed a string. This is needed so
// that makeGlobalsModule can just create the right
// globals of string type without checking.
return fmt.Errorf("%s: not a string", globalName)
}
name := global.Name()
newGlobal := llvm.AddGlobal(mod, global.GlobalValueType(), name+".tmp")
newGlobal := llvm.AddGlobal(mod, globalType, name+".tmp")
global.ReplaceAllUsesWith(newGlobal)
global.EraseFromParentAsGlobal()
newGlobal.SetName(name)
@@ -453,7 +487,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if pkgInit.IsNil() {
panic("init not found for " + pkg.Pkg.Path())
}
err := interp.RunFunc(pkgInit, config.Options.InterpTimeout, config.DumpSSA())
err := interp.RunFunc(pkgInit, config.Options.InterpTimeout, config.Options.InterpMaxLoopIterations, config.DumpSSA())
if err != nil {
return err
}
@@ -522,12 +556,38 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil {
return fmt.Errorf("failed to load bitcode file: %w", err)
}
// Resolve duplicate function definitions before linking.
// This can happen when a newer Go version adds a function
// body in a standard library package that was previously
// just a declaration provided by //go:linkname from the
// runtime. In that case, keep the existing (runtime)
// definition by weakening the new one's linkage so the
// LLVM linker discards it in favor of the existing one.
for fn := pkgMod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if fn.IsDeclaration() {
continue
}
existing := mod.NamedFunction(fn.Name())
if existing.IsNil() || existing.IsDeclaration() {
continue
}
fn.SetLinkage(llvm.LinkOnceODRLinkage)
}
err = llvm.LinkModules(mod, pkgMod)
if err != nil {
return fmt.Errorf("failed to link module: %w", err)
}
}
// Insert values from -ldflags="-X ..." into the IR.
// This is a separate module, so that the "runtime._string" type
// doesn't need to match precisely. LLVM tends to rename that type
// sometimes, leading to errors. But linking in a separate module
// works fine. See:
// https://github.com/tinygo-org/tinygo/issues/4810
globalsMod := makeGlobalsModule(ctx, globalValues, machine)
llvm.LinkModules(mod, globalsMod)
// Create runtime.initAll function that calls the runtime
// initializer of each package.
llvmInitFn := mod.NamedFunction("runtime.initAll")
@@ -580,7 +640,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Run all optimization passes, which are much more effective now
// that the optimizer can see the whole program at once.
err := optimizeProgram(mod, config, globalValues)
err := optimizeProgram(mod, config)
if err != nil {
return err
}
@@ -594,6 +654,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
},
}
// Create the output directory, if needed
if err := os.MkdirAll(filepath.Dir(outpath), 0777); err != nil {
return result, err
}
// Check whether we only need to create an object file.
// If so, we don't need to link anything and will be finished quickly.
outext := filepath.Ext(outpath)
@@ -657,6 +722,16 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
ldflags = append(ldflags, "--no-entry")
}
if config.Options.BuildMode == "wasi-legacy" {
if !strings.HasPrefix(config.Triple(), "wasm32-") {
return result, fmt.Errorf("buildmode wasi-legacy is only supported on wasm")
}
if config.Options.Scheduler != "none" {
return result, fmt.Errorf("buildmode wasi-legacy only supports scheduler=none")
}
}
// Add compiler-rt dependency if needed. Usually this is a simple load from
// a cache.
if config.Target.RTLib == "compiler-rt" {
@@ -668,6 +743,16 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
linkerDependencies = append(linkerDependencies, job)
}
// The Boehm collector is stored in a separate C library.
if config.GC() == "boehm" {
job, unlock, err := BoehmGC.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
}
defer unlock()
linkerDependencies = append(linkerDependencies, job)
}
// Add jobs to compile extra files. These files are in C or assembly and
// contain things like the interrupt vector table and low level operations
// such as stack switching.
@@ -688,9 +773,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// TODO: do this as part of building the package to be able to link the
// bitcode files together.
for _, pkg := range lprogram.Sorted() {
pkg := pkg
for _, filename := range pkg.CFiles {
abspath := filepath.Join(pkg.Dir, filename)
abspath := filepath.Join(pkg.OriginalDir(), filename)
job := &compileJob{
description: "compile CGo file " + abspath,
run: func(job *compileJob) error {
@@ -755,22 +839,25 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat
if config.GOOS() == "windows" {
// Options for the MinGW wrapper for the lld COFF linker.
switch config.LinkerFlavor() {
case "coff":
// Options for driving ld.lld in PE/COFF mode.
ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
} else if config.GOOS() == "darwin" {
case "darwin":
// Options for the ld64-compatible lld linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
} else {
case "gnu":
// Options for the ELF linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
)
default:
return fmt.Errorf("unknown linker flavor: %s", config.LinkerFlavor())
}
if config.CodeModel() != "default" {
ldflags = append(ldflags,
@@ -812,6 +899,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
// Apply patches of bootloader in the order they appear.
if len(config.Target.BootPatches) > 0 {
err = applyPatches(result.Executable, config.Target.BootPatches)
}
if config.RP2040BootPatch() {
// Patch the second stage bootloader CRC into the .boot2 section
err = patchRP2040BootCRC(result.Executable)
@@ -821,7 +914,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
// Run wasm-opt for wasm binaries
if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" {
if arch, _, _ := strings.Cut(config.Triple(), "-"); arch == "wasm32" {
optLevel, _, _ := config.OptLevel()
opt := "-" + optLevel
@@ -915,19 +1008,16 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
// Print code size if requested.
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
packagePathMap := make(map[string]string, len(lprogram.Packages))
for _, pkg := range lprogram.Sorted() {
packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
}
sizes, err := loadProgramSize(result.Executable, packagePathMap)
if config.Options.PrintSizes != "" {
sizes, err := loadProgramSize(result.Executable, result.PackagePathMap)
if err != nil {
return err
}
if config.Options.PrintSizes == "short" {
switch config.Options.PrintSizes {
case "short":
fmt.Printf(" code data bss | flash ram\n")
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code+sizes.ROData, sizes.Data, sizes.BSS, sizes.Flash(), sizes.RAM())
} else {
case "full":
if !config.Debug() {
fmt.Println("warning: data incomplete, remove the -no-debug flag for more detail")
}
@@ -939,6 +1029,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
fmt.Printf("------------------------------- | --------------- | -------\n")
fmt.Printf("%7d %7d %7d %7d | %7d %7d | total\n", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS, sizes.Code+sizes.ROData+sizes.Data, sizes.Data+sizes.BSS)
case "html":
const filename = "size-report.html"
err := writeSizeReport(sizes, filename, pkgName)
if err != nil {
return err
}
fmt.Println("Wrote size report to", filename)
}
}
@@ -979,11 +1076,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil {
return result, err
}
case "esp32", "esp32-img", "esp32c3", "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)
err := makeESPFirmareImage(result.Executable, result.Binary, outputBinaryFormat)
err := makeESPFirmwareImage(result.Executable, result.Binary, outputBinaryFormat)
if err != nil {
return result, err
}
@@ -1110,8 +1207,8 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c
// optimizeProgram runs a series of optimizations and transformations that are
// needed to convert a program to its final form. Some transformations are not
// optional and must be run as the compiler expects them to run.
func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues map[string]map[string]string) error {
err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA())
func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
err := interp.Run(mod, config.Options.InterpTimeout, config.Options.InterpMaxLoopIterations, config.DumpSSA())
if err != nil {
return err
}
@@ -1128,12 +1225,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m
}
}
// Insert values from -ldflags="-X ..." into the IR.
err = setGlobalValues(mod, globalValues)
if err != nil {
return err
}
// Run most of the whole-program optimizations (including the whole
// O0/O1/O2/Os/Oz optimization pipeline).
errs := transform.Optimize(mod, config)
@@ -1147,10 +1238,19 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m
return nil
}
// setGlobalValues sets the global values from the -ldflags="-X ..." compiler
// option in the given module. An error may be returned if the global is not of
// the expected type.
func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error {
func makeGlobalsModule(ctx llvm.Context, globals map[string]map[string]string, machine llvm.TargetMachine) llvm.Module {
mod := ctx.NewModule("cmdline-globals")
targetData := machine.CreateTargetData()
defer targetData.Dispose()
mod.SetDataLayout(targetData.String())
stringType := ctx.StructCreateNamed("runtime._string")
uintptrType := ctx.IntType(targetData.PointerSize() * 8)
stringType.StructSetBody([]llvm.Type{
llvm.PointerType(ctx.Int8Type(), 0),
uintptrType,
}, false)
var pkgPaths []string
for pkgPath := range globals {
pkgPaths = append(pkgPaths, pkgPath)
@@ -1166,24 +1266,6 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro
for _, name := range names {
value := pkg[name]
globalName := pkgPath + "." + name
global := mod.NamedGlobal(globalName)
if global.IsNil() || !global.Initializer().IsNil() {
// The global either does not exist (optimized away?) or has
// some value, in which case it has already been initialized at
// package init time.
continue
}
// A strin is a {ptr, len} pair. We need these types to build the
// initializer.
initializerType := global.GlobalValueType()
if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" {
return fmt.Errorf("%s: not a string", globalName)
}
elementTypes := initializerType.StructElementTypes()
if len(elementTypes) != 2 {
return fmt.Errorf("%s: not a string", globalName)
}
// Create a buffer for the string contents.
bufInitializer := mod.Context().ConstString(value, false)
@@ -1194,22 +1276,20 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro
buf.SetLinkage(llvm.PrivateLinkage)
// Create the string value, which is a {ptr, len} pair.
zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false)
ptr := llvm.ConstGEP(bufInitializer.Type(), buf, []llvm.Value{zero, zero})
if ptr.Type() != elementTypes[0] {
return fmt.Errorf("%s: not a string", globalName)
}
length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false)
initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{
ptr,
length := llvm.ConstInt(uintptrType, uint64(len(value)), false)
initializer := llvm.ConstNamedStruct(stringType, []llvm.Value{
buf,
length,
})
// Set the initializer. No initializer should be set at this point.
// Create the string global.
global := llvm.AddGlobal(mod, stringType, globalName)
global.SetInitializer(initializer)
global.SetAlignment(targetData.PrefTypeAlignment(stringType))
}
}
return nil
return mod
}
// functionStackSizes keeps stack size information about a single function
@@ -1272,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
@@ -1320,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,
@@ -1428,6 +1522,23 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS
}
}
func applyPatches(executable string, bootPatches []string) (err error) {
for _, patch := range bootPatches {
switch patch {
case "rp2040":
err = patchRP2040BootCRC(executable)
// case "rp2350":
// err = patchRP2350BootIMAGE_DEF(executable)
default:
err = errors.New("undefined boot patch name")
}
if err != nil {
return fmt.Errorf("apply boot patch %q: %w", patch, err)
}
}
return nil
}
// RP2040 second stage bootloader CRC32 calculation
//
// Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf
@@ -1439,7 +1550,7 @@ func patchRP2040BootCRC(executable string) error {
}
if len(bytes) != 256 {
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes")
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes, got %d", len(bytes))
}
// From the 'official' RP2040 checksum script:
@@ -1478,3 +1589,10 @@ func lock(path string) func() {
return func() { flock.Close() }
}
func b2u8(b bool) uint8 {
if b {
return 1
}
return 0
}
+65 -5
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
@@ -28,11 +29,15 @@ func TestClangAttributes(t *testing.T) {
"cortex-m4",
"cortex-m7",
"esp32c3",
"esp32c6",
"esp32s3",
"fe310",
"gameboy-advance",
"k210",
"nintendoswitch",
"riscv-qemu",
"tkey",
"uefi-amd64",
"wasip1",
"wasip2",
"wasm",
@@ -44,7 +49,6 @@ func TestClangAttributes(t *testing.T) {
targetNames = append(targetNames, "esp32", "esp8266")
}
for _, targetName := range targetNames {
targetName := targetName
t.Run(targetName, func(t *testing.T) {
testClangAttributes(t, &compileopts.Options{Target: targetName})
})
@@ -66,9 +70,9 @@ func TestClangAttributes(t *testing.T) {
{GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"},
{GOOS: "darwin", GOARCH: "amd64"},
{GOOS: "darwin", GOARCH: "arm64"},
{GOOS: "windows", GOARCH: "386"},
{GOOS: "windows", GOARCH: "amd64"},
{GOOS: "windows", GOARCH: "arm64"},
{GOOS: "wasip1", GOARCH: "wasm"},
} {
name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH
if options.GOARCH == "arm" {
@@ -126,8 +130,10 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
defer mod.Dispose()
// Check whether the LLVM target matches.
if mod.Target() != config.Triple() {
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
// Use ClangTriple since LLVM 22 normalizes wasm32-unknown-wasi to wasip1.
expectedTriple := compileopts.ClangTriple(config.Triple())
if mod.Target() != expectedTriple {
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", expectedTriple, mod.Target())
}
// Check the "target-cpu" and "target-features" string attribute of the add
@@ -151,11 +157,65 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
// The reason is that Debian has patched Clang in a way that
// modifies the LLVM features string, changing lots of FPU/float
// related flags. We want to test vanilla Clang, not Debian Clang.
t.Errorf("target has LLVM features\n\t%#v\nbut Clang makes it\n\t%#v", config.Features(), features)
//
// Rather than requiring an exact match (which breaks across LLVM
// versions as new features are added), check that every feature
// TinyGo specifies is consistent with what Clang produces:
// - A "+feature" in TinyGo must appear in Clang's output.
// - A "-feature" in TinyGo must not be "+feature" in Clang's output.
checkFeatureFlags(t, config.Features(), features)
}
}
}
// checkFeatureFlags verifies that all features specified by TinyGo's target
// configuration are consistent with Clang's output. This allows Clang to add
// new features across LLVM versions without breaking the test.
func checkFeatureFlags(t *testing.T, targetFeatures, clangFeatures string) {
t.Helper()
// Build a set of Clang's features for fast lookup.
clangSet := make(map[string]bool) // feature name -> enabled
for _, f := range strings.Split(clangFeatures, ",") {
f = strings.TrimSpace(f)
if len(f) < 2 {
continue
}
enabled := f[0] == '+'
name := f[1:]
clangSet[name] = enabled
}
// Check each feature that TinyGo specifies.
var missing, conflicts []string
for _, f := range strings.Split(targetFeatures, ",") {
f = strings.TrimSpace(f)
if len(f) < 2 {
continue
}
wantEnabled := f[0] == '+'
name := f[1:]
clangEnabled, inClang := clangSet[name]
if wantEnabled && (!inClang || !clangEnabled) {
// TinyGo requires +feature but Clang doesn't enable it.
missing = append(missing, f)
} else if !wantEnabled && inClang && clangEnabled {
// TinyGo requires -feature but Clang enables it.
conflicts = append(conflicts, fmt.Sprintf("target has %q but Clang has %q", f, "+"+name))
}
}
if len(missing) > 0 {
t.Errorf("target specifies features not present in Clang output: %s\n\ttarget features: %s\n\tclang features: %s",
strings.Join(missing, ", "), targetFeatures, clangFeatures)
}
if len(conflicts) > 0 {
t.Errorf("target disables features that Clang enables: %s",
strings.Join(conflicts, "; "))
}
}
// This TestMain is necessary because TinyGo may also be invoked to run certain
// LLVM tools in a separate process. Not capturing these invocations would lead
// to recursive tests.
+38 -2
View File
@@ -3,6 +3,7 @@ package builder
import (
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
@@ -201,6 +202,22 @@ var avrBuiltins = []string{
"avr/udivmodqi4.S",
}
// Builtins needed specifically for windows/386.
var windowsI386Builtins = []string{
"i386/chkstk.S", // __chkstk_ms
"i386/chkstk2.S", // _alloca (__alloca)
}
// Builtins needed specifically for windows/amd64.
var windowsAMD64Builtins = []string{
"x86_64/chkstk.S",
}
// Builtins needed specifically for windows/arm64.
var windowsARM64Builtins = []string{
"aarch64/chkstk.S",
}
// libCompilerRT is a library with symbols required by programs compiled with
// LLVM. These symbols are for operations that cannot be emitted with a single
// instruction or a short sequence of instructions for that target.
@@ -220,16 +237,35 @@ var libCompilerRT = Library{
// Development build.
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/compiler-rt-builtins")
},
librarySources: func(target string) ([]string, error) {
librarySources: func(target string, _ bool) ([]string, error) {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
switch compileopts.CanonicalArchName(target) {
case "arm":
builtins = append(builtins, aeabiBuiltins...)
case "avr":
builtins = append(builtins, avrBuiltins...)
case "x86_64", "aarch64", "riscv64": // any 64-bit arch
case "x86_64":
builtins = append(builtins, genericBuiltins128...)
if isWindowsTriple(target) {
builtins = append(builtins, windowsAMD64Builtins...)
}
case "aarch64":
builtins = append(builtins, genericBuiltins128...)
if isWindowsTriple(target) {
builtins = append(builtins, windowsARM64Builtins...)
}
case "riscv64":
builtins = append(builtins, genericBuiltins128...)
case "i386":
if isWindowsTriple(target) {
builtins = append(builtins, windowsI386Builtins...)
}
}
return builtins, nil
},
}
func isWindowsTriple(target string) bool {
parts := strings.Split(target, "-")
return len(parts) > 2 && parts[2] == "windows"
}
+1 -1
View File
@@ -281,7 +281,7 @@ func parseDepFile(s string) ([]string, error) {
s = strings.ReplaceAll(s, "\\\n", " ")
// Only use the first line, which is expected to begin with "deps:".
line := strings.SplitN(s, "\n", 2)[0]
line, _, _ := strings.Cut(s, "\n")
if !strings.HasPrefix(line, "deps:") {
return nil, errors.New("readDepFile: expected 'deps:' prefix")
}
+40 -35
View File
@@ -23,7 +23,7 @@
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Options.h"
#include "clang/Options/Options.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/Utils.h"
@@ -35,6 +35,7 @@
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCInstPrinter.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCObjectWriter.h"
@@ -67,8 +68,7 @@
#include <optional>
#include <system_error>
using namespace clang;
using namespace clang::driver;
using namespace clang::driver::options;
using namespace clang::options;
using namespace llvm;
using namespace llvm::opt;
@@ -109,7 +109,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
// Construct the invocation.
// Target Options
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
Opts.Triple = llvm::Triple(llvm::Triple::normalize(Args.getLastArgValue(OPT_triple)));
if (Arg *A = Args.getLastArg(options::OPT_darwin_target_variant_triple))
Opts.DarwinTargetVariantTriple = llvm::Triple(A->getValue());
if (Arg *A = Args.getLastArg(OPT_darwin_target_variant_sdk_version_EQ)) {
@@ -125,8 +125,8 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.Features = Args.getAllArgValues(OPT_target_feature);
// Use the default target triple if unspecified.
if (Opts.Triple.empty())
Opts.Triple = llvm::sys::getDefaultTargetTriple();
if (Opts.Triple.getTriple().empty())
Opts.Triple = llvm::Triple(llvm::sys::getDefaultTargetTriple());
// Language Options
Opts.IncludePaths = Args.getAllArgValues(OPT_I);
@@ -144,7 +144,6 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
.Default(llvm::DebugCompressionType::None);
}
Opts.RelaxELFRelocations = !Args.hasArg(OPT_mrelax_relocations_no);
if (auto *DwarfFormatArg = Args.getLastArg(OPT_gdwarf64, OPT_gdwarf32))
Opts.Dwarf64 = DwarfFormatArg->getOption().matches(OPT_gdwarf64);
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
@@ -234,6 +233,10 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.EmitCompactUnwindNonCanonical =
Args.hasArg(OPT_femit_compact_unwind_non_canonical);
Opts.Crel = Args.hasArg(OPT_crel);
Opts.ImplicitMapsyms = Args.hasArg(OPT_mmapsyms_implicit);
Opts.X86RelaxRelocations = !Args.hasArg(OPT_mrelax_relocations_no);
Opts.X86Sse2Avx = Args.hasArg(OPT_msse2avx);
Opts.AsSecureLogFile = Args.getLastArgValue(OPT_as_secure_log_file);
@@ -264,7 +267,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
std::string Error;
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
if (!TheTarget)
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str();
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
@@ -287,8 +290,15 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
assert(MRI && "Unable to create target register info!");
MCTargetOptions MCOptions;
MCOptions.MCRelaxAll = Opts.RelaxAll;
MCOptions.EmitDwarfUnwind = Opts.EmitDwarfUnwind;
MCOptions.EmitCompactUnwindNonCanonical = Opts.EmitCompactUnwindNonCanonical;
MCOptions.MCSaveTempLabels = Opts.SaveTemporaryLabels;
MCOptions.Crel = Opts.Crel;
MCOptions.ImplicitMapSyms = Opts.ImplicitMapsyms;
MCOptions.X86RelaxRelocations = Opts.X86RelaxRelocations;
MCOptions.X86Sse2Avx = Opts.X86Sse2Avx;
MCOptions.CompressDebugSections = Opts.CompressDebugSections;
MCOptions.AsSecureLogFile = Opts.AsSecureLogFile;
std::unique_ptr<MCAsmInfo> MAI(
@@ -297,9 +307,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections
// may be created with a combination of default and explicit settings.
MAI->setCompressDebugSections(Opts.CompressDebugSections);
MAI->setRelaxELFRelocations(Opts.RelaxELFRelocations);
bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj;
if (Opts.OutputPath.empty())
@@ -319,7 +327,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
assert(STI && "Unable to create subtarget info!");
MCContext Ctx(Triple(Opts.Triple), MAI.get(), MRI.get(), STI.get(), &SrcMgr,
MCContext Ctx(Opts.Triple, MAI.get(), MRI.get(), STI.get(), &SrcMgr,
&MCOptions);
bool PIC = false;
@@ -337,14 +345,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
std::unique_ptr<MCObjectFileInfo> MOFI(
TheTarget->createMCObjectFileInfo(Ctx, PIC));
if (Opts.DarwinTargetVariantTriple)
MOFI->setDarwinTargetVariantTriple(*Opts.DarwinTargetVariantTriple);
if (!Opts.DarwinTargetVariantSDKVersion.empty())
MOFI->setDarwinTargetVariantSDKVersion(Opts.DarwinTargetVariantSDKVersion);
Ctx.setObjectFileInfo(MOFI.get());
if (Opts.SaveTemporaryLabels)
Ctx.setAllowTemporaryLabels(false);
if (Opts.GenDwarfForAssembly)
Ctx.setGenDwarfForAssembly(true);
if (!Opts.DwarfDebugFlags.empty())
@@ -381,12 +383,15 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
MCOptions.MCNoWarn = Opts.NoWarn;
MCOptions.MCFatalWarnings = Opts.FatalWarnings;
MCOptions.MCNoTypeCheck = Opts.NoTypeCheck;
MCOptions.ShowMCInst = Opts.ShowInst;
MCOptions.AsmVerbose = true;
MCOptions.MCUseDwarfDirectory = MCTargetOptions::EnableDwarfDirectory;
MCOptions.ABIName = Opts.TargetABI;
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
if (Opts.OutputType == AssemblerInvocation::FT_Asm) {
MCInstPrinter *IP = TheTarget->createMCInstPrinter(
llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI);
std::unique_ptr<MCInstPrinter> IP(TheTarget->createMCInstPrinter(
Opts.Triple, Opts.OutputAsmVariant, *MAI, *MCII, *MRI));
std::unique_ptr<MCCodeEmitter> CE;
if (Opts.ShowEncoding)
@@ -395,10 +400,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
Str.reset(TheTarget->createAsmStreamer(
Ctx, std::move(FOut), /*asmverbose*/ true,
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
Opts.ShowInst));
Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), std::move(IP),
std::move(CE), std::move(MAB)));
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
Str.reset(createNullStreamer(Ctx));
} else {
@@ -419,12 +422,17 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
: MAB->createObjectWriter(*Out);
Triple T(Opts.Triple);
Triple T = Opts.Triple;
Str.reset(TheTarget->createMCObjectStreamer(
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI,
Opts.RelaxAll, Opts.IncrementalLinkerCompatible,
/*DWARFMustBeAtTheEnd*/ true));
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI));
Str.get()->initSections(Opts.NoExecStack, *STI);
if (T.isOSBinFormatMachO() && T.isOSDarwin()) {
Triple *TVT = Opts.DarwinTargetVariantTriple
? &*Opts.DarwinTargetVariantTriple
: nullptr;
Str->emitVersionForTarget(T, VersionTuple(), TVT,
Opts.DarwinTargetVariantSDKVersion);
}
}
// When -fembed-bitcode is passed to clang_as, a 1-byte marker
@@ -436,9 +444,6 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
Str.get()->emitZeros(1);
}
// Assembly to object compilation should leverage assembly info.
Str->setUseAssemblerInfoForParsing(true);
bool Failed = false;
std::unique_ptr<MCAsmParser> Parser(
@@ -448,7 +453,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
std::unique_ptr<MCTargetAsmParser> TAP(
TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
if (!TAP)
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str();
// Set values for symbols, if any.
for (auto &S : Opts.SymbolDefs) {
@@ -501,12 +506,12 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
InitializeAllAsmParsers();
// Construct our diagnostic client.
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
DiagnosticOptions DiagOpts;
TextDiagnosticPrinter *DiagClient
= new TextDiagnosticPrinter(errs(), &*DiagOpts);
= new TextDiagnosticPrinter(errs(), DiagOpts);
DiagClient->setPrefix("clang -cc1as");
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient);
DiagnosticsEngine Diags(DiagID, DiagOpts, DiagClient);
// Set an error handler, so that any LLVM backend diagnostics go through our
// error handler.
@@ -523,7 +528,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler", /*ShowHidden=*/false,
/*ShowAllAliases=*/false,
llvm::opt::Visibility(driver::options::CC1AsOption));
llvm::opt::Visibility(clang::options::CC1AsOption));
return 0;
}
+32 -3
View File
@@ -20,7 +20,7 @@ struct AssemblerInvocation {
/// @{
/// The name of the target triple to assemble for.
std::string Triple;
llvm::Triple Triple;
/// If given, the name of the target CPU to determine which instructions
/// are legal.
@@ -38,10 +38,13 @@ struct AssemblerInvocation {
/// @{
std::vector<std::string> IncludePaths;
LLVM_PREFERRED_TYPE(bool)
unsigned NoInitialTextSection : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned SaveTemporaryLabels : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned GenDwarfForAssembly : 1;
unsigned RelaxELFRelocations : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned Dwarf64 : 1;
unsigned DwarfVersion;
std::string DwarfDebugFlags;
@@ -66,7 +69,9 @@ struct AssemblerInvocation {
FT_Obj ///< Object file output.
};
FileType OutputType;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowHelp : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowVersion : 1;
/// @}
@@ -74,19 +79,28 @@ struct AssemblerInvocation {
/// @{
unsigned OutputAsmVariant;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowEncoding : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowInst : 1;
/// @}
/// @name Assembler Options
/// @{
LLVM_PREFERRED_TYPE(bool)
unsigned RelaxAll : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned NoExecStack : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned FatalWarnings : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned NoWarn : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned NoTypeCheck : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned IncrementalLinkerCompatible : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned EmbedBitcode : 1;
/// Whether to emit DWARF unwind info.
@@ -94,8 +108,19 @@ struct AssemblerInvocation {
// Whether to emit compact-unwind for non-canonical entries.
// Note: maybe overridden by other constraints.
LLVM_PREFERRED_TYPE(bool)
unsigned EmitCompactUnwindNonCanonical : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned Crel : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ImplicitMapsyms : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned X86RelaxRelocations : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned X86Sse2Avx : 1;
/// The name of the relocation model to use.
std::string RelocationModel;
@@ -117,7 +142,7 @@ struct AssemblerInvocation {
public:
AssemblerInvocation() {
Triple = "";
Triple = llvm::Triple();
NoInitialTextSection = 0;
InputFile = "-";
OutputPath = "-";
@@ -136,6 +161,10 @@ public:
EmbedBitcode = 0;
EmitDwarfUnwind = EmitDwarfUnwindType::Default;
EmitCompactUnwindNonCanonical = false;
Crel = false;
ImplicitMapsyms = 0;
X86RelaxRelocations = 0;
X86Sse2Avx = 0;
}
static bool CreateFromArgs(AssemblerInvocation &Res,
+3 -3
View File
@@ -27,9 +27,9 @@ bool tinygo_clang_driver(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
// The compiler invocation needs a DiagnosticsEngine so it can report problems
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions();
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false);
clang::DiagnosticOptions DiagOpts;
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), DiagOpts);
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), DiagOpts, &DiagnosticPrinter, false);
// Create the clang driver
clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
+1 -1
View File
@@ -17,7 +17,7 @@ import (
var commands = map[string][]string{}
func init() {
llvmMajor := strings.Split(llvm.Version, ".")[0]
llvmMajor, _, _ := strings.Cut(llvm.Version, ".")
commands["clang"] = []string{"clang-" + llvmMajor}
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
+9 -6
View File
@@ -25,18 +25,21 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
}
// Version range supported by TinyGo.
const minorMin = 19
const minorMax = 23
const minorMin = 25 // when updating the min version, also update .github/workflows/compat.yml
const minorMax = 27
// Check that we support this Go toolchain version.
gorootMajor, gorootMinor, err := goenv.GetGorootVersion()
if err != nil {
return nil, err
}
if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax {
// Note: when this gets updated, also update the Go compatibility matrix:
// https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md
return nil, fmt.Errorf("requires go version 1.19 through 1.23, got go%d.%d", gorootMajor, gorootMinor)
if options.GoCompatibility {
if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax {
// Note: when this gets updated, also update the Go compatibility matrix:
// https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md
return nil, fmt.Errorf("requires go version 1.%d through 1.%d, got go%d.%d", minorMin, minorMax, gorootMajor, gorootMinor)
}
}
// Check that the Go toolchain version isn't too new, if we haven't been
+1 -1
View File
@@ -15,7 +15,7 @@ func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJ
return &compileJob{
description: "compile Darwin libSystem.dylib",
run: func(job *compileJob) (err error) {
arch := strings.Split(config.Triple(), "-")[0]
arch, _, _ := strings.Cut(config.Triple(), "-")
job.result = filepath.Join(tmpdir, "libSystem.dylib")
objpath := filepath.Join(tmpdir, "libSystem.o")
inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s")
+150 -14
View File
@@ -23,7 +23,7 @@ type espImageSegment struct {
data []byte
}
// makeESPFirmareImage converts an input ELF file to an image file for an ESP32 or
// makeESPFirmwareImage converts an input ELF file to an image file for an ESP32 or
// ESP8266 chip. This is a special purpose image format just for the ESP chip
// family, and is parsed by the on-chip mask ROM bootloader.
//
@@ -31,7 +31,7 @@ type espImageSegment struct {
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
// https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58
// https://github.com/espressif/esptool/blob/master/esptool.py
func makeESPFirmareImage(infile, outfile, format string) error {
func makeESPFirmwareImage(infile, outfile, format string) error {
inf, err := elf.Open(infile)
if err != nil {
return err
@@ -65,15 +65,6 @@ func makeESPFirmareImage(infile, outfile, format string) error {
// Sort the segments by address. This is what esptool does too.
sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr })
// Calculate checksum over the segment data. This is used in the image
// footer.
checksum := uint8(0xef)
for _, segment := range segments {
for _, b := range segment.data {
checksum ^= b
}
}
// Write first to an in-memory buffer, primarily so that we can easily
// calculate a hash over the entire image.
// An added benefit is that we don't need to check for errors all the time.
@@ -88,6 +79,86 @@ func makeESPFirmareImage(infile, outfile, format string) error {
chip = format[:len(format)-len("-img")]
}
// For ESP32 (original): separate RAM segments (loadable by ROM bootloader)
// from flash-mapped segments (DROM/IROM, require MMU setup by startup code).
// The ROM bootloader on ESP32 does NOT handle flash-mapped segments —
// it tries to memcpy to the virtual address, which crashes.
var flashSegments []*espImageSegment
if chip == "esp32" {
var ramSegments []*espImageSegment
for _, seg := range segments {
if (seg.addr >= 0x3F400000 && seg.addr < 0x3F800000) || // DROM
(seg.addr >= 0x400D0000 && seg.addr < 0x40400000) { // IROM
flashSegments = append(flashSegments, seg)
} else {
ramSegments = append(ramSegments, seg)
}
}
segments = ramSegments
}
// ESP32 flash XIP: compute where the DROM segment will be placed in flash
// (page-aligned, right after the RAM segments) and patch the
// _drom_flash_addr variable so the startup code can program the cache MMU.
// This must happen before the checksum/hash are computed so the patched
// value is covered by both.
const esp32FlashBase = 0x1000 // esptool flashes the image at 0x1000
// The ESP32 flash cache MMU supports configurable page sizes down to 256 B. 64 KiB is the reset/default size.
// If the startup code ever changes the MMU page size, this constant must change too.
const esp32PageSize = 0x10000 // 64KB MMU pages
var esp32DromFlashAddr uint32
if chip == "esp32" && len(flashSegments) > 0 {
// Compute the size of the RAM portion of the image (everything the ROM
// bootloader loads, up to and including the appended SHA256 hash).
ramImageSize := 0
if makeImage {
ramImageSize += 4096
}
ramImageSize += 24 // image header (8) + trailer fields (16)
for _, seg := range segments {
ramImageSize += 8 + len(seg.data) // segment header + data (4-aligned)
}
ramImageSize += 16 - ramImageSize%16 // footer padding + checksum byte
ramImageSize += 32 // appended SHA256 hash
// DROM flash address must be 64KB page-aligned.
esp32DromFlashAddr = uint32(esp32FlashBase+ramImageSize+esp32PageSize-1) &^ (esp32PageSize - 1)
// Patch _drom_flash_addr in whichever RAM segment contains it.
syms, _ := inf.Symbols()
var dromSymAddr uint64
for _, s := range syms {
if s.Name == "_drom_flash_addr" {
dromSymAddr = s.Value
break
}
}
if dromSymAddr == 0 {
return fmt.Errorf("ESP32: _drom_flash_addr symbol not found")
}
patched := false
for _, seg := range segments {
if dromSymAddr >= uint64(seg.addr) && dromSymAddr+4 <= uint64(seg.addr)+uint64(len(seg.data)) {
off := int(dromSymAddr - uint64(seg.addr))
binary.LittleEndian.PutUint32(seg.data[off:], esp32DromFlashAddr)
patched = true
break
}
}
if !patched {
return fmt.Errorf("ESP32: _drom_flash_addr (0x%x) not in any RAM segment", dromSymAddr)
}
}
// Calculate checksum over the segment data. This is used in the image
// footer.
checksum := uint8(0xef)
for _, segment := range segments {
for _, b := range segment.data {
checksum ^= b
}
}
if makeImage {
// The bootloader starts at 0x1000, or 4096.
// TinyGo doesn't use a separate bootloader and runs the entire
@@ -100,11 +171,24 @@ func makeESPFirmareImage(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":
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
@@ -125,8 +209,8 @@ func makeESPFirmareImage(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,
@@ -178,6 +262,58 @@ func makeESPFirmareImage(infile, outfile, format string) error {
outf.Write(hash[:])
}
// For ESP32: append flash-mapped segments (DROM/IROM) at page-aligned flash
// offsets after the RAM portion. The startup code maps them via the flash
// cache MMU (DROM at esp32DromFlashAddr, patched into _drom_flash_addr).
if len(flashSegments) > 0 {
const flashBase = esp32FlashBase
const pageSize = esp32PageSize
dromFlashAddr := esp32DromFlashAddr
// Separate DROM and IROM segments.
var dromSegs, iromSegs []*espImageSegment
for _, seg := range flashSegments {
if seg.addr >= 0x3F400000 && seg.addr < 0x3F800000 {
dromSegs = append(dromSegs, seg)
} else {
iromSegs = append(iromSegs, seg)
}
}
// Write DROM segments at the computed page-aligned flash offset.
dromSize := 0
if len(dromSegs) > 0 {
targetImageOffset := int(dromFlashAddr - flashBase)
if outf.Len() > targetImageOffset {
return fmt.Errorf("ESP32: RAM segments too large (%d bytes), overlap DROM at flash 0x%x", outf.Len(), dromFlashAddr)
}
outf.Write(make([]byte, targetImageOffset-outf.Len()))
for _, seg := range dromSegs {
outf.Write(seg.data)
dromSize += len(seg.data)
}
}
// Write IROM segments immediately after DROM, at the next page boundary.
// IROM flash addr = dromFlashAddr + ceil(dromSize/pageSize)*pageSize
// (must match the computation in the startup assembly).
if len(iromSegs) > 0 {
dromPages := (dromSize + pageSize - 1) / pageSize
if dromPages == 0 {
dromPages = 1
}
iromFlashAddr := dromFlashAddr + uint32(dromPages)*pageSize
targetImageOffset := int(iromFlashAddr - flashBase)
if outf.Len() > targetImageOffset {
return fmt.Errorf("ESP32: DROM too large, overlaps IROM at flash 0x%x", iromFlashAddr)
}
outf.Write(make([]byte, targetImageOffset-outf.Len()))
for _, seg := range iromSegs {
outf.Write(seg.data)
}
}
}
// QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the
// image to be a certain size.
if makeImage {
+2 -2
View File
@@ -195,11 +195,11 @@ type intHeap struct {
sort.IntSlice
}
func (h *intHeap) Push(x interface{}) {
func (h *intHeap) Push(x any) {
h.IntSlice = append(h.IntSlice, x.(int))
}
func (h *intHeap) Pop() interface{} {
func (h *intHeap) Pop() any {
x := h.IntSlice[len(h.IntSlice)-1]
h.IntSlice = h.IntSlice[:len(h.IntSlice)-1]
return x
+42 -16
View File
@@ -15,6 +15,9 @@ import (
// Library is a container for information about a single C library, such as a
// compiler runtime or libc.
//
// Note: whenever a library gets changed, the version in compileopts/config.go
// probably also needs to be incremented.
type Library struct {
// The library name, such as compiler-rt or picolibc.
name string
@@ -25,11 +28,17 @@ type Library struct {
// cflags returns the C flags specific to this library
cflags func(target, headerPath string) []string
// cflagsForFile returns additional C flags for a particular source file.
cflagsForFile func(path string) []string
// needsLibc is set to true if this library needs libc headers.
needsLibc bool
// The source directory.
sourceDir func() string
// The source files, relative to sourceDir.
librarySources func(target string) ([]string, error)
librarySources func(target string, libcNeedsMalloc bool) ([]string, error)
// The source code for the crt1.o file, relative to sourceDir.
crt1Source string
@@ -44,13 +53,8 @@ type Library struct {
// As a side effect, this call creates the library header files if they didn't
// exist yet.
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) {
outdir, precompiled := config.LibcPath(l.name)
outdir := config.LibraryPath(l.name)
archiveFilePath := filepath.Join(outdir, "lib.a")
if precompiled {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return dummyCompileJob(archiveFilePath), func() {}, nil
}
// Create a lock on the output (if supported).
// This is a bit messy, but avoids a deadlock because it is ordered consistently with other library loads within a build.
@@ -129,7 +133,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the archive
// itself, which varies each run.
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+compileopts.ClangTriple(target), "-fdebug-prefix-map="+dir+"="+remapDir)
resourceDir := goenv.ClangResourceDir(false)
if resourceDir != "" {
args = append(args, "-resource-dir="+resourceDir)
@@ -163,9 +167,9 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// double.
args = append(args, "-mdouble=64")
case "riscv32":
args = append(args, "-march=rv32imac", "-fforce-enable-int128")
args = append(args, "-march="+riscvMarch(config, "rv32imac"), "-fforce-enable-int128")
case "riscv64":
args = append(args, "-march=rv64gc")
args = append(args, "-march="+riscvMarch(config, "rv64gc"))
case "mips":
args = append(args, "-fno-pic")
}
@@ -181,6 +185,9 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
args = append(args, "-mfpu=vfpv2")
}
}
if l.needsLibc {
args = append(args, config.LibcCFlags()...)
}
var once sync.Once
@@ -211,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)
},
}
@@ -219,7 +226,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// Create jobs to compile all sources. These jobs are depended upon by the
// archive job above, so must be run first.
paths, err := l.librarySources(target)
paths, err := l.librarySources(target, config.LibcNeedsMalloc())
if err != nil {
return nil, nil, err
}
@@ -233,11 +240,14 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
objpath := filepath.Join(dir, cleanpath+".o")
os.MkdirAll(filepath.Dir(objpath), 0o777)
objs = append(objs, objpath)
job.dependencies = append(job.dependencies, &compileJob{
objfile := &compileJob{
description: "compile " + srcpath,
run: func(*compileJob) error {
var compileArgs []string
compileArgs = append(compileArgs, args...)
if l.cflagsForFile != nil {
compileArgs = append(compileArgs, l.cflagsForFile(path)...)
}
compileArgs = append(compileArgs, "-o", objpath, srcpath)
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("clang", compileArgs...)
@@ -248,7 +258,8 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
}
return nil
},
})
}
job.dependencies = append(job.dependencies, objfile)
}
// Create crt1.o job, if needed.
@@ -257,7 +268,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// won't make much of a difference in speed).
if l.crt1Source != "" {
srcpath := filepath.Join(sourceDir, l.crt1Source)
job.dependencies = append(job.dependencies, &compileJob{
crt1Job := &compileJob{
description: "compile " + srcpath,
run: func(*compileJob) error {
var compileArgs []string
@@ -277,7 +288,8 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
}
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
},
})
}
job.dependencies = append(job.dependencies, crt1Job)
}
ok = true
@@ -285,3 +297,17 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
once.Do(unlock)
}, nil
}
// riscvMarch returns the -march value for RISC-V library compilation.
// It extracts the value from the target's cflags if present, otherwise
// falls back to the provided default. This ensures libraries are compiled
// with the correct ISA extensions for each target (e.g. rv32imc for
// ESP32-C3 which lacks the atomic extension).
func riscvMarch(config *compileopts.Config, defaultMarch string) string {
for _, flag := range config.Target.CFlags {
if strings.HasPrefix(flag, "-march=") {
return flag[len("-march="):]
}
}
return defaultMarch
}
+87 -33
View File
@@ -30,25 +30,62 @@ var libMinGW = Library{
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") },
cflags: func(target, headerPath string) []string {
mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64")
return []string{
flags := []string{
"-nostdlibinc",
"-isystem", mingwDir + "/mingw-w64-crt/include",
"-isystem", mingwDir + "/mingw-w64-headers/crt",
"-isystem", mingwDir + "/mingw-w64-headers/include",
"-I", mingwDir + "/mingw-w64-headers/defaults/include",
"-I" + headerPath,
}
if strings.Split(target, "-")[0] == "i386" {
flags = append(flags,
"-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002
"-D_WIN32_WINNT=0x0501", // target Windows XP
"-D_CRTBLD",
"-Wno-pragma-pack",
)
}
return flags
},
librarySources: func(target string) ([]string, error) {
librarySources: func(target string, _ bool) ([]string, error) {
// These files are needed so that printf and the like are supported.
sources := []string{
"mingw-w64-crt/stdio/ucrt_fprintf.c",
"mingw-w64-crt/stdio/ucrt_fwprintf.c",
"mingw-w64-crt/stdio/ucrt_printf.c",
"mingw-w64-crt/stdio/ucrt_snprintf.c",
"mingw-w64-crt/stdio/ucrt_sprintf.c",
"mingw-w64-crt/stdio/ucrt_vfprintf.c",
"mingw-w64-crt/stdio/ucrt_vprintf.c",
"mingw-w64-crt/stdio/ucrt_vsnprintf.c",
"mingw-w64-crt/stdio/ucrt_vsprintf.c",
var sources []string
if strings.Split(target, "-")[0] == "i386" {
// Old 32-bit x86 systems use msvcrt.dll.
sources = []string{
"mingw-w64-crt/crt/pseudo-reloc.c",
"mingw-w64-crt/gdtoa/dmisc.c",
"mingw-w64-crt/gdtoa/gdtoa.c",
"mingw-w64-crt/gdtoa/gmisc.c",
"mingw-w64-crt/gdtoa/misc.c",
"mingw-w64-crt/math/x86/exp2.S",
"mingw-w64-crt/math/x86/trunc.S",
"mingw-w64-crt/misc/___mb_cur_max_func.c",
"mingw-w64-crt/misc/lc_locale_func.c",
"mingw-w64-crt/misc/mbrtowc.c",
"mingw-w64-crt/misc/strnlen.c",
"mingw-w64-crt/misc/wcrtomb.c",
"mingw-w64-crt/misc/wcsnlen.c",
"mingw-w64-crt/stdio/acrt_iob_func.c",
"mingw-w64-crt/stdio/mingw_lock.c",
"mingw-w64-crt/stdio/mingw_pformat.c",
"mingw-w64-crt/stdio/mingw_vfprintf.c",
"mingw-w64-crt/stdio/mingw_vsnprintf.c",
}
} else {
// Anything somewhat modern (amd64, arm64) uses UCRT.
sources = []string{
"mingw-w64-crt/stdio/ucrt_fprintf.c",
"mingw-w64-crt/stdio/ucrt_fwprintf.c",
"mingw-w64-crt/stdio/ucrt_printf.c",
"mingw-w64-crt/stdio/ucrt_snprintf.c",
"mingw-w64-crt/stdio/ucrt_sprintf.c",
"mingw-w64-crt/stdio/ucrt_vfprintf.c",
"mingw-w64-crt/stdio/ucrt_vprintf.c",
"mingw-w64-crt/stdio/ucrt_vsnprintf.c",
"mingw-w64-crt/stdio/ucrt_vsprintf.c",
}
}
return sources, nil
},
@@ -63,27 +100,41 @@ var libMinGW = Library{
func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
var jobs []*compileJob
root := goenv.Get("TINYGOROOT")
// Normally all the api-ms-win-crt-*.def files are all compiled to a single
// .lib file. But to simplify things, we're going to leave them as separate
// files.
for _, name := range []string{
"kernel32.def.in",
"api-ms-win-crt-conio-l1-1-0.def",
"api-ms-win-crt-convert-l1-1-0.def.in",
"api-ms-win-crt-environment-l1-1-0.def",
"api-ms-win-crt-filesystem-l1-1-0.def",
"api-ms-win-crt-heap-l1-1-0.def",
"api-ms-win-crt-locale-l1-1-0.def",
"api-ms-win-crt-math-l1-1-0.def.in",
"api-ms-win-crt-multibyte-l1-1-0.def",
"api-ms-win-crt-private-l1-1-0.def.in",
"api-ms-win-crt-process-l1-1-0.def",
"api-ms-win-crt-runtime-l1-1-0.def.in",
"api-ms-win-crt-stdio-l1-1-0.def",
"api-ms-win-crt-string-l1-1-0.def",
"api-ms-win-crt-time-l1-1-0.def",
"api-ms-win-crt-utility-l1-1-0.def",
} {
var libs []string
if goarch == "386" {
libs = []string{
// x86 uses msvcrt.dll instead of UCRT for compatibility with old
// Windows versions.
"advapi32.def.in",
"kernel32.def.in",
"msvcrt.def.in",
}
} else {
// Use the modernized UCRT on new systems.
// Normally all the api-ms-win-crt-*.def files are all compiled to a
// single .lib file. But to simplify things, we're going to leave them
// as separate files.
libs = []string{
"advapi32.def.in",
"kernel32.def.in",
"api-ms-win-crt-conio-l1-1-0.def",
"api-ms-win-crt-convert-l1-1-0.def.in",
"api-ms-win-crt-environment-l1-1-0.def",
"api-ms-win-crt-filesystem-l1-1-0.def",
"api-ms-win-crt-heap-l1-1-0.def",
"api-ms-win-crt-locale-l1-1-0.def",
"api-ms-win-crt-math-l1-1-0.def.in",
"api-ms-win-crt-multibyte-l1-1-0.def",
"api-ms-win-crt-private-l1-1-0.def.in",
"api-ms-win-crt-process-l1-1-0.def",
"api-ms-win-crt-runtime-l1-1-0.def.in",
"api-ms-win-crt-stdio-l1-1-0.def",
"api-ms-win-crt-string-l1-1-0.def",
"api-ms-win-crt-time-l1-1-0.def",
"api-ms-win-crt-utility-l1-1-0.def",
}
}
for _, name := range libs {
outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib")
inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name)
job := &compileJob{
@@ -93,6 +144,9 @@ func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
defpath := inpath
var archDef, emulation string
switch goarch {
case "386":
archDef = "-DDEF_I386"
emulation = "i386pe"
case "amd64":
archDef = "-DDEF_X64"
emulation = "i386pep"
+59 -31
View File
@@ -12,6 +12,45 @@ import (
"github.com/tinygo-org/tinygo/goenv"
)
// Create the alltypes.h file from the appropriate alltypes.h.in files.
func buildMuslAllTypes(arch, muslDir, outputBitsDir string) error {
// Create the file alltypes.h.
f, err := os.Create(filepath.Join(outputBitsDir, "alltypes.h"))
if err != nil {
return err
}
infiles := []string{
filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"),
filepath.Join(muslDir, "include", "alltypes.h.in"),
}
for _, infile := range infiles {
data, err := os.ReadFile(infile)
if err != nil {
return err
}
lines := strings.SplitSeq(string(data), "\n")
for line := range lines {
if strings.HasPrefix(line, "TYPEDEF ") {
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
value := matches[1]
name := matches[2]
line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name)
}
if strings.HasPrefix(line, "STRUCT ") {
matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line)
name := matches[1]
value := matches[2]
line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name)
}
_, err := f.WriteString(line + "\n")
if err != nil {
return err
}
}
}
return f.Close()
}
var libMusl = Library{
name: "musl",
makeHeaders: func(target, includeDir string) error {
@@ -24,41 +63,13 @@ var libMusl = Library{
arch := compileopts.MuslArchitecture(target)
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl")
// Create the file alltypes.h.
f, err := os.Create(filepath.Join(bits, "alltypes.h"))
err = buildMuslAllTypes(arch, muslDir, bits)
if err != nil {
return err
}
infiles := []string{
filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"),
filepath.Join(muslDir, "include", "alltypes.h.in"),
}
for _, infile := range infiles {
data, err := os.ReadFile(infile)
if err != nil {
return err
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "TYPEDEF ") {
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
value := matches[1]
name := matches[2]
line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name)
}
if strings.HasPrefix(line, "STRUCT ") {
matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line)
name := matches[1]
value := matches[2]
line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name)
}
f.WriteString(line + "\n")
}
}
f.Close()
// Create the file syscall.h.
f, err = os.Create(filepath.Join(bits, "syscall.h"))
f, err := os.Create(filepath.Join(bits, "syscall.h"))
if err != nil {
return err
}
@@ -110,38 +121,55 @@ var libMusl = Library{
return cflags
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") },
librarySources: func(target string) ([]string, error) {
librarySources: func(target string, _ bool) ([]string, error) {
arch := compileopts.MuslArchitecture(target)
globs := []string{
"conf/*.c",
"ctype/*.c",
"env/*.c",
"errno/*.c",
"exit/*.c",
"fcntl/*.c",
"internal/defsysinfo.c",
"internal/intscan.c",
"internal/libc.c",
"internal/shgetc.c",
"internal/syscall_ret.c",
"internal/vdso.c",
"legacy/*.c",
"locale/*.c",
"linux/*.c",
"locale/*.c",
"malloc/*.c",
"malloc/mallocng/*.c",
"mman/*.c",
"math/*.c",
"misc/*.c",
"multibyte/*.c",
"sched/*.c",
"signal/" + arch + "/*.s",
"signal/*.c",
"stdio/*.c",
"stdlib/*.c",
"string/*.c",
"thread/" + arch + "/*.s",
"thread/*.c",
"time/*.c",
"unistd/*.c",
"process/*.c",
}
if arch == "arm" {
// These files need to be added to the start for some reason.
globs = append([]string{"thread/arm/*.c"}, globs...)
}
if arch != "aarch64" && arch != "mips" {
//aarch64 and mips have no architecture specific code, either they
// are not supported or don't need any?
globs = append([]string{"process/" + arch + "/*.s"}, globs...)
}
var sources []string
seenSources := map[string]struct{}{}
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
+2 -1
View File
@@ -34,6 +34,7 @@ var libPicolibc = Library{
"-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU
"-D__OBSOLETE_MATH_DOUBLE=0",
"-D_WANT_IO_C99_FORMATS",
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
"-nostdlibinc",
"-isystem", newlibDir + "/libc/include",
"-I" + newlibDir + "/libc/tinystdio",
@@ -42,7 +43,7 @@ var libPicolibc = Library{
}
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") },
librarySources: func(target string) ([]string, error) {
librarySources: func(target string, _ bool) ([]string, error) {
sources := append([]string(nil), picolibcSources...)
if !strings.HasPrefix(target, "avr") {
// Small chips without long jumps can't compile many files (printf,
+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
}
+56
View File
@@ -0,0 +1,56 @@
package builder
import (
_ "embed"
"fmt"
"html/template"
"os"
)
//go:embed size-report.html
var sizeReportBase string
func writeSizeReport(sizes *programSize, filename, pkgName string) error {
tmpl, err := template.New("report").Parse(sizeReportBase)
if err != nil {
return err
}
f, err := os.Create(filename)
if err != nil {
return fmt.Errorf("could not open report file: %w", err)
}
defer f.Close()
// Prepare data for the report.
type sizeLine struct {
Name string
Size *packageSize
}
programData := []sizeLine{}
for _, name := range sizes.sortedPackageNames() {
pkgSize := sizes.Packages[name]
programData = append(programData, sizeLine{
Name: name,
Size: pkgSize,
})
}
sizeTotal := map[string]uint64{
"code": sizes.Code,
"rodata": sizes.ROData,
"data": sizes.Data,
"bss": sizes.BSS,
"flash": sizes.Flash(),
}
// Write the report.
err = tmpl.Execute(f, map[string]any{
"pkgName": pkgName,
"sizes": programData,
"sizeTotal": sizeTotal,
})
if err != nil {
return fmt.Errorf("could not create report file: %w", err)
}
return nil
}
+109
View File
@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Size Report for {{.pkgName}}</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<style>
.table-vertical-border {
border-left: calc(var(--bs-border-width) * 2) solid currentcolor;
}
/* Hover on only the rows that are clickable. */
.row-package:hover > * {
--bs-table-color-state: var(--bs-table-hover-color);
--bs-table-bg-state: var(--bs-table-hover-bg);
}
</style>
</head>
<body>
<div class="container-xxl">
<h1>Size Report for {{.pkgName}}</h1>
<p>How much space is used by Go packages, C libraries, and other bits to set up the program environment.</p>
<ul>
<li><strong>Code</strong> is the actual program code (machine code instructions).</li>
<li><strong>Read-only data</strong> are read-only global variables. On most microcontrollers, these are stored in flash and do not take up any RAM.</li>
<li><strong>Data</strong> are writable global variables with a non-zero initializer. On microcontrollers, they are copied from flash to RAM on reset.</li>
<li><strong>BSS</strong> are writable global variables that are zero initialized. They do not take up any space in the binary, but do take up RAM. On microcontrollers, this area is zeroed on reset.</li>
</ul>
<p>The binary size consists of code, read-only data, and data. On microcontrollers, this is exactly the size of the firmware image. On other systems, there is some extra overhead: binary metadata (headers of the ELF/MachO/COFF file), debug information, exception tables, symbol names, etc. Using <code>-no-debug</code> strips most of those.</p>
<h2>Program breakdown</h2>
<p>You can click on the rows below to see which files contribute to the binary size.</p>
<div class="table-responsive">
<table class="table w-auto">
<thead>
<tr>
<th>Package</th>
<th class="table-vertical-border">Code</th>
<th>Read-only data</th>
<th>Data</th>
<th title="zero-initialized data">BSS</th>
<th class="table-vertical-border" style="min-width: 16em">Binary size</th>
</tr>
</thead>
<tbody class="table-group-divider">
{{range $i, $pkg := .sizes}}
<tr class="row-package" data-collapse=".collapse-row-{{$i}}">
<td>{{.Name}}</td>
<td class="table-vertical-border">{{.Size.Code}}</td>
<td>{{.Size.ROData}}</td>
<td>{{.Size.Data}}</td>
<td>{{.Size.BSS}}</td>
<td class="table-vertical-border" style="background: linear-gradient(to right, var(--bs-info-bg-subtle) {{.Size.FlashPercent}}%, var(--bs-table-bg) {{.Size.FlashPercent}}%)">
{{.Size.Flash}}
</td>
</tr>
{{range $filename, $sizes := .Size.Sub}}
<tr class="table-secondary collapse collapse-row-{{$i}}">
<td class="ps-4">
{{if eq $filename ""}}
(unknown file)
{{else}}
{{$filename}}
{{end}}
</td>
<td class="table-vertical-border">{{$sizes.Code}}</td>
<td>{{$sizes.ROData}}</td>
<td>{{$sizes.Data}}</td>
<td>{{$sizes.BSS}}</td>
<td class="table-vertical-border" style="background: linear-gradient(to right, var(--bs-info-bg-subtle) {{$sizes.FlashPercent}}%, var(--bs-table-bg) {{$sizes.FlashPercent}}%)">
{{$sizes.Flash}}
</td>
</tr>
{{end}}
{{end}}
</tbody>
<tfoot class="table-group-divider">
<tr>
<th>Total</th>
<td class="table-vertical-border">{{.sizeTotal.code}}</td>
<td>{{.sizeTotal.rodata}}</td>
<td>{{.sizeTotal.data}}</td>
<td>{{.sizeTotal.bss}}</td>
<td class="table-vertical-border">{{.sizeTotal.flash}}</td>
</tr>
</tfoot>
</table>
</div>
</div>
<script>
// Make table rows toggleable to show filenames.
for (let clickable of document.querySelectorAll('.row-package')) {
clickable.addEventListener('click', e => {
for (let row of document.querySelectorAll(clickable.dataset.collapse)) {
row.classList.toggle('show');
}
});
}
</script>
</body>
</html>
+115 -50
View File
@@ -12,6 +12,7 @@ import (
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
@@ -24,7 +25,7 @@ const sizesDebug = false
// programSize contains size statistics per package of a compiled program.
type programSize struct {
Packages map[string]packageSize
Packages map[string]*packageSize
Code uint64
ROData uint64
Data uint64
@@ -52,13 +53,29 @@ func (ps *programSize) RAM() uint64 {
return ps.Data + ps.BSS
}
// Return the package size information for a given package path, creating it if
// it doesn't exist yet.
func (ps *programSize) getPackage(path string) *packageSize {
if field, ok := ps.Packages[path]; ok {
return field
}
field := &packageSize{
Program: ps,
Sub: map[string]*packageSize{},
}
ps.Packages[path] = field
return field
}
// packageSize contains the size of a package, calculated from the linked object
// file.
type packageSize struct {
Code uint64
ROData uint64
Data uint64
BSS uint64
Program *programSize
Code uint64
ROData uint64
Data uint64
BSS uint64
Sub map[string]*packageSize
}
// Flash usage in regular microcontrollers.
@@ -71,6 +88,31 @@ func (ps *packageSize) RAM() uint64 {
return ps.Data + ps.BSS
}
// Flash usage in regular microcontrollers, as a percentage of the total flash
// usage of the program.
func (ps *packageSize) FlashPercent() float64 {
return float64(ps.Flash()) / float64(ps.Program.Flash()) * 100
}
// Add a single size data point to this package.
// This must only be called while calculating package size, not afterwards.
func (ps *packageSize) addSize(getField func(*packageSize, bool) *uint64, filename string, size uint64, isVariable bool) {
if size == 0 {
return
}
// Add size for the package.
*getField(ps, isVariable) += size
// Add size for file inside package.
sub, ok := ps.Sub[filename]
if !ok {
sub = &packageSize{Program: ps.Program}
ps.Sub[filename] = sub
}
*getField(sub, isVariable) += size
}
// A mapping of a single chunk of code or data to a file path.
type addressLine struct {
Address uint64
@@ -194,11 +236,22 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
if !prevLineEntry.EndSequence {
// The chunk describes the code from prevLineEntry to
// lineEntry.
path := prevLineEntry.File.Name
if runtime.GOOS == "windows" {
// Work around a Clang bug on Windows:
// https://github.com/llvm/llvm-project/issues/117317
path = strings.ReplaceAll(path, "\\\\", "\\")
// wasi-libc likes to use forward slashes, but we
// canonicalize everything to use backwards slashes as
// is common on Windows.
path = strings.ReplaceAll(path, "/", "\\")
}
line := addressLine{
Address: prevLineEntry.Address + codeOffset,
Length: lineEntry.Address - prevLineEntry.Address,
Align: codeAlignment,
File: prevLineEntry.File.Name,
File: path,
}
if line.Length != 0 {
addresses = append(addresses, line)
@@ -437,7 +490,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
continue
}
if section.Type == elf.SHT_NOBITS {
if section.Name == ".stack" {
if strings.HasPrefix(section.Name, ".stack") {
// TinyGo emits stack sections on microcontroller using the
// ".stack" name.
// This is a bit ugly, but I don't think there is a way to
@@ -448,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,
@@ -457,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{
@@ -773,49 +832,40 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
// Now finally determine the binary/RAM size usage per package by going
// through each allocated section.
sizes := make(map[string]packageSize)
sizes := make(map[string]*packageSize)
program := &programSize{
Packages: sizes,
}
for _, section := range sections {
switch section.Type {
case memoryCode:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
if isVariable {
field.ROData += size
} else {
field.Code += size
return &ps.ROData
}
sizes[path] = field
return &ps.Code
}, packagePathMap)
case memoryROData:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.ROData += size
sizes[path] = field
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
return &ps.ROData
}, packagePathMap)
case memoryData:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.Data += size
sizes[path] = field
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
return &ps.Data
}, packagePathMap)
case memoryBSS:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.BSS += size
sizes[path] = field
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
return &ps.BSS
}, packagePathMap)
case memoryStack:
// We store the C stack as a pseudo-package.
sizes["C stack"] = packageSize{
BSS: section.Size,
}
program.getPackage("C stack").addSize(func(ps *packageSize, isVariable bool) *uint64 {
return &ps.BSS
}, "", section.Size, false)
}
}
// ...and summarize the results.
program := &programSize{
Packages: sizes,
}
for _, pkg := range sizes {
program.Code += pkg.Code
program.ROData += pkg.ROData
@@ -826,8 +876,8 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
}
// readSection determines for each byte in this section to which package it
// belongs. It reports this usage through the addSize callback.
func readSection(section memorySection, addresses []addressLine, addSize func(string, uint64, bool), packagePathMap map[string]string) {
// belongs.
func readSection(section memorySection, addresses []addressLine, program *programSize, getField func(*packageSize, bool) *uint64, packagePathMap map[string]string) {
// The addr variable tracks at which address we are while going through this
// section. We start at the beginning.
addr := section.Address
@@ -849,9 +899,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
addrAligned := (addr + line.Align - 1) &^ (line.Align - 1)
if line.Align > 1 && addrAligned >= line.Address {
// It is, assume that's what causes the gap.
addSize("(padding)", line.Address-addr, true)
program.getPackage("(padding)").addSize(getField, "", line.Address-addr, true)
} else {
addSize("(unknown)", line.Address-addr, false)
program.getPackage("(unknown)").addSize(getField, "", line.Address-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (gap), alignment=%d\n", addr, line.Address, line.Address-addr, line.Align)
}
@@ -873,7 +923,8 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
length = line.Length - (addr - line.Address)
}
// Finally, mark this chunk of memory as used by the given package.
addSize(findPackagePath(line.File, packagePathMap), length, line.IsVariable)
packagePath, filename := findPackagePath(line.File, packagePathMap)
program.getPackage(packagePath).addSize(getField, filename, length, line.IsVariable)
addr = line.Address + line.Length
}
if addr < sectionEnd {
@@ -882,9 +933,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
if section.Align > 1 && addrAligned >= sectionEnd {
// The gap is caused by the section alignment.
// For example, if a .rodata section ends with a non-aligned string.
addSize("(padding)", sectionEnd-addr, true)
program.getPackage("(padding)").addSize(getField, "", sectionEnd-addr, true)
} else {
addSize("(unknown)", sectionEnd-addr, false)
program.getPackage("(unknown)").addSize(getField, "", sectionEnd-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (end), alignment=%d\n", addr, sectionEnd, sectionEnd-addr, section.Align)
}
@@ -894,17 +945,29 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
// findPackagePath returns the Go package (or a pseudo package) for the given
// path. It uses some heuristics, for example for some C libraries.
func findPackagePath(path string, packagePathMap map[string]string) string {
func findPackagePath(path string, packagePathMap map[string]string) (packagePath, filename string) {
// Check whether this path is part of one of the compiled packages.
packagePath, ok := packagePathMap[filepath.Dir(path)]
if !ok {
if ok {
// Directory is known as a Go package.
// Add the file itself as well.
filename = filepath.Base(path)
} else {
if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) {
// Emit C libraries (in the lib subdirectory of TinyGo) as a single
// package, with a "C" prefix. For example: "C compiler-rt" for the
// compiler runtime library from LLVM.
packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1]
} else if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project")) {
// package, with a "C" prefix. For example: "C picolibc" for the
// baremetal libc.
libPath := strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")+string(os.PathSeparator))
parts := strings.SplitN(libPath, string(os.PathSeparator), 2)
packagePath = "C " + parts[0]
if len(parts) > 1 {
filename = parts[1]
} else {
filename = parts[0]
}
} else if prefix := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project", "compiler-rt"); strings.HasPrefix(path, prefix) {
packagePath = "C compiler-rt"
filename = strings.TrimPrefix(path, prefix+string(os.PathSeparator))
} else if packageSymbolRegexp.MatchString(path) {
// Parse symbol names like main$alloc or runtime$string.
packagePath = path[:strings.LastIndex(path, "$")]
@@ -927,9 +990,11 @@ func findPackagePath(path string, packagePathMap map[string]string) string {
// fixed in the compiler.
packagePath = "-"
} else {
// This is some other path. Not sure what it is, so just emit its directory.
packagePath = filepath.Dir(path) // fallback
// This is some other path. Not sure what it is, so just emit its
// directory as a fallback.
packagePath = filepath.Dir(path)
filename = filepath.Base(path)
}
}
return packagePath
return
}
+70 -24
View File
@@ -1,6 +1,7 @@
package builder
import (
"regexp"
"runtime"
"testing"
"time"
@@ -41,40 +42,20 @@ 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", 4568, 280, 0, 2268},
{"microbit", "examples/serial", 2868, 388, 8, 2272},
{"wioterminal", "examples/pininterrupt", 6104, 1484, 116, 6832},
{"hifive1b", "examples/echo", 4313, 323, 0, 2260},
{"microbit", "examples/serial", 2838, 382, 8, 2256},
{"wioterminal", "examples/pininterrupt", 8027, 1665, 132, 7488},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
// output varies by binaryen version.
}
for _, tc := range tests {
tc := tc
t.Run(tc.target+"/"+tc.path, func(t *testing.T) {
t.Parallel()
// Build the binary.
options := compileopts.Options{
Target: tc.target,
Opt: "z",
Semaphore: sema,
InterpTimeout: 60 * time.Second,
Debug: true,
VerifyIR: true,
}
target, err := compileopts.LoadTarget(&options)
if err != nil {
t.Fatal("could not load target:", err)
}
config := &compileopts.Config{
Options: &options,
Target: target,
}
result, err := Build(tc.path, "", t.TempDir(), config)
if err != nil {
t.Fatal("could not build:", err)
}
result := buildBinary(t, tc.target, tc.path)
// Check whether the size of the binary matches the expected size.
sizes, err := loadProgramSize(result.Executable, nil)
@@ -90,3 +71,68 @@ func TestBinarySize(t *testing.T) {
})
}
}
// Check that the -size=full flag attributes binary size to the correct package
// without filesystem paths and things like that.
func TestSizeFull(t *testing.T) {
tests := []string{
"microbit",
"wasip1",
}
libMatch := regexp.MustCompile(`^C [a-z -]+$`) // example: "C interrupt vector"
pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task"
for _, target := range tests {
t.Run(target, func(t *testing.T) {
t.Parallel()
// Build the binary.
result := buildBinary(t, target, "examples/serial")
// Check whether the binary doesn't contain any unexpected package
// names.
sizes, err := loadProgramSize(result.Executable, result.PackagePathMap)
if err != nil {
t.Fatal("could not read program size:", err)
}
for _, pkg := range sizes.sortedPackageNames() {
if pkg == "(padding)" || pkg == "(unknown)" || pkg == "Go types" {
// TODO: correctly attribute all unknown binary size.
continue
}
if libMatch.MatchString(pkg) {
continue
}
if pkgMatch.MatchString(pkg) {
continue
}
t.Error("unexpected package name in size output:", pkg)
}
})
}
}
func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
options := compileopts.Options{
Target: targetString,
Opt: "z",
Semaphore: sema,
InterpTimeout: 60 * time.Second,
Debug: true,
VerifyIR: true,
}
target, err := compileopts.LoadTarget(&options)
if err != nil {
t.Fatal("could not load target:", err)
}
config := &compileopts.Config{
Options: &options,
Target: target,
}
result, err := Build(pkgName, "", t.TempDir(), config)
if err != nil {
t.Fatal("could not build:", err)
}
return result
}
+1 -1
View File
@@ -28,7 +28,7 @@ func RunTool(tool string, args ...string) error {
var cflag *C.char
buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag)))
defer C.free(buf)
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(args):len(args)]
cflags := unsafe.Slice((**C.char)(buf), len(args))
for i, flag := range args {
cflag := C.CString(flag)
cflags[i] = cflag
+32 -10
View File
@@ -32,10 +32,26 @@ func runCCompiler(flags ...string) error {
cmd.Stderr = os.Stderr
// Make sure the command doesn't use any environmental variables.
// Most importantly, it should not use C_INCLUDE_PATH and the like. But
// removing all environmental variables also works.
// Most importantly, it should not use C_INCLUDE_PATH and the like.
cmd.Env = []string{}
// Let some environment variables through. One important one is the
// temporary directory, especially on Windows it looks like Clang breaks if
// the temporary directory has not been set.
// See: https://github.com/tinygo-org/tinygo/issues/4557
// Also see: https://github.com/llvm/llvm-project/blob/release/18.x/llvm/lib/Support/Unix/Path.inc#L1435
for _, env := range os.Environ() {
// We could parse the key and look it up in a map, but since there are
// only a few keys iterating through them is easier and maybe even
// faster.
for _, prefix := range []string{"TMPDIR=", "TMP=", "TEMP=", "TEMPDIR="} {
if strings.HasPrefix(env, prefix) {
cmd.Env = append(cmd.Env, env)
break
}
}
}
return cmd.Run()
}
@@ -98,29 +114,35 @@ func parseLLDErrors(text string) error {
// Check for undefined symbols.
// This can happen in some cases like with CGo and //go:linkname tricker.
if matches := regexp.MustCompile(`^ld.lld: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil {
symbolName := matches[1]
for _, line := range strings.Split(message, "\n") {
if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil {
symbolName := matches[2]
for line := range strings.SplitSeq(message, "\n") {
matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line)
if matches != nil {
parsedError = true
line, _ := strconv.Atoi(matches[3])
// TODO: detect common mistakes like -gc=none?
msg := "linker could not find symbol " + symbolName
switch symbolName {
case "runtime.alloc":
msg = "object allocated on the heap with -gc=none"
case "runtime.alloc_noheap":
msg = "object allocated on the heap in //go:noheap function"
}
linkErrors = append(linkErrors, scanner.Error{
Pos: token.Position{
Filename: matches[2],
Line: line,
},
Msg: "linker could not find symbol " + symbolName,
Msg: msg,
})
}
}
}
// Check for flash/RAM overflow.
if matches := regexp.MustCompile(`^ld.lld: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil {
region := matches[2]
n, err := strconv.ParseUint(matches[3], 10, 64)
if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil {
region := matches[3]
n, err := strconv.ParseUint(matches[4], 10, 64)
if err != nil {
// Should not happen at all (unless it overflows an uint64 for some reason).
continue
+1 -1
View File
@@ -40,7 +40,7 @@ func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byt
}
bl.SetNumBlocks(len(blocks))
for i := 0; i < len(blocks); i++ {
for i := range blocks {
bl.SetBlockNo(i)
bl.SetData(blocks[i])
+284
View File
@@ -0,0 +1,284 @@
package builder
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
var libWasiLibc = Library{
name: "wasi-libc",
makeHeaders: func(target, includeDir string) error {
bits := filepath.Join(includeDir, "bits")
err := os.Mkdir(bits, 0777)
if err != nil {
return err
}
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "wasi-libc/libc-top-half/musl")
err = buildMuslAllTypes("wasm32", muslDir, bits)
if err != nil {
return err
}
// See MUSL_OMIT_HEADERS in the Makefile.
omitHeaders := map[string]struct{}{
"syslog.h": {},
"wait.h": {},
"ucontext.h": {},
"paths.h": {},
"utmp.h": {},
"utmpx.h": {},
"lastlog.h": {},
"elf.h": {},
"link.h": {},
"pwd.h": {},
"shadow.h": {},
"grp.h": {},
"mntent.h": {},
"netdb.h": {},
"resolv.h": {},
"pty.h": {},
"dlfcn.h": {},
"setjmp.h": {},
"ulimit.h": {},
"wordexp.h": {},
"spawn.h": {},
"termios.h": {},
"libintl.h": {},
"aio.h": {},
"stdarg.h": {},
"stddef.h": {},
"pthread.h": {},
}
for _, glob := range [][2]string{
{"libc-bottom-half/headers/public/*.h", ""},
{"libc-bottom-half/headers/public/wasi/*.h", "wasi"},
{"libc-top-half/musl/arch/wasm32/bits/*.h", "bits"},
{"libc-top-half/musl/include/*.h", ""},
{"libc-top-half/musl/include/netinet/*.h", "netinet"},
{"libc-top-half/musl/include/sys/*.h", "sys"},
} {
matches, _ := filepath.Glob(filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc", glob[0]))
outDir := filepath.Join(includeDir, glob[1])
os.MkdirAll(outDir, 0o777)
for _, match := range matches {
name := filepath.Base(match)
if _, ok := omitHeaders[name]; ok {
continue
}
data, err := os.ReadFile(match)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(outDir, name), data, 0o666)
if err != nil {
return err
}
}
}
return nil
},
cflags: func(target, headerPath string) []string {
libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc")
return []string{
"-Werror",
"-Wall",
"-std=gnu11",
"-nostdlibinc",
"-mnontrapping-fptoint", "-msign-ext", "-mbulk-memory",
"-Wno-null-pointer-arithmetic", "-Wno-unused-parameter", "-Wno-sign-compare", "-Wno-unused-variable", "-Wno-unused-function", "-Wno-ignored-attributes", "-Wno-missing-braces", "-Wno-ignored-pragmas", "-Wno-unused-but-set-variable", "-Wno-unknown-warning-option",
"-Wno-parentheses", "-Wno-shift-op-parentheses", "-Wno-bitwise-op-parentheses", "-Wno-logical-op-parentheses", "-Wno-string-plus-int", "-Wno-dangling-else", "-Wno-unknown-pragmas",
"-DNDEBUG",
"-D__wasilibc_printscan_no_long_double",
"-D__wasilibc_printscan_full_support_option=\"long double support is disabled\"",
"-DBULK_MEMORY_THRESHOLD=32", // default threshold in wasi-libc
"-isystem", headerPath,
"-I" + libcDir + "/libc-top-half/musl/src/include",
"-I" + libcDir + "/libc-top-half/musl/src/internal",
"-I" + libcDir + "/libc-top-half/musl/arch/wasm32",
"-I" + libcDir + "/libc-top-half/musl/arch/generic",
"-I" + libcDir + "/libc-top-half/headers/private",
}
},
cflagsForFile: func(path string) []string {
if strings.HasPrefix(path, "libc-bottom-half"+string(os.PathSeparator)) {
libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc")
return []string{
"-I" + libcDir + "/libc-bottom-half/headers/private",
"-I" + libcDir + "/libc-bottom-half/cloudlibc/src/include",
"-I" + libcDir + "/libc-bottom-half/cloudlibc/src",
}
}
return nil
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") },
librarySources: func(target string, libcNeedsMalloc bool) ([]string, error) {
type filePattern struct {
glob string
exclude []string
}
// See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile
globs := []filePattern{
// Top half: mostly musl sources.
{glob: "libc-top-half/sources/*.c"},
{glob: "libc-top-half/musl/src/conf/*.c"},
{glob: "libc-top-half/musl/src/internal/*.c", exclude: []string{
"procfdname.c", "syscall.c", "syscall_ret.c", "vdso.c", "version.c",
}},
{glob: "libc-top-half/musl/src/locale/*.c", exclude: []string{
"dcngettext.c", "textdomain.c", "bind_textdomain_codeset.c"}},
{glob: "libc-top-half/musl/src/math/*.c", exclude: []string{
"__signbit.c", "__signbitf.c", "__signbitl.c",
"__fpclassify.c", "__fpclassifyf.c", "__fpclassifyl.c",
"ceilf.c", "ceil.c",
"floorf.c", "floor.c",
"truncf.c", "trunc.c",
"rintf.c", "rint.c",
"nearbyintf.c", "nearbyint.c",
"sqrtf.c", "sqrt.c",
"fabsf.c", "fabs.c",
"copysignf.c", "copysign.c",
"fminf.c", "fmaxf.c",
"fmin.c", "fmax.c,",
}},
{glob: "libc-top-half/musl/src/multibyte/*.c"},
{glob: "libc-top-half/musl/src/stdio/*.c", exclude: []string{
"vfwscanf.c", "vfwprintf.c", // long double is unsupported
"__lockfile.c", "flockfile.c", "funlockfile.c", "ftrylockfile.c",
"rename.c",
"tmpnam.c", "tmpfile.c", "tempnam.c",
"popen.c", "pclose.c",
"remove.c",
"gets.c"}},
{glob: "libc-top-half/musl/src/stdlib/*.c"},
{glob: "libc-top-half/musl/src/string/*.c", exclude: []string{
"strsignal.c"}},
// Bottom half: connect top half to WASI equivalents.
{glob: "libc-bottom-half/cloudlibc/src/libc/*/*.c"},
{glob: "libc-bottom-half/cloudlibc/src/libc/sys/*/*.c"},
{glob: "libc-bottom-half/sources/*.c"},
}
// We're using the Boehm GC, so we need a heap implementation in the libc.
if libcNeedsMalloc {
globs = append(globs, filePattern{glob: "dlmalloc/src/dlmalloc.c"})
}
// See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile
sources := []string{
"libc-top-half/musl/src/misc/a64l.c",
"libc-top-half/musl/src/misc/basename.c",
"libc-top-half/musl/src/misc/dirname.c",
"libc-top-half/musl/src/misc/ffs.c",
"libc-top-half/musl/src/misc/ffsl.c",
"libc-top-half/musl/src/misc/ffsll.c",
"libc-top-half/musl/src/misc/fmtmsg.c",
"libc-top-half/musl/src/misc/getdomainname.c",
"libc-top-half/musl/src/misc/gethostid.c",
"libc-top-half/musl/src/misc/getopt.c",
"libc-top-half/musl/src/misc/getopt_long.c",
"libc-top-half/musl/src/misc/getsubopt.c",
"libc-top-half/musl/src/misc/uname.c",
"libc-top-half/musl/src/misc/nftw.c",
"libc-top-half/musl/src/errno/strerror.c",
"libc-top-half/musl/src/network/htonl.c",
"libc-top-half/musl/src/network/htons.c",
"libc-top-half/musl/src/network/ntohl.c",
"libc-top-half/musl/src/network/ntohs.c",
"libc-top-half/musl/src/network/inet_ntop.c",
"libc-top-half/musl/src/network/inet_pton.c",
"libc-top-half/musl/src/network/inet_aton.c",
"libc-top-half/musl/src/network/in6addr_any.c",
"libc-top-half/musl/src/network/in6addr_loopback.c",
"libc-top-half/musl/src/fenv/fenv.c",
"libc-top-half/musl/src/fenv/fesetround.c",
"libc-top-half/musl/src/fenv/feupdateenv.c",
"libc-top-half/musl/src/fenv/fesetexceptflag.c",
"libc-top-half/musl/src/fenv/fegetexceptflag.c",
"libc-top-half/musl/src/fenv/feholdexcept.c",
"libc-top-half/musl/src/exit/exit.c",
"libc-top-half/musl/src/exit/atexit.c",
"libc-top-half/musl/src/exit/assert.c",
"libc-top-half/musl/src/exit/quick_exit.c",
"libc-top-half/musl/src/exit/at_quick_exit.c",
"libc-top-half/musl/src/time/strftime.c",
"libc-top-half/musl/src/time/asctime.c",
"libc-top-half/musl/src/time/asctime_r.c",
"libc-top-half/musl/src/time/ctime.c",
"libc-top-half/musl/src/time/ctime_r.c",
"libc-top-half/musl/src/time/wcsftime.c",
"libc-top-half/musl/src/time/strptime.c",
"libc-top-half/musl/src/time/difftime.c",
"libc-top-half/musl/src/time/timegm.c",
"libc-top-half/musl/src/time/ftime.c",
"libc-top-half/musl/src/time/gmtime.c",
"libc-top-half/musl/src/time/gmtime_r.c",
"libc-top-half/musl/src/time/timespec_get.c",
"libc-top-half/musl/src/time/getdate.c",
"libc-top-half/musl/src/time/localtime.c",
"libc-top-half/musl/src/time/localtime_r.c",
"libc-top-half/musl/src/time/mktime.c",
"libc-top-half/musl/src/time/__tm_to_secs.c",
"libc-top-half/musl/src/time/__month_to_secs.c",
"libc-top-half/musl/src/time/__secs_to_tm.c",
"libc-top-half/musl/src/time/__year_to_secs.c",
"libc-top-half/musl/src/time/__tz.c",
"libc-top-half/musl/src/fcntl/creat.c",
"libc-top-half/musl/src/dirent/alphasort.c",
"libc-top-half/musl/src/dirent/versionsort.c",
"libc-top-half/musl/src/env/__stack_chk_fail.c",
"libc-top-half/musl/src/env/clearenv.c",
"libc-top-half/musl/src/env/getenv.c",
"libc-top-half/musl/src/env/putenv.c",
"libc-top-half/musl/src/env/setenv.c",
"libc-top-half/musl/src/env/unsetenv.c",
"libc-top-half/musl/src/unistd/posix_close.c",
"libc-top-half/musl/src/stat/futimesat.c",
"libc-top-half/musl/src/legacy/getpagesize.c",
"libc-top-half/musl/src/thread/thrd_sleep.c",
}
basepath := goenv.Get("TINYGOROOT") + "/lib/wasi-libc/"
for _, pattern := range globs {
matches, err := filepath.Glob(basepath + pattern.glob)
if err != nil {
// From the documentation:
// > Glob ignores file system errors such as I/O errors reading
// > directories. The only possible returned error is
// > ErrBadPattern, when pattern is malformed.
// So the only possible error is when the (statically defined)
// pattern is wrong. In other words, a programming bug.
return nil, fmt.Errorf("wasi-libc: could not glob source dirs: %w", err)
}
if len(matches) == 0 {
return nil, fmt.Errorf("wasi-libc: did not find any files for pattern %#v", pattern)
}
excludeSet := map[string]struct{}{}
for _, exclude := range pattern.exclude {
excludeSet[exclude] = struct{}{}
}
for _, match := range matches {
if _, ok := excludeSet[filepath.Base(match)]; ok {
continue
}
relpath, err := filepath.Rel(basepath, match)
if err != nil {
// Not sure if this is even possible.
return nil, err
}
sources = append(sources, relpath)
}
}
return sources, nil
},
}
+3 -1
View File
@@ -29,6 +29,8 @@ var libWasmBuiltins = Library{
"-Wall",
"-std=gnu11",
"-nostdlibinc",
"-mnontrapping-fptoint", // match wasm-unknown (default on in LLVM 20)
"-mno-bulk-memory", // same here
"-isystem", libcDir + "/libc-top-half/musl/arch/wasm32",
"-isystem", libcDir + "/libc-top-half/musl/arch/generic",
"-isystem", libcDir + "/libc-top-half/musl/src/internal",
@@ -39,7 +41,7 @@ var libWasmBuiltins = Library{
}
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") },
librarySources: func(target string) ([]string, error) {
librarySources: func(target string, _ bool) ([]string, error) {
return []string{
// memory builtins needed for llvm.memcpy.*, llvm.memmove.*, and
// llvm.memset.* LLVM intrinsics.
+216 -89
View File
@@ -18,6 +18,7 @@ import (
"go/scanner"
"go/token"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -25,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
@@ -42,7 +39,8 @@ type cgoPackage struct {
fset *token.FileSet
tokenFiles map[string]*token.File
definedGlobally map[string]ast.Node
anonDecls map[interface{}]string
noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines
anonDecls map[any]string
cflags []string // CFlags from #cgo lines
ldflags []string // LDFlags from #cgo lines
visitedFiles map[string][]byte
@@ -80,21 +78,28 @@ type bitfieldInfo struct {
endBit int64 // may be 0 meaning "until the end of the field"
}
// Information about a #cgo noescape line in the source code.
type noescapingFunc struct {
name string
pos token.Pos
used bool // true if used somewhere in the source (for proper error reporting)
}
// cgoAliases list type aliases between Go and C, for types that are equivalent
// in both languages. See addTypeAliases.
var cgoAliases = map[string]string{
"C.int8_t": "int8",
"C.int16_t": "int16",
"C.int32_t": "int32",
"C.int64_t": "int64",
"C.uint8_t": "uint8",
"C.uint16_t": "uint16",
"C.uint32_t": "uint32",
"C.uint64_t": "uint64",
"C.uintptr_t": "uintptr",
"C.float": "float32",
"C.double": "float64",
"C._Bool": "bool",
"_Cgo_int8_t": "int8",
"_Cgo_int16_t": "int16",
"_Cgo_int32_t": "int32",
"_Cgo_int64_t": "int64",
"_Cgo_uint8_t": "uint8",
"_Cgo_uint16_t": "uint16",
"_Cgo_uint32_t": "uint32",
"_Cgo_uint64_t": "uint64",
"_Cgo_uintptr_t": "uintptr",
"_Cgo_float": "float32",
"_Cgo_double": "float64",
"_Cgo__Bool": "bool",
}
// builtinAliases are handled specially because they only exist on the Go side
@@ -136,38 +141,105 @@ typedef unsigned long long _Cgo_ulonglong;
// The string/bytes functions below implement C.CString etc. To make sure the
// runtime doesn't need to know the C int type, lengths are converted to uintptr
// first.
// These functions will be modified to get a "C." prefix, so the source below
// doesn't reflect the final AST.
const generatedGoFilePrefix = `
const generatedGoFilePrefixBase = `
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func CString(string) *C.char
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.GoString runtime.cgo_GoString
func GoString(*C.char) string
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func __GoStringN(*C.char, uintptr) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
func GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func __GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname C.__CBytes runtime.cgo_CBytes
func __CBytes([]byte) unsafe.Pointer
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
`
const generatedGoFilePrefixOther = generatedGoFilePrefixBase + `
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
}
`
// Windows uses fake errno values in the syscall package.
// See for example: https://github.com/golang/go/issues/23468
// TinyGo uses mingw-w64 though, which does have defined errno values. Since the
// syscall package is the standard library one we can't change it, but we can
// map the errno values to match the values in the syscall package.
// Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h
const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + `
var _Cgo___errno_mapping = [...]syscall.Errno{
1: syscall.EPERM,
2: syscall.ENOENT,
3: syscall.ESRCH,
4: syscall.EINTR,
5: syscall.EIO,
6: syscall.ENXIO,
7: syscall.E2BIG,
8: syscall.ENOEXEC,
9: syscall.EBADF,
10: syscall.ECHILD,
11: syscall.EAGAIN,
12: syscall.ENOMEM,
13: syscall.EACCES,
14: syscall.EFAULT,
16: syscall.EBUSY,
17: syscall.EEXIST,
18: syscall.EXDEV,
19: syscall.ENODEV,
20: syscall.ENOTDIR,
21: syscall.EISDIR,
22: syscall.EINVAL,
23: syscall.ENFILE,
24: syscall.EMFILE,
25: syscall.ENOTTY,
27: syscall.EFBIG,
28: syscall.ENOSPC,
29: syscall.ESPIPE,
30: syscall.EROFS,
31: syscall.EMLINK,
32: syscall.EPIPE,
33: syscall.EDOM,
34: syscall.ERANGE,
36: syscall.EDEADLK,
38: syscall.ENAMETOOLONG,
39: syscall.ENOLCK,
40: syscall.ENOSYS,
41: syscall.ENOTEMPTY,
42: syscall.EILSEQ,
}
func _Cgo___get_errno() error {
num := _Cgo___get_errno_num()
if num < uintptr(len(_Cgo___errno_mapping)) {
if mapped := _Cgo___errno_mapping[num]; mapped != 0 {
return mapped
}
}
return syscall.Errno(num)
}
`
@@ -178,7 +250,7 @@ func CBytes(b []byte) unsafe.Pointer {
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
// hashes of the accessed C header files. If there is one or more error, it
// returns these in the []error slice but still modifies the AST.
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, goos string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{
packageName: files[0].Name.Name,
currentDir: dir,
@@ -186,7 +258,8 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
fset: fset,
tokenFiles: map[string]*token.File{},
definedGlobally: map[string]ast.Node{},
anonDecls: map[interface{}]string{},
noescapingFuncs: map[string]*noescapingFunc{},
anonDecls: map[any]string{},
visitedFiles: map[string][]byte{},
}
@@ -210,7 +283,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Construct a new in-memory AST for CGo declarations of this package.
// The first part is written as Go code that is then parsed, but more code
// is added later to the AST to declare functions, globals, etc.
goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix
goCode := "package " + files[0].Name.Name + "\n\n"
if goos == "windows" {
goCode += generatedGoFilePrefixWindows
} else {
goCode += generatedGoFilePrefixOther
}
p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments)
if err != nil {
// This is always a bug in the cgo package.
@@ -220,28 +298,11 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// If the Comments field is not set to nil, the go/format package will get
// confused about where comments should go.
p.generated.Comments = nil
// Adjust some of the functions in there.
for _, decl := range p.generated.Decls {
switch decl := decl.(type) {
case *ast.FuncDecl:
switch decl.Name.Name {
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes":
// Adjust the name to have a "C." prefix so it is correctly
// resolved.
decl.Name.Name = "C." + decl.Name.Name
}
}
}
// Patch some types, for example *C.char in C.CString.
cf := p.newCGoFile(nil, -1) // dummy *cgoFile for the walker
astutil.Apply(p.generated, func(cursor *astutil.Cursor) bool {
return cf.walker(cursor, nil)
}, nil)
// Find `import "C"` C fragments in the file.
p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file
for i, f := range files {
var cgoHeader string
var cgoHeader strings.Builder
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
genDecl, ok := decl.(*ast.GenDecl)
@@ -276,7 +337,8 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Iterate through all parts of the CGo header. Note that every //
// line is a new comment.
position := fset.Position(genDecl.Doc.Pos())
fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename)
var fragment strings.Builder
fragment.WriteString(fmt.Sprintf("# %d %#v\n", position.Line, position.Filename))
for _, comment := range genDecl.Doc.List {
// Find all #cgo lines, extract and use their contents, and
// replace the lines with spaces (to preserve locations).
@@ -293,12 +355,13 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
} else { // comment
c = " " + c[2:len(c)-2]
}
fragment += c + "\n"
fragment.WriteString(c)
fragment.WriteByte('\n')
}
cgoHeader += fragment
cgoHeader.WriteString(fragment.String())
}
p.cgoHeaders[i] = cgoHeader
p.cgoHeaders[i] = cgoHeader.String()
}
// Define CFlags that will be used while parsing the package.
@@ -315,7 +378,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
Tok: token.TYPE,
}
for _, name := range builtinAliases {
typeSpec := p.getIntegerType("C."+name, names["_Cgo_"+name])
typeSpec := p.getIntegerType("_Cgo_"+name, names["_Cgo_"+name])
gen.Specs = append(gen.Specs, typeSpec)
}
p.generated.Decls = append(p.generated.Decls, gen)
@@ -344,6 +407,22 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
})
}
// Show an error when a #cgo noescape line isn't used in practice.
// This matches upstream Go. I think the goal is to avoid issues with
// misspelled function names, which seems very useful.
var unusedNoescapeLines []*noescapingFunc
for _, value := range p.noescapingFuncs {
if !value.used {
unusedNoescapeLines = append(unusedNoescapeLines, value)
}
}
sort.SliceStable(unusedNoescapeLines, func(i, j int) bool {
return unusedNoescapeLines[i].pos < unusedNoescapeLines[j].pos
})
for _, value := range unusedNoescapeLines {
p.addError(value.pos, fmt.Sprintf("function %#v in #cgo noescape line is not used", value.name))
}
// Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated)
@@ -409,6 +488,33 @@ func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) strin
}
text = text[:lineStart] + string(spaces) + text[lineEnd:]
allFields := strings.Fields(line[4:])
switch allFields[0] {
case "noescape":
// The code indicates that pointer parameters will not be captured
// by the called C function.
if len(allFields) < 2 {
p.addErrorAfter(pos, text[:lineStart], "missing function name in #cgo noescape line")
continue
}
if len(allFields) > 2 {
p.addErrorAfter(pos, text[:lineStart], "multiple function names in #cgo noescape line")
continue
}
name := allFields[1]
p.noescapingFuncs[name] = &noescapingFunc{
name: name,
pos: pos,
used: false,
}
continue
case "nocallback":
// We don't do anything special when calling a C function, so there
// appears to be no optimization that we can do here.
// Accept, but ignore the parameter for compatibility.
continue
}
// Get the text before the colon in the #cgo directive.
colon := strings.IndexByte(line, ':')
if colon < 0 {
@@ -546,7 +652,6 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
X: &ast.Ident{
NamePos: pos,
Name: "union",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: pos,
@@ -600,7 +705,6 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
X: &ast.Ident{
NamePos: pos,
Name: typeName,
Obj: nil,
},
},
},
@@ -656,7 +760,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,
@@ -703,11 +806,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{
@@ -715,7 +813,6 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
@@ -773,7 +870,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,
@@ -856,11 +952,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{
@@ -868,7 +959,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
@@ -889,7 +979,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
{
NamePos: bitfield.pos,
Name: "value",
Obj: nil,
},
},
Type: bitfield.field.Type,
@@ -907,7 +996,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,
@@ -1131,7 +1219,7 @@ func getPos(node ast.Node) token.Pos {
// getUnnamedDeclName creates a name (with the given prefix) for the given C
// declaration. This is used for structs, unions, and enums that are often
// defined without a name and used in a typedef.
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string {
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf any) string {
if name, ok := p.anonDecls[itf]; ok {
return name
}
@@ -1145,22 +1233,22 @@ func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string {
func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) string {
// Some types are defined in stdint.h and map directly to a particular Go
// type.
if alias := cgoAliases["C."+name]; alias != "" {
if alias := cgoAliases["_Cgo_"+name]; alias != "" {
return alias
}
node := f.getASTDeclNode(name, found, iscall)
node := f.getASTDeclNode(name, found)
if node, ok := node.(*ast.FuncDecl); ok {
if !iscall {
return node.Name.Name + "$funcaddr"
}
return node.Name.Name
}
return "C." + name
return "_Cgo_" + name
}
// getASTDeclNode will declare the given C AST node (if not already defined) and
// returns it.
func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) ast.Node {
func (f *cgoFile) getASTDeclNode(name string, found clangCursor) ast.Node {
if node, ok := f.defined[name]; ok {
// Declaration was found in the current file, so return it immediately.
return node
@@ -1255,8 +1343,8 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
case *elaboratedTypeInfo:
// Add struct bitfields.
for _, bitfield := range elaboratedType.bitfields {
f.createBitfieldGetter(bitfield, "C."+name)
f.createBitfieldSetter(bitfield, "C."+name)
f.createBitfieldGetter(bitfield, "_Cgo_"+name)
f.createBitfieldSetter(bitfield, "_Cgo_"+name)
}
if elaboratedType.unionSize != 0 {
// Create union getters/setters.
@@ -1265,7 +1353,7 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
f.addError(elaboratedType.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names)))
continue
}
f.createUnionAccessor(field, "C."+name)
f.createUnionAccessor(field, "_Cgo_"+name)
}
}
}
@@ -1279,6 +1367,45 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
// separate namespace (no _Cgo_ hacks like in gc).
func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool {
switch node := cursor.Node().(type) {
case *ast.AssignStmt:
// An assign statement could be something like this:
//
// val, errno := C.some_func()
//
// Check whether it looks like that, and if so, read the errno value and
// return it as the second return value. The call will be transformed
// into something like this:
//
// val, errno := C.some_func(), C.__get_errno()
if len(node.Lhs) != 2 || len(node.Rhs) != 1 {
return true
}
rhs, ok := node.Rhs[0].(*ast.CallExpr)
if !ok {
return true
}
fun, ok := rhs.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
x, ok := fun.X.(*ast.Ident)
if !ok {
return true
}
if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" {
// Replace "C"."some_func" into "C.somefunc".
rhs.Fun = &ast.Ident{
NamePos: x.NamePos,
Name: f.getASTDeclName(fun.Sel.Name, found, true),
}
// Add the errno value as the second value in the statement.
node.Rhs = append(node.Rhs, &ast.CallExpr{
Fun: &ast.Ident{
NamePos: node.Lhs[1].End(),
Name: "_Cgo___get_errno",
},
})
}
case *ast.CallExpr:
fun, ok := node.Fun.(*ast.SelectorExpr)
if !ok {
@@ -1300,7 +1427,7 @@ func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) b
return true
}
if x.Name == "C" {
name := "C." + node.Sel.Name
name := "_Cgo_" + node.Sel.Name
if found, ok := names[node.Sel.Name]; ok {
name = f.getASTDeclName(node.Sel.Name, found, false)
}
-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
}
}
+23 -5
View File
@@ -45,7 +45,6 @@ func TestCGo(t *testing.T) {
"flags",
"const",
} {
name := name // avoid a race condition
t.Run(name, func(t *testing.T) {
// Read the AST in memory.
path := filepath.Join("testdata", name+".go")
@@ -56,7 +55,7 @@ func TestCGo(t *testing.T) {
}
// Process the AST with CGo.
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags)
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "linux")
// Check the AST for type errors.
var typecheckErrors []error
@@ -64,7 +63,7 @@ func TestCGo(t *testing.T) {
Error: func(err error) {
typecheckErrors = append(typecheckErrors, err)
},
Importer: simpleImporter{},
Importer: newSimpleImporter(),
Sizes: types.SizesFor("gccgo", "arm"),
}
_, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil)
@@ -202,14 +201,33 @@ func Test_cgoPackage_isEquivalentAST(t *testing.T) {
}
// simpleImporter implements the types.Importer interface, but only allows
// importing the unsafe package.
// importing the syscall and unsafe packages.
type simpleImporter struct {
syscallPkg *types.Package
}
func newSimpleImporter() *simpleImporter {
i := &simpleImporter{}
// Implement a dummy syscall package with the Errno type.
i.syscallPkg = types.NewPackage("syscall", "syscall")
obj := types.NewTypeName(token.NoPos, i.syscallPkg, "Errno", nil)
named := types.NewNamed(obj, nil, nil)
i.syscallPkg.Scope().Insert(obj)
named.SetUnderlying(types.Typ[types.Uintptr])
sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, i.syscallPkg, "", types.Typ[types.String])), false)
named.AddMethod(types.NewFunc(token.NoPos, i.syscallPkg, "Error", sig))
i.syscallPkg.MarkComplete()
return i
}
// Import implements the Importer interface. For testing usage only: it only
// supports importing the unsafe package.
func (i simpleImporter) Import(path string) (*types.Package, error) {
func (i *simpleImporter) Import(path string) (*types.Package, error) {
switch path {
case "syscall":
return i.syscallPkg, nil
case "unsafe":
return types.Unsafe, nil
default:
+136 -4
View File
@@ -54,8 +54,72 @@ func init() {
}
// parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value)
func parseConst(pos token.Pos, fset *token.FileSet, value string, params []ast.Expr, callerPos token.Pos, f *cgoFile) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value, f)
// If params is non-nil (could be a zero length slice), this const is
// actually a function-call like expression from another macro.
// This means we have to parse a string like "(a, b) (a+b)".
// We do this by parsing the parameters at the start and then treating the
// following like a normal constant expression.
if params != nil {
// Parse opening paren.
if t.curToken != token.LPAREN {
return nil, unexpectedToken(t, token.LPAREN)
}
t.Next()
// Parse parameters (identifiers) and closing paren.
var paramIdents []string
for i := 0; ; i++ {
if i == 0 && t.curToken == token.RPAREN {
// No parameters, break early.
t.Next()
break
}
// Read the parameter name.
if t.curToken != token.IDENT {
return nil, unexpectedToken(t, token.IDENT)
}
paramIdents = append(paramIdents, t.curValue)
t.Next()
// Read the next token: either a continuation (comma) or end of list
// (rparen).
if t.curToken == token.RPAREN {
// End of parameter list.
t.Next()
break
} else if t.curToken == token.COMMA {
// Comma, so there will be another parameter name.
t.Next()
} else {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + " inside macro parameters, expected ',' or ')'",
}
}
}
// Report an error if there is a mismatch in parameter length.
// The error is reported at the location of the closing paren from the
// caller location.
if len(params) != len(paramIdents) {
return nil, &scanner.Error{
Pos: t.fset.Position(callerPos),
Msg: fmt.Sprintf("unexpected number of parameters: expected %d, got %d", len(paramIdents), len(params)),
}
}
// Assign values to the parameters.
// These parameter names are closer in 'scope' than other identifiers so
// will be used first when parsing an identifier.
for i, name := range paramIdents {
t.params[name] = params[i]
}
}
expr, err := parseConstExpr(t, precedenceLowest)
t.Next()
if t.curToken != token.EOF {
@@ -96,6 +160,68 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
}
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
// If the identifier is one of the parameters of this function-like macro,
// use the parameter value.
if val, ok := t.params[t.curValue]; ok {
return val, nil
}
if t.f != nil {
// Check whether this identifier is actually a macro "call" with
// parameters. In that case, we should parse the parameters and pass it
// on to a new invocation of parseConst.
if t.peekToken == token.LPAREN {
if cursor, ok := t.f.names[t.curValue]; ok && t.f.isFunctionLikeMacro(cursor) {
// We know the current and peek tokens (the peek one is the '('
// token). So skip ahead until the current token is the first
// unknown token.
t.Next()
t.Next()
// Parse the list of parameters until ')' (rparen) is found.
params := []ast.Expr{}
for i := 0; ; i++ {
if i == 0 && t.curToken == token.RPAREN {
break
}
x, err := parseConstExpr(t, precedenceLowest)
if err != nil {
return nil, err
}
params = append(params, x)
t.Next()
if t.curToken == token.COMMA {
t.Next()
} else if t.curToken == token.RPAREN {
break
} else {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + ", ',' or ')'",
}
}
}
// Evaluate the macro value and use it as the identifier value.
rparen := t.curPos
pos, text := t.f.getMacro(cursor)
return parseConst(pos, t.fset, text, params, rparen, t.f)
}
}
// Normally the name is something defined in the file (like another
// macro) which we get the declaration from using getASTDeclName.
// This ensures that names that are only referenced inside a macro are
// still getting defined.
if cursor, ok := t.f.names[t.curValue]; ok {
return &ast.Ident{
NamePos: t.curPos,
Name: t.f.getASTDeclName(t.curValue, cursor, false),
}, nil
}
}
// t.f is nil during testing. This is a fallback.
return &ast.Ident{
NamePos: t.curPos,
Name: "C." + t.curValue,
@@ -164,21 +290,25 @@ func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
// tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct {
f *cgoFile
curPos, peekPos token.Pos
fset *token.FileSet
curToken, peekToken token.Token
curValue, peekValue string
buf string
params map[string]ast.Expr
}
// newTokenizer initializes a new tokenizer, positioned at the first token in
// the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
func newTokenizer(start token.Pos, fset *token.FileSet, buf string, f *cgoFile) *tokenizer {
t := &tokenizer{
f: f,
peekPos: start,
fset: fset,
buf: buf,
peekToken: token.ILLEGAL,
params: make(map[string]ast.Expr),
}
// Parse the first two tokens (cur and peek).
t.Next()
@@ -230,7 +360,7 @@ func (t *tokenizer) Next() {
t.peekValue = t.buf[:2]
t.buf = t.buf[2:]
return
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
case c == '(' || c == ')' || c == ',' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
// Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c {
@@ -238,6 +368,8 @@ func (t *tokenizer) Next() {
t.peekToken = token.LPAREN
case ')':
t.peekToken = token.RPAREN
case ',':
t.peekToken = token.COMMA
case '+':
t.peekToken = token.ADD
case '-':
+1 -1
View File
@@ -59,7 +59,7 @@ func TestParseConst(t *testing.T) {
} {
fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0)
expr, err := parseConst(startPos, fset, tc.C)
expr, err := parseConst(startPos, fset, tc.C, nil, token.NoPos, nil)
s := "<invalid>"
if err != nil {
if !strings.HasPrefix(tc.Go, "error: ") {
+122 -133
View File
@@ -63,10 +63,24 @@ long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
unsigned tinygo_clang_Cursor_isAnonymous(GoCXCursor c);
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
unsigned tinygo_clang_Cursor_isMacroFunctionLike(GoCXCursor c);
// Fix some warnings on Windows ARM. Without the __declspec(dllexport), it gives warnings like this:
// In file included from _cgo_export.c:4:
// cgo-gcc-export-header-prolog:49:34: warning: redeclaration of 'tinygo_clang_globals_visitor' should not add 'dllexport' attribute [-Wdll-attribute-on-redeclaration]
// libclang.go:68:5: note: previous declaration is here
// See: https://github.com/golang/go/issues/49721
#if defined(_WIN32)
#define CGO_DECL __declspec(dllexport)
#else
#define CGO_DECL
#endif
CGO_DECL
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
CGO_DECL
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
CGO_DECL
void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
*/
import "C"
@@ -116,7 +130,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
// convert Go slice of strings to C array of strings.
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
defer C.free(cmdargsC)
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
cmdargs := unsafe.Slice((**C.char)(cmdargsC), len(cflags))
for i, cflag := range cflags {
s := C.CString(cflag)
cmdargs[i] = s
@@ -146,7 +160,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
pos := f.getClangLocationPosition(location, unit)
f.addError(pos, severity+": "+spelling)
}
for i := 0; i < numDiagnostics; i++ {
for i := range numDiagnostics {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
addDiagnostic(diagnostic)
@@ -176,7 +190,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
// Sanity check. This should (hopefully) never trigger.
panic("libclang: file contents was not loaded")
}
data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size]
data := unsafe.Slice((*byte)(unsafe.Pointer(rawData)), size)
// Hash the contents if it isn't hashed yet.
if _, ok := f.visitedFiles[path]; !ok {
@@ -203,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: "C." + name,
}
exportName := name
localName := name
var stringSignature string
@@ -243,8 +253,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
},
Name: &ast.Ident{
NamePos: pos,
Name: "C." + localName,
Obj: obj,
Name: "_Cgo_" + localName,
},
Type: &ast.FuncType{
Func: pos,
@@ -255,13 +264,21 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
},
},
}
var doc []string
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
doc = append(doc, "//go:variadic")
}
if _, ok := f.noescapingFuncs[name]; ok {
doc = append(doc, "//go:noescape")
f.noescapingFuncs[name].used = true
}
if len(doc) != 0 {
decl.Doc.List = append(decl.Doc.List, &ast.Comment{
Slash: pos - 1,
Text: "//go:variadic",
Text: strings.Join(doc, "\n"),
})
}
for i := 0; i < numArgs; i++ {
for i := range numArgs {
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
argType := C.clang_getArgType(cursorType, C.uint(i))
@@ -273,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),
@@ -293,49 +305,34 @@ 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)
typeName := "C." + name
typeName := "_Cgo_" + name
typeExpr := typ.typeExpr
if typ.unionSize != 0 {
// 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 := "C." + name
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),
Assign: pos,
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)
@@ -354,61 +351,18 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
},
},
}
obj := &ast.Object{
Kind: ast.Var,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "C." + name,
Obj: obj,
Name: "_Cgo_" + name,
}},
Type: typeExpr,
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
case C.CXCursor_MacroDefinition:
// Extract tokens from the Clang tokenizer.
// See: https://stackoverflow.com/a/19074846/559350
sourceRange := C.tinygo_clang_getCursorExtent(c)
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var rawTokens *C.CXToken
var numTokens C.unsigned
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
tokens := unsafe.Slice(rawTokens, numTokens)
// Convert this range of tokens back to source text.
// Ugly, but it works well enough.
sourceBuf := &bytes.Buffer{}
var startOffset int
for i, token := range tokens {
spelling := getString(C.clang_getTokenSpelling(tu, token))
location := C.clang_getTokenLocation(tu, token)
var tokenOffset C.unsigned
C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
if i == 0 {
// The first token is the macro name itself.
// Skip it (after using its location).
startOffset = int(tokenOffset) + len(name)
} else {
// Later tokens are the macro contents.
for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
// Pad the source text with whitespace (that must have been
// present in the original source as well).
sourceBuf.WriteByte(' ')
}
sourceBuf.WriteString(spelling)
}
}
C.clang_disposeTokens(tu, rawTokens, numTokens)
value := sourceBuf.String()
// Try to convert this #define into a Go constant expression.
tokenPos := token.NoPos
if pos != token.NoPos {
tokenPos = pos + token.Pos(len(name))
}
expr, scannerError := parseConst(tokenPos, f.fset, value)
tokenPos, value := f.getMacro(c)
expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f)
if scannerError != nil {
f.errors = append(f.errors, *scannerError)
return nil, nil
@@ -420,39 +374,27 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Lparen: token.NoPos,
Rparen: token.NoPos,
}
obj := &ast.Object{
Kind: ast.Con,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "C." + name,
Obj: obj,
Name: "_Cgo_" + name,
}},
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: "C." + 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.
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: pos,
Name: "C." + name,
Obj: obj,
Name: "_Cgo_" + name,
},
Assign: pos,
Type: f.makeASTType(underlying, pos),
}
obj.Decl = typeSpec
return typeSpec, nil
case C.CXCursor_EnumConstantDecl:
value := C.tinygo_clang_getEnumConstantDeclValue(c)
@@ -467,19 +409,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: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "C." + name,
Obj: obj,
Name: "_Cgo_" + name,
}},
Values: []ast.Expr{expr},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
default:
@@ -488,6 +424,62 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}
}
// Return whether this is a macro that's also function-like, like this:
//
// #define add(a, b) (a+b)
func (f *cgoFile) isFunctionLikeMacro(c clangCursor) bool {
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_MacroDefinition {
return false
}
return C.tinygo_clang_Cursor_isMacroFunctionLike(c) != 0
}
// Get the macro value: the position in the source file and the string value of
// the macro.
func (f *cgoFile) getMacro(c clangCursor) (pos token.Pos, value string) {
// Extract tokens from the Clang tokenizer.
// See: https://stackoverflow.com/a/19074846/559350
sourceRange := C.tinygo_clang_getCursorExtent(c)
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var rawTokens *C.CXToken
var numTokens C.unsigned
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
tokens := unsafe.Slice(rawTokens, numTokens)
defer C.clang_disposeTokens(tu, rawTokens, numTokens)
// Convert this range of tokens back to source text.
// Ugly, but it works well enough.
sourceBuf := &bytes.Buffer{}
var startOffset int
for i, token := range tokens {
spelling := getString(C.clang_getTokenSpelling(tu, token))
location := C.clang_getTokenLocation(tu, token)
var tokenOffset C.unsigned
C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
if i == 0 {
// The first token is the macro name itself.
// Skip it (after using its location).
startOffset = int(tokenOffset)
} else {
// Later tokens are the macro contents.
for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
// Pad the source text with whitespace (that must have been
// present in the original source as well).
sourceBuf.WriteByte(' ')
}
sourceBuf.WriteString(spelling)
}
}
value = sourceBuf.String()
// Obtain the position of this token. This is the position of the first
// character in the 'value' string and is used to report errors at the
// correct location in the source file.
pos = f.getCursorPosition(c)
return
}
func getString(clangString C.CXString) (s string) {
rawString := C.clang_getCString(clangString)
s = C.GoString(rawString)
@@ -540,7 +532,7 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
// Get the precise location in the source code. Used for uniquely identifying
// source locations.
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) interface{} {
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) any {
clangLocation := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile
var line C.unsigned
@@ -583,7 +575,7 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
// now by reading the file from libclang.
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
source := unsafe.Slice((*byte)(unsafe.Pointer(sourcePtr)), size)
lines := []int{0}
for i := 0; i < len(source)-1; i++ {
if source[i] == '\n' {
@@ -598,7 +590,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]
@@ -704,27 +697,27 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
var typeName string
switch typ.kind {
case C.CXType_Char_S, C.CXType_Char_U:
typeName = "C.char"
typeName = "_Cgo_char"
case C.CXType_SChar:
typeName = "C.schar"
typeName = "_Cgo_schar"
case C.CXType_UChar:
typeName = "C.uchar"
typeName = "_Cgo_uchar"
case C.CXType_Short:
typeName = "C.short"
typeName = "_Cgo_short"
case C.CXType_UShort:
typeName = "C.ushort"
typeName = "_Cgo_ushort"
case C.CXType_Int:
typeName = "C.int"
typeName = "_Cgo_int"
case C.CXType_UInt:
typeName = "C.uint"
typeName = "_Cgo_uint"
case C.CXType_Long:
typeName = "C.long"
typeName = "_Cgo_long"
case C.CXType_ULong:
typeName = "C.ulong"
typeName = "_Cgo_ulong"
case C.CXType_LongLong:
typeName = "C.longlong"
typeName = "_Cgo_longlong"
case C.CXType_ULongLong:
typeName = "C.ulonglong"
typeName = "_Cgo_ulonglong"
case C.CXType_Bool:
typeName = "bool"
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
@@ -811,6 +804,14 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
typeName = "<unknown>"
}
case C.CXType_Unexposed:
// LLVM 22+ may report certain builtin type aliases (e.g. __size_t)
// as Unexposed. Resolve via the canonical type.
canonical := C.clang_getCanonicalType(typ)
if canonical.kind != C.CXType_Unexposed && canonical.kind != C.CXType_Invalid {
return f.makeASTType(canonical, pos)
}
// If still unexposed, fall through to the error below.
case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
@@ -855,7 +856,7 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
typeSpelling := getString(C.clang_getTypeSpelling(typ))
typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind))
f.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling))
typeName = "C.<unknown>"
typeName = "_Cgo_<unknown>"
}
return &ast.Ident{
NamePos: pos,
@@ -872,7 +873,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
var goName string
typeSize := C.clang_Type_getSizeOf(underlyingType)
switch name {
case "C.char":
case "_Cgo_char":
if typeSize != 1 {
// This happens for some very special purpose architectures
// (DSPs etc.) that are not currently targeted.
@@ -885,7 +886,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
case C.CXType_Char_U:
goName = "uint8"
}
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
case "_Cgo_schar", "_Cgo_short", "_Cgo_int", "_Cgo_long", "_Cgo_longlong":
switch typeSize {
case 1:
goName = "int8"
@@ -896,7 +897,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
case 8:
goName = "int64"
}
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
case "_Cgo_uchar", "_Cgo_ushort", "_Cgo_uint", "_Cgo_ulong", "_Cgo_ulonglong":
switch typeSize {
case 1:
goName = "uint8"
@@ -915,22 +916,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
}
@@ -1063,7 +1058,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
@@ -1080,11 +1074,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
@@ -1,4 +1,4 @@
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17
//go:build !byollvm && llvm18
package cgo
+15
View File
@@ -0,0 +1,15 @@
//go:build !byollvm && llvm19
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-19 -I/usr/include/llvm-c-19 -I/usr/lib/llvm-19/include -I/usr/lib64/llvm19/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@19/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@19/include
#cgo freebsd CFLAGS: -I/usr/local/llvm19/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-19/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@19/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@19/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm19/lib -lclang
*/
import "C"
+15
View File
@@ -0,0 +1,15 @@
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm21 && !llvm22
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-20 -I/usr/include/llvm-c-20 -I/usr/lib/llvm-20/include -I/usr/lib64/llvm20/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@20/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@20/include
#cgo freebsd CFLAGS: -I/usr/local/llvm20/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-20/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@20/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@20/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm20/lib -lclang
*/
import "C"
+15
View File
@@ -0,0 +1,15 @@
//go:build !byollvm && llvm21
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-21 -I/usr/include/llvm-c-21 -I/usr/lib/llvm-21/include -I/usr/lib64/llvm21/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@21/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@21/include
#cgo freebsd CFLAGS: -I/usr/local/llvm21/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-21/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@21/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@21/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm21/lib -lclang
*/
import "C"
+15
View File
@@ -0,0 +1,15 @@
//go:build !byollvm && llvm22
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-22 -I/usr/include/llvm-c-22 -I/usr/lib/llvm-22/include -I/usr/lib64/llvm22/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@22/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@22/include
#cgo freebsd CFLAGS: -I/usr/local/llvm22/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-22/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@22/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@22/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm22/lib -lclang
*/
import "C"
+4
View File
@@ -84,3 +84,7 @@ unsigned tinygo_clang_Cursor_isAnonymous(CXCursor c) {
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
return clang_Cursor_isBitField(c);
}
unsigned tinygo_clang_Cursor_isMacroFunctionLike(CXCursor c) {
return clang_Cursor_isMacroFunctionLike(c);
}
+1
View File
@@ -78,6 +78,7 @@ var validCompilerFlags = []*regexp.Regexp{
re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?plt`),
re(`-f(no-)?rtti`),
re(`-f(no-)?short-enums`),
re(`-f(no-)?split-stack`),
re(`-f(no-)?stack-(.+)`),
re(`-f(no-)?strict-aliasing`),
+4 -4
View File
@@ -12,17 +12,17 @@ import "C"
// C. It is useful if an API uses function pointers and you cannot pass a Go
// pointer but only a C pointer.
type refMap struct {
refs map[unsafe.Pointer]interface{}
refs map[unsafe.Pointer]any
lock sync.Mutex
}
// Put stores a value in the map. It can later be retrieved using Get. It must
// be removed using Remove to avoid memory leaks.
func (m *refMap) Put(v interface{}) unsafe.Pointer {
func (m *refMap) Put(v any) unsafe.Pointer {
m.lock.Lock()
defer m.lock.Unlock()
if m.refs == nil {
m.refs = make(map[unsafe.Pointer]interface{}, 1)
m.refs = make(map[unsafe.Pointer]any, 1)
}
ref := C.malloc(1)
m.refs[ref] = v
@@ -31,7 +31,7 @@ func (m *refMap) Put(v interface{}) unsafe.Pointer {
// Get returns a stored value previously inserted with Put. Use the same
// reference as you got from Put.
func (m *refMap) Get(ref unsafe.Pointer) interface{} {
func (m *refMap) Get(ref unsafe.Pointer) any {
m.lock.Lock()
defer m.lock.Unlock()
return m.refs[ref]
+35 -27
View File
@@ -1,46 +1,54 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname C.__CBytes runtime.cgo_CBytes
func C.__CBytes([]byte) unsafe.Pointer
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
)
+16
View File
@@ -3,10 +3,26 @@ package main
/*
#define foo 3
#define bar foo
#define unreferenced 4
#define referenced unreferenced
#define fnlike() 5
#define fnlike_val fnlike()
#define square(n) (n*n)
#define square_val square(20)
#define add(a, b) (a + b)
#define add_val add(3, 5)
*/
import "C"
const (
Foo = C.foo
Bar = C.bar
Baz = C.referenced
fnlike = C.fnlike_val
square = C.square_val
add = C.add_val
)
+42 -29
View File
@@ -1,49 +1,62 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname C.__CBytes runtime.cgo_CBytes
func C.__CBytes([]byte) unsafe.Pointer
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
)
const C.foo = 3
const C.bar = C.foo
const _Cgo_foo = 3
const _Cgo_bar = _Cgo_foo
const _Cgo_unreferenced = 4
const _Cgo_referenced = _Cgo_unreferenced
const _Cgo_fnlike_val = 5
const _Cgo_square_val = (20 * 20)
const _Cgo_add_val = (3 + 5)
+13
View File
@@ -10,6 +10,11 @@ typedef struct {
typedef someType noType; // undefined type
// Some invalid noescape lines
#cgo noescape
#cgo noescape foo bar
#cgo noescape unusedFunction
#define SOME_CONST_1 5) // invalid const syntax
#define SOME_CONST_2 6) // const not used (so no error)
#define SOME_CONST_3 1234 // const too large for byte
@@ -26,6 +31,11 @@ import "C"
// #warning another warning
import "C"
// #define add(a, b) (a+b)
// #define add_toomuch add(1, 2, 3)
// #define add_toolittle add(1)
import "C"
// Make sure that errors for the following lines won't change with future
// additions to the CGo preamble.
//
@@ -51,4 +61,7 @@ var (
// constants passed by a command line parameter
_ = C.SOME_PARAM_CONST_invalid
_ = C.SOME_PARAM_CONST_valid
_ = C.add_toomuch
_ = C.add_toolittle
)
+60 -45
View File
@@ -1,74 +1,89 @@
// CGo errors:
// testdata/errors.go:14:1: missing function name in #cgo noescape line
// testdata/errors.go:15:1: multiple function names in #cgo noescape line
// testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:26:5: warning: another warning
// testdata/errors.go:13:23: unexpected token ), expected end of expression
// testdata/errors.go:21:26: unexpected token ), expected end of expression
// testdata/errors.go:16:33: unexpected token ), expected end of expression
// testdata/errors.go:17:34: unexpected token ), expected end of expression
// testdata/errors.go:31:5: warning: another warning
// testdata/errors.go:18:23: unexpected token ), expected end of expression
// testdata/errors.go:26:26: unexpected token ), expected end of expression
// testdata/errors.go:21:33: unexpected token ), expected end of expression
// testdata/errors.go:22:34: unexpected token ), expected end of expression
// -: unexpected token INT, expected end of expression
// testdata/errors.go:35:35: unexpected number of parameters: expected 2, got 3
// testdata/errors.go:36:31: unexpected number of parameters: expected 2, got 1
// testdata/errors.go:3:1: function "unusedFunction" in #cgo noescape line is not used
// Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows)
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as _Cgo_char value in variable declaration (overflows)
// testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undefined: C.SOME_CONST_1
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
// testdata/errors.go:112: undefined: C.SOME_CONST_4
// testdata/errors.go:114: undefined: C.SOME_CONST_b
// testdata/errors.go:116: undefined: C.SOME_CONST_startspace
// testdata/errors.go:119: undefined: C.SOME_PARAM_CONST_invalid
// testdata/errors.go:108: undefined: _Cgo_SOME_CONST_1
// testdata/errors.go:110: cannot use _Cgo_SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
// testdata/errors.go:112: undefined: _Cgo_SOME_CONST_4
// testdata/errors.go:114: undefined: _Cgo_SOME_CONST_b
// testdata/errors.go:116: undefined: _Cgo_SOME_CONST_startspace
// testdata/errors.go:119: undefined: _Cgo_SOME_PARAM_CONST_invalid
// testdata/errors.go:122: undefined: _Cgo_add_toomuch
// testdata/errors.go:123: undefined: _Cgo_add_toolittle
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname C.__CBytes runtime.cgo_CBytes
func C.__CBytes([]byte) unsafe.Pointer
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
)
type C.struct_point_t struct {
x C.int
y C.int
type _Cgo_struct_point_t struct {
x _Cgo_int
y _Cgo_int
}
type C.point_t = C.struct_point_t
type _Cgo_point_t = _Cgo_struct_point_t
const C.SOME_CONST_3 = 1234
const C.SOME_PARAM_CONST_valid = 3 + 4
const _Cgo_SOME_CONST_3 = 1234
const _Cgo_SOME_PARAM_CONST_valid = 3 + 4
+37 -29
View File
@@ -5,50 +5,58 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname C.__CBytes runtime.cgo_CBytes
func C.__CBytes([]byte) unsafe.Pointer
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
)
const C.BAR = 3
const C.FOO_H = 1
const _Cgo_BAR = 3
const _Cgo_FOO_H = 1
+5
View File
@@ -9,6 +9,10 @@ static void staticfunc(int x);
// Global variable signatures.
extern int someValue;
void notEscapingFunction(int *a);
#cgo noescape notEscapingFunction
*/
import "C"
@@ -18,6 +22,7 @@ func accessFunctions() {
C.variadic0()
C.variadic2(3, 5)
C.staticfunc(3)
C.notEscapingFunction(nil)
}
func accessGlobals() {
+49 -36
View File
@@ -1,71 +1,84 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname C.__CBytes runtime.cgo_CBytes
func C.__CBytes([]byte) unsafe.Pointer
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
)
//export foo
func C.foo(a C.int, b C.int) C.int
func _Cgo_foo(a _Cgo_int, b _Cgo_int) _Cgo_int
var C.foo$funcaddr unsafe.Pointer
var _Cgo_foo$funcaddr unsafe.Pointer
//export variadic0
//go:variadic
func C.variadic0()
func _Cgo_variadic0()
var C.variadic0$funcaddr unsafe.Pointer
var _Cgo_variadic0$funcaddr unsafe.Pointer
//export variadic2
//go:variadic
func C.variadic2(x C.int, y C.int)
func _Cgo_variadic2(x _Cgo_int, y _Cgo_int)
var C.variadic2$funcaddr unsafe.Pointer
var _Cgo_variadic2$funcaddr unsafe.Pointer
//export _Cgo_static_173c95a79b6df1980521_staticfunc
func C.staticfunc!symbols.go(x C.int)
func _Cgo_staticfunc!symbols.go(x _Cgo_int)
var C.staticfunc!symbols.go$funcaddr unsafe.Pointer
var _Cgo_staticfunc!symbols.go$funcaddr unsafe.Pointer
//export notEscapingFunction
//go:noescape
func _Cgo_notEscapingFunction(a *_Cgo_int)
var _Cgo_notEscapingFunction$funcaddr unsafe.Pointer
//go:extern someValue
var C.someValue C.int
var _Cgo_someValue _Cgo_int
+107 -95
View File
@@ -1,154 +1,166 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname C.__CBytes runtime.cgo_CBytes
func C.__CBytes([]byte) unsafe.Pointer
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
)
type C.myint = C.int
type C.struct_point2d_t struct {
x C.int
y C.int
type _Cgo_myint = _Cgo_int
type _Cgo_struct_point2d_t struct {
x _Cgo_int
y _Cgo_int
}
type C.point2d_t = C.struct_point2d_t
type C.struct_point3d struct {
x C.int
y C.int
z C.int
type _Cgo_point2d_t = _Cgo_struct_point2d_t
type _Cgo_struct_point3d struct {
x _Cgo_int
y _Cgo_int
z _Cgo_int
}
type C.point3d_t = C.struct_point3d
type C.struct_type1 struct {
_type C.int
__type C.int
___type C.int
type _Cgo_point3d_t = _Cgo_struct_point3d
type _Cgo_struct_type1 struct {
_type _Cgo_int
__type _Cgo_int
___type _Cgo_int
}
type C.struct_type2 struct{ _type C.int }
type C.union_union1_t struct{ i C.int }
type C.union1_t = C.union_union1_t
type C.union_union3_t struct{ $union uint64 }
type _Cgo_struct_type2 struct{ _type _Cgo_int }
type _Cgo_union_union1_t struct{ i _Cgo_int }
type _Cgo_union1_t = _Cgo_union_union1_t
type _Cgo_union_union3_t struct{ $union uint64 }
func (union *C.union_union3_t) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_union3_t) unionfield_d() *float64 {
func (union *_Cgo_union_union3_t) unionfield_i() *_Cgo_int {
return (*_Cgo_int)(unsafe.Pointer(&union.$union))
}
func (union *_Cgo_union_union3_t) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union))
}
func (union *C.union_union3_t) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union))
func (union *_Cgo_union_union3_t) unionfield_s() *_Cgo_short {
return (*_Cgo_short)(unsafe.Pointer(&union.$union))
}
type C.union3_t = C.union_union3_t
type C.union_union2d struct{ $union [2]uint64 }
type _Cgo_union3_t = _Cgo_union_union3_t
type _Cgo_union_union2d struct{ $union [2]uint64 }
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_union2d) unionfield_d() *[2]float64 {
func (union *_Cgo_union_union2d) unionfield_i() *_Cgo_int {
return (*_Cgo_int)(unsafe.Pointer(&union.$union))
}
func (union *_Cgo_union_union2d) unionfield_d() *[2]float64 {
return (*[2]float64)(unsafe.Pointer(&union.$union))
}
type C.union2d_t = C.union_union2d
type C.union_unionarray_t struct{ arr [10]C.uchar }
type C.unionarray_t = C.union_unionarray_t
type C._Ctype_union___0 struct{ $union [3]uint32 }
type _Cgo_union2d_t = _Cgo_union_union2d
type _Cgo_union_unionarray_t struct{ arr [10]_Cgo_uchar }
type _Cgo_unionarray_t = _Cgo_union_unionarray_t
type _Cgo__Ctype_union___0 struct{ $union [3]uint32 }
func (union *C._Ctype_union___0) unionfield_area() *C.point2d_t {
return (*C.point2d_t)(unsafe.Pointer(&union.$union))
func (union *_Cgo__Ctype_union___0) unionfield_area() *_Cgo_point2d_t {
return (*_Cgo_point2d_t)(unsafe.Pointer(&union.$union))
}
func (union *C._Ctype_union___0) unionfield_solid() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
func (union *_Cgo__Ctype_union___0) unionfield_solid() *_Cgo_point3d_t {
return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union))
}
type C.struct_struct_nested_t struct {
begin C.point2d_t
end C.point2d_t
tag C.int
type _Cgo_struct_struct_nested_t struct {
begin _Cgo_point2d_t
end _Cgo_point2d_t
tag _Cgo_int
coord C._Ctype_union___0
coord _Cgo__Ctype_union___0
}
type C.struct_nested_t = C.struct_struct_nested_t
type C.union_union_nested_t struct{ $union [2]uint64 }
type _Cgo_struct_nested_t = _Cgo_struct_struct_nested_t
type _Cgo_union_union_nested_t struct{ $union [2]uint64 }
func (union *C.union_union_nested_t) unionfield_point() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
func (union *_Cgo_union_union_nested_t) unionfield_point() *_Cgo_point3d_t {
return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_union_nested_t) unionfield_array() *C.unionarray_t {
return (*C.unionarray_t)(unsafe.Pointer(&union.$union))
func (union *_Cgo_union_union_nested_t) unionfield_array() *_Cgo_unionarray_t {
return (*_Cgo_unionarray_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_union_nested_t) unionfield_thing() *C.union3_t {
return (*C.union3_t)(unsafe.Pointer(&union.$union))
func (union *_Cgo_union_union_nested_t) unionfield_thing() *_Cgo_union3_t {
return (*_Cgo_union3_t)(unsafe.Pointer(&union.$union))
}
type C.union_nested_t = C.union_union_nested_t
type C.enum_option = C.int
type C.option_t = C.enum_option
type C.enum_option2_t = C.uint
type C.option2_t = C.enum_option2_t
type C.struct_types_t struct {
type _Cgo_union_nested_t = _Cgo_union_union_nested_t
type _Cgo_enum_option = _Cgo_int
type _Cgo_option_t = _Cgo_enum_option
type _Cgo_enum_option2_t = _Cgo_uint
type _Cgo_option2_t = _Cgo_enum_option2_t
type _Cgo_struct_types_t struct {
f float32
d float64
ptr *C.int
ptr *_Cgo_int
}
type C.types_t = C.struct_types_t
type C.myIntArray = [10]C.int
type C.struct_bitfield_t struct {
start C.uchar
__bitfield_1 C.uchar
type _Cgo_types_t = _Cgo_struct_types_t
type _Cgo_myIntArray = [10]_Cgo_int
type _Cgo_struct_bitfield_t struct {
start _Cgo_uchar
__bitfield_1 _Cgo_uchar
d C.uchar
e C.uchar
d _Cgo_uchar
e _Cgo_uchar
}
func (s *C.struct_bitfield_t) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
func (s *C.struct_bitfield_t) set_bitfield_a(value C.uchar) {
func (s *_Cgo_struct_bitfield_t) bitfield_a() _Cgo_uchar { return s.__bitfield_1 & 0x1f }
func (s *_Cgo_struct_bitfield_t) set_bitfield_a(value _Cgo_uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
}
func (s *C.struct_bitfield_t) bitfield_b() C.uchar {
func (s *_Cgo_struct_bitfield_t) bitfield_b() _Cgo_uchar {
return s.__bitfield_1 >> 5 & 0x1
}
func (s *C.struct_bitfield_t) set_bitfield_b(value C.uchar) {
func (s *_Cgo_struct_bitfield_t) set_bitfield_b(value _Cgo_uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5
}
func (s *C.struct_bitfield_t) bitfield_c() C.uchar {
func (s *_Cgo_struct_bitfield_t) bitfield_c() _Cgo_uchar {
return s.__bitfield_1 >> 6
}
func (s *C.struct_bitfield_t) set_bitfield_c(value C.uchar,
func (s *_Cgo_struct_bitfield_t) set_bitfield_c(value _Cgo_uchar,
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
type C.bitfield_t = C.struct_bitfield_t
type _Cgo_bitfield_t = _Cgo_struct_bitfield_t
+149 -89
View File
@@ -8,12 +8,25 @@ import (
"os"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
"github.com/google/shlex"
"github.com/tinygo-org/tinygo/goenv"
)
// Library versions. Whenever an existing library is changed, this number should
// be added/increased so that existing caches are invalidated.
//
// (This is a bit of a layering violation, this should really be part of the
// builder.Library struct but that's hard to do since we want to know the
// library path in advance in several places).
var libVersions = map[string]int{
"musl": 3,
"bdwgc": 2,
}
// Config keeps all configuration affecting the build in a single struct.
type Config struct {
Options *Options
@@ -48,13 +61,15 @@ func (c *Config) BuildMode() string {
// RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
// will be returned.
func (c *Config) Features() string {
var features string
if c.Target.Features == "" {
return c.Options.LLVMFeatures
features = c.Options.LLVMFeatures
} else if c.Options.LLVMFeatures == "" {
features = c.Target.Features
} else {
features = c.Target.Features + "," + c.Options.LLVMFeatures
}
if c.Options.LLVMFeatures == "" {
return c.Target.Features
}
return c.Target.Features + "," + c.Options.LLVMFeatures
return patchFeatures(features)
}
// ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length
@@ -99,6 +114,11 @@ func (c *Config) BuildTags() []string {
"math_big_pure_go", // to get math/big to work
"gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package
"serial." + c.Serial()}...) // used inside the machine package
switch c.Scheduler() {
case "threads", "cores":
default:
tags = append(tags, "tinygo.unicore")
}
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
@@ -122,14 +142,8 @@ func (c *Config) GC() string {
// that can be traced by the garbage collector.
func (c *Config) NeedsStackObjects() bool {
switch c.GC() {
case "conservative", "custom", "precise":
for _, tag := range c.BuildTags() {
if tag == "tinygo.wasm" {
return true
}
}
return false
case "conservative", "custom", "precise", "boehm":
return slices.Contains(c.BuildTags(), "tinygo.wasm")
default:
return false
}
@@ -209,7 +223,7 @@ func (c *Config) StackSize() uint64 {
// MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap.
func (c *Config) MaxStackAlloc() uint64 {
if c.StackSize() > 32*1024 {
if c.StackSize() >= 16*1024 {
return 1024
}
@@ -228,7 +242,7 @@ func (c *Config) RP2040BootPatch() bool {
// Return a canonicalized architecture name, so we don't have to deal with arm*
// vs thumb* vs arm64.
func CanonicalArchName(triple string) string {
arch := strings.Split(triple, "-")[0]
arch, _, _ := strings.Cut(triple, "-")
if arch == "arm64" {
return "aarch64"
}
@@ -247,10 +261,18 @@ func MuslArchitecture(triple string) string {
return CanonicalArchName(triple)
}
// LibcPath returns the path to the libc directory. The libc path will be either
// a precompiled libc shipped with a TinyGo build, or a libc path in the cache
// directory (which might not yet be built).
func (c *Config) LibcPath(name string) (path string, precompiled bool) {
// Returns true if the libc needs to include malloc, for the libcs where this
// matters.
func (c *Config) LibcNeedsMalloc() bool {
if c.GC() == "boehm" && c.Target.Libc == "wasi-libc" {
return true
}
return false
}
// LibraryPath returns the path to the library build directory. The path will be
// a library path in the cache directory (which might not yet be built).
func (c *Config) LibraryPath(name string) string {
archname := c.Triple()
if c.CPU() != "" {
archname += "-" + c.CPU()
@@ -261,18 +283,24 @@ func (c *Config) LibcPath(name string) (path string, precompiled bool) {
if c.Target.SoftFloat {
archname += "-softfloat"
}
if name == "bdwgc" {
// Boehm GC is compiled against a particular libc.
archname += "-" + c.Target.Libc
}
// Try to load a precompiled library.
precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name)
if _, err := os.Stat(precompiledDir); err == nil {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return precompiledDir, true
// Append a version string, if this library has a version.
if v, ok := libVersions[name]; ok {
archname += "-v" + strconv.Itoa(v)
}
options := ""
if c.LibcNeedsMalloc() {
options += "+malloc"
}
// No precompiled library found. Determine the path name that will be used
// in the build cache.
return filepath.Join(goenv.Get("GOCACHE"), name+"-"+archname), false
return filepath.Join(goenv.Get("GOCACHE"), name+options+"-"+archname)
}
// DefaultBinaryExtension returns the default extension for binaries, such as
@@ -315,63 +343,13 @@ func (c *Config) CFlags(libclang bool) []string {
"-resource-dir="+resourceDir,
)
}
switch c.Target.Libc {
case "darwin-libSystem":
root := goenv.Get("TINYGOROOT")
cflags = append(cflags,
"-nostdlibinc",
"-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"),
)
case "picolibc":
root := goenv.Get("TINYGOROOT")
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
path, _ := c.LibcPath("picolibc")
cflags = append(cflags,
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(picolibcDir, "include"),
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
)
case "musl":
root := goenv.Get("TINYGOROOT")
path, _ := c.LibcPath("musl")
arch := MuslArchitecture(c.Triple())
cflags = append(cflags,
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
"-isystem", filepath.Join(root, "lib", "musl", "include"),
)
case "wasi-libc":
root := goenv.Get("TINYGOROOT")
cflags = append(cflags,
"-nostdlibinc",
"-isystem", root+"/lib/wasi-libc/sysroot/include")
case "wasmbuiltins":
// nothing to add (library is purely for builtins)
case "mingw-w64":
root := goenv.Get("TINYGOROOT")
path, _ := c.LibcPath("mingw-w64")
cflags = append(cflags,
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
"-D_UCRT",
)
case "":
// No libc specified, nothing to add.
default:
// Incorrect configuration. This could be handled in a better way, but
// usually this will be found by developers (not by TinyGo users).
panic("unknown libc: " + c.Target.Libc)
}
cflags = append(cflags, c.LibcCFlags()...)
// Always emit debug information. It is optionally stripped at link time.
cflags = append(cflags, "-gdwarf-4")
// Use the same optimization level as TinyGo.
cflags = append(cflags, "-O"+c.Options.Opt)
// Set the LLVM target triple.
cflags = append(cflags, "--target="+c.Triple())
cflags = append(cflags, "--target="+ClangTriple(c.Triple()))
// Set the -mcpu (or similar) flag.
if c.Target.CPU != "" {
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
@@ -392,6 +370,80 @@ func (c *Config) CFlags(libclang bool) []string {
return cflags
}
// LibcCFlags returns the C compiler flags for the configured libc.
// It only uses flags that are part of the libc path (triple, cpu, abi, libc
// name) so it can safely be used to compile another C library.
func (c *Config) LibcCFlags() []string {
switch c.Target.Libc {
case "darwin-libSystem":
root := goenv.Get("TINYGOROOT")
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"),
}
case "picolibc":
root := goenv.Get("TINYGOROOT")
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
path := c.LibraryPath("picolibc")
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(picolibcDir, "include"),
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
}
case "musl":
root := goenv.Get("TINYGOROOT")
path := c.LibraryPath("musl")
arch := MuslArchitecture(c.Triple())
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
"-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"),
"-isystem", filepath.Join(root, "lib", "musl", "include"),
}
case "wasi-libc":
path := c.LibraryPath("wasi-libc")
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
}
case "wasmbuiltins":
// nothing to add (library is purely for builtins)
return nil
case "mingw-w64":
root := goenv.Get("TINYGOROOT")
path := c.LibraryPath("mingw-w64")
cflags := []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "include"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
}
if c.GOARCH() == "386" {
cflags = append(cflags,
"-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002
"-D_WIN32_WINNT=0x0501", // target Windows XP
)
} else {
cflags = append(cflags,
"-D_UCRT",
"-D_WIN32_WINNT=0x0a00", // target Windows 10
)
}
return cflags
case "":
// No libc specified, nothing to add.
return nil
default:
// Incorrect configuration. This could be handled in a better way, but
// usually this will be found by developers (not by TinyGo users).
panic("unknown libc: " + c.Target.Libc)
}
}
// LDFlags returns the flags to pass to the linker. A few more flags are needed
// (like the one for the compiler runtime), but this represents the majority of
// the flags.
@@ -406,19 +458,27 @@ func (c *Config) LDFlags() []string {
if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
}
if c.Options.ExtLDFlags != "" {
ext, err := shlex.Split(c.Options.ExtLDFlags)
if err != nil {
// if shlex can't split it, pass it as-is and let the external linker complain
ext = []string{c.Options.ExtLDFlags}
}
ldflags = append(ldflags, ext...)
}
ldflags = append(ldflags, c.Options.ExtLDFlags...)
return ldflags
}
// LinkerFlavor returns how the configured linker should be driven.
// Usually this is derived from GOOS, but targets may override it explicitly.
func (c *Config) LinkerFlavor() string {
if c.Target.LinkerFlavor != "" {
return c.Target.LinkerFlavor
}
switch c.GOOS() {
case "windows":
return "coff"
case "darwin":
return "darwin"
default:
return "gnu"
}
}
// ExtraFiles returns the list of extra files to be built and linked with the
// executable. This can include extra C and assembly files.
func (c *Config) ExtraFiles() []string {
@@ -489,10 +549,10 @@ func (c *Config) Programmer() (method, openocdInterface string) {
case "":
// No configuration supplied.
return c.Target.FlashMethod, c.Target.OpenOCDInterface
case "openocd", "msd", "command":
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:
+9
View File
@@ -0,0 +1,9 @@
//go:build !llvm22 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19
package compileopts
// patchFeatures applies LLVM-version-specific feature name mappings.
// For LLVM 20/21, features in the target JSON files are already correct.
func patchFeatures(features string) string {
return features
}
+26
View File
@@ -0,0 +1,26 @@
//go:build llvm22
package compileopts
import "strings"
// patchFeatures applies LLVM-version-specific feature name mappings.
// LLVM 22 renamed several Xtensa target features.
func patchFeatures(features string) string {
// Xtensa feature renames in LLVM 22:
// atomctl → (removed, no direct replacement)
// memctl → (removed, no direct replacement)
// esp32s3 → esp32s3ops
// timerint → timers3 (for esp32/esp32s3) or timers1 (for esp8266)
// Since we can't distinguish which timer variant at this level,
// just remove the obsolete features. The CPU definition already
// implies the correct features in LLVM 22.
replacer := strings.NewReplacer(
"+atomctl,", "",
"+memctl,", "",
"+esp32s3,", "+esp32s3ops,",
"+timerint,", "",
",+timerint", "",
)
return replacer.Replace(features)
}
+16
View File
@@ -0,0 +1,16 @@
//go:build llvm14 || llvm15 || llvm16 || llvm17 || llvm18 || llvm19
package compileopts
import "strings"
// patchFeatures applies LLVM-version-specific feature name mappings.
// LLVM 19 and earlier do not have +bulk-memory-opt or
// +call-indirect-overlong for WebAssembly (added in LLVM 20).
func patchFeatures(features string) string {
features = strings.ReplaceAll(features, ",+bulk-memory-opt", "")
features = strings.ReplaceAll(features, "+bulk-memory-opt,", "")
features = strings.ReplaceAll(features, ",+call-indirect-overlong", "")
features = strings.ReplaceAll(features, "+call-indirect-overlong,", "")
return features
}
+53 -58
View File
@@ -3,16 +3,17 @@ package compileopts
import (
"fmt"
"regexp"
"slices"
"strings"
"time"
)
var (
validBuildModeOptions = []string{"default", "c-shared"}
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"}
validSchedulerOptions = []string{"none", "tasks", "asyncify"}
validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"}
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"}
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads", "cores"}
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
validPrintSizeOptions = []string{"none", "short", "full"}
validPrintSizeOptions = []string{"none", "short", "full", "html"}
validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
)
@@ -21,50 +22,53 @@ var (
// usually passed from the command line, but can also be passed in environment
// variables for example.
type Options struct {
GOOS string // environment variable
GOARCH string // environment variable
GOARM string // environment variable (only used with GOARCH=arm)
GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle)
Directory string // working dir, leave it unset to use the current working dir
Target string
BuildMode string // -buildmode flag
Opt string
GC string
PanicStrategy string
Scheduler string
StackSize uint64 // goroutine stack size (if none could be automatically determined)
Serial string
Work bool // -work flag to print temporary build directory
InterpTimeout time.Duration
PrintIR bool
DumpSSA bool
VerifyIR bool
SkipDWARF bool
PrintCommands func(cmd string, args ...string) `json:"-"`
Semaphore chan struct{} `json:"-"` // -p flag controls cap
Debug bool
PrintSizes string
PrintAllocs *regexp.Regexp // regexp string
PrintStacks bool
Tags []string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
TestConfig TestConfig
Programmer string
OpenOCDCommands []string
LLVMFeatures string
PrintJSON bool
Monitor bool
BaudRate int
Timeout time.Duration
WITPackage string // pass through to wasm-tools component embed invocation
WITWorld string // pass through to wasm-tools component embed -w option
ExtLDFlags string
GOOS string // environment variable
GOARCH string // environment variable
GOARM string // environment variable (only used with GOARCH=arm)
GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle)
Directory string // working dir, leave it unset to use the current working dir
Target string
BuildMode string // -buildmode flag
Opt string
GC string
PanicStrategy string
Scheduler string
StackSize uint64 // goroutine stack size (if none could be automatically determined)
Serial string
Work bool // -work flag to print temporary build directory
InterpTimeout time.Duration
InterpMaxLoopIterations int
PrintIR bool
DumpSSA bool
VerifyIR bool
SkipDWARF bool
PrintCommands func(cmd string, args ...string) `json:"-"`
Semaphore chan struct{} `json:"-"` // -p flag controls cap
Debug bool
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
TestConfig TestConfig
Programmer string
OpenOCDCommands []string
LLVMFeatures string
Monitor bool
BaudRate int
Timeout time.Duration
WITPackage string // pass through to wasm-tools component embed invocation
WITWorld string // pass through to wasm-tools component embed -w option
ExtLDFlags []string
GoCompatibility bool // enable to check for Go version compatibility
}
// Verify performs a validation on the given options, raising an error if options are not valid.
func (o *Options) Verify() error {
if o.BuildMode != "" {
valid := isInArray(validBuildModeOptions, o.BuildMode)
valid := slices.Contains(validBuildModeOptions, o.BuildMode)
if !valid {
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
o.BuildMode,
@@ -72,7 +76,7 @@ func (o *Options) Verify() error {
}
}
if o.GC != "" {
valid := isInArray(validGCOptions, o.GC)
valid := slices.Contains(validGCOptions, o.GC)
if !valid {
return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
o.GC,
@@ -81,7 +85,7 @@ func (o *Options) Verify() error {
}
if o.Scheduler != "" {
valid := isInArray(validSchedulerOptions, o.Scheduler)
valid := slices.Contains(validSchedulerOptions, o.Scheduler)
if !valid {
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
o.Scheduler,
@@ -90,7 +94,7 @@ func (o *Options) Verify() error {
}
if o.Serial != "" {
valid := isInArray(validSerialOptions, o.Serial)
valid := slices.Contains(validSerialOptions, o.Serial)
if !valid {
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
o.Serial,
@@ -99,7 +103,7 @@ func (o *Options) Verify() error {
}
if o.PrintSizes != "" {
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
valid := slices.Contains(validPrintSizeOptions, o.PrintSizes)
if !valid {
return fmt.Errorf(`invalid size option '%s': valid values are %s`,
o.PrintSizes,
@@ -108,7 +112,7 @@ func (o *Options) Verify() error {
}
if o.PanicStrategy != "" {
valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
valid := slices.Contains(validPanicStrategyOptions, o.PanicStrategy)
if !valid {
return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
o.PanicStrategy,
@@ -117,19 +121,10 @@ func (o *Options) Verify() error {
}
if o.Opt != "" {
if !isInArray(validOptOptions, o.Opt) {
if !slices.Contains(validOptOptions, o.Opt) {
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
}
}
return nil
}
func isInArray(arr []string, item string) bool {
for _, i := range arr {
if i == item {
return true
}
}
return false
}
+3 -3
View File
@@ -9,9 +9,9 @@ import (
func TestVerifyOptions(t *testing.T) {
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`)
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`)
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise, boehm`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify, threads, cores`)
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`)
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
testCases := []struct {
+77 -37
View File
@@ -24,6 +24,7 @@ import (
// https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json
type TargetSpec struct {
Inherits []string `json:"inherits,omitempty"`
InheritableOnly bool `json:"inheritable-only"` // this target is only meant to be inherited from, not used directly
Triple string `json:"llvm-target,omitempty"`
CPU string `json:"cpu,omitempty"`
ABI string `json:"target-abi,omitempty"` // roughly equivalent to -mabi= flag
@@ -37,7 +38,8 @@ type TargetSpec struct {
Scheduler string `json:"scheduler,omitempty"`
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none)
Linker string `json:"linker,omitempty"`
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
LinkerFlavor string `json:"linker-flavor,omitempty"` // how to drive the configured linker (for example: gnu, coff, darwin)
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc,omitempty"`
AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time.
DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time.
@@ -46,6 +48,7 @@ type TargetSpec struct {
LinkerScript string `json:"linkerscript,omitempty"`
ExtraFiles []string `json:"extra-files,omitempty"`
RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum
BootPatches []string `json:"boot-patches,omitempty"` // Bootloader patches to be applied in the order they appear.
Emulator string `json:"emulator,omitempty"`
FlashCommand string `json:"flash-command,omitempty"`
GDB []string `json:"gdb,omitempty"`
@@ -62,6 +65,10 @@ type TargetSpec struct {
OpenOCDCommands []string `json:"openocd-commands,omitempty"`
OpenOCDVerify *bool `json:"openocd-verify,omitempty"` // enable verify when flashing with openocd
JLinkDevice string `json:"jlink-device,omitempty"`
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"`
@@ -147,6 +154,11 @@ func (spec *TargetSpec) loadFromGivenStr(str string) error {
// resolveInherits loads inherited targets, recursively.
func (spec *TargetSpec) resolveInherits() error {
// Save InheritableOnly before resolving, since it must not propagate
// from parent to child (a board target should not become inheritable-only
// just because its parent processor target is).
inheritableOnly := spec.InheritableOnly
// First create a new spec with all the inherited properties.
newSpec := &TargetSpec{}
for _, name := range spec.Inherits {
@@ -172,11 +184,29 @@ func (spec *TargetSpec) resolveInherits() error {
}
*spec = *newSpec
// Restore InheritableOnly from the original spec, not from parents.
spec.InheritableOnly = inheritableOnly
return nil
}
// Load a target specification.
func LoadTarget(options *Options) (*TargetSpec, error) {
if options.Target == "" && options.GOARCH == "wasm" {
// Set a specific target if we're building from a known GOOS/GOARCH
// combination that is defined in a target JSON file.
switch options.GOOS {
case "js":
options.Target = "wasm"
case "wasip1":
options.Target = "wasip1"
case "wasip2":
options.Target = "wasip2"
default:
return nil, errors.New("GOARCH=wasm but GOOS is not set correctly. Please set GOOS to js, wasip1, or wasip2.")
}
}
if options.Target == "" {
return defaultTarget(options)
}
@@ -223,10 +253,17 @@ func GetTargetSpecs() (map[string]*TargetSpec, error) {
continue
}
path := filepath.Join(dir, entry.Name())
spec, err := LoadTarget(&Options{Target: path})
if err != nil {
return nil, fmt.Errorf("could not list target: %w", err)
}
if spec.InheritableOnly {
// Skip targets that are only meant to be inherited from, not used directly.
continue
}
if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" {
// This doesn't look like a regular target file, but rather like
// a parent target (such as targets/cortex-m.json).
@@ -246,8 +283,6 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
GOOS: options.GOOS,
GOARCH: options.GOARCH,
BuildTags: []string{options.GOOS, options.GOARCH},
GC: "precise",
Scheduler: "tasks",
Linker: "cc",
DefaultStackSize: 1024 * 64, // 64kB
GDB: []string{"gdb"},
@@ -327,14 +362,14 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
spec.CPU = "generic"
llvmarch = "aarch64"
if options.GOOS == "darwin" {
spec.Features = "+fp-armv8,+neon"
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a"
// Looks like Apple prefers to call this architecture ARM64
// instead of AArch64.
llvmarch = "arm64"
} else if options.GOOS == "windows" {
spec.Features = "+fp-armv8,+neon,-fmv"
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv"
} else { // linux
spec.Features = "+fp-armv8,+neon,-fmv,-outline-atomics"
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv,-outline-atomics"
}
case "mips", "mipsle":
spec.CPU = "mips32"
@@ -355,15 +390,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
return nil, fmt.Errorf("invalid GOMIPS=%s: must be hardfloat or softfloat", options.GOMIPS)
}
case "wasm":
llvmarch = "wasm32"
spec.CPU = "generic"
spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext"
spec.BuildTags = append(spec.BuildTags, "tinygo.wasm")
spec.CFlags = append(spec.CFlags,
"-mbulk-memory",
"-mnontrapping-fptoint",
"-msign-ext",
)
return nil, fmt.Errorf("GOARCH=wasm but GOOS is unset. Please set GOOS to js, wasip1, or wasip2.")
default:
return nil, fmt.Errorf("unknown GOARCH=%s", options.GOARCH)
}
@@ -373,11 +400,13 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
llvmvendor := "unknown"
switch options.GOOS {
case "darwin":
spec.GC = "boehm"
platformVersion := "10.12.0"
if options.GOARCH == "arm64" {
platformVersion = "11.0.0" // first macosx platform with arm64 support
}
llvmvendor = "apple"
spec.Scheduler = "threads"
spec.Linker = "ld.lld"
spec.Libc = "darwin-libSystem"
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer to
@@ -390,10 +419,14 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
"-platform_version", "macos", platformVersion, platformVersion,
)
spec.ExtraFiles = append(spec.ExtraFiles,
"src/internal/futex/futex_darwin.c",
"src/internal/task/task_threads.c",
"src/runtime/os_darwin.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
case "linux":
spec.GC = "boehm"
spec.Scheduler = "threads"
spec.Linker = "ld.lld"
spec.RTLib = "compiler-rt"
spec.Libc = "musl"
@@ -413,27 +446,41 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
spec.CFlags = append(spec.CFlags, "-mno-outline-atomics")
}
spec.ExtraFiles = append(spec.ExtraFiles,
"src/internal/futex/futex_linux.c",
"src/internal/task/task_threads.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
case "windows":
spec.GC = "boehm"
spec.Scheduler = "tasks"
spec.Linker = "ld.lld"
spec.Libc = "mingw-w64"
// Note: using a medium code model, low image base and no ASLR
// because Go doesn't really need those features. ASLR patches
// around issues for unsafe languages like C/C++ that are not
// normally present in Go (without explicitly opting in).
// For more discussion:
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
switch options.GOARCH {
case "386":
spec.LDFlags = append(spec.LDFlags,
"-m", "i386pe",
"--major-os-version", "4",
"--major-subsystem-version", "4",
)
// __udivdi3 is not present in ucrt it seems.
spec.RTLib = "compiler-rt"
case "amd64":
// Note: using a medium code model, low image base and no ASLR
// because Go doesn't really need those features. ASLR patches
// around issues for unsafe languages like C/C++ that are not
// normally present in Go (without explicitly opting in).
// For more discussion:
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
spec.LDFlags = append(spec.LDFlags,
"-m", "i386pep",
"--image-base", "0x400000",
)
spec.RTLib = "compiler-rt"
case "arm64":
spec.LDFlags = append(spec.LDFlags,
"-m", "arm64pe",
)
spec.RTLib = "compiler-rt"
}
spec.LDFlags = append(spec.LDFlags,
"-Bdynamic",
@@ -441,27 +488,20 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
"--no-insert-timestamp",
"--no-dynamicbase",
)
case "wasip1":
spec.GC = "" // use default GC
spec.Scheduler = "asyncify"
spec.Linker = "wasm-ld"
spec.RTLib = "compiler-rt"
spec.Libc = "wasi-libc"
spec.DefaultStackSize = 1024 * 64 // 64kB
spec.LDFlags = append(spec.LDFlags,
"--stack-first",
"--no-demangle",
)
spec.Emulator = "wasmtime --dir={tmpDir}::/tmp {}"
spec.ExtraFiles = append(spec.ExtraFiles,
"src/runtime/asm_tinygowasm.S",
"src/internal/task/task_asyncify_wasm.S",
)
llvmos = "wasi"
"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:
return nil, fmt.Errorf("unknown GOOS=%s", options.GOOS)
}
if spec.GC == "boehm" {
// Add this file only when needed. This fixes a build failure on
// Windows.
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_boehm.c")
}
// Target triples (which actually have four components, but are called
// triples for historical reasons) have the form:
// arch-vendor-os-environment
+80
View File
@@ -23,6 +23,37 @@ func TestLoadTarget(t *testing.T) {
}
}
func TestGetTargetSpecs_InheritableOnlyTargetsExcluded(t *testing.T) {
specs, err := GetTargetSpecs()
if err != nil {
t.Fatal("GetTargetSpecs failed:", err)
}
// Inheritable-only processor-level targets should not appear in the listing.
inheritableOnlyTargets := []string{"esp32", "esp32c3", "esp32s3", "esp8266", "rp2040", "rp2350", "rp2350b"}
for _, name := range inheritableOnlyTargets {
if _, ok := specs[name]; ok {
t.Errorf("inheritable-only target %q should not appear in GetTargetSpecs", name)
}
}
// Board targets that inherit from inheritable-only targets should still appear.
boardTargets := []string{"esp32-coreboard-v2", "pico"}
for _, name := range boardTargets {
if _, ok := specs[name]; !ok {
t.Errorf("board target %q should appear in GetTargetSpecs", name)
}
}
}
func TestLoadTarget_InheritableOnlyTargetStillLoadable(t *testing.T) {
// Inheritable-only targets should still be loadable directly (for building).
_, err := LoadTarget(&Options{Target: "esp32"})
if err != nil {
t.Errorf("LoadTarget should still load inheritable-only target esp32: %v", err)
}
}
func TestOverrideProperties(t *testing.T) {
baseAutoStackSize := true
base := &TargetSpec{
@@ -81,3 +112,52 @@ func TestOverrideProperties(t *testing.T) {
}
}
func TestConfigLinkerFlavor(t *testing.T) {
tests := []struct {
name string
target *TargetSpec
goos string
want string
}{
{
name: "default gnu",
target: &TargetSpec{},
goos: "linux",
want: "gnu",
},
{
name: "default coff",
target: &TargetSpec{},
goos: "windows",
want: "coff",
},
{
name: "default darwin",
target: &TargetSpec{},
goos: "darwin",
want: "darwin",
},
{
name: "target override",
target: &TargetSpec{
LinkerFlavor: "coff",
},
goos: "linux",
want: "coff",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc.target.GOOS = tc.goos
config := &Config{
Options: &Options{},
Target: tc.target,
}
if got := config.LinkerFlavor(); got != tc.want {
t.Fatalf("LinkerFlavor() = %q, want %q", got, tc.want)
}
})
}
}
+12
View File
@@ -0,0 +1,12 @@
//go:build llvm22
package compileopts
import "strings"
// ClangTriple returns the target triple to pass to Clang's --target flag.
// LLVM 22 deprecated the "wasm32-unknown-wasi" triple in favor of
// "wasm32-unknown-wasip1", so we substitute it here to avoid warnings.
func ClangTriple(triple string) string {
return strings.Replace(triple, "wasm32-unknown-wasi", "wasm32-unknown-wasip1", 1)
}
+9
View File
@@ -0,0 +1,9 @@
//go:build !llvm22
package compileopts
// ClangTriple returns the target triple to pass to Clang's --target flag.
// For pre-LLVM 22, the triple is used as-is.
func ClangTriple(triple string) string {
return triple
}
+6 -5
View File
@@ -18,11 +18,12 @@ var stdlibAliases = map[string]string{
// crypto packages
"crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric",
"crypto/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric",
"crypto/md5.block": "crypto/md5.blockGeneric",
"crypto/sha1.block": "crypto/sha1.blockGeneric",
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
"crypto/sha256.block": "crypto/sha256.blockGeneric",
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
"crypto/md5.block": "crypto/md5.blockGeneric",
"crypto/sha1.block": "crypto/sha1.blockGeneric",
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
"crypto/sha256.block": "crypto/sha256.blockGeneric",
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
"internal/chacha8rand.block": "internal/chacha8rand.block_generic",
// AES
"crypto/aes.decryptBlockAsm": "crypto/aes.decryptBlock",
+22 -9
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.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
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.
+71 -6
View File
@@ -19,8 +19,9 @@ const maxFieldsPerParam = 3
// useful while declaring or defining a function.
type paramInfo struct {
llvmType llvm.Type
name string // name, possibly with suffixes for e.g. struct fields
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
name string // name, possibly with suffixes for e.g. struct fields
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
flags paramFlags // extra flags for this parameter
}
// paramFlags identifies parameter attributes for flags. Most importantly, it
@@ -28,9 +29,15 @@ type paramInfo struct {
type paramFlags uint8
const (
// Parameter may have the deferenceable_or_null attribute. This attribute
// cannot be applied to unsafe.Pointer and to the data pointer of slices.
paramIsDeferenceableOrNull = 1 << iota
// Whether this is a full or partial Go parameter (int, slice, etc).
// The extra context parameter is not a Go parameter.
paramIsGoParam = 1 << iota
// Whether this is a readonly parameter (for example, a string pointer).
paramIsReadonly
// Whether this parameter is passed through backing storage.
paramIsIndirect
)
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
@@ -75,7 +82,15 @@ func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value,
fragments := b.expandFormalParam(arg)
expanded = append(expanded, fragments...)
}
return b.CreateCall(fnType, fn, expanded, name)
call := b.CreateCall(fnType, fn, expanded, name)
if !fn.IsAFunction().IsNil() {
if cc := fn.FunctionCallConv(); cc != llvm.CCallConv {
// Set a different calling convention if needed.
// This is needed for GetModuleHandleExA on Windows, for example.
call.SetInstructionCallConv(cc)
}
}
return call
}
// createInvoke is like createCall but continues execution at the landing pad if
@@ -90,6 +105,18 @@ func (b *builder) createInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Valu
// Expand an argument type to a list that can be used in a function call
// parameter list.
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
if c.isIndirectAggregate(t) {
return []paramInfo{{
llvmType: c.dataPtrType,
name: name,
elemSize: c.targetData.TypeAllocSize(t),
flags: paramIsGoParam | paramIsReadonly | paramIsIndirect,
}}
}
return c.expandDirectFormalParamType(t, name, goType)
}
func (c *compilerContext) expandDirectFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
switch t.TypeKind() {
case llvm.StructTypeKind:
fieldInfos := c.flattenAggregateType(t, name, goType)
@@ -103,6 +130,38 @@ func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType
return []paramInfo{c.getParamInfo(t, name, goType)}
}
func (c *compilerContext) storedParamType(t llvm.Type, exported bool) llvm.Type {
if c.isIndirectParam(t, exported) {
return c.dataPtrType
}
return t
}
func (c *compilerContext) isIndirectParam(t llvm.Type, exported bool) bool {
return !exported && c.isIndirectAggregate(t)
}
func (b *builder) appendStoredValueTypes(valueTypes []llvm.Type, values []ssa.Value, exported bool) []llvm.Type {
for _, value := range values {
valueTypes = append(valueTypes, b.storedParamType(b.getLLVMType(value.Type()), exported))
}
return valueTypes
}
func (b *builder) appendStoredParamTypes(valueTypes []llvm.Type, params []*types.Var, exported bool) []llvm.Type {
for _, param := range params {
valueTypes = append(valueTypes, b.storedParamType(b.getLLVMType(param.Type()), exported))
}
return valueTypes
}
func (b *builder) prependIndirectResult(sig *types.Signature, exported bool, params []llvm.Value, name string) []llvm.Value {
if resultType, indirect := b.hasIndirectResult(sig); !exported && indirect {
return append([]llvm.Value{b.createIndirectStorage(resultType, name)}, params...)
}
return params
}
// expandFormalParamOffsets returns a list of offsets from the start of an
// object of type t after it would have been split up by expandFormalParam. This
// is useful for debug information, where it is necessary to know the offset
@@ -158,6 +217,7 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
continue
}
suffix := strconv.Itoa(i)
isString := false
if goType != nil {
// Try to come up with a good suffix for this struct field,
// depending on which Go type it's based on.
@@ -174,12 +234,16 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
suffix = []string{"r", "i"}[i]
case types.String:
suffix = []string{"data", "len"}[i]
isString = true
}
case *types.Signature:
suffix = []string{"context", "funcptr"}[i]
}
}
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
if isString {
subInfos[0].flags |= paramIsReadonly
}
paramInfos = append(paramInfos, subInfos...)
}
return paramInfos
@@ -195,6 +259,7 @@ func (c *compilerContext) getParamInfo(t llvm.Type, name string, goType types.Ty
info := paramInfo{
llvmType: t,
name: name,
flags: paramIsGoParam,
}
if goType != nil {
switch underlying := goType.Underlying().(type) {
+57 -54
View File
@@ -4,7 +4,9 @@ package compiler
// or pseudo-operations that are lowered during goroutine lowering.
import (
"fmt"
"go/types"
"math"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
@@ -28,33 +30,29 @@ func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
// actual channel send operation during goroutine lowering.
func (b *builder) createChanSend(instr *ssa.Send) {
ch := b.getValue(instr.Chan, getPos(instr))
chanValue := b.getValue(instr.X, getPos(instr))
// store value-to-send
valueType := b.getLLVMType(instr.X.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaSize llvm.Value
var storage valueStorage
if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType)
storage.ptr = llvm.ConstNull(b.dataPtrType)
} else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
storage = b.getValueStorage(instr.X, "chan.value")
}
// Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Allocate buffer for the channel operation.
channelOp := b.getLLVMRuntimeType("channelOp")
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, storage.ptr, channelOpAlloca}, "")
// End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
if !isZeroSize {
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
}
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
b.endValueStorage(storage)
}
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
@@ -64,42 +62,22 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
ch := b.getValue(unop.X, getPos(unop))
// Allocate memory to receive into.
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType)
} else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
result := b.createRuntimeValueResult(valueType, unop.CommaOk, true, "chan")
// Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Allocate buffer for the channel operation.
channelOp := b.getLLVMRuntimeType("channelOp")
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
var received llvm.Value
if isZeroSize {
received = llvm.ConstNull(valueType)
} else {
received = b.CreateLoad(valueType, valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
}
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, received, 0, "")
tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
return tuple
} else {
return received
}
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, result.valuePtr, channelOpAlloca}, "")
received := result.finish(b, commaOk, "chan.received")
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
return received
}
// 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
@@ -124,6 +102,20 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}
}
const maxSelectStates = math.MaxUint32 >> 2
if len(expr.States) > maxSelectStates {
// The runtime code assumes that the number of state must fit in 30 bits
// (so the select index can be stored in a uint32 with two bits reserved
// for other purposes). It seems unlikely that a real program would have
// that many states, but we check for this case anyway to be sure.
// We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic
// operations which aren't available everywhere.
b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates))
// Continue as usual (we'll generate broken code but the error will
// prevent the compilation to complete).
}
// This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select
// statement.
@@ -154,9 +146,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
sendValue := b.getValue(state.Send, state.Pos)
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca)
alloca := b.getSelectSendStorage(state.Send)
selectState = b.CreateInsertValue(selectState, alloca, 1, "")
default:
panic("unreachable")
@@ -198,10 +188,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if expr.Blocking {
// Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate.
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{
opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates))
opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca")
opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.block")
@@ -209,15 +199,18 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
results = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
opsPtr, opsLen, opsLen, // []channelOp
}, "select.result")
// Terminate the lifetime of the operation structures.
b.emitLifetimeEnd(chBlockAlloca, chBlockSize)
b.emitLifetimeEnd(opsAlloca, opsSize)
} else {
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
opsPtr := llvm.ConstNull(b.dataPtrType)
opsLen := llvm.ConstInt(b.uintptrType, 0, false)
results = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
opsPtr, opsLen, opsLen, // []channelOp (nil slice)
}, "select.result")
}
@@ -261,7 +254,17 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
// receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it.
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := b.getLLVMType(expr.Type())
return b.CreateLoad(typ, recvbuf, "")
return b.loadFromStorage(recvbuf, expr.Type(), "select.received")
}
}
func (b *builder) getSelectSendStorage(value ssa.Value) llvm.Value {
typ := b.getLLVMType(value.Type())
if b.isIndirectAggregate(typ) {
return b.getValuePointer(value)
}
llvmValue := b.getValue(value, getPos(value))
ptr := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, "select.send.value")
b.CreateStore(llvmValue, ptr)
return ptr
}
+533 -113
View File
File diff suppressed because it is too large Load Diff
+176 -19
View File
@@ -4,6 +4,7 @@ import (
"flag"
"go/types"
"os"
"regexp"
"strconv"
"strings"
"testing"
@@ -50,6 +51,8 @@ func TestCompiler(t *testing.T) {
{"channel.go", "", ""},
{"gc.go", "", ""},
{"zeromap.go", "", ""},
{"generics.go", "", ""},
{"large.go", "", ""},
}
if goMinor >= 20 {
tests = append(tests, testCase{"go1.20.go", "", ""})
@@ -57,6 +60,9 @@ func TestCompiler(t *testing.T) {
if goMinor >= 21 {
tests = append(tests, testCase{"go1.21.go", "", ""})
}
if goMinor >= 27 {
tests = append(tests, testCase{"go1.27.go", "", ""})
}
for _, tc := range tests {
name := tc.file
@@ -121,30 +127,138 @@ func TestCompiler(t *testing.T) {
t.Fatal("failed to read golden file:", err)
}
if !fuzzyEqualIR(mod.String(), string(expected)) {
t.Errorf("output does not match expected output:\n%s", mod.String())
if diff := diffIR(string(expected), mod.String()); diff != "" {
t.Errorf("output does not match expected output (re-run with -update to regenerate):\n%s", diff)
}
})
}
}
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
// equal. That means, only relevant lines are compared (excluding comments
// etc.).
func fuzzyEqualIR(s1, s2 string) bool {
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
if len(lines1) != len(lines2) {
return false
}
for i, line1 := range lines1 {
line2 := lines2[i]
if line1 != line2 {
return false
func TestOptimizedLargeAggregateABI(t *testing.T) {
options := &compileopts.Options{Target: "wasm"}
mod, errs := testCompilePackage(t, options, "large-optimized.go")
if len(errs) != 0 {
for _, err := range errs {
t.Error(err)
}
return
}
defer mod.Dispose()
passOptions := llvm.NewPassBuilderOptions()
defer passOptions.Dispose()
if err := mod.RunPasses("default<O2>", llvm.TargetMachine{}, passOptions); err != nil {
t.Fatal(err)
}
return true
resultFn := mod.NamedFunction("main.makeLargeOptimizedValue")
if resultFn.IsNil() {
t.Fatal("missing function main.makeLargeOptimizedValue")
}
if resultType := resultFn.GlobalValueType().ReturnType(); resultType.TypeKind() != llvm.VoidTypeKind {
t.Errorf("large aggregate result was promoted to %s", resultType)
}
for _, name := range []string{
"main.makeLargeOptimizedValue",
"main.readLargeOptimizedValue",
"main.readMixedLargeOptimizedValue",
} {
fn := mod.NamedFunction(name)
if fn.IsNil() {
t.Fatalf("missing function %s", name)
}
if paramType := fn.GlobalValueType().ParamTypes()[0]; paramType.TypeKind() != llvm.PointerTypeKind {
t.Errorf("%s aggregate parameter was promoted to %s", name, paramType)
}
}
}
// normalizeIR canonicalizes LLVM IR so a single golden file keeps matching
// across LLVM versions. Golden files are written against LLVM <21; newer LLVM
// prints some attributes differently.
func normalizeIR(s string) string {
// Golden files are written using the pre-LLVM21 'nocapture' spelling,
// which LLVM printed before any co-occurring attribute such as
// 'readonly' (e.g. "ptr nocapture readonly"). LLVM 21+ prints the
// equivalent 'captures(none)' instead, and after such attributes (e.g.
// "ptr readonly captures(none)"). Normalize both name and position back
// to the old spelling.
s = normalizeCapturesAttr(s)
// LLVM 21+ also added an explicit 'nocreateundeforpoison' attribute to
// certain intrinsic declarations (e.g. llvm.umin) that were implicitly
// assumed not to create undef/poison before. It's unrelated to the
// behavior under test, so ignore it for comparison.
s = strings.ReplaceAll(s, "nocreateundeforpoison ", "")
// LLVM 22 dropped the (redundant) i64 size argument from
// llvm.lifetime.start/end. Normalize away that argument so golden files
// written against the two-argument form still match.
s = lifetimeSizeArgRe.ReplaceAllString(s, "$1")
return s
}
// diffIR compares two LLVM IR strings, ignoring irrelevant lines (comments,
// empty lines, etc.) and normalizing LLVM-version-specific spellings via
// normalizeIR. It returns "" when they are equal. Otherwise it returns a
// compact diff of only the region that differs: the common prefix and suffix
// are trimmed, then the differing expected lines (prefixed "-") are shown
// followed by the differing actual lines (prefixed "+").
func diffIR(expected, actual string) string {
exp := filterIrrelevantIRLines(strings.Split(normalizeIR(expected), "\n"))
act := filterIrrelevantIRLines(strings.Split(normalizeIR(actual), "\n"))
// Trim the common prefix.
start := 0
for start < len(exp) && start < len(act) && exp[start] == act[start] {
start++
}
// Trim the common suffix.
e, a := len(exp), len(act)
for e > start && a > start && exp[e-1] == act[a-1] {
e--
a--
}
if start == e && start == a {
return "" // equal
}
var b strings.Builder
b.WriteString("first difference at relevant line ")
b.WriteString(strconv.Itoa(start + 1))
b.WriteString(":\n")
for _, line := range exp[start:e] {
b.WriteString("- ")
b.WriteString(line)
b.WriteByte('\n')
}
for _, line := range act[start:a] {
b.WriteString("+ ")
b.WriteString(line)
b.WriteByte('\n')
}
return b.String()
}
// capturesNoneAttrRe matches a co-occurring attribute directly followed by
// 'captures(none)', which is how LLVM 21+ orders these two attributes when
// printing IR (the pre-LLVM21 'nocapture' attribute printed the other way
// around).
var capturesNoneAttrRe = regexp.MustCompile(`\b(readonly|readnone|writeonly|nonnull)\s+captures\(none\)`)
// lifetimeSizeArgRe matches the i64 size argument of an
// llvm.lifetime.start/end call or declaration, which LLVM 22 removed.
var lifetimeSizeArgRe = regexp.MustCompile(`(@llvm\.lifetime\.(?:start|end)\.p0\()i64(?: immarg| \d+), `)
// normalizeCapturesAttr rewrites LLVM 21+'s 'captures(none)' attribute back
// to the pre-LLVM21 'nocapture' spelling and position, so golden IR files
// written against LLVM <21 keep matching.
func normalizeCapturesAttr(s string) string {
s = capturesNoneAttrRe.ReplaceAllString(s, "nocapture $1")
s = strings.ReplaceAll(s, "captures(none)", "nocapture")
return s
}
// filterIrrelevantIRLines removes lines from the input slice of strings that
@@ -187,9 +301,9 @@ func TestCompilerErrors(t *testing.T) {
t.Error(err)
}
errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n")
for _, line := range strings.Split(errorsFileString, "\n") {
if strings.HasPrefix(line, "// ERROR: ") {
expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: "))
for line := range strings.SplitSeq(errorsFileString, "\n") {
if after, ok := strings.CutPrefix(line, "// ERROR: "); ok {
expectedErrors = append(expectedErrors, after)
}
}
@@ -213,6 +327,49 @@ func TestCompilerErrors(t *testing.T) {
}
}
func TestAggregateValueCount(t *testing.T) {
t.Parallel()
ctx := llvm.NewContext()
defer ctx.Dispose()
byteType := ctx.Int8Type()
tests := []struct {
name string
typ llvm.Type
count uint64
exceeded bool
}{
{"empty", llvm.ArrayType(byteType, 0), 0, false},
{"limit", llvm.ArrayType(byteType, 1024), 1024, false},
{"over limit", llvm.ArrayType(byteType, 1025), 0, true},
{"combined limit", ctx.StructType([]llvm.Type{
llvm.ArrayType(byteType, 512),
llvm.ArrayType(byteType, 512),
}, false), 1024, false},
{"combined over limit", ctx.StructType([]llvm.Type{
llvm.ArrayType(byteType, 1000),
llvm.ArrayType(byteType, 1000),
}, false), 0, true},
{"comma-ok over limit", ctx.StructType([]llvm.Type{
llvm.ArrayType(byteType, 1024),
ctx.Int1Type(),
}, false), 0, true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
count, exceeded := aggregateValueCount(test.typ, 0)
if exceeded != test.exceeded {
t.Errorf("expected exceeded=%t, got %t", test.exceeded, exceeded)
}
if !exceeded && count != test.count {
t.Errorf("expected count=%d, got %d", test.count, count)
}
})
}
}
// Build a package given a number of compiler options and a file.
func testCompilePackage(t *testing.T, options *compileopts.Options, file string) (llvm.Module, []error) {
target, err := compileopts.LoadTarget(options)
+232 -129
View File
@@ -32,7 +32,7 @@ func (b *builder) supportsRecover() bool {
// proposal of WebAssembly:
// https://github.com/WebAssembly/exception-handling
return false
case "riscv64", "xtensa":
case "xtensa":
// TODO: add support for these architectures
return false
default:
@@ -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)
}
}
@@ -100,17 +106,18 @@ func (b *builder) createLandingPad() {
// Continue at the 'recover' block, which returns to the parent in an
// appropriate way.
b.CreateBr(b.blockEntries[b.fn.Recover])
b.CreateBr(b.blockInfo[b.fn.Recover.Index].entry)
}
// createInvokeCheckpoint saves the function state at the given point, to
// continue at the landing pad if a panic happened. This is implemented using a
// setjmp-like construct.
func (b *builder) createInvokeCheckpoint() {
// Create a checkpoint (similar to setjmp). This emits inline assembly that
// stores the current program counter inside the ptr address (actually
// ptr+sizeof(ptr)) and then returns a boolean indicating whether this is the
// normal flow (false) or we jumped here from somewhere else (true).
func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
// Construct inline assembly equivalents of setjmp.
// The assembly works as follows:
// * All registers (both callee-saved and caller saved) are clobbered
// after the inline assembly returns.
// * Registers are either clobbered or, on 386, saved for longjmp to
// restore if the ABI requires them to survive calls.
// * The assembly stores the address just past the end of the assembly
// into the jump buffer.
// * The return value (eax, rax, r0, etc) is set to zero in the inline
@@ -123,8 +130,12 @@ func (b *builder) createInvokeCheckpoint() {
asmString = `
xorl %eax, %eax
movl $$1f, 4(%ebx)
movl %ebx, 8(%ebx)
movl %esi, 12(%ebx)
movl %edi, 16(%ebx)
movl %ebp, 20(%ebx)
1:`
constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
constraints = "={eax},{ebx},~{ecx},~{edx},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
// This doesn't include the floating point stack because TinyGo uses
// newer floating point instructions.
case "x86_64":
@@ -161,7 +172,7 @@ str x2, [x1, #8]
mov x0, #0
1:
`
constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{vg},~{memory}"
constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{memory}"
if b.GOOS != "darwin" && b.GOOS != "windows" {
// These registers cause the following warning when compiling for
// MacOS and Windows:
@@ -206,60 +217,192 @@ sw $$ra, 4($$5)
// So only add them when using hardfloat.
constraints += ",~{$f0},~{$f1},~{$f2},~{$f3},~{$f4},~{$f5},~{$f6},~{$f7},~{$f8},~{$f9},~{$f10},~{$f11},~{$f12},~{$f13},~{$f14},~{$f15},~{$f16},~{$f17},~{$f18},~{$f19},~{$f20},~{$f21},~{$f22},~{$f23},~{$f24},~{$f25},~{$f26},~{$f27},~{$f28},~{$f29},~{$f30},~{$f31}"
}
case "riscv32":
asmString = `
case "riscv32", "riscv64":
if b.archFamily() == "riscv32" {
asmString = `
la a2, 1f
sw a2, 4(a1)
li a0, 0
1:`
} else {
asmString = `
la a2, 1f
sd a2, 8(a1)
li a0, 0
1:`
}
constraints = "={a0},{a1},~{a1},~{a2},~{a3},~{a4},~{a5},~{a6},~{a7},~{s0},~{s1},~{s2},~{s3},~{s4},~{s5},~{s6},~{s7},~{s8},~{s9},~{s10},~{s11},~{t0},~{t1},~{t2},~{t3},~{t4},~{t5},~{t6},~{ra},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{memory}"
default:
// This case should have been handled by b.supportsRecover().
b.addError(b.fn.Pos(), "unknown architecture for defer: "+b.archFamily())
}
asmType := llvm.FunctionType(resultType, []llvm.Type{b.deferFrame.Type()}, false)
asmType := llvm.FunctionType(resultType, []llvm.Type{b.dataPtrType}, false)
asm := llvm.InlineAsm(asmType, asmString, constraints, false, false, 0, false)
result := b.CreateCall(asmType, asm, []llvm.Value{b.deferFrame}, "setjmp")
result := b.CreateCall(asmType, asm, []llvm.Value{ptr}, "setjmp")
result.AddCallSiteAttribute(-1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("returns_twice"), 0))
isZero := b.CreateICmp(llvm.IntEQ, result, llvm.ConstInt(resultType, 0, false), "setjmp.result")
return isZero
}
// createInvokeCheckpoint saves the function state at the given point, to
// continue at the landing pad if a panic happened. This is implemented using a
// setjmp-like construct.
func (b *builder) createInvokeCheckpoint() {
isZero := b.createCheckpoint(b.deferFrame)
continueBB := b.insertBasicBlock("")
b.CreateCondBr(isZero, continueBB, b.landingpad)
b.SetInsertPointAtEnd(continueBB)
b.blockExits[b.currentBlock] = continueBB
b.currentBlockInfo.exit = continueBB
}
// isInLoop checks if there is a path from a basic block to itself.
func isInLoop(start *ssa.BasicBlock) bool {
// Use a breadth-first search to scan backwards through the block graph.
queue := []*ssa.BasicBlock{start}
checked := map[*ssa.BasicBlock]struct{}{}
// 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)
}
for len(queue) > 0 {
// pop a block off of the queue
block := queue[len(queue)-1]
queue = queue[:len(queue)-1]
// 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.
// A multi-node SCC is always a cycle.
// https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
func (b *builder) isInLoop() bool {
if b.currentBlockInfo.tarjan.lowLink == 0 {
b.strongConnect(b.currentBlock)
}
return b.currentBlockInfo.tarjan.cyclic
}
// Search through predecessors.
// Searching backwards means that this is pretty fast when the block is close to the start of the function.
// Defers are often placed near the start of the function.
for _, pred := range block.Preds {
if pred == start {
// cycle found
return true
}
func (b *builder) strongConnect(block *ssa.BasicBlock) {
// Assign a new index.
// Indices start from 1 so that 0 can be used as a sentinel.
assignedIndex := b.tarjanIndex + 1
b.tarjanIndex = assignedIndex
if _, ok := checked[pred]; ok {
// block already checked
continue
}
// Apply the new index.
blockIndex := block.Index
node := &b.blockInfo[blockIndex].tarjan
node.lowLink = assignedIndex
// add to queue and checked map
queue = append(queue, pred)
checked[pred] = struct{}{}
// Push the node onto the stack.
node.onStack = true
b.tarjanStack = append(b.tarjanStack, uint(blockIndex))
// Process the successors.
for _, successor := range block.Succs {
// Look up the successor's state.
successorIndex := successor.Index
if successorIndex == blockIndex {
// Handle a self-cycle specially.
node.cyclic = true
continue
}
successorNode := &b.blockInfo[successorIndex].tarjan
switch {
case successorNode.lowLink == 0:
// This node has not yet been visisted.
b.strongConnect(successor)
case !successorNode.onStack:
// This node has been visited, but is in a different SCC.
// Ignore it, and do not update lowLink.
continue
}
// Update the lowLink index.
// This always uses the min-of-lowlink instead of using index in the on-stack case.
// This is done for two reasons:
// 1. The lowLink update can be shared between the new-node and on-stack cases.
// 2. The assigned index does not need to be saved - it is only needed for root node detection.
if successorNode.lowLink < node.lowLink {
node.lowLink = successorNode.lowLink
}
}
return false
if node.lowLink == assignedIndex {
// This is a root node.
// Pop the SCC off the stack.
stack := b.tarjanStack
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
blocks := b.blockInfo
topNode := &blocks[top].tarjan
topNode.onStack = false
if top != uint(blockIndex) {
// The root node is not the only node in the SCC.
// Mark all nodes in this SCC as cyclic.
topNode.cyclic = true
for top != uint(blockIndex) {
top = stack[len(stack)-1]
stack = stack[:len(stack)-1]
topNode = &blocks[top].tarjan
topNode.onStack = false
topNode.cyclic = true
}
}
b.tarjanStack = stack
}
}
// tarjanNode holds per-block state for isInLoop and strongConnect.
type tarjanNode struct {
// lowLink is the index of the first visited node that is reachable from this block.
// The lowLink indices are assigned by the SCC search, and do not correspond to b.Index.
// A lowLink of 0 is used as a sentinel to mark a node which has not yet been visited.
lowLink uint
// onStack tracks whether this node is currently on the SCC search stack.
onStack bool
// cyclic indicates whether this block is in a loop.
// If lowLink is 0, strongConnect must be called before reading this field.
cyclic bool
}
type llvmValueList struct {
values []llvm.Value
types []llvm.Type
}
func newLLVMValueList(values ...llvm.Value) llvmValueList {
var list llvmValueList
list.append(values...)
return list
}
func (l *llvmValueList) append(values ...llvm.Value) {
for _, value := range values {
l.values = append(l.values, value)
l.types = append(l.types, value.Type())
}
}
func (l *llvmValueList) appendSSAValues(values []ssa.Value, lower func(ssa.Value) llvm.Value) {
for _, value := range values {
l.append(lower(value))
}
}
func (b *builder) loadDeferredCallParams(structType llvm.Type, ptr llvm.Value) []llvm.Value {
fieldTypes := structType.StructElementTypes()
values := make([]llvm.Value, 0, len(fieldTypes)-2)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(fieldTypes); i++ {
fieldPtr := b.CreateInBoundsGEP(structType, ptr, []llvm.Value{
zero,
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
}, "gep")
values = append(values, b.CreateLoad(fieldTypes[i], fieldPtr, "param"))
}
return values
}
// createDefer emits a single defer instruction, to be run when this function
@@ -269,31 +412,28 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// make a linked list.
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next")
var values []llvm.Value
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
var values llvmValueList
lowerArgument := func(value ssa.Value) llvm.Value {
return b.getCallArgument(value, false)
}
if instr.Call.IsInvoke() {
// Method call on an interface.
// Get callback type number.
methodName := instr.Call.Method.FullName()
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
key := b.getInvokeFunctionName(&instr.Call)
if _, ok := b.deferInvokeFuncs[key]; !ok {
b.deferInvokeFuncs[key] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[key]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by the call parameters).
itf := b.getValue(instr.Call.Value, getPos(instr)) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
for _, arg := range instr.Call.Args {
val := b.getValue(arg, getPos(instr))
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
values = newLLVMValueList(callback, next, typecode, receiverValue)
values.appendSSAValues(instr.Call.Args, lowerArgument)
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call.
@@ -305,12 +445,11 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr))
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
values = newLLVMValueList(callback, next)
exported := b.getFunctionInfo(callee).exported
values.appendSSAValues(instr.Call.Args, func(value ssa.Value) llvm.Value {
return b.getCallArgument(value, exported)
})
} else if makeClosure, ok := instr.Call.Value.(*ssa.MakeClosure); ok {
// Immediately applied function literal with free variables.
@@ -333,14 +472,9 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr))
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
values = append(values, context)
valueTypes = append(valueTypes, context.Type())
values = newLLVMValueList(callback, next)
values.appendSSAValues(instr.Call.Args, lowerArgument)
values.append(context)
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
var argTypes []types.Type
@@ -363,11 +497,8 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range argValues {
values = append(values, param)
valueTypes = append(valueTypes, param.Type())
}
values = newLLVMValueList(callback, next)
values.append(argValues...)
} else {
funcValue := b.getValue(instr.Call.Value, getPos(instr))
@@ -382,34 +513,33 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = []llvm.Value{callback, next, funcValue}
valueTypes = append(valueTypes, funcValue.Type())
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr))
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
values = newLLVMValueList(callback, next, funcValue)
values.appendSSAValues(instr.Call.Args, lowerArgument)
}
// Make a struct out of the collected values to put in the deferred call
// struct.
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallType := b.ctx.StructType(values.types, false)
deferredCall := llvm.ConstNull(deferredCallType)
for i, value := range values {
for i, value := range values.values {
deferredCall = b.CreateInsertValue(deferredCall, value, i, "")
}
// Put this struct in an allocation.
var alloca llvm.Value
if !isInLoop(instr.Block()) {
if instr.Block() != b.currentBlock {
panic("block mismatch")
}
if !b.isInLoop() {
// This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferredCallType, "defer.alloca")
} else {
// This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferredCallType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
nilPtr := llvm.ConstNull(b.dataPtrType)
alloca = b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
layoutValue := b.createObjectLayout(deferredCallType, instr.Pos())
align := b.targetData.ABITypeAlignment(deferredCallType)
alloca = b.createAlloc(sizeValue, layoutValue, align, "defer.alloc.call")
}
if b.NeedsStackObjects {
b.trackPointer(alloca)
@@ -493,19 +623,11 @@ func (b *builder) createRunDefers() {
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
}
valueTypes = b.appendStoredValueTypes(valueTypes, callback.Args, false)
// Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
deferredCallType := b.ctx.StructType(valueTypes, false)
for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
var fnPtr llvm.Value
var fnType llvm.Type
@@ -534,6 +656,7 @@ func (b *builder) createRunDefers() {
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
}
forwardParams = b.prependIndirectResult(callback.Signature(), false, forwardParams, "defer.result")
b.createCall(fnType, fnPtr, forwardParams, "")
@@ -542,27 +665,21 @@ func (b *builder) createRunDefers() {
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
for _, param := range getParams(callback.Signature) {
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
}
exported := b.getFunctionInfo(callback).exported
valueTypes = b.appendStoredParamTypes(valueTypes, getParams(callback.Signature), exported)
deferredCallType := b.ctx.StructType(valueTypes, false)
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range getParams(callback.Signature) {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
// Plain TinyGo functions add some extra parameters to implement async functionality and function receivers.
// These parameters should not be supplied when calling into an external C/ASM function.
if !b.getFunctionInfo(callback).exported {
if !exported {
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
}
forwardParams = b.prependIndirectResult(callback.Signature, exported, forwardParams, "defer.result")
// Call real function.
fnType, fn := b.getFunction(callback)
@@ -572,24 +689,16 @@ func (b *builder) createRunDefers() {
// Get the real defer struct type and cast to it.
fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
}
valueTypes = b.appendStoredParamTypes(valueTypes, getParams(fn.Signature), false)
valueTypes = append(valueTypes, b.dataPtrType) // closure
deferredCallType := b.ctx.StructType(valueTypes, false)
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
// Call deferred function.
fnType, llvmFn := b.getFunction(fn)
forwardParams = b.prependIndirectResult(fn.Signature, false, forwardParams, "defer.result")
b.createCall(fnType, llvmFn, forwardParams, "")
case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback]
@@ -599,20 +708,14 @@ func (b *builder) createRunDefers() {
//Get signature from call results
params := callback.Type().Underlying().(*types.Signature).Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
for v := range params.Variables() {
valueTypes = append(valueTypes, b.getLLVMType(v.Type()))
}
deferredCallType := b.ctx.StructType(valueTypes, false)
// Extract the params from the struct.
var argValues []llvm.Value
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < params.Len(); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
argValues = append(argValues, forwardParam)
}
argValues := b.loadDeferredCallParams(deferredCallType, deferData)
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos)
if err != nil {
+97 -22
View File
@@ -10,6 +10,91 @@ import (
"tinygo.org/x/go-llvm"
)
// LLVM recursively expands each struct field and array element in parameters
// and results into separate values. It gets very slow with too many values, so
// pass larger aggregates indirectly before LLVM expands them.
const maxDirectAggregateValues = 1024
func (c *compilerContext) getLLVMResultType(sig *types.Signature) llvm.Type {
switch sig.Results().Len() {
case 0:
return c.ctx.VoidType()
case 1:
return c.getLLVMType(sig.Results().At(0).Type())
default:
results := make([]llvm.Type, sig.Results().Len())
for i := range results {
results[i] = c.getLLVMType(sig.Results().At(i).Type())
}
return c.ctx.StructType(results, false)
}
}
func (c *compilerContext) hasIndirectResult(sig *types.Signature) (llvm.Type, bool) {
resultType := c.getLLVMResultType(sig)
return resultType, c.isIndirectAggregate(resultType)
}
func (c *compilerContext) isIndirectAggregate(typ llvm.Type) bool {
switch typ.TypeKind() {
case llvm.ArrayTypeKind, llvm.StructTypeKind:
_, exceeded := aggregateValueCount(typ, 0)
return exceeded
default:
return false
}
}
func aggregateValueCount(typ llvm.Type, count uint64) (uint64, bool) {
switch typ.TypeKind() {
case llvm.ArrayTypeKind:
length := uint64(typ.ArrayLength())
if length == 0 {
return count, false
}
elementCount, exceeded := aggregateValueCount(typ.ElementType(), 0)
if exceeded {
return count, true
}
if elementCount != 0 && length > (maxDirectAggregateValues-count)/elementCount {
return count, true
}
return count + length*elementCount, false
case llvm.StructTypeKind:
for _, field := range typ.StructElementTypes() {
var exceeded bool
count, exceeded = aggregateValueCount(field, count)
if exceeded {
return count, true
}
}
return count, false
default:
count++
return count, count > maxDirectAggregateValues
}
}
func isLLVMValueType(typ types.Type) bool {
switch typ := typ.Underlying().(type) {
case *types.Basic:
return typ.Kind() != types.Invalid
case *types.Array:
return isLLVMValueType(typ.Elem())
case *types.Struct:
for field := range typ.Fields() {
if !isLLVMValueType(field.Type()) {
return false
}
}
return true
case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice:
return true
default:
return false
}
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
@@ -48,28 +133,18 @@ func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
// getLLVMFunctionType returns a LLVM function type for a given signature.
func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
// Get the return type.
var returnType llvm.Type
switch typ.Results().Len() {
case 0:
// No return values.
returnType = c.ctx.VoidType()
case 1:
// Just one return value.
returnType = c.getLLVMType(typ.Results().At(0).Type())
default:
// Multiple return values. Put them together in a struct.
// This appears to be the common way to handle multiple return values in
// LLVM.
members := make([]llvm.Type, typ.Results().Len())
for i := 0; i < typ.Results().Len(); i++ {
members[i] = c.getLLVMType(typ.Results().At(i).Type())
}
returnType = c.ctx.StructType(members, false)
}
returnType, indirectResult := c.hasIndirectResult(typ)
// Get the parameter types.
var paramTypes []llvm.Type
if indirectResult {
// LLVM expands aggregate returns into scalar leaves before deciding
// whether to pass them indirectly, so a large IR return can exhaust
// memory. Returning void avoids that expansion and cannot be demoted
// again. Keep the result pointer first so the context remains last.
paramTypes = append(paramTypes, c.dataPtrType)
returnType = c.ctx.VoidType()
}
if typ.Recv() != nil {
recv := c.getLLVMType(typ.Recv().Type())
if recv.StructName() == "runtime._interface" {
@@ -81,8 +156,8 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
paramTypes = append(paramTypes, info.llvmType)
}
}
for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type())
for v := range typ.Params().Variables() {
subType := c.getLLVMType(v.Type())
for _, info := range c.expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
@@ -112,7 +187,7 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
// Store the bound variables in a single object, allocating it on the heap
// if necessary.
context := b.emitPointerPack(boundVars)
context := b.emitPointerPack(boundVars, expr.Pos())
// Create the closure.
_, fn := b.getFunction(f)
+33 -8
View File
@@ -5,11 +5,38 @@ package compiler
import (
"go/token"
"slices"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// Heap-allocate a buffer of the given size. This will typically call
// runtime.alloc.
func (b *builder) createAlloc(sizeValue, layoutValue llvm.Value, align int, comment string) llvm.Value {
// Normally allocate using "runtime.alloc", but use "runtime.alloc_noheap"
// if the //go:noheap pragma is used.
allocFunc := "alloc"
if b.info.noheap {
allocFunc = "alloc_noheap"
}
// Allocs that don't allocate anything can return an architecture-specific
// sentinel value.
if !sizeValue.IsAConstantInt().IsNil() && sizeValue.ZExtValue() == 0 {
allocFunc = "alloc_zero"
}
// Make the runtime call.
call := b.createRuntimeCall(allocFunc, []llvm.Value{sizeValue, layoutValue}, comment)
if align == 0 || align&(align-1) != 0 {
panic("invalid 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) {
@@ -62,7 +89,7 @@ func (b *builder) trackValue(value llvm.Value) {
return
}
numElements := typ.StructElementTypesCount()
for i := 0; i < numElements; i++ {
for i := range numElements {
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
@@ -71,7 +98,7 @@ func (b *builder) trackValue(value llvm.Value) {
return
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
for i := range numElements {
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
@@ -92,13 +119,11 @@ func typeHasPointers(t llvm.Type) bool {
case llvm.PointerTypeKind:
return true
case llvm.StructTypeKind:
for _, subType := range t.StructElementTypes() {
if typeHasPointers(subType) {
return true
}
}
return false
return slices.ContainsFunc(t.StructElementTypes(), typeHasPointers)
case llvm.ArrayTypeKind:
if t.ArrayLength() == 0 {
return false
}
if typeHasPointers(t.ElementType()) {
return true
}
+29 -15
View File
@@ -47,20 +47,16 @@ func (b *builder) createGo(instr *ssa.Go) {
return
}
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...)
}
var prefix string
var funcPtr llvm.Value
var funcType llvm.Type
var context llvm.Value
hasContext := false
exported := false
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
@@ -73,10 +69,10 @@ func (b *builder) createGo(instr *ssa.Go) {
panic("StaticCallee returned an unexpected value")
}
if !context.IsNil() {
params = append(params, context) // context parameter
hasContext = true
}
funcType, funcPtr = b.getFunction(callee)
exported = b.getFunctionInfo(callee).exported
} else if instr.Call.IsInvoke() {
// This is a method call on an interface value.
itf := b.getValue(instr.Call.Value, getPos(instr))
@@ -84,23 +80,32 @@ func (b *builder) createGo(instr *ssa.Go) {
itfValue := b.CreateExtractValue(itf, 1, "")
funcPtr = b.getInvokeFunction(&instr.Call)
funcType = funcPtr.GlobalValueType()
params = append([]llvm.Value{itfValue}, params...) // start with receiver
params = append(params, itfTypeCode) // end with typecode
params = append(params, itfValue)
context = itfTypeCode
} else {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr)))
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context, funcPtr)
hasContext = true
prefix = b.fn.RelString(nil)
prefix = b.getFunctionInfo(b.fn).linkName
}
paramBundle := b.emitPointerPack(params)
for _, param := range instr.Call.Args {
params = append(params, b.getGoroutineCallArgument(param, exported)...)
}
if !context.IsNil() {
params = append(params, context)
}
if hasContext && instr.Call.StaticCallee() == nil {
params = append(params, funcPtr)
}
params = b.prependIndirectResult(instr.Call.Signature(), exported, params, "go.result")
paramBundle := b.emitPointerPack(params, instr.Pos())
var stackSize llvm.Value
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos())
if b.AutomaticStackSize {
@@ -122,6 +127,15 @@ func (b *builder) createGo(instr *ssa.Go) {
b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "")
}
func (b *builder) getGoroutineCallArgument(value ssa.Value, exported bool) []llvm.Value {
typ := b.getLLVMType(value.Type())
arg := b.getCallArgument(value, exported)
if b.isIndirectParam(typ, exported) {
return []llvm.Value{b.copyToIndirectStorage(arg, typ, "go.param")}
}
return b.expandFormalParam(arg)
}
// Create an exported wrapper function for functions with the //go:wasmexport
// pragma. This wrapper function is quite complex when the scheduler is enabled:
// it needs to start a new goroutine each time the exported function is called.
@@ -139,7 +153,7 @@ func (b *builder) createWasmExport() {
// Declare the exported function.
paramTypes := b.llvmFnType.ParamTypes()
exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false)
exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
exportedFn := llvm.AddFunction(b.mod, b.getFunctionInfo(b.fn).linkName+suffix, exportedFnType)
b.addStandardAttributes(exportedFn)
llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport))
@@ -414,7 +428,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
// Extract parameters from the state object, and call the function
// that's being wrapped.
var callParams []llvm.Value
for i := 0; i < numParams; i++ {
for i := range numParams {
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
+27 -13
View File
@@ -38,10 +38,10 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
// provided immediately. For example:
//
// arm.AsmFull(
// "str {value}, {result}",
// "str {value}, [{result}]",
// map[string]interface{}{
// "value": 1
// "result": &dest,
// "value": 1,
// "result": uintptr(unsafe.Pointer(&dest)),
// })
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
@@ -146,13 +146,14 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10)
constraints := "={r0}"
var constraints strings.Builder
constraints.WriteString("={r0}")
for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints += ",0"
constraints.WriteString(",0")
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
constraints.WriteString(",{r" + strconv.Itoa(i) + "}")
}
llvmValue := b.getValue(arg, pos)
llvmArgs = append(llvmArgs, llvmValue)
@@ -161,9 +162,9 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
// Implement the ARM calling convention by marking r1-r3 as
// clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{r1},~{r2},~{r3}"
constraints.WriteString(",~{r1},~{r2},~{r3}")
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false)
return b.CreateCall(fnType, target, llvmArgs, ""), nil
}
@@ -184,13 +185,14 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10)
constraints := "={x0}"
var constraints strings.Builder
constraints.WriteString("={x0}")
for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints += ",0"
constraints.WriteString(",0")
} else {
constraints += ",{x" + strconv.Itoa(i) + "}"
constraints.WriteString(",{x" + strconv.Itoa(i) + "}")
}
llvmValue := b.getValue(arg, pos)
llvmArgs = append(llvmArgs, llvmValue)
@@ -199,9 +201,9 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
// Implement the ARM64 calling convention by marking x1-x7 as
// clobbered. x0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
constraints.WriteString(",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}")
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false)
return b.CreateCall(fnType, target, llvmArgs, ""), nil
}
@@ -249,3 +251,15 @@ func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) {
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
}
}
// Implement runtime/interrupt.Checkpoint.Save. It needs to be implemented
// directly at the call site. If it isn't implemented directly at the call site
// (but instead through a function call), it might result in an overwritten
// stack in the non-jump return case.
func (b *builder) createInterruptCheckpoint(ptr ssa.Value) llvm.Value {
addr := b.getValue(ptr, ptr.Pos())
b.createNilCheck(ptr, addr, "deref")
stackPointer := b.readStackPointer()
b.CreateStore(stackPointer, addr)
return b.createCheckpoint(addr)
}
+587 -209
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -41,6 +41,8 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass.
// It must have an alignment of 1, otherwise LLVM thinks a ptrtoint of the
// global has the lower bits unset.
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType)
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
@@ -48,6 +50,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
global.SetVisibility(llvm.HiddenVisibility)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetAlignment(1)
initializer := llvm.ConstNull(globalLLVMType)
initializer = b.CreateInsertValue(initializer, funcContext, 0, "")
initializer = b.CreateInsertValue(initializer, funcPtr, 1, "")
+111 -16
View File
@@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@@ -27,6 +28,8 @@ func (b *builder) defineIntrinsicFunction() {
b.createStackSaveImpl()
case name == "runtime.KeepAlive":
b.createKeepAliveImpl()
case name == "machine.keepAliveNoEscape":
b.createMachineKeepAliveImpl()
case strings.HasPrefix(name, "runtime/volatile.Load"):
b.createVolatileLoad()
case strings.HasPrefix(name, "runtime/volatile.Store"):
@@ -48,19 +51,24 @@ func (b *builder) defineIntrinsicFunction() {
// and will otherwise be lowered to regular libc memcpy/memmove calls.
func (b *builder) createMemoryCopyImpl() {
b.createFunctionStart(true)
fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
params := b.fn.Params[0:3]
b.createMemCopy(
b.fn.Name(),
b.getValue(params[0], getPos(b.fn)),
b.getValue(params[1], getPos(b.fn)),
b.getValue(params[2], getPos(b.fn)),
)
b.CreateRetVoid()
}
func (b *builder) createMemCopy(kind string, dst, src, len llvm.Value) llvm.Value {
fnName := "llvm." + kind + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType, b.dataPtrType, b.uintptrType, b.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
}
var params []llvm.Value
for _, param := range b.fn.Params {
params = append(params, b.getValue(param, getPos(b.fn)))
}
params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false))
b.CreateCall(llvmFn.GlobalValueType(), llvmFn, params, "")
b.CreateRetVoid()
return b.CreateCall(llvmFn.GlobalValueType(), llvmFn, []llvm.Value{dst, src, len, llvm.ConstInt(b.ctx.Int1Type(), 0, false)}, "")
}
// createMemoryZeroImpl creates calls to llvm.memset.* to zero a block of
@@ -121,13 +129,60 @@ func (b *builder) createKeepAliveImpl() {
b.CreateRetVoid()
}
// createAbiEscapeImpl implements the generic internal/abi.Escape function. It
// currently only supports pointer types.
func (b *builder) createAbiEscapeImpl() {
b.createFunctionStart(true)
// The first parameter is assumed to be a pointer. This is checked at the
// call site of createAbiEscapeImpl.
pointerValue := b.getValue(b.fn.Params[0], getPos(b.fn))
// Create an equivalent of the following C code, which is basically just a
// nop but ensures the pointerValue is kept alive:
//
// __asm__ __volatile__("" : : "r"(pointerValue))
//
// It should be portable to basically everything as the "r" register type
// exists basically everywhere.
asmType := llvm.FunctionType(b.dataPtrType, []llvm.Type{b.dataPtrType}, false)
asmFn := llvm.InlineAsm(asmType, "", "=r,0", true, false, 0, false)
result := b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "")
b.CreateRet(result)
}
// Implement machine.keepAliveNoEscape, which makes sure the compiler keeps the
// pointer parameter alive until this point (for GC).
func (b *builder) createMachineKeepAliveImpl() {
b.createFunctionStart(true)
pointerValue := b.getValue(b.fn.Params[0], getPos(b.fn))
// See createKeepAliveImpl for details.
asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false)
asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false)
b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "")
b.CreateRetVoid()
}
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",
}
@@ -141,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.
@@ -162,6 +239,24 @@ func (b *builder) defineMathOp() {
}
result := b.CreateCall(llvmFn.GlobalValueType(), llvmFn, args, "")
b.CreateRet(result)
return true
}
func (b *builder) defineCryptoIntrinsic() bool {
if b.fn.Pkg.Pkg.Path() != "crypto/internal/constanttime" {
return false
}
switch b.fn.Name() {
case "boolToUint8":
b.createFunctionStart(true)
param := b.getValue(b.fn.Params[0], b.fn.Pos())
result := b.CreateZExt(param, b.ctx.Int8Type(), "")
b.CreateRet(result)
return true
}
return false
}
// Implement most math/bits functions.
+110 -107
View File
@@ -1,10 +1,10 @@
package compiler
import (
"encoding/binary"
"fmt"
"go/token"
"go/types"
"math/big"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
@@ -54,7 +54,7 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
// pointer value directly. It returns the pointer with the packed data.
// If the values are all constants, they are be stored in a constant global and
// deduplicated.
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
func (b *builder) emitPointerPack(values []llvm.Value, pos token.Pos) llvm.Value {
valueTypes := make([]llvm.Type, len(values))
for i, value := range values {
valueTypes[i] = value.Type()
@@ -128,14 +128,9 @@ 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)
layoutValue := b.createObjectLayout(packedType, pos)
align := b.targetData.ABITypeAlignment(packedType)
alloc := b.mod.NamedFunction("runtime.alloc")
packedAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{
sizeValue,
llvm.ConstNull(b.dataPtrType),
llvm.Undef(b.dataPtrType), // unused context parameter
}, "")
packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
packedAlloc := b.createAlloc(sizeValue, layoutValue, align, "")
if b.NeedsStackObjects {
b.trackPointer(packedAlloc)
}
@@ -212,7 +207,7 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
globalType := llvm.ArrayType(elementType, len(buf))
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
for i := 0; i < len(buf); i++ {
for i := range buf {
ch := uint64(buf[i])
value = c.builder.CreateInsertValue(value, llvm.ConstInt(elementType, ch, false), i, "")
}
@@ -231,6 +226,12 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
//
// For details on what's in this value, see src/runtime/gc_precise.go.
func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value {
if !typeHasPointers(t) {
// There are no pointers in this type, so we can simplify the layout.
layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
}
// Use the element type for arrays. This works even for nested arrays.
for {
kind := t.TypeKind()
@@ -248,54 +249,29 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
break
}
// Do a few checks to see whether we need to generate any object layout
// information at all.
// Create the pointer bitmap.
objectSizeBytes := c.targetData.TypeAllocSize(t)
pointerAlignment := uint64(c.targetData.PrefTypeAlignment(c.dataPtrType))
bitmapLen := objectSizeBytes / pointerAlignment
bitmapBytes := (bitmapLen + 7) / 8
bitmap := make([]byte, bitmapBytes, max(bitmapBytes, 8))
c.buildPointerBitmap(bitmap, pointerAlignment, pos, t, 0)
// Try to encode the layout inline.
pointerSize := c.targetData.TypeAllocSize(c.dataPtrType)
pointerAlignment := c.targetData.PrefTypeAlignment(c.dataPtrType)
if objectSizeBytes < pointerSize {
// Too small to contain a pointer.
layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
}
bitmap := c.getPointerBitmap(t, pos)
if bitmap.BitLen() == 0 {
// There are no pointers in this type, so we can simplify the layout.
// TODO: this can be done in many other cases, e.g. when allocating an
// array (like [4][]byte, which repeats a slice 4 times).
layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
}
if objectSizeBytes%uint64(pointerAlignment) != 0 {
// This shouldn't happen except for packed structs, which aren't
// currently used.
c.addError(pos, "internal error: unexpected object size for object with pointer field")
return llvm.ConstNull(c.dataPtrType)
}
objectSizeWords := objectSizeBytes / uint64(pointerAlignment)
pointerBits := pointerSize * 8
var sizeFieldBits uint64
switch pointerBits {
case 16:
sizeFieldBits = 4
case 32:
sizeFieldBits = 5
case 64:
sizeFieldBits = 6
default:
panic("unknown pointer size")
}
layoutFieldBits := pointerBits - 1 - sizeFieldBits
if bitmapLen < pointerBits {
rawMask := binary.LittleEndian.Uint64(bitmap[0:8])
layout := rawMask*pointerBits + bitmapLen
layout <<= 1
layout |= 1
// Try to emit the value as an inline integer. This is possible in most
// cases.
if objectSizeWords < layoutFieldBits {
// If it can be stored directly in the pointer value, do so.
// The runtime knows that if the least significant bit of the pointer is
// set, the pointer contains the value itself.
layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
// Check if the layout fits.
layout &= 1<<pointerBits - 1
if (layout>>1)/pointerBits == rawMask {
// No set bits were shifted off.
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
}
}
// Unfortunately, the object layout is too big to fit in a pointer-sized
@@ -303,25 +279,24 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// Try first whether the global already exists. All objects with a
// particular name have the same type, so this is possible.
globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap)
globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", bitmapLen, (bitmapLen+15)/16, bitmap)
global := c.mod.NamedGlobal(globalName)
if !global.IsNil() {
return global
}
// Create the global initializer.
bitmapBytes := make([]byte, int(objectSizeWords+7)/8)
bitmap.FillBytes(bitmapBytes)
reverseBytes(bitmapBytes) // big-endian to little-endian
var bitmapByteValues []llvm.Value
for _, b := range bitmapBytes {
bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false))
bitmapByteValues := make([]llvm.Value, bitmapBytes)
i8 := c.ctx.Int8Type()
for i, b := range bitmap {
bitmapByteValues[i] = llvm.ConstInt(i8, uint64(b), false)
}
initializer := c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, objectSizeWords, false),
llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues),
llvm.ConstInt(c.uintptrType, bitmapLen, false),
llvm.ConstArray(i8, bitmapByteValues),
}, false)
// Create the actual global.
global = llvm.AddGlobal(c.mod, initializer.Type(), globalName)
global.SetInitializer(initializer)
global.SetUnnamedAddr(true)
@@ -329,6 +304,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
global.SetLinkage(llvm.LinkOnceODRLinkage)
if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 {
// AVR doesn't have alignment by default.
// The lowest bit must be unset to distinguish this from an inline layout.
global.SetAlignment(2)
}
if c.Debug && pos != token.NoPos {
@@ -360,59 +336,71 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
return global
}
// getPointerBitmap scans the given LLVM type for pointers and sets bits in a
// bigint at the word offset that contains a pointer. This scan is recursive.
func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.dataPtrType)
switch typ.TypeKind() {
// buildPointerBitmap scans the given LLVM type for pointers and sets bits in a
// bitmap at the word offset that contains a pointer. This scan is recursive.
func (c *compilerContext) buildPointerBitmap(
dst []byte,
ptrAlign uint64,
pos token.Pos,
t llvm.Type,
offset uint64,
) {
switch t.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
// These types do not contain pointers.
case llvm.PointerTypeKind:
return big.NewInt(1)
// Set the corresponding position in the bitmap.
dst[offset/8] |= 1 << (offset % 8)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
if typ.StructName() == "runtime.funcValue" {
// Hack: the type runtime.funcValue contains an 'id' field which is
// of type uintptr, but before the LowerFuncValues pass it actually
// contains a pointer (ptrtoint) to a global. This trips up the
// interp package. Therefore, make the id field a pointer for now.
typ = c.ctx.StructType([]llvm.Type{c.dataPtrType, c.dataPtrType}, false)
}
for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, pos)
if subptrs.BitLen() == 0 {
// Recurse over struct elements.
for i, et := range t.StructElementTypes() {
eo := c.targetData.ElementOffset(t, i)
if eo%uint64(ptrAlign) != 0 {
if typeHasPointers(et) {
// This error will let the compilation fail, but by continuing
// the error can still easily be shown.
c.addError(pos, "internal error: allocated struct contains unaligned pointer")
}
continue
}
offset := c.targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
// This error will let the compilation fail, but by continuing
// the error can still easily be shown.
c.addError(pos, "internal error: allocated struct contains unaligned pointer")
continue
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
c.buildPointerBitmap(
dst,
ptrAlign,
pos,
et,
offset+(eo/ptrAlign),
)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := c.getPointerBitmap(subtyp, pos)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
// Recurse over array elements.
len := t.ArrayLength()
if len <= 0 {
return
}
elementSize := c.targetData.TypeAllocSize(subtyp)
if elementSize%uint64(alignment) != 0 {
// This error will let the compilation fail (but continues so that
// other errors can be shown).
c.addError(pos, "internal error: allocated array contains unaligned pointer")
return ptrs
et := t.ElementType()
elementSize := c.targetData.TypeAllocSize(et)
if elementSize%ptrAlign != 0 {
if typeHasPointers(et) {
// This error will let the compilation fail (but continues so that
// other errors can be shown).
c.addError(pos, "internal error: allocated array contains unaligned pointer")
}
return
}
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
elementSize /= ptrAlign
for i := range len {
c.buildPointerBitmap(
dst,
ptrAlign,
pos,
et,
offset+uint64(i)*elementSize,
)
}
return ptrs
default:
// Should not happen.
panic("unknown LLVM type")
@@ -430,7 +418,7 @@ func (c *compilerContext) archFamily() string {
// features string is not one for an ARM architecture.
func (c *compilerContext) isThumb() bool {
var isThumb, isNotThumb bool
for _, feature := range strings.Split(c.Features, ",") {
for feature := range strings.SplitSeq(c.Features, ",") {
if feature == "+thumb-mode" {
isThumb = true
}
@@ -459,6 +447,21 @@ func (b *builder) readStackPointer() llvm.Value {
return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "")
}
// writeStackPointer emits a LLVM intrinsic call that updates the current stack
// pointer.
func (b *builder) writeStackPointer(sp llvm.Value) {
name := "llvm.stackrestore.p0"
if llvmutil.Version() < 18 {
name = "llvm.stackrestore" // backwards compatibility with LLVM 17 and below
}
stackrestore := b.mod.NamedFunction(name)
if stackrestore.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false)
stackrestore = llvm.AddFunction(b.mod, name, fnType)
}
b.CreateCall(stackrestore.GlobalValueType(), stackrestore, []llvm.Value{sp}, "")
}
// createZExtOrTrunc lets the input value fit in the output type bits, by zero
// extending or truncating the integer.
func (b *builder) createZExtOrTrunc(value llvm.Value, t llvm.Type) llvm.Value {
+57 -7
View File
@@ -44,7 +44,7 @@ func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, n
alloca = CreateEntryBlockAlloca(builder, t, name)
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
return
}
@@ -58,14 +58,14 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
builder.SetInsertPointBefore(inst)
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
if next := llvm.NextInstruction(inst); !next.IsNil() {
builder.SetInsertPointBefore(next)
} else {
builder.SetInsertPointAtEnd(inst.InstructionParent())
}
fnType, fn = getLifetimeEndFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
return alloca
}
@@ -74,7 +74,27 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
// createTemporaryAlloca.
func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
fnType, fn := getLifetimeEndFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, ptr}, "")
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, ptr), "")
}
// lifetimeCallArgs returns the arguments to pass to a call of the
// llvm.lifetime.start/end intrinsics. LLVM 22 dropped the (redundant,
// already required to match the alloca size) i64 size argument, so the
// intrinsic now only takes the pointer.
func lifetimeCallArgs(size, ptr llvm.Value) []llvm.Value {
if Version() >= 22 {
return []llvm.Value{ptr}
}
return []llvm.Value{size, ptr}
}
// lifetimeFuncType returns the function type of the llvm.lifetime.start/end
// intrinsics, which lost their i64 size parameter in LLVM 22.
func lifetimeFuncType(ctx llvm.Context, ptrType llvm.Type) llvm.Type {
if Version() >= 22 {
return llvm.FunctionType(ctx.VoidType(), []llvm.Type{ptrType}, false)
}
return llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
}
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
@@ -84,7 +104,7 @@ func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fn := mod.NamedFunction(fnName)
ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
fnType := lifetimeFuncType(ctx, ptrType)
if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType)
}
@@ -98,7 +118,7 @@ func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fn := mod.NamedFunction(fnName)
ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
fnType := lifetimeFuncType(ctx, ptrType)
if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType)
}
@@ -218,7 +238,37 @@ func Version() int {
return major
}
// Return the byte order for the given target triple. Most targets are little
// NoCaptureAttrName returns the name of the LLVM attribute kind that marks a
// pointer parameter as guaranteed not to escape by capture.
//
// LLVM 21 removed the old boolean 'nocapture' enum attribute in favor of the
// more expressive 'captures' int attribute, where 'captures(none)' (encoded
// as the value 0) is the equivalent of the old 'nocapture'. LLVM 20 supports
// both, but its own optimizer still emits 'nocapture', so the cutoff for
// reading/writing the new name is LLVM 21.
func NoCaptureAttrName() string {
if Version() >= 21 {
return "captures"
}
return "nocapture"
}
// IsNoCapture reports whether attr (looked up using the kind returned by
// NoCaptureAttrName) indicates that the pointer it is attached to does not
// escape by capture. It returns false for a nil attribute.
func IsNoCapture(attr llvm.Attribute) bool {
if attr.IsNil() {
return false
}
if Version() >= 21 {
// captures(none) is encoded as the value 0; any other value permits
// some form of capture.
return attr.GetEnumValue() == 0
}
return true
}
// ByteOrder returns the byte order for the given target triple. Most targets are little
// endian, but for example MIPS can be big-endian.
func ByteOrder(target string) binary.ByteOrder {
if strings.HasPrefix(target, "mips-") {
+469 -198
View File
@@ -3,19 +3,15 @@ package compiler
// This file emits the correct map intrinsics for map operations.
import (
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"strings"
"tinygo.org/x/go-llvm"
)
// constants for hashmap algorithms; must match src/runtime/hashmap.go
const (
hashmapAlgorithmBinary = iota
hashmapAlgorithmString
hashmapAlgorithmInterface
)
const hashArrayUnrollLimit = 4
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
// initializing an appropriately sized object.
@@ -23,28 +19,13 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
mapType := expr.Type().Underlying().(*types.Map)
keyType := mapType.Key().Underlying()
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
var llvmKeyType llvm.Type
var alg uint64 // must match values in src/runtime/hashmap.go
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// String keys.
llvmKeyType = b.getLLVMType(keyType)
alg = hashmapAlgorithmString
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = b.getLLVMType(keyType)
alg = hashmapAlgorithmBinary
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = b.getLLVMRuntimeType("_interface")
alg = hashmapAlgorithmInterface
}
llvmKeyType := b.getLLVMType(keyType)
keySize := b.targetData.TypeAllocSize(llvmKeyType)
valueSize := b.targetData.TypeAllocSize(llvmValueType)
llvmKeySize := llvm.ConstInt(b.uintptrType, keySize, false)
llvmValueSize := llvm.ConstInt(b.uintptrType, valueSize, false)
sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
algEnum := llvm.ConstInt(b.ctx.Int8Type(), alg, false)
if expr.Reserve != nil {
sizeHint = b.getValue(expr.Reserve, getPos(expr))
var err error
@@ -53,134 +34,129 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
return llvm.Value{}, err
}
}
hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint, algEnum}, "")
// Resolve hash and equal functions for this key type. For string and
// binary key types, reference the corresponding runtime functions
// directly. For composite types, generate type-specific functions.
var hashFn, equalFn llvm.Value
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
hashFn = b.getRuntimeFunctionValue("hashmapStringPtrHash", hashmapKeyHashSignature())
equalFn = b.getRuntimeFunctionValue("hashmapStringEqual", hashmapKeyEqualSignature())
} else if hashmapIsBinaryKey(keyType) {
hashFn = b.getRuntimeFunctionValue("hash32", hashmapKeyHashSignature())
equalFn = b.getRuntimeFunctionValue("memequal", hashmapKeyEqualSignature())
} else {
fn := b.getOrGenerateKeyHashFunc(keyType)
hashFn = b.createFuncValue(fn, llvm.ConstNull(b.dataPtrType), hashmapKeyHashSignature())
fn = b.getOrGenerateKeyEqualFunc(keyType)
equalFn = b.createFuncValue(fn, llvm.ConstNull(b.dataPtrType), hashmapKeyEqualSignature())
}
hashmap := b.createRuntimeCall("hashmapMakeGeneric", []llvm.Value{
llvmKeySize, llvmValueSize, sizeHint,
hashFn, equalFn,
}, "")
return hashmap, nil
}
// getRuntimeFunctionValue returns a TinyGo function value (with nil context)
// for the named runtime function.
func (b *builder) getRuntimeFunctionValue(name string, sig *types.Signature) llvm.Value {
member := b.program.ImportedPackage("runtime").Members[name]
if member == nil {
panic("unknown runtime function: " + name)
}
_, llvmFn := b.getFunction(member.(*ssa.Function))
return b.createFuncValue(llvmFn, llvm.ConstNull(b.dataPtrType), sig)
}
// createMapLookup returns the value in a map. It calls a runtime function
// depending on the map key type to load the map value and its comma-ok value.
func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, key ssa.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := b.getLLVMType(valueType)
// Allocate the memory for the resulting type. Do not zero this memory: it
// will be zeroed by the hashmap get implementation if the key is not
// present in the map.
mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
result := b.createRuntimeValueResult(llvmValueType, commaOk, false, "hashmap")
mapValueAlloca := result.valuePtr
// We need the map size (with type uintptr) to pass to the hashmap*Get
// functions. This is necessary because those *Get functions are valid on
// nil maps, and they'll need to zero the value pointer by that number of
// bytes.
mapValueSize := mapValueAllocaSize
mapValueSize := result.valueSize
if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
}
// Do the lookup. How it is done depends on the key type.
var commaOkValue llvm.Value
origKeyType := keyType
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, mapValueAlloca, mapValueSize}
params := []llvm.Value{m, b.getValue(key, getPos(key)), mapValueAlloca, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
// Store the key in an alloca, in the entry block to avoid dynamic stack
// growth.
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
} else {
// Not trivially comparable using memcmp. Make it an interface instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface now.
itfKey = b.createMakeInterface(key, origKeyType, pos)
// Key stored at actual type: either binary-comparable or with
// compiler-generated hash/equal.
mapKey := b.getValueStorage(key, "hashmap.key")
params := []llvm.Value{m, mapKey.ptr, mapValueAlloca, mapValueSize}
fnName := "hashmapBinaryGet"
if !hashmapIsBinaryKey(keyType) {
fnName = "hashmapGenericGet"
}
params := []llvm.Value{m, itfKey, mapValueAlloca, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
commaOkValue = b.createRuntimeCall(fnName, params, "")
b.endValueStorage(mapKey)
}
// Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap.
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize)
if commaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, mapValue, 0, "")
tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "")
return tuple, nil
} else {
return mapValue, nil
}
// The value is set to the zero value if the key doesn't exist.
return result.finish(b, commaOkValue, ""), nil
}
// createMapUpdate updates a map key to a given value, by creating an
// appropriate runtime call.
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
b.CreateStore(value, valueAlloca)
origKeyType := keyType
func (b *builder) createMapUpdate(keyType types.Type, m llvm.Value, key, value ssa.Value, pos token.Pos) {
storedValue := b.getValueStorage(value, "hashmap.value")
keyType = keyType.Underlying()
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, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyAlloca, valueAlloca}
b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyAlloca, keySize)
params := []llvm.Value{m, b.getValue(key, getPos(key)), storedValue.ptr}
b.createRuntimeInvoke("hashmapStringSet", params, "")
} else {
// Key is not trivially comparable, so compare it as an interface instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, origKeyType, pos)
// Key stored at actual type.
keyStorage := b.getValueStorage(key, "hashmap.key")
fnName := "hashmapBinarySet"
if !hashmapIsBinaryKey(keyType) {
fnName = "hashmapGenericSet"
}
params := []llvm.Value{m, itfKey, valueAlloca}
b.createRuntimeCall("hashmapInterfaceSet", params, "")
params := []llvm.Value{m, keyStorage.ptr, storedValue.ptr}
b.createRuntimeInvoke(fnName, params, "")
b.endValueStorage(keyStorage)
}
b.emitLifetimeEnd(valueAlloca, valueSize)
b.endValueStorage(storedValue)
}
// createMapDelete deletes a key from a map by calling the appropriate runtime
// function. It is the implementation of the Go delete() builtin.
func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
origKeyType := keyType
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key}
b.createRuntimeCall("hashmapStringDelete", params, "")
return nil
} else if hashmapIsBinaryKey(keyType) {
} else {
// Key stored at actual type.
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyAlloca}
b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyAlloca, keySize)
return nil
} else {
// Key is not trivially comparable, so compare it as an interface
// instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, origKeyType, pos)
fnName := "hashmapBinaryDelete"
if !hashmapIsBinaryKey(keyType) {
fnName = "hashmapGenericDelete"
}
params := []llvm.Value{m, itfKey}
b.createRuntimeCall("hashmapInterfaceDelete", params, "")
params := []llvm.Value{m, keyAlloca}
b.createRuntimeCall(fnName, params, "")
b.emitLifetimeEnd(keyAlloca, keySize)
return nil
}
}
@@ -201,42 +177,15 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
llvmKeyType := b.getLLVMType(keyType)
llvmValueType := b.getLLVMType(valueType)
// There is a special case in which keys are stored as an interface value
// instead of the value they normally are. This happens for non-trivially
// comparable types such as float32 or some structs.
isKeyStoredAsInterface := false
if t, ok := keyType.Underlying().(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
} else {
// The key is stored as an interface value, and may or may not be an
// interface type (for example, float32 keys are stored as an interface
// value).
if _, ok := keyType.Underlying().(*types.Interface); !ok {
isKeyStoredAsInterface = true
}
}
// Determine the type of the key as stored in the map.
llvmStoredKeyType := llvmKeyType
if isKeyStoredAsInterface {
llvmStoredKeyType = b.getLLVMRuntimeType("_interface")
}
// All key types are now stored at their declared type (no interface wrapping).
// Extract the key and value from the map.
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key")
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(llvmKeyType, "range.key")
mapValueAlloca, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value")
ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyAlloca, mapValueAlloca}, "range.next")
mapKey := b.CreateLoad(llvmStoredKeyType, mapKeyAlloca, "")
mapKey := b.CreateLoad(llvmKeyType, mapKeyAlloca, "")
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
if isKeyStoredAsInterface {
// The key is stored as an interface but it isn't of interface type.
// Extract the underlying value.
mapKey = b.extractValueFromInterface(mapKey, llvmKeyType)
}
// End the lifetimes of the allocas, because we're done with them.
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
b.emitLifetimeEnd(mapValueAlloca, mapValueSize)
@@ -254,90 +203,412 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
// can be compared with runtime.memequal. Note that padding bytes are undef
// and can alter two "equal" structs being equal when compared with memequal.
func hashmapIsBinaryKey(keyType types.Type) bool {
switch keyType := keyType.(type) {
switch keyType := keyType.Underlying().(type) {
case *types.Basic:
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0 || keyType.Kind() == types.UnsafePointer
case *types.Pointer:
return true
case *types.Struct:
for i := 0; i < keyType.NumFields(); i++ {
fieldType := keyType.Field(i).Type().Underlying()
if !hashmapIsBinaryKey(fieldType) {
return false
}
}
return true
case *types.Array:
return hashmapIsBinaryKey(keyType.Elem())
case *types.Named:
return hashmapIsBinaryKey(keyType.Underlying())
default:
return false
}
}
func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error {
// We know that hashmapIsBinaryKey is true, so we only have to handle those types that can show up there.
// To zero all undefined bytes, we iterate over all the fields in the type. For each element, compute the
// offset of that element. If it's Basic type, there are no internal padding bytes. For compound types, we recurse to ensure
// we handle nested types. Next, we determine if there are any padding bytes before the next
// element and zero those as well.
// hashmapKeyHashSignature returns the Go type signature for hashmap key hash
// functions: func(key unsafe.Pointer, size, seed uintptr) uint32
func hashmapKeyHashSignature() *types.Signature {
return types.NewSignatureType(nil, nil, nil,
types.NewTuple(
types.NewVar(token.NoPos, nil, "key", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "size", types.Typ[types.Uintptr]),
types.NewVar(token.NoPos, nil, "seed", types.Typ[types.Uintptr]),
),
types.NewTuple(
types.NewVar(token.NoPos, nil, "", types.Typ[types.Uint32]),
),
false,
)
}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
// hashmapKeyEqualSignature returns the Go type signature for hashmap key equal
// functions: func(x, y unsafe.Pointer, n uintptr) bool
func hashmapKeyEqualSignature() *types.Signature {
return types.NewSignatureType(nil, nil, nil,
types.NewTuple(
types.NewVar(token.NoPos, nil, "x", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "y", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "n", types.Typ[types.Uintptr]),
),
types.NewTuple(
types.NewVar(token.NoPos, nil, "", types.Typ[types.Bool]),
),
false,
)
}
switch llvmType.TypeKind() {
case llvm.IntegerTypeKind:
// no padding bytes
return nil
case llvm.PointerTypeKind:
// mo padding bytes
return nil
case llvm.ArrayTypeKind:
llvmArrayType := llvmType
llvmElemType := llvmType.ElementType()
// hashmapKeyFuncName returns a canonical name for a generated hash or equal
// function based on the key type's underlying structure. Named types are
// replaced with their underlying types so that structurally identical key
// types (e.g., struct{i1; str1} and struct{i2; str2} where both i1, i2 are
// int and str1, str2 are string) share the same generated function.
func hashmapKeyFuncName(prefix string, keyType types.Type) string {
return prefix + "." + hashmapCanonicalTypeName(keyType)
}
for i := 0; i < llvmArrayType.ArrayLength(); i++ {
idx := llvm.ConstInt(b.uintptrType, uint64(i), false)
elemPtr := b.CreateInBoundsGEP(llvmArrayType, ptr, []llvm.Value{zero, idx}, "")
// zero any padding bytes in this element
b.zeroUndefBytes(llvmElemType, elemPtr)
// hashmapCanonicalTypeName returns a string representation of the hash/equal
// operations needed for a type, stripping named types where the operation does
// not depend on the name. Pointer and channel names do not include the element
// type because their hash/equal operations only use the pointer word.
func hashmapCanonicalTypeName(t types.Type) string {
switch t := t.Underlying().(type) {
case *types.Basic:
return t.Name()
case *types.Pointer:
return "*"
case *types.Chan:
switch t.Dir() {
case types.SendRecv:
return "chan"
case types.SendOnly:
return "chan<-"
case types.RecvOnly:
return "<-chan"
}
case llvm.StructTypeKind:
llvmStructType := llvmType
numFields := llvmStructType.StructElementTypesCount()
llvmElementTypes := llvmStructType.StructElementTypes()
for i := 0; i < numFields; i++ {
idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)
elemPtr := b.CreateInBoundsGEP(llvmStructType, ptr, []llvm.Value{zero, idx}, "")
// zero any padding bytes in this field
llvmElemType := llvmElementTypes[i]
b.zeroUndefBytes(llvmElemType, elemPtr)
// zero any padding bytes before the next field, if any
offset := b.targetData.ElementOffset(llvmStructType, i)
storeSize := b.targetData.TypeStoreSize(llvmElemType)
fieldEndOffset := offset + storeSize
var nextOffset uint64
if i < numFields-1 {
nextOffset = b.targetData.ElementOffset(llvmStructType, i+1)
} else {
// Last field? Next offset is the total size of the allocate struct.
nextOffset = b.targetData.TypeAllocSize(llvmStructType)
}
if fieldEndOffset != nextOffset {
n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false)
llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false)
paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), elemPtr, []llvm.Value{llvmStoreSize}, "")
b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "")
}
case *types.Interface:
if t.NumMethods() == 0 {
return "interface{}"
}
return t.String()
case *types.Struct:
var s strings.Builder
s.WriteString("struct{")
for i := 0; i < t.NumFields(); i++ {
if i > 0 {
s.WriteString("; ")
}
s.WriteString(hashmapCanonicalTypeName(t.Field(i).Type()))
}
return s.String() + "}"
case *types.Array:
return fmt.Sprintf("[%d]%s", t.Len(), hashmapCanonicalTypeName(t.Elem()))
}
return t.String()
}
// getOrGenerateKeyHashFunc returns an LLVM function that computes the hash
// of a key of the given type. The function is generated on first call and
// cached in the module.
func (b *builder) getOrGenerateKeyHashFunc(keyType types.Type) llvm.Value {
name := hashmapKeyFuncName("hashmapKeyHash", keyType)
if fn := b.mod.NamedFunction(name); !fn.IsNil() {
return fn
}
return nil
// Create the LLVM function type:
// (key ptr, size uintptr, seed uintptr, context ptr) -> i32
fnType := llvm.FunctionType(b.ctx.Int32Type(), []llvm.Type{
b.dataPtrType, b.uintptrType, b.uintptrType, b.dataPtrType,
}, false)
fn := llvm.AddFunction(b.mod, name, fnType)
fn.SetLinkage(llvm.LinkOnceODRLinkage)
fn.SetUnnamedAddr(true)
b.addStandardAttributes(fn)
// Generate the function body.
savedBlock := b.GetInsertBlock()
defer b.SetInsertPointAtEnd(savedBlock)
entry := b.ctx.AddBasicBlock(fn, "entry")
b.SetInsertPointAtEnd(entry)
keyPtr := fn.Param(0)
seed := fn.Param(2)
llvmKeyType := b.getLLVMType(keyType)
hash := b.generateKeyHash(keyType, llvmKeyType, keyPtr, seed)
b.CreateRet(hash)
return fn
}
// getOrGenerateKeyEqualFunc returns an LLVM function that compares two keys
// of the given type for equality. The function is generated on first call
// and cached in the module.
func (b *builder) getOrGenerateKeyEqualFunc(keyType types.Type) llvm.Value {
name := hashmapKeyFuncName("hashmapKeyEqual", keyType)
if fn := b.mod.NamedFunction(name); !fn.IsNil() {
return fn
}
// Create the LLVM function type:
// (x ptr, y ptr, n uintptr, context ptr) -> i1
fnType := llvm.FunctionType(b.ctx.Int1Type(), []llvm.Type{
b.dataPtrType, b.dataPtrType, b.uintptrType, b.dataPtrType,
}, false)
fn := llvm.AddFunction(b.mod, name, fnType)
fn.SetLinkage(llvm.LinkOnceODRLinkage)
fn.SetUnnamedAddr(true)
b.addStandardAttributes(fn)
// Generate the function body.
savedBlock := b.GetInsertBlock()
defer b.SetInsertPointAtEnd(savedBlock)
entry := b.ctx.AddBasicBlock(fn, "entry")
b.SetInsertPointAtEnd(entry)
xPtr := fn.Param(0)
yPtr := fn.Param(1)
llvmKeyType := b.getLLVMType(keyType)
result := b.generateKeyEqual(keyType, llvmKeyType, xPtr, yPtr, fn)
b.CreateRet(result)
return fn
}
// generateKeyHash generates IR that hashes a key value. Returns the i32 hash.
func (b *builder) generateKeyHash(keyType types.Type, llvmKeyType llvm.Type, keyPtr llvm.Value, seed llvm.Value) llvm.Value {
switch keyType := keyType.Underlying().(type) {
case *types.Basic:
if keyType.Info()&types.IsString != 0 {
// Hash the string contents. The size parameter is unused by
// hashmapStringPtrHash (it dereferences the string header to
// get the actual length), but we pass it for signature
// consistency with other hash functions.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false)
return b.createRuntimeCall("hashmapStringPtrHash", []llvm.Value{keyPtr, size, seed}, "hash")
}
if keyType.Info()&types.IsFloat != 0 {
// Float hash: normalizes -0 to +0 before hashing.
if keyType.Kind() == types.Float32 {
return b.createRuntimeCall("hashmapFloat32Hash", []llvm.Value{keyPtr, seed}, "hash")
}
return b.createRuntimeCall("hashmapFloat64Hash", []llvm.Value{keyPtr, seed}, "hash")
}
if keyType.Info()&types.IsComplex != 0 {
// Complex hash: hash real and imaginary parts as floats.
if keyType.Kind() == types.Complex64 {
realPtr := keyPtr
imagPtr := b.CreateInBoundsGEP(b.ctx.Int8Type(), keyPtr, []llvm.Value{
llvm.ConstInt(b.uintptrType, 4, false),
}, "")
realHash := b.createRuntimeCall("hashmapFloat32Hash", []llvm.Value{realPtr, seed}, "hash.real")
imagHash := b.createRuntimeCall("hashmapFloat32Hash", []llvm.Value{imagPtr, seed}, "hash.imag")
return b.CreateXor(realHash, imagHash, "")
}
realPtr := keyPtr
imagPtr := b.CreateInBoundsGEP(b.ctx.Int8Type(), keyPtr, []llvm.Value{
llvm.ConstInt(b.uintptrType, 8, false),
}, "")
realHash := b.createRuntimeCall("hashmapFloat64Hash", []llvm.Value{realPtr, seed}, "hash.real")
imagHash := b.createRuntimeCall("hashmapFloat64Hash", []llvm.Value{imagPtr, seed}, "hash.imag")
return b.CreateXor(realHash, imagHash, "")
}
// Integer/boolean: hash the raw bytes.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false)
return b.createRuntimeCall("hash32", []llvm.Value{keyPtr, size, seed}, "hash")
case *types.Pointer, *types.Chan:
// Pointers and channels: hash as raw pointer-sized bytes.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false)
return b.createRuntimeCall("hash32", []llvm.Value{keyPtr, size, seed}, "hash")
case *types.Interface:
// Interface: use runtime reflection-based hash.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false)
return b.createRuntimeCall("hashmapInterfacePtrHash", []llvm.Value{keyPtr, size, seed}, "hash")
case *types.Struct:
hash := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < keyType.NumFields(); i++ {
if keyType.Field(i).Name() == "_" {
continue // blank fields are ignored in Go equality
}
fieldType := keyType.Field(i).Type()
llvmFieldType := b.getLLVMType(fieldType)
if b.targetData.TypeAllocSize(llvmFieldType) == 0 {
continue // skip zero-sized fields
}
idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)
fieldPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, idx}, "")
fieldHash := b.generateKeyHash(fieldType, llvmFieldType, fieldPtr, seed)
hash = b.CreateXor(hash, fieldHash, "")
}
return hash
case *types.Array:
elemType := keyType.Elem()
llvmElemType := b.getLLVMType(elemType)
arrayLen := keyType.Len()
if hashmapIsBinaryKey(elemType) {
// All elements are binary-comparable; hash the entire array as raw bytes.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false)
return b.createRuntimeCall("hash32", []llvm.Value{keyPtr, size, seed}, "hash")
}
if arrayLen == 0 {
return llvm.ConstInt(b.ctx.Int32Type(), 0, false)
}
if arrayLen <= hashArrayUnrollLimit {
hash := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < int(arrayLen); i++ {
idx := llvm.ConstInt(b.uintptrType, uint64(i), false)
elemPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, idx}, "")
elemHash := b.generateKeyHash(elemType, llvmElemType, elemPtr, seed)
hash = b.CreateXor(hash, elemHash, "")
}
return hash
}
initHash := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
loopEntry := b.GetInsertBlock()
loopBody := b.ctx.AddBasicBlock(loopEntry.Parent(), "hash.array.body")
loopDone := b.ctx.AddBasicBlock(loopEntry.Parent(), "hash.array.done")
b.CreateBr(loopBody)
b.SetInsertPointAtEnd(loopBody)
phiI := b.CreatePHI(b.uintptrType, "i")
phiHash := b.CreatePHI(b.ctx.Int32Type(), "hash.acc")
elemPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, phiI}, "")
elemHash := b.generateKeyHash(elemType, llvmElemType, elemPtr, seed)
newHash := b.CreateXor(phiHash, elemHash, "")
nextI := b.CreateAdd(phiI, llvm.ConstInt(b.uintptrType, 1, false), "")
cond := b.CreateICmp(llvm.IntULT, nextI, llvm.ConstInt(b.uintptrType, uint64(arrayLen), false), "")
b.CreateCondBr(cond, loopBody, loopDone)
bodyEnd := b.GetInsertBlock()
phiI.AddIncoming([]llvm.Value{llvm.ConstInt(b.uintptrType, 0, false), nextI},
[]llvm.BasicBlock{loopEntry, bodyEnd})
phiHash.AddIncoming([]llvm.Value{initHash, newHash},
[]llvm.BasicBlock{loopEntry, bodyEnd})
b.SetInsertPointAtEnd(loopDone)
return newHash
default:
panic(fmt.Sprintf("unhandled key type for hash generation: %T", keyType))
}
}
// generateKeyEqual generates IR that compares two key values for equality.
// Returns an i1 result.
func (b *builder) generateKeyEqual(keyType types.Type, llvmKeyType llvm.Type, xPtr, yPtr llvm.Value, fn llvm.Value) llvm.Value {
switch keyType := keyType.Underlying().(type) {
case *types.Basic:
if keyType.Info()&types.IsString != 0 {
// Compare strings: load both string headers and compare.
xStr := b.CreateLoad(llvmKeyType, xPtr, "x.str")
yStr := b.CreateLoad(llvmKeyType, yPtr, "y.str")
return b.createRuntimeCall("stringEqual", []llvm.Value{xStr, yStr}, "eq")
}
if keyType.Info()&types.IsFloat != 0 {
// Float equality: fcmp oeq handles -0==+0 (true) and NaN==NaN (false).
xVal := b.CreateLoad(llvmKeyType, xPtr, "x.float")
yVal := b.CreateLoad(llvmKeyType, yPtr, "y.float")
return b.CreateFCmp(llvm.FloatOEQ, xVal, yVal, "eq")
}
if keyType.Info()&types.IsComplex != 0 {
// Complex equality: both real and imaginary parts must be equal.
var floatType llvm.Type
if keyType.Kind() == types.Complex64 {
floatType = b.ctx.FloatType()
} else {
floatType = b.ctx.DoubleType()
}
floatSize := b.targetData.TypeAllocSize(floatType)
imagOffset := llvm.ConstInt(b.uintptrType, floatSize, false)
// Real parts
xReal := b.CreateLoad(floatType, xPtr, "x.real")
yReal := b.CreateLoad(floatType, yPtr, "y.real")
realEq := b.CreateFCmp(llvm.FloatOEQ, xReal, yReal, "eq.real")
// Imaginary parts
xImagPtr := b.CreateInBoundsGEP(b.ctx.Int8Type(), xPtr, []llvm.Value{imagOffset}, "")
yImagPtr := b.CreateInBoundsGEP(b.ctx.Int8Type(), yPtr, []llvm.Value{imagOffset}, "")
xImag := b.CreateLoad(floatType, xImagPtr, "x.imag")
yImag := b.CreateLoad(floatType, yImagPtr, "y.imag")
imagEq := b.CreateFCmp(llvm.FloatOEQ, xImag, yImag, "eq.imag")
return b.CreateAnd(realEq, imagEq, "")
}
// Integer/boolean: compare raw bytes.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false)
return b.createRuntimeCall("memequal", []llvm.Value{xPtr, yPtr, size}, "eq")
case *types.Pointer, *types.Chan:
// Pointers and channels: compare as raw pointer-sized bytes.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false)
return b.createRuntimeCall("memequal", []llvm.Value{xPtr, yPtr, size}, "eq")
case *types.Interface:
// Interface: use runtime interface equality.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false)
return b.createRuntimeCall("hashmapInterfaceEqual", []llvm.Value{xPtr, yPtr, size}, "eq")
case *types.Struct:
result := llvm.ConstInt(b.ctx.Int1Type(), 1, false) // start with true
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < keyType.NumFields(); i++ {
if keyType.Field(i).Name() == "_" {
continue // blank fields are ignored in Go equality
}
fieldType := keyType.Field(i).Type()
llvmFieldType := b.getLLVMType(fieldType)
if b.targetData.TypeAllocSize(llvmFieldType) == 0 {
continue // skip zero-sized fields
}
idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)
xFieldPtr := b.CreateInBoundsGEP(llvmKeyType, xPtr, []llvm.Value{zero, idx}, "")
yFieldPtr := b.CreateInBoundsGEP(llvmKeyType, yPtr, []llvm.Value{zero, idx}, "")
fieldEq := b.generateKeyEqual(fieldType, llvmFieldType, xFieldPtr, yFieldPtr, fn)
result = b.CreateAnd(result, fieldEq, "")
}
return result
case *types.Array:
elemType := keyType.Elem()
llvmElemType := b.getLLVMType(elemType)
arrayLen := keyType.Len()
if hashmapIsBinaryKey(elemType) {
// All elements are binary-comparable; compare the entire array.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false)
return b.createRuntimeCall("memequal", []llvm.Value{xPtr, yPtr, size}, "eq")
}
if arrayLen == 0 {
return llvm.ConstInt(b.ctx.Int1Type(), 1, false)
}
if arrayLen <= hashArrayUnrollLimit {
result := llvm.ConstInt(b.ctx.Int1Type(), 1, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < int(arrayLen); i++ {
idx := llvm.ConstInt(b.uintptrType, uint64(i), false)
xElemPtr := b.CreateInBoundsGEP(llvmKeyType, xPtr, []llvm.Value{zero, idx}, "")
yElemPtr := b.CreateInBoundsGEP(llvmKeyType, yPtr, []llvm.Value{zero, idx}, "")
elemEq := b.generateKeyEqual(elemType, llvmElemType, xElemPtr, yElemPtr, fn)
result = b.CreateAnd(result, elemEq, "")
}
return result
}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
loopEntry := b.GetInsertBlock()
loopBody := b.ctx.AddBasicBlock(loopEntry.Parent(), "eq.array.body")
loopDone := b.ctx.AddBasicBlock(loopEntry.Parent(), "eq.array.done")
b.CreateBr(loopBody)
b.SetInsertPointAtEnd(loopBody)
phiI := b.CreatePHI(b.uintptrType, "i")
xElemPtr := b.CreateInBoundsGEP(llvmKeyType, xPtr, []llvm.Value{zero, phiI}, "")
yElemPtr := b.CreateInBoundsGEP(llvmKeyType, yPtr, []llvm.Value{zero, phiI}, "")
elemEq := b.generateKeyEqual(elemType, llvmElemType, xElemPtr, yElemPtr, fn)
nextI := b.CreateAdd(phiI, llvm.ConstInt(b.uintptrType, 1, false), "")
atEnd := b.CreateICmp(llvm.IntUGE, nextI, llvm.ConstInt(b.uintptrType, uint64(arrayLen), false), "")
exitLoop := b.CreateOr(atEnd, b.CreateNot(elemEq, ""), "")
b.CreateCondBr(exitLoop, loopDone, loopBody)
bodyEnd := b.GetInsertBlock()
phiI.AddIncoming([]llvm.Value{llvm.ConstInt(b.uintptrType, 0, false), nextI},
[]llvm.BasicBlock{loopEntry, bodyEnd})
b.SetInsertPointAtEnd(loopDone)
return elemEq
default:
panic(fmt.Sprintf("unhandled key type for equal generation: %T", keyType))
}
}
+1 -2
View File
@@ -29,8 +29,7 @@ func (s *stdSizes) Alignof(T types.Type) int64 {
// is the largest of the values unsafe.Alignof(x.f) for each
// field f of x, but at least 1."
max := int64(1)
for i := 0; i < t.NumFields(); i++ {
f := t.Field(i)
for f := range t.Fields() {
if a := s.Alignof(f.Type()); a > max {
max = a
}

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