summary_lower_l6.md 3.8 KB

Lower phase — L6 status (2026-10-05)

Branch ast-stage-c. L6 = "classes/vtables, imports, nested modules".

Landed

Nested modules (f82f72b, tag v3-lower6-nested)

  • CanDecl accepts a nested NkUnit (with no imports).
  • LowerDecls emits the nested unit's declarations and, when it has a BEGIN body, BeginInit/body/EndInit (registering <nested>_init so the enclosing BeginBody calls it).
  • ReenterGlobals re-registers a nested module's non-exported globals in the still-open enclosing module scope, so QbeGen.GlobalRef resolves their "<outer>_<name>" symbols after the nested scope has been popped.
  • lower_ok t_nestedmod.mod byte-matches. Suite 194/194; FIXPOINT OK (3,328,857 bytes).

Blocked / remaining

Imports — blocked by the session-image model

-lower is invoked only from ProgModule's END (M2.atg), so Lower re-emits only the last unit. But a session's image is accumulated across all units (sessBuf): a program that IMPORTs a module pulls that module's procs/init/globals into the same gen_ssa/<Prog>.ssa. So re-emitting just the program cannot byte-match.

Supporting imports therefore needs a mechanism change: lower every unit (definition/implementation/program) and accumulate a session image (mirroring sessBuf), or otherwise compare per unit. That is the same mechanism the L7 flip needs, so it is the natural next structural step. Qualified names (M.x) additionally need SymTab.MaterializeAlias at lowering time (module scope is open — workable) and qualified-call resolution in LowerCall (currently CalleeName reads only the base ident).

Classes/vtables — done

751a227 (methods, fields, sibling calls, inheritance) and 7aa21ff (virtual dispatch, class pointers, empty class BEGIN).

  • Class declarations (op=0) emit nothing but replay each method heading (BeginFunc/FuncParam/AbortFunc) so the backend temp counters advance exactly as the legacy parse did.
  • Class implementations (op=1) emit each method with a hidden THIS receiver, THIS pushed as the WITH base (bare field names resolve); a BEGIN body emits <class>_init (empty BEGIN is marked with an empty NkBlock in M2.atg so it is distinguishable from "no BEGIN").
  • Method calls: obj.Method, p^.Method and bare sibling names bind the receiver (ArmRecv) and dispatch via VirtCallBegin/CallBegin.
  • CanType accepts ClClass; methods are gated by CanMethod/ CanMethods with the class pushed as the WITH base.

lower_ok: t_class, t_classinit, t_classmethod, t_classinherit, t_classsibling, t_virtual. Suite 200/200; FIXPOINT OK (3,378,633 bytes).

Qualified names (prepared for the flip)

76048e0 (v3-lower6-qualified): M.x designators and M.P calls resolve through MaterializeAlias + LoadDesignator / SymTab.Proc*/Param*; CanLower no longer rejects imports.

Not yet byte-verifiable. Any multi-unit session contributes code/data and consumes backend counters from the earlier units — e.g. a definition-module CONST emits data $M_K, and a def-module proc heading consumes a temp — while -lower re-emits only the last unit. So imports can only be verified once the per-unit mechanism exists.

Remaining: the per-unit mechanism (= the L7 flip)

Lower is invoked only from ProgModule's END, so it re-emits one unit. Making it re-emit every unit needs either a second backend state (QbeGen is a singleton) or the flip — deleting the inline emit so Lower becomes the sole emitter, running per unit with that unit's scope still open (which is also what makes GlobalRef/MaterializeAlias resolve module symbols naturally).

The flip is the L7 step. Because Lower must then handle everything the compiler uses, the qualified-name support above is its prerequisite.