Compare commits

...

107 Commits

Author SHA1 Message Date
deadprogram ae9eb7e64e esp32c6: bind ROM data symbols to their ROM addresses
The ROM symbol tables were emitted entirely as PROVIDE(), which is right
for ROM functions but wrong for ROM data. Symbols like g_osi_funcs_p,
pTxRx, our_tx_eb and lmacConfMib_ptr are variables in RAM that mask ROM
code reads and writes at a fixed address; they are shared storage, not a
fallback implementation.

With PROVIDE(), a weak definition in the program wins and the program and
the mask ROM then use two different locations for the same variable.
g_osi_funcs_p hit exactly this: espradio declares it weak, so it resolved
to .bss while ROM code kept reading 0x4087ff6c, which nothing ever wrote.

Assign the 121 ROM data symbols unconditionally, as esp32c3.ld already
does for the same variables. Addresses verified identical to ESP-IDF's
esp32c6 ROM linker scripts.

Signed-off-by: Ron Evans <ron@hybridgroup.com>
2026-08-30 11:05:14 +02:00
deadprogram 6b53c58f92 esp32c6: fix .rodata flash mapping being off by a few bytes
The flash MMU maps 64kB pages, so a flash-mapped section is only read
correctly when its virtual address and its offset within the firmware
image agree modulo 64kB. targets/esp32c6.ld reproduces the image offset
in .rodata_dummy by accumulating the preceding segment sizes and headers,
which works only if the linker inserts no alignment padding between the
dummy and .rodata -- padding moves the virtual address without moving the
image offset.

lld gives .rodata 8-byte alignment, but the accumulated offset is only
4-byte aligned, so whenever the preceding segments happened to leave the
running offset at a 4-mod-8 boundary the whole of .rodata was mapped 4
bytes off. Every read of constant data then returned neighbouring bytes.

This is silent and looks like arbitrary memory corruption rather than a
mapping bug. It was found while bringing up WiFi: the blob rejected its
init config because the first field of a const struct read back as a code
pointer, and whether a given build was affected depended on unrelated code
size changes.

Pad .data, .iram and .text to a multiple of 8 so the running image offset
stays 8-aligned and matches, and add ASSERTs for both flash-mapped
sections so this cannot regress silently.

Signed-off-by: Ron Evans <ron@hybridgroup.com>
2026-08-30 10:36:44 +02:00
deadprogram a7626e87e9 esp32c6: prepare target for espradio WiFi support
The ESP32-C6 target was never built with a radio blob in mind. Three gaps
in targets/esp32c6.ld prevented espradio from linking or running:

- The Espressif WiFi/PHY libraries resolve ~245 internal symbols against
  the mask ROM. Only seven ROM symbols were defined. Add the ESP-IDF
  generated ROM interface tables (1201 symbols), all as PROVIDE() so any
  real definition in the program wins over the ROM copy.
- The .iram output section did not collect the blob IRAM sections
  (.wifi0iram, .wifirxiram, .wifislpiram, .wifislprxiram, .wifiextrairam,
  .wifiorslpiram, .coexiram, .iram1). Without them that code lands in
  flash and crashes when called from an interrupt.
- _heap_end ran to the end of SRAM at 0x40880000, over the mask ROM stack
  and ROM data. phy_param_rom sits at 0x4087fce8, inside that range, so a
  growing Go heap would corrupt the PHY. Cap the heap at 0x4087C610.

Also add the espradio build tag to esp32c6.json, matching esp32c3.json
and esp32s3.json.

Signed-off-by: Ron Evans <ron@hybridgroup.com>
2026-08-29 19:30:41 +02:00
deadprogram 421d98b248 ci: stop the LLVM caches from being evicted
The Actions cache of the repository was at 10.24 GB against a quota of
10 GB, so GitHub removed entries by least recent use. The LLVM entries
were among the first to go, and CI then built LLVM again although the
LLVM version did not change.

The Docker buildx layer blobs held 6.0 GB of the quota. All LLVM caches
together held 1.97 GB. Move the Docker layer cache to a registry cache in
GHCR, which does not use the Actions quota. Both jobs have the necessary
permission and login already.

Pin the LLVM commit in llvm-version.txt and fetch that commit, in place
of a clone of the branch tip tinygo_22.x. Add a hash of that file to each
LLVM cache key, so a change of the LLVM version invalidates the caches
and no other change does.

