Skip to content

fix(runtime,codegen): built-ins tails v2 (TypedArray/String/JSON/Proxy)#4900

Merged
proggeramlug merged 1 commit into
mainfrom
builtins-tail-v2-parity
Jun 10, 2026
Merged

fix(runtime,codegen): built-ins tails v2 (TypedArray/String/JSON/Proxy)#4900
proggeramlug merged 1 commit into
mainfrom
builtins-tail-v2-parity

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Mops several independent built-ins test262 tails. Differential vs node v26.3.0, 8 target dirs (Promise/String/JSON/Proxy/TypedArray/DataView + eval-code/function-code).

Parity: 87.6% → 89.0% over the 8 dirs (judged=3540). +50 tests fixed, 0 regressions (verified by diffing the full failure sets, not just the aggregate).

dir pass before → after compile-fail
TypedArray 739 → 770 (+31)
String 857 → 873 (+16) 16 → 1
Proxy 205 → 207 (+2)
JSON 108 → 109 (+1)

Root causes & fixes

1. TypedArray stringification (+~29). String(ta), `${ta}`, "" + ta, and ta.toString() produced "[object Object]" — or, heap-layout-dependently, threw TypeError: Cannot convert object to primitive value. A TypedArrayHeader is not an ObjectHeader, but ordinary_to_primitive_number_for_add (and js_jsvalue_to_string) bit-cast it as one and read garbage for the valueOf/toString field lookups — so a second TA in the program could throw where the first only mis-rendered. Detect TypedArrays via the registry (lookup_typed_array_kind, a by-value lookup, no deref) and stringify via js_typed_array_join(","), matching %TypedArray%.prototype.toString. Symbol.toPrimitive overrides are still honored. Files: value/to_string.rs.

2. String methods with 0 args (+15 compile-fails). "x".split(), "".indexOf(), "".lastIndexOf(), and s.localeCompare() all hard-errored at codegen (expects 1 or 2 args, got 0). These are valid: a missing separator/search/that argument coerces to undefined[S] / "undefined". Allow 0 args and default the missing operand to undefined; localeCompare now ToString-coerces a non-string that instead of bit-casting it as a string pointer. Files: lower_string_method.rs.

3. JSON.stringify circular arrays (+1, prevents crash). a=[]; a.push(a); JSON.stringify(a) infinitely recursed → native stack overflow (crash, no output). Object cycles were already detected past MAX_FAST_DEPTH, but the compact array path never bumps depth, so an all-array cycle never reached the guard. Track open-array pointers in STRINGIFY_STACK (push/check/pop, so a sibling array reused in two positions is not a false cycle) and throw TypeError: Converting circular structure to JSON. Cleared at the outermost js_json_stringify entry so a longjmp-out throw can't leak ancestors across top-level calls. Files: json/stringify.rs, json/stringify_api.rs.

4. Proxy with a Symbol target/handler (+2). new Proxy(Symbol(), {}) / new Proxy({}, Symbol()) must throw TypeError, but proxy_arg_is_object accepted any POINTER_TAG value — and a Symbol is a registered POINTER_TAG value. Exclude registered symbols. Files: proxy.rs.

Validation

  • cargo fmt --all clean; scripts/check_file_size.sh OK.
  • Affected perry-runtime unit tests (json/stringify/typed_array/to_string/proxy) green.
  • Full 8-dir differential: 50 fixed / 0 new failures.

Deferred (out of scope here)

  • Buffer-backed TA includes/indexOf and reduce (no-initial-value) are codegen-sensitive Heisenbugs (adding an unrelated alias of the receiver makes them pass) — a receiver-lowering/escape issue, risky to fix blind.
  • JSON.stringify object cycles routed through a toJSON that returns an ancestor (still crashes; pre-existing — hot-path object tracking deferred).
  • eval-in-parameter-scope lexical-conflict negatives (~24, AOT-hard).

… 0-arg methods, JSON array cycles, Proxy symbol args)
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Follow-up work staged but intentionally held back (to keep this PR's regression record clean)

While mopping these tails I also wrote two more correct fixes but am not including them here, because they each perturb SSA/heap layout enough to flip ~3 pre-existing buffer-backed-TypedArray codegen Heisenbugs — net-neutral on the score but regression-shaped, which muddies the zero-regression story. They belong in a follow-up that first stabilizes the underlying miscompile.

Held-back fix A — DataView.prototype.setBigInt64/setBigUint64 value-conversion ordering (+2). to_bigint_raw_or_throw (buffer/dataview.rs) threw TypeError directly for an object value instead of running ToPrimitive(value, "number") first, so dv.setBigInt64(100, {valueOf(){throw}}) surfaced the bounds error / a wrong TypeError instead of the poisoned-valueOf throw (SetViewValue calls ToBigInt before the bounds check).

Held-back fix B — String.prototype.split with >2 args (+1). lower_string_method.rs rejected s.split(a, b, c) at codegen; the spec ignores extras but they must still be evaluated for side effects.

Both are saved as a patch.

Root cause blocking them — buffer-backed TA includes/indexOf/lastIndexOf/reduce miscompile. new Float64Array(buf).includes(43) returns false (and indexOf-1) even though s[i], s.length, and String(s) all read correctly. The runtime is fine: instrumenting as_typed_array shows lookup_hit=true and the call then returns the right answer — i.e. adding any code (an eprintln, an unrelated var d:any = s alias, a harness print) makes it pass. So the generated code for the Expr::ArrayIncludes receiver passes a wrong/garbage pointer to js_array_includes_jsvalue under some escape/i32-locals/SSA state, and the result flips with the smallest perturbation. This is the same class as the reduce-no-initial-value "Reduce of empty array" failures. Fixing it (likely in receiver lowering / collectors/escape_* / i32_locals) would both stabilize these tests and unlock ~8-13 buffer-backed TA cases deterministically — and then fixes A/B can land cleanly.

@proggeramlug proggeramlug merged commit ec8f6ba into main Jun 10, 2026
12 of 13 checks passed
@proggeramlug proggeramlug deleted the builtins-tail-v2-parity branch June 10, 2026 11:59
proggeramlug added a commit that referenced this pull request Jun 14, 2026
…#5148)

Audit and update of the documentation after the recent Node.js parity sprint
(PRs ~#4900#5040+). Brings the public docs in line with current behavior:

- README: new "Node.js compatibility" section (~97% of Node's own test suite,
  ~95% overall) and a note that Perry compiles .js/.cjs/.mjs/.jsx source
  directly; fix the stale "Decorators: not supported" row (legacy TS decorators
  + emitDecoratorMetadata are supported).
- introduction / supported-features: add Node.js compatibility + JavaScript
  input sections.
- limitations: correct the stale "no SharedArrayBuffer or Atomics" claim
  (real cross-thread Atomics + SAB landed); note .js compilation.
- threading/overview: document the SharedArrayBuffer/Atomics shared-state path.
- runtime-parity-gaps: add a behavioral-status header.
- typescript-parity-gaps (v0.4.56) / typescript-compatibility-tests (v0.4.50):
  add prominent 'historical/outdated' banners pointing at the maintained
  source of truth.

Co-authored-by: Ralph Küpper <ralph2@skelpo.com>
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