fix(codegen): sloppy-mode class-field number stores keep the inline raw store - #7423
Merged
Conversation
added 4 commits
August 5, 2026 09:12
…aw store (#7288) A byte-identical .ts file compiled to a 46x slower object depending on where on disk it lived: 83 ms inside the Perry checkout, 3,762 ms anywhere else. The discriminator is strict mode, resolved by an upward walk for the nearest package.json -- Perry's root is "type": "module", so every file inside the checkout is strict and every file outside it is sloppy. put_value_static_property_fast_path barred sloppy code from the whole class-field store route (#6542) because that route's terminal fallback throws on a non-writable slot, which is right for strict PutValue and wrong for sloppy. That discarded the fast arm to fix the fallback arm. The #5093 inline precheck already rejects frozen and descriptor-bearing receivers and every non-plain-finite value, so a store that reaches the raw slot could not have been rejected in either mode -- the fast arm is mode-independent. Sloppy obj.f = <number> on a declared number field now emits that same precheck and raw slot store, sending every miss to js_put_value_set(..., 0), the sloppy-correct runtime the surrounding lowering already used. No runtime change. 09_method_calls outside a checkout: 3,762 ms -> 81 ms, matching the in-checkout arm exactly.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe compiler adds sloppy-mode lowering for eligible numeric class-field stores. It emits an inline guard and direct raw-slot write on hits, and calls ChangesSloppy class-field store
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant PutValueSet
participant ClassFieldLowering
participant RawFieldSlot
participant RuntimeSetter
PutValueSet->>ClassFieldLowering: attempt eligible sloppy class-field store
ClassFieldLowering->>RawFieldSlot: run inline guard and store canonicalized f64
ClassFieldLowering->>RuntimeSetter: call js_put_value_set with strict = 0 on guard miss
Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
proggeramlug
added a commit
that referenced
this pull request
Aug 5, 2026
…2 counters (#7425) * perf(codegen): revive the #5093 class-field versioned loop for canonical-i32 counters Repsel Phase 1 made the canonical i32 slot the ONLY storage for a proven-integer local, so such a local has no `ctx.locals` entry. The #5093 matcher gated its counter and its bound on `ctx.locals`, so it matched nothing. Also teach the sloppy class-field store (#7423) about the loop fact, so the fast clone stays call-free. * test(codegen): assert the class-field versioned loop is actually reached (#7287) * docs(changelog): #7287 class-field loop guard hoist * docs: name the fragment for its real PR (#7425) --------- Co-authored-by: Ralph Küpper <ralph@skelpo.com>
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.
Fixes #7288. 3762ms → 81ms outside a Perry checkout, matching the in-checkout number exactly.
Root cause: strict mode, from an upward
package.jsonwalkperry_parser::file_is_in_esm_package_contextwalks up for the nearestpackage.json."type": "module"⇒ ES module ⇒ strict. Perry's rootpackage.jsonis"type": "module", so everything inside the checkout compiles strict and everything outside compiles sloppy.Three
if !strict { return None; }bails inexpr/proxy_reflect.rs(added by #6542) then discard the fast class-field-store path. The HIR diff between arms is one bit:benchmarks/results/public-node-bun-v1.jsonreports the fast arm becausecompare.shdoescd benchmarks/suite. A user compiling the same file in their own project got the slow one.6/6 controls, including the decisive one: inside the checkout with a nested
{"name":"x"}→ slow, because Node stops the walk at the nearest package scope. A bare{"name":"x"}with noperrykey reproduces the flip exactly.The fix
The bail discarded the fast arm in order to fix the fallback arm — but only the fallback needed strict-awareness, and
js_put_value_set(…, strict=0)already existed.The #5093 precheck already rejects
OBJ_FLAG_FROZEN,OBJ_FLAG_HAS_DESCRIPTORS, mismatched class-id/keys, cleared layout-intact, and every non-plain-finite value — so a store reaching the raw slot could not have been rejected in either mode. No runtime change. Scope: raw-f64 fields, receiver == target.Verification
PERRY_CACHE_DIRon both, at load 25 (the 46× gap is far outside any noise band)preventExtensions, delete+re-add, aliasing, 100k megamorphic loop,Object.freezemid-loop, enumeration order — byte-identical to Node 26.5.1 and to the pre-change compiler, with 150class_field_sloppy_setblocks proving the arm is livetest_gap_class*/test_gap_object*match Node;--lib633 pass;native_proof_regressionsidentical 4 pre-existing failures before and afterorigin/main)Two things worth knowing
Today's two perf wins are unaffected — verified by IR diff, not timing: the only difference between arms for
bench_bitwiseandbench_array_opsis one startup-timejs_register_closure_strict_functioncall; hot code byte-identical. #7416 and #7421 stand.The gap suite structurally cannot exercise the sloppy arm. Every
test-files/*.tssits under the root"type": "module", so the entire corpus compiles strict.run_parity_tests.shalready acknowledges this for Node (retrying globals fixtures as.cts) but there is no sloppy arm on the compiler side — so any codegen predicate keyed onstrictis tested in one state only. Worth its own issue.Summary by CodeRabbit
Performance
Bug Fixes
Tests