Pārlūkot izejas kodu

docs: L3 finding (option 1 needs two backend states or the flip) + session summary

Option 1 (per-proc Lower before CloseProc) needs the L image built
concurrently with the legacy one, but QbeGen shares one buffer and one
counter set, so interleaved emitters corrupt the legacy path.  The
second-buffer attempt was reverted.  Conclusion: apply the flip (L7) to
the test path (suppress legacy emit under -lower and lower the whole unit
from the AST during the parse).  Adds the 2026-10-04(b) session summary.

Gates: suite 182/182; fixpoint OK (3147849 bytes).
Eric Streit 1 nedēļu atpakaļ
vecāks
revīzija
e8956b29dc

+ 31 - 14
docs/summary_two-phase-lower3-finding.md

@@ -36,27 +36,44 @@ which time a procedure's locals/params are no longer resolvable — so
 
 This is exactly the "flip" the plan anticipated (§3 phase 3): the walk
 cannot be one end-of-unit pass for *everything*; body lowering has to
-happen at each procedure while `SymTab` still has its scope.  Options:
+happen at each procedure while `SymTab` still has its scope.
 
-1. **Grammar calls `Lower.LowerProc(node)` right before `SymTab.CloseProc`**
-   (and the program body before `EndUnit`) — i.e. per-unit-piece lowering,
-   still writing the `L` image.
-2. **Make `SymTab` keep enough per-symbol info** (kind/type/param names)
-   accessible after the scope closes (a durable "codegen view"), so one
-   end-of-unit walk works.
+## Option 1 was attempted and is *not* a small hook
 
-Option 1 matches the existing inline timing and is the lower-risk route;
-it also foreshadows the flip, where `Lower` *replaces* the inline emit at
-that same point.
+The chosen route — grammar calls `Lower.LowerProc(node)` right before
+`SymTab.CloseProc` — requires the `L` image to be built **concurrently**
+with the legacy image.  `QbeGen` has a **single** session buffer and a
+single set of counters (`nTemp`, `nLoc`, `scopeTop`, …), so two emitters
+interleaved **corrupt the legacy path** — the very path that must stay
+byte-identical for self-hosting.
+
+A second image buffer (`altBuf` + `SetAlt`/`WriteAlt`) was added and then
+**reverted**: it does not solve the shared-counter problem, only the
+buffer.  Concurrent emit needs either:
+
+- **two full independent backend states** (a second `QbeGen` instance /
+  duplicated counters — invasive), or
+- **not running both at once**: under `-lower`, suppress the legacy emit
+  for the unit and lower only from the AST — i.e. the **flip** (L7)
+  applied to the test path.  Lowering then happens *during* the parse, so
+  every scope is open at the right time and the whole class of
+  "closed-scope" problems disappears.
+
+The second is the clean destination and subsumes L3.  Recommendation:
+make the flip the next dedicated step, rather than fake option 1 with two
+entangled emitters.
 
 ## Verified this slice
 
 - L0–L2 comparisons still pass (`t_exit`, `t_arith`, `t_lower1`,
-  `t_lower2`); `t_lower3` is **gated out** of `CanLower`.
+  `t_lower2`); `t_lower3` is **gated out** of `CanLower`, so the tree
+  stays green.
 - The call-mangling and callee-designator fixes are exercised by the
-  parse (and would be by a future flip).
+  parse (and will be by the flip).
 
 ## Next
 
-Decide option 1 vs 2 for scope timing, then finish L3 (procedures).  The
-rest of the subset order (L4+) is unchanged.
+Start the flip: a backend-level emit gate so `-lower` suppresses the
+legacy emit and the grammar lowers the whole unit from the AST during the
+parse.  That completes L3 and is the basis for L4+.
+

+ 63 - 0
docs/summary_two-phase-session-2026-10-04b.md

@@ -0,0 +1,63 @@
+# Session summary — 2026-10-04 (b): Lower phase L0–L2, L3 finding
+
+Branch **`ast-stage-c`**.  Suite **182/182**; self-hosting fixpoint **OK**
+(**3,147,849 bytes**).
+
+The Lower phase (`docs/plan-lower.md`) went from a design to a working
+byte-compare harness plus three verified subsets — and hit a real
+architectural wall at L3 that is now precisely documented.
+
+## Landed
+
+| Tag | Doc | What |
+| --- | --- | --- |
+| `v3-lower0` | `summary_two-phase-lower0.md` | mechanism: `Lower` module, `QbeGen.SetOutName`, `-lower` flag, `lower_ok` compare; scalar L0 |
+| `v3-lower1` | `summary_two-phase-lower1.md` | relations, booleans, short-circuit `AND`/`OR`, `NOT` |
+| `v3-lower2` | `summary_two-phase-lower2.md` | `IF`/`ELSIF`/`ELSE`, `WHILE`, `REPEAT`, `LOOP`/`EXIT`, `FOR` (no `BY`), `HALT` |
+| `v3-lower3-wip` | `summary_two-phase-lower3-finding.md` | L3 groundwork (calls, proc headers, `ParamName`, local capture, callee fix) + the blocking finding |
+
+## How the comparison works
+
+`-lower` makes the grammar re-emit the program unit from the AST after the
+legacy `EndModule` and before `SymTab.EndUnit`; `LowerUnit` writes
+`gen_ssa/<Mod>L.ssa` and `lower_ok` byte-compares it to the legacy image.
+Four tests pass: `t_exit`, `t_arith`, `t_lower1`, `t_lower2`.
+
+## The L3 finding
+
+`SymTab.CloseProc`/`EndUnit` close the scope the inline actions lowered
+inside.  `Lower` at unit end can resolve module names but **not procedure
+locals/params**, so L3 does not byte-compare.  Fixing it at each
+procedure ("option 1") needs the `L` image built **concurrently** with the
+legacy one — but `QbeGen` has one buffer and one counter set, so
+interleaved emitters corrupt the legacy path.  A second buffer was tried
+and reverted; it does not address the shared counters.
+
+**Conclusion:** the right step is the **flip** (L7) applied to the test
+path — under `-lower`, suppress the legacy emit and lower the whole unit
+from the AST *during* the parse, when every scope is open.  That
+completes L3 and subsumes the rest.
+
+## Bugs found & fixed (L3 groundwork)
+
+- `NkCall` child 0 is an `NkDesignator`, not a bare `NkIdent`; calls were
+  mangling as `$_0`.  Fixed with `CalleeName`.
+- Procedure `VAR` locals were never recorded (`Block` set `astStmt`, not
+  the decls); now captured via `astBlkDecls` → `NkProcDecl` child 2.
+- `FORWARD` declarations break gm2's whole-program pass (find: L2) — the
+  walk merges mutually-recursive bodies instead.
+
+## State / next
+
+- Tags `v3-lower0..2`, `v3-lower3-wip`.  Only `compiler/toto.mod` dirty.
+- **Next:** start the flip (emit gate + AST lowering during parse),
+  completing L3 and giving the basis for L4+.
+
+## Resume
+
+```sh
+cd ~/Projets/Projets-Modula2/MyWork/m2compiler-V3
+git checkout ast-stage-c
+cd compiler && ./build.sh && ./run_tests.sh   # 182/182
+cd .. && ./bootstrap/fixpoint.sh              # FIXPOINT OK
+```