Skip to content

fix(runtime): Promise async-resolution / microtask-ordering test262 tail#5026

Merged
proggeramlug merged 1 commit into
mainfrom
promise-async-v2-parity
Jun 11, 2026
Merged

fix(runtime): Promise async-resolution / microtask-ordering test262 tail#5026
proggeramlug merged 1 commit into
mainfrom
promise-async-v2-parity

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

built-ins/Promise test262: 506/574 (88.2%) → 511/574 (89.0%), +5, zero regressions (verified by diffing the per-test failure set before/after).

Two shared root causes in the async-resolution path:

1. Promise.prototype.finally called onFinally with one argument instead of zero. The wrapper invoked js_closure_call1(on_finally, undefined), so arguments.length reported 1. The spec invokes onFinally with no arguments — switched to js_closure_call0. Fixes the finally cases that assert a zero-arg cleanup invocation (resolution-value-no-override, rejection-reason-no-fulfill, rejection-reason-override-with-throw).

2. Native resolving functions read a garbage value when called with too few args. promise_resolve_fn/promise_reject_fn and the PromiseResolveThenableJob resolve/reject closures are extern "C" fn(closure, value). When a thenable's then does resolve() with no arguments (spec 27.2.1.3.2 step 9), js_closure_call0 fell to its zero-arg direct-call arm and the function read an uninitialized second register — surfacing as the denormal 5e-324 (bits = 1) and corrupting the resolution value. Registering these bodies' dispatch arity (= 1, once per thread) makes the missing value pad to undefined. Fixes exception-after-resolve-in-executor and exception-after-resolve-in-thenable-job.

Files

  • promise/then.rs — finally zero-arg invocation
  • promise/combinators.rsensure_native_resolving_arity_registered

Validation

test262_subset.py --dir built-ins/Promise against tc39/test262 @ 4249661, Node v26.3.0 oracle: pass 506→511, 0 newly-broken. (The remaining tail is dominated by SpeciesConstructor / subclassed-Promise capability machinery, deferred to avoid hot-path risk.)

…resolving-fn arity

built-ins/Promise test262 506/574 (88.2%) -> 511/574 (89.0%), +5, zero regressions.

Two root causes:

1. Promise.prototype.finally invoked onFinally with ONE argument
   (js_closure_call1(on_finally, undefined)) instead of zero. The spec calls
   onFinally with no arguments, so every test that asserts
   arguments.length === 0 inside the cleanup callback failed. Switched to
   js_closure_call0. (finally/resolution-value-no-override,
   rejection-reason-no-fulfill, rejection-reason-override-with-throw.)

2. The native resolving functions (promise_resolve_fn / promise_reject_fn and
   the PromiseResolveThenableJob resolve/reject) are extern "C" fn(closure,
   value). When a thenable's then calls resolve() with NO args (spec
   27.2.1.3.2), js_closure_call0 fell to its zero-arg direct-call arm and the
   function read a GARBAGE second register (surfacing as the denormal 5e-324,
   bits=1), corrupting the resolution value. Registering these bodies' dispatch
   arity (= 1) makes the missing value pad to undefined.
   (exception-after-resolve-in-executor, exception-after-resolve-in-thenable-job.)
@proggeramlug proggeramlug merged commit 1098dbc into main Jun 11, 2026
11 of 13 checks passed
@proggeramlug proggeramlug deleted the promise-async-v2-parity branch June 11, 2026 22:28
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