Skip to content

docs(gc-rooting): #7211 is fixed, and add the runtime-cache class - #7281

Merged
proggeramlug merged 1 commit into
PerryTS:mainfrom
jdalton:docs/gc-rooting-invariant-destale
Aug 3, 2026
Merged

docs(gc-rooting): #7211 is fixed, and add the runtime-cache class#7281
proggeramlug merged 1 commit into
PerryTS:mainfrom
jdalton:docs/gc-rooting-invariant-destale

Conversation

@jdalton

@jdalton jdalton commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

docs/src/internals/gc-rooting-invariant.md teaches contributors the rooting invariant using real bugs as case studies, and it had drifted from reality in two ways that matter.

#7211 is closed, not open. The page carried a section titled "And the one that is still open (#7211)". It was fixed by #7226, and the checker's allowlist is now empty (scripts/gc_root_dominance_allowlist.json reads "entries": [], annotated "EMPTY IS THE GOAL AND IT IS CURRENTLY MET"). The stale section is removed and the hazard-2 paragraph now cites #7226.

The runtime-cache class was missing from the taxonomy. It is added as a named class with its tell — a thread-local or static cell holding a GC pointer that no registered scanner rewrites — citing #7226 (the interned typeof result strings and the rawJSON key) and #7239 (ten runtime caches: CACHED_ENV, CACHED_PERMISSION, CACHED_REPORT, ERROR_CONSTRUCTOR_PTR, INPUT_HANDLER, RESIZE_CALLBACK, FRAME_CALLBACKS, CURRENT_NEW_TARGET, ACCESSOR_RECEIVER_OVERRIDE, PENDING_FETCH_SIGNAL).

This one is worth naming explicitly because it is invisible to the static checker by construction: the checker reads emitted IR, and a runtime-side cache never appears there. It was also the hardest instance of the class to find.

The "rules of thumb" cross-reference was renumbered to match, and CLAUDE.md:253 was checked for consistency (it already describes the runtime cache as a sibling class and points here for the full writeup).

Docs only — one file, 25 insertions, 31 deletions.

Summary by CodeRabbit

  • Documentation
    • Updated garbage-collection rooting guidance to cover five failure modes, including runtime-cache hazards.
    • Clarified detection characteristics, checker limitations, and mutable-root scanner registration requirements.
    • Removed outdated ClassExprFresh allowlist guidance following predicate fixes.
    • Added guidance connecting root-before-call practices with emitted field-setting operations.

… class

Three corrections to the GC rooting invariant documentation:

1. Remove the "And the one that is still open (PerryTS#7211)" section. Issue PerryTS#7211
   (ClassExprFresh rooting predicate) was fixed in PerryTS#7226, and the allowlist is
   now empty, verified by scripts/gc_root_dominance_allowlist.json.

2. Add the runtime-cache class as class 5 to the taxonomy. This class describes
   thread-local or static cells holding GC pointers with no registered scanner,
   distinguished by failing 10/10 rather than intermittently. Real instances:
   PerryTS#7226 (js_value_typeof strings, rawJSON key) and PerryTS#7239 (ten runtime caches).

3. Update the hazard-2 paragraph to reference PerryTS#7226 as the PR that fixed PerryTS#7211,
   and clarify that the PerryTS#7211 entries were deleted when the predicate was fixed.

4. Update the "Root before you call" rule to reference PerryTS#7211 specifically rather
   than "bug PerryTS#5" since the numbering has changed.

Verified against:
- Issue PerryTS#7211 is closed
- scripts/gc_root_dominance_allowlist.json is empty
- PerryTS#7226 commit message documents the runtime-cache class
- CLAUDE.md:253 consistent with these changes
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The GC-rooting invariant documentation now describes five failure modes, including unscanned runtime caches. It also updates gate-promotion status and explains the ClassExprFresh root-before-call issue.

Changes

GC-rooting invariant documentation

