Sfoglia il codice sorgente

docs: session summary for two-phase parse milestones 2a-2c (2026-10-16)

Eric Streit 1 giorno fa
parent
commit
64e5028207
1 ha cambiato i file con 104 aggiunte e 0 eliminazioni
  1. 104 0
      docs/summary_two-phase-m2-2026-10-16.md

+ 104 - 0
docs/summary_two-phase-m2-2026-10-16.md

@@ -0,0 +1,104 @@
+# Session summary — 2026-10-16: two-phase parse, milestones 2a–2c
+
+Branch **`resolve-check`**, HEAD `f500a12`, tags `v3-two-phase-m2a`,
+`v3-two-phase-m2b`, `v3-two-phase-m2c`.  Suite **199/199**; fixpoint
+**OK (3,620,461 bytes)**; tree clean.  Design/record:
+`docs/plan-two-phase-parse.md`.
+
+## Goal
+
+Delete `SymTab.FwdVarRef`/`FwdVarName`/`FwdVarResolve`,
+`Check.NoteForward`/`FlushForward`, the `Design` forward branch and the
+`Op=1` marker — by parsing a unit's declarations before its procedure
+bodies, so forward references bind normally.
+
+## What landed (all behind `-twophase`, default off)
+
+| Step | Commit | What |
+| --- | --- | --- |
+| 2a | `dce67fc` | `scanner.SeekTo(p)`: replay `NextCh` over the in-memory buffer (O(p), no I/O); body-span table + a `SeekTo` round-trip fidelity check in the probe. |
+| 2b | `cdb6436` | `SymTab.DeferReset`/`DeferBody`/`ReenterDeferred`/`ParamName`; `Lower.ReenterScopeProc`/`CurScopeProc`; span table carries the deferral index. |
+| 2c | `f500a12` | Real deferral: phase-1 `DeferSkip` (leave scanner at the matching `END`, close like `FORWARD`), phase-2 re-parse loop at module ends. |
+
+## Mechanism (2c)
+
+- **Phase 1** — `DeferSkip` (reached from `Block`, only for plain
+  `ProcDecl` bodies; methods stay inline) consumes the body tokens for
+  real, verifies `END <name>`, leaves the scanner at that `END` so
+  `Block`'s trailing `END ident` consumes it, records the span; then
+  `ProcDecl` closes the proc like `FORWARD` (`AbortFunc`, no body node).
+- **Phase 2** — an inlined loop at both module ends
+  (`ProgModule`/`ImplUnit`) does, per span: `SeekTo`+`Adv`,
+  `SymTab.ReenterDeferred`, `Lower.ReenterScopeProc`, `QbeGen.BeginFunc`,
+  `Block(nm)`, attach body + block decls to the proc node,
+  `ScopeLeave`/`CloseProc`/`EndFunc`.  Scanner/parser state and the
+  unit's own `astDecl`/`astStmt` are saved and restored around the loop.
+- Opens/closes pair **within each pass**; **registrations run once**
+  (skip pass); the re-parse uses the resume paths.
+
+## Two real bugs found and fixed while landing it
+
+1. The re-parse loop **clobbered the unit's `astDecl`/`astStmt`** before
+   `ProgModule` built the unit (main got a proc's statements).  Fixed by
+   saving/restoring them around the loop.
+2. `PopPos` restores the scanner's **`errors` counter** (deliberate for
+   the shadow probe), so it **wiped every diagnostic recorded during the
+   re-parse** — `210`/`232` etc. inside deferred bodies were silently
+   dropped (`t_bad_fwd` was wrongly accepted).  Fixed by re-instating
+   the post-loop count after `PopPos`.
+
+## Findings worth keeping
+
+- **No forward procedure tolerance exists.**  A call to a proc declared
+  later in the same module is `233` (+ a knock-on `201` at EOF); a bare
+  `P;` two lines early is `233`.  The compiler's own sources self-compile
+  only because each `.mod` is paired with its `.def`, parsed first (so
+  e.g. `SemError` is declared before `NoteLabel`'s body compiles), plus
+  strict declaration-before-use inside modules.  Consequence for the
+  grammar: **atg helpers must be declared before use** (`NoteSpan` had to
+  move above `ProbeBody`); the `FwdVar` tolerance covers module-level
+  *variables* only.
+- **Suppressed inline emit = emission only.**  Under `SetNoEmit` every
+  `QbeGen` counter still advances and is discarded per unit by
+  `RestoreSession`; `Lower`'s tables are permanent session state.
+- **Byte-identity vs the old default is the wrong gate for forward
+  code.**  Deferral makes a forward variable ordinary, so it compiles
+  like a normally-ordered one; the legacy `FwdVar` path is the divergent
+  one (it drops a `loadw`).  Deferred output matches the *normal*
+  codegen and runs correctly.
+
+## Measured (2c)
+
+- Suite **199/199** — 193 core + gates: `twophase_same`
+  (`t_nestedproc`, `t_classmethod`) identity; `twophase_run`
+  (`t_fwdvar`/`t_fwdsel`/`t_fwdprop`) `-> 42`; `twophase_fail`
+  (`t_bad_fwd`) `incompatible assignment`.
+- All **142 single-file tests** under `-twophase`: **0 signals**,
+  **138 byte-identical**.  The 4 differing files are exactly the
+  forward-reference tests.
+- Sessions `DBasicProg`/`TString`/`Showcase12` byte-identical.
+- Fixpoint OK (flag off).
+
+## Known gap (why it is not flipped)
+
+On some multi-module **implementation** sessions (repro:
+`convtypes.def/.mod conversions.def/.mod`, i.e. `Conversions.mod` inside
+the Library1 session) `-twophase` records a **spurious `ident expected`**
+positioned inside a repassed body — while the emitted `.ssa` is
+**identical** to the default.  The repass reaches the correct AST but
+trips one extra `SynError`.  Candidate causes (to pin next): the
+`pos`/`len` "previous token" model interacting with `SeekTo`, or a
+construct where the repass consumes one token too many.  Reproduction is
+small and self-contained, so this is the immediate next step; the flip
+and the `FwdVar*` deletion follow it.
+
+## Resume
+
+```sh
+cd .../m2compiler-V3/compiler
+./build.sh && ./run_tests.sh        # 199/199
+./M2 -twophase ../stdlib/convtypes.def ../stdlib/convtypes.mod \
+          ../stdlib/conversions.def ../stdlib/conversions.mod
+# -> spurious ".../conversions.mod:148:3: ident expected"
+cd .. && ./bootstrap/fixpoint.sh     # FIXPOINT OK
+```