Просмотр исходного кода

docs: L3 option 2 functionally proven; blocker is no-FORWARD recursion

Records that option 2 (zero SymTab surface) makes t_lower3 byte-match
(183/183 suite): Lower-owned syms/procs tables, inert grammar hooks, and
ScopeLeave as a parse-time no-op (the closed-scope trap in miniature).

The self-host blocker is not the tables but three mutually-recursive
procedure pairs in Lower (CanExpr<->CanCallArgs, CanProc<->CanDecls,
LowerExpr<->LowerCall), which the V3 compiler rejects without FORWARD,
and FORWARD itself breaks gm2's whole-program pass.  Fix: merge/inline
the three pairs, exactly as the committed CanStmt/LowerStmt already do.

Gates at this point: suite 182/182; fixpoint OK (3147849 bytes).
Eric Streit 6 дней назад
Родитель
Сommit
edf24d9ba4
1 измененных файлов с 71 добавлено и 90 удалено
  1. 71 90
      docs/handoff-l3-option2.md

+ 71 - 90
docs/handoff-l3-option2.md

@@ -1,103 +1,84 @@
-# Handoff — finishing L3 via option 2 (next session)
-
-Branch **`ast-stage-c`**, HEAD **`e276345`**.  Suite **182/182**;
-fixpoint **OK** (**3,147,849 bytes**).  Tree clean except `toto.mod`.
-
-## Goal
-
-Finish **L3 (procedures)** for the Lower phase
-(`docs/plan-lower.md`, `docs/summary_two-phase-lower3-finding.md`) using
-**option 2 with zero `SymTab` surface**: a `Lower`-owned symbol/signature
-table filled by the grammar while scopes are open, so the end-of-unit
-walk can resolve locals/params after `SymTab` has popped their scopes —
-**without** touching `SymTab` (which broke self-hosting when tried).
-
-## What is already proven (rebuilt transiently, then reverted)
-
-Option 2's architecture **works** and does not break self-hosting:
-
-- `Lower` owned tables worked: `syms[]` (name/kind/ty) with a scope
-  save-stack (`ScopeEnter`/`ScopeLeave`), and `procs[]` (uid/res/depth/
-  ext/params) via `NoteProc`/`NoteParam`/`SetProcRes`.  Resolution via
-  `FindSym`/`FindProc` (`Resolve`, `ProcInfo`, `ParamInfo`).
-- Grammar hooks (all inert — they only fill tables, never emit):
-  - root `M2`: `Lower.Reset`
-  - `VarDecl`: `Lower.NoteVar(nm, KindVar, t)` per pend name
-  - `ConstDecl`: `Lower.NoteVar(n, KindConst, t)`
-  - `ProcHeading`: `Lower.ScopeEnter`, `Lower.NoteProc(pn, uid, Invalid,
-    depth, FALSE)` after `EnterProc`; `Lower.SetProcRes(t)` at the result
-  - `ParamSection`: `Lower.NoteParam(nm, t, isV)` +
-    `Lower.NoteVar(nm, KindParam, t)` per name
-  - `ProcDecl`: `Lower.ScopeLeave` in all three arms (FORWARD / EXTERNAL /
-    body)
-- With the bypass of `CanLower`, the procedure **prologue emitted
-  correctly and byte-aligned** with the legacy image: `BeginFunc`,
-  `FuncParam` for each formal, `EndFuncHeader`, and the **locals did
-  emit** (`%t8 =l alloc4 4 ...` for a local `s`).
-
-## The one remaining bug
-
-The procedure **body** did not emit: `LowerDecls`'s `NkProcDecl` branch
-does
-
-```modula2
-LowerDecls(AST.Child(c, 2));   (* locals  -- emitted OK *)
-LowerStmt(AST.Child(c, 1));    (* body    -- emitted NOTHING *)
-QbeGen.EndFunc(res)
-```
+# Handoff — L3 via option 2: functionally proven, blocked by no-FORWARD
 
-and the output jumped straight from the locals to `EndFunc`'s default
-`ret 0`, i.e. `LowerStmt(AST.Child(c, 1))` dispatched to nothing.
-
-**First step next session:** dump ONE `NkProcDecl`'s children to pin the
-body index.  Add a one-off `AST.Dump(astUnit, 0)` in the root (and
-**remove it before committing** — an `ASTDUMP` was accidentally committed
-once and cost real time), compile e.g.:
-
-```modula2
-MODULE Proc1;
-VAR ExitCode : INTEGER;
-PROCEDURE Add(a, b : INTEGER) : INTEGER;
-VAR s : INTEGER;
-BEGIN s := a + b; RETURN s END Add;
-BEGIN ExitCode := Add(4, 10) END Proc1.
-```
+Branch **`ast-stage-c`**, HEAD **`90b1d76`**.  Suite **182/182**;
+fixpoint **OK** (**3,147,849 bytes**).  Clean except `toto.mod`.
+
+## Headline
+
+Option 2 (zero `SymTab` surface) **works functionally**: with it,
+`t_lower3.mod` re-emits **byte-identical** to the legacy image and the
+suite reaches **183/183**.  It does **not** self-host yet, and the cause
+is now precisely known and is **not** the table design.
+
+## What was built and proven (this session, then reverted to keep green)
+
+`compiler/src/Lower.def` / `Lower.mod`:
 
