Repository navigation
class X extends Array in an T[]-annotated binding takes the raw ArrayHeader fast paths (sibling of #7570) #7574
Description
Sibling of #7570 (fixed by #7573) on a different family. Filed from the sweep
that PR did of every declared-type-keyed lowering; not fixed there because it is
a separate set of code paths with a different guard already half-present.
Shape
class X extends Array produces a plain ObjectHeader, not an
ArrayHeader — stated outright in crates/perry-runtime/src/array/subclass.rs:1-7
("perry has no exotic array-object representation"), and js_array_subclass_init
(node_stream_constructors/builders.rs:98) installs the elements as ordinary
object properties, with no ArrayHeader backing (unlike
js_map_set_subclass_init, which does allocate a real hidden Map/Set).
ObjectHeader and ArrayHeader overlay:
ArrayHeader |
offset | reads on an ObjectHeader |
|---|---|---|
length: u32 |
0 | object_type (= 1) |
capacity: u32 |
4 | class_id |
| (elements begin) | 8 | parent_class_id ‖ field_count, then keys_array, then meta |
is_array_expr (crates/perry-codegen/src/type_analysis/predicates.rs:401) is
satisfied by a declared Type::Array(_) / Generic { base: "Array" }, so a
binding annotated with the base type and holding a subclass instance takes the
raw lowering — the same "a declared type is a hint, never a layout fact" premise
as #7570.
class MyArr<T> extends Array<T> {}
const a: number[] = new MyArr<number>();
a.push(1);
console.log(a.length, a[0]);Tiers that do NOT brand-check
crates/perry-codegen/src/expr/index_get.rs:531—lower_bounded_array_index_get
(the hoisted-arr.lengthloop fast path) tests onlyGC_TYPE_LAZY_ARRAY,
GC_FLAG_FORWARDEDandOBJ_FLAG_ARRAY_DESCRIPTORS, then does a raw
gep+load doubleathandle + 8 + idx*8— straight into
parent_class_id ‖ field_count, thenkeys_array, thenmeta.crates/perry-codegen/src/expr/property_get.rs:236— inlinearr.lengthis a
baresafe_load_i32_from_ptr(recv)at offset 0, no GC-type test, so it reports
object_type(= 1).crates/perry-codegen/src/lower_call/property_get.rs:215→lower_array_method
(js_array_pop_f64,js_array_join,js_array_sort,js_array_reverse, …).crates/perry-codegen/src/expr/property_set.rs:383→js_array_set_length_strict.crates/perry-codegen/src/expr/index_set.rs— the guard-free
Ptr<NumArray>store arm (try_lower_num_array_guard_free_set).
The tier that already does it right
crates/perry-codegen/src/expr/index_get/guarded_array.rs:22-100 loads the GC
header byte at handle - 8 and tests icmp eq i8 %gc_type, 1 (GC_TYPE_ARRAY)
before any slot load, branching to a generic fallback otherwise. An Array
subclass instance takes the fallback there and behaves correctly.
The recognition machinery also already exists on the runtime side and is used by
js_native_call_method — array::subclass::is_array_subclass_instance (:46,
which requires GC_TYPE_OBJECT via try_read_gc_header) and
array_subclass_dense_snapshot (:77, used by iteration/spread) — so
class X extends Array works fine through the generic dispatch. It is only the
declared-type fast paths that are unsafe.
Suggested fix
Whichever of the two #7573 considered:
- extend the
guarded_array.rsGC_TYPE_ARRAYtest to the other tiers (keeps
the fast path, costs one header-byte load + branch), or - give the raw
js_array_*entries a receiver resolution the way fix(runtime): a Map/Set subclass in a base-typed binding was read as a raw header (#7570) #7573 gave
clean_map_ptr/clean_set_ptrone, so it is fail-closed for every future
caller. Note this family has no hidden backing to redirect to — an Array
subclass keeps its elements as object properties — so the runtime-side answer
is "fall back to the generic array-like path", not "redirect".
The second is the one that cannot be forgotten at a new call site; the first is
the smaller diff.
Activity
fix(runtime): an Array subclass in a base-typed binding was read as a…
fix(runtime): an Array subclass in a base-typed binding was read as a…
fix(runtime): an Array subclass in a base-typed binding was read as a…
fix(runtime): an Array subclass in a base-typed binding was read as a…
fix(array): ask the typed-array question before clean_arr_ptr rejects…
fix(array): ask the typed-array question before clean_arr_ptr rejects…
fix(array): ask the typed-array question before clean_arr_ptr rejects…
fix(array): ask the typed-array question before clean_arr_ptr rejects it
perf(codegen): inline append tier for Array-subclass elements receive…