fix(hir,codegen,runtime): class elements/subclass/dstr test262 tail (native-region-safe)#4997
Merged
Merged
Conversation
Three shared root causes plus stragglers, +67 tests (94.5% -> 96.0%) on the
class elements/subclass/dstr dirs, zero regressions:
1. GetIterator ignored a patched/deleted Array.prototype[Symbol.iterator]:
js_get_iterator short-circuited every array to the builtin values
iterator. Sticky ARRAY_PROTO_ITERATOR_MODIFIED flag (noted by the
symbol set/delete paths) gates the fast path; when modified, the
patched method is read off the prototype and called with this=array,
and a deleted method throws TypeError. Also fixes array_prototype_addr
caching 0 when first probed during runtime init. Covers the async-gen
dstr *-array-prototype / iter-get-err families (~50 tests).
2. Static-method 'this' was a compile-time class-ref literal, so
C.m.call({}) / inherited D.m() never saw the real receiver and static
private brand checks could not fire. New one-shot STATIC_THIS_OVERRIDE
(object/mod.rs): armed by js_class_static_method_call, the
Function.prototype call/apply arms (for static bound-method values),
the codegen static-dispatch tower, and inherited direct calls
(js_static_this_arm_classref); consumed by js_static_this_resolve in
the compile_static_method prologue, falling back to the lexical
class-ref for direct calls. Covers the static-private-* brand-check
family (~30 tests) and the wrongly-throwing
private-method-referenced-from-static-method cases.
3. 'this' in static field initializers evaluated in module-init context
(implicit this) instead of the class constructor: substitute lexical
this (including arrow bodies, which compile from these exprs) with
ClassRef at class lowering; dedup init_static_fields_late against the
inline spec-position StaticFieldSet stmts so initializers no longer
run twice nor clobber user reassignments.
Stragglers: static private getters/setters register on the static-accessor
side (and the instance-ABI direct accessor call is gated off for them);
static fields get real own-property descriptors (CLASS_DYNAMIC_PROPS
registration + getOwnPropertyDescriptor support, computed string keys
become real static props with the 'prototype' TypeError per spec);
NamedEvaluation for anonymous functions in field initializers;
__lookupGetter__/__lookupSetter__/__defineGetter__/__defineSetter__
dispatch on class instances; for (o.#f of iter) loop heads compile with
the brand-guarded private write.
862a55e to
d4fb220
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Targets the biggest remaining class-leaf test262 gaps:
language/{statements,expressions}/class/elements(+ subclass, subclass-builtins, dstr). Verified withscripts/test262_subset.pyagainst the node v26 oracle on the 48-core box.Class dirs (6 target dirs, 4201 judged): 3968 → 4035 pass, parity 94.5% → 96.0% (+67), zero regressions (per-round failure-set diffs).
Broad regression shard (
--shard 0/12 built-ins language, 2645 judged): +8 fixed, ZERO regressed vs a from-scratch merge-base baseline build (set-diff, both runs on an idle box).Three shared root causes
1. GetIterator ignored a patched/deleted
Array.prototype[Symbol.iterator](~50 tests)js_get_iteratorshort-circuited every array to the builtin values iterator, so the whole async-gen dstr*-array-prototype/iter-get-errfamily diverged (sync variants are filtered out byfeatures-applicable.txt, so only async showed). Fix: stickyARRAY_PROTO_ITERATOR_MODIFIEDflag (same shape asARRAY_PROTO_HAS_INDEX) noted by the symbol-property set and delete paths; when flipped, GetIterator reads the patched method off the prototype, calls it withthis === array, and throws TypeError when deleted. Also fixesarray_prototype_addr()permanently caching 0 when first probed during runtime init (before globalArraymaterializes).2. Static-method
thiswas a compile-time class-ref literal (~30 tests)C.m.call({})and inheritedD.m()ran withthis === Cbaked into the LLVM body, so the (already-correct!)js_private_guardbrand checks could never fire, andC.g.call(new C())for an instance-private read wrongly threw. Fix — no ABI change: a one-shotSTATIC_THIS_OVERRIDEthread-local, armed byjs_class_static_method_call(arm-if-unarmed so an outer call/apply thisArg wins), theFunction.prototype.call/applyarms (only when the target is a static bound-method value), the codegen static-dispatch tower, and inherited direct calls; consumed by a newjs_static_this_resolve(lexical_classref)prologue call incompile_static_method. Direct calls keep the lexical class-ref fallback. GC-scanned like IMPLICIT_THIS.3.
thisin static field initializers saw the module's implicit this (~10 tests)The HIR's inline
StaticFieldSetstmts (the spec-position evaluation) run in module-init context whereExpr::Thisreads implicit this; the correctly-seededinit_static_fields_latepass ran AFTER user code (too late) and double-evaluated every initializer. Fix: substitute lexicalthis→Expr::ClassRefin place onclass.static_fieldsat class lowering (in-place matters: closure bodies compile from the class's copy — substituting a stmt-level clone desyncs the closure creation site from its compiled body), and dedup the late pass against inline-emitted fields (also stops initializer side effects firing twice / clobbering user reassignments).Stragglers fixed
CLASS_DYNAMIC_PROPSregistration at init +getOwnPropertyDescriptorsupport on class refs (verifyProperty family); computed string-keyed static fields become real props, with the specTypeErrorfor a static field namedprototype.static f = function(){}→.name === "f", incl.#field).__lookupGetter__/__lookupSetter__/__defineGetter__/__defineSetter__dispatch on class instances.for (o.#f of iter)loop heads compile with the brand-guarded private write (was a compile error).Verification
cargo test --release -p perry-runtime(RUST_TEST_THREADS=1): 1023 passed, 0 failed.compiler_output_regression.py suite --suite native-region-proof→ status pass, all 7 workloads, no failed_workloads.cargo fmt --allclean, file-size gate OK. No version/changelog edits (maintainer folds at merge).Deferred tail (~144 remaining in the dirs)
async-gen
yield*return/throw protocol (~46, state-machine risk), subclass-builtins exotic constructors (~25), private-on-proxy, per-evaluation private brands (multiple-evaluations family),[self.#f]computed-key visibility,static constructor(){}SWC parse (TS1089), private-method-value.call(plainObj)(BOUND_METHOD by-name re-resolution),static h;descriptor.