Skip to content

Commit 2be2341

Browse files
olwangclaude
andcommitted
docs(selfhost): reframe SH-016..SH-023 findings as historical-with-status
The "Findings" section read as if SH-016..SH-020 were current unresolved gaps and SH-022 still attributed the ~5100x slowdown to per-character dispatch — conflicting with the corrected table/ledger (556/556, ~46x). - Mark each finding with its current status from the ledger: SH-016/017/018/019 FIXED, SH-020 CLOSED (over-claim), SH-021 accepted methodology limitation. - Correct SH-022: root cause was O(n^2) DeepCopy of the read List<Char> param (the per-character-dispatch hypothesis was wrong), fixed to ~46x by classifying the scalar Char.* intrinsics as PureFreshReader. - Replace "highest-ROI next lever" (which assumed SH-022 unfixed) with the actual current residual: a flat ~46x general per-op tax, TypedMap/TypedSet rewrite parked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 77fcd51 commit 2be2341

1 file changed

Lines changed: 49 additions & 29 deletions

File tree

docs/self-hosting-stress-test-plan.md

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -198,32 +198,52 @@ a degenerate always-`OK`/always-`CLEAN` tool is caught without the corpus gate);
198198
corpus gates run with `-- --ignored` (`lexer_parity_corpus`, `parser_parity_corpus`,
199199
`checker_parity_corpus`, `lexer_perf_corpus`).
200200

201-
### Findings (ledger `SH-016``SH-023`)
202-
- **SH-016** — no character-literal syntax; `'` lexes to `?`, cascading a misleading
203-
`RS0013`. *(language + diagnostics)*
204-
- **SH-017** — statement-level binary-operator expressions can't cross a newline; the
205-
leading-operator form **compiles but is silently wrong**. *(language + correctness-grade
206-
diagnostics)*
207-
- **SH-018** — no cursor/state object (no methods/`impl`, `mut` params can't advance a
208-
cursor); stateful passes thread state positionally. *(language ergonomics)*
209-
- **SH-019** — a `fresh`-returning fn can't build its result via `mut` + `List.push`
210-
(`RS0601`). *(analyzer/freshness ergonomics)*
211-
- **SH-020** — recursive descent must encode `(ok, new-index)` as a sentinel `Int`. *(ergonomics)*
212-
- **SH-021**`parse_source_raw` defers body validation, so recognition parity under-tests
213-
the grammar (deep grammar is enforced in the analyzer, not the parser). *(methodology)*
214-
- **SH-022****the headline perf result:** the self-hosted lexer is ~5100× slower on the
215-
reg-VM than native Rust (79.5 s vs 15.3 ms over 712 KB). A controlled `String.concat`(O(n²))
216-
`StringBuilder`(O(n)) swap moved nothing, isolating the cost to **per-character intrinsic
217-
dispatch** (`List.get` on `List<Char>` + `Char.*` peeks — VM value-representation +
218-
intrinsic-dispatch overhead), not string building. *(VM + stdlib)*
219-
- **SH-023** — the checker reaches `RS0005` parity; the load-bearing rule is the analyzer's
220-
**merged callable namespace** (fn names + type-constructor names collide). *(insight)*
221-
222-
### Highest-ROI next lever (from the data)
223-
SH-022 is the first real workload that unambiguously profiles as **VM-dispatch-bound**. The
224-
indicated fix is a native **string char/byte-cursor intrinsic** (iterate a `String` without
225-
materializing a boxed `List<Char>`) plus lower per-intrinsic dispatch cost — this feeds the
226-
parked collection-representation / perf roadmap with real-workload evidence. Deeper checker
227-
passes (name resolution, exhaustiveness) are the natural next self-hosting step but require a
228-
real expression/statement/pattern parser (the depth `parse_source_raw` let Phase 2 skip —
229-
SH-021).
201+
### Findings (ledger `SH-016``SH-023`) — historical; current status per item
202+
203+
These are the findings the stress test surfaced. **All of the actionable ones have
204+
since been fixed or closed** — the table above (556/556, ~46×) is the current state;
205+
the list below records what each finding was and where it landed. See the linked
206+
ledger entry for the full write-up.
207+
208+
- **SH-016***(was)* no character-literal syntax; `'` lexed to `?`, cascading a
209+
misleading `RS0013`. **FIXED**`'x'` is a real `Char` value end-to-end (and a
210+
stray out-of-inventory char now lexes to an `Unknown` token the parser reports,
211+
not `?`; empty/multi-char literals are `RS0038`). *(language + diagnostics)*
212+
- **SH-017***(was)* statement-level binary-operator expressions couldn't cross a
213+
newline; the leading-operator form compiled but was silently wrong. **FIXED**
214+
operator-continuation lexing is supported. *(language + correctness diagnostics)*
215+
- **SH-018***(was)* no cursor/state object; `mut` params couldn't advance a
216+
cursor. **FIXED** — scalar-`Copy` `mut` params are reassignable with caller
217+
write-back (and, as of this review, `mut` field/element *places* are written back
218+
too, on both backends). *(language ergonomics)*
219+
- **SH-019***(was)* a `fresh`-returning fn couldn't build its result via `mut` +
220+
`List.push` (`RS0601`). **FIXED** — managed fresh bindings survive the flow merge.
221+
*(analyzer/freshness)*
222+
- **SH-020***(was)* recursive descent had to encode `(ok, new-index)` as a
223+
sentinel `Int`. **CLOSED** — not a real gap; tuple returns work (the original
224+
over-claim was corrected). *(ergonomics)*
225+
- **SH-021**`parse_source_raw` defers body validation, so recognition parity
226+
under-tests the grammar (deep grammar is enforced in the analyzer, not the
227+
parser). **Accepted methodology limitation** — partly mitigated by the
228+
non-ignored negative-parser / positive-`RS0005` smoke tests. *(methodology)*
229+
- **SH-022***(was)* the self-hosted lexer was ~5100× slower on the reg-VM than
230+
native Rust. **FIXED (→ ~46×).** NOTE: the original "per-character intrinsic
231+
dispatch" hypothesis was **wrong**; the real root cause was **O(n²)** — every
232+
lexer helper's `read List<Char>` param got an eager prologue `DeepCopy` that the
233+
elision pass KEPT (taint flowed through `List.get` to the extracted `Char`, and
234+
`Char.*` were classified `Keep`). The fix classifies the pure scalar `Char.*`
235+
intrinsics as `PureFreshReader` so the copy is elided (VM-only, parity-safe):
236+
lexer/VM 79.5 s → 732 ms (~108×), VM-vs-native 5100× → ~45.6×. *(VM elision)*
237+
- **SH-023** — the checker reaches `RS0005` parity; the load-bearing rule is the
238+
analyzer's **merged callable namespace** (fn names + type-constructor names
239+
collide). *(insight)*
240+
241+
### Current residual perf problem (separate from SH-022)
242+
With SH-022's O(n²) removed, the remaining VM-vs-native gap is a flat **~46×**
243+
general per-op tax (HashMap/`Handle`/`Rc<RefCell>` per op + per-intrinsic dispatch),
244+
not an algorithmic blowup — real code (e.g. the mailbox) already runs ~2.4× native.
245+
The only remaining lever is a `TypedMap`/`TypedSet` representation rewrite (parked;
246+
~10–15% on collection-bound micro-kernels, med-high risk), tracked in the perf
247+
roadmap. Deeper checker passes (name resolution, exhaustiveness) are the natural
248+
next self-hosting step but need a real expression/statement/pattern parser (the
249+
depth `parse_source_raw` let Phase 2 skip — SH-021).

0 commit comments

Comments
 (0)