Skip to content

fix(runtime): built-ins/Object test262 parity (v3) — exotic expandos, array descriptors, Object statics#4863

Merged
proggeramlug merged 1 commit into
mainfrom
object-v3-parity
Jun 10, 2026
Merged

fix(runtime): built-ins/Object test262 parity (v3) — exotic expandos, array descriptors, Object statics#4863
proggeramlug merged 1 commit into
mainfrom
object-v3-parity

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Third pass on built-ins/Object test262 parity (after #4784 / #4789). Groups the remaining ~311 failures by root cause and fixes the five biggest clusters.

built-ins/Object: 2862/3173 (90.2%) → 3006/3173 (94.7%), +144 tests, zero regressions.

Root causes

1. Exotic expando properties — Date / RegExp / Error (~90 tests)

New crates/perry-runtime/src/object/exotic_expando.rs: own user properties for instances whose heap form is not an ObjectHeader (DateCell / RegExpHeader / ErrorHeader). test262 exercises these in both directions: as defineProperty targets and as the attributes bag (dateObj.value = "x"; Object.defineProperty(o, k, dateObj)).

  • Values in an insertion-ordered side table keyed by cell address (Error delegates to the existing ERROR_USER_PROPS store); attributes/accessors reuse the generic PROPERTY_DESCRIPTORS / ACCESSOR_DESCRIPTORS tables.
  • Wired into set (js_object_set_field_by_name guard + js_put_value_set), get (per-kind arms), defineProperty, getOwnPropertyDescriptor, getOwnPropertyNames, keys, has/hasOwnProperty, delete, propertyIsEnumerable, freeze/seal, TestIntegrityLevel.
  • A registered GC mutable-root scanner keeps stored values alive; Date/RegExp allocation clears stale entries on address reuse.
  • Writes that resolve to an accessor on the builtin prototype (Object.defineProperty(Date.prototype, "p", {set})) invoke the setter instead of creating an own property; Error reads fall back to <Kind>.prototypeError.prototype.

Previously: Date expando sets segfaulted (ordinary_set_with_receiver bit-cast the 8-byte cell as ObjectHeader), RegExp sets wrote through garbage field offsets, Error attribute bags read as empty.

2. Array descriptor semantics (~70 tests)

  • Named-key accessors on array targets were silently dropped (define_array_property stored undefined as data and discarded get/set).
  • Accessor/attrs dispatch added to the raw numeric fast paths (array_numeric_raw_f64_get/set_inbounds, js_array_get/set_f64_unchecked, js_array_set_f64_extend), gated by a new per-array OBJ_FLAG_ARRAY_DESCRIPTORS header bit (set on first defineProperty against the array) so ordinary arrays pay one header-flag test, not a side-table probe.
  • defineProperty(arr, "length", {writable:false}) now honored by getOwnPropertyDescriptor, arr.length = writes, and extending index stores.
  • A generic (attrs-only) redefine no longer converts an accessor index back to data; accessor→data conversions default writable to false (spec: retained-attrs rule doesn't cross the kind switch — both the array and ordinary-object paths); data→accessor keeps the existing dense element's enumerability.
  • [[DefineOwnProperty]] stores clear stale attrs first (so the new [[Set]]-side writability gate can't reject them) and materialize the element before installing the accessor (so the internal write doesn't fire the brand-new setter).

3. Object statics reified (~35 tests)

install_builtin_constructor_statics was missing seal / isSealed / isFrozen / isExtensible / preventExtensions / is / setPrototypeOf / getOwnPropertySymbols / getOwnPropertyDescriptors / defineProperties / groupBy (and Object.create had length 1 with no Properties-arg support). Reading e.g. Object.seal as a value produced a raw function-pointer numbertypeof said "number", .length/.name/hasOwnProperty all failed. Codegen value reads now route through the reified closures for names unique to Object; the Reflect-overlapping names (defineProperty, getOwnPropertyDescriptor, get/setPrototypeOf, isExtensible, preventExtensions) and Map-overlapping groupBy keep their old behavior because the HIR collapses the receiver to a GlobalGet(0) sentinel (documented; ~8 tests deferred).

4. freeze/seal/TestIntegrityLevel on non-ObjectHeader receivers

mark_all_keys bit-cast TypedArray / closure / exotic receivers as ObjectHeader and walked a garbage keys_array. The Object.seal(new Int8Array()) crash was latent on main — this PR's extra closure installs shifted the heap layout and surfaced it. TypedArrays now use the GC flag bits only; closures and exotics freeze/seal via side-table attrs; object_integrity_level got matching arms.

5. Smaller fixes

  • Symbol-keyed writes honor FROZEN / NO_EXTEND / per-symbol writability (silent rejection, matching the sloppy-mode oracle).
  • Object.getPrototypeOf(primitive) returns the wrapper prototype (Number.prototype etc.); INT32-tagged class refs are explicitly excluded from the number arm.
  • Array Object.keys / getOwnPropertyNames include accessor-only properties.
  • js_put_value_set relocated to proxy/put_value.rs (proxy.rs sat at 1999 lines, over the 2000-line gate with any addition).

Validation

  • built-ins/Object sweep (box, jobs 24–40, node v26 oracle): 2862 → 3006 pass; failure-set diff against the baseline run shows +144 fixed / 0 regressed (4–6 transient "compile-fail: Wrote executable" entries under load all pass isolated, including 20× stress runs of the seal-TA tests).
  • Broad regression shard --shard 0/12 over built-ins language vs a clean main (8ff2c81) build: main 279 fails → branch 261 fails; 18 fixed, 0 regressed (bonus fixes in RegExp/Array/Temporal/literals).
  • RUST_TEST_THREADS=1 cargo test --release -p perry-runtime: green (the two raw-f64 layout tests initially tripped the process-global descriptor gate — that's what motivated the per-array OBJ_FLAG_ARRAY_DESCRIPTORS bit).
  • cargo fmt --all + scripts/check_file_size.sh green.

Deferred (documented for the next pass)

arguments-object defineProperty tail; Array.prototype-as-target index defines; entries/values observable-operations ordering; Object.assign OwnPropertyKeys ordering + proxy source traps; gOPD of global builtins (eval/parseInt); Reflect-overlap static value reads; bound-function writes through inherited Function.prototype accessors.

Per CONTRIBUTING guidance for external PRs, no version bump / changelog edits — maintainer folds those in at merge.

… array descriptors, Object statics

built-ins/Object: 2862/3173 (90.2%) -> ~3001/3173 (94.6%), +143 tests, zero
regressions (verified by failure-set diff against the baseline sweep).

Root causes, by cluster size:

1. Exotic expando properties (Date/RegExp/Error, ~90 tests). New
   object/exotic_expando.rs: own user properties for instances whose heap
   form is not an ObjectHeader (DateCell / RegExpHeader / ErrorHeader).
   Values live in an insertion-ordered side table keyed by cell address
   (Error delegates to the existing ERROR_USER_PROPS store); attributes and
   accessors reuse the generic PROPERTY_DESCRIPTORS / ACCESSOR_DESCRIPTORS
   tables. Wired into set (js_object_set_field_by_name guard +
   js_put_value_set), get (per-kind arms), defineProperty, gOPD, gOPN,
   keys, has/hasOwnProperty, delete, propertyIsEnumerable, freeze/seal,
   TestIntegrityLevel. A registered GC scanner keeps stored values alive;
   Date/RegExp allocation clears stale entries on address reuse. Writes
   falling through to an accessor on the builtin prototype invoke the
   setter instead of creating an own property; Error instance reads fall
   back to <Kind>.prototype then Error.prototype. Previously: Date expando
   sets crashed in ordinary_set_with_receiver (cell bit-cast as
   ObjectHeader), RegExp sets wrote through garbage field offsets, and
   ToPropertyDescriptor couldn't read exotic attribute bags.

2. Array descriptor semantics (~70 tests). Named-key accessors on array
   targets were silently dropped (define stored undefined as data);
   accessor get/set now dispatches on the named path, the dynamic index
   paths, and the raw numeric fast paths (array_numeric_raw_f64_get/
   set_inbounds, js_array_get/set_f64_unchecked, js_array_set_f64_extend
   gate on descriptors_in_use). defineProperty(arr,'length',
   {writable:false}) is now honored by gOPD, arr.length writes, and
   extending index stores. A generic (attrs-only) redefine no longer
   converts an accessor index back to data; accessor->data conversions
   default writable to false instead of retaining the accessor's
   placeholder attrs; data->accessor keeps the existing dense element's
   enumerability. [[DefineOwnProperty]] stores clear stale attrs first so
   the new [[Set]]-side writability gate can't reject them, and the
   element is materialized before the accessor is installed so the
   internal write doesn't fire the brand-new setter.

3. Object statics reified (~35 tests). install_builtin_constructor_statics
   now installs seal/isSealed/isFrozen/isExtensible/preventExtensions/is/
   setPrototypeOf/getOwnPropertySymbols/getOwnPropertyDescriptors/
   defineProperties/groupBy as real closures (Object.create length fixed
   to 2, with Properties-arg support); codegen value reads of
   Object.<static> (GlobalGet sentinel) route to those closures for names
   unique to Object — the Reflect-overlapping names (defineProperty,
   getOwnPropertyDescriptor, get/setPrototypeOf, isExtensible,
   preventExtensions) and Map-overlapping groupBy keep their old behavior
   since the HIR collapses the receiver name.

4. freeze/seal/TestIntegrityLevel on non-ObjectHeader receivers. The
   mark_all_keys walk bit-cast TypedArray/closure/exotic receivers (the
   seal-int8array crash was latent and surfaced when this PR's extra
   closure installs shifted the heap layout). TypedArrays now use the GC
   flag bits only; closures and exotics freeze/seal via side-table attrs.

5. Smaller fixes: symbol-keyed writes honor FROZEN/NO_EXTEND/per-symbol
   writability; Object.getPrototypeOf(primitive) returns the wrapper
   prototype (class-ref INT32 values excluded); array Object.keys/gOPN
   include accessor-only properties; js_put_value_set relocated to
   proxy/put_value.rs for the 2000-line gate.
@proggeramlug proggeramlug merged commit a1c9190 into main Jun 10, 2026
13 checks passed
@proggeramlug proggeramlug deleted the object-v3-parity branch June 10, 2026 03:08
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