Skip to content

runtime/wasm: add timers and host event wakeups (based on #102) - #107

Draft
cpunion wants to merge 5 commits into
codex/fork-wasm-blocking-primitives-20260801from
codex/fork-wasm-timers-host-wakeup-20260801
Draft

runtime/wasm: add timers and host event wakeups (based on #102)#107
cpunion wants to merge 5 commits into
codex/fork-wasm-blocking-primitives-20260801from
codex/fork-wasm-timers-host-wakeup-20260801

Conversation

@cpunion

@cpunion cpunion commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Depends on #102.

Fork-only staging PR for xgo-dev#2152 (stage C). It is rebuilt directly from the validated B head and avoids CI load on xgo-dev/llgo; the old upstream xgo-dev#2204 draft remains frozen.

Problem

The single-worker wasm runtime can park goroutines, but it has no host event source for time. time.Sleep, timers, tickers, timeout selects, and AfterFunc therefore cannot suspend one G while allowing the worker to run another G and later resume the timer owner.

Implementation

  • Add a private runtime/internal/wasmevent min-heap with stable timer ordering, Stop/Reset semantics, periodic deadline catch-up, and lazy dispatch.
  • Poll due events from the wasm scheduler. When the run queue is empty, wait through the host adapter; callbacks run only after control returns to the scheduler, so Asyncify is not synchronously re-entered during unwind/rewind.
  • Use emscripten_sleep for J32/J64 host wakeups and poll(NULL, 0, timeout) for default P1. Preserve the explicit LLGO_WASI_THREADS=1 backend.
  • Use a monotonic clock for runtime deadlines while retaining wall-clock and local-time-zone behavior for time.Now.
  • Lower standard-library //go:wasmimport declarations to LLVM wasm import attributes. Directive collection remains in the existing package syntax pass, and metadata is carried by ssa.Program across package/cache compilation.
  • Support the current Emscripten unified EM_INVOKER contract while retaining the legacy adapter.

The compiler directive scanner, SSA metadata, timer queue, scheduler, and host adapters remain separate. This PR introduces no new target or build tag. Static wasm defer dispatch is inherited through #102's W base and is not part of this effective diff.

Semantics covered

One acceptance fixture runs on J32, J64, and P1 and covers wall clock/local zone, time.Sleep, active/stopped/reset/expired timers, tickers, AfterFunc, and timeout select. Existing scheduler normal/deadlock behavior is also re-executed on all three profiles.

Validation

All local builds used GOMAXPROCS=2, GOMEMLIMIT=6GiB, -p=1 where applicable, fresh -a builds, and LLGO_BUILD_CACHE=off. The Ubuntu container had 2 CPUs and a 6 GiB hard memory limit.

  • macOS arm64: changed directive/import tests, full ssa, and the full nested runtime module pass.
  • Ubuntu amd64, Go 1.26.5: changed directive/import tests and the full nested runtime module pass.
  • J32, J64, and P1 execute the timer fixture with wasm timers ok; P1 also passes wasm-tools validate --features all and Wasmtime.
  • J32/J64/P1 pass at -O0 and -O3; J64/P1 pass ThinLTO.
  • Existing scheduler normal and deadlock fixtures pass on J32/J64/P1.
  • Explicit P1 threads builds and passes wasm validation.
  • runtime/internal/wasmevent is 93.9% covered; dispatch and due-event paths are 100% covered. The new directive collection and SSA wasm-import accessors are 100% covered.
  • git diff --check and gofmt checks pass.\n- Current head a5305b8ee has 39 passing checks, one expected release-publication skip, and no failures; the PR is clean, mergeable, and has no unresolved review threads.

Cost

On Apple M4 Max with GOMAXPROCS=2, queue Reset+RunDue measures 7.653-8.021 ns/op, 0 B/op, 0 allocs/op across five runs.

Against #102 with identical default builds of a scheduler fixture that does not import time:

Profile #102 C Delta
J32 185,913 B 185,919 B +6 B
J64 194,208 B 194,214 B +6 B
P1 531,112 B 533,906 B +2,794 B

The representative native binary remains 84,864 B with identical section sizes. The Cortex-M4 ELF is byte-identical at 1,804 B (text/data/bss = 140/0/10).

Independent diff over #102: 25 files, +1100/-84. No previously runnable test is skipped or ignored.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

a5305b8ee7cc | workflow run | long-term charts

Program measurements

Platform Workload File size vs main Build vs main Run vs main
Linux cprintf 18784 B +1.3% (worse) 354.115 ms -14.1% (better) 1.302 ms -16.1% (better)
Linux fmtprintf 2219256 B +0.3% (worse) 3.474 s -9.9% (better) 2.428 ms -26.1% (better)
Linux println 71744 B -1.3% (better) 353.071 ms -11.8% (better) 1.640 ms -2.0% (better)
macOS cprintf 84672 B +0.0% 325.156 ms -21.5% (better) 2.738 ms -23.7% (better)
macOS fmtprintf 2361968 B +0.7% (worse) 2.889 s -26.7% (better) 23.252 ms -0.5% (better)
macOS println 125712 B -1.0% (better) 354.542 ms -14.5% (better) 3.374 ms -53.5% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs main
Linux BenchmarkLookupPCRandom 13.480 ns/op +0.0%
Linux BenchmarkMergeCompilerFlags 163.200 ns/op +5.6% (worse)
Linux BenchmarkMergeLinkerFlags 101.300 ns/op +0.0%
Linux BenchmarkChannelBuffered 33.720 ns/op -3.0% (better)
Linux BenchmarkChannelHandoff 25725 ns/op -19.7% (better)
Linux BenchmarkDefer 44.710 ns/op -20.6% (better)
Linux BenchmarkDirectCall 1.590 ns/op +1.7% (worse)
Linux BenchmarkGlobalRead 1.868 ns/op +19.7% (worse)
Linux BenchmarkGlobalWrite 2.486 ns/op -0.3% (better)
Linux BenchmarkGoroutine 31374 ns/op -58.5% (better)
Linux BenchmarkInterfaceCall 8.094 ns/op +3.8% (worse)
Linux BenchmarkRuntimeGetG 1.866 ns/op -64.8% (better)
macOS BenchmarkLookupPCRandom 11.690 ns/op -6.0% (better)
macOS BenchmarkMergeCompilerFlags 114 ns/op -8.4% (better)
macOS BenchmarkMergeLinkerFlags 70.130 ns/op +1.1% (worse)
macOS BenchmarkChannelBuffered 23.650 ns/op -14.7% (better)
macOS BenchmarkChannelHandoff 9833 ns/op +40.3% (worse)
macOS BenchmarkDefer 31.500 ns/op -15.8% (better)
macOS BenchmarkDirectCall 1.074 ns/op -26.1% (better)
macOS BenchmarkGlobalRead 1.116 ns/op -7.8% (better)
macOS BenchmarkGlobalWrite 1.097 ns/op -26.2% (better)
macOS BenchmarkGoroutine 23980 ns/op -24.3% (better)
macOS BenchmarkInterfaceCall 4.624 ns/op -14.3% (better)
macOS BenchmarkRuntimeGetG 2.206 ns/op -14.4% (better)

Compared only with the latest matching platform in the main series.

Warning

  • Persistent publishing is unavailable because no data token is configured.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant