Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 46 additions & 17 deletions docs/engine-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ pointer has **three different homes**, each needing a different mechanism.
|---|---|---|---|---|
| **0** | *enabler* | — | **in-process LLVM** (#7241) | ✅ **landed** (#7301) |
| 1 | `perry-codegen` lowering code | #7192, #7206, #7211 | `Raw`/`Rooted` borrow discipline | proposed |
| 2 | emitted code's liveness | #7280, #7271, #7252, #7243 | statepoints (#7108, #7174) | ✅ **landed opt-in** (#7314), **usable** (#7339, #7340) |
| 3 | `perry-runtime` hand-written Rust | #7249, #7239, #7226, #7231 | `RuntimeHandleScope`, non-optional | mechanism exists (675 uses), **still optional**; 54 open catches (#7341) |
| 2 | emitted code's liveness | #7280, #7271, #7252, #7243 | statepoints (#7108, #7174) | ✅ **THE DEFAULT** (#7370); landed #7314, made usable by #7339/#7340 |
| 3 | `perry-runtime` hand-written Rust | #7249, #7239, #7226, #7231 | `RuntimeHandleScope`, non-optional | mechanism exists (675 uses), **still optional**; **41** open catches (#7341) |
Comment on lines +31 to +32

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

Reconcile the Layer 3 backlog count.

Line 32 reports 41 open catches, but Lines 436-438 still describe a 54-item worklist under #7341. Update 54 to 41, or state that 54 is the historical total and 41 is the remaining count.

Also applies to: 436-438

🤖 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/engine-plan.md` around lines 31 - 32, Reconcile the Layer 3 backlog
counts in the `perry-runtime` row and the `#7341` worklist: either change the
54-item reference to 41, or explicitly label 54 as the historical total and 41
as the remaining open catches.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 \
  'PERRY_RS4GC|native_stack_roots_enabled|shadow.?stack|gc_map|statepoint.*default|default.*statepoint' \
  .

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== docs/engine-plan relevant lines =="
sed -n '1,70p;426,465p' docs/engine-plan.md | cat -n

echo "== changelog snippets =="
for f in changelog.d/7314-statepoint-native-roots.md changelog.d/7345-delete-statepoint-bridge.md; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,220p' "$f" | cat -n
  fi
done

echo "== PERRY_RS4GC references, focused =="
rg -n -C 3 '(^|[^A-Za-z0-9_])PERRY_RS4GC|RS4GC|native.*root|fallback|llvm-inprocess|target-aware|PERRY_SHADOW_STACK|PERRY_GC_SAFEPOINT_ONLY' --glob '!**/*.lock' --glob '!**/target/**' --glob '!**/dist/**' | sed -n '1,240p'

Repository: PerryTS/perry

Length of output: 14554


Remove the inaccurate default-rooting claim.

#7370 does not change PERRY_RS4GC=1 from opt-in, and the runtime notes still say the default path leaves native stack-root behavior unchanged. Change line 31 to describe statepoints as adopted while keeping the default path opt-in/shadow-stack, or update the implementation and related docs so the claim matches the actual runtime default.

🤖 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/engine-plan.md` around lines 31 - 32, Update the statepoints entry in
the engine plan to remove the inaccurate claim that it is the default; describe
statepoints as adopted while accurately retaining the opt-in/shadow-stack
default behavior. Keep the surrounding `perry-runtime` and issue references
unchanged unless implementation defaults are also intentionally updated to
match.


**Order is 0 → 2. Layers 1 and 3 are independent and can proceed now.**
#7108 measured statepoints viable but blocked: *"the text-IR-plus-stock-clang
Expand All @@ -49,7 +49,7 @@ while #7255's defect made "minors" fall back to a conservative full scan. Minor-
cost should scale with *survivors*, not collection count, so 20× is a symptom.
Re-derive after Part 1 lands. See #7056.

## ★ Status 2026-08-03 — the architecture is proven, not yet adopted
## ★ Status 2026-08-03 — the architecture is proven (ADOPTED 2026-08-04, #7370)

**Layers 0 and 2 have landed.** #7301 put the LLVM pipeline in-process; #7305
replaced setjmp/longjmp with `invoke`/`landingpad`, which is what makes try-
Expand Down Expand Up @@ -285,7 +285,24 @@ the shadow-stack default:
| workload | total delta | `__text` | `__perry_gcmap` |
|---|---:|---:|---:|
| 2000 **root-free** functions (scalar only) | **+0 B** | +12 B | not emitted |
| 2000 **root-dense** functions (3 heap values live across an alloc) | **+4,330,592 B (+18.95%)** | +4,203,608 B | 902,124 B |
| 2000 **root-dense** functions (3 heap values live across an alloc) | +4,330,592 B (+18.95%) | +4,203,608 B | 902,124 B |

**The operative number is +1.86%, not +18.95%.** Measured on a real dependency —
`zod` from source, 81 native modules, a 29 MB binary — RS4GC in-process vs the
shadow-stack default:

| section | shadow | RS4GC | delta |
|---|---:|---:|---:|
| total | 28,955,656 | 29,495,048 | **+539,392 (+1.86%)** |
| `__text` | 20,989,544 | 21,508,556 | +519,012 |
| `__perry_gcmap` | 0 | 362,487 | new section |

**Do not quote the +18.95%.** It is a worst case constructed to isolate the
mechanism — three heap values live across an allocation in *every* function —
and it overstates real exposure by an order of magnitude. An earlier revision of
this section led with it and concluded root density was a prerequisite for
adoption; the dependency-scale measurement says otherwise, and adoption shipped
in #7370 without it.

Two things follow, and both matter for planning:

Expand Down Expand Up @@ -406,22 +423,34 @@ landed (#7314) and became *reachable* (#7339) and *selectable* (#7340). The spin
`0 → 2` is done, so the ordering that remains is:

1. ~~**Next:** in-process LLVM (#7241) → statepoints (#7108/#7174).~~ **Done.**
2. **Reduce root density — now a PREREQUISITE, not a nice-to-have.** Statepoints
cost **+18.95% binary size on root-dense code and +0% on root-free code**
(measured, see Part 1), and 97% of that is `__text`. Adopting them as the
default today therefore *regresses* the owner's stated goal of minimal binary
size. Fewer roots fixes that, and it is the same lever #7296 already proved
worth 9.9× on `matmul`, so speed and size pull together here rather than
trading off. The plan predicted this lever but flagged it "expected, not
measured. Layer 2 must prove it first" — layer 2 has now landed, so it can be
measured.
2. **Reduce root density — worth doing, NOT a prerequisite.** Measured at
dependency scale, statepoints cost **+1.86% binary size** against **−1–2%
runtime**: a trade worth taking, and adoption shipped in #7370 without this.
Statepoints carry **zero fixed cost** — a function with nothing live across a
safepoint pays nothing at all — so a program's exposure is exactly its root
density, and 97% of the growth is `__text` (the per-root relocation
sequence), not metadata. #7314's compact map closed the metadata objection
completely; metadata was never the dominant term. Fewer roots is still the
same lever #7296 proved worth 9.9× on `matmul`, so size and speed pull
together rather than trading off.
Comment on lines +426 to +435

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

Correct the stated __text percentage.

Using the values in the table, 519,012 / 539,392 = 96.2%, not 97%. Change the statement to 96% or 96.2%, or document the different denominator used for 97%.

🧰 Tools
🪛 LanguageTool

[style] ~430-~430: Consider an alternative for the overused word “exactly”.
Context: ...ing at all — so a program's exposure is exactly its root density, and 97% of the gro...

(EXACTLY_PRECISELY)

🤖 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/engine-plan.md` around lines 426 - 435, Update the root-density
paragraph’s `97%` `__text` claim to match the table’s calculation, using 96% or
96.2%, unless you explicitly document a different denominator that produces 97%.

3. **Layers 1 and 3, independent of the above.** Layer 3's instrument is now
aimed (#7342 arm 4) and has a 54-item worklist (#7341) whose dominant
signature is a stale `GC_TYPE_STRING` at minor #0.
4. **Then the adoption fork.** Flipping statepoints on by default additionally
needs `llvm-inprocess` to become a default cargo feature (#7301's scope, since
RS4GC is the only invoke-capable backend). ~~and x86-64 to work (#7333)~~ —
x86-64 landed in #7349 and Windows in #7355; see the 2026-08-04 update above.
4. ~~**Then the adoption fork.**~~ **CLOSED — statepoints are the default as of
#7370.** Every gate it listed is shut: `llvm-inprocess` became a default
cargo feature (#7353), x86-64 landed (#7349), Windows landed (#7355), the
bridge was deleted leaving one backend (#7348), and the full 479-test gap
suite with no env set matches the shadow baseline exactly — 447 pass / 19
diff / 13 node_fail, zero regressions, zero compile failures, all 128
try-carrying tests included.

The default is **target-aware**, which is the part worth carrying forward:
native roots where the runtime can walk the frames, shadow stack where it
cannot. `gc_map` refuses to emit a map for a target whose bases the runtime
cannot resolve, so a blanket flip would hard-fail every watchOS `arm64_32`
and ARM64-Windows compile. Falling back is not "no roots" — it is the other
lowering of the same analysis, which #7340 split apart precisely so this
choice could be per-target.
5. **After the collector is trustworthy:** re-derive the RSS numbers (#7056).
6. **Do not** re-measure GC pacing, or update the README's performance table,
mid-cycle.
Loading