9.0 KiB
Memory Slab AST Refactoring Plan
Replace the memory() builtin function call with dedicated AST node types: MemorySlabReservation (declaration) and MemorySlabRef (reference).
Motivation
- Replace 22+ scattered
nameInSource == listOf("memory")string checks with proper type dispatch - Enable correct const propagation for memory slab pointers
- Clean separation between slab declaration and slab reference
- Memory slabs are always constant (assembly label references)
Design
CompilerAst (2 new nodes)
-
MemorySlabReservation—Statement, placed in enclosing block scope by CodeDesugarer- Fields:
slabName: String,size: UInt,align: UInt,position
- Fields:
-
MemorySlabRef—Expression, replacesmemory()call- Fields:
slabName: Stringonly (name-only ref, size/align live in reservation) constValue()returns null (address unknown at compile time)inferType()returns UWORD
- Fields:
SimpleAst (1 new node + existing mechanisms)
PtMemorySlabReservation— Statement, codegen no-op (slab emitted from symbol table)- Fields:
name: String,size: UInt,align: UInt,position
- Fields:
No PtMemorySlabRef needed. PtConstant(memorySlab=StMemorySlab(...)) replaces
PtFunctionCall("memory") in ALL contexts, including array/struct-alloc elements.
Note: PtConstant extends PtNamedNode, NOT PtExpression. For array/struct-alloc
elements, the SimplifiedAstMaker creates PtConstant via a special-case in the array
literal transform (not through transformExpression()). The PtArray's children
list accepts PtNode, so adding PtConstant is type-safe.
Existing mechanisms:
- Const variable:
PtConstant(memorySlab=StMemorySlab(...))— already exists - Array literal element:
PtConstant(memorySlab=StMemorySlab(...))via special-case in SimplifiedAstMaker array transform, →StArrayElement.MemorySlab(name)via SymbolTableMaker - Struct alloc argument:
PtConstant(memorySlab=StMemorySlab(...))via special-case in SimplifiedAstMaker array transform, →StArrayElement.MemorySlab(name)via SymbolTableMaker - Non-const var with memory() initializer: auto-promote to
PtConstant(value is always constant)
SymbolTableMaker changes:
makeInitialArray()adds anis PtConstant ->branch that extractsmemorySlab.name- Standalone memory() call handling changes from
is PtFunctionCall && isMemoryCalltois PtMemorySlabReservation createMemorySlabFromCallis removed (replaced by PtMemorySlabReservation handler)- The
structallocarg validation checksis PtConstantinstead ofis PtFunctionCall && isMemoryCall
Implementation Phases
Phase 1: SymbolTableMaker + Codegen + AstPrinter
- Add
PtMemorySlabReservationtosimpleAst/.../AstStatements.ktDONE - Add
PtMemorySlabReservationtoAstPrinter.ktDONE - Update
SymbolTableMaker:- Handle
PtMemorySlabReservation→ createsStMemorySlab, added to global scope - Add
is PtConstant ->branch inmakeInitialArray()→StArrayElement.MemorySlab(name)(no new slab creation, already from reservation) - Add
is PtConstant ->branch inhandleStructAllocation()inner check alongside existingis PtFunctionCall
- Handle
- Update ast printers/checkers that iterate
PtArraychildren: addPtConstantto accepted element types - 6502 codegen
AsmGen.kttranslate(): addis PtMemorySlabReservation -> {}(no-op) - IR codegen
IRCodeGen.kttranslateNode(): addis PtMemorySlabReservation -> emptyList()(no-op)
Phase 2: CompilerAst nodes + CodeDesugarer
- Add
MemorySlabReservationandMemorySlabRefto CompilerAst - Add
after(FunctionCallExpression)inCodeDesugarer.kt:- Check
isMemoryCall - Extract name, size, alignment from args
- Return
AstReplaceNode(call → MemorySlabRef) +AstInsert.before(MemorySlabReservation before containing statement)
- Check
- Update
AstIdentifiersChecker: slab name sanitization on MemorySlabReservation
Phase 3: SimplifiedAstMaker updates
- Side-channel map
slabDefs: Map<String, StMemorySlab>in SimplifiedAstMaker (populated by MemorySlabReservation processing) MemorySlabReservation→ createStMemorySlab, store in map, emitPtMemorySlabReservation- Const VarDecl with
MemorySlabRefinitializer → look up slab from map, createPtConstant(memorySlab=...) - Non-const VarDecl with
MemorySlabRefinitializer → auto-promote toPtConstant MemorySlabRefin array literal → special case intransform(srcArr: ArrayLiteral):- Before calling
transformExpression, check ifeltisFunctionCallExpression(isMemoryCall)orMemorySlabRef - Create
PtConstantdirectly (since notPtExpression, bypassestransformExpression) - Update element validation
require(...)to includePtConstant
- Before calling
- Same for struct alloc inner memory() →
PtConstantwith synthetic name - Remove all
isMemoryCallspecial cases from SimplifiedAstMaker (the array/const handling now uses MemorySlabRef)
Phase 4: Downstream CompilerAst pass updates
AstChecker: replaceisMemoryCallwithis MemorySlabRef/is MemorySlabReservation- Add explicit check: after desugaring, any remaining
FunctionCallExpressionwithisMemoryCallis an error
- Add explicit check: after desugaring, any remaining
VerifyFunctionArgTypes: visitMemorySlabReservationinstead of memory() callsConstantIdentifierReplacer: keep the memory() workaround (runs before desugaring); add parallel handling forMemorySlabReffor optimizer phaseLiteralsToAutoVarsAndRecombineIdentifiers: keep as-is (runs before desugaring)
Phase 5: Cleanup
- Remove
createMemorySlabFromCallhelper from SymbolTableMaker (no longer needed) - Remove standalone
is PtFunctionCall if node.isMemoryCall ->handler from SymbolTableMaker - Remove
funcMemory()from both codegens (6502 and IR) — no longer reachable since PtConstant replaces PtFunctionCall("memory") in all contexts - Remove
"memory"from BuiltinFunctions dispatch tables - AstChecker: add verification that no
PtFunctionCallwithisMemoryCallremains in SimpleAst - Remove dead
isMemoryCallimports where no longer used - Full test suite:
gradle build
isMemoryCall helper — what remains after refactoring
The extension property stays, but only used in pre-desugaring phases (where memory() still exists as a FunctionCallExpression):
| Phase | File | Reason |
|---|---|---|
| processAst step 2 | AstIdentifiersChecker.kt |
Sanitize slab name in the string arg |
| processAst step 2 | LiteralsToAutoVarsAndRecombineIdentifiers.kt |
Keep string literal, don't intern |
| processAst step 4 | ConstantIdentifierReplacer.kt |
Const reference replacement workaround |
| processAst step 6 | CodeDesugarer.kt |
Detect memory() calls to transform |
| processAst step 10 | AstChecker.kt |
Flag any surviving memory() call as error |
Post-desugaring phases (SimplifiedAstMaker, SymbolTableMaker, VerifyFunctionArgTypes, codegens) no longer need it — they see MemorySlabRef/MemorySlabReservation instead. Total reduction from ~22 uses to ~5.
Key Pipeline Flow (after implementation)
Source: const uword buf = memory("mybuf", 100, 1)
│
├─ Parser: FunctionCallExpression(target="memory", args=[StringLiteral, 100, 1])
│
├─ processAst step 2 (AstIdentifiersChecker):
│ └─ Sanitizes "mybuf" name in the FunctionCallExpression
│
├─ processAst step 2 (LiteralsToAutoVars):
│ └─ Keeps string literal as-is (not interned) because parent is memory() call
│
├─ processAst step 4 (ConstantIdentifierReplacer):
│ └─ CONST memory() variable: replaces identifier refs with FunctionCallExpression copies
│
├─ processAst step 6 (CodeDesugarer):
│ └─ Transforms FunctionCallExpression("memory") into:
│ MemorySlabReservation("mybuf", 100, 1) ← inserted before the VarDecl
│ const uword buf = MemorySlabRef("mybuf") ← replaces the call
│
├─ optimizeAst:
│ └─ MemorySlabRef constValue() = null → no constant folding
│
├─ postprocessAst (VerifyFunctionArgTypes):
│ └─ Visits MemorySlabReservation nodes for duplicate detection
│
├─ SimplifiedAstMaker:
│ ├─ MemorySlabReservation → PtMemorySlabReservation (and cached in side-channel map)
│ └─ Const VarDecl with MemorySlabRef → PtConstant(memorySlab=StMemorySlab(...))
│
├─ SymbolTableMaker:
│ ├─ PtMemorySlabReservation → StMemorySlab added to global scope
│ └─ PtConstant(memorySlab=...) → StConstant referencing the slab
│
├─ Codegen:
│ ├─ PtMemorySlabReservation → no-op
│ ├─ StConstant(memorySlab) → EQUATE: constname = prog8_slabs.memory_mybuf
│ └─ StMemorySlab → .section BSS_SLABS: mybuf .fill 100
└─
Non-const variable handling
uword buf = memory("x", 100, 1) — the slab address is constant, even if the variable isn't declared const. The SimplifiedAstMaker auto-promotes this to PtConstant(memorySlab=...), making it always const internally.