ReviewOS

zig-utils/zig-js

Function.arguments: return fresh detached argument snapshots

#1092
Closed chris opened this 8 days ago ·
8 days ago

Parent: #465, #491. Found while preparing captured-binding admission under #467; follows #1086.

Executed on 5b73904e against Node and direct system JavaScriptCore: Function.arguments currently aliases live parameters and the function's own arguments object, and repeat property reads return the same object.

Examples confirmed in both reference engines:

  • A nested function saves outer.arguments, then performs a += 3. Both references retain snapshot index 0 = 4 while the live parameter becomes 7; zig-js tree/required-VM returns index 0 = 7.
  • var one = f.arguments; a = 9; var two = f.arguments; one[0] = 22 yields [false,22,9,9] without an own arguments binding. Both zig-js modes yield [true,22,22,22].
  • When the function has an own arguments binding, snapshots retain the original argument values even as mapped parameters change. Deleting or replacing an index with a getter must not cause the snapshot read to invoke that getter. Both references return [4,0,9] for the accessor witness; both zig-js modes return [7,1,9].
  • Missing duplicate formals expose an additional tree/VM discrepancy: f(a,a) called with one argument preserves snapshot [1,3,null] in both references/VM while tree mode returns [1,9,null].

Scope: create a fresh detached snapshot for every active legacy property read; retain correct original/current argument sources, duplicate/missing/extra argument rules, non-simple callee poison, callee realm intrinsics, GC ownership, and OOM cleanup. Preserve the separately mapped ordinary arguments binding. Pin the JSC descriptor choice where Node differs (simple snapshot callee is enumerable in JSC). Correct existing native caller witnesses that assume mutating Function.arguments mutates the formal; the frame-preservation fix itself remains required.

Definition of done:

  • Reference-confirmed snapshots/identity/mutation/getter/duplicate cases match in tree, required-VM, and native-enabled execution.
  • Own mapped arguments semantics, strict/function-kind restrictions, realms, recursive active frames, and missing/extra/non-simple arguments remain exact.
  • Moving-GC/OOM/no-GIL and Debug/ReleaseSafe/unsuppressed TSan gates pass with exact-root ownership.
  • Exact-parent affected corpus outcomes and production build pass; small conventional fix/evidence pushed to main.
8 days ago

Landed in 11273292.

Every active property read now builds a fresh detached snapshot. Original inputs are retained for body arguments, non-simple parameters and direct eval; simple formals otherwise use canonical current bindings, with rightmost duplicate and missing/extra input rules. Ordinary mapped arguments retain their separate aliases. Construction roots the captured values and result, restores the caller realm on failure, and releases temporary storage even with GC disabled.

Reference controls were executed against Node and direct system JavaScriptCore. The fixtures pin identity/mutation, accessor non-invocation, duplicate/missing/extra inputs, non-simple callee poison, formal/body declarations named arguments, and direct eval. JSC's enumerable simple snapshot callee and body-function declaration choices are pinned where Node differs.

Validation:

  • 21 focused context/interpreter/VM tests passed in Debug, ReleaseSafe and unsuppressed TSan. Module flags precede each declaration; linked TSan instrumentation was verified. The new fixture covers tree/required-VM/JIT-enabled execution in serialized and no-GIL contexts, actual pointer relocation, and wide retained inputs. Allocation-failure cleanup was exercised with GC enabled and disabled; existing recursive, realm, restriction and four-worker native-caller fixtures pass.
  • Frontend 79 cases; default threadfuzz 400 and broad 300 programs, zero failures; production library/runner and GC relocation inventory passed.
  • Exact-parent corpus comparison against 8fcbf4d: 2,064 cases, zero default-tier failures before and after, zero flips and no new diverging paths. Counts: arguments-object 263, function expressions 264, function statements 451, Function builtins 509, calls 92, rest 11, Reflect.ownKeys 13, Object.keys 59, property names 45, eval-code 347 and eval builtin 10. The same three TCO witnesses differ only because forced tree mode fails while VM mode passes (tco-member-args.js, tco-non-eval-with.js, tco-call-args.js).

Before binary SHA-256: af6238ed45afb8ae6c123ef0e1c7e79d6d16e6070b978e25171b3db122235e84; after: 862aa2fc444bd404b895ceb9ab0917ca4c606d51ef85283accd578c7b3f33615.

The full combined local unit link remains blocked by #933's memory cap. Hosted integration run 38013873126 is queued on the subsequent documentation-only commit. #465, #467 and #491 remain open.

Sign in to comment on this issue.