Layer / File(s) Summary
Rooting failure modes and validation guidance
docs/src/internals/gc-rooting-invariant.md
The guide adds runtime-cache hazards, scanner registration requirements, checker limitations, updated gate-promotion status, and explicit guidance for js_object_set_field_by_name calls.

Estimated code review effort: 1 (Trivial) | ~4 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#7226 — Implements GC-rooting fixes for runtime caches and ClassExprFresh, which this documentation describes.
  • PerryTS/perry#7227 — Updates related GC-rooting documentation and runtime-cache scanner guidance.
  • PerryTS/perry#7212 — Updates the same GC-rooting invariant documentation.

Suggested reviewers: proggeramlug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies both primary documentation changes: closing #7211 and adding the runtime-cache class.
Description check ✅ Passed The description clearly explains the documentation changes, related issues, rationale, examples, scope, and consistency check.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/src/internals/gc-rooting-invariant.md`:
- Around line 115-119: Revise the next section’s description of
scripts/gc_root_dominance_check.py to state that it detects only emitted-LLVM
rooting hazards, not runtime-table cache issues. Keep PERRY_GC_ZEAL,
PERRY_GC_PROTECT_FROMSPACE, and PERRY_GC_PROTECT_FROMSPACE_DEPTH as the checks
for runtime caches, and preserve the existing guidance to register mutable
roots.
- Around line 98-103: Update the “Runtime-cache class” description to avoid
claiming failure always occurs at collection `#0`. Explain that the pointer
becomes stale at the first moving collection after the cache is populated and
remains stale until code rewrites the cache, while preserving the distinction
from register-class bugs.
- Line 53: Run perry-doc-tests against docs/src and update all 13 untagged
TypeScript code fences in the documentation: mark illustrative fences with the
typescript,no-test tags, or replace them with {%#include ...%} directives where
the content should be sourced from a file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ece7df8c-adfc-4980-8643-99ab98cd6f80

📥 Commits

Reviewing files that changed from the base of the PR and between 97c6921 and 1e73fdb.

📒 Files selected for processing (1)
  • docs/src/internals/gc-rooting-invariant.md

cost every time.

## The four ways it has actually broken
## The five ways it has actually broken

Copy link
Copy Markdown

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
cargo run -p perry-doc-tests -- --lint docs/src

Repository: PerryTS/perry

Length of output: 143


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching linter/doc-test names:\n'
git ls-files | rg -i 'perry-doc-tests|doc.*(test|lint)|lint' | head -200 || true

printf '\nCargo manifests mentioning perry-doc-tests:\n'
rg -n 'perry-doc-tests|doc-tests|lint' Cargo.toml Cargo.lock -S || true

printf '\nChanged/added file context:\n'
rg -n 'typescript|ts|js|markdown-magic|remark' docs/src/internals/gc-rooting-invariant.md -S -C 2 || true

printf '\nRepository commands/scripts:\n'
git ls-files | rg '(^|/)(package\.json|README\.md|CONTRIBUTING\.md|Makefile|justfile|taskfile\.ya?ml|scripts/.*\.sh|nixpacks|deno\.json|bun\.lockb)' | xargs -r grep -n 'cargo run -p perry-doc-tests\|--lint\|perry-doc-tests\|rustfmt\|clippy' 2>/dev/null || true

Repository: PerryTS/perry

Length of output: 143


🏁 Script executed:

cargo run -p perry-doc-tests -- --lint docs/src

Repository: PerryTS/perry

Length of output: 143


🏁 Script executed:

cargo run -p perry-doc-tests -- --lint docs/src

Repository: PerryTS/perry

Length of output: 143


🏁 Script executed:

cargo run -p perry-doc-tests -- --lint docs/src

Repository: PerryTS/perry

Length of output: 143


🏁 Script executed:

cargo run -p perry-doc-tests -- --lint docs/src

Repository: PerryTS/perry

Length of output: 398


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cargo run -p perry-doc-tests -- --lint docs/src

Repository: PerryTS/perry

Length of output: 398


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rustup default stable || true
cargo run -p perry-doc-tests -- --lint docs/src

Repository: PerryTS/perry

Length of output: 143


🏁 Script executed:

cargo run -p perry-doc-tests -- --lint docs/src

Repository: PerryTS/perry

Length of output: 5134


Run perry-doc-tests before merge.

cargo run -p perry-doc-tests -- --lint docs/src reports 13 untagged ts/typescript fences in the docs src tree. Tag those fences as typescript,no-test or make them {%#include ...%} directives.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/src/internals/gc-rooting-invariant.md` at line 53, Run perry-doc-tests
against docs/src and update all 13 untagged TypeScript code fences in the
documentation: mark illustrative fences with the typescript,no-test tags, or
replace them with {%#include ...%} directives where the content should be
sourced from a file.

Source: Learnings

Comment on lines +98 to +103
### 5. Runtime-cache class (#7226, #7239)

`Expr::ClassExprFresh` roots its class object only when it believes the static
*initializers* can collect:
A thread-local or static cell holding a GC pointer that no registered scanner
rewrites. Unlike the register-class bugs above (which go bad intermittently when a
collection lands in a narrow window), a runtime cache goes bad at collection #0
and stays bad.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Describe the first moving collection, not collection #0.

A lazy cache can be populated after earlier collections. Code can also rewrite the cache later. State that the pointer becomes stale at the first moving collection after population and remains stale until rewritten.

Proposed wording
- a runtime cache goes bad at collection `#0`
- and stays bad.
+ an unregistered runtime cache becomes stale at the first moving
+ collection after it is populated and stays stale until rewritten.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### 5. Runtime-cache class (#7226, #7239)
`Expr::ClassExprFresh` roots its class object only when it believes the static
*initializers* can collect:
A thread-local or static cell holding a GC pointer that no registered scanner
rewrites. Unlike the register-class bugs above (which go bad intermittently when a
collection lands in a narrow window), a runtime cache goes bad at collection #0
and stays bad.
### 5. Runtime-cache class (`#7226`, `#7239`)
A thread-local or static cell holding a GC pointer that no registered scanner
rewrites. Unlike the register-class bugs above (which go bad intermittently when a
collection lands in a narrow window), an unregistered runtime cache becomes stale
at the first moving collection after it is populated and stays stale until rewritten.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/src/internals/gc-rooting-invariant.md` around lines 98 - 103, Update the
“Runtime-cache class” description to avoid claiming failure always occurs at
collection `#0`. Explain that the pointer becomes stale at the first moving
collection after the cache is populated and remains stale until code rewrites
the cache, while preserving the distinction from register-class bugs.

Comment on lines +115 to +119
**`scripts/gc_root_dominance_check.py` is structurally blind to this class** — it
reads emitted LLVM IR and cannot see a runtime table. The instruments that catch it
are `PERRY_GC_ZEAL=1 PERRY_GC_PROTECT_FROMSPACE=1 PERRY_GC_PROTECT_FROMSPACE_DEPTH=800`
on a real workload. When adding a cache of a heap pointer, register it in
`gc_register_mutable_root_scanner` in `gc/mod.rs` in the same commit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clarify the static-checker scope.

This paragraph says that scripts/gc_root_dominance_check.py cannot detect runtime tables. The next section at Line 125 says that the static checker is the only instrument that sees “this class” before a crash. Revise that statement to limit the checker to emitted-LLVM rooting hazards. Keep GC zeal and from-space protection as the runtime-cache checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/src/internals/gc-rooting-invariant.md` around lines 115 - 119, Revise
the next section’s description of scripts/gc_root_dominance_check.py to state
that it detects only emitted-LLVM rooting hazards, not runtime-table cache
issues. Keep PERRY_GC_ZEAL, PERRY_GC_PROTECT_FROMSPACE, and
PERRY_GC_PROTECT_FROMSPACE_DEPTH as the checks for runtime caches, and preserve
the existing guidance to register mutable roots.

@proggeramlug
proggeramlug merged commit 57ec369 into PerryTS:main Aug 3, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants