Files
bux-lang/bootstrap/lir_lower.nim
T
dimgigov 2e536488e6 feat(bootstrap): function pointer types + C emission
- Add tekFunc AST node and parser support for func(Params) -> Ret
- Add tkFunc type resolution in sema and hir_lower
- Add &FuncName address-taking yielding tkMutRef(tkFunc)
- Fix C emission: function pointer params use Ret (*name)(Params) syntax
  via cParamDecl helper that embeds the name inside (*)
- Fix forward declarations and temp declarations for function pointers
- Add funcPtrTypes table in C backend so &Fn temps get proper type
- Add _test_funcptr integration test
2026-06-08 22:31:32 +03:00

786 lines
25 KiB
Nim

## 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
## Convert LirValue to C expression string (no % prefix)
proc lirValToC(v: LirValue): string =
case v.kind
of lvkTemp: v.strVal
of lvkVar: v.strVal
of lvkInt: $v.intVal
of lvkFloat: $v.floatVal
of lvkString: v.strVal
of lvkLabel: v.strVal
of lvkGlobal: v.strVal
of lvkField: v.strVal
of lvkVoid: ""
of lvkType: v.strVal
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
## Loop end labels for break/continue
loopEndLabels*: seq[string]
loopStartLabels*: seq[string]
proc initLowerToLirCtx*(): LowerToLirCtx =
result = LowerToLirCtx(
builder: initLirBuilder(),
varTypes: initTable[string, string](),
varLirValues: initTable[string, LirValue](),
loopEndLabels: @[],
loopStartLabels: @[],
)
# ── 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
of tkFunc:
if typ.inner.len == 0: return "void (*)(void)"
let params = typ.inner[0..^2].mapIt(typeToCStr(it)).join(", ")
let ret = typeToCStr(typ.inner[^1])
return ret & " (*)(" & params & ")"
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)
# ── Alloca (address of local) ──
of hAlloca:
let cType = typeToCStr(node.allocaType)
let name = node.allocaName
if not ctx.varLirValues.hasKey(name):
ctx.varTypes[name] = cType
ctx.varLirValues[name] = lirVar(name)
b.emitAlloca(name, cType)
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: &expr
# Optimize: &struct.field → fieldPtr (no temp copy)
# &array[i] → indexPtr (no temp copy)
if node.unaryOperand.kind == hLoad and node.unaryOperand.loadPtr != nil:
let ptrNode = node.unaryOperand.loadPtr
case ptrNode.kind
of hFieldPtr:
let base = lowerExpr(ctx, ptrNode.fieldPtrBase)
let baseTyp = ptrNode.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} = &({lirValToC(base)}->{ptrNode.fieldName});")
else:
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}.{ptrNode.fieldName});")
return t
of hArrowField:
let base = lowerExpr(ctx, ptrNode.arrowFieldBase)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}->{ptrNode.arrowFieldName});")
return t
of hIndexPtr:
let base = lowerExpr(ctx, ptrNode.indexPtrBase)
let idx = lowerExpr(ctx, ptrNode.indexPtrIndex)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}[{lirValToC(idx)}]);")
return t
else: discard
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 branches for short-circuit evaluation
let t = b.freshTemp()
b.emitAlloca(t.strVal, "int")
let falseLbl = b.freshLabel("and_false")
let trueLbl = b.freshLabel("and_true")
let endLbl = b.freshLabel("and_end")
if node.binaryOp == tkAmpAmp:
# left && right: if !left goto false; if !right goto false; t=1; goto end; false: t=0; end:
b.emitJz(falseLbl, left)
b.emitJz(falseLbl, right)
b.emitMov(t, lirInt(1))
b.emitJmp(endLbl)
b.emitLabel(falseLbl)
b.emitMov(t, lirInt(0))
b.emitLabel(endLbl)
else:
# left || right: if left goto true; if right goto true; t=0; goto end; true: t=1; end:
b.emitJnz(trueLbl, left)
b.emitJnz(trueLbl, right)
b.emitMov(t, lirInt(0))
b.emitJmp(endLbl)
b.emitLabel(trueLbl)
b.emitMov(t, lirInt(1))
b.emitLabel(endLbl)
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*)&({lirValToC(base)}->{node.fieldName});")
else:
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}.{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*)&({lirValToC(base)}->{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*)&({lirValToC(base)}[{lirValToC(idx)}]);")
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} = {lirValToC(base)}->{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} = {lirValToC(base)}->{node.loadPtr.fieldName};")
else:
b.emitRawC(&"{t.strVal} = {lirValToC(base)}.{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} = {lirValToC(base)}[{lirValToC(idx)}];")
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}*){lirValToC(ptrVal)};")
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)({lirValToC(idx)}), ({lirValToC(base)}).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]))
else:
args.add(lirInt(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} = {lirValToC(receiver)}.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)
# ── Build C lvalue string for direct field/index assignment ──
proc buildLval(ctx: var LowerToLirCtx, n: HirNode): string =
case n.kind
of hLoad:
if n.loadPtr != nil:
return buildLval(ctx, n.loadPtr)
else:
let v = lowerExpr(ctx, n)
return lirValToC(v)
of hVar:
return n.varName
of hSelf:
return "self"
of hFieldPtr:
let baseStr = buildLval(ctx, n.fieldPtrBase)
let baseTyp = n.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let sep = if isPtr: "->" else: "."
return baseStr & sep & n.fieldName
of hArrowField:
let baseStr = buildLval(ctx, n.arrowFieldBase)
return baseStr & "->" & n.arrowFieldName
of hIndexPtr:
let baseStr = buildLval(ctx, n.indexPtrBase)
let idx = lowerExpr(ctx, n.indexPtrIndex)
return baseStr & "[" & lirValToC(idx) & "]"
else:
let v = lowerExpr(ctx, n)
return lirValToC(v)
# ── 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")
ctx.loopStartLabels.add(startLbl.strVal)
ctx.loopEndLabels.add(endLbl.strVal)
b.emitLabel(startLbl)
let cond = lowerExpr(ctx, node.whileCond)
b.emitJz(endLbl, cond)
lowerStmt(ctx, node.whileBody)
b.emitJmp(startLbl)
b.emitLabel(endLbl)
discard ctx.loopStartLabels.pop()
discard ctx.loopEndLabels.pop()
# ── Loop (infinite) ──
of hLoop:
let startLbl = b.freshLabel("loop")
let endLbl = b.freshLabel("lend")
ctx.loopStartLabels.add(startLbl.strVal)
ctx.loopEndLabels.add(endLbl.strVal)
b.emitLabel(startLbl)
lowerStmt(ctx, node.loopBody)
b.emitJmp(startLbl)
b.emitLabel(endLbl)
discard ctx.loopStartLabels.pop()
discard ctx.loopEndLabels.pop()
# ── Break ──
of hBreak:
if ctx.loopEndLabels.len > 0:
b.emitJmp(lirLabel(ctx.loopEndLabels[^1]))
else:
b.emitRawC("break;")
# ── Continue ──
of hContinue:
if ctx.loopStartLabels.len > 0:
b.emitJmp(lirLabel(ctx.loopStartLabels[^1]))
else:
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}*){lirValToC(ptrVal)} = {lirValToC(val)};")
# ── Assign ──
of hAssign:
let value = lowerExpr(ctx, node.assignValue)
case node.assignOp
of tkAssign:
case node.assignTarget.kind
of hFieldPtr:
let lval = buildLval(ctx, node.assignTarget)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hArrowField:
let lval = buildLval(ctx, node.assignTarget)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hIndexPtr:
let base = lowerExpr(ctx, node.assignTarget.indexPtrBase)
let idx = lowerExpr(ctx, node.assignTarget.indexPtrIndex)
b.emit(LirInstr(kind: lirStore, src: value, src2: base, dst: idx))
of hLoad:
if node.assignTarget.loadPtr != nil:
let ptrNode = node.assignTarget.loadPtr
case ptrNode.kind
of hIndexPtr:
let base = lowerExpr(ctx, ptrNode.indexPtrBase)
let idx = lowerExpr(ctx, ptrNode.indexPtrIndex)
b.emit(LirInstr(kind: lirStore, src: value, src2: base, dst: idx))
of hFieldPtr:
let lval = buildLval(ctx, ptrNode)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hArrowField:
let lval = buildLval(ctx, ptrNode)
b.emitRawC(&"{lval} = {lirValToC(value)};")
else:
let ptrVal = lowerExpr(ctx, ptrNode)
let valCType = hirTypeToC(ctx, node.assignValue)
b.emitRawC(&"*({valCType}*){lirValToC(ptrVal)} = {lirValToC(value)};")
else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value)
else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value)
of tkPlusAssign:
let target = lowerExpr(ctx, node.assignTarget)
let t = b.freshTemp()
b.emitBinOp(lirAdd, t, target, value)
b.emit(LirInstr(kind: lirStore, src: t, src2: target))
of tkMinusAssign:
let target = lowerExpr(ctx, node.assignTarget)
let t = b.freshTemp()
b.emitBinOp(lirSub, t, target, value)
b.emit(LirInstr(kind: lirStore, src: t, src2: target))
else:
let target = lowerExpr(ctx, node.assignTarget)
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