Skip to content

fix(wasm): #1323 dispatch timer builtins through mem_call bridge - #1329

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-fix-web-timers-1323
May 22, 2026
Merged

fix(wasm): #1323 dispatch timer builtins through mem_call bridge#1329
proggeramlug merged 1 commit into
mainfrom
worktree-fix-web-timers-1323

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Fixes #1323 — on --target web/--target wasm, setTimeout/setInterval callbacks never fired. Top-level code ran, but every deferred callback was silently dropped.

Root cause

The timer builtins lower to Expr::ExternFuncRef { name: "setTimeout" } (via is_builtin_function in perry-hir), but the WASM emitter had no call-site handler for them. The generic ExternFuncRef arm in emit/expr/calls.rs looks the name up in func_name_map, doesn't find it (timers aren't user/FFI functions), and falls through to its else branch — which drops the args and pushes undefined. The timer was never scheduled.

The __memDispatch.set_timeout / set_interval / clear_timeout / clear_interval bridges in wasm_runtime.js already existed and were correct, and the names were already interned in string_collection.rs — only the emitter wiring was missing. (The rt.set_timeout direct-import variant is declared but never called.)

Fix

Intercept the four timer builtins in calls.rs before the generic ExternFuncRef arm and route them through the existing mem_call bridge, mirroring how fetch/closure_call dispatch:

  • setTimeout/setIntervalset_timeout/set_interval with (closure, delay), returning the timer id.
  • clearTimeout/clearIntervalclear_timeout/clear_interval with (id).

Missing args (e.g. setTimeout(fn)) are padded with undefined. Trailing setTimeout(fn, delay, ...args) extras are dropped for now (Node forwards them to the callback — out of scope here).

Test

Adds tests/wasm/23_timers.{ts,expected}: a self-clearing setInterval plus a setTimeout that prints top / tick / done.

Verified through the Node harness (tests/wasm/run_wasm_tests.sh, which evals the generated <script>s under real Node timers):

  • With the fix: prints top, tick, done (interval fires once, clears itself, timeout fires) and Node exits cleanly.
  • Without the fix (codegen change reverted): prints only top — reproducing the issue exactly.

The other 7 pre-existing wasm-suite failures (objects/arrays, higher-order, map/set, regex/date, class-field-splice, module-splice, ffi-imports) are unchanged by this PR; confirmed by running the suite with and without the codegen change.

setTimeout/setInterval/clearTimeout/clearInterval lower to
Expr::ExternFuncRef with no entry in func_name_map, so the generic
ExternFuncRef arm in emit/expr/calls.rs dropped their args and pushed
undefined — the timer was never scheduled and the callback never fired
on the web/wasm target. Top-level code ran; only deferred callbacks were
lost.

Intercept the four timer builtins before the generic arm and route them
through the mem_call bridge to __memDispatch.set_timeout/set_interval/
clear_timeout/clear_interval in wasm_runtime.js (which were already
present and correct), mirroring fetch/closure_call dispatch. The set_*
bridges take (closure, delay) and return the timer id; the clear_*
bridges take (id).

Adds tests/wasm/23_timers.{ts,expected}: a self-clearing setInterval +
setTimeout that prints top/tick/done. Without the fix only 'top' prints
(verified by reverting the codegen change).
@proggeramlug
proggeramlug merged commit af46fe9 into main May 22, 2026
9 checks passed
@proggeramlug
proggeramlug deleted the worktree-fix-web-timers-1323 branch May 22, 2026 10:33
proggeramlug added a commit that referenced this pull request May 22, 2026
…sweep (#1414)

Rolls up 26 PRs that merged to main post-v0.5.1023 without version
bumps:

- node:crypto gap-fixes (#1386 #1393 #1394 #1402 #1405): randomInt,
  timingSafeEqual, getHashes/getCiphers, sha224/sha384, base64 digest,
  Buffer hash input, no-arg digest() → Buffer, pbkdf2Sync digest arg,
  scryptSync.
- node:perf_hooks (#1321 + #1328 #1342 coverage): performance + User
  Timing + PerformanceObserver native impl, granular node-suite +
  edge-case coverage.
- #1090 GC checkpoint runtime work (#1324).
- #1311 geisterhand on iOS (#1316 #1383 #1384 #1385).
- #1312 process.env.X (unset) is nullish undefined (#1314).
- #1319 thread-safety hardening for cross-thread runtime statics.
- #1322 exact-head GC evidence packet.
- #1323 wasm timers dispatch through mem_call bridge (#1329).
- #1317 node:timers/promises shadow-segfault fix (#1326).
- #1330 node:process suite (#1331).
- #1292 bcrypt.hash() returns String (#1307).
- #1293 fastify .json()/.body external-fastify dispatch (#1308).
- #1296 app pattern performance gaps.
- #1297 diagnostics_channel parity.
- #1301 iOS App Groups capability (#1313).
- #1318 #1325 os/methods/modern-methods static dispatch.
- #1315 expanded Node parity test coverage.
- #1382 ui-ios stdlib pump for async fetch.
- #1392 ui-wasm reactive state + setText (#1404).
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.

web target: setTimeout/setInterval callbacks never fire (top-level runs; deferred callbacks dropped)

1 participant