Branch ast-stage-c. L7 = "flip: lower from the AST instead of
inline; delete the inline emit + Fwd* machinery".
The flip makes Lower the sole emitter. The obstacle is that QbeGen
holds one session state, used by the inline emit during parse; a
per-unit Lower emit would be clobbered by the next unit's inline emit.
So the flip is a big-bang: either remove/suppress the grammar's 533
QbeGen.* call sites, or give QbeGen two states — neither is a
byte-verifiable step.
The pragmatic path is therefore a session re-emit: collect every
unit's AST and re-emit the whole session with Lower after parsing.
That is byte-comparable against the legacy session image and becomes the
flip once the inline emit is removed.
d30abfd, tag v3-lower7-scaffold)Lower.LowerSession/LowerOneUnit/MaterializeGlobals.compiler.frm) collects GetUnit() per file and calls
Lower.LowerSession under -lower.SymTab.Materialize makes a unit's own VAR/CONST visible in
globScope, so GlobalRef resolves "<unit>_<name>" at session end
(the unit scopes are closed by then).Lower.Reset from the M2 root so the owned tables persist
across a session.Still scaffolding (inline emit untouched); suite 200/200, FIXPOINT OK (3,401,129 bytes).
ce2fc67, tag v3-lower7-session)DefUnit now builds an AST (proc headings recorded as
NkProcDecl); LowerDecls replays a bodyless proc as a heading.lower_ok_files added,
and DBasicProg, DQualProg, TQual (def-CONST), TQualP
(def-PROC) byte-match. Fixes: image named after the program module;
MaterializeGlobals also materializes TYPE/CLASS names;
SymTab.ReenterModGlobal keeps nested-module globals'
"<outer>_<name>" symbols; AST.Init once per session under -lower.Suite 204/204; FIXPOINT OK (3,405,813 bytes).
LowerSession needs every unit's
AST alive at once, but AST.MaxNodes = 65536 is sized for one unit;
the compiler's session overflows it (MakeNode → NoNode, later
units dump (nil)). Growing the arena past the 4 MiB sessBuf is
not viable. So the session re-emit cannot handle the compiler —
the flip must instead emit per unit at each unit's END (AST valid,
scope open), which is the two-state / inline-emit-removal problem.storage_prog differs: Lower emits
$malloc_6(l link, ...) where the legacy emits $malloc(...) — the
ext flag in Lower.NoteProc is hardcoded FALSE and Mangled's
ProcLink fails after scopes close.LowerDecls emitted a procedure's parameter loop even when ProcInfo
failed, using an uninitialised np — so the loop ran ~forever inside
FindProc. (MaxProc = 64 is far too small for a whole session, so
ProcInfo failed for most of the compiler's procs.) Fixed
(a9a8ecd): the proc is skipped unless ProcInfo succeeds, and each
ParamInfo is guarded. Also grew the AST arena/pool (caeeeb5) — the
arena/pool data lines are compact z runs, so this is nearly free.
Implemented an open-addressing name index (HashName/HashGetSym/
HashGetProc) for FindSym/FindProc in Lower. Results:
MaxProc=512 completed in <300 s; before it hung).MaxProc ≥ 713), and at MaxProc ∈ {768,1024} the same
run is slow/segfaults (CopySpan via ParamInfo) — the
size/layout-sensitive bug again.Lower, m2s2 (built by m1s1) produces no output on the compiler
input (stage 3). So the index triggers another latent V3 miscompile.Reverted to the green state (a9a8ecd). Suite 204/204.
Reproduced with a proc-only hash (HashName/ProcEq/HashPutProc/
HashGetProc; FindSym left linear): the fixpoint still fails at stage 3.
m2s2 (V3-built) works on small programs (t_exit, t_arith,
t_lower5set, t_classmethod) but aborts (SIGABRT) on
Lower.def + Lower.mod.HashPutProc ← NoteProc ← ProcHeading ← DefUnit —
i.e. m2s2 crashes while parsing the definition module's proc headings.(hashProc[s2] = 0) OR ProcEq(VAL(CARDINAL,
hashProc[s2]-1), name) evaluating the RHS (so 0-1 → huge index) —
was restructured away, and a ProcEq bounds guard added; neither
fixed it. So it is a deeper V3 miscompile of HashPutProc.Narrowing further needs many ~10-minute fixpoint cycles; reverted to
green (a9a8ecd). Suite 204/204.
a3ef897)The bad=T that blocked per-unit emission was a phantom: it was the
pre-existing invalid call errors in Lower.mod (forward references),
now fixed by declaring the walkers in Lower.def. With that gone, the
per-unit flip works end to end.
Lower.BeginSession (open + SetNoEmit), BeginUnit
(SaveSession) before each Parse, EndSession (write image).Lower.LowerUnitNow(unit, isProg) after each Parse:
RestoreSession → SetNoEmit(FALSE) → LowerOneUnit → SetNoEmit(TRUE).QbeGen.EndModule is a no-op under NoEmit, so the grammar's unit-end
emit neither flushes nor writes.LocAdd/FuncParam no longer skip NewTemp under
NoEmit, so the temp/NoteAddr numbering aligns with Lower's emit.MaxSym=4096, MaxProc=2048, MaxPar=32.HashStr/SpanEq/HashPut/HashGet,
HashSize=8192) for FindSym/FindProc. The compiler session now
lowers in ~1.5 s. (The earlier "hash miscompile" was a symptom of the
miscompiled Lower; it is gone now that Lower is V3-legal.)CopySpan's source is a VAR open array: gm2 copies a value
open-array formal to a stack VLA of its full length, which overflowed
the stack for the multi-megabyte tables.Lower.MarkProcExternal (called from the grammar's
EXTERNAL branch) records ext; LowerCall binds external callees to
their own name (no uid, no static link).CurrentCh):
LowerCall detects a proc-typed VAR/PARAM callee and uses
CallBeginInd.ActParam saves/restores the enclosing call's astArgs/astNArgs:
Factor reset astNArgs for designator arguments, so any call with
two or more designator actuals kept only the last one.ImplUnit/ProgModule reset astStmt := AST.NoNode after DeclSeq:
the module-body child was the stale last procedure body, so modules
without a module BEGIN (SysShim) grew a spurious _init.Lower.NoteConstVal: const values are per-unit (every module has
EOL), so they live in Lower's tables, not SymTab (which collided
across modules after EndUnit).LEN/HIGH added to LowerBuiltinExpr.The compiler session now lowers, but is not byte-identical to the
legacy image yet: the first divergence is in M2S.Get ($Get_61),
where Lower emits 148 labels vs. the legacy 430 — a large part of the
Get body (a big CASE ch OF …) is missing. lower_ok single-file
tests and the suite are green; the fixpoint is green (-lower is not
used there).
Suite 204/204; FIXPOINT OK (3,695,508 bytes), session 0 errors.
132a981, tag v3-lower7-gaps)First divergence moved to AST.AddTxt (line ~6961). Fixes:
AST.MaxChild (8):
CaseStat/CaseAlt appended arms with SetChild(cnode, NChild, arm),
which silently drops beyond 8. They now build a chunked arm sequence
(AstAppend, container kind NkBlock), and LowerCase follows the
continuation chunks. This was the big M2S.Get gap.VAL(T, e) in LowerBuiltinExpr, same widen/narrow/convert chain
as the grammar.FileIO.ReadBytes) resolve through Lower's proc
tables (SymTab's scopes are closed); CalleeAlias returns the base
name so the mangled symbol stays <base>_<uid>.LowerExprFull records value→address with
NoteAddr, mirroring the grammar's Design; AddrOfVal could not
rely on the grammar's notes because its 256-entry ring wraps during a
whole-session emit.CurrentCh := CharAt): Resolve recognises the
proc table and LowerProcAddr emits the code address; sibling method
names fall through to the THIS-binding path.Lower's symbol table is a single flat, latest-wins table, so
common names (s, i, k) collide across procedures. E.g. HIGH(s)
in AST.AddTxt (an open-array parameter) resolved to a fixed array of
256 from another procedure. lower_ok tests are too small to collide.
Two ways forward:
Lower with SymTab's scope open (the grammar-based call) and
resolve locals via SymTab first — the earlier attempt only broke
EndModule's ExitCode lookup, fixable by materialising the program
globals in EndSession.Suite 204/204; FIXPOINT OK (3,711,028 bytes), session 0 errors.