# 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 `_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 `"_"` 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 `IMPORT`s a module pulls that module's procs/init/globals into the *same* `gen_ssa/.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 `_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.