Repository navigation
perf: class-field inline fast path (#5093) is disabled process-wide in normal runs #5654
Description
Summary
The codegen-inlined class-field shape guard (#5093) — the
class_field_inline.deref precheck that lets this.field read/write skip the
js_typed_feedback_class_field_*_guard call — never engages in ordinary
programs. Every class-field access pays a full guard call (with a thread-local
TYPED_LAYOUTS lookup) on every iteration, even for a trivial monomorphic class.
Evidence
- A/B on
benchmarks/suite/09_method_calls.ts:
PERRY_DISABLE_CLASS_FIELD_INLINE=1→ 2898 ms vs default → 2925 ms.
If the inline precheck were active by default, force-disabling it would slow
the loop down; it doesn't — so the inline path is already inactive by default. - Instrumentation: printing on the first flip of
PERRY_CLASS_FIELD_INLINE_GUARD_DISABLEDshows it is set to1during runtime
startup, beforemainruns — even for a single-class program with no
Object.defineProperty/ accessors. - Disassembly of the hot loop confirms both
js_typed_feedback_class_field_get_guardand_set_guardarebl-called every
iteration; theclass_field_inline.derefbranch is gated off because the
volatilePERRY_CLASS_FIELD_INLINE_GUARD_DISABLEDbyte reads non-zero.
Impact
~295 ns per class-field access (guard call + contract + TLS lookup) instead of
the slot-direct handful-of-ns the inline precheck was designed to deliver. This
affects all OOP code — the whole #5093 inline machinery is effectively dead
in normal runs.
Likely cause
PERRY_CLASS_FIELD_INLINE_GUARD_DISABLED only flips via
disable_class_field_inline_guard(). Its callers are set_property_attrs /
set_accessor_descriptor (descriptor install) and js_gc_init (gated on
typed-feedback tracing / PERRY_VERIFY_TYPED_INTACT / PERRY_DISABLE_*, none of
which are set here). Since no env flags are set, a property descriptor is being
installed during startup (builtin/prototype setup) that trips the process-wide
global and permanently disables the inline path for the rest of the process.
The gate is monotonic and process-wide; a finer-grained gate (disable the inline
fast path only for the specific objects/prototypes that actually carry
descriptors, instead of globally) would let unrelated class instances keep the
inline fast path.
Repro
class C { value: number; constructor() { this.value = 0; } }
const c = new C();
const N = 10_000_000;
const s = Date.now();
for (let i = 0; i < N; i++) c.value = c.value + 1;
console.log(Date.now() - s); // ~2900 ms; Node ~12 msRelation to PR
PR perf(codegen): register typed-shape layout on standalone-constructor new path fixes a different, compounding bug (the layout was never registered on
the default new path, forcing the by-name hashmap fallback — ~2× on its own).
Once the inline precheck above is re-enabled, the typed layout that PR registers
is what the precheck reads, so the two together should close most of the
09_method_calls gap.
Activity
fix(codegen): resolve class-field index/type via the authoritative in…
perf(runtime,codegen): stop unrelated descriptor installs disabling t…
fix(codegen): versioned-loop preheader vets OBJ_FLAG_HAS_DESCRIPTORS …
fix(codegen): versioned-loop preheader vets OBJ_FLAG_HAS_DESCRIPTORS …
perf(codegen,runtime): #5093 — class-field loop versioning: monomorph…