feat: LIR backend replaces direct HIR→C emission (v0.3.0)
- 42 LIR instructions in bootstrap/lir.nim - HIR→LIR lowering in bootstrap/lir_lower.nim - LIR→C emission with full struct/enum/vtable support - All 22 examples + 5 Nim unit tests passing - Updated README status and backend description
This commit is contained in:
@@ -0,0 +1,625 @@
|
||||
## LIR Lowering — HIR → LIR
|
||||
## Converts the high-level HIR tree into flat, linear LIR instructions.
|
||||
## Each HIR node kind lowers to 1-20 LIR instructions.
|
||||
|
||||
import std/[strutils, strformat, tables, sequtils]
|
||||
import ast, types, token, hir, lir
|
||||
|
||||
type
|
||||
LowerToLirCtx* = object
|
||||
builder*: LirBuilder
|
||||
## Map HIR var names -> C type names (for alloca/load/store type info)
|
||||
varTypes*: Table[string, string]
|
||||
## Map HIR var names -> LirValue kind (lvkVar or lvkTemp)
|
||||
varLirValues*: Table[string, LirValue]
|
||||
## C types for function params / returns
|
||||
funcRetType*: string
|
||||
## Current source location for debug
|
||||
currentFile*: string
|
||||
|
||||
proc initLowerToLirCtx*(): LowerToLirCtx =
|
||||
result = LowerToLirCtx(
|
||||
builder: initLirBuilder(),
|
||||
varTypes: initTable[string, string](),
|
||||
varLirValues: initTable[string, LirValue](),
|
||||
)
|
||||
|
||||
# ── Helpers ──
|
||||
|
||||
proc cEscape(s: string): string =
|
||||
result = ""
|
||||
for c in s:
|
||||
case c
|
||||
of '\\': result.add("\\\\")
|
||||
of '"': result.add("\\\"")
|
||||
of '\n': result.add("\\n")
|
||||
of '\r': result.add("\\r")
|
||||
of '\t': result.add("\\t")
|
||||
of '\0': result.add("\\0")
|
||||
else: result.add(c)
|
||||
|
||||
proc typeToCStr(typ: Type): string =
|
||||
## Convert a Bux Type to a C type string.
|
||||
if typ == nil: return "int"
|
||||
case typ.kind
|
||||
of tkVoid: return "void"
|
||||
of tkBool, tkBool8, tkBool16, tkBool32: return "bool"
|
||||
of tkChar8: return "char"
|
||||
of tkChar16: return "char16_t"
|
||||
of tkChar32: return "char32_t"
|
||||
of tkStr: return "const char*"
|
||||
of tkInt8: return "int8_t"
|
||||
of tkInt16: return "int16_t"
|
||||
of tkInt32: return "int32_t"
|
||||
of tkInt64: return "int64_t"
|
||||
of tkInt: return "int"
|
||||
of tkUInt8: return "uint8_t"
|
||||
of tkUInt16: return "uint16_t"
|
||||
of tkUInt32: return "uint32_t"
|
||||
of tkUInt64: return "uint64_t"
|
||||
of tkUInt: return "unsigned int"
|
||||
of tkFloat32: return "float"
|
||||
of tkFloat64: return "double"
|
||||
of tkPointer, tkRef, tkMutRef:
|
||||
if typ.inner.len > 0:
|
||||
return typeToCStr(typ.inner[0]) & "*"
|
||||
return "void*"
|
||||
of tkDynRef:
|
||||
return typ.name & "_FatPtr"
|
||||
of tkSlice:
|
||||
let elem = if typ.inner.len > 0: typeToCStr(typ.inner[0]) else: "void"
|
||||
return "Slice_" & elem.replace(" ", "_").replace("*", "Ptr")
|
||||
of tkNamed:
|
||||
case typ.name
|
||||
of "String", "str": return "const char*"
|
||||
of "int": return "int"
|
||||
of "int8": return "int8_t"
|
||||
of "int16": return "int16_t"
|
||||
of "int32": return "int32_t"
|
||||
of "int64": return "int64_t"
|
||||
of "uint": return "unsigned int"
|
||||
of "uint8": return "uint8_t"
|
||||
of "uint16": return "uint16_t"
|
||||
of "uint32": return "uint32_t"
|
||||
of "uint64": return "uint64_t"
|
||||
of "float32": return "float"
|
||||
of "float64": return "double"
|
||||
of "bool": return "bool"
|
||||
else: return typ.name
|
||||
else: return "int"
|
||||
|
||||
proc hirTypeToC(ctx: var LowerToLirCtx, node: HirNode): string =
|
||||
if node == nil: return "int"
|
||||
result = typeToCStr(node.typ)
|
||||
|
||||
proc binOpToLir(op: TokenKind): LirKind =
|
||||
case op
|
||||
of tkPlus: lirAdd
|
||||
of tkMinus: lirSub
|
||||
of tkStar: lirMul
|
||||
of tkSlash: lirDiv
|
||||
of tkPercent: lirMod
|
||||
of tkAmp: lirAnd
|
||||
of tkPipe: lirOr
|
||||
of tkCaret: lirXor
|
||||
of tkShl: lirShl
|
||||
of tkShr: lirShr
|
||||
else: lirAdd
|
||||
|
||||
proc cmpOpToLir(op: TokenKind): LirKind =
|
||||
case op
|
||||
of tkEq: lirCmpEq
|
||||
of tkNe: lirCmpNe
|
||||
of tkLt: lirCmpLt
|
||||
of tkLe: lirCmpLe
|
||||
of tkGt: lirCmpGt
|
||||
of tkGe: lirCmpGe
|
||||
else: lirCmpEq
|
||||
|
||||
# ── Forward declarations ──
|
||||
proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue
|
||||
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode)
|
||||
|
||||
# ── Lowering: Expressions → LirValue ──
|
||||
|
||||
proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
if node == nil: return lirInt(0)
|
||||
template b: var LirBuilder = ctx.builder
|
||||
|
||||
case node.kind
|
||||
|
||||
# ── Literals ──
|
||||
of hLit:
|
||||
case node.litToken.kind
|
||||
of tkBoolLiteral:
|
||||
if node.litToken.text == "true": return lirInt(1)
|
||||
else: return lirInt(0)
|
||||
of tkStringLiteral:
|
||||
var text = node.litToken.text
|
||||
# Handle backtick strings
|
||||
if text.len >= 2 and text[0] == '`' and text[text.len-1] == '`':
|
||||
text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
|
||||
elif text.len >= 2 and text[0] == '"' and text[text.len-1] == '"':
|
||||
# Strip c8" c16" c32" prefixes
|
||||
if text.startsWith("c32\""):
|
||||
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
|
||||
elif text.startsWith("c16\""):
|
||||
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
|
||||
elif text.startsWith("c8\""):
|
||||
text = "\"" & cEscape(text[3 ..< text.len-1]) & "\""
|
||||
else:
|
||||
text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
|
||||
elif text.len >= 2 and text[0] == '"':
|
||||
text = "\"" & cEscape(text[1 ..< text.len]) & "\""
|
||||
else:
|
||||
text = "\"" & cEscape(text) & "\""
|
||||
return lirStr(text)
|
||||
of tkNull:
|
||||
return lirInt(0)
|
||||
else:
|
||||
# Integer/float literal
|
||||
return lirVar(node.litToken.text)
|
||||
|
||||
# ── Variable reference ──
|
||||
of hVar:
|
||||
let name = node.varName
|
||||
if ctx.varLirValues.hasKey(name):
|
||||
return ctx.varLirValues[name]
|
||||
return lirVar(name)
|
||||
|
||||
# ── Self ──
|
||||
of hSelf:
|
||||
return lirVar("self")
|
||||
|
||||
# ── Unary ──
|
||||
of hUnary:
|
||||
let operand = lowerExpr(ctx, node.unaryOperand)
|
||||
case node.unaryOp
|
||||
of tkMinus:
|
||||
let t = b.freshTemp()
|
||||
b.emitUnary(lirNeg, t, operand)
|
||||
return t
|
||||
of tkBang:
|
||||
let t = b.freshTemp()
|
||||
b.emitUnary(lirNot, t, operand)
|
||||
return t
|
||||
of tkTilde:
|
||||
let t = b.freshTemp()
|
||||
b.emitUnary(lirBNot, t, operand)
|
||||
return t
|
||||
of tkStar:
|
||||
# Dereference: *ptr → load
|
||||
let t = b.freshTemp()
|
||||
b.emitLoad(t, operand)
|
||||
return t
|
||||
of tkAmp:
|
||||
# Address of: &var
|
||||
let t = b.freshTemp()
|
||||
b.emitAddrOf(t, operand)
|
||||
return t
|
||||
else:
|
||||
return operand
|
||||
|
||||
# ── Binary ──
|
||||
of hBinary:
|
||||
let left = lowerExpr(ctx, node.binaryLeft)
|
||||
let right = lowerExpr(ctx, node.binaryRight)
|
||||
case node.binaryOp
|
||||
of tkEq, tkNe, tkLt, tkLe, tkGt, tkGe:
|
||||
let t = b.freshTemp()
|
||||
b.emitCmp(cmpOpToLir(node.binaryOp), t, left, right)
|
||||
return t
|
||||
of tkAmpAmp, tkPipePipe:
|
||||
# Logical and/or: lowered to select
|
||||
let t = b.freshTemp()
|
||||
if node.binaryOp == tkAmpAmp:
|
||||
# left && right → left ? (right != 0) : 0
|
||||
let rhsBool = b.freshTemp()
|
||||
b.emitCmp(lirCmpNe, rhsBool, right, lirInt(0))
|
||||
b.emitSelect(t, left, rhsBool, lirInt(0))
|
||||
else:
|
||||
# left || right → left ? 1 : (right != 0)
|
||||
let rhsBool = b.freshTemp()
|
||||
b.emitCmp(lirCmpNe, rhsBool, right, lirInt(0))
|
||||
b.emitSelect(t, left, lirInt(1), rhsBool)
|
||||
return t
|
||||
else:
|
||||
let t = b.freshTemp()
|
||||
b.emitBinOp(binOpToLir(node.binaryOp), t, left, right)
|
||||
return t
|
||||
|
||||
# ── Call ──
|
||||
of hCall:
|
||||
var args: seq[LirValue] = @[]
|
||||
for arg in node.callArgs:
|
||||
args.add(lowerExpr(ctx, arg))
|
||||
let callee = node.callCallee
|
||||
let t = b.freshTemp()
|
||||
let cType = hirTypeToC(ctx, node)
|
||||
if cType != "void" and cType != "":
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
b.emitCall(t, callee, args)
|
||||
return t
|
||||
|
||||
# ── CallIndirect ──
|
||||
of hCallIndirect:
|
||||
let callee = lowerExpr(ctx, node.callIndirectCallee)
|
||||
var args: seq[LirValue] = @[callee]
|
||||
for arg in node.callIndirectArgs:
|
||||
args.add(lowerExpr(ctx, arg))
|
||||
let t = b.freshTemp()
|
||||
let cType = hirTypeToC(ctx, node)
|
||||
if cType != "void" and cType != "":
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
# Use lirCallIndirect: dst = (*fn_ptr)(args...)
|
||||
b.emit(LirInstr(kind: lirCallIndirect, dst: t, src: callee, extra: args[1..^1]))
|
||||
return t
|
||||
|
||||
# ── Field pointer expressions (return address) ──
|
||||
# These return a typed pointer (void* for now, cast before deref)
|
||||
of hFieldPtr:
|
||||
let base = lowerExpr(ctx, node.fieldPtrBase)
|
||||
let baseTyp = node.fieldPtrBase.typ
|
||||
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, "void*")
|
||||
if isPtr:
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}->{node.fieldName});")
|
||||
else:
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}.{node.fieldName});")
|
||||
return t
|
||||
|
||||
of hArrowField:
|
||||
let base = lowerExpr(ctx, node.arrowFieldBase)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, "void*")
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}->{node.arrowFieldName});")
|
||||
return t
|
||||
|
||||
of hIndexPtr:
|
||||
let base = lowerExpr(ctx, node.indexPtrBase)
|
||||
let idx = lowerExpr(ctx, node.indexPtrIndex)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, "void*")
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}[{idx.strVal}]);")
|
||||
return t
|
||||
|
||||
# ── Load ──
|
||||
of hLoad:
|
||||
# Load through a pointer or field access
|
||||
# Optimize common patterns: load(field_ptr) → direct field access
|
||||
if node.loadPtr != nil and node.loadPtr.kind == hArrowField:
|
||||
let base = lowerExpr(ctx, node.loadPtr.arrowFieldBase)
|
||||
let cType = hirTypeToC(ctx, node)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
b.emitRawC(&"{t.strVal} = {base.strVal}->{node.loadPtr.arrowFieldName};")
|
||||
return t
|
||||
if node.loadPtr != nil and node.loadPtr.kind == hFieldPtr:
|
||||
let base = lowerExpr(ctx, node.loadPtr.fieldPtrBase)
|
||||
let baseTyp = node.loadPtr.fieldPtrBase.typ
|
||||
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
|
||||
let cType = hirTypeToC(ctx, node)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
if isPtr:
|
||||
b.emitRawC(&"{t.strVal} = {base.strVal}->{node.loadPtr.fieldName};")
|
||||
else:
|
||||
b.emitRawC(&"{t.strVal} = {base.strVal}.{node.loadPtr.fieldName};")
|
||||
return t
|
||||
if node.loadPtr != nil and node.loadPtr.kind == hIndexPtr:
|
||||
let base = lowerExpr(ctx, node.loadPtr.indexPtrBase)
|
||||
let idx = lowerExpr(ctx, node.loadPtr.indexPtrIndex)
|
||||
let cType = hirTypeToC(ctx, node)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
b.emitRawC(&"{t.strVal} = {base.strVal}[{idx.strVal}];")
|
||||
return t
|
||||
# Generic: dereference pointer
|
||||
let ptrVal = lowerExpr(ctx, node.loadPtr)
|
||||
let cType = hirTypeToC(ctx, node)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
b.emitRawC(&"{t.strVal} = *({cType}*){ptrVal.strVal};")
|
||||
return t
|
||||
|
||||
# ── Slice Index ──
|
||||
of hSliceIndex:
|
||||
let base = lowerExpr(ctx, node.sliceIndexBase)
|
||||
let idx = lowerExpr(ctx, node.sliceIndexIndex)
|
||||
let t = b.freshTemp()
|
||||
# Emit: base.data[idx] (with optional bounds check)
|
||||
if node.sliceIndexBoundsCheck:
|
||||
b.emitRawC(&"bux_bounds_check((size_t)({idx.strVal}), ({base.strVal}).len)")
|
||||
b.emit(LirInstr(kind: lirLoad, dst: t, src: base, src2: idx))
|
||||
return t
|
||||
|
||||
# ── Cast ──
|
||||
of hCast:
|
||||
let operand = lowerExpr(ctx, node.castOperand)
|
||||
let targetCType = typeToCStr(node.castType)
|
||||
let t = b.freshTemp()
|
||||
b.emitCast(t, operand, targetCType)
|
||||
return t
|
||||
|
||||
# ── SizeOf ──
|
||||
of hSizeOf:
|
||||
let ctype = typeToCStr(node.sizeOfType)
|
||||
let t = b.freshTemp()
|
||||
b.emit(LirInstr(kind: lirRawC, src: lirStr(&"/* sizeof({ctype}) */")))
|
||||
return lirVar(&"sizeof({ctype})")
|
||||
|
||||
# ── Spawn ──
|
||||
of hSpawn:
|
||||
if node.spawnAsync:
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, "void*")
|
||||
b.emitCall(t, "bux_async_spawn", @[lirGlobal(node.spawnCallee)])
|
||||
return t
|
||||
else:
|
||||
var args: seq[LirValue] = @[]
|
||||
if node.spawnArgs.len > 0:
|
||||
args.add(lowerExpr(ctx, node.spawnArgs[0]))
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, "void*")
|
||||
b.emitCall(t, "bux_task_spawn", @[lirGlobal(node.spawnCallee)] & args)
|
||||
return t
|
||||
|
||||
# ── DynRef (trait object) ──
|
||||
of hDynRef:
|
||||
let data = lowerExpr(ctx, node.dynRefData)
|
||||
let t = b.freshTemp()
|
||||
let fatPtrType = node.dynRefInterface & "_FatPtr"
|
||||
b.emitRawC(&"{fatPtrType} {t.strVal};")
|
||||
b.emitMov(lirVar(t.strVal & ".data"), data)
|
||||
b.emitMov(lirVar(t.strVal & ".vtable"), lirGlobal(node.dynRefConcreteType & "_" & node.dynRefInterface & "_VTable"))
|
||||
return t
|
||||
|
||||
# ── DynCall ──
|
||||
of hDynCall:
|
||||
let receiver = lowerExpr(ctx, node.dynCallReceiver)
|
||||
var args: seq[LirValue] = @[receiver]
|
||||
for i in 1 ..< node.dynCallArgs.len:
|
||||
args.add(lowerExpr(ctx, node.dynCallArgs[i]))
|
||||
let t = b.freshTemp()
|
||||
b.emitRawC(&"{t.strVal} = {receiver.strVal}.vtable->{node.dynCallMethod}({args.mapIt($it).join(\", \")});")
|
||||
return t
|
||||
|
||||
# ── StructInit ──
|
||||
of hStructInit:
|
||||
var fields: seq[tuple[name: string, val: LirValue]] = @[]
|
||||
for f in node.structInitFields:
|
||||
fields.add((f.name, lowerExpr(ctx, f.value)))
|
||||
let t = b.freshTemp()
|
||||
b.emitStructInit(t, node.structInitName, fields)
|
||||
return t
|
||||
|
||||
# ── SliceInit ──
|
||||
of hSliceInit:
|
||||
let t = b.freshTemp()
|
||||
let elemType = if node.typ.inner.len > 0: typeToCStr(node.typ.inner[0]) else: "void"
|
||||
var elems: seq[LirValue] = @[]
|
||||
for e in node.sliceInitElements:
|
||||
elems.add(lowerExpr(ctx, e))
|
||||
# Create a temporary array, then wrap in slice
|
||||
let arrTmp = b.freshTemp()
|
||||
b.emitRawC(&"{elemType} {arrTmp.strVal}[] = {{{elems.mapIt($it).join(\", \")}}};")
|
||||
b.emitSliceInit(t, elemType, arrTmp, lirInt(node.sliceInitLen))
|
||||
return t
|
||||
|
||||
# ── TupleInit ──
|
||||
of hTupleInit:
|
||||
var elems: seq[LirValue] = @[]
|
||||
for e in node.tupleInitElements:
|
||||
elems.add(lowerExpr(ctx, e))
|
||||
let t = b.freshTemp()
|
||||
b.emitRawC(&"/* tuple */ {t.strVal} = {{{elems.mapIt($it).join(\", \")}}};")
|
||||
return t
|
||||
|
||||
# ── If expression (ternary) ──
|
||||
of hIf:
|
||||
if node.ifThen.kind != hBlock and node.ifElse != nil:
|
||||
# Simple ternary
|
||||
let cond = lowerExpr(ctx, node.ifCond)
|
||||
let thenVal = lowerExpr(ctx, node.ifThen)
|
||||
let elseVal = lowerExpr(ctx, node.ifElse)
|
||||
let t = b.freshTemp()
|
||||
b.emitSelect(t, cond, thenVal, elseVal)
|
||||
return t
|
||||
else:
|
||||
# Complex if — fallback to block lowering
|
||||
# This shouldn't happen if lowering is done right, but handle gracefully
|
||||
return lirInt(0)
|
||||
|
||||
# ── Block expression (returns last expr) ──
|
||||
of hBlock:
|
||||
for stmt in node.blockStmts:
|
||||
lowerStmt(ctx, stmt)
|
||||
if node.blockExpr != nil:
|
||||
return lowerExpr(ctx, node.blockExpr)
|
||||
return lirVoid()
|
||||
|
||||
# ── Match (lowered by hir_lower already, but handle if present) ──
|
||||
of hMatch:
|
||||
# Should have been lowered by hir_lower.nim already
|
||||
return lirInt(0)
|
||||
|
||||
else:
|
||||
# Fallback for unhandled expression kinds
|
||||
b.emitComment(&"/* unhandled expr kind: {node.kind} */")
|
||||
return lirInt(0)
|
||||
|
||||
# ── Lowering: Statements → void ──
|
||||
|
||||
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
|
||||
if node == nil: return
|
||||
template b: var LirBuilder = ctx.builder
|
||||
|
||||
case node.kind
|
||||
|
||||
# ── Return ──
|
||||
of hReturn:
|
||||
if node.returnValue != nil:
|
||||
let val = lowerExpr(ctx, node.returnValue)
|
||||
b.emitRet(val)
|
||||
else:
|
||||
b.emitRet()
|
||||
|
||||
# ── If statement ──
|
||||
of hIf:
|
||||
# Lower to: cond = lower(ifCond); jz else_label, cond
|
||||
# lower(ifThen); jmp end_label
|
||||
# else_label: lower(ifElse); end_label:
|
||||
let cond = lowerExpr(ctx, node.ifCond)
|
||||
let elseLbl = b.freshLabel("else")
|
||||
let endLbl = b.freshLabel("endif")
|
||||
|
||||
if node.ifElse != nil:
|
||||
b.emitJz(elseLbl, cond)
|
||||
lowerStmt(ctx, node.ifThen)
|
||||
b.emitJmp(endLbl)
|
||||
b.emitLabel(elseLbl)
|
||||
lowerStmt(ctx, node.ifElse)
|
||||
b.emitLabel(endLbl)
|
||||
else:
|
||||
b.emitJz(endLbl, cond)
|
||||
lowerStmt(ctx, node.ifThen)
|
||||
b.emitLabel(endLbl)
|
||||
|
||||
# ── While statement ──
|
||||
of hWhile:
|
||||
let startLbl = b.freshLabel("while")
|
||||
let bodyLbl = b.freshLabel("wbody")
|
||||
let endLbl = b.freshLabel("wend")
|
||||
|
||||
b.emitLabel(startLbl)
|
||||
let cond = lowerExpr(ctx, node.whileCond)
|
||||
b.emitJz(endLbl, cond)
|
||||
lowerStmt(ctx, node.whileBody)
|
||||
b.emitJmp(startLbl)
|
||||
b.emitLabel(endLbl)
|
||||
|
||||
# ── Loop (infinite) ──
|
||||
of hLoop:
|
||||
let startLbl = b.freshLabel("loop")
|
||||
b.emitLabel(startLbl)
|
||||
lowerStmt(ctx, node.loopBody)
|
||||
b.emitJmp(startLbl)
|
||||
|
||||
# ── Break ──
|
||||
of hBreak:
|
||||
# We emit a break label reference; the emitter tracks the nearest loop end
|
||||
b.emitRawC("break;")
|
||||
|
||||
# ── Continue ──
|
||||
of hContinue:
|
||||
b.emitRawC("continue;")
|
||||
|
||||
# ── Alloca ──
|
||||
of hAlloca:
|
||||
let cType = typeToCStr(node.allocaType)
|
||||
let name = node.allocaName
|
||||
ctx.varTypes[name] = cType
|
||||
ctx.varLirValues[name] = lirVar(name)
|
||||
b.emitAlloca(name, cType)
|
||||
|
||||
# ── Store ──
|
||||
of hStore:
|
||||
# If storing to a simple variable, use mov (direct assignment)
|
||||
if node.storePtr.kind == hVar:
|
||||
let val = lowerExpr(ctx, node.storeValue)
|
||||
b.emitMov(lirVar(node.storePtr.varName), val)
|
||||
else:
|
||||
let ptrVal = lowerExpr(ctx, node.storePtr)
|
||||
let val = lowerExpr(ctx, node.storeValue)
|
||||
# ptrVal is a void* address; cast and store
|
||||
let valCType = hirTypeToC(ctx, node.storeValue)
|
||||
b.emitRawC(&"*({valCType}*){ptrVal.strVal} = {val.strVal};")
|
||||
|
||||
# ── Assign ──
|
||||
of hAssign:
|
||||
let target = lowerExpr(ctx, node.assignTarget)
|
||||
let value = lowerExpr(ctx, node.assignValue)
|
||||
case node.assignOp
|
||||
of tkAssign:
|
||||
b.emitMov(target, value)
|
||||
of tkPlusAssign:
|
||||
let t = b.freshTemp()
|
||||
b.emitBinOp(lirAdd, t, target, value)
|
||||
b.emit(LirInstr(kind: lirStore, src: t, src2: target))
|
||||
of tkMinusAssign:
|
||||
let t = b.freshTemp()
|
||||
b.emitBinOp(lirSub, t, target, value)
|
||||
b.emit(LirInstr(kind: lirStore, src: t, src2: target))
|
||||
else:
|
||||
b.emitMov(target, value)
|
||||
|
||||
# ── Call statement (void return) ──
|
||||
of hCall:
|
||||
var args: seq[LirValue] = @[]
|
||||
for arg in node.callArgs:
|
||||
args.add(lowerExpr(ctx, arg))
|
||||
b.emitCallVoid(node.callCallee, args)
|
||||
|
||||
# ── CallIndirect statement ──
|
||||
of hCallIndirect:
|
||||
let callee = lowerExpr(ctx, node.callIndirectCallee)
|
||||
var args: seq[LirValue] = @[]
|
||||
for arg in node.callIndirectArgs:
|
||||
args.add(lowerExpr(ctx, arg))
|
||||
b.emit(LirInstr(kind: lirCallIndirect, src: callee, extra: args))
|
||||
|
||||
# ── Block ──
|
||||
of hBlock:
|
||||
if node.isScope:
|
||||
b.emitRawC("{")
|
||||
for stmt in node.blockStmts:
|
||||
lowerStmt(ctx, stmt)
|
||||
if node.blockExpr != nil:
|
||||
let exprVal = lowerExpr(ctx, node.blockExpr)
|
||||
# If block is an expression, store result
|
||||
discard
|
||||
if node.isScope:
|
||||
b.emitRawC("}")
|
||||
|
||||
# ── Emit (inline C) ──
|
||||
of hEmit:
|
||||
b.emitRawC(node.emitCode)
|
||||
|
||||
# ── Expression statement ──
|
||||
else:
|
||||
let exprVal = lowerExpr(ctx, node)
|
||||
# Expression evaluated for side effects; temp is unused
|
||||
discard
|
||||
|
||||
# ── Module-level lowering ──
|
||||
|
||||
proc lowerModuleToLir*(hirMod: HirModule): LirBuilder =
|
||||
## Convert a full HIR module into LIR functions.
|
||||
var ctx = initLowerToLirCtx()
|
||||
|
||||
for f in hirMod.funcs:
|
||||
var params: seq[tuple[name: string, cType: string]] = @[]
|
||||
for p in f.params:
|
||||
let ct = typeToCStr(p.typ)
|
||||
params.add((p.name, ct))
|
||||
ctx.varTypes[p.name] = ct
|
||||
ctx.varLirValues[p.name] = lirVar(p.name)
|
||||
|
||||
let retCT = if f.retType != nil: typeToCStr(f.retType) else: "void"
|
||||
ctx.funcRetType = retCT
|
||||
ctx.builder.beginFunc(f.name, params, retCT, f.isPublic)
|
||||
|
||||
if f.body != nil:
|
||||
if f.body.kind == hBlock:
|
||||
for stmt in f.body.blockStmts:
|
||||
lowerStmt(ctx, stmt)
|
||||
if f.body.blockExpr != nil and f.retType != nil and f.retType.kind != tkVoid:
|
||||
let val = lowerExpr(ctx, f.body.blockExpr)
|
||||
ctx.builder.emitRet(val)
|
||||
else:
|
||||
lowerStmt(ctx, f.body)
|
||||
|
||||
ctx.builder.endFunc()
|
||||
|
||||
return ctx.builder
|
||||
Reference in New Issue
Block a user