|
|
@@ -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
|
|
|
|