-Expect `NkProcDecl` = `[0]=NkIdent Add, [1]=NkBlock[assign, return],
-[2]=locals`.  If the body is elsewhere (e.g. child 1 is the locals and
-the block is nested), fix the dispatch in `LowerDecls`.  The earlier
-slice-5 dump showed child 1 = the body block; the locals (child 2) now
-emit, so verify whether setting child 2 shifted the body.
+- Owned tables: `syms[]` (`name/kind/ty`) and `procs[]`
+  (`uid/res/depth/ext/params`), with `Reset`, `NoteVar`, `NoteParam`,
+  `NoteProc`, `SetProcRes`, `ScopeEnter`/`ScopeLeave`, and resolvers
+  `FindSym`/`FindProc`/`Resolve`/`ProcInfo`/`ParamInfo`.  **No `SymTab`
+  edits at all.**
+- All `SymTab.ProcUid/ProcNPar/ParamType/...` call-sites switched to
+  `ProcInfo`/`ParamInfo`; `LowerDesign`/`IsSimpleScalar`/`IsCall` to
+  `Resolve`/`FindProc`.
+- Grammar hooks (inert; fill tables only): `M2` root `Lower.Reset`;
+  `VarDecl`/`ConstDecl` `Lower.NoteVar`; `ProcHeading` `Lower.ScopeEnter`
+  + `Lower.NoteProc` + `Lower.SetProcRes`; `ParamSection`
+  `Lower.NoteParam`/`NoteVar`; `ProcDecl` `Lower.ScopeLeave` and
+  `AST.SetOp(astNode, 0)`.
+- **Key bug fixed:** `ScopeLeave` must be a **no-op during parse** — a
+  real pop removed locals/params *before* the end-of-unit walk (same
+  closed-scope trap as `SymTab`).  With it no-op'd, procedure bodies emit
+  and `t_lower3` matches.
 
-Likely fix: the body is `AST.Child(c, 1)` and `LowerStmt` handles
-`NkBlock` — check `LowerStmt`'s `NkBlock` branch and the assign/return
-cases reach it.  A `QbeGen.Remark` at the top of `LowerStmt` (removed
-after) will show whether it is entered for the body.
+## The blocker (why it can't self-host yet)
 
-## Then
+`Lower` now has **three mutually-recursive procedure pairs**:
 
-- Enable procs in `CanLower` (it already routes `NkDecl → CanProc`; the
-  earlier session found `NkProcDecl` `op` defaulted to `-1` and `CanProc`
-  requires `0` for a normal body — **set `AST.SetOp(astNode, 0)` at node
-  creation** in `ProcDecl`; this was one of the two real bugs).
-- Add `lower_ok t_lower3.mod` to `run_tests.sh`.
-- Gate: `build.sh` + `run_tests.sh` (expect 183/183) + `fixpoint.sh`
-  (**must stay OK** — the whole point of option 2).
+- `CanExpr` ⇄ `CanCallArgs`
+- `CanProc` ⇄ `CanDecls` (via `CanDecl`)
+- `LowerExpr` ⇄ `LowerCall`
 
-## Two bugs already identified (re-apply with the option-2 work)
+The V3 self-hosted compiler rejects mutual recursion **without `FORWARD`**
+(`Lower.mod:NNN invalid call`), and `FORWARD` is not usable either (it
+breaks gm2's whole-program pass — see `docs/summary_two-phase-lower2.md`).
+The committed `Lower.mod` avoids this by **merging** mutually recursive
+bodies into one dispatching procedure (`CanStmt`, `LowerStmt`).
 
-1. `NkProcDecl` op defaults to `-1`; normal body must be `0`
-   (`CanProc` rejects otherwise).  Set it at creation.
-2. `NkCall` child 0 is an `NkDesignator`, not a bare `NkIdent` — calls
-   mangled as `$_0`.  `Lower.CalleeName` unwraps the designator (already
-   in the committed `v3-lower3-wip` `Lower.mod`; re-add for option 2).
+## The fix (next session)
+
+Break all three cycles the same way the committed code already does —
+**merge, don't split**:
+
+1. **Inline `CanCallArgs` into `CanExpr`**'s `NkCall` branch (it is a
+   few lines: arity + recurse `CanExpr` on actuals).
+2. **Inline `CanProc` into `CanDecl`** (call `CanStmt` on `NkProcDecl`
+   body/children directly).
+3. **Inline `LowerCall` into `LowerExpr`**'s `NkCall` branch.
+
+After inlining, no procedure calls a later or mutually-recursive one, so
+the V3 compiler accepts it and the fixpoint should return.  All three are
+mechanical (their bodies are small and already written).
+
+Then: `run_tests.sh` already needs `lower_ok t_lower3.mod`; expect
+**183/183** and **FIXPOINT OK**.
 
 ## Do NOT
 
-- Do not modify `SymTab` (that broke self-hosting).
-- Do not run the legacy and `L` emitters concurrently (shared
-  `QbeGen` buffer + counters).
-- Do not leave an `ASTDUMP` in `M2.atg`.
+- Do not add `FORWARD` (breaks gm2 whole-program pass).
+- Do not edit `SymTab`.
+- Do not run legacy + `L` emitters concurrently (shared `QbeGen` state).
+- Do not leave an `ASTDUMP` in `M2.atg` (it was committed once by
+  accident and cost time — `git show v3-lower3-wip:compiler/src/M2.atg`
+  had it).
+
+## Also re-apply (two real bugs, from earlier this session)
+
+- `NkProcDecl` `op` must be `0` for a normal body (`CanProc` checks it;
+  default is `-1`).
+- `NkCall` child 0 is an `NkDesignator`, not a bare `NkIdent` — unwrap it
+  (`CalleeName`), else calls mangle as `$_0`.
 
 ## Resume