fix(wasm): #1323 dispatch timer builtins through mem_call bridge - #1329
Merged
Conversation
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).
4 tasks
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1323 — on
--target web/--target wasm,setTimeout/setIntervalcallbacks never fired. Top-level code ran, but every deferred callback was silently dropped.Root cause
The timer builtins lower to
Expr::ExternFuncRef { name: "setTimeout" }(viais_builtin_functioninperry-hir), but the WASM emitter had no call-site handler for them. The genericExternFuncRefarm inemit/expr/calls.rslooks the name up infunc_name_map, doesn't find it (timers aren't user/FFI functions), and falls through to its else branch — which drops the args and pushesundefined. The timer was never scheduled.The
__memDispatch.set_timeout/set_interval/clear_timeout/clear_intervalbridges inwasm_runtime.jsalready existed and were correct, and the names were already interned instring_collection.rs— only the emitter wiring was missing. (Thert.set_timeoutdirect-import variant is declared but never called.)Fix
Intercept the four timer builtins in
calls.rsbefore the genericExternFuncRefarm and route them through the existingmem_callbridge, mirroring howfetch/closure_calldispatch:setTimeout/setInterval→set_timeout/set_intervalwith(closure, delay), returning the timer id.clearTimeout/clearInterval→clear_timeout/clear_intervalwith(id).Missing args (e.g.
setTimeout(fn)) are padded withundefined. TrailingsetTimeout(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-clearingsetIntervalplus asetTimeoutthat printstop/tick/done.Verified through the Node harness (
tests/wasm/run_wasm_tests.sh, which evals the generated<script>s under real Node timers):top,tick,done(interval fires once, clears itself, timeout fires) and Node exits cleanly.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.