From dc0a09415811840cb95ac7e8ccd6f4c57a014dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Wed, 5 Aug 2026 18:17:32 +0200 Subject: [PATCH 1/2] =?UTF-8?q?docs(engine-plan):=20record=202026-08-05=20?= =?UTF-8?q?=E2=80=94=20four=20dead=20gates,=20layer=201=20and=203=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plan says sequencing and rationale live in this document, and it was materially out of date after today. Four gates on main could not fail, three of them required contexts, so every merge in their windows bypassed them. That came before any layer work was verifiable and is recorded first, with the generalisable part called out: the dominance checker refuses a clean verdict when it counts zero root stores, and that guard is the only reason the statepoint-era vacuity was visible at all. Layer 1: the RFC's own API does not compile (E0499); corrected, and the "borrow checker rejects the bug shape" claim is now an executable compile_fail doctest pinned to the error code. The real emitter needs the combinator form rather than the borrow form. One bug shape -- a raw pointer bound before a lower_expr and used after it -- is eliminated crate-wide, and the honest part is recorded too: four of seven merges corrected the previous one, and every defect compiled without warnings. Layer 3: a global total cannot make the discipline non-optional. The ratchet is now per-module, with 595 modules locked at zero and a list that can only shrink. --- docs/engine-plan.md | 62 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/docs/engine-plan.md b/docs/engine-plan.md index 4b8e75fc23..2d97474aeb 100644 --- a/docs/engine-plan.md +++ b/docs/engine-plan.md @@ -27,9 +27,9 @@ pointer has **three different homes**, each needing a different mechanism. | Layer | Home | Example bugs | Mechanism | Status | |---|---|---|---|---| | **0** | *enabler* | — | **in-process LLVM** (#7241) | ✅ **landed** (#7301) | -| 1 | `perry-codegen` lowering code | #7192, #7206, #7211 | `Raw`/`Rooted` borrow discipline | proposed | +| 1 | `perry-codegen` lowering code | #7192, #7206, #7211 | `Raw`/`Rooted` borrow discipline | design **validated** (#7459); migration **started** (#7461); one bug shape **eliminated** crate-wide (#7462–#7465) | | 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) | +| 3 | `perry-runtime` hand-written Rust | #7249, #7239, #7226, #7231 | `RuntimeHandleScope`, non-optional | still optional *inside* 107 listed modules; **595 modules locked at zero** by per-module ceilings (#7457), debt 1008 → 999 | **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 @@ -631,6 +631,64 @@ samples) is untouched and remains the real target. --- +## ★★★ Status 2026-08-05 — four gates were dead, and that came first + +**Before any of the layer work below was verifiable, four gates on `main` could +not fail.** Three were *required* contexts, so every merge in their windows +bypassed them. None surfaced from CI; each came from running the gate by hand +and asking whether its subject was actually present. + +| gate | why it could not fail | fix | +|---|---|---| +| `gc-root-dominance` (curated corpus) | statepoints became the default lowering (#7370) and express roots as `gc.statepoint` bundles, not `@js_shadow_slot_bind` calls — the corpus held **0** of the checker's subject | #7452 | +| the same, **dependency-scale** corpus | identical cause; #7452 fixed one corpus and missed its twin. 81 modules, **0** bind call sites | #7460 | +| raw-handle debt ratchet | red since #7424, and its baseline only moves *down*, so it could not be satisfied by re-pinning | #7455 | +| the dominance checker itself | `ALLOC_RE` did not recognise `js_url_coerce_string`, so it could not see the #7453 bug class at all | #7454 | + +**The lesson is the checkers' own design, not the incidents.** `gc_root_dominance_check.py` +refuses a clean verdict when it counts zero root stores — that guard is the only +reason the statepoint-era vacuity was visible at all. Every gate added here +should assert its subject was live, and CI's floors should be written so a +corpus that stops containing the subject goes red rather than green. + +### Layer 1 — what changed + +The RFC's proposed API **does not compile as written**: `emit_call(...).root(&mut e, …)` +is `E0499`, because the returned handle already borrows the emitter. Corrected in +#7459, where the design is also turned into `compile_fail` doctests pinned to +`E0499` and sabotage-tested, so the claim "the borrow checker rejects the bug +shape" is executable rather than asserted. + +`FnCtx` has no interior mutability, so the borrow-carrying form cannot be built +on the real emitter. The shape that works there is the **combinator** — the same +one layer 3 settled on with `RuntimeHandle::across_*`: never hand out an unrooted +handle. That is what makes the migration incremental. + +**One bug shape is now eliminated crate-wide**: a raw pointer bound before a +`lower_expr` and used after it. 11 sites in `url_main.rs` (nine URL setters, +the URLSearchParams family, `forEach`) and 3 in `child_proc.rs`. An arm-aware +scan now reports zero, with the single remaining hit confirmed *not* a bug — a +`perry/ui` widget handle, a registry id below the handle band. + +**Four of those seven merges corrected the previous one.** Every defect compiled +cleanly with no warnings: a guard bound to `_operand_guard` that emitted no +truncate, a release placed inside one branch of an `if/else`, a fix that covered +the two-operand path and missed the three-operand one. Two of my scan counts +were also wrong — one high (arm-boundary false positives), one low (missed the +third operand). **This class does not survive review, only mechanical checking**: +read the emitted IR, audit reachability with a script, verify the runtime +semantics of anything that looks like a leak. + +### Layer 3 — the ratchet was the wrong shape + +A single global total cannot make the discipline non-optional: it is blind to +debt moving between modules, it lets a new file start dirty if something else +got cleaner, and it has no finish line. Since **595 of 705 runtime modules were +already clean**, #7457 inverts it — list the 110 modules *permitted* to carry +debt, and everything else must be zero. A cleaned module's line must be +**deleted** (an entry matching nothing fails), so cleanup is permanent and the +list can only shrink. + ## Sequencing **Updated 2026-08-04.** Step 2 below is complete: layer 0 landed (#7301), layer 2 From 8461e43327520d4cac73cc366038b9d25bac3ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Wed, 5 Aug 2026 18:17:36 +0200 Subject: [PATCH 2/2] docs: changelog fragment for 7466 --- changelog.d/7466-engine-plan-update.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7466-engine-plan-update.md diff --git a/changelog.d/7466-engine-plan-update.md b/changelog.d/7466-engine-plan-update.md new file mode 100644 index 0000000000..a302901475 --- /dev/null +++ b/changelog.d/7466-engine-plan-update.md @@ -0,0 +1 @@ +- **`docs/engine-plan.md` records the 2026-08-05 state.** Four gates on `main` could not fail — both GC root-dominance corpora held zero of the checker's subject after statepoints became the default lowering, the raw-handle ratchet was red with a baseline that only moves down, and the checker itself did not recognise `js_url_coerce_string` — and three were required contexts, so merges in those windows bypassed them. The layer table now reflects that Layer 1's design is validated and corrected (the RFC's own API is `E0499`), its migration started, and one bug shape eliminated crate-wide; and that Layer 3's ratchet is per-module with 595 modules locked at zero. What is *not* done is stated too: `RuntimeHandleScope` remains optional inside the 107 listed modules. (#7466)