-
-
Notifications
You must be signed in to change notification settings - Fork 161
perf(codegen,runtime): retire the per-access class-field latch, and gate the compiler's copy of the GC header layout #10646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
c2a4f5c
perf(codegen,runtime): retire the per-access class-field latch
503dd06
tooling: gate the compiler's copy of the GC header layout
74fca39
docs(changelog): fragment for #10646
a37d537
fix(runtime): gate the guard-shape test restore on cfg(test)
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| ### Static-key class-field reads drop the per-access latch (−18% on the guard's fast path) | ||
|
|
||
| Every static-key class-field read gated its fast path on | ||
| `@PERRY_CLASS_FIELD_INLINE_GUARD_DISABLED`. That is an `external global`, so on | ||
| arm64 reading it cost `adrp` + a GOT `ldr` + a dependent `ldrb` through it + a | ||
| compare — four instructions and two dependent loads, before the guard had | ||
| looked at the receiver at all. It could not be hoisted: the runtime flips it | ||
| mid-execution when a descriptor or accessor lands on a class prototype, so the | ||
| load was `volatile` by necessity. | ||
|
|
||
| That authority now rides on a value the guard already had to load. Each class | ||
| gains a `@perry_class_guard_shape_*` expectation, seeded at module init with | ||
| the class ShapeId and registered with the runtime; | ||
| `disable_class_field_inline_guard` poisons every registered slot with | ||
| `u32::MAX`. ShapeIds are allocated from `[0x8000_0000, 0xC000_0000)` and never | ||
| reused, so a poisoned expectation can never match a live object — every guard | ||
| misses and routes to the IC, exactly what the latch bought. | ||
|
|
||
| It is deliberately a SEPARATE global from `@perry_class_shape_id_*`: that one is | ||
| stamped into every new instance by `js_object_alloc_class_inline_keys_stamped`, | ||
| so poisoning it would brand live objects with a bogus ShapeId rather than close | ||
| a fast path. Subclass arms and imported-class stubs carry the poisonable | ||
| expectation too, and an imported-stub rewrite that lands after a disable | ||
| re-poisons rather than resurrects. | ||
|
|
||
| Measured on arm64 (`-Os` + `llc -O2 -mcpu=apple-m1`): one `o.a` on a typed | ||
| receiver goes from 28 to 23 executed fast-path instructions (−18%) with one | ||
| fewer dependent load; a probe making 16 reads on one receiver goes from 595.2 | ||
| to 563.0 executed instructions per call (−5.4%, `/usr/bin/time -l` instructions | ||
| retired, differenced over iteration count). The per-read marginal is −2 rather | ||
| than −5 because LLVM already hoisted the latch's GOT base register across | ||
| accesses within a function. The latch is gone from `$generic`, `$spec_b` and | ||
| the copy the inliner leaves in the caller — the last of which is the code that | ||
| actually executes. | ||
|
|
||
| ### The compiler's copy of the GC header layout is now gated | ||
|
|
||
| `perry-codegen` does not depend on `perry-runtime`, yet it bakes the collector's | ||
| header layout into emitted code: the inline `new` path stores a packed | ||
| `GcHeader` word as a compile-time constant, and every class-field / | ||
| element-shape / method-probe guard masks that word against a literal. Thirty-six | ||
| restatements across ten files, with the agreement held by a code comment — the | ||
| `debug_assert_eq!` that looked like enforcement compared codegen's constant to a | ||
| string literal, a tautology that never referenced the runtime and is compiled | ||
| out of `release` and `perry-dev` anyway. A flag renumbered in the runtime | ||
| compiled clean, passed every suite, and shipped a compiler whose allocator baked | ||
| one bit layout while the collector read another. | ||
|
|
||
| `scripts/check_gc_header_constants.py` (in `lint`) re-derives every restatement | ||
| from the runtime constant it quotes, including composites and the fused 32-bit | ||
| masks. A registered constant that stops existing fails, so a fix deletes its own | ||
| entry, and a new header-shaped `const` in a watched file must be registered or | ||
| exempted with a reason. Writing the registry found five restatements a | ||
| module-scope grep misses, because they are declared inside function bodies. | ||
|
|
||
| `shape_descriptor_census` gains a matching requirement: the class-field | ||
| precheck must read its expectation VOLATILE from the poisonable global, since a | ||
| lowering that hoisted that load would reopen a fast path the runtime has closed | ||
| and would still satisfy a shape-only assertion. |
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
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
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
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
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
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
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the stale comment above the pointer-shape gate.
The comment ending at Line 469 still says: "The enable flag is checked first so the escape hatch (PERRY_DISABLE_CLASS_FIELD_INLINE) and verify mode cleanly bypass the inline reads entirely." That description matches the removed
PERRY_CLASS_FIELD_INLINE_GUARD_DISABLEDlatch check, not the current code.The gate at Lines 470-477 now only checks
ptr_safe(tag and handle-band). The disable mechanism moved into the volatileguard_shape_globalcompare emitted later in this function, which the comment at Lines 524-529 already documents correctly.Update the comment so it does not describe a flag check that no longer exists here. Leaving stale documentation next to changed gating logic in a security-critical guard path risks misleading a future edit.
Suggested comment fix
🤖 Prompt for AI Agents