Also rename the LLVM image tags from llvm-20 to llvm-22.
2026-08-29 17:18:13 +02:00
Pavel Burgr b420a8be17 Puya PY32F MCU support (#5106)
* pinout yamls removed

* machine: implement default UART pin configuration for Embedfire boards

* flash command moved to chip level so VS Code plugin can select bare chip

* machine: add support for alternate pin mode configuration

* add build targets for embedfire on py32

* add support for embedfire target in GNUmakefile for py32

* lib/py32-svd: remove duplicate DBGMCU.IDCODE CODE field

Update submodule to fix duplicate SetIDCODE/GetIDCODE method
declarations in the generated py32f002bxx.go device file.

* update subproject commit reference in py32-svd

* refactor: update CPU frequency handling in machine and runtime packages

* refactor: replace ConfigureUARTPin function with direct pin configuration in UART setup

* SetAltFunc documentation for GPIO pin alternate functions

* refactor: improve UART write and flush error handling with timeout

* machine/py32: generalize UART driver to any USART

Add a per-instance setup func to the UART type so the driver is no longer
hardwired to USART1. DefaultUART stays USART1; add UART2 (USART2) in a
separately build-tagged file since py32f002x parts lack USART2. RX IRQ
handlers reference runtime-assigned vars to break the init cycle, and setup
is assigned in the var initializer so it runs before InitSerial.

Add the py32f003_32k_4k target (32K flash / 4K RAM).

* Add canonical PY32F002, F003, and F030 density targets

Use CMSIS/pyocd device names for target files and build tags. Add all F003 and F030 densities plus F002A/B, correct F002B to 24K flash and its own startup file, fix the F030x8 pyocd target, and update Embedfire inheritance.

* Add targets for all PY32 CMSIS devices

* Support all PY32 register layout variants

* machine/py32: use generated register definitions

* machine/py32: fix alternate-function documentation

* machine/py32: report the configured CPU frequency

* machine/py32: use unsafe.Add for GPIO ports

* machine/py32: bound and yield UART polling

* machine/py32: configure UART pins from UARTConfig

* build: use the current PY32 SVD repository URL

* test: cover PY32 UART register layouts

* machine/py32: restore dynamic CPU frequency tracking

* machine/py32: share the USART TX-ready bit

* targets/py32: scale system stacks with RAM

* machine/py32: keep clock state internal

* targets/py32: normalize generated metadata

* machine/py32: normalize GPIO configuration

* machine/py32: fix T020 UART setup

* machine/py32: reduce clock variant files

* machine/py32: clean up UART variants

* machine/py32: decouple UART clock capability

* lib/py32-svd: use organization repository

* runtime/py32: name 24MHz HSI encodings

* machine/py32: derive startup clock from capability

* machine/py32: use generated USART clock mask

* machine/py32: name T020 8-bit UART mode

* ci: include PY32 in sharded smoke tests

* runtime: remove PY32 HSI frequency workaround
2026-08-29 08:41:43 +02:00
Matthew Hiles f6e502e121 UEFI: add support for tasks scheduler and make it default (#5553)
* add support for UEFI time and UEFI events; fix STOP \n -> \r\n conversion

* make it so both scheduler=none and scheduler=tasks works

* address pr comments
  - Renamed/shared the amd64 Win64 ABI task stack Go file for both Windows and UEFI.
  - Deleted the duplicate UEFI task stack Go file and old Windows-suffixed Go file.
  - Added task_stack_amd64_windows.S unconditionally to targets/uefi-amd64.json.
  - Removed the UEFI ExtraFiles() special case from compileopts/config.go.
  - Added a scheduler.none tinygo_task_exit stub.
  - Removed the custom UEFI sleep override so normal scheduler sleep queue is used.

* revert back to simpler return value for ExtraFiles()

* create uefi specific tasks_none file

* remove unused sleepSchedulerCustom stuff

* add back the uefi tag

* lib: restore macos-minimal-sdk pointer
2026-08-28 19:48:48 +02:00
Pat Whittingslow f924a9e3ea Add agents.md guideline for using ASD-STE100 in documentation and discourse (#5566)
* Add agents.md guideline for using ASD-STE100 in documentation

* @deadprogram success story

* Rename agents.md to AGENTS.md
2026-08-28 17:42:58 +02:00
deadprogram b9577ac8aa net: update net submodule from latest changes
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-28 11:27:07 +02:00
Ron Evans a213d9ae46 ci: run the full smoke test once, and split the GNUmakefile (#5616)
* GNUmakefile: split into topic files in make/

The GNUmakefile had 1295 lines and mixed build configuration, LLVM
bootstrapping, device generation, four test suites, the smoke tests,
release packaging, and lint tools. The smoke tests alone were 500 lines.

Move each part into its own file in make/ and include them from
GNUmakefile. config.mk must be included first because the other files
use its variables in immediate assignments and conditionals.

There is no change in behavior. The parsed make database is identical
for the default build and for ASSERT=1, STATIC=1, XTENSA=0, STM32=0,
WASM=0, and CROSS=aarch64-linux-gnu, except for MAKEFILE_LIST and the
.PHONY list, which now also includes targets that were phony but not
declared.

The Dockerfile copies GNUmakefile alone before it builds LLVM, to keep
that layer independent of the source tree. It must copy make/ too.

* make/smoketest.mk: split into groups and add smoketest-quick

The smoke test was one recipe of about 500 lines with 236 builds that
always ran in sequence. Split it at the group boundaries that were
already there, so that:

- `make -j smoketest` builds the groups in parallel. A full run goes
  from 325 to 91 seconds on a 32 core machine.
- CI can shard the groups across runners.

Each group writes to its own name in build/smoke/, because all builds
wrote to test.hex before and would overwrite each other in a parallel
build. The output extension selects the format, so it stays per line.

Add smoketest-quick, which builds one board for each processor
architecture. The full smoke test answers "can TinyGo build for every
board", which does not depend on the host OS, so it only needs to run
on one OS. The other jobs use smoketest-quick.

The comment above the esp32c3 group had 4 spaces of indentation instead
of a tab. That was harmless in the middle of a recipe, but it is now the
first line of a group, where it would stop the recipe from starting.

The set of build commands is unchanged for the default flags and for
XTENSA=0, STM32=0, and WASM=0. All 226 checksums are the same as before,
for a sequential build and for `make -j16`.

* ci: run the full smoke test once, on Linux only

The smoke test ran six times for each push: twice on Linux, twice on
macOS, once on Windows, and once in the compatibility test. Together
that was about 97 minutes of the CI time.

The smoke test checks that TinyGo can build a binary for each board.
That does not depend on the host OS. The only part that does is one
build behind a Windows check.

Add a smoketest-linux job that runs the full set, split across four
runners that use the tarball from the build-linux job. The groups are
balanced with the measured build time of each group. Remove the full
smoke test from test-linux-build, which the new job replaces, and use
smoketest-quick for the other four jobs.

Expected result: about 55 to 39 minutes for the slowest workflow, and
about 97 to 35 minutes of total smoke test time.

* make/smoketest.mk: build nintendoswitch in smoketest-quick

It is the only target that uses the aarch64 LLVM triple. With it,
smoketest-quick covers all 13 triples that the full smoke test uses.
2026-08-28 09:08:26 +02:00
Patricio Whittingslow ac98a3df85 targets:wasm_exec.js set _pendingEvent to null 2026-08-27 20:32:13 +02:00
deadprogram 86d58db1af lib: update macos-minimal-sdk to v0.1.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-27 09:09:19 +02:00
Felipe Gené 31fff2c9a3 runtime: run syscall/js finalizers on wasm without a manual GC (#5545)
* runtime: run syscall/js finalizers on wasm without a manual GC

* runtime: address review feedback on finalizer idle GC

* runtime: clear a finished task's args pointer so its arguments are collectable

* runtime: skip the finalizer scan with a per-block registration bit

* runtime: guard the finalizer registration bitmap with gcLock

* testdata: cover finalizer invariants on every scheduler

* main_test: limit the finalizer scheduler variants to linux and darwin

* testdata: wait for the finalizer queue to drain before asserting

* testdata: make the finalizer counters atomic and wait for a known drain count

* runtime: add finalizer bookkeeping asserts under runtime_asserts

* runtime: address finalizer GC review feedback

* testdata: strengthen blocked stack finalizer test

* runtime: fix finalizer cleanup edge cases

* runtime: decouple wasm export scheduling from finalizers

* runtime: avoid redundant wakeups for re-entrant wasm exports

* runtime: simplify finalizer comments
2026-08-26 20:03:44 +02:00
deadprogram 8d6240a5e6 modules: update espflasher with version including fixes for esp32c3/esp32s3
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-26 15:37:27 +02:00
Jake Bailey 8f8ce06594 builder: convert TestBinarySize to a golden file test 2026-08-26 13:00:31 +02:00
V 840af7a842 machine/esp32: add ADC driver (#5595)
* machine/esp32: add ADC driver

Implements ADC1 on the Xtensa ESP32: InitADC, Configure and Get for
GPIO36, GPIO37, GPIO38, GPIO39, GPIO32, GPIO33, GPIO34 and GPIO35
(channels 0-7). The ADC pins are not contiguous on this chip, so the
pin to channel mapping is a lookup rather than arithmetic as on the
ESP32-S3.

Conversions are driven by the RTC controller under software control,
and Get returns the 12-bit sample scaled to 0..65520 to match the
other ESP ADC drivers.

The analog pads are spread over three unrelated RTC_IO registers
(SENSOR_PADS, XTAL_32K_PAD and ADC_PAD), so pad setup is kept local to
this file rather than adding a PinAnalog mode to machine_esp32.go.
That keeps the change to a single file.

Values are raw and uncalibrated. Unlike the ESP32-C3, S3 and C6
drivers there is no eFuse or self-calibration step; accurate voltage
mapping should be done with a two-point calibration in user code.

ADC2 is not implemented. On the ESP32 it is shared with the Wi-Fi
radio and cannot be used reliably while the radio is active.

Tested on an ESP32 Coreboard V2 with a photoresistor divider on
GPIO36. Readings swept 5056..59824 over the light range, all eight
channels returned independent values, and an invalid pin returned an
error from Configure and 0 from Get.

Signed-off-by: zombieleet <osikwemhev@gmail.com>

* fix: use package level err definiition and return (uint32, bool) from adcRTCGPIO

---------

Signed-off-by: zombieleet <osikwemhev@gmail.com>
2026-08-25 13:24:05 +02:00
deadprogram 242f34d71a esp32s3: fix register-window corruption under interrupt load
Remove the C3 bluetooth hook addresses from esp32s3.ld (on the S3 they
point into the ROM md5/crc thunk table, and being bare assignments they
also shadowed the blob's own definitions), keep the interrupt frame clear
of the 16-byte windowed-ABI save area below SP, and make tinygo_swapTask
hold INTLEVEL across the stack switch while keeping the running frame's
WINDOWSTART bit set.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-23 21:29:21 +02:00
Moses Narrow f71b63053c reflect: skip the MakeChan panic cases on every wasm target, not just wasip1
The guard was on runtime.GOOS == "wasip1", so under wasip2 the three cases that
rely on recover ran anyway. recover does not work on wasm yet, so the panic
escaped and trapped the test binary — which is the tinygo-test-wasip2-fast
failure on this PR.

Checked on runtime.GOARCH instead, since the limitation is wasm's rather than
any one platform's. wasip1, wasip2 and js/wasm all set the tinygo.wasm build
tag, and arch_tinygowasm.go defines GOARCH as wasm for it, so one condition
covers all three of the wasm test targets.

Worth noting for anyone reading targets/wasip2.json: the goarch there is arm,
but that is what is handed to the Go toolchain for package selection —
runtime.GOARCH is the one above.
2026-08-20 17:11:24 -07:00
Moses Narrow 1579b235e8 reflect: implement MakeChan
TinyGo had MakeMap/MakeSlice but not MakeChan. Implement it via the runtime
chanMake primitive (mirroring MakeMap), so packages that call reflect.MakeChan
(e.g. github.com/ugorji/go/codec used by gin) compile and work.
2026-08-20 17:11:24 -07:00
rdon-key af429a597b interp: mark pointers in aggregate call operands as external 2026-08-20 19:01:03 +02:00
sago35 93bc65479f machine: add USBDevice.Attach and USBDevice.Detach (#5563)
* machine: add USBDevice.Attach and USBDevice.Detach

The USB device is attached to the bus automatically during startup,
before user code has a chance to finish its USB configuration (device
identifiers, extra HID interfaces, ...). Composite devices such as
keyboards may therefore be enumerated by the host with an incomplete
configuration.

Attach and Detach expose the soft-connect control (DP pull-up) so that
an application or library can detach in an init function, complete its
configuration, and attach again to let the host enumerate the finished
device. They can also be used to force re-enumeration without
replugging the cable.

Implemented for atsamd21, atsamd51, nrf52840, rp2040 and rp2350.

* machine: make USB Detach sticky on nrf52840

The USB IRQ handler re-enables the DP pull-up on every power-ready
event, silently undoing an earlier Detach. Guard the pull-up write with
a detached flag so the device stays off the bus until Attach is called.

* machine: add USB Attach and Detach to stm32 and esp32 targets

- stm32f4, stm32f7, stm32h7: implement Attach and Detach using the
  DCTL soft-disconnect bit that Configure already toggles.
- esp32c3, esp32c6, esp32s3: add no-op stubs to keep user code
  portable; the fixed-function USB Serial/JTAG controller has no
  software-controlled soft-connect.

* machine: use Attach and Detach in USB Configure

Replace the direct soft-connect register writes in Configure with the
equivalent Attach and Detach calls on atsamd21, atsamd51, rp2040,
rp2350, stm32f4, stm32f7 and stm32h7.
2026-08-20 17:17:33 +02:00
Jake Bailey 1958d6ff9d test: run ESP32 programs in QEMU 2026-08-20 16:07:13 +02:00
Jake Bailey 1991b00395 builder: fix ESP32 QEMU XIP image offsets 2026-08-20 16:07:13 +02:00
deadprogram ada22bc691 all: build/test using Go 1.27.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-20 14:27:13 +02:00
deadprogram e3bf9306b3 Revert "chore: copy fix to esp32xx"
This reverts commit 6cd0c00060.
2026-08-20 12:53:31 +02:00
Navendu Pottekkat 6cd0c00060 chore: copy fix to esp32xx
Signed-off-by: Navendu Pottekkat <navendu@apache.org>
2026-08-20 10:20:11 +02:00
Navendu Pottekkat dc97300c2e fix: remove incorrect !readLast check
Signed-off-by: Navendu Pottekkat <navendu@apache.org>
2026-08-20 10:20:11 +02:00
deadprogram 6136fb7b95 test: skip asn1 nesting-limit tests, and BoundarySlices crypto tests
Skip TestUnmarshalNestingLimitSlice/Struct, which fails due to the nesting limit
added in golang/go@6a6d115f9a.

Since Go 1.27 the crypto hash tests go through cryptotest.TestHash, which
calls cryptotest.BoundarySlices. Targets reporting GOOS=linux build
boundary.go (//go:build linux || darwin) instead of boundary_compat.go, and
that needs a working syscall.Mmap/syscall.Mprotect:

* baremetal fails to compile: undefined: syscall.Mprotect
* wasip2 gets ENOSYS from syscall.Mmap, and since t.Fatalf cannot Goexit on
  wasm the test falls through and panics with "slice out of range"

Exclude crypto/md5, crypto/sha1, crypto/sha256 and crypto/sha512 from both.
wasip1 reports GOOS=wasip1, gets the boundary_compat.go fallback, and keeps
testing all four.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-19 08:39:13 -07:00
deadprogram f21e027b5c all: build/test using Go 1.27-rc3
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-19 08:39:13 -07:00
Moses Narrow ac45c35924 os: add File.Chown on the unix path
file_unix.go (darwin/linux/wasip) was missing the exported (*File).Chown method
that file_other.go (baremetal/wasm) already has, so callers requiring the full
os.File surface — e.g. github.com/pkg/sftp — failed to compile for the
linux/amd64 TinyGo target. Delegates to the package-level Chown, mirroring
(*File).Truncate.
2026-08-18 14:18:42 -07:00
Moses Narrow 338af91ae6 runtime: implement MemStats.NumGC
Reading runtime.MemStats.NumGC is common enough in dependency code that its
absence is a compile error for programs that never look at the value. Rather
than add the field as a constant zero, track it:

- gc_blocks: count completed cycles in runGC, so collections triggered by an
  allocation are counted as well as explicit runtime.GC() calls. The counter is
  read and written under gcLock, like the other counters beside it.
- gc_boehm: report bdwgc's own gc_no from the prof_stats struct.
- gc_leaking: always 0, since that collector never completes a cycle.
2026-08-18 14:18:29 -07:00
v1rtl d74f70bdd0 os, syscall: add Statfs/Fstatfs stubs and Getpagesize for non-hosted targets 2026-08-18 14:18:19 -07:00
Navendu Pottekkat 80506a9cea esp32: fix pullup/pulldown on RTC GPIO pins
Signed-off-by: Navendu Pottekkat <navendu@apache.org>
2026-08-16 06:45:06 -07:00
Jake Bailey c33682cf00 builder: build SSA before compiling packages 2026-08-14 11:56:40 -07:00
Jake Bailey 570a3deac2 tests: normalize LLVM IR when updating goldens 2026-08-14 11:56:25 -07:00
Damian Gryski c4219439eb runtime: make arrays and struct field hashes order dependent 2026-08-12 12:08:52 -07:00
Konstantin Sharlaimov 7a9c649268 fix(gc): pause all cores before scanning stack and globals.
In gcMarkReachable, busy-wait for other cores to enter the interrupt handler and pause before scanning the GC core's stack or globals. Prevents data race where a running core relocates heap references to globals during mark phase.
2026-08-09 12:11:59 -07:00
Konstantin Sharlaimov 213d10838f fix(gc): correct leaking allocator bounds and overflow checks.
Change heap-end comparison to > to prevent spurious OOM when heapptr exactly matches heapEnd. Add overflow check to prevent heap wrapping.
2026-08-09 12:11:59 -07:00
Konstantin Sharlaimov b573ef3813 fix(gc): correct old size calculation in block realloc.
Using blocksPerStateByte instead of bytesPerBlock caused the old size to be underestimated for multi-block allocations, leading to data truncation on growing reallocations.
2026-08-09 12:11:59 -07:00
Mohammed-Asad-Khan 854f91ecf2 Fix to allow -o flag to point to a directory
Previously, running 'tinygo build -o dist/ file.go' where dist/ already
exists as a directory failed with 'open dist/: is a directory', because
Build() only auto-derived a binary name when outpath was empty, not when
it pointed to an existing directory. This adds that check, matching the
behavior of 'go build -o dir/' with a package.

Note: only handles the case where the directory already exists; if the
directory doesn't exist yet, behavior is unchanged.
2026-08-05 07:17:00 -07:00
deadprogram c2346570fb targets/esp32*: add espradio tag for convenience
This add the `espradio` build tag to all ESP32C3,
ESP32S3, and ESP32 boards, since they all have support
for wireless communication using espradio. This
just makes it to people do not have to remember to use
the build tag, the same as we have done with both
`ninafw` and `cyw43439` build tags.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-30 02:03:13 +02:00
deadprogram 169daed2a1 main: update to espflasher 0.8.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-28 13:57:25 +02:00
deadprogram cd5e2580c1 targets/esp32c3: add remaining required BT ROM functions to linker
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-28 12:11:11 +02:00
deadprogram 8b3200cec8 targets/esp32c3: add BT ROM functions to linker
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-27 17:34:22 +02:00
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
555 changed files with 18448 additions and 2501 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
build/ build/
llvm-*/ llvm-*/
!llvm-version.txt
.github .github
+5 -5
View File
@@ -40,13 +40,13 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.27.0'
cache: true cache: true
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-20-${{ matrix.os }}-v1 key: llvm-source-22-${{ matrix.os }}-v1-${{ hashFiles('llvm-version.txt') }}
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -71,7 +71,7 @@ jobs:
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-20-${{ matrix.os }}-v2 key: llvm-build-22-${{ matrix.os }}-v1-${{ hashFiles('llvm-version.txt') }}
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -107,7 +107,7 @@ jobs:
path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
archive: false archive: false
- name: Smoke tests - name: Smoke tests
run: make smoketest TINYGO=$(PWD)/build/tinygo run: make smoketest-quick TINYGO=$(PWD)/build/tinygo
test-macos-homebrew: test-macos-homebrew:
name: homebrew-install name: homebrew-install
runs-on: macos-latest runs-on: macos-latest
@@ -131,7 +131,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.27.0'
cache: true cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }}) - name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install -tags=llvm${{ matrix.version }} run: go install -tags=llvm${{ matrix.version }}
+3 -3
View File
@@ -20,7 +20,7 @@ jobs:
env: env:
# Oldest versions currently supported by TinyGo # Oldest versions currently supported by TinyGo
LLVM: "15" LLVM: "15"
Go: "1.24" # when updating this, also update minorMin in builder/config.go Go: "1.25" # when updating this, also update minorMin in builder/config.go
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -46,7 +46,7 @@ jobs:
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-20-linux-compat key: llvm-source-22-linux-compat-${{ hashFiles('llvm-version.txt') }}
path: llvm-project/compiler-rt path: llvm-project/compiler-rt
- name: Download LLVM source - name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true' if: steps.cache-llvm-source.outputs.cache-hit != 'true'
@@ -69,4 +69,4 @@ jobs:
- run: tinygo version - run: tinygo version
- run: make gen-device -j4 - run: make gen-device -j4
- run: go test -tags=llvm${{ env.LLVM }} -short -skip=TestErrors - run: go test -tags=llvm${{ env.LLVM }} -short -skip=TestErrors
- run: make smoketest XTENSA=0 - run: make smoketest-quick XTENSA=0
+2 -2
View File
@@ -64,5 +64,5 @@ jobs:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/tinygo-dev:buildcache
cache-to: type=gha,mode=max cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/tinygo-dev:buildcache,mode=max
+63 -15
View File
@@ -23,7 +23,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.27.0'
cache: true cache: true
- name: Run go mod tidy - name: Run go mod tidy
run: go mod tidy run: go mod tidy
@@ -36,7 +36,7 @@ jobs:
# statically linked binary. # statically linked binary.
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: golang:1.26-alpine image: golang:1.27-alpine
outputs: outputs:
version: ${{ steps.version.outputs.version }} version: ${{ steps.version.outputs.version }}
steps: steps:
@@ -66,7 +66,7 @@ jobs:
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-20-linux-alpine-v2 key: llvm-source-22-linux-alpine-v1-${{ hashFiles('llvm-version.txt') }}
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -91,7 +91,7 @@ jobs:
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-20-linux-alpine-v2 key: llvm-build-22-linux-alpine-v1-${{ hashFiles('llvm-version.txt') }}
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -115,7 +115,7 @@ jobs:
uses: actions/cache@v5 uses: actions/cache@v5
id: cache-binaryen id: cache-binaryen
with: with:
key: binaryen-linux-alpine-v2 key: binaryen-linux-alpine-v3
path: build/wasm-opt path: build/wasm-opt
- name: Build Binaryen - name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true' if: steps.cache-binaryen.outputs.cache-hit != 'true'
@@ -160,7 +160,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.27.0'
cache: true cache: true
- name: Install wasmtime - name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1 uses: bytecodealliance/actions/wasmtime/setup@v1
@@ -180,7 +180,43 @@ jobs:
- run: make tinygo-test-wasip1-fast - run: make tinygo-test-wasip1-fast
- run: make tinygo-test-wasip2-fast - run: make tinygo-test-wasip2-fast
- run: make tinygo-test-wasm - run: make tinygo-test-wasm
- run: make smoketest smoketest-linux:
# Build a binary for every supported board, using the binaries built in the
# build-linux job. This is the only job that runs the full smoke test. The
# result does not depend on the host OS, so the other jobs build a
# representative board for each architecture with smoketest-quick.
runs-on: ubuntu-latest
needs: build-linux
strategy:
fail-fast: false
matrix:
# Balanced with the measured build time of each group, so that the
# shards finish at about the same time.
group:
- smoketest-rp2xxx smoketest-selftest smoketest-wasm-sim smoketest-py32
- smoketest-esp smoketest-riscv smoketest-flags
- smoketest-examples smoketest-nxp smoketest-pwm-usb smoketest-avr
- smoketest-samd smoketest-stm32 smoketest-nrf smoketest-wasm
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0'
cache: true
- name: Download release artifact
uses: actions/download-artifact@v8
with:
name: tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
- name: Extract release tarball
run: |
mkdir -p ~/lib
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 testchdir ${{ matrix.group }}
assert-test-linux: assert-test-linux:
# Run all tests that can run on Linux, with LLVM assertions enabled to catch # Run all tests that can run on Linux, with LLVM assertions enabled to catch
# potential bugs. # potential bugs.
@@ -196,6 +232,10 @@ jobs:
cat /proc/cpuinfo cat /proc/cpuinfo
sudo apt-get update sudo apt-get update
sudo apt-get install --no-install-recommends \ sudo apt-get install --no-install-recommends \
libgcrypt20 \
libpixman-1-0 \
libsdl2-2.0-0 \
libslirp0 \
qemu-system-arm \ qemu-system-arm \
qemu-system-riscv32 \ qemu-system-riscv32 \
qemu-user \ qemu-user \
@@ -204,7 +244,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.27.0'
cache: true cache: true
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
@@ -216,11 +256,19 @@ jobs:
version: "29.0.1" version: "29.0.1"
- name: Setup `wasm-tools` - name: Setup `wasm-tools`
uses: bytecodealliance/actions/wasm-tools/setup@v1 uses: bytecodealliance/actions/wasm-tools/setup@v1
- name: Install Espressif QEMU
run: |
release=esp-develop-9.2.2-20260417
archive=qemu-xtensa-softmmu-${release//-/_}-x86_64-linux-gnu.tar.xz
curl -fL --retry 3 -o "$RUNNER_TEMP/$archive" \
"https://github.com/espressif/qemu/releases/download/$release/$archive"
tar -xJf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP"
echo "$RUNNER_TEMP/qemu/bin" >> "$GITHUB_PATH"
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-20-linux-asserts-v1 key: llvm-source-22-linux-asserts-v1-${{ hashFiles('llvm-version.txt') }}
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -245,7 +293,7 @@ jobs:
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-20-linux-asserts-v1 key: llvm-build-22-linux-asserts-v1-${{ hashFiles('llvm-version.txt') }}
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -281,7 +329,7 @@ jobs:
echo "$(pwd)/build" >> $GITHUB_PATH echo "$(pwd)/build" >> $GITHUB_PATH
- name: Test stdlib packages - name: Test stdlib packages
run: make tinygo-test run: make tinygo-test
- run: make smoketest - run: make smoketest-quick
- run: make wasmtest - run: make wasmtest
- run: make tinygo-test-baremetal - run: make tinygo-test-baremetal
- name: Check Go code formatting - name: Check Go code formatting
@@ -323,13 +371,13 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.27.0'
cache: true cache: true
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-20-linux-v1 key: llvm-source-22-linux-v1-${{ hashFiles('llvm-version.txt') }}
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -354,7 +402,7 @@ jobs:
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-20-linux-${{ matrix.goarch }}-v1 key: llvm-build-22-linux-${{ matrix.goarch }}-v1-${{ hashFiles('llvm-version.txt') }}
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -378,7 +426,7 @@ jobs:
uses: actions/cache@v5 uses: actions/cache@v5
id: cache-binaryen id: cache-binaryen
with: with:
key: binaryen-linux-${{ matrix.goarch }}-v4 key: binaryen-linux-${{ matrix.goarch }}-v5
path: build/wasm-opt path: build/wasm-opt
- name: Build Binaryen - name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true' if: steps.cache-binaryen.outputs.cache-hit != 'true'
+4 -4
View File
@@ -35,8 +35,8 @@ jobs:
uses: docker/metadata-action@v6 uses: docker/metadata-action@v6
with: with:
images: | images: |
tinygo/llvm-20 tinygo/llvm-22
ghcr.io/${{ github.repository_owner }}/llvm-20 ghcr.io/${{ github.repository_owner }}/llvm-22
tags: | tags: |
type=sha,format=long type=sha,format=long
type=raw,value=latest type=raw,value=latest
@@ -59,5 +59,5 @@ jobs:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/llvm-22:buildcache
cache-to: type=gha,mode=max cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/llvm-22:buildcache,mode=max
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-20-linux-nix-v1 key: llvm-source-22-linux-nix-v1-${{ hashFiles('llvm-version.txt') }}
path: | path: |
llvm-project/compiler-rt llvm-project/compiler-rt
- name: Download LLVM source - name: Download LLVM source
+5 -1
View File
@@ -19,6 +19,10 @@ jobs:
- name: Add GOBIN to $PATH - name: Add GOBIN to $PATH
run: | run: |
echo "$HOME/go/bin" >> $GITHUB_PATH echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '~1.25'
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
@@ -30,7 +34,7 @@ jobs:
uses: actions/cache@v5 uses: actions/cache@v5
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-20-sizediff-v1 key: llvm-source-22-sizediff-v1-${{ hashFiles('llvm-version.txt') }}
path: | path: |
llvm-project/compiler-rt llvm-project/compiler-rt
- name: Download LLVM source - name: Download LLVM source
+7 -7
View File
@@ -34,13 +34,13 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.27.0'
cache: true cache: true
- name: Restore cached LLVM source - name: Restore cached LLVM source
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-20-windows-v3 key: llvm-source-22-windows-v3-${{ hashFiles('llvm-version.txt') }}
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -65,7 +65,7 @@ jobs:
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-20-windows-v5 key: llvm-build-22-windows-v1-${{ hashFiles('llvm-version.txt') }}
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -129,7 +129,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.27.0'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
@@ -139,7 +139,7 @@ jobs:
# This build is already unzipped. # This build is already unzipped.
- name: Smoke tests - name: Smoke tests
shell: bash shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo run: make smoketest-quick TINYGO=$(PWD)/build/tinygo/bin/tinygo
stdlib-test-windows: stdlib-test-windows:
runs-on: windows-2022 runs-on: windows-2022
@@ -150,7 +150,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.27.0'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
@@ -176,7 +176,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.27.0'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
+2
View File
@@ -24,6 +24,8 @@ src/device/renesas/*.go
src/device/renesas/*.s src/device/renesas/*.s
src/device/rp/*.go src/device/rp/*.go
src/device/rp/*.s src/device/rp/*.s
src/device/py32/*.go
src/device/py32/*.s
./vendor ./vendor
llvm-build llvm-build
llvm-project llvm-project
+3
View File
@@ -41,3 +41,6 @@
[submodule "lib/bdwgc"] [submodule "lib/bdwgc"]
path = lib/bdwgc path = lib/bdwgc
url = https://github.com/ivmai/bdwgc.git url = https://github.com/ivmai/bdwgc.git
[submodule "lib/py32-svd"]
path = lib/py32-svd
url = https://github.com/tinygo-org/py32-svd.git
+10
View File
@@ -0,0 +1,10 @@
## Communication style
Use ASD-STE-100 Simplified Technical English when you speak to the operator.
## Comments
Use ASD-STE100 Simplified Technical English for content outside of the code like comments, PR description, PR title, github comments, and issues.
Generally omit extra redundant comments. If a comment is absolutely needed, always keep it brief, no more than 2 lines. Avoid extra use of colons, semicolons, and dashes. When comments are required due to some specific requirements, include a reference such as to a datasheet or other definitive source to explain why this is the case, including the chapter/section number, page number, and/or URL.
## No coding tool attributions
Never add "created by XXX" or any other attributions from coding tools to any PRs, issues, comments, or anywhere else.
+9 -2
View File
@@ -30,7 +30,10 @@ on a different system like Mac.
## Using GNU Make ## Using GNU Make
The static build of TinyGo is driven by GNUmakefile, which provides a help target for quick reference: The static build of TinyGo is driven by GNUmakefile, which includes the topic
files in the `make/` directory (`config.mk`, `llvm.mk`, `gen-device.mk`,
`build.mk`, `test.mk`, `smoketest.mk`, `release.mk`, and `tools.mk`).
It provides a help target for quick reference:
% make help % make help
clean Remove build directory clean Remove build directory
@@ -50,6 +53,10 @@ the git repository). Then, inside the directory, download the LLVM source:
make llvm-source make llvm-source
The LLVM commit to use is pinned in `llvm-version.txt`. A change to that file
makes CI build LLVM again, because the file is part of the LLVM cache key. All
other changes reuse the cached LLVM build.
You can also store LLVM outside of the TinyGo root directory by setting the You can also store LLVM outside of the TinyGo root directory by setting the
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not `LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
covered by this guide. covered by this guide.
@@ -64,7 +71,7 @@ while producing binaries that are about as fast.
export CC=clang export CC=clang
export CXX=clang++ export CXX=clang++
The Makefile includes a default configuration that is good for most users. It `make/config.mk` holds a default configuration that is good for most users. It
builds a release version of LLVM (optimized, no asserts) and includes all builds a release version of LLVM (optimized, no asserts) and includes all
targets supported by TinyGo: targets supported by TinyGo:
+4 -2
View File
@@ -1,5 +1,5 @@
# tinygo-llvm stage obtains the llvm source for TinyGo # tinygo-llvm stage obtains the llvm source for TinyGo
FROM golang:1.26 AS tinygo-llvm FROM golang:1.27 AS tinygo-llvm
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y apt-utils make cmake clang-17 ninja-build && \ apt-get install -y apt-utils make cmake clang-17 ninja-build && \
@@ -10,6 +10,8 @@ RUN apt-get update && \
/tmp/* /tmp/*
COPY ./GNUmakefile /tinygo/GNUmakefile COPY ./GNUmakefile /tinygo/GNUmakefile
COPY ./make /tinygo/make
COPY ./llvm-version.txt /tinygo/llvm-version.txt
RUN cd /tinygo/ && \ RUN cd /tinygo/ && \
make llvm-source make llvm-source
@@ -33,7 +35,7 @@ RUN cd /tinygo/ && \
# tinygo-compiler copies the compiler build over to a base Go container (without # tinygo-compiler copies the compiler build over to a base Go container (without
# all the build tools etc). # all the build tools etc).
FROM golang:1.26 AS tinygo-compiler FROM golang:1.27 AS tinygo-compiler
# Copy tinygo build. # Copy tinygo build.
COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo
+10 -1250
View File
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -24,6 +24,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/gofrs/flock" "github.com/gofrs/flock"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
@@ -269,6 +270,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Create the *ssa.Program. This does not yet build the entire SSA of the // 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 so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
buildProgram := sync.OnceFunc(program.Build)
// Add jobs to compile each package. // Add jobs to compile each package.
// Packages that have a cache hit will not be compiled again. // Packages that have a cache hit will not be compiled again.
@@ -398,8 +400,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return nil return nil
} }
// Compile AST to IR. The compiler.CompilePackage function will // SSA package builds may run concurrently, but the resulting
// build the SSA as needed. // functions cannot be inspected until all builds have finished:
// generic instances and wrappers can be shared across packages.
// Build the whole program once before compiling any package.
buildProgram()
// Compile AST to IR.
mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA()) mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA())
defer mod.Context().Dispose() defer mod.Context().Dispose()
defer mod.Dispose() defer mod.Dispose()
+61 -3
View File
@@ -5,6 +5,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"testing" "testing"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
@@ -36,6 +37,7 @@ func TestClangAttributes(t *testing.T) {
"nintendoswitch", "nintendoswitch",
"riscv-qemu", "riscv-qemu",
"tkey", "tkey",
"uefi-amd64",
"wasip1", "wasip1",
"wasip2", "wasip2",
"wasm", "wasm",
@@ -128,8 +130,10 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
defer mod.Dispose() defer mod.Dispose()
// Check whether the LLVM target matches. // Check whether the LLVM target matches.
if mod.Target() != config.Triple() { // Use ClangTriple since LLVM 22 normalizes wasm32-unknown-wasi to wasip1.
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target()) 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 // Check the "target-cpu" and "target-features" string attribute of the add
@@ -153,11 +157,65 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
// The reason is that Debian has patched Clang in a way that // The reason is that Debian has patched Clang in a way that
// modifies the LLVM features string, changing lots of FPU/float // modifies the LLVM features string, changing lots of FPU/float
// related flags. We want to test vanilla Clang, not Debian Clang. // 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 // 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 // LLVM tools in a separate process. Not capturing these invocations would lead
// to recursive tests. // to recursive tests.
+29 -3
View File
@@ -204,7 +204,18 @@ var avrBuiltins = []string{
// Builtins needed specifically for windows/386. // Builtins needed specifically for windows/386.
var windowsI386Builtins = []string{ var windowsI386Builtins = []string{
"i386/chkstk.S", // also _alloca "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 // libCompilerRT is a library with symbols required by programs compiled with
@@ -233,13 +244,28 @@ var libCompilerRT = Library{
builtins = append(builtins, aeabiBuiltins...) builtins = append(builtins, aeabiBuiltins...)
case "avr": case "avr":
builtins = append(builtins, avrBuiltins...) 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...) builtins = append(builtins, genericBuiltins128...)
case "i386": case "i386":
if strings.Split(target, "-")[2] == "windows" { if isWindowsTriple(target) {
builtins = append(builtins, windowsI386Builtins...) builtins = append(builtins, windowsI386Builtins...)
} }
} }
return builtins, nil return builtins, nil
}, },
} }
func isWindowsTriple(target string) bool {
parts := strings.Split(target, "-")
return len(parts) > 2 && parts[2] == "windows"
}
+17 -17
View File
@@ -23,7 +23,7 @@
#include "clang/Basic/Diagnostic.h" #include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/DiagnosticOptions.h"
#include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Options.h" #include "clang/Options/Options.h"
#include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/Utils.h" #include "clang/Frontend/Utils.h"
@@ -35,6 +35,7 @@
#include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCCodeEmitter.h" #include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h" #include "llvm/MC/MCContext.h"
#include "llvm/MC/MCInstPrinter.h"
#include "llvm/MC/MCInstrInfo.h" #include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCObjectFileInfo.h" #include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCObjectWriter.h" #include "llvm/MC/MCObjectWriter.h"
@@ -67,8 +68,7 @@
#include <optional> #include <optional>
#include <system_error> #include <system_error>
using namespace clang; using namespace clang;
using namespace clang::driver; using namespace clang::options;
using namespace clang::driver::options;
using namespace llvm; using namespace llvm;
using namespace llvm::opt; using namespace llvm::opt;
@@ -109,7 +109,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
// Construct the invocation. // Construct the invocation.
// Target Options // 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)) if (Arg *A = Args.getLastArg(options::OPT_darwin_target_variant_triple))
Opts.DarwinTargetVariantTriple = llvm::Triple(A->getValue()); Opts.DarwinTargetVariantTriple = llvm::Triple(A->getValue());
if (Arg *A = Args.getLastArg(OPT_darwin_target_variant_sdk_version_EQ)) { 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); Opts.Features = Args.getAllArgValues(OPT_target_feature);
// Use the default target triple if unspecified. // Use the default target triple if unspecified.
if (Opts.Triple.empty()) if (Opts.Triple.getTriple().empty())
Opts.Triple = llvm::sys::getDefaultTargetTriple(); Opts.Triple = llvm::Triple(llvm::sys::getDefaultTargetTriple());
// Language Options // Language Options
Opts.IncludePaths = Args.getAllArgValues(OPT_I); Opts.IncludePaths = Args.getAllArgValues(OPT_I);
@@ -267,7 +267,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
std::string Error; std::string Error;
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error); const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
if (!TheTarget) 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 = ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true); MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
@@ -327,7 +327,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS)); TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
assert(STI && "Unable to create subtarget info!"); 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); &MCOptions);
bool PIC = false; bool PIC = false;
@@ -390,8 +390,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
// FIXME: There is a bit of code duplication with addPassesToEmitFile. // FIXME: There is a bit of code duplication with addPassesToEmitFile.
if (Opts.OutputType == AssemblerInvocation::FT_Asm) { if (Opts.OutputType == AssemblerInvocation::FT_Asm) {
MCInstPrinter *IP = TheTarget->createMCInstPrinter( std::unique_ptr<MCInstPrinter> IP(TheTarget->createMCInstPrinter(
llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI); Opts.Triple, Opts.OutputAsmVariant, *MAI, *MCII, *MRI));
std::unique_ptr<MCCodeEmitter> CE; std::unique_ptr<MCCodeEmitter> CE;
if (Opts.ShowEncoding) if (Opts.ShowEncoding)
@@ -400,7 +400,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions)); TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
auto FOut = std::make_unique<formatted_raw_ostream>(*Out); auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), IP, Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), std::move(IP),
std::move(CE), std::move(MAB))); std::move(CE), std::move(MAB)));
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) { } else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
Str.reset(createNullStreamer(Ctx)); Str.reset(createNullStreamer(Ctx));
@@ -422,7 +422,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS) DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
: MAB->createObjectWriter(*Out); : MAB->createObjectWriter(*Out);
Triple T(Opts.Triple); Triple T = Opts.Triple;
Str.reset(TheTarget->createMCObjectStreamer( Str.reset(TheTarget->createMCObjectStreamer(
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI)); T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI));
Str.get()->initSections(Opts.NoExecStack, *STI); Str.get()->initSections(Opts.NoExecStack, *STI);
@@ -453,7 +453,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
std::unique_ptr<MCTargetAsmParser> TAP( std::unique_ptr<MCTargetAsmParser> TAP(
TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions)); TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
if (!TAP) 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. // Set values for symbols, if any.
for (auto &S : Opts.SymbolDefs) { for (auto &S : Opts.SymbolDefs) {
@@ -506,12 +506,12 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
InitializeAllAsmParsers(); InitializeAllAsmParsers();
// Construct our diagnostic client. // Construct our diagnostic client.
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); DiagnosticOptions DiagOpts;
TextDiagnosticPrinter *DiagClient TextDiagnosticPrinter *DiagClient
= new TextDiagnosticPrinter(errs(), &*DiagOpts); = new TextDiagnosticPrinter(errs(), DiagOpts);
DiagClient->setPrefix("clang -cc1as"); DiagClient->setPrefix("clang -cc1as");
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 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 // Set an error handler, so that any LLVM backend diagnostics go through our
// error handler. // error handler.
@@ -528,7 +528,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
llvm::outs(), "clang -cc1as [options] file...", llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler", /*ShowHidden=*/false, "Clang Integrated Assembler", /*ShowHidden=*/false,
/*ShowAllAliases=*/false, /*ShowAllAliases=*/false,
llvm::opt::Visibility(driver::options::CC1AsOption)); llvm::opt::Visibility(clang::options::CC1AsOption));
return 0; return 0;
} }
+2 -2
View File
@@ -20,7 +20,7 @@ struct AssemblerInvocation {
/// @{ /// @{
/// The name of the target triple to assemble for. /// 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 /// If given, the name of the target CPU to determine which instructions
/// are legal. /// are legal.
@@ -142,7 +142,7 @@ struct AssemblerInvocation {
public: public:
AssemblerInvocation() { AssemblerInvocation() {
Triple = ""; Triple = llvm::Triple();
NoInitialTextSection = 0; NoInitialTextSection = 0;
InputFile = "-"; InputFile = "-";
OutputPath = "-"; OutputPath = "-";
+4 -4
View File
@@ -27,9 +27,9 @@ bool tinygo_clang_driver(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc); std::vector<const char*> args(argv, argv + argc);
// The compiler invocation needs a DiagnosticsEngine so it can report problems // The compiler invocation needs a DiagnosticsEngine so it can report problems
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions(); clang::DiagnosticOptions DiagOpts;
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts); clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), DiagOpts);
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false); clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), DiagOpts, &DiagnosticPrinter, false);
// Create the clang driver // Create the clang driver
clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags); clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
@@ -60,7 +60,7 @@ bool tinygo_clang_driver(int argc, char **argv) {
} }
// Create the actual diagnostics engine. // Create the actual diagnostics engine.
Clang->createDiagnostics(*llvm::vfs::getRealFileSystem()); Clang->createDiagnostics();
if (!Clang->hasDiagnostics()) { if (!Clang->hasDiagnostics()) {
return false; return false;
} }
+2 -2
View File
@@ -25,8 +25,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
} }
// Version range supported by TinyGo. // Version range supported by TinyGo.
const minorMin = 24 // when updating the min version, also update .github/workflows/compat.yml const minorMin = 25 // when updating the min version, also update .github/workflows/compat.yml
const minorMax = 26 const minorMax = 27
// Check that we support this Go toolchain version. // Check that we support this Go toolchain version.
gorootMajor, gorootMinor, err := goenv.GetGorootVersion() gorootMajor, gorootMinor, err := goenv.GetGorootVersion()
+135 -9
View File
@@ -65,15 +65,6 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
// Sort the segments by address. This is what esptool does too. // 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 }) 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 // Write first to an in-memory buffer, primarily so that we can easily
// calculate a hash over the entire image. // calculate a hash over the entire image.
// An added benefit is that we don't need to check for errors all the time. // An added benefit is that we don't need to check for errors all the time.
@@ -88,6 +79,83 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
chip = format[:len(format)-len("-img")] 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
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 { if makeImage {
// The bootloader starts at 0x1000, or 4096. // The bootloader starts at 0x1000, or 4096.
// TinyGo doesn't use a separate bootloader and runs the entire // TinyGo doesn't use a separate bootloader and runs the entire
@@ -191,6 +259,64 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
outf.Write(hash[:]) 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 makeImage {
targetImageOffset = int(dromFlashAddr)
}
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 makeImage {
targetImageOffset = int(iromFlashAddr)
}
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 // QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the
// image to be a certain size. // image to be a certain size.
if makeImage { if makeImage {
+1 -1
View File
@@ -133,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 // Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the archive // reproducible. Otherwise the temporary directory is stored in the archive
// itself, which varies each run. // 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) resourceDir := goenv.ClangResourceDir(false)
if resourceDir != "" { if resourceDir != "" {
args = append(args, "-resource-dir="+resourceDir) args = append(args, "-resource-dir="+resourceDir)
+121 -33
View File
@@ -1,8 +1,13 @@
package builder package builder
import ( import (
"flag"
"fmt"
"os"
"regexp" "regexp"
"runtime" "runtime"
"strconv"
"strings"
"testing" "testing"
"time" "time"
@@ -11,21 +16,19 @@ import (
var sema = make(chan struct{}, runtime.NumCPU()) var sema = make(chan struct{}, runtime.NumCPU())
var flagUpdate = flag.Bool("update", false, "update builder package tests")
type sizeTest struct { type sizeTest struct {
target string target string
path string path string
codeSize uint64
rodataSize uint64
dataSize uint64
bssSize uint64
} }
// Test whether code and data size is as expected for the given targets. // Test whether code and data size is as expected for the given targets.
// This tests both the logic of loadProgramSize and checks that code size // This tests both the logic of loadProgramSize and checks that code size
// doesn't change unintentionally. // doesn't change unintentionally.
// //
// If you find that code or data size is reduced, then great! You can reduce the // If you find that code or data size is reduced, then great! You can update the
// number in this test. // golden file by passing -update to the test.
// If you find that the code or data size is increased, take a look as to why // If you find that the code or data size is increased, take a look as to why
// this is. It could be due to an update (LLVM version, Go version, etc) which // this is. It could be due to an update (LLVM version, Go version, etc) which
// is fine, but it could also mean that a recent change introduced this size // is fine, but it could also mean that a recent change introduced this size
@@ -42,34 +45,110 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test. // This is a small number of very diverse targets that we want to test.
tests := []sizeTest{ tests := []sizeTest{
// microcontrollers // microcontrollers
{"hifive1b", "examples/echo", 3771, 309, 0, 2260}, {"hifive1b", "examples/echo"},
{"microbit", "examples/serial", 2832, 368, 8, 2256}, {"microbit", "examples/serial"},
{"wioterminal", "examples/pininterrupt", 8065, 1663, 132, 7488}, {"wioterminal", "examples/pininterrupt"},
// TODO: also check wasm. Right now this is difficult, because // TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the // wasm binaries are run through wasm-opt and therefore the
// output varies by binaryen version. // output varies by binaryen version.
} }
for _, tc := range tests { sizes := measureBinarySizes(t, tests)
t.Run(tc.target+"/"+tc.path, func(t *testing.T) { output := formatSizeTable(tests, sizes)
t.Parallel() checkGolden(t, "testdata/binary-size.txt", output)
}
// Build the binary. func checkGolden(t *testing.T, path, actual string) {
result := buildBinary(t, tc.target, tc.path) t.Helper()
if *flagUpdate {
// Check whether the size of the binary matches the expected size. if err := os.WriteFile(path, []byte(actual), 0o666); err != nil {
sizes, err := loadProgramSize(result.Executable, nil) t.Fatal("failed to write updated golden file:", err)
if err != nil { }
t.Fatal("could not read program size:", err) return
}
if sizes.Code != tc.codeSize || sizes.ROData != tc.rodataSize || sizes.Data != tc.dataSize || sizes.BSS != tc.bssSize {
t.Errorf("Unexpected code size when compiling: -target=%s %s", tc.target, tc.path)
t.Errorf(" code rodata data bss")
t.Errorf("expected: %6d %6d %6d %6d", tc.codeSize, tc.rodataSize, tc.dataSize, tc.bssSize)
t.Errorf("actual: %6d %6d %6d %6d", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS)
}
})
} }
expected, err := os.ReadFile(path)
if err != nil {
t.Fatal("failed to read golden file:", err)
}
if actual != string(expected) {
t.Errorf("%s does not match expected output (re-run with -update to regenerate):\nexpected:\n%sactual:\n%s", path, expected, actual)
}
}
func measureBinarySizes(t *testing.T, tests []sizeTest) []*programSize {
t.Helper()
type result struct {
index int
size *programSize
err error
}
results := make(chan result, len(tests))
for i, tc := range tests {
tmpdir := t.TempDir()
go func() {
size, err := measureBinarySize(tc, tmpdir)
results <- result{i, size, err}
}()
}
sizes := make([]*programSize, len(tests))
failed := false
for range tests {
result := <-results
if result.err != nil {
tc := tests[result.index]
t.Errorf("%s/%s: %v", tc.target, tc.path, result.err)
failed = true
}
sizes[result.index] = result.size
}
if failed {
t.FailNow()
}
return sizes
}
func measureBinarySize(tc sizeTest, tmpdir string) (*programSize, error) {
result, err := buildBinaryInDir(tc.target, tc.path, tmpdir)
if err != nil {
return nil, err
}
size, err := loadProgramSize(result.Executable, nil)
if err != nil {
return nil, fmt.Errorf("could not read program size: %w", err)
}
return size, nil
}
func formatSizeTable(tests []sizeTest, sizes []*programSize) string {
targetWidth := len("target")
packageWidth := len("package")
codeWidth := len("code")
rodataWidth := len("rodata")
dataWidth := len("data")
bssWidth := len("bss")
for i, tc := range tests {
targetWidth = max(targetWidth, len(tc.target))
packageWidth = max(packageWidth, len(tc.path))
codeWidth = max(codeWidth, len(strconv.FormatUint(sizes[i].Code, 10)))
rodataWidth = max(rodataWidth, len(strconv.FormatUint(sizes[i].ROData, 10)))
dataWidth = max(dataWidth, len(strconv.FormatUint(sizes[i].Data, 10)))
bssWidth = max(bssWidth, len(strconv.FormatUint(sizes[i].BSS, 10)))
}
var output strings.Builder
fmt.Fprintf(&output, "%-*s %-*s %*s %*s %*s %*s\n",
targetWidth, "target", packageWidth, "package",
codeWidth, "code", rodataWidth, "rodata", dataWidth, "data", bssWidth, "bss")
for i, tc := range tests {
size := sizes[i]
fmt.Fprintf(&output, "%-*s %-*s %*d %*d %*d %*d\n",
targetWidth, tc.target, packageWidth, tc.path,
codeWidth, size.Code, rodataWidth, size.ROData,
dataWidth, size.Data, bssWidth, size.BSS)
}
return output.String()
} }
// Check that the -size=full flag attributes binary size to the correct package // Check that the -size=full flag attributes binary size to the correct package
@@ -114,6 +193,15 @@ func TestSizeFull(t *testing.T) {
} }
func buildBinary(t *testing.T, targetString, pkgName string) BuildResult { func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
t.Helper()
result, err := buildBinaryInDir(targetString, pkgName, t.TempDir())
if err != nil {
t.Fatal(err)
}
return result
}
func buildBinaryInDir(targetString, pkgName, tmpdir string) (BuildResult, error) {
options := compileopts.Options{ options := compileopts.Options{
Target: targetString, Target: targetString,
Opt: "z", Opt: "z",
@@ -124,15 +212,15 @@ func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
} }
target, err := compileopts.LoadTarget(&options) target, err := compileopts.LoadTarget(&options)
if err != nil { if err != nil {
t.Fatal("could not load target:", err) return BuildResult{}, fmt.Errorf("could not load target: %w", err)
} }
config := &compileopts.Config{ config := &compileopts.Config{
Options: &options, Options: &options,
Target: target, Target: target,
} }
result, err := Build(pkgName, "", t.TempDir(), config) result, err := Build(pkgName, "", tmpdir, config)
if err != nil { if err != nil {
t.Fatal("could not build:", err) return BuildResult{}, fmt.Errorf("could not build: %w", err)
} }
return result return result, nil
} }
+4
View File
@@ -0,0 +1,4 @@
target package code rodata data bss
hifive1b examples/echo 4321 323 0 2268
microbit examples/serial 2842 382 8 2264
wioterminal examples/pininterrupt 8039 1665 132 7496
+10 -4
View File
@@ -330,10 +330,8 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
NamePos: pos, NamePos: pos,
Name: typeName, Name: typeName,
}, },
Type: f.makeASTType(underlyingType, pos), Assign: pos,
} Type: f.makeASTType(underlyingType, pos),
if underlyingType.kind != C.CXType_Enum {
typeSpec.Assign = pos
} }
return typeSpec, nil return typeSpec, nil
case C.CXCursor_VarDecl: case C.CXCursor_VarDecl:
@@ -806,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)) f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
typeName = "<unknown>" 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: case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ) cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor)) name := getString(C.tinygo_clang_getCursorSpelling(cursor))
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 //go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm21 && !llvm22
package cgo package cgo
+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"
+8 -6
View File
@@ -61,13 +61,15 @@ func (c *Config) BuildMode() string {
// RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list // RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
// will be returned. // will be returned.
func (c *Config) Features() string { func (c *Config) Features() string {
var features string
if c.Target.Features == "" { 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 patchFeatures(features)
return c.Target.Features
}
return c.Target.Features + "," + c.Options.LLVMFeatures
} }
// ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length // ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length
@@ -347,7 +349,7 @@ func (c *Config) CFlags(libclang bool) []string {
// Use the same optimization level as TinyGo. // Use the same optimization level as TinyGo.
cflags = append(cflags, "-O"+c.Options.Opt) cflags = append(cflags, "-O"+c.Options.Opt)
// Set the LLVM target triple. // Set the LLVM target triple.
cflags = append(cflags, "--target="+c.Triple()) cflags = append(cflags, "--target="+ClangTriple(c.Triple()))
// Set the -mcpu (or similar) flag. // Set the -mcpu (or similar) flag.
if c.Target.CPU != "" { if c.Target.CPU != "" {
if c.GOARCH() == "amd64" || c.GOARCH() == "386" { if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
+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
}
+65
View File
@@ -0,0 +1,65 @@
package compileopts
import (
"go/build/constraint"
"os"
"path/filepath"
"strings"
"testing"
)
// TestFinalizerRunnerSchedulerCoverage verifies that each scheduler selects one runner file.
// It uses validSchedulerOptions so new schedulers are included.
func TestFinalizerRunnerSchedulerCoverage(t *testing.T) {
files := []string{
"gc_finalizer_sched.go",
"gc_finalizer_sched_none.go",
"gc_finalizer_sched_other.go",
}
exprs := make([]constraint.Expr, len(files))
for i, name := range files {
exprs[i] = readBuildConstraint(t, filepath.Join("..", "src", "runtime", name))
}
for _, sched := range validSchedulerOptions {
// The finalizer table exists under block GCs.
// gc.conservative satisfies the GC condition in every constraint.
tags := map[string]bool{
"gc.conservative": true,
"scheduler." + sched: true,
}
var matched []string
for i, expr := range exprs {
if expr.Eval(func(tag string) bool { return tags[tag] }) {
matched = append(matched, files[i])
}
}
if len(matched) != 1 {
t.Errorf("scheduler.%s: spawnFinalizerRunner defined in %d files %v, want exactly 1",
sched, len(matched), matched)
}
}
}
func readBuildConstraint(t *testing.T, path string) constraint.Expr {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if constraint.IsGoBuild(line) {
expr, err := constraint.Parse(line)
if err != nil {
t.Fatalf("%s: %v", path, err)
}
return expr
}
if line != "" && !strings.HasPrefix(line, "//") {
break // reached code before any //go:build line
}
}
t.Fatalf("%s: no //go:build line found", path)
return nil
}
+2
View File
@@ -475,10 +475,12 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
"-m", "i386pep", "-m", "i386pep",
"--image-base", "0x400000", "--image-base", "0x400000",
) )
spec.RTLib = "compiler-rt"
case "arm64": case "arm64":
spec.LDFlags = append(spec.LDFlags, spec.LDFlags = append(spec.LDFlags,
"-m", "arm64pe", "-m", "arm64pe",
) )
spec.RTLib = "compiler-rt"
} }
spec.LDFlags = append(spec.LDFlags, spec.LDFlags = append(spec.LDFlags,
"-Bdynamic", "-Bdynamic",
+34
View File
@@ -1,8 +1,10 @@
package compileopts package compileopts
import ( import (
"encoding/csv"
"errors" "errors"
"io/fs" "io/fs"
"os"
"reflect" "reflect"
"testing" "testing"
) )
@@ -54,6 +56,38 @@ func TestLoadTarget_InheritableOnlyTargetStillLoadable(t *testing.T) {
} }
} }
func TestLoadPY32ConcreteTargets(t *testing.T) {
file, err := os.Open("../tools/gen-py32-targets/devices.csv")
if err != nil {
t.Fatal(err)
}
defer file.Close()
records, err := csv.NewReader(file).ReadAll()
if err != nil {
t.Fatal(err)
}
for _, record := range records[1:] {
part, core := record[0], record[2]
t.Run(part, func(t *testing.T) {
spec, err := LoadTarget(&Options{Target: part})
if err != nil {
t.Fatal(err)
}
if spec.LinkerScript == "" {
t.Error("concrete target has no linker script")
}
wantCPU := "cortex-m0plus"
if core == "m4" {
wantCPU = "cortex-m4"
}
if spec.CPU != wantCPU {
t.Errorf("CPU is %q, want %q", spec.CPU, wantCPU)
}
})
}
}
func TestOverrideProperties(t *testing.T) { func TestOverrideProperties(t *testing.T) {
baseAutoStackSize := true baseAutoStackSize := true
base := &TargetSpec{ base := &TargetSpec{
+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
}
+47
View File
@@ -35,6 +35,9 @@ const (
// Whether this is a readonly parameter (for example, a string pointer). // Whether this is a readonly parameter (for example, a string pointer).
paramIsReadonly paramIsReadonly
// Whether this parameter is passed through backing storage.
paramIsIndirect
) )
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
@@ -102,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 // Expand an argument type to a list that can be used in a function call
// parameter list. // parameter list.
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo { 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() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
fieldInfos := c.flattenAggregateType(t, name, goType) fieldInfos := c.flattenAggregateType(t, name, goType)
@@ -115,6 +130,38 @@ func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType
return []paramInfo{c.getParamInfo(t, name, 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 // 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 // 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 // is useful for debug information, where it is necessary to know the offset
+22 -38
View File
@@ -30,17 +30,15 @@ func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
// actual channel send operation during goroutine lowering. // actual channel send operation during goroutine lowering.
func (b *builder) createChanSend(instr *ssa.Send) { func (b *builder) createChanSend(instr *ssa.Send) {
ch := b.getValue(instr.Chan, getPos(instr)) ch := b.getValue(instr.Chan, getPos(instr))
chanValue := b.getValue(instr.X, getPos(instr))
// store value-to-send // store value-to-send
valueType := b.getLLVMType(instr.X.Type()) valueType := b.getLLVMType(instr.X.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaSize llvm.Value var storage valueStorage
if isZeroSize { if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType) storage.ptr = llvm.ConstNull(b.dataPtrType)
} else { } else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") storage = b.getValueStorage(instr.X, "chan.value")
b.CreateStore(chanValue, valueAlloca)
} }
// Allocate buffer for the channel operation. // Allocate buffer for the channel operation.
@@ -48,15 +46,13 @@ func (b *builder) createChanSend(instr *ssa.Send) {
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the send. // Do the send.
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") b.createRuntimeInvoke("chanSend", []llvm.Value{ch, storage.ptr, channelOpAlloca}, "")
// End the lifetime of the allocas. // End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8: // This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742 // https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
if !isZeroSize { b.endValueStorage(storage)
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
}
} }
// createChanRecv emits a pseudo chan receive operation. It is lowered to the // createChanRecv emits a pseudo chan receive operation. It is lowered to the
@@ -66,37 +62,17 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
ch := b.getValue(unop.X, getPos(unop)) ch := b.getValue(unop.X, getPos(unop))
// Allocate memory to receive into. // Allocate memory to receive into.
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 result := b.createRuntimeValueResult(valueType, unop.CommaOk, true, "chan")
var valueAlloca, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType)
} else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
// Allocate buffer for the channel operation. // Allocate buffer for the channel operation.
channelOp := b.getLLVMRuntimeType("channelOp") channelOp := b.getLLVMRuntimeType("channelOp")
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the receive. // Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, result.valuePtr, channelOpAlloca}, "")
var received llvm.Value received := result.finish(b, commaOk, "chan.received")
if isZeroSize {
received = llvm.ConstNull(valueType)
} else {
received = b.CreateLoad(valueType, valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
}
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
return received
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
}
} }
// createChanClose closes the given channel. // createChanClose closes the given channel.
@@ -170,9 +146,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
case types.SendOnly: case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca // Store this value in an alloca and put a pointer to this alloca
// in the send state. // in the send state.
sendValue := b.getValue(state.Send, state.Pos) alloca := b.getSelectSendStorage(state.Send)
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca)
selectState = b.CreateInsertValue(selectState, alloca, 1, "") selectState = b.CreateInsertValue(selectState, alloca, 1, "")
default: default:
panic("unreachable") panic("unreachable")
@@ -280,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 // receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it. // it to the correct type, and dereference it.
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)] recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := b.getLLVMType(expr.Type()) return b.loadFromStorage(recvbuf, expr.Type(), "select.received")
return b.CreateLoad(typ, recvbuf, "")
} }
} }
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
}
+322 -58
View File
@@ -155,6 +155,8 @@ type builder struct {
llvmFn llvm.Value llvmFn llvm.Value
info functionInfo info functionInfo
locals map[ssa.Value]llvm.Value // local variables locals map[ssa.Value]llvm.Value // local variables
indirectValues map[ssa.Value]llvm.Value
indirectReturn llvm.Value
blockInfo []blockInfo blockInfo []blockInfo
currentBlock *ssa.BasicBlock currentBlock *ssa.BasicBlock
currentBlockInfo *blockInfo currentBlockInfo *blockInfo
@@ -193,6 +195,7 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu
llvmFn: fn, llvmFn: fn,
info: c.getFunctionInfo(f), info: c.getFunctionInfo(f),
locals: make(map[ssa.Value]llvm.Value), locals: make(map[ssa.Value]llvm.Value),
indirectValues: make(map[ssa.Value]llvm.Value),
dilocals: make(map[*types.Var]llvm.Metadata), dilocals: make(map[*types.Var]llvm.Metadata),
} }
} }
@@ -293,6 +296,9 @@ func Sizes(machine llvm.TargetMachine) types.Sizes {
} }
// CompilePackage compiles a single package to a LLVM module. // CompilePackage compiles a single package to a LLVM module.
//
// The SSA package must already be built. When packages are compiled
// concurrently, the entire SSA program must be built before compilation starts.
func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) { func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) {
c := newCompilerContext(moduleName, machine, config, dumpSSA) c := newCompilerContext(moduleName, machine, config, dumpSSA)
defer c.dispose() defer c.dispose()
@@ -303,9 +309,6 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
c.program = ssaPkg.Prog c.program = ssaPkg.Prog
// Convert AST to SSA.
ssaPkg.Build()
// Assign names to function-local named types before compiling the // Assign names to function-local named types before compiling the
// package, so that types declared in different functions (or in // package, so that types declared in different functions (or in
// different instantiations of a generic function) do not collide. // different instantiations of a generic function) do not collide.
@@ -422,19 +425,19 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
return c.ctx.Int8Type() return c.ctx.Int8Type()
case types.Int16, types.Uint16: case types.Int16, types.Uint16:
return c.ctx.Int16Type() return c.ctx.Int16Type()
case types.Int32, types.Uint32: case types.Int32, types.Uint32, types.UntypedRune:
return c.ctx.Int32Type() return c.ctx.Int32Type()
case types.Int, types.Uint: case types.Int, types.Uint, types.UntypedInt:
return c.intType return c.intType
case types.Int64, types.Uint64: case types.Int64, types.Uint64:
return c.ctx.Int64Type() return c.ctx.Int64Type()
case types.Float32: case types.Float32:
return c.ctx.FloatType() return c.ctx.FloatType()
case types.Float64: case types.Float64, types.UntypedFloat:
return c.ctx.DoubleType() return c.ctx.DoubleType()
case types.Complex64: case types.Complex64:
return c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false) return c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false)
case types.Complex128: case types.Complex128, types.UntypedComplex:
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false) return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
case types.String, types.UntypedString: case types.String, types.UntypedString:
return c.getLLVMRuntimeType("_string") return c.getLLVMRuntimeType("_string")
@@ -1282,10 +1285,28 @@ func (b *builder) createFunctionStart(intrinsic bool) {
// Load function parameters // Load function parameters
llvmParamIndex := 0 llvmParamIndex := 0
if _, indirectResult := b.hasIndirectResult(b.fn.Signature); indirectResult && !b.info.exported {
b.indirectReturn = b.llvmFn.Param(llvmParamIndex)
b.indirectReturn.SetName("return")
llvmParamIndex++
}
for _, param := range b.fn.Params { for _, param := range b.fn.Params {
llvmType := b.getLLVMType(param.Type()) llvmType := b.getLLVMType(param.Type())
if b.isIndirectParam(llvmType, b.info.exported) {
llvmParam := b.llvmFn.Param(llvmParamIndex)
llvmParam.SetName(param.Name())
b.indirectValues[param] = llvmParam
llvmParamIndex++
continue
}
var paramInfos []paramInfo
if b.info.exported {
paramInfos = b.expandDirectFormalParamType(llvmType, param.Name(), param.Type())
} else {
paramInfos = b.expandFormalParamType(llvmType, param.Name(), param.Type())
}
fields := make([]llvm.Value, 0, 1) fields := make([]llvm.Value, 0, 1)
for _, info := range b.expandFormalParamType(llvmType, param.Name(), param.Type()) { for _, info := range paramInfos {
param := b.llvmFn.Param(llvmParamIndex) param := b.llvmFn.Param(llvmParamIndex)
param.SetName(info.name) param.SetName(info.name)
fields = append(fields, param) fields = append(fields, param)
@@ -1423,7 +1444,7 @@ func (b *builder) createFunction() {
for _, phi := range b.phis { for _, phi := range b.phis {
block := phi.ssa.Block() block := phi.ssa.Block()
for i, edge := range phi.ssa.Edges { for i, edge := range phi.ssa.Edges {
llvmVal := b.getValue(edge, getPos(phi.ssa)) llvmVal := b.getCallArgument(edge, false)
llvmBlock := b.blockInfo[block.Preds[i].Index].exit llvmBlock := b.blockInfo[block.Preds[i].Index].exit
phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock}) phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock})
} }
@@ -1432,6 +1453,9 @@ func (b *builder) createFunction() {
if b.NeedsStackObjects { if b.NeedsStackObjects {
// Track phi nodes. // Track phi nodes.
for _, phi := range b.phis { for _, phi := range b.phis {
if b.isOversizedAggregate(phi.ssa.Type()) {
continue
}
insertPoint := llvm.NextInstruction(phi.llvm) insertPoint := llvm.NextInstruction(phi.llvm)
for !insertPoint.IsAPHINode().IsNil() { for !insertPoint.IsAPHINode().IsNil() {
insertPoint = llvm.NextInstruction(insertPoint) insertPoint = llvm.NextInstruction(insertPoint)
@@ -1523,10 +1547,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
b.diagnostics = append(b.diagnostics, err) b.diagnostics = append(b.diagnostics, err)
b.locals[instr] = llvm.Undef(b.getLLVMType(instr.Type())) b.locals[instr] = llvm.Undef(b.getLLVMType(instr.Type()))
} else { } else {
b.locals[instr] = value b.setValue(instr, value)
if len(*instr.Referrers()) != 0 && b.NeedsStackObjects {
b.trackExpr(instr, value)
}
} }
case *ssa.DebugRef: case *ssa.DebugRef:
// ignore // ignore
@@ -1546,10 +1567,8 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
b.CreateBr(blockJump) b.CreateBr(blockJump)
case *ssa.MapUpdate: case *ssa.MapUpdate:
m := b.getValue(instr.Map, getPos(instr)) m := b.getValue(instr.Map, getPos(instr))
key := b.getValue(instr.Key, getPos(instr))
value := b.getValue(instr.Value, getPos(instr))
mapType := instr.Map.Type().Underlying().(*types.Map) mapType := instr.Map.Type().Underlying().(*types.Map)
b.createMapUpdate(mapType.Key(), m, key, value, instr.Pos()) b.createMapUpdate(mapType.Key(), m, instr.Key, instr.Value, instr.Pos())
case *ssa.Panic: case *ssa.Panic:
value := b.getValue(instr.X, getPos(instr)) value := b.getValue(instr.X, getPos(instr))
b.createRuntimeInvoke("_panic", []llvm.Value{value}, "") b.createRuntimeInvoke("_panic", []llvm.Value{value}, "")
@@ -1558,19 +1577,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
if b.hasDeferFrame() { if b.hasDeferFrame() {
b.createRuntimeCall("destroyDeferFrame", []llvm.Value{b.deferFrame}, "") b.createRuntimeCall("destroyDeferFrame", []llvm.Value{b.deferFrame}, "")
} }
if len(instr.Results) == 0 { b.createReturn(instr.Results, getPos(instr))
b.CreateRetVoid()
} else if len(instr.Results) == 1 {
b.CreateRet(b.getValue(instr.Results[0], getPos(instr)))
} else {
// Multiple return values. Put them all in a struct.
retVal := llvm.ConstNull(b.llvmFn.GlobalValueType().ReturnType())
for i, result := range instr.Results {
val := b.getValue(result, getPos(instr))
retVal = b.CreateInsertValue(retVal, val, i, "")
}
b.CreateRet(retVal)
}
case *ssa.RunDefers: case *ssa.RunDefers:
// Note where we're going to put the rundefers block // Note where we're going to put the rundefers block
run := b.insertBasicBlock("rundefers.block") run := b.insertBasicBlock("rundefers.block")
@@ -1584,18 +1591,246 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
b.createChanSend(instr) b.createChanSend(instr)
case *ssa.Store: case *ssa.Store:
llvmAddr := b.getValue(instr.Addr, getPos(instr)) llvmAddr := b.getValue(instr.Addr, getPos(instr))
llvmVal := b.getValue(instr.Val, getPos(instr))
b.createNilCheck(instr.Addr, llvmAddr, "store") b.createNilCheck(instr.Addr, llvmAddr, "store")
if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 { llvmType := b.getLLVMType(instr.Val.Type())
if b.targetData.TypeAllocSize(llvmType) == 0 {
// nothing to store // nothing to store
return return
} }
b.CreateStore(llvmVal, llvmAddr) b.storeValue(llvmAddr, instr.Val)
default: default:
b.addError(instr.Pos(), "unknown instruction: "+instr.String()) b.addError(instr.Pos(), "unknown instruction: "+instr.String())
} }
} }
func (b *builder) setValue(value ssa.Value, llvmValue llvm.Value) {
if b.isAggregateValue(value.Type()) && !llvmValue.IsNil() && llvmValue.Type().TypeKind() == llvm.PointerTypeKind {
b.indirectValues[value] = llvmValue
return
}
b.locals[value] = llvmValue
if len(*value.Referrers()) != 0 && b.NeedsStackObjects {
b.trackExpr(value, llvmValue)
}
}
func (b *builder) createReturn(results []ssa.Value, pos token.Pos) {
if len(results) == 0 {
b.CreateRetVoid()
} else if !b.indirectReturn.IsNil() {
if len(results) == 1 {
b.storeValue(b.indirectReturn, results[0])
} else {
returnType := b.getLLVMResultType(b.fn.Signature)
for i, result := range results {
fieldPtr := b.CreateStructGEP(returnType, b.indirectReturn, i, "")
b.storeValue(fieldPtr, result)
}
}
b.CreateRetVoid()
} else if len(results) == 1 {
b.CreateRet(b.getValue(results[0], pos))
} else {
result := llvm.ConstNull(b.llvmFn.GlobalValueType().ReturnType())
for i, value := range results {
result = b.CreateInsertValue(result, b.getValue(value, pos), i, "")
}
b.CreateRet(result)
}
}
func (b *builder) isOversizedAggregate(typ types.Type) bool {
if !b.isAggregateValue(typ) {
return false
}
return b.isIndirectAggregate(b.getLLVMType(typ))
}
func (b *builder) isAggregateValue(typ types.Type) bool {
if tuple, ok := typ.(*types.Tuple); ok {
for i := 0; i < tuple.Len(); i++ {
if !isLLVMValueType(tuple.At(i).Type()) {
return false
}
}
} else {
switch typ.Underlying().(type) {
case *types.Array, *types.Struct:
default:
return false
}
if !isLLVMValueType(typ) {
return false
}
}
return true
}
func (b *builder) getValuePointer(value ssa.Value) llvm.Value {
if ptr, ok := b.indirectValues[value]; ok {
return ptr
}
llvmType := b.getLLVMType(value.Type())
ptr := b.createIndirectStorage(llvmType, value.Name())
b.storeValue(ptr, value)
return ptr
}
func (b *builder) getCallArgument(value ssa.Value, exported bool) llvm.Value {
paramType := b.getLLVMType(value.Type())
if b.isIndirectParam(paramType, exported) {
return b.getValuePointer(value)
}
return b.getValue(value, getPos(value))
}
func (b *builder) createIndirectStorage(typ llvm.Type, name string) llvm.Value {
// Use runtime.alloc here so storage that escapes remains valid. The
// allocation optimizer moves bounded non-escaping storage to the stack.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false)
layout := b.createObjectLayout(typ, b.fn.Pos())
ptr := b.createAlloc(size, layout, b.targetData.ABITypeAlignment(typ), name)
if b.NeedsStackObjects {
b.trackPointer(ptr)
}
return ptr
}
func (b *builder) copyIndirectAggregate(dst, src llvm.Value, typ llvm.Type) {
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false)
b.createMemCopy("memcpy", dst, src, size)
}
func (b *builder) storeValue(dst llvm.Value, value ssa.Value) {
typ := b.getLLVMType(value.Type())
if src, ok := b.indirectValues[value]; ok {
b.copyIndirectAggregate(dst, src, typ)
} else {
b.CreateStore(b.getValue(value, getPos(value)), dst)
}
}
func (b *builder) copyToIndirectStorage(src llvm.Value, typ llvm.Type, name string) llvm.Value {
dst := b.createIndirectStorage(typ, name)
b.copyIndirectAggregate(dst, src, typ)
return dst
}
func (b *builder) loadFromStorage(ptr llvm.Value, typ types.Type, name string) llvm.Value {
llvmType := b.getLLVMType(typ)
if b.isIndirectAggregate(llvmType) {
return b.copyToIndirectStorage(ptr, llvmType, name)
}
return b.CreateLoad(llvmType, ptr, name)
}
func (b *builder) getValueField(value ssa.Value, index int, resultType types.Type, name string) (llvm.Value, bool) {
if !b.isAggregateValue(value.Type()) {
return llvm.Value{}, false
}
valueType := b.getLLVMType(value.Type())
if _, indirect := b.indirectValues[value]; !indirect && !b.isIndirectAggregate(valueType) {
return llvm.Value{}, false
}
fieldPtr := b.CreateStructGEP(valueType, b.getValuePointer(value), index, "")
return b.loadFromStorage(fieldPtr, resultType, name), true
}
func (b *builder) zeroIndirectStorage(ptr llvm.Value, typ llvm.Type) {
memset := b.getMemsetFunc()
b.createCall(memset.GlobalValueType(), memset, []llvm.Value{
ptr,
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false),
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
}, "")
}
type valueStorage struct {
ptr, size llvm.Value
temporary bool
}
func (b *builder) getValueStorage(value ssa.Value, name string) valueStorage {
typ := b.getLLVMType(value.Type())
if b.isIndirectAggregate(typ) {
return valueStorage{ptr: b.getValuePointer(value)}
}
ptr, size := b.createTemporaryAlloca(typ, name)
b.storeValue(ptr, value)
return valueStorage{ptr: ptr, size: size, temporary: true}
}
func (b *builder) endValueStorage(storage valueStorage) {
if storage.temporary {
b.emitLifetimeEnd(storage.ptr, storage.size)
}
}
type runtimeValueResult struct {
valueType llvm.Type
resultType llvm.Type
result llvm.Value
valuePtr llvm.Value
valueSize llvm.Value
temporary bool
zero bool
commaOk bool
}
func (b *builder) createRuntimeValueResult(valueType llvm.Type, commaOk, zeroAsNull bool, name string) runtimeValueResult {
result := runtimeValueResult{
valueType: valueType,
resultType: valueType,
valueSize: llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(valueType), false),
commaOk: commaOk,
}
if commaOk {
result.resultType = b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)
}
if b.isIndirectAggregate(result.resultType) {
result.result = b.createIndirectStorage(result.resultType, name+".result")
result.valuePtr = result.result
if commaOk {
result.valuePtr = b.CreateStructGEP(result.resultType, result.result, 0, "")
}
return result
}
if zeroAsNull && b.targetData.TypeAllocSize(valueType) == 0 {
result.valuePtr = llvm.ConstNull(b.dataPtrType)
result.zero = true
return result
}
result.valuePtr, result.valueSize = b.createTemporaryAlloca(valueType, name+".value")
result.temporary = true
return result
}
func (r runtimeValueResult) finish(b *builder, commaOk llvm.Value, name string) llvm.Value {
if !r.result.IsNil() {
if r.commaOk {
b.CreateStore(commaOk, b.CreateStructGEP(r.resultType, r.result, 1, ""))
}
return r.result
}
var value llvm.Value
if r.zero {
value = llvm.ConstNull(r.valueType)
} else {
value = b.CreateLoad(r.valueType, r.valuePtr, name)
}
if r.temporary {
b.emitLifetimeEnd(r.valuePtr, r.valueSize)
}
if !r.commaOk {
return value
}
result := llvm.Undef(r.resultType)
result = b.CreateInsertValue(result, value, 0, "")
return b.CreateInsertValue(result, commaOk, 1, "")
}
// createBuiltin lowers a builtin Go function (append, close, delete, etc.) to // createBuiltin lowers a builtin Go function (append, close, delete, etc.) to
// LLVM IR. It uses runtime calls for some builtins. // LLVM IR. It uses runtime calls for some builtins.
func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, callName string, pos token.Pos) (llvm.Value, error) { func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, callName string, pos token.Pos) (llvm.Value, error) {
@@ -1981,7 +2216,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
if fn := instr.StaticCallee(); fn != nil { if fn := instr.StaticCallee(); fn != nil {
// Direct function call, either to a named or anonymous (directly // Direct function call, either to a named or anonymous (directly
// applied) function call. If it is anonymous, it may be a closure. // applied) function call. If it is anonymous, it may be a closure.
name := fn.RelString(nil) name := b.getFunctionInfo(fn).linkName
switch { switch {
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
return b.createInlineAsm(instr.Args) return b.createInlineAsm(instr.Args)
@@ -2030,14 +2265,10 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
} }
} }
var params []llvm.Value
for _, param := range instr.Args {
params = append(params, b.getValue(param, getPos(instr)))
}
// Try to call the function directly for trivially static calls. // Try to call the function directly for trivially static calls.
var callee, context llvm.Value var callee, context llvm.Value
var calleeType llvm.Type var calleeType llvm.Type
var invokeTypecode, invokeReceiver llvm.Value
exported := false exported := false
if fn := instr.StaticCallee(); fn != nil { if fn := instr.StaticCallee(); fn != nil {
calleeType, callee = b.getFunction(fn) calleeType, callee = b.getFunction(fn)
@@ -2067,19 +2298,18 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
exported = info.exported exported = info.exported
} else if call, ok := instr.Value.(*ssa.Builtin); ok { } else if call, ok := instr.Value.(*ssa.Builtin); ok {
// Builtin function (append, close, delete, etc.).) // Builtin function (append, close, delete, etc.).)
var params []llvm.Value
var argTypes []types.Type var argTypes []types.Type
for _, arg := range instr.Args { for _, arg := range instr.Args {
argTypes = append(argTypes, arg.Type()) argTypes = append(argTypes, arg.Type())
params = append(params, b.getValue(arg, getPos(instr)))
} }
return b.createBuiltin(argTypes, params, call.Name(), instr.Pos()) return b.createBuiltin(argTypes, params, call.Name(), instr.Pos())
} else if instr.IsInvoke() { } else if instr.IsInvoke() {
// Interface method call (aka invoke call). // Interface method call (aka invoke call).
itf := b.getValue(instr.Value, getPos(instr)) // interface value (runtime._interface) itf := b.getValue(instr.Value, getPos(instr)) // interface value (runtime._interface)
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") invokeTypecode = b.CreateExtractValue(itf, 0, "invoke.func.typecode")
value := b.CreateExtractValue(itf, 1, "invoke.func.value") // receiver invokeReceiver = b.CreateExtractValue(itf, 1, "invoke.func.value")
// Prefix the params with receiver value and suffix with typecode.
params = append([]llvm.Value{value}, params...)
params = append(params, typecode)
callee = b.getInvokeFunction(instr) callee = b.getInvokeFunction(instr)
calleeType = callee.GlobalValueType() calleeType = callee.GlobalValueType()
context = llvm.Undef(b.dataPtrType) context = llvm.Undef(b.dataPtrType)
@@ -2093,7 +2323,23 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
b.createNilCheck(instr.Value, callee, "fpcall") b.createNilCheck(instr.Value, callee, "fpcall")
} }
var params []llvm.Value
for _, param := range instr.Args {
params = append(params, b.getCallArgument(param, exported))
}
if instr.IsInvoke() {
params = append([]llvm.Value{invokeReceiver}, params...)
params = append(params, invokeTypecode)
}
if !exported { if !exported {
if resultType, indirectResult := b.hasIndirectResult(instr.Signature()); indirectResult {
result := b.createIndirectStorage(resultType, "call.result")
params = append([]llvm.Value{result}, params...)
params = append(params, context)
b.createInvoke(calleeType, callee, params, "")
return result, nil
}
// This function takes a context parameter. // This function takes a context parameter.
// Add it to the end of the parameter list. // Add it to the end of the parameter list.
params = append(params, context) params = append(params, context)
@@ -2132,6 +2378,9 @@ func (b *builder) getValue(expr ssa.Value, pos token.Pos) llvm.Value {
return value return value
default: default:
// other (local) SSA value // other (local) SSA value
if value, ok := b.indirectValues[expr]; ok {
return b.CreateLoad(b.getLLVMType(expr.Type()), value, "")
}
if value, ok := b.locals[expr]; ok { if value, ok := b.locals[expr]; ok {
return value return value
} else { } else {
@@ -2214,8 +2463,15 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// This instruction changes the type, but the underlying value remains // This instruction changes the type, but the underlying value remains
// the same. This is often a no-op, but sometimes we have to change the // the same. This is often a no-op, but sometimes we have to change the
// LLVM type as well. // LLVM type as well.
x := b.getValue(expr.X, getPos(expr))
llvmType := b.getLLVMType(expr.Type()) llvmType := b.getLLVMType(expr.Type())
if b.isIndirectAggregate(llvmType) {
sourceType := b.getLLVMType(expr.X.Type())
if !b.isIndirectAggregate(sourceType) || b.targetData.TypeAllocSize(sourceType) != b.targetData.TypeAllocSize(llvmType) {
return llvm.Value{}, errors.New("todo: indirect aggregate ChangeType with different layout")
}
return b.getValuePointer(expr.X), nil
}
x := b.getValue(expr.X, getPos(expr))
if x.Type() == llvmType { if x.Type() == llvmType {
// Different Go type but same LLVM type (for example, named int). // Different Go type but same LLVM type (for example, named int).
// This is the common case. // This is the common case.
@@ -2245,9 +2501,15 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
if _, ok := expr.Tuple.(*ssa.Select); ok { if _, ok := expr.Tuple.(*ssa.Select); ok {
return b.getChanSelectResult(expr), nil return b.getChanSelectResult(expr), nil
} }
if value, ok := b.getValueField(expr.Tuple, expr.Index, expr.Type(), expr.Name()); ok {
return value, nil
}
value := b.getValue(expr.Tuple, getPos(expr)) value := b.getValue(expr.Tuple, getPos(expr))
return b.CreateExtractValue(value, expr.Index, ""), nil return b.CreateExtractValue(value, expr.Index, ""), nil
case *ssa.Field: case *ssa.Field:
if value, ok := b.getValueField(expr.X, expr.Field, expr.Type(), expr.Name()); ok {
return value, nil
}
value := b.getValue(expr.X, getPos(expr)) value := b.getValue(expr.X, getPos(expr))
result := b.CreateExtractValue(value, expr.Field, "") result := b.CreateExtractValue(value, expr.Field, "")
return result, nil return result, nil
@@ -2270,11 +2532,11 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
case *ssa.Global: case *ssa.Global:
panic("global is not an expression") panic("global is not an expression")
case *ssa.Index: case *ssa.Index:
collection := b.getValue(expr.X, getPos(expr))
index := b.getValue(expr.Index, getPos(expr)) index := b.getValue(expr.Index, getPos(expr))
switch xType := expr.X.Type().Underlying().(type) { switch xType := expr.X.Type().Underlying().(type) {
case *types.Basic: // extract byte from string case *types.Basic: // extract byte from string
collection := b.getValue(expr.X, getPos(expr))
// Value type must be a string, which is a basic type. // Value type must be a string, which is a basic type.
if xType.Info()&types.IsString == 0 { if xType.Info()&types.IsString == 0 {
panic("lookup on non-string?") panic("lookup on non-string?")
@@ -2308,13 +2570,12 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// Can't load directly from array (as index is non-constant), so // Can't load directly from array (as index is non-constant), so
// have to do it using an alloca+gep+load. // have to do it using an alloca+gep+load.
arrayType := collection.Type() arrayType := b.getLLVMType(expr.X.Type())
alloca, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca") storage := b.getValueStorage(expr.X, "index.alloca")
b.CreateStore(collection, alloca)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep") ptr := b.CreateInBoundsGEP(arrayType, storage.ptr, []llvm.Value{zero, index}, "index.gep")
result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load") result := b.loadFromStorage(ptr, expr.Type(), "index.load")
b.emitLifetimeEnd(alloca, allocaSize) b.endValueStorage(storage)
return result, nil return result, nil
default: default:
panic("unknown *ssa.Index type") panic("unknown *ssa.Index type")
@@ -2373,17 +2634,21 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
} }
case *ssa.Lookup: // map lookup case *ssa.Lookup: // map lookup
value := b.getValue(expr.X, getPos(expr)) value := b.getValue(expr.X, getPos(expr))
index := b.getValue(expr.Index, getPos(expr))
valueType := expr.Type() valueType := expr.Type()
if expr.CommaOk { if expr.CommaOk {
valueType = valueType.(*types.Tuple).At(0).Type() valueType = valueType.(*types.Tuple).At(0).Type()
} }
return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, index, expr.CommaOk, expr.Pos()) return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, expr.Index, expr.CommaOk, expr.Pos())
case *ssa.MakeChan: case *ssa.MakeChan:
return b.createMakeChan(expr), nil return b.createMakeChan(expr), nil
case *ssa.MakeClosure: case *ssa.MakeClosure:
return b.parseMakeClosure(expr) return b.parseMakeClosure(expr)
case *ssa.MakeInterface: case *ssa.MakeInterface:
if b.isOversizedAggregate(expr.X.Type()) {
typ := b.getLLVMType(expr.X.Type())
ptr := b.copyToIndirectStorage(b.getValuePointer(expr.X), typ, "interface.value")
return b.createMakeInterfaceFromPointer(ptr, expr.X.Type()), nil
}
val := b.getValue(expr.X, getPos(expr)) val := b.getValue(expr.X, getPos(expr))
return b.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil return b.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil
case *ssa.MakeMap: case *ssa.MakeMap:
@@ -2417,8 +2682,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
} }
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap") sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos()) layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
slicePtr := b.createAlloc(sliceSize, layoutValue, 0, "makeslice.buf") slicePtr := b.createAlloc(sliceSize, layoutValue, elemAlign, "makeslice.buf")
slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign)))
// Extend or truncate if necessary. This is safe as we've already done // Extend or truncate if necessary. This is safe as we've already done
// the bounds check. // the bounds check.
@@ -2451,7 +2715,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
return b.createMapIteratorNext(rangeVal, llvmRangeVal, it), nil return b.createMapIteratorNext(rangeVal, llvmRangeVal, it), nil
} }
case *ssa.Phi: case *ssa.Phi:
phi := b.CreatePHI(b.getLLVMType(expr.Type()), "") phiType := b.storedParamType(b.getLLVMType(expr.Type()), false)
phi := b.CreatePHI(phiType, "")
b.phis = append(b.phis, phiNode{expr, phi}) b.phis = append(b.phis, phiNode{expr, phi})
return phi, nil return phi, nil
case *ssa.Range: case *ssa.Range:
@@ -3454,8 +3719,7 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) {
return fn, nil return fn, nil
} else { } else {
b.createNilCheck(unop.X, x, "deref") b.createNilCheck(unop.X, x, "deref")
load := b.CreateLoad(valueType, x, "") return b.loadFromStorage(x, unop.Type(), ""), nil
return load, nil
} }
case token.XOR: // ^x, toggle all bits in integer case token.XOR: // ^x, toggle all bits in integer
return b.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil return b.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil
+175 -18
View File
@@ -4,6 +4,7 @@ import (
"flag" "flag"
"go/types" "go/types"
"os" "os"
"regexp"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@@ -51,6 +52,7 @@ func TestCompiler(t *testing.T) {
{"gc.go", "", ""}, {"gc.go", "", ""},
{"zeromap.go", "", ""}, {"zeromap.go", "", ""},
{"generics.go", "", ""}, {"generics.go", "", ""},
{"large.go", "", ""},
} }
if goMinor >= 20 { if goMinor >= 20 {
tests = append(tests, testCase{"go1.20.go", "", ""}) tests = append(tests, testCase{"go1.20.go", "", ""})
@@ -58,6 +60,9 @@ func TestCompiler(t *testing.T) {
if goMinor >= 21 { if goMinor >= 21 {
tests = append(tests, testCase{"go1.21.go", "", ""}) tests = append(tests, testCase{"go1.21.go", "", ""})
} }
if goMinor >= 27 {
tests = append(tests, testCase{"go1.27.go", "", ""})
}
for _, tc := range tests { for _, tc := range tests {
name := tc.file name := tc.file
@@ -110,7 +115,7 @@ func TestCompiler(t *testing.T) {
// Update test if needed. Do not check the result. // Update test if needed. Do not check the result.
if *flagUpdate { if *flagUpdate {
err := os.WriteFile(outPath, []byte(mod.String()), 0666) err := os.WriteFile(outPath, []byte(normalizeIR(mod.String())), 0666)
if err != nil { if err != nil {
t.Error("failed to write updated output file:", err) t.Error("failed to write updated output file:", err)
} }
@@ -122,30 +127,137 @@ func TestCompiler(t *testing.T) {
t.Fatal("failed to read golden file:", err) t.Fatal("failed to read golden file:", err)
} }
if !fuzzyEqualIR(mod.String(), string(expected)) { if diff := diffIR(string(expected), mod.String()); diff != "" {
t.Errorf("output does not match expected output:\n%s", mod.String()) 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 func TestOptimizedLargeAggregateABI(t *testing.T) {
// equal. That means, only relevant lines are compared (excluding comments options := &compileopts.Options{Target: "wasm"}
// etc.). mod, errs := testCompilePackage(t, options, "large-optimized.go")
func fuzzyEqualIR(s1, s2 string) bool { if len(errs) != 0 {
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) for _, err := range errs {
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) t.Error(err)
if len(lines1) != len(lines2) {
return false
}
for i, line1 := range lines1 {
line2 := lines2[i]
if line1 != line2 {
return false
} }
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-version-specific IR spellings for comparison
// and when regenerating golden files.
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 // filterIrrelevantIRLines removes lines from the input slice of strings that
@@ -214,6 +326,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. // 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) { func testCompilePackage(t *testing.T, options *compileopts.Options, file string) (llvm.Module, []error) {
target, err := compileopts.LoadTarget(options) target, err := compileopts.LoadTarget(options)
@@ -258,5 +413,7 @@ func testCompilePackage(t *testing.T, options *compileopts.Options, file string)
// Compile AST to IR. // Compile AST to IR.
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
pkg := lprogram.MainPkg() pkg := lprogram.MainPkg()
return CompilePackage(file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false) ssaPkg := program.Package(pkg.Pkg)
ssaPkg.Build()
return CompilePackage(file, pkg, ssaPkg, machine, compilerConfig, false)
} }
+88 -85
View File
@@ -32,7 +32,7 @@ func (b *builder) supportsRecover() bool {
// proposal of WebAssembly: // proposal of WebAssembly:
// https://github.com/WebAssembly/exception-handling // https://github.com/WebAssembly/exception-handling
return false return false
case "riscv64", "xtensa": case "xtensa":
// TODO: add support for these architectures // TODO: add support for these architectures
return false return false
default: default:
@@ -217,12 +217,20 @@ sw $$ra, 4($$5)
// So only add them when using hardfloat. // 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}" 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": case "riscv32", "riscv64":
asmString = ` if b.archFamily() == "riscv32" {
asmString = `
la a2, 1f la a2, 1f
sw a2, 4(a1) sw a2, 4(a1)
li a0, 0 li a0, 0
1:` 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}" 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: default:
// This case should have been handled by b.supportsRecover(). // This case should have been handled by b.supportsRecover().
@@ -359,6 +367,44 @@ type tarjanNode struct {
cyclic bool 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 // createDefer emits a single defer instruction, to be run when this function
// returns. // returns.
func (b *builder) createDefer(instr *ssa.Defer) { func (b *builder) createDefer(instr *ssa.Defer) {
@@ -366,31 +412,28 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// make a linked list. // make a linked list.
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next") next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next")
var values []llvm.Value var values llvmValueList
valueTypes := []llvm.Type{b.uintptrType, next.Type()} lowerArgument := func(value ssa.Value) llvm.Value {
return b.getCallArgument(value, false)
}
if instr.Call.IsInvoke() { if instr.Call.IsInvoke() {
// Method call on an interface. // Method call on an interface.
// Get callback type number. // Get callback type number.
methodName := instr.Call.Method.FullName() key := b.getInvokeFunctionName(&instr.Call)
if _, ok := b.deferInvokeFuncs[methodName]; !ok { if _, ok := b.deferInvokeFuncs[key]; !ok {
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs) b.deferInvokeFuncs[key] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call) 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 // Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by the call parameters). // runtime._defer fields, followed by the call parameters).
itf := b.getValue(instr.Call.Value, getPos(instr)) // interface itf := b.getValue(instr.Call.Value, getPos(instr)) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, typecode, receiverValue} values = newLLVMValueList(callback, next, typecode, receiverValue)
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) values.appendSSAValues(instr.Call.Args, lowerArgument)
for _, arg := range instr.Call.Args {
val := b.getValue(arg, getPos(instr))
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok { } else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call. // Regular function call.
@@ -402,12 +445,11 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with // Collect all values to be put in the struct (starting with
// runtime._defer fields). // runtime._defer fields).
values = []llvm.Value{callback, next} values = newLLVMValueList(callback, next)
for _, param := range instr.Call.Args { exported := b.getFunctionInfo(callee).exported
llvmParam := b.getValue(param, getPos(instr)) values.appendSSAValues(instr.Call.Args, func(value ssa.Value) llvm.Value {
values = append(values, llvmParam) return b.getCallArgument(value, exported)
valueTypes = append(valueTypes, llvmParam.Type()) })
}
} else if makeClosure, ok := instr.Call.Value.(*ssa.MakeClosure); ok { } else if makeClosure, ok := instr.Call.Value.(*ssa.MakeClosure); ok {
// Immediately applied function literal with free variables. // Immediately applied function literal with free variables.
@@ -430,14 +472,9 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with // Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the // runtime._defer fields, followed by all parameters including the
// context pointer). // context pointer).
values = []llvm.Value{callback, next} values = newLLVMValueList(callback, next)
for _, param := range instr.Call.Args { values.appendSSAValues(instr.Call.Args, lowerArgument)
llvmParam := b.getValue(param, getPos(instr)) values.append(context)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
values = append(values, context)
valueTypes = append(valueTypes, context.Type())
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
var argTypes []types.Type var argTypes []types.Type
@@ -460,11 +497,8 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with // Collect all values to be put in the struct (starting with
// runtime._defer fields). // runtime._defer fields).
values = []llvm.Value{callback, next} values = newLLVMValueList(callback, next)
for _, param := range argValues { values.append(argValues...)
values = append(values, param)
valueTypes = append(valueTypes, param.Type())
}
} else { } else {
funcValue := b.getValue(instr.Call.Value, getPos(instr)) funcValue := b.getValue(instr.Call.Value, getPos(instr))
@@ -479,20 +513,15 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with // Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the // runtime._defer fields, followed by all parameters including the
// context pointer). // context pointer).
values = []llvm.Value{callback, next, funcValue} values = newLLVMValueList(callback, next, funcValue)
valueTypes = append(valueTypes, funcValue.Type()) values.appendSSAValues(instr.Call.Args, lowerArgument)
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr))
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
} }
// Make a struct out of the collected values to put in the deferred call // Make a struct out of the collected values to put in the deferred call
// struct. // struct.
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(values.types, false)
deferredCall := llvm.ConstNull(deferredCallType) deferredCall := llvm.ConstNull(deferredCallType)
for i, value := range values { for i, value := range values.values {
deferredCall = b.CreateInsertValue(deferredCall, value, i, "") deferredCall = b.CreateInsertValue(deferredCall, value, i, "")
} }
@@ -508,8 +537,9 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// This may be hit a variable number of times, so use a heap allocation. // This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferredCallType) size := b.targetData.TypeAllocSize(deferredCallType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
nilPtr := llvm.ConstNull(b.dataPtrType) layoutValue := b.createObjectLayout(deferredCallType, instr.Pos())
alloca = b.createAlloc(sizeValue, nilPtr, 0, "defer.alloc.call") align := b.targetData.ABITypeAlignment(deferredCallType)
alloca = b.createAlloc(sizeValue, layoutValue, align, "defer.alloc.call")
} }
if b.NeedsStackObjects { if b.NeedsStackObjects {
b.trackPointer(alloca) b.trackPointer(alloca)
@@ -593,19 +623,11 @@ func (b *builder) createRunDefers() {
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
} }
for _, arg := range callback.Args { valueTypes = b.appendStoredValueTypes(valueTypes, callback.Args, false)
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
}
// Extract the params from the struct (including receiver). // 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) deferredCallType := b.ctx.StructType(valueTypes, false)
for i := 2; i < len(valueTypes); i++ { forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
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)
}
var fnPtr llvm.Value var fnPtr llvm.Value
var fnType llvm.Type var fnType llvm.Type
@@ -634,6 +656,7 @@ func (b *builder) createRunDefers() {
// with a strict calling convention. // with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
} }
forwardParams = b.prependIndirectResult(callback.Signature(), false, forwardParams, "defer.result")
b.createCall(fnType, fnPtr, forwardParams, "") b.createCall(fnType, fnPtr, forwardParams, "")
@@ -642,27 +665,21 @@ func (b *builder) createRunDefers() {
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
for _, param := range getParams(callback.Signature) { exported := b.getFunctionInfo(callback).exported
valueTypes = append(valueTypes, b.getLLVMType(param.Type())) valueTypes = b.appendStoredParamTypes(valueTypes, getParams(callback.Signature), exported)
}
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
// Extract the params from the struct. // Extract the params from the struct.
forwardParams := []llvm.Value{} forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
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)
}
// Plain TinyGo functions add some extra parameters to implement async functionality and function receivers. // 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. // 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 // Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway. // function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
} }
forwardParams = b.prependIndirectResult(callback.Signature, exported, forwardParams, "defer.result")
// Call real function. // Call real function.
fnType, fn := b.getFunction(callback) fnType, fn := b.getFunction(callback)
@@ -672,24 +689,16 @@ func (b *builder) createRunDefers() {
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
fn := callback.Fn.(*ssa.Function) fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
params := fn.Signature.Params() valueTypes = b.appendStoredParamTypes(valueTypes, getParams(fn.Signature), false)
for v := range params.Variables() {
valueTypes = append(valueTypes, b.getLLVMType(v.Type()))
}
valueTypes = append(valueTypes, b.dataPtrType) // closure valueTypes = append(valueTypes, b.dataPtrType) // closure
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
// Extract the params from the struct. // Extract the params from the struct.
forwardParams := []llvm.Value{} forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
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)
}
// Call deferred function. // Call deferred function.
fnType, llvmFn := b.getFunction(fn) fnType, llvmFn := b.getFunction(fn)
forwardParams = b.prependIndirectResult(fn.Signature, false, forwardParams, "defer.result")
b.createCall(fnType, llvmFn, forwardParams, "") b.createCall(fnType, llvmFn, forwardParams, "")
case *ssa.Builtin: case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback] db := b.deferBuiltinFuncs[callback]
@@ -706,13 +715,7 @@ func (b *builder) createRunDefers() {
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
// Extract the params from the struct. // Extract the params from the struct.
var argValues []llvm.Value argValues := b.loadDeferredCallParams(deferredCallType, deferData)
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)
}
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos) _, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos)
if err != nil { if err != nil {
+95 -20
View File
@@ -10,6 +10,91 @@ import (
"tinygo.org/x/go-llvm" "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 // createFuncValue creates a function value from a raw function pointer with no
// context. // context.
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { 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. // getLLVMFunctionType returns a LLVM function type for a given signature.
func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type { func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
// Get the return type. returnType, indirectResult := c.hasIndirectResult(typ)
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)
}
// Get the parameter types. // Get the parameter types.
var paramTypes []llvm.Type 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 { if typ.Recv() != nil {
recv := c.getLLVMType(typ.Recv().Type()) recv := c.getLLVMType(typ.Recv().Type())
if recv.StructName() == "runtime._interface" { if recv.StructName() == "runtime._interface" {
@@ -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 // Store the bound variables in a single object, allocating it on the heap
// if necessary. // if necessary.
context := b.emitPointerPack(boundVars) context := b.emitPointerPack(boundVars, expr.Pos())
// Create the closure. // Create the closure.
_, fn := b.getFunction(f) _, fn := b.getFunction(f)
+3 -3
View File
@@ -29,10 +29,10 @@ func (b *builder) createAlloc(sizeValue, layoutValue llvm.Value, align int, comm
// Make the runtime call. // Make the runtime call.
call := b.createRuntimeCall(allocFunc, []llvm.Value{sizeValue, layoutValue}, comment) call := b.createRuntimeCall(allocFunc, []llvm.Value{sizeValue, layoutValue}, comment)
if align != 0 { if align == 0 || align&(align-1) != 0 {
// TODO: make sure all callsites set the correct alignment. panic("invalid alignment")
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
} }
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
return call return call
} }
+31 -18
View File
@@ -47,20 +47,16 @@ func (b *builder) createGo(instr *ssa.Go) {
return return
} }
// Get all function parameters to pass to the goroutine.
var params []llvm.Value var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...)
}
var prefix string var prefix string
var funcPtr llvm.Value var funcPtr llvm.Value
var funcType llvm.Type var funcType llvm.Type
var context llvm.Value
hasContext := false hasContext := false
exported := false
if callee := instr.Call.StaticCallee(); callee != nil { if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new // Static callee is known. This makes it easier to start a new
// goroutine. // goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) { switch value := instr.Call.Value.(type) {
case *ssa.Function: case *ssa.Function:
// Goroutine call is regular function call. No context is necessary. // 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") panic("StaticCallee returned an unexpected value")
} }
if !context.IsNil() { if !context.IsNil() {
params = append(params, context) // context parameter
hasContext = true hasContext = true
} }
funcType, funcPtr = b.getFunction(callee) funcType, funcPtr = b.getFunction(callee)
exported = b.getFunctionInfo(callee).exported
} else if instr.Call.IsInvoke() { } else if instr.Call.IsInvoke() {
// This is a method call on an interface value. // This is a method call on an interface value.
itf := b.getValue(instr.Call.Value, getPos(instr)) itf := b.getValue(instr.Call.Value, getPos(instr))
@@ -84,23 +80,32 @@ func (b *builder) createGo(instr *ssa.Go) {
itfValue := b.CreateExtractValue(itf, 1, "") itfValue := b.CreateExtractValue(itf, 1, "")
funcPtr = b.getInvokeFunction(&instr.Call) funcPtr = b.getInvokeFunction(&instr.Call)
funcType = funcPtr.GlobalValueType() funcType = funcPtr.GlobalValueType()
params = append([]llvm.Value{itfValue}, params...) // start with receiver params = append(params, itfValue)
params = append(params, itfTypeCode) // end with typecode context = itfTypeCode
} else { } else {
// This is a function pointer. // This is a function pointer.
// At the moment, two extra params are passed to the newly started // At the moment, two extra params are passed to the newly started
// goroutine: // goroutine:
// * The function context, for closures. // * The function context, for closures.
// * The function pointer (for tasks). // * The function pointer (for tasks).
var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr))) funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr)))
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature)) funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context, funcPtr)
hasContext = true hasContext = true
prefix = b.getFunctionInfo(b.fn).linkName 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 var stackSize llvm.Value
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos()) callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos())
if b.AutomaticStackSize { 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)}, "") 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 // Create an exported wrapper function for functions with the //go:wasmexport
// pragma. This wrapper function is quite complex when the scheduler is enabled: // 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. // it needs to start a new goroutine each time the exported function is called.
@@ -295,10 +309,10 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
} }
defer b.Dispose() defer b.Dispose()
var deadlock llvm.Value var exitGoroutine llvm.Value
var deadlockType llvm.Type var exitGoroutineType llvm.Type
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
deadlockType, deadlock = c.getFunction(c.program.ImportedPackage("runtime").Members["deadlock"].(*ssa.Function)) exitGoroutineType, exitGoroutine = c.getFunction(c.program.ImportedPackage("runtime").Members["exitGoroutine"].(*ssa.Function))
} }
if !fn.IsAFunction().IsNil() { if !fn.IsAFunction().IsNil() {
@@ -363,7 +377,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
b.CreateCall(fnType, fn, params, "") b.CreateCall(fnType, fn, params, "")
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{ b.CreateCall(exitGoroutineType, exitGoroutine, []llvm.Value{
llvm.Undef(c.dataPtrType), llvm.Undef(c.dataPtrType),
}, "") }, "")
} }
@@ -514,14 +528,13 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
b.CreateCall(fnType, fnPtr, params, "") b.CreateCall(fnType, fnPtr, params, "")
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{ b.CreateCall(exitGoroutineType, exitGoroutine, []llvm.Value{
llvm.Undef(c.dataPtrType), llvm.Undef(c.dataPtrType),
}, "") }, "")
} }
} }
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
// The goroutine was terminated via deadlock.
b.CreateUnreachable() b.CreateUnreachable()
} else { } else {
// Finish the function. Every basic block must end in a terminator, and // Finish the function. Every basic block must end in a terminator, and
+111 -134
View File
@@ -84,7 +84,11 @@ const (
// //
// An interface value is a {typecode, value} tuple named runtime._interface. // An interface value is a {typecode, value} tuple named runtime._interface.
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value { func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val}) itfValue := b.emitPointerPack([]llvm.Value{val}, pos)
return b.createMakeInterfaceFromPointer(itfValue, typ)
}
func (b *builder) createMakeInterfaceFromPointer(itfValue llvm.Value, typ types.Type) llvm.Value {
itfType := b.getTypeCode(typ) itfType := b.getTypeCode(typ)
itf := llvm.Undef(b.getLLVMRuntimeType("_interface")) itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfType, 0, "") itf = b.CreateInsertValue(itf, itfType, 0, "")
@@ -98,9 +102,16 @@ func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.
// doesn't match the underlying type of the interface. // doesn't match the underlying type of the interface.
func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value { func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value {
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr") valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
if b.isIndirectAggregate(llvmType) {
return valuePtr
}
return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0] return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0]
} }
func (b *builder) extractValuePointerFromInterface(itf llvm.Value) llvm.Value {
return b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
}
func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value { func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value {
pkgpathName := "reflect/types.type.pkgpath.empty" pkgpathName := "reflect/types.type.pkgpath.empty"
if pkgpath != "" { if pkgpath != "" {
@@ -125,6 +136,16 @@ func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value {
return pkgPathPtr return pkgPathPtr
} }
// isGenericMethod returns true for a method that has its own type parameters
// (independent of any type parameters on its receiver), e.g. the N method in
// "func (r *Rand) N[Int intType](n Int) Int { ... }". Like the reflect
// package, such methods are excluded from runtime method sets: they aren't
// instantiated, so their signature can't be represented in a type code, and
// they can never satisfy an interface method anyway.
func isGenericMethod(fn *types.Func) bool {
return fn.Signature().TypeParams().Len() > 0
}
// getTypeCode returns a reference to a type code. // getTypeCode returns a reference to a type code.
// A type code is a pointer to a constant global that describes the type. // A type code is a pointer to a constant global that describes the type.
// This function returns a pointer to the 'kind' field (which might not be the // This function returns a pointer to the 'kind' field (which might not be the
@@ -134,22 +155,26 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
typ = types.Unalias(typ) typ = types.Unalias(typ)
ms := c.program.MethodSets.MethodSet(typ) ms := c.program.MethodSets.MethodSet(typ)
hasMethodSet := ms.Len() != 0
_, isInterface := typ.Underlying().(*types.Interface) _, isInterface := typ.Underlying().(*types.Interface)
if isInterface {
hasMethodSet = false
}
// As defined in https://pkg.go.dev/reflect#Type: // As defined in https://pkg.go.dev/reflect#Type:
// NumMethod returns the number of methods accessible using Method. // NumMethod returns the number of methods accessible using Method.
// For a non-interface type, it returns the number of exported methods. // For a non-interface type, it returns the number of exported methods.
// For an interface type, it returns the number of exported and unexported methods. // For an interface type, it returns the number of exported and unexported methods.
var numMethods int var numMethods int
var hasMethodSet bool
for method := range ms.Methods() { for method := range ms.Methods() {
if isGenericMethod(method.Obj().(*types.Func)) {
continue
}
hasMethodSet = true
if isInterface || method.Obj().Exported() { if isInterface || method.Obj().Exported() {
numMethods++ numMethods++
} }
} }
if isInterface {
hasMethodSet = false
}
// Short-circuit all the global pointer logic here for pointers to pointers. // Short-circuit all the global pointer logic here for pointers to pointers.
if typ, ok := typ.(*types.Pointer); ok { if typ, ok := typ.(*types.Pointer); ok {
@@ -194,7 +219,11 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// Compute the method set value for types that support methods. // Compute the method set value for types that support methods.
var methods []*types.Func var methods []*types.Func
for method := range ms.Methods() { for method := range ms.Methods() {
methods = append(methods, method.Obj().(*types.Func)) fn := method.Obj().(*types.Func)
if isGenericMethod(fn) {
continue
}
methods = append(methods, fn)
} }
methodSetType := types.NewStruct([]*types.Var{ methodSetType := types.NewStruct([]*types.Var{
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]), types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
@@ -599,8 +628,21 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
case *types.Named: case *types.Named:
tn := t.Obj() tn := t.Obj()
if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() { if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() {
// Package-scope or builtin: the printed name is unique. name := tn.Name()
return "named:" + t.String(), false if tn.Pkg() != nil {
name = tn.Pkg().Path() + "." + name
}
isLocal := false
if targs := t.TypeArgs(); targs.Len() != 0 {
parts := make([]string, targs.Len())
for i := range parts {
var local bool
parts[i], local = c.getTypeCodeName(targs.At(i))
isLocal = isLocal || local
}
name += "[" + strings.Join(parts, ",") + "]"
}
return "named:" + name, isLocal
} }
if tn.Parent() != nil { if tn.Parent() != nil {
// Ordinary function-local type. Use the un-//line-adjusted // Ordinary function-local type. Use the un-//line-adjusted
@@ -707,8 +749,8 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
// Synthetic TypeNames are produced by generic instantiation: two // Synthetic TypeNames are produced by generic instantiation: two
// instantiations of the same generic function (e.g. F[int] and // instantiations of the same generic function (e.g. F[int] and
// F[string]) produce TypeNames with the same printed name and the // F[string]) produce TypeNames with the same printed name and the
// same source position, so each is named with the enclosing // same source position, so each is named with the enclosing instance's
// instance's RelString as prefix. RelString encodes the type // canonical function name as prefix. The function name encodes the type
// arguments, matching Go's runtime behavior, where F[int].Inner and // arguments, matching Go's runtime behavior, where F[int].Inner and
// F[string].Inner are distinct types even when Inner does not mention // F[string].Inner are distinct types even when Inner does not mention
// the type parameter. // the type parameter.
@@ -717,9 +759,9 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
// of F[int] is compiled in every package that calls F[int]); its // of F[int] is compiled in every package that calls F[int]); its
// reflect/types.type:* global has LinkOnceODRLinkage and is merged by // reflect/types.type:* global has LinkOnceODRLinkage and is merged by
// name at link time. The chosen name therefore depends only on // name at link time. The chosen name therefore depends only on
// intrinsic SSA properties (RelString and the raw token.Pos used as a // intrinsic SSA properties (the canonical function name and raw token.Pos),
// sort key), so any package compiling the same instance produces the // so any package compiling the same instance produces the same
// same identifier. // identifier.
// //
// Ordinary function-local TypeNames (TypeName.Parent() != nil) are // Ordinary function-local TypeNames (TypeName.Parent() != nil) are
// not handled here: they are nameable only inside their declaring // not handled here: they are nameable only inside their declaring
@@ -807,9 +849,8 @@ func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
// registerSyntheticLocalTypes walks every type reachable from fn's // registerSyntheticLocalTypes walks every type reachable from fn's
// body and records each synthetic *types.Named (TypeName.Parent() == // body and records each synthetic *types.Named (TypeName.Parent() ==
// nil) in c.localTypeNames. Each is named with fn.RelString as the // nil) in c.localTypeNames. Each is named with the canonical function
// owning function plus a per-function counter assigned in source // name plus a per-function counter assigned in source order.
// order.
// //
// First-writer-wins: a *types.Named already present in // First-writer-wins: a *types.Named already present in
// c.localTypeNames is left alone, so a synthetic type reachable from // c.localTypeNames is left alone, so a synthetic type reachable from
@@ -920,7 +961,7 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
sort.Slice(found, func(i, j int) bool { sort.Slice(found, func(i, j int) bool {
return found[i].Obj().Pos() < found[j].Obj().Pos() return found[i].Obj().Pos() < found[j].Obj().Pos()
}) })
enclosing := fn.RelString(nil) enclosing := c.canonicalFunctionName(fn)
for i, named := range found { for i, named := range found {
c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i)) c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i))
} }
@@ -929,7 +970,8 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
// getTypeMethodSet returns a reference (GEP) to a global method set. This // getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass. // method set should be unreferenced after the interface lowering pass.
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
globalName := typ.String() + "$methodset" typeName, _ := c.getTypeCodeName(typ)
globalName := typeName + "$methodset"
global := c.mod.NamedGlobal(globalName) global := c.mod.NamedGlobal(globalName)
if global.IsNil() { if global.IsNil() {
ms := c.program.MethodSets.MethodSet(typ) ms := c.program.MethodSets.MethodSet(typ)
@@ -937,6 +979,9 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// Create method set. // Create method set.
var signatures, wrappers []llvm.Value var signatures, wrappers []llvm.Value
for method := range ms.Methods() { for method := range ms.Methods() {
if isGenericMethod(method.Obj().(*types.Func)) {
continue
}
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
signatures = append(signatures, signatureGlobal) signatures = append(signatures, signatureGlobal)
fn := c.program.MethodValue(method) fn := c.program.MethodValue(method)
@@ -951,7 +996,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// Construct global value. // Construct global value.
globalValue := c.ctx.ConstStruct([]llvm.Value{ globalValue := c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false), llvm.ConstInt(c.uintptrType, uint64(len(signatures)), false),
llvm.ConstArray(c.dataPtrType, signatures), llvm.ConstArray(c.dataPtrType, signatures),
c.ctx.ConstStruct(wrappers, false), c.ctx.ConstStruct(wrappers, false),
}, false) }, false)
@@ -967,14 +1012,15 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// getMethodSignatureName returns a unique name (that can be used as the name of // getMethodSignatureName returns a unique name (that can be used as the name of
// a global) for the given method. // a global) for the given method.
func (c *compilerContext) getMethodSignatureName(method *types.Func) string { func (c *compilerContext) getMethodSignatureName(method *types.Func) string {
signature := methodSignature(method) name := method.Name()
var globalName string var prefix string
if token.IsExported(method.Name()) { if token.IsExported(method.Name()) {
globalName = "reflect/methods." + signature prefix = "reflect/methods."
} else { } else {
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature prefix = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods."
} }
return globalName signature, _ := c.getTypeCodeName(method.Type())
return prefix + name + ":" + signature
} }
// getMethodSignature returns a global variable which is a reference to an // getMethodSignature returns a global variable which is a reference to an
@@ -1053,6 +1099,21 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
if expr.CommaOk { if expr.CommaOk {
nextBlock := b.insertBasicBlock("typeassert.next") nextBlock := b.insertBasicBlock("typeassert.next")
b.currentBlockInfo.exit = nextBlock b.currentBlockInfo.exit = nextBlock
if b.isIndirectAggregate(assertedType) {
resultType := b.getLLVMType(expr.Type())
result := b.createIndirectStorage(resultType, "typeassert.result")
b.zeroIndirectStorage(result, resultType)
b.CreateCondBr(commaOk, okBlock, nextBlock)
b.SetInsertPointAtEnd(okBlock)
valuePtr := b.extractValuePointerFromInterface(itf)
b.copyIndirectAggregate(b.CreateStructGEP(resultType, result, 0, ""), valuePtr, assertedType)
b.CreateBr(nextBlock)
b.SetInsertPointAtEnd(nextBlock)
b.CreateStore(commaOk, b.CreateStructGEP(resultType, result, 1, ""))
return result
}
b.CreateCondBr(commaOk, okBlock, nextBlock) b.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was // Retrieve the value from the interface if the type assert was
@@ -1179,8 +1240,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
// thunk is declared, not defined: it will be defined by the interface lowering // thunk is declared, not defined: it will be defined by the interface lowering
// pass. // pass.
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value { func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying()) fnName := c.getInvokeFunctionName(instr)
fnName := s + "." + instr.Method.Name() + "$invoke"
llvmFn := c.mod.NamedFunction(fnName) llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
sig := instr.Method.Type().(*types.Signature) sig := instr.Method.Type().(*types.Signature)
@@ -1193,12 +1253,20 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn) c.addStandardDeclaredAttributes(llvmFn)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method))) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
if _, indirect := c.hasIndirectResult(sig); indirect {
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-indirect-result", "true"))
}
methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface)) methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods)) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
} }
return llvmFn return llvmFn
} }
func (c *compilerContext) getInvokeFunctionName(instr *ssa.CallCommon) string {
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying())
return s + "." + instr.Method.Name() + "$invoke"
}
// createInterfaceTypeAssert creates a call to a declared-but-not-defined // createInterfaceTypeAssert creates a call to a declared-but-not-defined
// $typeassert function for the given interface. This function will be defined // $typeassert function for the given interface. This function will be defined
// by the interface lowering pass as a type-ID comparison chain, avoiding the // by the interface lowering pass as a type-ID comparison chain, avoiding the
@@ -1233,6 +1301,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
// Get the expanded receiver type. // Get the expanded receiver type.
receiverType := c.getLLVMType(fn.Signature.Recv().Type()) receiverType := c.getLLVMType(fn.Signature.Recv().Type())
var expandedReceiverType []llvm.Type var expandedReceiverType []llvm.Type
receiverIndirect := c.isIndirectAggregate(receiverType)
for _, info := range c.expandFormalParamType(receiverType, "", nil) { for _, info := range c.expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType) expandedReceiverType = append(expandedReceiverType, info.llvmType)
} }
@@ -1247,7 +1316,13 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
} }
// create wrapper function // create wrapper function
paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...) resultOffset := 0
if _, indirect := c.hasIndirectResult(fn.Signature); indirect {
resultOffset = 1
}
paramTypes := append([]llvm.Type{}, llvmFnType.ParamTypes()[:resultOffset]...)
paramTypes = append(paramTypes, c.dataPtrType)
paramTypes = append(paramTypes, llvmFnType.ParamTypes()[resultOffset+len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false) wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
c.addStandardAttributes(wrapper) c.addStandardAttributes(wrapper)
@@ -1273,8 +1348,15 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
block := b.ctx.AddBasicBlock(wrapper, "entry") block := b.ctx.AddBasicBlock(wrapper, "entry")
b.SetInsertPointAtEnd(block) b.SetInsertPointAtEnd(block)
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0] params := append([]llvm.Value{}, wrapper.Params()[:resultOffset]...)
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...) receiverParam := wrapper.Param(resultOffset)
if receiverIndirect {
params = append(params, receiverParam)
} else {
receiverValue := b.emitPointerUnpack(receiverParam, []llvm.Type{receiverType})[0]
params = append(params, b.expandFormalParam(receiverValue)...)
}
params = append(params, wrapper.Params()[resultOffset+1:]...)
if llvmFnType.ReturnType().TypeKind() == llvm.VoidTypeKind { if llvmFnType.ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(llvmFnType, llvmFn, params, "") b.CreateCall(llvmFnType, llvmFn, params, "")
b.CreateRetVoid() b.CreateRetVoid()
@@ -1285,108 +1367,3 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
return wrapper return wrapper
} }
// methodSignature creates a readable version of a method signature (including
// the function name, excluding the receiver name). This string is used
// internally to match interfaces and to call the correct method on an
// interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func methodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
var s strings.Builder
if sig.Params().Len() == 0 {
s.WriteString("()")
} else {
s.WriteString("(")
i := 0
for v := range sig.Params().Variables() {
if i > 0 {
s.WriteString(", ")
}
s.WriteString(typestring(v.Type()))
i++
}
s.WriteString(")")
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s.WriteString(" " + typestring(sig.Results().At(0).Type()))
} else {
s.WriteString(" (")
i := 0
for v := range sig.Results().Variables() {
if i > 0 {
s.WriteString(", ")
}
s.WriteString(typestring(v.Type()))
i++
}
s.WriteString(")")
}
return s.String()
}
// typestring returns a stable (human-readable) type string for the given type
// that can be used for interface equality checks. It is almost (but not
// exactly) the same as calling t.String(). The main difference is some
// normalization around `byte` vs `uint8` for example.
func typestring(t types.Type) string {
// See: https://github.com/golang/go/blob/master/src/go/types/typestring.go
switch t := types.Unalias(t).(type) {
case *types.Array:
return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
case *types.Basic:
return basicTypeNames[t.Kind()]
case *types.Chan:
switch t.Dir() {
case types.SendRecv:
return "chan (" + typestring(t.Elem()) + ")"
case types.SendOnly:
return "chan<- (" + typestring(t.Elem()) + ")"
case types.RecvOnly:
return "<-chan (" + typestring(t.Elem()) + ")"
default:
panic("unknown channel direction")
}
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := range methods {
method := t.Method(i)
methods[i] = method.Name() + signature(method.Type().(*types.Signature))
}
return "interface{" + strings.Join(methods, ";") + "}"
case *types.Map:
return "map[" + typestring(t.Key()) + "]" + typestring(t.Elem())
case *types.Named:
return t.String()
case *types.Pointer:
return "*" + typestring(t.Elem())
case *types.Signature:
return "func" + signature(t)
case *types.Slice:
return "[]" + typestring(t.Elem())
case *types.Struct:
fields := make([]string, t.NumFields())
for i := range fields {
field := t.Field(i)
fields[i] = field.Name() + " " + typestring(field.Type())
if tag := t.Tag(i); tag != "" {
fields[i] += " " + strconv.Quote(tag)
}
}
return "struct{" + strings.Join(fields, ";") + "}"
default:
panic("unknown type: " + t.String())
}
}
+3 -2
View File
@@ -54,7 +54,7 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
// pointer value directly. It returns the pointer with the packed data. // 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 // If the values are all constants, they are be stored in a constant global and
// deduplicated. // 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)) valueTypes := make([]llvm.Type, len(values))
for i, value := range values { for i, value := range values {
valueTypes[i] = value.Type() valueTypes[i] = value.Type()
@@ -128,8 +128,9 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
// Packed data is bigger than a pointer, so allocate it on the heap. // Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
layoutValue := b.createObjectLayout(packedType, pos)
align := b.targetData.ABITypeAlignment(packedType) align := b.targetData.ABITypeAlignment(packedType)
packedAlloc := b.createAlloc(sizeValue, llvm.ConstNull(b.dataPtrType), align, "") packedAlloc := b.createAlloc(sizeValue, layoutValue, align, "")
if b.NeedsStackObjects { if b.NeedsStackObjects {
b.trackPointer(packedAlloc) b.trackPointer(packedAlloc)
} }
+56 -6
View File
@@ -44,7 +44,7 @@ func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, n
alloca = CreateEntryBlockAlloca(builder, t, name) alloca = CreateEntryBlockAlloca(builder, t, name)
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod) fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
return return
} }
@@ -58,14 +58,14 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
builder.SetInsertPointBefore(inst) builder.SetInsertPointBefore(inst)
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod) 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() { if next := llvm.NextInstruction(inst); !next.IsNil() {
builder.SetInsertPointBefore(next) builder.SetInsertPointBefore(next)
} else { } else {
builder.SetInsertPointAtEnd(inst.InstructionParent()) builder.SetInsertPointAtEnd(inst.InstructionParent())
} }
fnType, fn = getLifetimeEndFunc(mod) fnType, fn = getLifetimeEndFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
return alloca return alloca
} }
@@ -74,7 +74,27 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
// createTemporaryAlloca. // createTemporaryAlloca.
func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) { func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
fnType, fn := getLifetimeEndFunc(mod) 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 // 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) fn := mod.NamedFunction(fnName)
ctx := mod.Context() ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0) ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false) fnType := lifetimeFuncType(ctx, ptrType)
if fn.IsNil() { if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType) fn = llvm.AddFunction(mod, fnName, fnType)
} }
@@ -98,7 +118,7 @@ func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fn := mod.NamedFunction(fnName) fn := mod.NamedFunction(fnName)
ctx := mod.Context() ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0) ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false) fnType := lifetimeFuncType(ctx, ptrType)
if fn.IsNil() { if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType) fn = llvm.AddFunction(mod, fnName, fnType)
} }
@@ -218,6 +238,36 @@ func Version() int {
return major return major
} }
// 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 // ByteOrder returns the byte order for the given target triple. Most targets are little
// endian, but for example MIPS can be big-endian. // endian, but for example MIPS can be big-endian.
func ByteOrder(target string) binary.ByteOrder { func ByteOrder(target string) binary.ByteOrder {
+17 -30
View File
@@ -72,19 +72,20 @@ func (b *builder) getRuntimeFunctionValue(name string, sig *types.Signature) llv
// createMapLookup returns the value in a map. It calls a runtime function // 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. // 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) llvmValueType := b.getLLVMType(valueType)
// Allocate the memory for the resulting type. Do not zero this memory: it // 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 // will be zeroed by the hashmap get implementation if the key is not
// present in the map. // 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 // 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 // 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 // nil maps, and they'll need to zero the value pointer by that number of
// bytes. // bytes.
mapValueSize := mapValueAllocaSize mapValueSize := result.valueSize
if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType) mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
} }
@@ -94,60 +95,46 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // 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, "") commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
} else { } else {
// Key stored at actual type: either binary-comparable or with // Key stored at actual type: either binary-comparable or with
// compiler-generated hash/equal. // compiler-generated hash/equal.
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") mapKey := b.getValueStorage(key, "hashmap.key")
b.CreateStore(key, mapKeyAlloca) params := []llvm.Value{m, mapKey.ptr, mapValueAlloca, mapValueSize}
params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize}
fnName := "hashmapBinaryGet" fnName := "hashmapBinaryGet"
if !hashmapIsBinaryKey(keyType) { if !hashmapIsBinaryKey(keyType) {
fnName = "hashmapGenericGet" fnName = "hashmapGenericGet"
} }
commaOkValue = b.createRuntimeCall(fnName, params, "") commaOkValue = b.createRuntimeCall(fnName, params, "")
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) b.endValueStorage(mapKey)
} }
// Load the resulting value from the hashmap. The value is set to the zero // The value is set to the zero value if the key doesn't exist.
// value if the key doesn't exist in the hashmap. return result.finish(b, commaOkValue, ""), nil
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
}
} }
// createMapUpdate updates a map key to a given value, by creating an // createMapUpdate updates a map key to a given value, by creating an
// appropriate runtime call. // appropriate runtime call.
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { func (b *builder) createMapUpdate(keyType types.Type, m llvm.Value, key, value ssa.Value, pos token.Pos) {
valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value") storedValue := b.getValueStorage(value, "hashmap.value")
b.CreateStore(value, valueAlloca)
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
params := []llvm.Value{m, key, valueAlloca} params := []llvm.Value{m, b.getValue(key, getPos(key)), storedValue.ptr}
b.createRuntimeInvoke("hashmapStringSet", params, "") b.createRuntimeInvoke("hashmapStringSet", params, "")
} else { } else {
// Key stored at actual type. // Key stored at actual type.
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") keyStorage := b.getValueStorage(key, "hashmap.key")
b.CreateStore(key, keyAlloca)
fnName := "hashmapBinarySet" fnName := "hashmapBinarySet"
if !hashmapIsBinaryKey(keyType) { if !hashmapIsBinaryKey(keyType) {
fnName = "hashmapGenericSet" fnName = "hashmapGenericSet"
} }
params := []llvm.Value{m, keyAlloca, valueAlloca} params := []llvm.Value{m, keyStorage.ptr, storedValue.ptr}
b.createRuntimeInvoke(fnName, params, "") b.createRuntimeInvoke(fnName, params, "")
b.emitLifetimeEnd(keyAlloca, keySize) b.endValueStorage(keyStorage)
} }
b.emitLifetimeEnd(valueAlloca, valueSize) b.endValueStorage(storedValue)
} }
// createMapDelete deletes a key from a map by calling the appropriate runtime // createMapDelete deletes a key from a map by calling the appropriate runtime
+48 -51
View File
@@ -79,24 +79,27 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
return llvmFn.GlobalValueType(), llvmFn return llvmFn.GlobalValueType(), llvmFn
} }
var retType llvm.Type retType, indirectResult := c.hasIndirectResult(fn.Signature)
if fn.Signature.Results() == nil { if info.exported {
retType = c.ctx.VoidType() indirectResult = false
} else if fn.Signature.Results().Len() == 1 {
retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
} else {
results := make([]llvm.Type, 0, fn.Signature.Results().Len())
for v := range fn.Signature.Results().Variables() {
results = append(results, c.getLLVMType(v.Type()))
}
retType = c.ctx.StructType(results, false)
} }
var paramInfos []paramInfo var paramInfos []paramInfo
if indirectResult {
paramInfos = append(paramInfos, paramInfo{
llvmType: c.dataPtrType,
name: "return",
elemSize: c.targetData.TypeAllocSize(retType),
})
retType = c.ctx.VoidType()
}
for _, param := range getParams(fn.Signature) { for _, param := range getParams(fn.Signature) {
paramType := c.getLLVMType(param.Type()) paramType := c.getLLVMType(param.Type())
paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type()) if info.exported {
paramInfos = append(paramInfos, paramFragmentInfos...) paramInfos = append(paramInfos, c.expandDirectFormalParamType(paramType, param.Name(), param.Type())...)
} else {
paramInfos = append(paramInfos, c.expandFormalParamType(paramType, param.Name(), param.Type())...)
}
} }
// Add an extra parameter as the function context. This context is used in // Add an extra parameter as the function context. This context is used in
@@ -106,12 +109,20 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
} }
var paramTypes []llvm.Type var paramTypes []llvm.Type
hasIndirectABI := indirectResult
for _, info := range paramInfos { for _, info := range paramInfos {
paramTypes = append(paramTypes, info.llvmType) paramTypes = append(paramTypes, info.llvmType)
hasIndirectABI = hasIndirectABI || info.flags&paramIsIndirect != 0
} }
fnType := llvm.FunctionType(retType, paramTypes, info.variadic) fnType := llvm.FunctionType(retType, paramTypes, info.variadic)
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType) llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
if hasIndirectABI {
// Argument promotion only rewrites functions whose uses are all direct
// calls. Keep an address use so LLVM cannot reconstruct the large
// aggregate signature that this ABI exists to avoid.
llvmutil.AppendToGlobal(c.mod, "llvm.used", llvmFn)
}
if strings.HasPrefix(c.Triple, "wasm") { if strings.HasPrefix(c.Triple, "wasm") {
// C functions without prototypes like this: // C functions without prototypes like this:
// void foo(); // void foo();
@@ -141,7 +152,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// not. // not.
// (It may be safe to add the nocapture parameter to the context // (It may be safe to add the nocapture parameter to the context
// parameter, but I'd like to stay on the safe side here). // parameter, but I'd like to stay on the safe side here).
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0) nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0)
llvmFn.AddAttributeAtIndex(i+1, nocapture) llvmFn.AddAttributeAtIndex(i+1, nocapture)
} }
if paramInfo.flags&paramIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind { if paramInfo.flags&paramIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
@@ -160,9 +171,9 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// Mark it as noreturn so LLVM can optimize away code. // Mark it as noreturn so LLVM can optimize away code.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0)) llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
case "internal/abi.NoEscape": case "internal/abi.NoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape": case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
case "runtime.alloc", "runtime.alloc_noheap", "runtime.alloc_zero": case "runtime.alloc", "runtime.alloc_noheap", "runtime.alloc_zero":
// Tell the optimizer that runtime.alloc is an allocator, meaning that it // Tell the optimizer that runtime.alloc is an allocator, meaning that it
// returns values that are never null and never alias to an existing value. // returns values that are never null and never alias to an existing value.
@@ -184,44 +195,44 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
case "runtime.sliceAppend": case "runtime.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't // Appending a slice will only read the to-be-appended slice, it won't
// be modified. // be modified.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.stringFromBytes": case "runtime.stringFromBytes":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.stringFromRunes": case "runtime.stringFromRunes":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.hashmapSet": case "runtime.hashmapSet":
// The key (param 2) and value (param 3) pointers are only read via // The key (param 2) and value (param 3) pointers are only read via
// memcpy/hash/equal and are never captured. The indirect calls // memcpy/hash/equal and are never captured. The indirect calls
// through m.keyHash and m.keyEqual function pointers prevent LLVM's // through m.keyHash and m.keyEqual function pointers prevent LLVM's
// functionattrs pass from inferring this automatically. // functionattrs pass from inferring this automatically.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
case "runtime.hashmapGet": case "runtime.hashmapGet":
// The key (param 2) is read-only and never captured. // The key (param 2) is read-only and never captured.
// The value (param 3) is written to (receives the result) but never captured. // The value (param 3) is written to (receives the result) but never captured.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
case "runtime.hashmapDelete": case "runtime.hashmapDelete":
// The key (param 2) is read-only and never captured. // The key (param 2) is read-only and never captured.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
case "runtime.hashmapGenericSet": case "runtime.hashmapGenericSet":
// Same as hashmapBinarySet: key (param 2) and value (param 3) are // Same as hashmapBinarySet: key (param 2) and value (param 3) are
// not captured. // not captured.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
case "runtime.hashmapGenericGet": case "runtime.hashmapGenericGet":
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
case "runtime.hashmapGenericDelete": case "runtime.hashmapGenericDelete":
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
case "runtime.trackPointer": case "runtime.trackPointer":
// This function is necessary for tracking pointers on the stack in a // This function is necessary for tracking pointers on the stack in a
// portable way (see gc_stack_portable.go). Indicate to the optimizer // portable way (see gc_stack_portable.go). Indicate to the optimizer
// that the only thing we'll do is read the pointer. // that the only thing we'll do is read the pointer.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "__mulsi3", "__divmodsi4", "__udivmodsi4": case "__mulsi3", "__divmodsi4", "__udivmodsi4":
if strings.Split(c.Triple, "-")[0] == "avr" { if strings.Split(c.Triple, "-")[0] == "avr" {
@@ -256,7 +267,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", info.wasmName)) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", info.wasmName))
} }
nocaptureKind := llvm.AttributeKindID("nocapture") nocaptureKind := llvm.AttributeKindID(llvmutil.NoCaptureAttrName())
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes { for i, typ := range paramTypes {
if typ.TypeKind() == llvm.PointerTypeKind { if typ.TypeKind() == llvm.PointerTypeKind {
@@ -320,13 +331,7 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
} }
info := functionInfo{ info := functionInfo{
// Pick the default linkName. // Pick the default linkName.
linkName: f.RelString(nil), linkName: c.canonicalFunctionName(f),
}
// RelString is not unique for local type arguments, so add a suffix
// when needed.
if suffix := c.localTypeArgsSuffix(f); suffix != "" {
info.linkName += suffix
} }
// Check for a few runtime functions that are treated specially. // Check for a few runtime functions that are treated specially.
@@ -353,24 +358,16 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
return info return info
} }
func (c *compilerContext) localTypeArgsSuffix(f *ssa.Function) string { func (c *compilerContext) canonicalFunctionName(f *ssa.Function) string {
typeArgs := f.TypeArgs() typeArgs := f.TypeArgs()
if len(typeArgs) == 0 { if len(typeArgs) == 0 {
return "" return f.RelString(nil)
} }
var hasLocal bool
parts := make([]string, len(typeArgs)) parts := make([]string, len(typeArgs))
for i, ta := range typeArgs { for i, ta := range typeArgs {
name, isLocal := c.getTypeCodeName(ta) parts[i], _ = c.getTypeCodeName(ta)
if isLocal {
hasLocal = true
}
parts[i] = name
} }
if !hasLocal { return f.Origin().RelString(nil) + "[" + strings.Join(parts, ",") + "]"
return ""
}
return "$localtype:" + strings.Join(parts, ",")
} }
// parsePragmas is used by getFunctionInfo to parse function pragmas such as // parsePragmas is used by getFunctionInfo to parse function pragmas such as
@@ -515,7 +512,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
info.inline = inlineHint info.inline = inlineHint
case "//go:noinline": case "//go:noinline":
info.inline = inlineNone info.inline = inlineNone
case "//go:linkname": case "//go:linkname", "//go:linknamestd":
if len(parts) != 3 || parts[1] != f.Name() { if len(parts) != 3 || parts[1] != f.Name() {
continue continue
} }
+5
View File
@@ -66,6 +66,11 @@ func complexSub(x, y complex64) complex64 {
return x - y return x - y
} }
func shiftNested(x uint64) uint64 {
k := 3
return x >> (1 << k) // https://github.com/tinygo-org/tinygo/issues/5496
}
func complexMul(x, y complex64) complex64 { func complexMul(x, y complex64) complex64 {
return x * y return x * y
} }
+7
View File
@@ -176,6 +176,13 @@ entry:
ret { float, float } %3 ret { float, float } %3
} }
; Function Attrs: nounwind
define hidden i64 @main.shiftNested(i64 %x, ptr %context) unnamed_addr #1 {
entry:
%0 = lshr i64 %x, 8
ret i64 %0
}
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #1 { define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #1 {
entry: entry:
+16 -16
View File
@@ -19,33 +19,33 @@ define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %c
entry: entry:
%chan.op = alloca %runtime.channelOp, align 8 %chan.op = alloca %runtime.channelOp, align 8
%chan.value = alloca i32, align 4 %chan.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.start.p0(ptr nonnull %chan.value)
store i32 3, ptr %chan.value, align 4 store i32 3, ptr %chan.value, align 4
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3 call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.end.p0(ptr nonnull %chan.value)
ret void ret void
} }
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #2 declare void @llvm.lifetime.start.p0(ptr nocapture) #2
declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0 declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #2 declare void @llvm.lifetime.end.p0(ptr nocapture) #2
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
entry: entry:
%chan.op = alloca %runtime.channelOp, align 8 %chan.op = alloca %runtime.channelOp, align 8
%chan.value = alloca i32, align 4 %chan.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.start.p0(ptr nonnull %chan.value)
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3 %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.end.p0(ptr nonnull %chan.value)
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
ret void ret void
} }
@@ -55,9 +55,9 @@ declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferen
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
entry: entry:
%chan.op = alloca %runtime.channelOp, align 8 %chan.op = alloca %runtime.channelOp, align 8
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3 call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
ret void ret void
} }
@@ -65,9 +65,9 @@ entry:
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
entry: entry:
%chan.op = alloca %runtime.channelOp, align 8 %chan.op = alloca %runtime.channelOp, align 8
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3 %0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
ret void ret void
} }
@@ -77,7 +77,7 @@ entry:
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8 %select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.send.value = alloca i32, align 4 %select.send.value = alloca i32, align 4
store i32 1, ptr %select.send.value, align 4 store i32 1, ptr %select.send.value, align 4
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %select.states.alloca) call void @llvm.lifetime.start.p0(ptr nonnull %select.states.alloca)
store ptr %ch1, ptr %select.states.alloca, align 4 store ptr %ch1, ptr %select.states.alloca, align 4
%select.states.alloca.repack1 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4 %select.states.alloca.repack1 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4
store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4 store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4
@@ -86,7 +86,7 @@ entry:
%.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12 %.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12
store ptr null, ptr %.repack3, align 4 store ptr null, ptr %.repack3, align 4
%select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #3 %select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #3
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca) call void @llvm.lifetime.end.p0(ptr nonnull %select.states.alloca)
%1 = extractvalue { i32, i1 } %select.result, 0 %1 = extractvalue { i32, i1 } %select.result, 0
%2 = icmp eq i32 %1, 0 %2 = icmp eq i32 %1, 0
br i1 %2, label %select.done, label %select.next br i1 %2, label %select.done, label %select.next
+2 -2
View File
@@ -278,7 +278,7 @@ entry:
for.body: ; preds = %for.body, %entry for.body: ; preds = %for.body, %entry
%defer.next = load ptr, ptr %deferPtr, align 4 %defer.next = load ptr, ptr %deferPtr, align 4
%defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4 %defer.alloc.call = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 135 to ptr), ptr undef) #4
store i32 0, ptr %defer.alloc.call, align 4 store i32 0, ptr %defer.alloc.call, align 4
%defer.alloc.call.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4 %defer.alloc.call.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4
store ptr %defer.next, ptr %defer.alloc.call.repack1, align 4 store ptr %defer.next, ptr %defer.alloc.call.repack1, align 4
@@ -330,7 +330,7 @@ for.loop: ; preds = %for.body, %entry
for.body: ; preds = %for.loop for.body: ; preds = %for.loop
%defer.next = load ptr, ptr %deferPtr, align 4 %defer.next = load ptr, ptr %deferPtr, align 4
%defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4 %defer.alloc.call = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 135 to ptr), ptr undef) #4
store i32 0, ptr %defer.alloc.call, align 4 store i32 0, ptr %defer.alloc.call, align 4
%defer.alloc.call.repack13 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4 %defer.alloc.call.repack13 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4
store ptr %defer.next, ptr %defer.alloc.call.repack13, align 4 store ptr %defer.next, ptr %defer.alloc.call.repack13, align 4
+1 -1
View File
@@ -131,7 +131,7 @@ entry:
define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, ptr %context) unnamed_addr #1 { define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, ptr %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %stackalloc = alloca i8, align 1
%0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #3 %0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3
store double %v.r, ptr %0, align 8 store double %v.r, ptr %0, align 8
%.repack1 = getelementptr inbounds nuw i8, ptr %0, i32 8 %.repack1 = getelementptr inbounds nuw i8, ptr %0, i32 8
+37
View File
@@ -19,12 +19,49 @@ func Add[T Coord](a, b Point[T]) Point[T] {
} }
} }
func aliasSize[F float32 | float64]() uintptr {
return unsafe.Sizeof(F(0))
}
func aliasSize32() uintptr {
type F = float32
return aliasSize[F]()
}
func aliasSize64() uintptr {
type F = float64
return aliasSize[F]()
}
type aliasMethodResult[F float32 | float64] struct {
Value F
}
type aliasMethodValue[F float32 | float64] struct{}
func (aliasMethodValue[F]) Get() aliasMethodResult[F] {
return aliasMethodResult[F]{}
}
func main() { func main() {
var af, bf Point[float32] var af, bf Point[float32]
Add(af, bf) Add(af, bf)
var ai, bi Point[int] var ai, bi Point[int]
Add(ai, bi) Add(ai, bi)
checkSize(aliasSize32())
checkSize(aliasSize64())
} }
func checkSize(uintptr) func checkSize(uintptr)
func checkBool(bool)
func aliasMethod32(x any) {
type F = float32
_, ok := x.(interface {
Get() aliasMethodResult[F]
})
checkBool(ok)
}
+70 -21
View File
@@ -14,32 +14,62 @@ entry:
ret void ret void
} }
; Function Attrs: nounwind
define hidden i32 @main.aliasSize32(ptr %context) unnamed_addr #1 {
entry:
%0 = call i32 @"main.aliasSize[basic:float32]"(ptr undef)
ret i32 %0
}
; Function Attrs: nounwind
define linkonce_odr hidden i32 @"main.aliasSize[basic:float32]"(ptr %context) unnamed_addr #1 {
entry:
ret i32 4
}
; Function Attrs: nounwind
define hidden i32 @main.aliasSize64(ptr %context) unnamed_addr #1 {
entry:
%0 = call i32 @"main.aliasSize[basic:float64]"(ptr undef)
ret i32 %0
}
; Function Attrs: nounwind
define linkonce_odr hidden i32 @"main.aliasSize[basic:float64]"(ptr %context) unnamed_addr #1 {
entry:
ret i32 8
}
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.main(ptr %context) unnamed_addr #1 { define hidden void @main.main(ptr %context) unnamed_addr #1 {
entry: entry:
%0 = call %"main.Point[float32]" @"main.Add[float32]"(float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, ptr undef) %0 = call %"main.Point[float32]" @"main.Add[basic:float32]"(float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, ptr undef)
%1 = call %"main.Point[int]" @"main.Add[int]"(i32 0, i32 0, i32 0, i32 0, ptr undef) %1 = call %"main.Point[int]" @"main.Add[basic:int]"(i32 0, i32 0, i32 0, i32 0, ptr undef)
%2 = call i32 @main.aliasSize32(ptr undef)
call void @main.checkSize(i32 %2, ptr undef) #4
%3 = call i32 @main.aliasSize64(ptr undef)
call void @main.checkSize(i32 %3, ptr undef) #4
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define linkonce_odr hidden %"main.Point[float32]" @"main.Add[float32]"(float %a.X, float %a.Y, float %b.X, float %b.Y, ptr %context) unnamed_addr #1 { define linkonce_odr hidden %"main.Point[float32]" @"main.Add[basic:float32]"(float %a.X, float %a.Y, float %b.X, float %b.Y, ptr %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %stackalloc = alloca i8, align 1
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #4
store float %a.X, ptr %a, align 4 store float %a.X, ptr %a, align 4
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4 %a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4
store float %a.Y, ptr %a.repack5, align 4 store float %a.Y, ptr %a.repack5, align 4
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #4
store float %b.X, ptr %b, align 4 store float %b.X, ptr %b, align 4
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4 %b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4
store float %b.Y, ptr %b.repack7, align 4 store float %b.Y, ptr %b.repack7, align 4
call void @main.checkSize(i32 4, ptr undef) #3 call void @main.checkSize(i32 4, ptr undef) #4
call void @main.checkSize(i32 8, ptr undef) #3 call void @main.checkSize(i32 8, ptr undef) #4
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #4
br i1 false, label %deref.throw, label %deref.next br i1 false, label %deref.throw, label %deref.next
deref.next: ; preds = %entry deref.next: ; preds = %entry
@@ -86,23 +116,23 @@ declare void @main.checkSize(i32, ptr) #0
declare void @runtime.nilPanic(ptr) #0 declare void @runtime.nilPanic(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define linkonce_odr hidden %"main.Point[int]" @"main.Add[int]"(i32 %a.X, i32 %a.Y, i32 %b.X, i32 %b.Y, ptr %context) unnamed_addr #1 { define linkonce_odr hidden %"main.Point[int]" @"main.Add[basic:int]"(i32 %a.X, i32 %a.Y, i32 %b.X, i32 %b.Y, ptr %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %stackalloc = alloca i8, align 1
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #4
store i32 %a.X, ptr %a, align 4 store i32 %a.X, ptr %a, align 4
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4 %a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4
store i32 %a.Y, ptr %a.repack5, align 4 store i32 %a.Y, ptr %a.repack5, align 4
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #4
store i32 %b.X, ptr %b, align 4 store i32 %b.X, ptr %b, align 4
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4 %b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4
store i32 %b.Y, ptr %b.repack7, align 4 store i32 %b.Y, ptr %b.repack7, align 4
call void @main.checkSize(i32 4, ptr undef) #3 call void @main.checkSize(i32 4, ptr undef) #4
call void @main.checkSize(i32 8, ptr undef) #3 call void @main.checkSize(i32 8, ptr undef) #4
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #4
br i1 false, label %deref.throw, label %deref.next br i1 false, label %deref.throw, label %deref.next
deref.next: ; preds = %entry deref.next: ; preds = %entry
@@ -141,7 +171,26 @@ deref.throw: ; preds = %store.next, %deref.
unreachable unreachable
} }
declare void @main.checkBool(i1, ptr) #0
; Function Attrs: nounwind
define hidden void @main.aliasMethod32(ptr %x.typecode, ptr %x.value, ptr %context) unnamed_addr #1 {
entry:
%0 = call i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[basic:float32]}}.$typeassert"(ptr %x.typecode) #4
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
call void @main.checkBool(i1 %0, ptr undef) #4
ret void
typeassert.ok: ; preds = %entry
br label %typeassert.next
}
declare i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[basic:float32]}}.$typeassert"(ptr) #3
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #2 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #3 = { nounwind } attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Get:func:{}{named:main.aliasMethodResult[basic:float32]}" }
attributes #4 = { nounwind }
+24
View File
@@ -0,0 +1,24 @@
package main
// genericMethod has both a regular method and a "generic method": a method
// with its own type parameter, independent of any type parameter on the
// receiver. This is a Go 1.27 feature (see math/rand/v2.Rand.N for a
// real-world example). Boxing such a value into an interface must not try to
// encode the generic method's type-parameterized signature into a type code.
type genericMethod struct{}
func (t genericMethod) Regular(n int) int { return n }
func (t genericMethod) GenericParam[X int | int64](n X) X { return n }
// onlyGenericMethod's only method is generic, so its runtime type must have
// an empty method set (hasMethodSet must become false, not just numMethods).
type onlyGenericMethod struct{}
func (t onlyGenericMethod) GenericParam[X int | int64](n X) X { return n }
func useGenericMethods() (any, any) {
var g genericMethod
var o onlyGenericMethod
return any(g), any(o)
}
+67
View File
@@ -0,0 +1,67 @@
; ModuleID = 'go1.27.go'
source_filename = "go1.27.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%runtime.structField = type { ptr, ptr }
%runtime._interface = type { ptr, ptr }
@"reflect/types.signature:Regular:func:{basic:int}{basic:int}" = linkonce_odr constant i8 0, align 1
@"reflect/types.type:named:main.genericMethod" = linkonce_odr constant { ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [19 x i8] } { ptr @"named:main.genericMethod$methodset", i8 122, i16 -32767, ptr getelementptr ({ ptr, i8, i16, ptr, { i32, [1 x ptr] } }, ptr @"reflect/types.type:pointer:named:main.genericMethod", i32 0, i32 1), ptr @"reflect/types.type:struct:{}", ptr @"reflect/types.type.pkgpath:main", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Regular:func:{basic:int}{basic:int}"] }, [19 x i8] c"main.genericMethod\00" }, align 4
@"reflect/types.type.pkgpath:main" = linkonce_odr unnamed_addr constant [5 x i8] c"main\00", align 1
@"reflect/types.type:pointer:named:main.genericMethod" = linkonce_odr constant { ptr, i8, i16, ptr, { i32, [1 x ptr] } } { ptr @"pointer:named:main.genericMethod$methodset", i8 -43, i16 -32767, ptr getelementptr ({ ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [19 x i8] }, ptr @"reflect/types.type:named:main.genericMethod", i32 0, i32 1), { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Regular:func:{basic:int}{basic:int}"] } }, align 4
@"reflect/methods.Regular:func:{basic:int}{basic:int}" = linkonce_odr constant i8 0, align 1
@"main$string" = internal unnamed_addr constant [18 x i8] c"main.genericMethod", align 1
@"main$string.1" = internal unnamed_addr constant [7 x i8] c"Regular", align 1
@"pointer:named:main.genericMethod$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Regular:func:{basic:int}{basic:int}"], { ptr } { ptr @"(*main.genericMethod).Regular" } }
@"reflect/types.type:struct:{}" = linkonce_odr constant { i8, i16, ptr, ptr, i32, i16, [0 x %runtime.structField] } { i8 90, i16 0, ptr @"reflect/types.type:pointer:struct:{}", ptr @"reflect/types.type.pkgpath.empty", i32 0, i16 0, [0 x %runtime.structField] zeroinitializer }, align 4
@"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1
@"reflect/types.type:pointer:struct:{}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:struct:{}" }, align 4
@"named:main.genericMethod$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Regular:func:{basic:int}{basic:int}"], { ptr } { ptr @"(main.genericMethod).Regular$invoke" } }
@"reflect/types.type:named:main.onlyGenericMethod" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [23 x i8] } { i8 122, i16 0, ptr @"reflect/types.type:pointer:named:main.onlyGenericMethod", ptr @"reflect/types.type:struct:{}", ptr @"reflect/types.type.pkgpath:main", [23 x i8] c"main.onlyGenericMethod\00" }, align 4
@"reflect/types.type:pointer:named:main.onlyGenericMethod" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:main.onlyGenericMethod" }, align 4
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #0
; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden i32 @"(main.genericMethod).Regular"(i32 %n, ptr %context) unnamed_addr #1 {
entry:
ret i32 %n
}
; Function Attrs: nounwind
define hidden { %runtime._interface, %runtime._interface } @main.useGenericMethods(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull getelementptr inbounds nuw (i8, ptr @"reflect/types.type:named:main.genericMethod", i32 4), ptr nonnull %stackalloc, ptr undef) #2
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #2
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:named:main.onlyGenericMethod", ptr nonnull %stackalloc, ptr undef) #2
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #2
ret { %runtime._interface, %runtime._interface } { %runtime._interface { ptr getelementptr ({ ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [19 x i8] }, ptr @"reflect/types.type:named:main.genericMethod", i32 0, i32 1), ptr null }, %runtime._interface { ptr @"reflect/types.type:named:main.onlyGenericMethod", ptr null } }
}
; Function Attrs: nounwind
define linkonce_odr hidden i32 @"(*main.genericMethod).Regular"(ptr %t, i32 %n, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr %t, ptr nonnull %stackalloc, ptr undef) #2
%0 = call i32 @"(main.genericMethod).Regular"(i32 %n, ptr undef)
ret i32 %0
}
; Function Attrs: nounwind
define linkonce_odr i32 @"(main.genericMethod).Regular$invoke"(ptr %0, i32 %1, ptr %2) unnamed_addr #1 {
entry:
%ret = call i32 @"(main.genericMethod).Regular"(i32 %1, ptr %2)
ret i32 %ret
}
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #2 = { nounwind }
+4 -4
View File
@@ -60,7 +60,7 @@ define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #0
entry: entry:
%n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #11 %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #11
store i32 3, ptr %n, align 4 store i32 3, ptr %n, align 4
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #11 %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 133 to ptr), ptr undef) #11
store i32 5, ptr %0, align 4 store i32 5, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4
store ptr %n, ptr %1, align 4 store ptr %n, ptr %1, align 4
@@ -102,7 +102,7 @@ declare void @runtime.printunlock(ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #0 { define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #0 {
entry: entry:
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #11 %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 391 to ptr), ptr undef) #11
store i32 5, ptr %0, align 4 store i32 5, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4
store ptr %fn.context, ptr %1, align 4 store ptr %fn.context, ptr %1, align 4
@@ -157,7 +157,7 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #0 { define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #0 {
entry: entry:
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #11 %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 713 to ptr), ptr undef) #11
store ptr %itf.value, ptr %0, align 4 store ptr %itf.value, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4
store ptr @"main$string", ptr %1, align 4 store ptr @"main$string", ptr %1, align 4
@@ -195,6 +195,6 @@ attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb
attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" } attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" }
attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
attributes #9 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } attributes #9 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print:func:{basic:string}{}" "tinygo-methods"="reflect/methods.Print:func:{basic:string}{}" }
attributes #10 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } attributes #10 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
attributes #11 = { nounwind } attributes #11 = { nounwind }
+10 -10
View File
@@ -22,14 +22,14 @@ entry:
declare void @main.regularFunction(i32, ptr) #0 declare void @main.regularFunction(i32, ptr) #0
declare void @runtime.deadlock(ptr) #0 declare void @runtime.exitGoroutine(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define linkonce_odr void @"main.regularFunction$gowrapper"(ptr %0) unnamed_addr #2 { define linkonce_odr void @"main.regularFunction$gowrapper"(ptr %0) unnamed_addr #2 {
entry: entry:
%unpack.int = ptrtoint ptr %0 to i32 %unpack.int = ptrtoint ptr %0 to i32
call void @main.regularFunction(i32 %unpack.int, ptr undef) #11 call void @main.regularFunction(i32 %unpack.int, ptr undef) #11
call void @runtime.deadlock(ptr undef) #11 call void @runtime.exitGoroutine(ptr undef) #11
unreachable unreachable
} }
@@ -53,7 +53,7 @@ define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(ptr %0) unn
entry: entry:
%unpack.int = ptrtoint ptr %0 to i32 %unpack.int = ptrtoint ptr %0 to i32
call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, ptr undef) call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, ptr undef)
call void @runtime.deadlock(ptr undef) #11 call void @runtime.exitGoroutine(ptr undef) #11
unreachable unreachable
} }
@@ -66,7 +66,7 @@ entry:
store i32 3, ptr %n, align 4 store i32 3, ptr %n, align 4
call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #11 call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #11
call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #11 call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #11
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #11 %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 133 to ptr), ptr undef) #11
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
store i32 5, ptr %0, align 4 store i32 5, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4
@@ -96,7 +96,7 @@ entry:
%2 = getelementptr inbounds nuw i8, ptr %0, i32 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 4
%3 = load ptr, ptr %2, align 4 %3 = load ptr, ptr %2, align 4
call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3) call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3)
call void @runtime.deadlock(ptr undef) #11 call void @runtime.exitGoroutine(ptr undef) #11
unreachable unreachable
} }
@@ -110,7 +110,7 @@ declare void @runtime.printunlock(ptr) #0
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 { define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %stackalloc = alloca i8, align 1
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #11 %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 391 to ptr), ptr undef) #11
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
store i32 5, ptr %0, align 4 store i32 5, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4
@@ -130,7 +130,7 @@ entry:
%4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8
%5 = load ptr, ptr %4, align 4 %5 = load ptr, ptr %4, align 4
call void %5(i32 %1, ptr %3) #11 call void %5(i32 %1, ptr %3) #11
call void @runtime.deadlock(ptr undef) #11 call void @runtime.exitGoroutine(ptr undef) #11
unreachable unreachable
} }
@@ -167,7 +167,7 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #0
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %stackalloc = alloca i8, align 1
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #11 %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 713 to ptr), ptr undef) #11
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
store ptr %itf.value, ptr %0, align 4 store ptr %itf.value, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4
@@ -193,7 +193,7 @@ entry:
%6 = getelementptr inbounds nuw i8, ptr %0, i32 12 %6 = getelementptr inbounds nuw i8, ptr %0, i32 12
%7 = load ptr, ptr %6, align 4 %7 = load ptr, ptr %6, align 4
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #11 call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #11
call void @runtime.deadlock(ptr undef) #11 call void @runtime.exitGoroutine(ptr undef) #11
unreachable unreachable
} }
@@ -206,6 +206,6 @@ attributes #5 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+cal
attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper" } attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper" }
attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
attributes #9 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } attributes #9 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print:func:{basic:string}{}" "tinygo-methods"="reflect/methods.Print:func:{basic:string}{}" }
attributes #10 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } attributes #10 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
attributes #11 = { nounwind } attributes #11 = { nounwind }
+4 -4
View File
@@ -131,8 +131,8 @@ declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #2 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error() string" } attributes #2 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error:func:{}{basic:string}" }
attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String() string" } attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String:func:{}{basic:string}" }
attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" } attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo:func:{basic:int}{basic:uint8}" "tinygo-methods"="reflect/methods.String:func:{}{basic:string}; main.$methods.foo:func:{basic:int}{basic:uint8}" }
attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" } attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error:func:{}{basic:string}" "tinygo-methods"="reflect/methods.Error:func:{}{basic:string}" }
attributes #6 = { nounwind } attributes #6 = { nounwind }
+20
View File
@@ -0,0 +1,20 @@
package main
type largeOptimizedValue [1025]byte
type mixedLargeOptimizedValue struct {
value [1025]byte
any any
}
func makeLargeOptimizedValue() largeOptimizedValue {
return largeOptimizedValue{}
}
func readLargeOptimizedValue(value largeOptimizedValue) byte {
return value[len(value)-1]
}
func readMixedLargeOptimizedValue(value mixedLargeOptimizedValue) byte {
return value.value[len(value.value)-1]
}
+120
View File
@@ -0,0 +1,120 @@
package main
type largeValue [1025]byte
type largeStruct struct {
value [1025]byte
ptr *byte
}
type largeInterface interface {
makeLargeValue() largeValue
readLargeValue(largeValue) byte
}
type largeReceiver largeValue
func makeLargeValue(value byte) largeValue {
var result largeValue
result[len(result)-1] = value
return result
}
func makeZeroLargeValue() largeValue {
return largeValue{}
}
func passZeroLargeValue() byte {
return readLargeValue(largeValue{})
}
func readLargeValue(value largeValue) byte {
return value[len(value)-1]
}
func useLargeValue() byte {
return readLargeValue(makeLargeValue(42))
}
func useLargeFunctionValue(fn func(largeValue) byte) byte {
return fn(makeLargeValue(42))
}
func (receiver largeReceiver) makeLargeValue() largeValue {
return largeValue(receiver)
}
func (receiver largeReceiver) readLargeValue(value largeValue) byte {
return value[len(value)-1]
}
func useLargeInterface(value largeInterface) byte {
return value.readLargeValue(value.makeLargeValue())
}
func deferLargeValue(value largeValue) {
defer readLargeValue(value)
}
func goLargeValue(value largeValue) {
go readLargeValue(value)
}
func makeLargeResults(value byte) (largeValue, byte) {
return makeLargeValue(value), value
}
func makeTwoLargeResults(value byte) (largeValue, largeValue) {
return makeLargeValue(value), makeLargeValue(value + 1)
}
func makeMixedLargeResults(value byte) (largeValue, byte, largeValue) {
return makeLargeValue(value), value + 1, makeLargeValue(value + 2)
}
func chooseLargeValue(flag bool) largeValue {
value := makeLargeValue(1)
if flag {
value = makeLargeValue(42)
}
return value
}
func makePointerLargeValue(value *byte) largeStruct {
return largeStruct{ptr: value}
}
func assertLargeValue(value any) byte {
large, ok := value.(largeValue)
if !ok {
return 0
}
return large[len(large)-1]
}
func useLargeMap(key, value largeValue) byte {
values := map[largeValue]largeValue{key: value}
result, ok := values[key]
if !ok {
return 0
}
return result[len(result)-1]
}
func useLargeChannel(ch chan largeValue, value largeValue) byte {
ch <- value
result, ok := <-ch
if !ok {
return 0
}
return result[len(result)-1]
}
func selectLargeChannel(ch chan largeValue, value largeValue) byte {
select {
case ch <- value:
return 0
case result := <-ch:
return result[len(result)-1]
}
}
+498
View File
@@ -0,0 +1,498 @@
; ModuleID = 'large.go'
source_filename = "large.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%runtime._string = type { ptr, i32 }
%runtime.channelOp = type { ptr, ptr, i32, ptr }
%runtime.chanSelectState = type { ptr, ptr }
@"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002" = linkonce_odr unnamed_addr constant { i32, [33 x i8] } { i32 258, [33 x i8] c"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\02" }
@"reflect/types.typeid:named:main.largeValue" = external constant i8
@llvm.used = appending global [15 x ptr] [ptr @"(main.largeReceiver).makeLargeValue", ptr @"(main.largeReceiver).readLargeValue", ptr @main.makeLargeValue, ptr @main.makeZeroLargeValue, ptr @main.readLargeValue, ptr @main.deferLargeValue, ptr @main.goLargeValue, ptr @main.makeLargeResults, ptr @main.makeTwoLargeResults, ptr @main.makeMixedLargeResults, ptr @main.chooseLargeValue, ptr @main.makePointerLargeValue, ptr @main.useLargeMap, ptr @main.useLargeChannel, ptr @main.selectLargeChannel]
@"main$string" = internal unnamed_addr constant [31 x i8] c"blocking select matched no case", align 1
@"main$pack" = internal unnamed_addr constant { %runtime._string } { %runtime._string { ptr @"main$string", i32 31 } }
@"reflect/types.type:basic:string" = linkonce_odr constant { i8, ptr } { i8 81, ptr @"reflect/types.type:pointer:basic:string" }, align 4
@"reflect/types.type:pointer:basic:string" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:string" }, align 4
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #0
; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @"(main.largeReceiver).makeLargeValue"(ptr dereferenceable_or_null(1025) %return, ptr readonly dereferenceable_or_null(1025) %receiver, ptr %context) unnamed_addr #1 {
entry:
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %receiver, i32 1025, i1 false)
ret void
}
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
declare void @llvm.memcpy.p0.p0.i32(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i32, i1 immarg) #2
; Function Attrs: nounwind
define hidden i8 @"(main.largeReceiver).readLargeValue"(ptr readonly dereferenceable_or_null(1025) %receiver, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %value1, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false)
%0 = getelementptr inbounds nuw i8, ptr %value1, i32 1024
%1 = load i8, ptr %0, align 1
ret i8 %1
}
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #3
; Function Attrs: nounwind
define hidden void @main.makeLargeValue(ptr dereferenceable_or_null(1025) %return, i8 %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
%0 = getelementptr inbounds nuw i8, ptr %result, i32 1024
store i8 %value, ptr %0, align 1
%1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %1, ptr noundef nonnull align 1 dereferenceable(1025) %result, i32 1025, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %1, i32 1025, i1 false)
ret void
}
; Function Attrs: nounwind
define hidden void @main.makeZeroLargeValue(ptr dereferenceable_or_null(1025) %return, ptr %context) unnamed_addr #1 {
entry:
store [1025 x i8] zeroinitializer, ptr %return, align 1
ret void
}
; Function Attrs: nounwind
define hidden i8 @main.passZeroLargeValue(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%"main.largeValue{}:main.largeValue" = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %"main.largeValue{}:main.largeValue", ptr nonnull %stackalloc, ptr undef) #9
store [1025 x i8] zeroinitializer, ptr %"main.largeValue{}:main.largeValue", align 1
%0 = call i8 @main.readLargeValue(ptr nonnull %"main.largeValue{}:main.largeValue", ptr undef)
ret i8 %0
}
; Function Attrs: nounwind
define hidden i8 @main.readLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %value1, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false)
%0 = getelementptr inbounds nuw i8, ptr %value1, i32 1024
%1 = load i8, ptr %0, align 1
ret i8 %1
}
; Function Attrs: nounwind
define hidden i8 @main.useLargeValue(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 42, ptr undef)
%0 = call i8 @main.readLargeValue(ptr nonnull %call.result, ptr undef)
ret i8 %0
}
; Function Attrs: nounwind
define hidden i8 @main.useLargeFunctionValue(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 42, ptr undef)
%0 = icmp eq ptr %fn.funcptr, null
br i1 %0, label %fpcall.throw, label %fpcall.next
fpcall.next: ; preds = %entry
%1 = call i8 %fn.funcptr(ptr nonnull %call.result, ptr %fn.context) #9
ret i8 %1
fpcall.throw: ; preds = %entry
call void @runtime.nilPanic(ptr undef) #9
unreachable
}
declare void @runtime.nilPanic(ptr) #0
; Function Attrs: nounwind
define hidden i8 @main.useLargeInterface(ptr %value.typecode, ptr %value.value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr nonnull %call.result, ptr %value.value, ptr %value.typecode, ptr undef) #9
%0 = call i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr %value.value, ptr nonnull %call.result, ptr %value.typecode, ptr undef) #9
ret i8 %0
}
declare void @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr, ptr, ptr, ptr) #4
declare i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr, ptr, ptr, ptr) #5
; Function Attrs: nounwind
define hidden void @main.deferLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%defer.alloca = alloca { i32, ptr, ptr }, align 8
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull %defer.alloca, ptr nonnull %stackalloc, ptr undef) #9
store i32 0, ptr %defer.alloca, align 4
%defer.alloca.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
store ptr null, ptr %defer.alloca.repack1, align 4
%defer.alloca.repack3 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 8
store ptr %value, ptr %defer.alloca.repack3, align 4
store ptr %defer.alloca, ptr %deferPtr, align 4
br label %rundefers.block
rundefers.after: ; preds = %rundefers.end
ret void
rundefers.block: ; preds = %entry
br label %rundefers.loophead
rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block
%0 = load ptr, ptr %deferPtr, align 4
%stackIsNil = icmp eq ptr %0, null
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
rundefers.loop: ; preds = %rundefers.loophead
%stack.next.gep = getelementptr inbounds nuw i8, ptr %0, i32 4
%stack.next = load ptr, ptr %stack.next.gep, align 4
store ptr %stack.next, ptr %deferPtr, align 4
%callback = load i32, ptr %0, align 4
switch i32 %callback, label %rundefers.default [
i32 0, label %rundefers.callback0
]
rundefers.callback0: ; preds = %rundefers.loop
%gep = getelementptr inbounds nuw i8, ptr %0, i32 8
%param = load ptr, ptr %gep, align 4
%1 = call i8 @main.readLargeValue(ptr %param, ptr undef)
br label %rundefers.loophead
rundefers.default: ; preds = %rundefers.loop
unreachable
rundefers.end: ; preds = %rundefers.loophead
br label %rundefers.after
recover: ; No predecessors!
ret void
}
; Function Attrs: nounwind
define hidden void @main.goLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%go.param = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %go.param, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %go.param, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false)
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.readLargeValue$gowrapper" to i32), ptr nonnull %go.param, i32 65536, ptr undef) #9
ret void
}
declare void @runtime.exitGoroutine(ptr) #0
; Function Attrs: nounwind
define linkonce_odr void @"main.readLargeValue$gowrapper"(ptr %0) unnamed_addr #6 {
entry:
%1 = call i8 @main.readLargeValue(ptr %0, ptr undef)
call void @runtime.exitGoroutine(ptr undef) #9
unreachable
}
declare void @"internal/task.start"(i32, ptr, i32, ptr) #0
; Function Attrs: nounwind
define hidden void @main.makeLargeResults(ptr dereferenceable_or_null(1026) %return, i8 %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false)
%0 = getelementptr inbounds nuw i8, ptr %return, i32 1025
store i8 %value, ptr %0, align 1
ret void
}
; Function Attrs: nounwind
define hidden void @main.makeTwoLargeResults(ptr dereferenceable_or_null(2050) %return, i8 %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef)
%0 = add i8 %value, 1
%call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result1, i8 %0, ptr undef)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false)
%1 = getelementptr inbounds nuw i8, ptr %return, i32 1025
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %1, ptr noundef nonnull align 1 dereferenceable(1025) %call.result1, i32 1025, i1 false)
ret void
}
; Function Attrs: nounwind
define hidden void @main.makeMixedLargeResults(ptr dereferenceable_or_null(2051) %return, i8 %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef)
%0 = add i8 %value, 1
%1 = add i8 %value, 2
%call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result1, i8 %1, ptr undef)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false)
%2 = getelementptr inbounds nuw i8, ptr %return, i32 1025
store i8 %0, ptr %2, align 1
%3 = getelementptr inbounds nuw i8, ptr %return, i32 1026
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %3, ptr noundef nonnull align 1 dereferenceable(1025) %call.result1, i32 1025, i1 false)
ret void
}
; Function Attrs: nounwind
define hidden void @main.chooseLargeValue(ptr dereferenceable_or_null(1025) %return, i1 %flag, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 1, ptr undef)
br i1 %flag, label %if.then, label %if.done
if.then: ; preds = %entry
%call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result1, i8 42, ptr undef)
br label %if.done
if.done: ; preds = %if.then, %entry
%0 = phi ptr [ %call.result, %entry ], [ %call.result1, %if.then ]
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %0, i32 1025, i1 false)
ret void
}
; Function Attrs: nounwind
define hidden void @main.makePointerLargeValue(ptr dereferenceable_or_null(1032) %return, ptr dereferenceable_or_null(1) %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%complit = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #9
br i1 false, label %store.throw, label %store.next
store.next: ; preds = %entry
%0 = getelementptr inbounds nuw i8, ptr %complit, i32 1028
store ptr %value, ptr %0, align 4
%1 = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 4 dereferenceable(1032) %1, ptr noundef nonnull align 4 dereferenceable(1032) %complit, i32 1032, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1032) %return, ptr noundef nonnull align 4 dereferenceable(1032) %1, i32 1032, i1 false)
ret void
store.throw: ; preds = %entry
unreachable
}
; Function Attrs: nounwind
define hidden i8 @main.assertLargeValue(ptr %value.typecode, ptr %value.value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%large = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %large, ptr nonnull %stackalloc, ptr undef) #9
%typecode = call i1 @runtime.typeAssert(ptr %value.typecode, ptr nonnull @"reflect/types.typeid:named:main.largeValue", ptr undef) #9
%typeassert.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %typeassert.result, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memset.p0.i32(ptr noundef nonnull align 1 dereferenceable(1026) %typeassert.result, i8 0, i32 1026, i1 false)
br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
%0 = getelementptr inbounds nuw i8, ptr %typeassert.result, i32 1025
store i1 %typecode, ptr %0, align 1
%t2 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %t2, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t2, ptr noundef nonnull align 1 dereferenceable(1025) %typeassert.result, i32 1025, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %large, ptr noundef nonnull align 1 dereferenceable(1025) %t2, i32 1025, i1 false)
br i1 %typecode, label %if.done, label %if.then
typeassert.ok: ; preds = %entry
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %typeassert.result, ptr noundef nonnull align 1 dereferenceable(1025) %value.value, i32 1025, i1 false)
br label %typeassert.next
if.done: ; preds = %typeassert.next
%1 = getelementptr inbounds nuw i8, ptr %large, i32 1024
%2 = load i8, ptr %1, align 1
ret i8 %2
if.then: ; preds = %typeassert.next
ret i8 0
}
declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #0
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write)
declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #7
; Function Attrs: nounwind
define hidden i8 @main.useLargeMap(ptr readonly dereferenceable_or_null(1025) %key, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%0 = call ptr @runtime.hashmapMakeGeneric(i32 1025, i32 1025, i32 1, ptr null, ptr nonnull @runtime.hash32, ptr null, ptr nonnull @runtime.memequal, ptr undef) #9
call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #9
call void @runtime.hashmapBinarySet(ptr %0, ptr %key, ptr %value, ptr undef) #9
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
%hashmap.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %hashmap.result, ptr nonnull %stackalloc, ptr undef) #9
%1 = call i1 @runtime.hashmapBinaryGet(ptr %0, ptr %key, ptr nonnull %hashmap.result, i32 1025, ptr undef) #9
%2 = getelementptr inbounds nuw i8, ptr %hashmap.result, i32 1025
store i1 %1, ptr %2, align 1
%t3 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %t3, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t3, ptr noundef nonnull align 1 dereferenceable(1025) %hashmap.result, i32 1025, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %t3, i32 1025, i1 false)
%3 = getelementptr inbounds nuw i8, ptr %hashmap.result, i32 1025
%t4 = load i1, ptr %3, align 1
br i1 %t4, label %if.done, label %if.then
if.done: ; preds = %entry
%4 = getelementptr inbounds nuw i8, ptr %result, i32 1024
%5 = load i8, ptr %4, align 1
ret i8 %5
if.then: ; preds = %entry
ret i8 0
}
declare i32 @runtime.hash32(ptr, i32, i32, ptr) #0
declare i1 @runtime.memequal(ptr, ptr, i32, ptr) #0
declare ptr @runtime.hashmapMakeGeneric(i32, i32, i32, ptr, ptr, ptr, ptr, ptr) #0
declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(48), ptr, ptr, ptr) #0
declare i1 @runtime.hashmapBinaryGet(ptr dereferenceable_or_null(48), ptr, ptr, i32, ptr) #0
; Function Attrs: nounwind
define hidden i8 @main.useLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%chan.op1 = alloca %runtime.channelOp, align 8
%chan.op = alloca %runtime.channelOp, align 8
%stackalloc = alloca i8, align 1
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
call void @runtime.chanSend(ptr %ch, ptr %value, ptr nonnull %chan.op, ptr undef) #9
call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
%chan.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %chan.result, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op1)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.result, ptr nonnull %chan.op1, ptr undef) #9
%1 = getelementptr inbounds nuw i8, ptr %chan.result, i32 1025
store i1 %0, ptr %1, align 1
call void @llvm.lifetime.end.p0(ptr nonnull %chan.op1)
%t2 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %t2, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t2, ptr noundef nonnull align 1 dereferenceable(1025) %chan.result, i32 1025, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %t2, i32 1025, i1 false)
%2 = getelementptr inbounds nuw i8, ptr %chan.result, i32 1025
%t3 = load i1, ptr %2, align 1
br i1 %t3, label %if.done, label %if.then
if.done: ; preds = %entry
%3 = getelementptr inbounds nuw i8, ptr %result, i32 1024
%4 = load i8, ptr %3, align 1
ret i8 %4
if.then: ; preds = %entry
ret i8 0
}
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(ptr nocapture) #8
declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(ptr nocapture) #8
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
; Function Attrs: nounwind
define hidden i8 @main.selectLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%select.block.alloca = alloca [2 x %runtime.channelOp], align 8
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.recvbuf.alloca = alloca [1025 x i8], align 1
%stackalloc = alloca i8, align 1
call void @llvm.lifetime.start.p0(ptr nonnull %select.recvbuf.alloca)
call void @llvm.lifetime.start.p0(ptr nonnull %select.states.alloca)
store ptr %ch, ptr %select.states.alloca, align 4
%select.states.alloca.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4
store ptr %value, ptr %select.states.alloca.repack3, align 4
%0 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 8
store ptr %ch, ptr %0, align 4
%.repack5 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12
store ptr null, ptr %.repack5, align 4
call void @llvm.lifetime.start.p0(ptr nonnull %select.block.alloca)
%select.result = call { i32, i1 } @runtime.chanSelect(ptr nonnull %select.recvbuf.alloca, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr nonnull %select.block.alloca, i32 2, i32 2, ptr undef) #9
call void @llvm.lifetime.end.p0(ptr nonnull %select.block.alloca)
call void @llvm.lifetime.end.p0(ptr nonnull %select.states.alloca)
call void @runtime.trackPointer(ptr nonnull %select.recvbuf.alloca, ptr nonnull %stackalloc, ptr undef) #9
%1 = extractvalue { i32, i1 } %select.result, 0
%2 = icmp eq i32 %1, 0
br i1 %2, label %select.body, label %select.next
select.body: ; preds = %entry
ret i8 0
select.next: ; preds = %entry
%3 = icmp eq i32 %1, 1
br i1 %3, label %select.body1, label %select.next2
select.body1: ; preds = %select.next
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
%select.received = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %select.received, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %select.received, ptr noundef nonnull align 1 dereferenceable(1025) %select.recvbuf.alloca, i32 1025, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %select.received, i32 1025, i1 false)
%4 = getelementptr inbounds nuw i8, ptr %result, i32 1024
%5 = load i8, ptr %4, align 1
ret i8 %5
select.next2: ; preds = %select.next
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull %stackalloc, ptr undef) #9
call void @runtime.trackPointer(ptr nonnull @"main$pack", ptr nonnull %stackalloc, ptr undef) #9
call void @runtime._panic(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull @"main$pack", ptr undef) #9
unreachable
}
declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #0
declare void @runtime._panic(ptr, ptr, ptr) #0
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
attributes #3 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-indirect-result"="true" "tinygo-invoke"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" }
attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" }
attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.readLargeValue" }
attributes #7 = { nocallback nofree nounwind willreturn memory(argmem: write) }
attributes #8 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
attributes #9 = { nounwind }
+6
View File
@@ -35,6 +35,12 @@ func withLinkageName1() {
//go:linkname withLinkageName2 somepkg.someFunction2 //go:linkname withLinkageName2 somepkg.someFunction2
func withLinkageName2() func withLinkageName2()
// Import a function from a different package using go:linknamestd (the standard
// library variant of go:linkname introduced in Go 1.27).
//
//go:linknamestd withLinkageNameStd somepkg.someFunctionStd
func withLinkageNameStd()
// Function has an 'inline hint', similar to the inline keyword in C. // Function has an 'inline hint', similar to the inline keyword in C.
// //
//go:inline //go:inline
+4 -2
View File
@@ -33,6 +33,8 @@ entry:
declare void @somepkg.someFunction2(ptr) #0 declare void @somepkg.someFunction2(ptr) #0
declare void @somepkg.someFunctionStd(ptr) #0
; Function Attrs: inlinehint nounwind ; Function Attrs: inlinehint nounwind
define hidden void @main.inlineFunc(ptr %context) unnamed_addr #3 { define hidden void @main.inlineFunc(ptr %context) unnamed_addr #3 {
entry: entry:
@@ -48,12 +50,12 @@ entry:
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.useGeneric(ptr %context) unnamed_addr #1 { define hidden void @main.useGeneric(ptr %context) unnamed_addr #1 {
entry: entry:
call void @"main.noinlineGenericFunc[int8]"(ptr undef) call void @"main.noinlineGenericFunc[basic:int8]"(ptr undef)
ret void ret void
} }
; Function Attrs: noinline nounwind ; Function Attrs: noinline nounwind
define linkonce_odr hidden void @"main.noinlineGenericFunc[int8]"(ptr %context) unnamed_addr #4 { define linkonce_odr hidden void @"main.noinlineGenericFunc[basic:int8]"(ptr %context) unnamed_addr #4 {
entry: entry:
ret void ret void
} }
+18 -18
View File
@@ -21,23 +21,23 @@ entry:
%0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0 %0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0
%1 = insertvalue %main.hasPadding %0, i32 %s.i, 1 %1 = insertvalue %main.hasPadding %0, i32 %s.i, 1
%2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2 %2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key)
store %main.hasPadding %2, ptr %hashmap.key, align 4 store %main.hasPadding %2, ptr %hashmap.key, align 4
%3 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4 %3 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
%4 = load i32, ptr %hashmap.value, align 4 %4 = load i32, ptr %hashmap.value, align 4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
ret i32 %4 ret i32 %4
} }
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3 declare void @llvm.lifetime.start.p0(ptr nocapture) #3
declare i1 @runtime.hashmapGenericGet(ptr dereferenceable_or_null(48), ptr nocapture, ptr nocapture, i32, ptr) #0 declare i1 @runtime.hashmapGenericGet(ptr dereferenceable_or_null(48), ptr nocapture, ptr nocapture, i32, ptr) #0
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3 declare void @llvm.lifetime.end.p0(ptr nocapture) #3
; Function Attrs: noinline nounwind ; Function Attrs: noinline nounwind
define hidden void @main.testZeroSet(ptr dereferenceable_or_null(48) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 { define hidden void @main.testZeroSet(ptr dereferenceable_or_null(48) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 {
@@ -47,13 +47,13 @@ entry:
%0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0 %0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0
%1 = insertvalue %main.hasPadding %0, i32 %s.i, 1 %1 = insertvalue %main.hasPadding %0, i32 %s.i, 1
%2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2 %2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
store i32 5, ptr %hashmap.value, align 4 store i32 5, ptr %hashmap.value, align 4
call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key)
store %main.hasPadding %2, ptr %hashmap.key, align 4 store %main.hasPadding %2, ptr %hashmap.key, align 4
call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4 call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
ret void ret void
} }
@@ -64,17 +64,17 @@ define hidden i32 @main.testZeroArrayGet(ptr dereferenceable_or_null(48) %m, [2
entry: entry:
%hashmap.key = alloca [2 x %main.hasPadding], align 8 %hashmap.key = alloca [2 x %main.hasPadding], align 8
%hashmap.value = alloca i32, align 4 %hashmap.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key)
%s.elt = extractvalue [2 x %main.hasPadding] %s, 0 %s.elt = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 store %main.hasPadding %s.elt, ptr %hashmap.key, align 4
%hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12 %hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12
%s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4
%0 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4 %0 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
%1 = load i32, ptr %hashmap.value, align 4 %1 = load i32, ptr %hashmap.value, align 4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
ret i32 %1 ret i32 %1
} }
@@ -83,17 +83,17 @@ define hidden void @main.testZeroArraySet(ptr dereferenceable_or_null(48) %m, [2
entry: entry:
%hashmap.key = alloca [2 x %main.hasPadding], align 8 %hashmap.key = alloca [2 x %main.hasPadding], align 8
%hashmap.value = alloca i32, align 4 %hashmap.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
store i32 5, ptr %hashmap.value, align 4 store i32 5, ptr %hashmap.value, align 4
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key)
%s.elt = extractvalue [2 x %main.hasPadding] %s, 0 %s.elt = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 store %main.hasPadding %s.elt, ptr %hashmap.key, align 4
%hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12 %hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12
%s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4
call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4 call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
ret void ret void
} }
+9 -10
View File
@@ -1,6 +1,6 @@
module github.com/tinygo-org/tinygo module github.com/tinygo-org/tinygo
go 1.24.0 go 1.25.0
require ( require (
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139 github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139
@@ -15,22 +15,21 @@ require (
github.com/mgechev/revive v1.3.9 github.com/mgechev/revive v1.3.9
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
github.com/tetratelabs/wazero v1.9.0 github.com/tetratelabs/wazero v1.9.0
go.bug.st/serial v1.6.4 go.bug.st/serial v1.8.0
go.bytecodealliance.org v0.6.2 go.bytecodealliance.org v0.6.2
go.bytecodealliance.org/cm v0.2.2 go.bytecodealliance.org/cm v0.2.2
golang.org/x/net v0.50.0 golang.org/x/net v0.56.0
golang.org/x/sys v0.41.0 golang.org/x/sys v0.47.0
golang.org/x/tools v0.42.0 golang.org/x/tools v0.47.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
tinygo.org/x/espflasher v0.6.1 tinygo.org/x/espflasher v0.8.1
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6 tinygo.org/x/go-llvm v0.0.0-20260721072906-185673ef46a5
) )
require ( require (
github.com/BurntSushi/toml v1.4.0 // indirect github.com/BurntSushi/toml v1.4.0 // indirect
github.com/chavacava/garif v0.1.0 // indirect github.com/chavacava/garif v0.1.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect
github.com/creack/goselect v0.1.2 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/fatih/color v1.17.0 // indirect github.com/fatih/color v1.17.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect github.com/fatih/structtag v1.2.0 // indirect
@@ -48,6 +47,6 @@ require (
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect github.com/ulikunitz/xz v0.5.12 // indirect
github.com/urfave/cli/v3 v3.0.0-beta1 // indirect github.com/urfave/cli/v3 v3.0.0-beta1 // indirect
golang.org/x/mod v0.33.0 // indirect golang.org/x/mod v0.37.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.38.0 // indirect
) )
+18 -20
View File
@@ -8,8 +8,6 @@ github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc
github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -89,30 +87,30 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= go.bug.st/serial v1.8.0 h1:ZtnmN8aYXtPlTghwSvDWPHKBHL9TM6oFDa+KpSn4SQE=
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= go.bug.st/serial v1.8.0/go.mod h1:d0MmS16Qt9b1m06yoYRNUXhRRTJV5Qg2S5EKqQtnayQ=
go.bytecodealliance.org v0.6.2 h1:Jy4u5DVmSkXgsnwojBhJ+AD/YsJsR3VzVnxF0xRCqTQ= go.bytecodealliance.org v0.6.2 h1:Jy4u5DVmSkXgsnwojBhJ+AD/YsJsR3VzVnxF0xRCqTQ=
go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA= go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA=
go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA= go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA=
go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@@ -120,7 +118,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
tinygo.org/x/espflasher v0.6.1 h1:9jfyAP9jGjxF63FQUY2Bml9TFb5fCZYxVgbgR2IjUGs= tinygo.org/x/espflasher v0.8.1 h1:Vp+xA16af9NKVOOSJCcrJHqPSLDCpbfCd/TG3x449ks=
tinygo.org/x/espflasher v0.6.1/go.mod h1:tr5u08HoE67WD5zxJesCiiVF/R1b6Akz3yXwh5zah8U= tinygo.org/x/espflasher v0.8.1/go.mod h1:YLkCtOCz6gdrbueTi6uZbGPSYduTYt62JdNGmiGxgc0=
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6 h1:QSnqFgNV2Ij0T4hM2qKv53fcDAFElxClPjVUZXzYkWU= tinygo.org/x/go-llvm v0.0.0-20260721072906-185673ef46a5 h1:0PKRhM1INWAi7PdIng1BHMPTDaRcOmv1UymdTgZ76Jo=
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= tinygo.org/x/go-llvm v0.0.0-20260721072906-185673ef46a5/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
+17 -5
View File
@@ -140,13 +140,25 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
panic("unknown number of operands") panic("unknown number of operands")
} }
case llvm.Switch: case llvm.Switch:
// A switch is an array of (value, label) pairs, of which the // Compile to an array of (value, label) pairs, of which the
// first one indicates the to-switch value and the default // first one indicates the to-switch value and the default
// label. // label.
numOperands := llvmInst.OperandsCount() //
for i := 0; i < numOperands; i += 2 { // Successor 0 is always the default destination; successors
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i))) // 1..N-1 are the individual cases. This must be read via
inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])}) // GetSwitchCaseValue/Successor rather than raw operands,
// because LLVM 22 stopped exposing switch case values as
// regular instruction operands (only the condition and
// destination-block operands remain).
inst.operands = append(inst.operands,
r.getValue(llvmInst.Operand(0)),
literalValue{uint32(blockIndices[llvmInst.Successor(0).AsValue()])},
)
for i := 1; i < llvmInst.SuccessorsCount(); i++ {
inst.operands = append(inst.operands,
r.getValue(llvmInst.GetSwitchCaseValue(i)),
literalValue{uint32(blockIndices[llvmInst.Successor(i).AsValue()])},
)
} }
case llvm.PHI: case llvm.PHI:
inst.name = llvmInst.Name() inst.name = llvmInst.Name()
+27
View File
@@ -2,6 +2,7 @@ package interp
import ( import (
"os" "os"
"regexp"
"strings" "strings"
"testing" "testing"
"time" "time"
@@ -21,6 +22,7 @@ func TestInterp(t *testing.T) {
"store", "store",
"alloc", "alloc",
"slicedata", "slicedata",
"aggregate",
} { } {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
@@ -92,6 +94,16 @@ func runTest(t *testing.T, pathPrefix string) {
// equal. That means, only relevant lines are compared (excluding comments // equal. That means, only relevant lines are compared (excluding comments
// etc.). // etc.).
func fuzzyEqualIR(s1, s2 string) bool { func fuzzyEqualIR(s1, s2 string) bool {
// 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 to keep a single golden file working across LLVM
// versions.
s1 = normalizeCapturesAttr(s1)
s2 = normalizeCapturesAttr(s2)
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
if len(lines1) != len(lines2) { if len(lines1) != len(lines2) {
@@ -107,6 +119,21 @@ func fuzzyEqualIR(s1, s2 string) bool {
return true return true
} }
// 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\)`)
// 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 // filterIrrelevantIRLines removes lines from the input slice of strings that
// are not relevant in comparing IR. For example, empty lines and comments are // are not relevant in comparing IR. For example, empty lines and comments are
// stripped out. // stripped out.
+3 -5
View File
@@ -928,11 +928,9 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
llvmFn := operands[len(operands)-1] llvmFn := operands[len(operands)-1]
args := operands[:len(operands)-1] args := operands[:len(operands)-1]
for _, op := range operands { for _, op := range operands {
if op.Type().TypeKind() == llvm.PointerTypeKind { err := mem.markExternalStore(op)
err := mem.markExternalStore(op) if err != nil {
if err != nil { return r.errorAt(inst, err)
return r.errorAt(inst, err)
}
} }
} }
result = r.builder.CreateCall(inst.llvmInst.CalledFunctionType(), llvmFn, args, inst.name) result = r.builder.CreateCall(inst.llvmInst.CalledFunctionType(), llvmFn, args, inst.name)
+4 -4
View File
@@ -931,10 +931,10 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
return llvm.Value{}, err return llvm.Value{}, err
} }
} }
if llvmType.StructName() != "" { // Always use ConstNamedStruct to preserve the exact type identity.
return llvm.ConstNamedStruct(llvmType, fields), nil // ConstStruct creates a literal struct type which may differ from an
} // anonymous identified struct even when structurally identical.
return llvmType.Context().ConstStruct(fields, false), nil return llvm.ConstNamedStruct(llvmType, fields), nil
case llvm.ArrayTypeKind: case llvm.ArrayTypeKind:
numElements := llvmType.ArrayLength() numElements := llvmType.ArrayLength()
childType := llvmType.ElementType() childType := llvmType.ElementType()
+27
View File
@@ -0,0 +1,27 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
declare void @externalAggregate({ ptr })
@main.value = global i32 1
@main.result = global i32 0
define void @runtime.initAll() unnamed_addr {
entry:
call void @main.init(ptr undef)
ret void
}
define internal void @main.init(ptr %context) unnamed_addr {
entry:
; The pointer is hidden inside an aggregate argument.
%arg = insertvalue { ptr } undef, ptr @main.value, 0
; This call runs at runtime and may modify @main.value.
call void @externalAggregate({ ptr } %arg)
; Therefore this load must also remain at runtime.
%value = load i32, ptr @main.value
store i32 %value, ptr @main.result
ret void
}
+15
View File
@@ -0,0 +1,15 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.value = global i32 1
@main.result = local_unnamed_addr global i32 0
declare void @externalAggregate({ ptr }) local_unnamed_addr
define void @runtime.initAll() unnamed_addr {
entry:
call void @externalAggregate({ ptr } { ptr @main.value })
%value = load i32, ptr @main.value, align 4
store i32 %value, ptr @main.result, align 4
ret void
}
Submodule
+1
Submodule lib/py32-svd added at 6aa7396c1d
+1
View File
@@ -0,0 +1 @@
2be7242b6a4d59fe89fb43f7d1e7333dc9b307a2
+19 -15
View File
@@ -177,8 +177,15 @@ func Build(pkgName, outpath string, config *compileopts.Config) error {
// Pick a default output path based on the main directory. // Pick a default output path based on the main directory.
outpath = filepath.Base(result.MainDir) + config.DefaultBinaryExtension() outpath = filepath.Base(result.MainDir) + config.DefaultBinaryExtension()
} }
} else if fi, statErr := os.Stat(outpath); statErr == nil && fi.IsDir() {
var name string
if strings.HasSuffix(pkgName, ".go") {
name = filepath.Base(pkgName[:len(pkgName)-3]) + config.DefaultBinaryExtension()
} else {
name = filepath.Base(result.MainDir) + config.DefaultBinaryExtension()
}
outpath = filepath.Join(outpath, name)
} }
if err := os.Rename(result.Binary, outpath); err != nil { if err := os.Rename(result.Binary, outpath); err != nil {
// Moving failed. Do a file copy. // Moving failed. Do a file copy.
inf, err := os.Open(result.Binary) inf, err := os.Open(result.Binary)
@@ -1122,13 +1129,19 @@ const (
jtagReset = "jtag" jtagReset = "jtag"
) )
var progressFunc = func(current, total int) {
pct := float64(current) / float64(total) * 100
bar := int(pct / 2)
fmt.Printf("\r[%-50s] %6.1f%%", strings.Repeat("#", bar)+strings.Repeat(".", 50-bar), pct)
if current >= total {
fmt.Println()
}
}
func flashBinUsingEsp32(port, resetMode, tmppath string, options *compileopts.Options) error { func flashBinUsingEsp32(port, resetMode, tmppath string, options *compileopts.Options) error {
opts := espflasher.DefaultOptions() opts := espflasher.DefaultOptions()
opts.Compress = true opts.Compress = true
opts.Logger = &espflasher.StdoutLogger{W: os.Stdout} opts.Logger = &espflasher.StdoutLogger{W: os.Stdout}
if options.BaudRate != 0 {
opts.FlashBaudRate = options.BaudRate
}
if resetMode == jtagReset { if resetMode == jtagReset {
opts.ResetMode = espflasher.ResetUSBJTAG opts.ResetMode = espflasher.ResetUSBJTAG
@@ -1152,21 +1165,12 @@ func flashBinUsingEsp32(port, resetMode, tmppath string, options *compileopts.Op
return err return err
} }
if err := flasher.EraseFlash(); err != nil { if err := flasher.EraseFlash(progressFunc); err != nil {
return fmt.Errorf("erase failed: %v", err) return fmt.Errorf("erase failed: %v", err)
} }
progress := func(current, total int) {
pct := float64(current) / float64(total) * 100
bar := int(pct / 2)
fmt.Printf("\r[%-50s] %6.1f%%", strings.Repeat("#", bar)+strings.Repeat(".", 50-bar), pct)
if current >= total {
fmt.Println()
}
}
// Flash with progress reporting // Flash with progress reporting
err = flasher.FlashImage(data, offset, progress) err = flasher.FlashImage(data, offset, progressFunc)
if err != nil { if err != nil {
return err return err
} }
+156 -10
View File
@@ -59,6 +59,11 @@ func TestBuild(t *testing.T) {
"cgo/", "cgo/",
"channel.go", "channel.go",
"embed/", "embed/",
"finalizer.go",
"finalizerbits.go",
"finalizeridle.go",
"finalizerinvariants.go",
"finalizerlarge.go",
"float.go", "float.go",
"gc.go", "gc.go",
"generics.go", "generics.go",
@@ -115,7 +120,25 @@ func TestBuild(t *testing.T) {
t.Run("Host", func(t *testing.T) { t.Run("Host", func(t *testing.T) {
t.Parallel() t.Parallel()
runPlatTests(optionsFromTarget("", sema), tests, t) hostOptions := optionsFromTarget("", sema)
runPlatTests(hostOptions, tests, t)
// scheduler.threads needs threadID, which exists only on Linux and Darwin.
// scheduler.none does not link on Windows.
switch runtime.GOOS {
case "darwin", "linux":
for _, scheduler := range []string{"threads", "none"} {
scheduler := scheduler
t.Run("finalizerinvariants.go-gc-conservative-scheduler-"+scheduler, func(t *testing.T) {
t.Parallel()
options := compileopts.Options(hostOptions)
options.GC = "conservative"
options.Scheduler = scheduler
options.Tags = append(append([]string(nil), hostOptions.Tags...), "runtime_asserts")
runTest("finalizerinvariants.go", options, t, nil, nil)
})
}
}
}) })
// Test a few build options. // Test a few build options.
@@ -260,6 +283,21 @@ func TestTimerStopResetRace(t *testing.T) {
runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil) runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil)
} }
func TestESP32QEMU(t *testing.T) {
t.Parallel()
options := optionsFromTarget("esp32-qemu", sema)
emuCheck(t, options)
machines, err := exec.Command("qemu-system-xtensa", "-machine", "help").Output()
if err != nil {
t.Fatal("failed to list qemu-system-xtensa machines:", err)
}
if !regexp.MustCompile(`(?m)^esp32\s`).Match(machines) {
t.Skip("qemu-system-xtensa does not support the ESP32 machine")
}
runTest("print.go", options, t, nil, nil)
}
func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
emuCheck(t, options) emuCheck(t, options)
@@ -273,8 +311,17 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
isWASI := strings.HasPrefix(options.Target, "wasi") isWASI := strings.HasPrefix(options.Target, "wasi")
isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm")) isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm"))
isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu" isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu"
_, goMinor, err := goenv.GetGorootVersion()
if err != nil {
t.Fatal("could not get version:", goMinor)
}
for _, name := range tests { for _, name := range tests {
if name == "goroutines.go" && (spec.Scheduler == "threads" || spec.Scheduler == "cores") {
// This test intentionally checks concurrent scheduling by comparing
// output order, so only run it with non-threaded schedulers.
continue
}
if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") { if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") {
switch name { switch name {
case "timers.go": case "timers.go":
@@ -296,6 +343,11 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
continue continue
} }
} }
if options.Target == "cortex-m-qemu" && goMinor >= 27 && name == "json.go" {
// Go 1.27 jsonv2 exceeds the LM3S6965's 256KiB flash. json.go
// is still covered by larger targets such as riscv-qemu.
continue
}
if options.Target == "simavr" { if options.Target == "simavr" {
// Not all tests are currently supported on AVR. // Not all tests are currently supported on AVR.
// Skip the ones that aren't. // Skip the ones that aren't.
@@ -349,11 +401,38 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
continue continue
} }
} }
if options.Target != "wasm" {
switch name {
case "finalizer.go", "finalizerbits.go", "finalizeridle.go", "finalizerlarge.go":
// These tests require deterministic finalization on target wasm.
// finalizerinvariants.go covers other block GC targets.
continue
}
}
if options.Target == "" && options.GC == "" {
switch name {
case "finalizerinvariants.go":
// Skip the default host GC because it does not implement finalizers.
// Explicit conservative GC variants cover this test.
continue
}
}
if options.Target == "simavr" {
switch name {
case "finalizerinvariants.go":
// Skip because runtime.GC does not return. See the gc.go exclusion above.
continue
}
}
name := name // redefine to avoid race condition name := name // redefine to avoid race condition
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
runTest(name, options, t, nil, nil) testOptions := compileopts.Options(options)
if name == "finalizerinvariants.go" || name == "finalizerlarge.go" {
testOptions.Tags = append(append([]string(nil), options.Tags...), "runtime_asserts")
}
runTest(name, testOptions, t, nil, nil)
}) })
} }
if !strings.HasPrefix(spec.Emulator, "simavr ") { if !strings.HasPrefix(spec.Emulator, "simavr ") {
@@ -513,6 +592,9 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
if config.EmulatorName() == "simavr" { if config.EmulatorName() == "simavr" {
actual = cleanSimAVRTestOutput(actual) actual = cleanSimAVRTestOutput(actual)
} }
if config.EmulatorName() == "qemu-system-xtensa" {
actual = cleanESP32QEMUOutput(actual)
}
if name == "testing.go" { if name == "testing.go" {
// Strip actual time. // Strip actual time.
re := regexp.MustCompile(`\([0-9]\.[0-9][0-9]s\)`) re := regexp.MustCompile(`\([0-9]\.[0-9][0-9]s\)`)
@@ -538,6 +620,18 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
} }
} }
func cleanESP32QEMUOutput(output []byte) []byte {
entryLine := bytes.Index(output, []byte("\nentry "))
if entryLine < 0 {
return output
}
entryLineEnd := bytes.IndexByte(output[entryLine+1:], '\n')
if entryLineEnd < 0 {
return output
}
return output[entryLine+1+entryLineEnd+1:]
}
func cleanSimAVRTestOutput(output []byte) []byte { func cleanSimAVRTestOutput(output []byte) []byte {
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '3', '2', 'm'}, nil) output = bytes.ReplaceAll(output, []byte{0x1b, '[', '3', '2', 'm'}, nil)
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '0', 'm'}, nil) output = bytes.ReplaceAll(output, []byte{0x1b, '[', '0', 'm'}, nil)
@@ -894,6 +988,30 @@ func TestWasmExportJS(t *testing.T) {
} }
} }
func TestWasmExportFinalizersJS(t *testing.T) {
t.Parallel()
tmpdir := t.TempDir()
options := optionsFromTarget("wasm", sema)
options.BuildMode = "c-shared"
buildConfig, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}
result, err := builder.Build("testdata/wasmexport-finalizer.go", ".wasm", tmpdir, buildConfig)
if err != nil {
t.Fatal("failed to build binary:", err)
}
output := &bytes.Buffer{}
cmd := exec.Command("node", "testdata/wasmexport-finalizer.js", result.Binary)
cmd.Stdout = output
cmd.Stderr = output
if err := cmd.Run(); err != nil {
t.Fatalf("failed to run node: %v\n%s", err, output)
}
}
// Test whether Go.run() (in wasm_exec.js) normally returns and returns the // Test whether Go.run() (in wasm_exec.js) normally returns and returns the
// right exit code. // right exit code.
func TestWasmExit(t *testing.T) { func TestWasmExit(t *testing.T) {
@@ -967,15 +1085,15 @@ func TestGoexitCrash(t *testing.T) {
name string name string
want string want string
}{ }{
{"main", "all goroutines are asleep - deadlock!"}, {"main", "fatal error: all goroutines are asleep - deadlock!"},
{"deadlock", "all goroutines are asleep - deadlock!"}, {"deadlock", "fatal error: all goroutines are asleep - deadlock!"},
{"exit", "all goroutines are asleep - deadlock!"}, {"exit", "fatal error: all goroutines are asleep - deadlock!"},
{"main-other", "all goroutines are asleep - deadlock!"}, {"main-other", "fatal error: all goroutines are asleep - deadlock!"},
{"in-panic", "all goroutines are asleep - deadlock!"}, {"in-panic", "fatal error: all goroutines are asleep - deadlock!"},
{"panic", "panic: panic after Goexit"}, {"panic", "panic: panic after Goexit"},
{"recovered-panic", "all goroutines are asleep - deadlock!"}, {"recovered-panic", "fatal error: all goroutines are asleep - deadlock!"},
{"recover-before-panic", "all goroutines are asleep - deadlock!"}, {"recover-before-panic", "fatal error: all goroutines are asleep - deadlock!"},
{"recover-before-panic-loop", "all goroutines are asleep - deadlock!"}, {"recover-before-panic-loop", "fatal error: all goroutines are asleep - deadlock!"},
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
output := &bytes.Buffer{} output := &bytes.Buffer{}
@@ -996,6 +1114,34 @@ func TestGoexitCrash(t *testing.T) {
} }
} }
func TestRuntimeFatal(t *testing.T) {
t.Parallel()
options := optionsFromTarget("", sema)
config, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}
output := &bytes.Buffer{}
_, err = buildAndRun("testdata/runtimefatal.go", config, output, nil, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
cmd.Stdout = nil
cmd.Stderr = nil
data, err := cmd.CombinedOutput()
output.Write(data)
return err
})
if err == nil {
t.Fatal("program unexpectedly exited successfully")
}
if want := "fatal error: sync: unlock of unlocked Mutex"; !strings.Contains(output.String(), want) {
t.Fatalf("output does not contain %q:\n%s", want, output.String())
}
if strings.Contains(output.String(), "recovered:") {
t.Fatalf("fatal runtime error was recovered:\n%s", output.String())
}
}
func TestTest(t *testing.T) { func TestTest(t *testing.T) {
t.Parallel() t.Parallel()
+41
View File
@@ -0,0 +1,41 @@
# Build the TinyGo compiler, plus housekeeping and code generation helpers.
.PHONY: all tinygo clean fmt fmt-check
clean: ## Remove build directory
@rm -rf build
FMT_PATHS = ./*.go builder cgo/*.go compiler interp loader src transform
fmt: ## Reformat source
@gofmt -l -w $(FMT_PATHS)
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
# Generate WASI syscall bindings
WASM_TOOLS_MODULE=go.bytecodealliance.org
.PHONY: wasi-syscall
wasi-syscall: wasi-cm
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 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.
MIN_NODEJS_VERSION=22
.PHONY: check-nodejs-version
check-nodejs-version:
@# 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_CFLAGS="$(CGO_CFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm llvm22 osusergo" .
+132
View File
@@ -0,0 +1,132 @@
# Build configuration: host detection, LLVM paths, and CGO flags.
# Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build
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 = 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 ($(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)
endif
# First search for a custom built copy, then move on to explicitly version-tagged binaries, then just see if the tool is in path with its normal name.
findLLVMTool = $(call detect,$(1),$(abspath llvm-build/bin/$(1)) $(foreach ver,$(LLVM_VERSIONS),$(call toolSearchPathsVersion,$(1),$(ver))) $(1))
CLANG ?= $(call findLLVMTool,clang)
LLVM_AR ?= $(call findLLVMTool,llvm-ar)
LLVM_NM ?= $(call findLLVMTool,llvm-nm)
# Go binary and GOROOT to select
GO ?= go
# Flags to pass to go test.
GOTESTFLAGS ?=
GOTESTPKGS ?= ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
# tinygo binary for tests
TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo)
# Check for ccache if the user hasn't set it to on or off.
ifeq (, $(CCACHE))
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=$(if $(shell command -v ccache 2> /dev/null),ON,OFF)'
else
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=$(CCACHE)'
endif
# Allow enabling LLVM assertions
ifeq (1, $(ASSERT))
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=ON'
else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif
# Enable AddressSanitizer
ifeq (1, $(ASAN))
LLVM_OPTION += -DLLVM_USE_SANITIZER=Address
CGO_LDFLAGS += -fsanitize=address
endif
ifeq (1, $(STATIC))
# Build TinyGo as a fully statically linked binary (no dynamically loaded
# libraries such as a libc). This is not supported with glibc which is used
# on most major Linux distributions. However, it is supported in Alpine
# Linux with musl.
CGO_LDFLAGS += -static
# Also set the thread stack size to 1MB. This is necessary on musl as the
# default stack size is 128kB and LLVM uses more than that.
# For more information, see:
# https://wiki.musl-libc.org/functional-differences-from-glibc.html#Thread-stack-size
CGO_LDFLAGS += -Wl,-z,stack-size=1048576
# Build wasm-opt with static linking.
# For details, see:
# https://github.com/WebAssembly/binaryen/blob/version_102/.github/workflows/ci.yml#L181
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
CXX = $(CROSS)-g++
LLVM_OPTION += \
-DCMAKE_C_COMPILER=$(CC) \
-DCMAKE_CXX_COMPILER=$(CXX) \
-DLLVM_DEFAULT_TARGET_TRIPLE=$(CROSS) \
-DCROSS_TOOLCHAIN_FLAGS_NATIVE="-UCMAKE_C_COMPILER;-UCMAKE_CXX_COMPILER"
ifeq ($(CROSS), arm-linux-gnueabihf)
# Assume we're building on a Debian-like distro, with QEMU installed.
LLVM_CONFIG_PREFIX = qemu-arm -L /usr/arm-linux-gnueabihf/
# The CMAKE_SYSTEM_NAME flag triggers cross compilation mode.
LLVM_OPTION += \
-DCMAKE_SYSTEM_NAME=Linux \
-DLLVM_TARGET_ARCH=ARM
GOENVFLAGS = GOARCH=arm CC=$(CC) CXX=$(CXX) CGO_ENABLED=1
BINARYEN_OPTION += -DCMAKE_C_COMPILER=$(CC) -DCMAKE_CXX_COMPILER=$(CXX)
else ifeq ($(CROSS), aarch64-linux-gnu)
# Assume we're building on a Debian-like distro, with QEMU installed.
LLVM_CONFIG_PREFIX = qemu-aarch64 -L /usr/aarch64-linux-gnu/
# The CMAKE_SYSTEM_NAME flag triggers cross compilation mode.
LLVM_OPTION += \
-DCMAKE_SYSTEM_NAME=Linux \
-DLLVM_TARGET_ARCH=AArch64
GOENVFLAGS = GOARCH=arm64 CC=$(CC) CXX=$(CXX) CGO_ENABLED=1
BINARYEN_OPTION += -DCMAKE_C_COMPILER=$(CC) -DCMAKE_CXX_COMPILER=$(CXX)
else
$(error Unknown cross compilation target: $(CROSS))
endif
endif
+67
View File
@@ -0,0 +1,67 @@
# Generate microcontroller-specific sources from SVD files.
.PHONY: 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-stm32 gen-device-renesas gen-device-py32 gen-target-py32
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-py32 ## Generate microcontroller-specific sources
ifneq ($(RENESAS), 0)
gen-device: gen-device-renesas
endif
ifneq ($(STM32), 0)
gen-device: gen-device-stm32
endif
gen-device-avr:
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
@GO111MODULE=off $(GO) fmt ./src/device/avr
build/gen-device-svd: ./tools/gen-device-svd/*.go
$(GO) build -o $@ ./tools/gen-device-svd/
gen-device-esp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/
GO111MODULE=off $(GO) fmt ./src/device/esp
gen-device-nrf: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
GO111MODULE=off $(GO) fmt ./src/device/nrf
gen-device-nxp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/NXP lib/cmsis-svd/data/NXP/ src/device/nxp/
GO111MODULE=off $(GO) fmt ./src/device/nxp
gen-device-sam: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
GO111MODULE=off $(GO) fmt ./src/device/sam
gen-device-sifive: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
GO111MODULE=off $(GO) fmt ./src/device/sifive
gen-device-kendryte: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Kendryte-Community -interrupts=software lib/cmsis-svd/data/Kendryte-Community/ src/device/kendryte/
GO111MODULE=off $(GO) fmt ./src/device/kendryte
gen-device-stm32: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
GO111MODULE=off $(GO) fmt ./src/device/stm32
gen-device-rp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
GO111MODULE=off $(GO) fmt ./src/device/rp
gen-device-renesas: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/cmsis-svd/cmsis-svd-data/tree/master/data/Renesas lib/cmsis-svd/data/Renesas/ src/device/renesas/
GO111MODULE=off $(GO) fmt ./src/device/renesas
gen-device-py32: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/tinygo-org/py32-svd lib/py32-svd/svd src/device/py32/
GO111MODULE=off $(GO) fmt ./src/device/py32
gen-target-py32: ## Generate PY32 target and linker definitions
$(GO) run ./tools/gen-py32-targets
+105
View File
@@ -0,0 +1,105 @@
# LLVM and Binaryen: component lists, link flags, source checkout, and build.
.PHONY: llvm-source $(LLVM_BUILDDIR)
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
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
# 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++
CGO_LDFLAGS_EXTRA += -lversion
USE_SYSTEM_BINARYEN ?= 1
else ifeq ($(uname),Darwin)
MD5SUM ?= md5
CGO_LDFLAGS += -lxar
USE_SYSTEM_BINARYEN ?= 1
else ifeq ($(uname),FreeBSD)
MD5SUM ?= md5
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
else
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
endif
# md5sum binary default, can be overridden by an environment variable
MD5SUM ?= md5sum
# Libraries that should be linked in for the statically linked Clang.
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.
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 = 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)
# These build targets appear to be the only ones necessary to build all TinyGo
# dependencies. Only building a subset significantly speeds up rebuilding LLVM.
# The Makefile rules convert a name like lldELF to lib/liblldELF.a to match the
# library path (for ninja).
# This list also includes a few tools that are necessary as part of the full
# TinyGo build.
NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm lld $(addprefix lib/lib,$(addsuffix .a,$(LIB_NAMES)))
# For static linking.
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
# Pinned in llvm-version.txt. Branch tinygo_22.x of tinygo-org/llvm-project.
LLVM_REVISION = $(shell cat llvm-version.txt)
$(LLVM_PROJECTDIR)/llvm:
git init $(LLVM_PROJECTDIR)
cd $(LLVM_PROJECTDIR) && \
git remote add origin https://github.com/tinygo-org/llvm-project && \
git fetch --depth=1 origin $(LLVM_REVISION) && \
git checkout FETCH_HEAD
llvm-source: $(LLVM_PROJECTDIR)/llvm ## Get LLVM sources
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja:
mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;AVR;Mips;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_ZSTD=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION)
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja ## Build LLVM
cd $(LLVM_BUILDDIR) && ninja $(NINJA_BUILD_TARGETS)
ifneq ($(USE_SYSTEM_BINARYEN),1)
# Build Binaryen
.PHONY: binaryen
binaryen: build/wasm-opt$(EXE)
build/wasm-opt$(EXE):
mkdir -p build
cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON -DBUILD_TESTS=OFF -DENABLE_WERROR=OFF $(BINARYEN_OPTION) && ninja bin/wasm-opt$(EXE)
cp lib/binaryen/bin/wasm-opt$(EXE) build/wasm-opt$(EXE)
endif
+142
View File
@@ -0,0 +1,142 @@
# Build the release tarball and the Debian package.
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-headers/defaults
@mkdir -p build/release/tinygo/lib/musl/arch
@mkdir -p build/release/tinygo/lib/musl/crt
@mkdir -p build/release/tinygo/lib/musl/src
@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/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/
@echo copying source files
@cp -p build/tinygo$(EXE) build/release/tinygo/bin
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
@cp -rp lib/macos-minimal-sdk/* build/release/tinygo/lib/macos-minimal-sdk
@cp -rp lib/musl/arch/aarch64 build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/mips build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch
@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
@cp -rp lib/musl/src/locale build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/linux build/release/tinygo/lib/musl/src
@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/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
@cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc
@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/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/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_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
release:
tar -czf build/release.tar.gz -C build/release tinygo
DEB_ARCH ?= native
deb:
@mkdir -p build/release-deb/usr/local/bin
@mkdir -p build/release-deb/usr/local/lib
cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo
ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo
fpm -f -s dir -t deb -n tinygo -a $(DEB_ARCH) -v $(shell grep "const version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
ifneq ($(RELEASEONLY), 1)
release: build/release
deb: build/release
endif
+698
View File
@@ -0,0 +1,698 @@
# Smoke tests: check that TinyGo can build a binary for every supported board.
.PHONY: testchdir
testchdir:
# test 'build' command with{,out} -C argument
$(TINYGO) build -C tests/testing/chdir chdir.go && rm tests/testing/chdir/chdir
$(TINYGO) build ./tests/testing/chdir/chdir.go && rm chdir
# test 'run' command with{,out} -C argument
EXPECT_DIR=$(PWD)/tests/testing/chdir $(TINYGO) run -C tests/testing/chdir chdir.go
EXPECT_DIR=$(PWD) $(TINYGO) run ./tests/testing/chdir/chdir.go
SMOKETEST_SUBTARGETS = \
smoketest-selftest \
smoketest-examples \
smoketest-wasm-sim \
smoketest-nrf \
smoketest-samd \
smoketest-nxp \
smoketest-rp2xxx \
smoketest-pwm-usb \
smoketest-stm32 \
smoketest-py32 \
smoketest-avr \
smoketest-esp \
smoketest-riscv \
smoketest-wasm \
smoketest-flags
.PHONY: smoketest $(SMOKETEST_SUBTARGETS)
# Build a binary for every supported board. Run `make -j smoketest` to
# build the groups in parallel.
smoketest: testchdir $(SMOKETEST_SUBTARGETS)
# Each group writes to its own output name so that a parallel build does
# not let one group overwrite the output of another.
SMOKE_OUT = build/smoke/test
build/smoke:
@mkdir -p build/smoke
smoketest-selftest: | build/smoke
$(TINYGO) version
$(TINYGO) targets > /dev/null
# regression test for #2892
cd tests/testing/recurse && ($(TINYGO) test ./... > recurse.log && cat recurse.log && test $$(wc -l < recurse.log) = 2 && rm recurse.log)
# compile-only platform-independent examples
cd tests/text/template/smoke && $(TINYGO) test -c && rm -f smoke.test
# regression test for #2563
cd tests/os/smoke && $(TINYGO) test -c -target=pybadge && rm smoke.test
smoketest-examples: SMOKE_OUT = build/smoke/examples
smoketest-examples: | build/smoke
# test all examples (except pwm)
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pga2350 examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/adc
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/blinkm
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/blinky2
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/button
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/button2
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/echo2
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=circuitplay-express examples/i2s
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/mcp3008
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/memstats
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=microbit examples/microbit-blink
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nano-rp2040 examples/rtcinterrupt
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/systick
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/test
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/time-offset
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=wioterminal examples/hid-mouse
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=wioterminal examples/hid-keyboard
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-rp2040 examples/i2c-target
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-rp2040 examples/watchdog
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-rp2040 examples/device-id
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico2-ice examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -o $(SMOKE_OUT).efi -target=uefi-amd64 examples/test
@$(MD5SUM) $(SMOKE_OUT).efi
smoketest-wasm-sim: SMOKE_OUT = build/smoke/wasm-sim
smoketest-wasm-sim: | build/smoke
# test simulated boards on play.tinygo.org
ifneq ($(WASM), 0)
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o $(SMOKE_OUT).wasm -tags=arduino_uno examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o $(SMOKE_OUT).wasm -tags=hifive1b examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o $(SMOKE_OUT).wasm -tags=reelboard examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o $(SMOKE_OUT).wasm -tags=microbit examples/microbit-blink
@$(MD5SUM) $(SMOKE_OUT).wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o $(SMOKE_OUT).wasm -tags=circuitplay_express examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o $(SMOKE_OUT).wasm -tags=circuitplay_bluefruit examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o $(SMOKE_OUT).wasm -tags=mch2022 examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o $(SMOKE_OUT).wasm -tags=gopher_badge examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o $(SMOKE_OUT).wasm -tags=pico examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).wasm
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o $(SMOKE_OUT).wasm -tags=xiao_esp32s3 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).wasm
endif
smoketest-nrf: SMOKE_OUT = build/smoke/nrf
smoketest-nrf: | build/smoke
# test all targets/boards
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040-s132v6 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=microbit examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=microbit-s110v8 examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=microbit-v2 examples/microbit-blink
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=microbit-v2-s113v7 examples/microbit-blink
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=microbit-v2-s140v7 examples/microbit-blink
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nrf52840-mdk examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=btt-skr-pico examples/uart
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10031 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=reelboard examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=reelboard examples/blinky2
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10056 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10056 examples/blinky2
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10059 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10059 examples/blinky2
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=bluemicro840 examples/blinky2
@$(MD5SUM) $(SMOKE_OUT).hex
smoketest-samd: SMOKE_OUT = build/smoke/samd
smoketest-samd: | build/smoke
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=itsybitsy-m0 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-m0 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=trinket-m0 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=gemma-m0 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=circuitplay-express examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=circuitplay-bluefruit examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=circuitplay-express examples/i2s
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=clue-alpha examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).gba -target=gameboy-advance examples/gba-display
@$(MD5SUM) $(SMOKE_OUT).gba
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=grandcentral-m4 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=itsybitsy-m4 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-m4 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=matrixportal-m4 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pybadge examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=metro-m4-airlift examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pyportal examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=particle-argon examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=particle-boron examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=particle-xenon examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pinetime examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=x9pro examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10056-s140v7 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10059-s140v7 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=reelboard-s140v7 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=wioterminal examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pygamer examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=xiao examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=xiao-ble-plus examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=rak4631 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=circuitplay-express examples/dac
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pyportal examples/dac
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-nrf52840 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-nrf52840-sense examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=itsybitsy-nrf52840 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=qtpy examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).hex
smoketest-nxp: SMOKE_OUT = build/smoke/nxp
smoketest-nxp: | build/smoke
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=teensy41 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=teensy40 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=teensy36 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=p1am-100 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=atsame54-xpro examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=atsame54-xpro examples/can
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-m4-can examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-m4-can examples/caninterrupt
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-nano33 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-mkrwifi1010 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
smoketest-rp2xxx: SMOKE_OUT = build/smoke/rp2xxx
smoketest-rp2xxx: | build/smoke
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico -gc=leaking examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico-w examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nano-33-ble examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nano-rp2040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-rp2040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=qtpy-rp2040 examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=kb2040 examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=macropad-rp2040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=badger2040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=badger2040-w examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=tufty2040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=thingplus-rp2040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=xiao-rp2040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=waveshare-rp2040-zero examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=challenger-rp2040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=trinkey-qt2040 examples/temp
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=gopher-badge examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=gopher-arcade examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=ae-rp2040 examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=thumby examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico2 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico2-w examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=tiny2350 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=badger2350 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=blinky2350 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico-plus2 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=metro-rp2350 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=waveshare-rp2040-tiny examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=vicharak_shrike-lite examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=xiao-rp2350 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
smoketest-pwm-usb: SMOKE_OUT = build/smoke/pwm-usb
smoketest-pwm-usb: | build/smoke
# test pwm
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=itsybitsy-m4 examples/pwm
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-m4 examples/pwm
@$(MD5SUM) $(SMOKE_OUT).hex
# test usb
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-nrf52840 examples/hid-keyboard
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=circuitplay-express examples/hid-keyboard
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-nrf52840 examples/usb-midi
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico examples/usb-storage
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico2 examples/usb-storage
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nrf52840-s140v6-uf2-generic examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).hex
smoketest-stm32: SMOKE_OUT = build/smoke/stm32
smoketest-stm32: | build/smoke
ifneq ($(STM32), 0)
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=bluepill examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-stm32f405 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=lgt92 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-f103rb examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-f722ze examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-h753zi examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-l031k6 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-l432kc examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-l476rg examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-l552ze examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=nucleo-wl55jc examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=stm32f4disco examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=stm32f4disco-1 examples/pwm
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=stm32f469disco examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=lorae5 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=swan examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=mksnanov3 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=stm32l0x1 examples/serial
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=stm32u031 examples/empty
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-uno-q examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-uno-q examples/serial
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-uno-q examples/blinkm
@$(MD5SUM) $(SMOKE_OUT).hex
endif
smoketest-py32: SMOKE_OUT = build/smoke/py32
smoketest-py32: | build/smoke
ifneq ($(PY32), 0)
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=embedfire-py32f030 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=embedfire-py32f030 examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=embedfire-py32f002b examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=embedfire-py32f002b examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=py32f002bx5 ./testdata/py32-uart
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=py32f003x6 ./testdata/py32-uart
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=py32f030x8 ./testdata/py32-uart
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=py32f403xb examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=py32f030x8 ./testdata/py32-clock
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=py32f403xb ./testdata/py32-clock
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=py32e407xc examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=py32f410xb examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=py32t020x5 examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
endif
smoketest-avr: SMOKE_OUT = build/smoke/avr
smoketest-avr: | build/smoke
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=atmega328pb examples/blinkm
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=atmega1284p examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-uno examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-leonardo examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-uno examples/pwm
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-uno -scheduler=tasks examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-mega1280 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-mega1280 examples/pwm
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-nano examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=attiny1616 examples/empty
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=digispark examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=digispark examples/pwm
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=digispark examples/mcp3008
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=digispark -gc=leaking examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
smoketest-esp: SMOKE_OUT = build/smoke/esp
smoketest-esp: | build/smoke
ifneq ($(XTENSA), 0)
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32-generic examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32-coreboard-v2 examples/adc
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-generic examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32s3-generic examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32-mini32 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-supermini examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-supermini examples/blinkm
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=nodemcu examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target m5stack-core2 examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target m5stack examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target m5stamp-s3a examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target m5stick-c examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target m5paper examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target mch2022 examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
# xiao-esp32c6
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=xiao-esp32c6 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=xiao-esp32c6 examples/blinkm
@$(MD5SUM) $(SMOKE_OUT).bin
# xiao-esp32s3
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=xiao-esp32s3 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=xiao-esp32s3 examples/blinkm
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=xiao-esp32s3 examples/mcp3008
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=xiao-esp32s3 examples/pwm
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=xiao-esp32s3 examples/adc
@$(MD5SUM) $(SMOKE_OUT).bin
# esp32s3-supermini
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32s3-supermini examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32s3-supermini examples/blinkm
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32s3-supermini examples/mcp3008
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32s3-supermini examples/adc
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32s3-box-3 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
endif
smoketest-riscv: SMOKE_OUT = build/smoke/riscv
smoketest-riscv: | build/smoke
# esp32c3-supermini
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-supermini examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-supermini examples/blinkm
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-supermini examples/mcp3008
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-supermini examples/pwm
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-supermini examples/adc
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp-c3-32s-kit examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=qtpy-esp32c3 examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=m5stamp-c3 examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=xiao-esp32c3 examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32-c3-devkit-rust-1 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-12f examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=makerfabs-esp32c3spi35 examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=hifive1b examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=maixbit examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=tkey examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=elecrow-rp2040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=elecrow-rp2350 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=hw-651 examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=hw-651-s110v8 examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).hex
smoketest-wasm: SMOKE_OUT = build/smoke/wasm
smoketest-wasm: | build/smoke
ifneq ($(WASM), 0)
$(TINYGO) build -size short -o $(SMOKE_OUT).wasm -target=wasm examples/wasm/export
$(TINYGO) build -size short -o $(SMOKE_OUT).wasm -target=wasm examples/wasm/main
$(TINYGO) build -size short -o $(SMOKE_OUT).wasm -target=wasm-unknown examples/hello-wasm-unknown
endif
smoketest-flags: SMOKE_OUT = build/smoke/flags
smoketest-flags: | build/smoke
# test various compiler flags
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 -gc=none -scheduler=none examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 -opt=1 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 -serial=none examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 -serial=rtt examples/echo
@$(MD5SUM) $(SMOKE_OUT).hex
$(TINYGO) build -o $(SMOKE_OUT).nro -target=nintendoswitch examples/echo2
@$(MD5SUM) $(SMOKE_OUT).nro
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) $(SMOKE_OUT).hex
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o $(SMOKE_OUT).elf ./testdata/cgo
GOOS=linux GOARCH=mips $(TINYGO) build -size short -o $(SMOKE_OUT).elf ./testdata/cgo
GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o $(SMOKE_OUT).exe ./testdata/cgo
GOOS=windows GOARCH=arm64 $(TINYGO) build -size short -o $(SMOKE_OUT).exe ./testdata/cgo
GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o $(SMOKE_OUT) ./testdata/cgo
GOOS=darwin GOARCH=arm64 $(TINYGO) build -size short -o $(SMOKE_OUT) ./testdata/cgo
ifneq ($(OS),Windows_NT)
# TODO: this does not yet work on Windows. Somehow, unused functions are
# not garbage collected.
$(TINYGO) build -o $(SMOKE_OUT).elf -gc=leaking -scheduler=none examples/serial
endif
# A representative board for each processor architecture. This answers the
# question "can TinyGo build a binary for each architecture" at a fraction of
# the cost of the full smoke test, which runs separately on Linux.
.PHONY: smoketest-quick
smoketest-quick: SMOKE_OUT = build/smoke/quick
smoketest-quick: testchdir | build/smoke
$(TINYGO) version
$(TINYGO) targets > /dev/null
# regression test for #2892
cd tests/testing/recurse && ($(TINYGO) test ./... > recurse.log && cat recurse.log && test $$(wc -l < recurse.log) = 2 && rm recurse.log)
# nrf51, Cortex-M0
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=microbit examples/microbit-blink
@$(MD5SUM) $(SMOKE_OUT).hex
# nrf52, Cortex-M4
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10040 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# nrf52840 with SoftDevice, which uses a different memory layout
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pca10056-s140v7 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# samd21, Cortex-M0+
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=circuitplay-express examples/i2s
@$(MD5SUM) $(SMOKE_OUT).hex
# samd51, Cortex-M4 with hardware floating point
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=feather-m4 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# same5x, which adds CAN
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=atsame54-xpro examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# rp2040, dual Cortex-M0+ with a second stage bootloader
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# rp2350, Cortex-M33
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=pico2 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# i.MX RT1062, Cortex-M7
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=teensy41 examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# ARM7TDMI, the only target that is not Thumb-2
$(TINYGO) build -size short -o $(SMOKE_OUT).gba -target=gameboy-advance examples/gba-display
@$(MD5SUM) $(SMOKE_OUT).gba
# AVR, ATmega328p
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=arduino-uno examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# AVR, ATtiny85, which has a smaller instruction set than the ATmega
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=digispark examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# RISC-V 32 bit, esp32c3. Not behind the XTENSA flag, so the compatibility
# test also builds it.
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-supermini examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).bin
# RISC-V 32 bit, SiFive E31
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=hifive1b examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# RISC-V 64 bit, the only 64 bit baremetal target
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=maixbit examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# x86-64 with PE/COFF output
$(TINYGO) build -size short -o $(SMOKE_OUT).efi -target=uefi-amd64 examples/test
@$(MD5SUM) $(SMOKE_OUT).efi
# aarch64
$(TINYGO) build -o $(SMOKE_OUT).nro -target=nintendoswitch examples/echo2
@$(MD5SUM) $(SMOKE_OUT).nro
# cross compilation with cgo
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o $(SMOKE_OUT).elf ./testdata/cgo
ifneq ($(STM32), 0)
# STM32F1, Cortex-M3
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=bluepill examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
# STM32F4, Cortex-M4 with hardware floating point
$(TINYGO) build -size short -o $(SMOKE_OUT).hex -target=stm32f4disco examples/blinky1
@$(MD5SUM) $(SMOKE_OUT).hex
endif
ifneq ($(XTENSA), 0)
# Xtensa LX6
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32-coreboard-v2 examples/adc
@$(MD5SUM) $(SMOKE_OUT).bin
# Xtensa LX7
$(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32s3-generic examples/machinetest
@$(MD5SUM) $(SMOKE_OUT).bin
endif
ifneq ($(WASM), 0)
# wasm with the JavaScript host bindings
$(TINYGO) build -size short -o $(SMOKE_OUT).wasm -target=wasm examples/wasm/main
# wasm without a host, so without any imports
$(TINYGO) build -size short -o $(SMOKE_OUT).wasm -target=wasm-unknown examples/hello-wasm-unknown
endif
ifneq ($(OS),Windows_NT)
# TODO: this does not yet work on Windows. Somehow, unused functions are
# not garbage collected.
$(TINYGO) build -o $(SMOKE_OUT).elf -gc=leaking -scheduler=none examples/serial
endif
+294
View File
@@ -0,0 +1,294 @@
# Test suites: compiler tests, standard library tests, benchmarks, and the corpus.
.PHONY: test wasmtest
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 = \
compress/bzip2 \
crypto/dsa \
index/suffixarray \
# Standard library packages that pass tests quickly on darwin, linux, wasi, and windows
TEST_PACKAGES_FAST = \
cmp \
compress/lzw \
compress/zlib \
container/heap \
container/list \
container/ring \
crypto/ecdsa \
crypto/elliptic \
crypto/md5 \
crypto/sha1 \
crypto/sha256 \
crypto/sha512 \
database/sql/driver \
debug/macho \
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 \
hash/crc64 \
hash/fnv \
html \
internal/itoa \
internal/profile \
math \
math/cmplx \
net/http/internal/ascii \
net/mail \
net/url \
os \
path \
reflect \
sync \
testing \
testing/iotest \
text/scanner \
unicode \
unicode/utf16 \
unicode/utf8 \
unique \
$(nil)
# archive/zip requires os.ReadAt, which is not yet supported on windows
# bytes requires mmap
# compress/flate appears to hang on wasi
# crypto/aes needs reflect.Type.Method(), not yet implemented
# crypto/des fails on wasi, needs panic()/recover()
# crypto/hmac fails on wasi, it exits with a "slice out of range" panic
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
# encoding/xml takes a minute on linux and gives a stack overflow on wasi
# image fails on wasi, needs panic()/recover()
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
# 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: 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 \
mime/multipart \
mime/quotedprintable \
net \
net/mail \
net/textproto \
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 \
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.
# * Since Go 1.27 the crypto tests below go through cryptotest.TestHash, which
# calls cryptotest.BoundarySlices. These targets report GOOS=linux, so they
# build boundary.go (//go:build linux || darwin) rather than
# boundary_compat.go, and that needs a working syscall.Mmap/syscall.Mprotect
# which we don't have. See #5593.
TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST))
TEST_PACKAGES_NONBAREMETAL = \
$(TEST_PACKAGES_NONWASM) \
$(TEST_PACKAGES_NOBOUNDARYSLICES) \
math \
$(nil)
TEST_PACKAGES_FAST_WASI = $(filter-out $(TEST_PACKAGES_NOWASI), $(TEST_PACKAGES_FAST))
TEST_PACKAGES_NOWASI = \
crypto/ecdsa \
$(nil)
# wasip1 reports GOOS=wasip1 and so gets the boundary_compat.go fallback, but
# wasip2 reports GOOS=linux and hits the same BoundarySlices problem as
# baremetal. On wasip2 syscall.Mmap returns ENOSYS and t.Fatalf cannot Goexit,
# so the test falls through and panics with "slice out of range".
TEST_PACKAGES_FAST_WASIP2 = $(filter-out $(TEST_PACKAGES_NOBOUNDARYSLICES), $(TEST_PACKAGES_FAST_WASI))
TEST_PACKAGES_NOBOUNDARYSLICES = \
crypto/md5 \
crypto/sha1 \
crypto/sha256 \
crypto/sha512 \
$(nil)
# Report platforms on which each standard library package is known to pass tests
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
@join -a1 -a2 $(jointmp).darwin $(jointmp).linux | \
join -a1 -a2 - $(jointmp).portable
@rm $(jointmp).*
# Standard library packages that pass tests quickly on the current platform
ifeq ($(uname),Darwin)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_DARWIN)
TEST_IOFS := true
TEST_ENCODING_XML := true
endif
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|TestUnmarshalNestingLimitSlice|TestUnmarshalNestingLimitStruct'
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:
@# 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.
@# TestUnmarshalNestingLimit{Slice,Struct}: encoding/asn1 nesting limit added in
@# https://github.com/golang/go/commit/6a6d115f9a7422b2fa081ba6f567eefb4a099462
$(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.
ifeq ($(TEST_IOFS),true)
$(TINYGO) test -stack-size=6MB io/fs
endif
tinygo-test-fast:
$(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_SKIP_FLAG) $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
tinygo-test-wasip1:
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_SKIP_FLAG) $(TEST_PACKAGES_FAST_WASI) ./tests/runtime_wasi
tinygo-test-wasip2-slow:
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_SLOW)
tinygo-test-wasip2-fast:
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST_WASIP2) ./tests/runtime_wasi
tinygo-test-wasip2-sum-slow:
TINYGO=$(TINYGO) \
TARGET=wasip2 \
TESTOPTS="-x -work" \
PACKAGES="$(TEST_PACKAGES_SLOW)" \
gotestsum --raw-command -- ./tools/tgtestjson.sh
tinygo-test-wasip2-sum-fast:
TINYGO=$(TINYGO) \
TARGET=wasip2 \
TESTOPTS="-x -work" \
PACKAGES="$(TEST_PACKAGES_FAST)" \
gotestsum --raw-command -- ./tools/tgtestjson.sh
tinygo-bench-wasip1:
$(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW)
tinygo-bench-wasip1-fast:
$(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST)
tinygo-bench-wasip2:
$(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW)
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 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 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
wasmtest:
cd ./tests/wasm && $(GO) test .
+36
View File
@@ -0,0 +1,36 @@
# Developer tooling: lint, spellcheck, and the help target.
.PHONY: tools
tools:
go generate -tags tools ./
LINTDIRS=src/os/ src/reflect/
.PHONY: lint
lint: tools ## Lint source tree
revive -version
# TODO: lint more directories!
# 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/... $$( 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
spell: tools ## Spellcheck source tree
misspell -error --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md
.PHONY: spellfix
spellfix: tools ## Same as spell, but fixes what it finds
misspell -w --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md
# https://www.client9.com/self-documenting-makefiles/
.PHONY: help
help:
@awk -F ':|##' '/^[^\t].+?:.*?##/ {\
gsub(/\$$\(LLVM_BUILDDIR\)/, "$(LLVM_BUILDDIR)"); \
printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF \
}' $(MAKEFILE_LIST)
#.DEFAULT_GOAL=help
+143
View File
@@ -0,0 +1,143 @@
//go:build cortexm
package arm
import (
"runtime/volatile"
"unsafe"
)
/*
The struct below has been created using gen-device-svd
on cortex_m/armv7em.yaml from stm32-rs.
The MPU type has been included as some stm32 svd files
do not contain MPU definitions.
*/
const MPU_BASE = SCS_BASE + 0x0D90
// Memory Protection Unit
type MPU_Type struct {
TYPE volatile.Register32 // 0x0
CTRL volatile.Register32 // 0x4
RNR volatile.Register32 // 0x8
RBAR volatile.Register32 // 0xC
RASR volatile.Register32 // 0x10
RBAR_A1 volatile.Register32 // 0x14
RASR_A1 volatile.Register32 // 0x18
RBAR_A2 volatile.Register32 // 0x1C
RASR_A2 volatile.Register32 // 0x20
RBAR_A3 volatile.Register32 // 0x24
RASR_A3 volatile.Register32 // 0x28
}
var MPU = (*MPU_Type)(unsafe.Pointer(uintptr(MPU_BASE)))
// Constants for MPU: Memory protection unit
const (
// TYPER: MPU type register
// Position of SEPARATE field.
MPU_TYPER_SEPARATE_Pos = 0x0
// Bit mask of SEPARATE field.
MPU_TYPER_SEPARATE_Msk = 0x1
// Bit SEPARATE.
MPU_TYPER_SEPARATE = 0x1
// Position of DREGION field.
MPU_TYPER_DREGION_Pos = 0x8
// Bit mask of DREGION field.
MPU_TYPER_DREGION_Msk = 0xff00
// Position of IREGION field.
MPU_TYPER_IREGION_Pos = 0x10
// Bit mask of IREGION field.
MPU_TYPER_IREGION_Msk = 0xff0000
// CTRL: MPU control register
// Position of ENABLE field.
MPU_CTRL_ENABLE_Pos = 0x0
// Bit mask of ENABLE field.
MPU_CTRL_ENABLE_Msk = 0x1
// Bit ENABLE.
MPU_CTRL_ENABLE = 0x1
// Position of HFNMIENA field.
MPU_CTRL_HFNMIENA_Pos = 0x1
// Bit mask of HFNMIENA field.
MPU_CTRL_HFNMIENA_Msk = 0x2
// Bit HFNMIENA.
MPU_CTRL_HFNMIENA = 0x2
// Position of PRIVDEFENA field.
MPU_CTRL_PRIVDEFENA_Pos = 0x2
// Bit mask of PRIVDEFENA field.
MPU_CTRL_PRIVDEFENA_Msk = 0x4
// Bit PRIVDEFENA.
MPU_CTRL_PRIVDEFENA = 0x4
// RNR: MPU region number register
// Position of REGION field.
MPU_RNR_REGION_Pos = 0x0
// Bit mask of REGION field.
MPU_RNR_REGION_Msk = 0xff
// RBAR: MPU region base address register
// Position of REGION field.
MPU_RBAR_REGION_Pos = 0x0
// Bit mask of REGION field.
MPU_RBAR_REGION_Msk = 0xf
// Position of VALID field.
MPU_RBAR_VALID_Pos = 0x4
// Bit mask of VALID field.
MPU_RBAR_VALID_Msk = 0x10
// Bit VALID.
MPU_RBAR_VALID = 0x10
// Position of ADDR field.
MPU_RBAR_ADDR_Pos = 0x5
// Bit mask of ADDR field.
MPU_RBAR_ADDR_Msk = 0xffffffe0
// RASR: MPU region attribute and size register
// Position of ENABLE field.
MPU_RASR_ENABLE_Pos = 0x0
// Bit mask of ENABLE field.
MPU_RASR_ENABLE_Msk = 0x1
// Bit ENABLE.
MPU_RASR_ENABLE = 0x1
// Position of SIZE field.
MPU_RASR_SIZE_Pos = 0x1
// Bit mask of SIZE field.
MPU_RASR_SIZE_Msk = 0x3e
// Position of SRD field.
MPU_RASR_SRD_Pos = 0x8
// Bit mask of SRD field.
MPU_RASR_SRD_Msk = 0xff00
// Position of B field.
MPU_RASR_B_Pos = 0x10
// Bit mask of B field.
MPU_RASR_B_Msk = 0x10000
// Bit B.
MPU_RASR_B = 0x10000
// Position of C field.
MPU_RASR_C_Pos = 0x11
// Bit mask of C field.
MPU_RASR_C_Msk = 0x20000
// Bit C.
MPU_RASR_C = 0x20000
// Position of S field.
MPU_RASR_S_Pos = 0x12
// Bit mask of S field.
MPU_RASR_S_Msk = 0x40000
// Bit S.
MPU_RASR_S = 0x40000
// Position of TEX field.
MPU_RASR_TEX_Pos = 0x13
// Bit mask of TEX field.
MPU_RASR_TEX_Msk = 0x380000
// Position of AP field.
MPU_RASR_AP_Pos = 0x18
// Bit mask of AP field.
MPU_RASR_AP_Msk = 0x7000000
// Position of XN field.
MPU_RASR_XN_Pos = 0x1c
// Bit mask of XN field.
MPU_RASR_XN_Msk = 0x10000000
// Bit XN.
MPU_RASR_XN = 0x10000000
)
+232 -8
View File
@@ -10,6 +10,49 @@
.section .text.call_start_cpu0 .section .text.call_start_cpu0
1: 1:
.long _stack_top .long _stack_top
.Lmain_addr:
.long main
.Lrom_mmu_init:
.long 0x400095a4 // mmu_init(int cpu_no)
.Lrom_cache_flash_mmu_set:
.long 0x400095e0 // cache_flash_mmu_set(cpu, pid, vaddr, paddr, pgsz, pgcnt)
.Lrom_Cache_Read_Enable:
.long 0x40009a84 // Cache_Read_Enable(int cpu_no)
.Lrom_Cache_Read_Disable:
.long 0x40009ab8 // Cache_Read_Disable(int cpu_no)
.Lrom_Cache_Flush:
.long 0x40009a14 // Cache_Flush(int cpu_no)
.Lrodata_start:
.long _rodata_start
.Lrodata_end:
.long _rodata_end
.Ltext_start:
.long _text_start
.Ltext_end:
.long _text_end
.Ldport_pro_cache_ctrl1:
.long 0x3FF00044 // DPORT_PRO_CACHE_CTRL1_REG
.Ldrom_paddr_ptr:
.long _drom_flash_addr // pointer to builder-patched DROM flash offset
.Ldrom_vaddr:
.long 0x3F400000 // DROM virtual base address
.Lirom_vaddr:
.long 0x400D0000 // IROM virtual base address
.Lmmu_table_base:
.long 0x3FF10000 // PRO CPU Flash MMU table
.Lrtc_wdt_protect:
.long 0x3FF480A4 // RTC_CNTL_WDTWPROTECT_REG
.Lrtc_wdt_key:
.long 0x50D83AA1 // WDT write-protect key
.Lrtc_wdt_config0:
.long 0x3FF4808C // RTC_CNTL_WDTCONFIG0_REG
.Ltimg0_wdt_protect:
.long 0x3FF5F064 // TIMG0_WDTWPROTECT_REG
.Ltimg0_wdt_config0:
.long 0x3FF5F048 // TIMG0_WDTCONFIG0_REG
.Lvector_table:
.long _vector_table
.global call_start_cpu0 .global call_start_cpu0
call_start_cpu0: call_start_cpu0:
// We need to set the stack pointer to a different value. This is somewhat // We need to set the stack pointer to a different value. This is somewhat
@@ -17,10 +60,14 @@ call_start_cpu0:
// version of the following code: // version of the following code:
// https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47 // https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47
// Disable WOE. // Disable WOE (bit 18 of PS).
// Avoid large movi constants to prevent auto-generated .literal section
// entries, which cause l32r offset miscalculation in LLVM 22 / lld.
rsr.ps a2 rsr.ps a2
movi a3, ~(PS_WOE_MASK) movi a3, 1
and a2, a2, a3 slli a3, a3, 18 // a3 = PS_WOE_MASK (0x40000)
and a3, a2, a3 // a3 = a2 & WOE_MASK (isolate WOE bit)
xor a2, a2, a3 // clear WOE bit
wsr.ps a2 wsr.ps a2
rsync rsync
@@ -37,7 +84,8 @@ call_start_cpu0:
// Re-enable WOE. // Re-enable WOE.
rsr.ps a2 rsr.ps a2
movi a3, PS_WOE movi a3, 1
slli a3, a3, 18 // a3 = PS_WOE (0x40000)
or a2, a2, a3 or a2, a2, a3
wsr.ps a2 wsr.ps a2
rsync rsync
@@ -47,11 +95,187 @@ call_start_cpu0:
wsr.cpenable a2 wsr.cpenable a2
rsync rsync
// Jump to the runtime start function written in Go. // Disable the RTC and TIMG0 watchdogs before configuring the flash cache.
call4 main // The ROM bootloader leaves them running; a fault during cache setup would
// otherwise reset the chip. The Go runtime re-disables them once it starts.
l32r a2, .Lrtc_wdt_protect
l32r a3, .Lrtc_wdt_key
s32i a3, a2, 0 // unlock WDT write-protect
memw
l32r a2, .Lrtc_wdt_config0
movi a3, 0
s32i a3, a2, 0 // disable WDT (write 0 to config0)
memw
// Disable TG0 WDT (Timer Group 0 Main Watchdog).
// TIMG0_WDTWPROTECT_REG = 0x3FF5F064, TIMG0_WDTCONFIG0_REG = 0x3FF5F048
l32r a2, .Ltimg0_wdt_protect
l32r a3, .Lrtc_wdt_key // same unlock key 0x50D83AA1
s32i a3, a2, 0
memw
l32r a2, .Ltimg0_wdt_config0
movi a3, 0
s32i a3, a2, 0
memw
// Set VECBASE to our vector table. Must happen before any callx4 so that
// register-window overflow exceptions route to our handlers.
l32r a2, .Lvector_table
wsr.vecbase a2
rsync
// Clear PS.EXCM so window overflow exceptions work properly.
rsr.ps a2
movi a3, ~0x1F
and a2, a2, a3
movi a3, 0x20 // PS.UM = 1
or a2, a2, a3
wsr.ps a2
rsync
// ---- Configure flash cache and MMU ----
movi a6, 0
mov a5, a1
l32r a4, .Lrom_Cache_Read_Disable
callx4 a4
movi a6, 0
mov a5, a1
l32r a4, .Lrom_Cache_Flush
callx4 a4
movi a6, 0
mov a5, a1
l32r a4, .Lrom_mmu_init
callx4 a4
l32r a2, .Lrodata_end
l32r a3, .Lrodata_start
sub a2, a2, a3
beqz a2, .Lskip_drom
addi a2, a2, -1
srli a2, a2, 16
addi a2, a2, 1
movi a6, 0
movi a7, 0
l32r a8, .Ldrom_vaddr
l32r a9, .Ldrom_paddr_ptr
l32i a9, a9, 0
movi a10, 64
mov a11, a2
mov a5, a1
l32r a4, .Lrom_cache_flash_mmu_set
callx4 a4
.Lskip_drom:
l32r a2, .Ltext_end
l32r a3, .Ltext_start
sub a2, a2, a3
beqz a2, .Lskip_irom
addi a2, a2, -1
srli a2, a2, 16
addi a2, a2, 1
l32r a9, .Lrodata_end
l32r a3, .Lrodata_start
sub a9, a9, a3
l32r a3, .Ldrom_paddr_ptr
l32i a3, a3, 0
beqz a9, .Lirom_paddr_ready
addi a9, a9, -1
srli a9, a9, 16
addi a9, a9, 1
slli a9, a9, 16
add a3, a3, a9
.Lirom_paddr_ready:
movi a6, 0
movi a7, 0
l32r a8, .Lirom_vaddr
mov a9, a3
movi a10, 64
mov a11, a2
mov a5, a1
l32r a4, .Lrom_cache_flash_mmu_set
callx4 a4
.Lskip_irom:
l32r a2, .Ldport_pro_cache_ctrl1
l32i a3, a2, 0
movi a4, ~0x11
and a3, a3, a4
s32i a3, a2, 0
memw
movi a6, 0
mov a5, a1
l32r a4, .Lrom_Cache_Read_Enable
callx4 a4
isync
// ---- Jump to main (in IROM/flash, now accessible) ----
mov a5, a1
l32r a4, .Lmain_addr
callx4 a4
// If main returns, loop forever.
1: j 1b
// -----------------------------------------------------------------------
// tinygo_scanCurrentStack Spill all Xtensa register windows to the
// stack, then call tinygo_scanstack(sp) so the conservative GC can
// discover live heap pointers that are currently in physical registers.
//
// On RISC-V / ARM the equivalent function pushes callee-saved registers
// before the call. On Xtensa windowed ABI the same effect is achieved
// by forcing hardware window-overflow for every occupied pane: each
// overflow saves the four registers in that pane to the stack frame
// pointed to by the pane's a1 (sp). After all panes are flushed, a
// scan from the current sp to stackTop covers every live value.
//
// Without this spill the conservative GC misses heap pointers held only
// in physical registers, frees live objects, and later crashes jumping
// through a freed/garbage function pointer (e.g. a goroutine trampoline).
// -----------------------------------------------------------------------
.section .text.tinygo_scanCurrentStack .section .text.tinygo_scanCurrentStack
.global tinygo_scanCurrentStack .global tinygo_scanCurrentStack
tinygo_scanCurrentStack: tinygo_scanCurrentStack:
// TODO: save callee saved registers on the stack entry a1, 48
j tinygo_scanstack
// Disable interrupts while flushing register windows.
rsr a4, PS
s32i a4, a1, 0 // save PS for later restore
rsil a4, 3 // XCHAL_EXCM_LEVEL
// Flush all register windows using recursive call4.
// For NAREG=64 (16 panes), 15 recursive levels cover all panes
// except the current one (which is kept active).
movi a6, 15
call4 .Lscan_spill
// Restore interrupts.
l32i a4, a1, 0
wsr.ps a4
rsync
// Pass current sp to tinygo_scanstack.
// call4 maps caller's a5callee's a1 (stack ptr for callee's entry)
// and caller's a6callee's a2 (first argument = sp).
mov a5, a1 // callee's a1 = valid stack pointer
mov a6, a1 // callee's a2 = sp argument
call4 tinygo_scanstack
retw
.balign 4
.Lscan_spill:
entry a1, 16
beqz a2, .Lscan_spill_done
addi a2, a2, -1
mov a6, a2
call4 .Lscan_spill
.Lscan_spill_done:
retw
+14 -6
View File
@@ -102,10 +102,14 @@ call_start_cpu0:
// ---- 1. Windowed-ABI register file setup ---- // ---- 1. Windowed-ABI register file setup ----
// Disable WOE so we can safely manipulate WINDOWSTART. // Disable WOE (bit 18 of PS).
// Avoid large movi constants to prevent auto-generated .literal section
// entries, which cause l32r offset miscalculation in LLVM 22 / lld.
rsr.ps a2 rsr.ps a2
movi a3, ~(PS_WOE) movi a3, 1
and a2, a2, a3 slli a3, a3, 18 // a3 = PS_WOE (0x40000)
and a3, a2, a3 // isolate WOE bit
xor a2, a2, a3 // clear WOE bit
wsr.ps a2 wsr.ps a2
rsync rsync
@@ -122,7 +126,8 @@ call_start_cpu0:
// Re-enable WOE. // Re-enable WOE.
rsr.ps a2 rsr.ps a2
movi a3, PS_WOE movi a3, 1
slli a3, a3, 18 // a3 = PS_WOE (0x40000)
or a2, a2, a3 or a2, a2, a3
wsr.ps a2 wsr.ps a2
rsync rsync
@@ -213,7 +218,9 @@ call_start_cpu0:
// and cannot service flash accesses. // and cannot service flash accesses.
// 4a. Configure ICache mode: 16KB, 8-way, 32-byte line // 4a. Configure ICache mode: 16KB, 8-way, 32-byte line
movi a6, 0x4000 // cache_size = 16KB // Use movi+slli to avoid auto-literal generation (lld l32r bug).
movi a6, 1
slli a6, a6, 14 // a6 = 0x4000 = 16KB
movi a7, 8 // ways = 8 movi a7, 8 // ways = 8
movi a8, 32 // line_size = 32 movi a8, 32 // line_size = 32
mov a5, a1 mov a5, a1
@@ -226,7 +233,8 @@ call_start_cpu0:
callx4 a4 callx4 a4
// 4c. Configure DCache mode: 32KB, 8-way, 32-byte line // 4c. Configure DCache mode: 32KB, 8-way, 32-byte line
movi a6, 0x8000 // cache_size = 32KB movi a6, 1
slli a6, a6, 15 // a6 = 0x8000 = 32KB
movi a7, 8 // ways = 8 movi a7, 8 // ways = 8
movi a8, 32 // line_size = 32 movi a8, 32 // line_size = 32
mov a5, a1 mov a5, a1
+21
View File
@@ -0,0 +1,21 @@
package uefi
import _ "unsafe"
//go:linkname gosched runtime.Gosched
func gosched()
// WaitForEvent blocks while yielding to the TinyGo scheduler so other
// goroutines can continue to run.
func WaitForEvent(event EFI_EVENT) EFI_STATUS {
for {
status := BS().CheckEvent(event)
if status == EFI_SUCCESS {
return EFI_SUCCESS
}
if status != EFI_NOT_READY {
return status
}
gosched()
}
}
+8
View File
@@ -20,6 +20,10 @@ type EFI_RUNTIME_SERVICES struct {
queryVariableInfo uintptr queryVariableInfo uintptr
} }
func (p *EFI_RUNTIME_SERVICES) GetTime(time *EFI_TIME, capabilities *EFI_TIME_CAPABILITIES) EFI_STATUS {
return UefiCall2(p.getTime, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(capabilities)))
}
type EFI_BOOT_SERVICES struct { type EFI_BOOT_SERVICES struct {
Hdr EFI_TABLE_HEADER Hdr EFI_TABLE_HEADER
raiseTPL uintptr raiseTPL uintptr
@@ -82,6 +86,10 @@ func (p *EFI_BOOT_SERVICES) WaitForEvent(numberOfEvents UINTN, event *EFI_EVENT,
return UefiCall3(p.waitForEvent, uintptr(numberOfEvents), uintptr(unsafe.Pointer(event)), uintptr(unsafe.Pointer(index))) return UefiCall3(p.waitForEvent, uintptr(numberOfEvents), uintptr(unsafe.Pointer(event)), uintptr(unsafe.Pointer(index)))
} }
func (p *EFI_BOOT_SERVICES) SignalEvent(event EFI_EVENT) EFI_STATUS {
return UefiCall1(p.signalEvent, uintptr(event))
}
func (p *EFI_BOOT_SERVICES) CloseEvent(event EFI_EVENT) EFI_STATUS { func (p *EFI_BOOT_SERVICES) CloseEvent(event EFI_EVENT) EFI_STATUS {
return UefiCall1(p.closeEvent, uintptr(event)) return UefiCall1(p.closeEvent, uintptr(event))
} }
+104
View File
@@ -0,0 +1,104 @@
package uefi
type EFI_TIME struct {
Year uint16
Month byte
Day byte
Hour byte
Minute byte
Second byte
Pad1 byte
Nanosecond uint32
TimeZone int16
Daylight byte
Pad2 byte
}
type EFI_TIME_CAPABILITIES struct {
Resolution uint32
Accuracy uint32
SetsToZero BOOLEAN
}
func GetTime() (EFI_TIME, EFI_STATUS) {
var time EFI_TIME
status := ST().RuntimeServices.GetTime(&time, nil)
return time, status
}
func (t *EFI_TIME) GetEpoch() (sec int64, nsec int32) {
if t.TimeZone != 0x07FF { // EFI_UNSPECIFIED_TIMEZONE
sec -= int64(t.TimeZone) * 60
}
year := int(t.Year)
month := int(t.Month)
d := daysSinceEpoch(year)
d += uint64(daysBefore[month-1])
if isLeap(year) && month > 2 {
d++
}
d += uint64(t.Day - 1)
abs := d * secondsPerDay
abs += uint64(uint64(t.Hour)*uint64(secondsPerHour) + uint64(t.Minute)*uint64(secondsPerMinute) + uint64(t.Second))
sec = int64(abs) + (absoluteToInternal + internalToUnix)
nsec = int32(t.Nanosecond)
return
}
const (
secondsPerMinute = 60
secondsPerHour = 60 * secondsPerMinute
secondsPerDay = 24 * secondsPerHour
daysPer400Years = 365*400 + 97
daysPer100Years = 365*100 + 24
daysPer4Years = 365*4 + 1
absoluteZeroYear = -292277022399
internalYear = 1
absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay
unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay
internalToUnix int64 = -unixToInternal
)
var daysBefore = [...]int32{
0,
31,
31 + 28,
31 + 28 + 31,
31 + 28 + 31 + 30,
31 + 28 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
}
func daysSinceEpoch(year int) uint64 {
y := uint64(int64(year) - absoluteZeroYear)
n := y / 400
y -= 400 * n
d := daysPer400Years * n
n = y / 100
y -= 100 * n
d += daysPer100Years * n
n = y / 4
y -= 4 * n
d += daysPer4Years * n
d += 365 * y
return d
}
func isLeap(year int) bool {
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}
+11
View File
@@ -0,0 +1,11 @@
//go:build stm32h7
package main
import "machine"
var (
pwm = &machine.TIM1
pinA = machine.PA8
pinB = machine.PA9
)
+31
View File
@@ -0,0 +1,31 @@
package main
import (
"machine"
"time"
)
func main() {
time.Sleep(2 * time.Second)
println("configuring window watchdog")
config := machine.WindowWatchdogConfig{
TimeoutMicros: 100000, // 100ms
WindowPercent: 50, // 50ms to 100ms refresh window
}
machine.WindowWatchdog.Configure(config)
machine.WindowWatchdog.Start()
println("updating wwdg for 1 second")
for i := 0; i < 10; i++ {
time.Sleep(75 * time.Millisecond) // middle of the window
machine.WindowWatchdog.Update()
println("alive")
}
println("entering tight loop (will reset)")
for {
time.Sleep(10 * time.Millisecond)
}
}
+23
View File
@@ -2208,6 +2208,9 @@ func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe
//go:linkname hashmapMakeReflect runtime.hashmapMakeReflect //go:linkname hashmapMakeReflect runtime.hashmapMakeReflect
func hashmapMakeReflect(keySize, valueSize, sizeHint uintptr, keyType unsafe.Pointer) unsafe.Pointer func hashmapMakeReflect(keySize, valueSize, sizeHint uintptr, keyType unsafe.Pointer) unsafe.Pointer
//go:linkname chanMake runtime.chanMake
func chanMake(elementSize uintptr, bufSize uintptr) unsafe.Pointer
// MakeMapWithSize creates a new map with the specified type and initial space // MakeMapWithSize creates a new map with the specified type and initial space
// for approximately n elements. // for approximately n elements.
func MakeMapWithSize(typ Type, n int) Value { func MakeMapWithSize(typ Type, n int) Value {
@@ -2254,6 +2257,26 @@ func MakeMap(typ Type) Value {
return MakeMapWithSize(typ, 8) return MakeMapWithSize(typ, 8)
} }
// MakeChan creates a new channel with the specified type and buffer size.
func MakeChan(typ Type, size int) Value {
if typ.Kind() != Chan {
panic(&ValueError{Method: "MakeChan", Kind: typ.Kind()})
}
if size < 0 {
panic("reflect.MakeChan: negative buffer size")
}
if typ.(*RawType).ChanDir() != BothDir {
panic("reflect.MakeChan: unidirectional channel type")
}
elem := typ.Elem().(*RawType)
ch := chanMake(elem.Size(), uintptr(size))
return Value{
typecode: typ.(*RawType),
value: ch,
flags: valueFlagExported,
}
}
func (v Value) Call(in []Value) []Value { func (v Value) Call(in []Value) []Value {
panic("unimplemented: (reflect.Value).Call()") panic("unimplemented: (reflect.Value).Call()")
} }

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