@@ -537,7 +537,7 @@ gap is VM value-representation / intrinsic-dispatch cost (the next big lever).
537537- ** Tests:** ` crate::selfhost_parity::parser_parity_corpus ` .
538538- ** Status:** decided.
539539
540- ### SH-022 — self-hosted lexer is ~ 5000 × slower on the VM; cost is per-char intrinsic/collection dispatch, NOT string building
540+ ### SH-022 — self-hosted lexer is ~ 5100 × slower on the VM; cost is per-char intrinsic/collection dispatch, NOT string building
541541
542542- ** Tool:** self-hosted lexer (` selfhost/lexer.rss ` ) run on the reg-VM vs native
543543 ` crate::lexer::lex ` , over the whole 545-file corpus (712 KB).
@@ -565,15 +565,22 @@ gap is VM value-representation / intrinsic-dispatch cost (the next big lever).
565565 forces per-char boxed access).
566566- ** Decision:** the real lever is cheaper per-char access, NOT string building:
567567 e.g. a native string byte/char cursor intrinsic (iterate without materializing a
568- boxed ` List<Char> ` ), and lower per-intrinsic dispatch overhead. Cross-ref SH-006:
569- AOT is ~ 144× faster than the VM on such code, so an AOT-compiled self-hosted lexer
570- would land ~ 0.5 s (~ 30× native Rust) — the residual being the intrinsic * count*
571- per char, which the char-cursor intrinsic would also cut. Feeds
568+ boxed ` List<Char> ` ), and lower per-intrinsic dispatch overhead. Feeds
572569 [[ perf-refactor-roadmap]] / [[ jit-collection-perf-measurement]] with real-workload
573570 evidence (the trigger that work was waiting for).
571+ - ** Measured vs. extrapolated (be honest):** only two things here are * measured* —
572+ (a) the VM-vs-native table above, and (b) the ` String.concat ` →` StringBuilder `
573+ control. The VM-vs-** AOT** split is ** NOT measured** : SH-006 measured AOT ~ 144×
574+ faster than the VM on comparable tool code, which * would* put an AOT-compiled
575+ self-hosted lexer near ~ 0.5 s (~ 30× native Rust), but this lexer has not actually
576+ been run under AOT. That AOT number is the piece that would separate * fixable VM
577+ per-op overhead* from the * inherent per-char intrinsic count* (which AOT also
578+ pays) — worth measuring as a follow-up (needs a file-reading lexer variant so a
579+ 700 KB input isn't passed via ` argv ` ).
574580- ** Tests / bench:** ` crate::selfhost_parity::lexer_perf_corpus `
575581 (` --release -- --ignored ` ).
576- - ** Status:** open (VM/stdlib lever identified; feeds perf roadmap).
582+ - ** Status:** open (VM/stdlib lever identified; feeds perf roadmap; AOT split
583+ still to be measured).
577584
578585### SH-023 — self-hosted checker reaches RS0005 parity at declaration level; the merged callable namespace is the load-bearing rule
579586
@@ -619,3 +626,36 @@ gap is VM value-representation / intrinsic-dispatch cost (the next big lever).
619626- ** Tests:** ` crate::selfhost_parity::checker_parity_tiny_sample ` and
620627 ` crate::selfhost_parity::checker_parity_corpus ` (` --ignored ` ).
621628- ** Status:** done.
629+
630+ ### SH-024 — multi-field variant destructuring is not positional; only struct-style field patterns bind
631+
632+ - ** Tool:** pre-code feasibility spike for the self-hosting effort (` rss run --vm ` ).
633+ - ** Symptom:** matching a sum variant with ≥2 payload fields positionally —
634+ ` Add(l, r) => … ` — fails: each binding is reported `RS0026 "unknown value
635+ binding"` . The struct-style form ` Add { left, right } => …` works, but it also
636+ requires an explicit scrutinee effect (` match read e { … } ` , else ` RS0202 ` ).
637+ Single-field positional binding (` Circle(r) => … ` ) * does* work, so the
638+ two-field failure is an inconsistency, not a blanket "no positional patterns".
639+ - ** Minimal RSS:**
640+ ```
641+ sum Pair { Both(a: Int, b: Int) Nothing }
642+ // fails: match p { Both(a, b) => ... } // RS0026 on a, b
643+ // fails: match p { Both { a, b } => ... } // RS0202 missing effect
644+ // works: match read p { Both { a, b } => ... }
645+ ```
646+ - ** Backend:** all (frontend / parser + binding resolution).
647+ - ** Root cause:** positional binding is only wired for single-field variants;
648+ multi-field variants must be destructured with named ` { field, … } ` patterns,
649+ which additionally project fields and so require a ` read ` /` mut ` /` take ` scrutinee
650+ effect. The two rules compound into confusing errors for the natural
651+ ` Variant(a, b) ` shape.
652+ - ** Classification:** language (parser / pattern binding) + docs.
653+ - ** Decision:** worked around throughout the self-hosted code by using
654+ ` match read scrutinee { Variant { field, … } => … } ` . Recorded here for
655+ completeness — this was found in the initial spike and used to choose the AST
656+ representation, but had not been written to the ledger. Language-side: allow
657+ positional binding for multi-field variants (or emit a targeted diagnostic
658+ pointing at the missing feature rather than ` RS0026 ` ).
659+ - ** Tests:** covered indirectly by every ` match read … { V { … } } ` in
660+ ` selfhost/parser.rss ` / ` selfhost/check.rss ` .
661+ - ** Status:** open (worked around; language decision pending).
0 commit comments