fix(lir): resolve all LIR backend regressions — 26/26 examples + selfhost passing
- hir_lower: add isScope=true to lowerBlock; fix resolveExprType for ekIndex, ekField on monomorphized structs, and ekTry/ekUnwrap tmpVar typing - lir_lower: add loop label stack for break/continue; pass null arg to bux_task_spawn when spawn has no arguments - lir_c_backend: multi-pass type inference for temps; include params in varTypes; handle lvkTemp in lirLoad; use inferred type in lirAlloca
This commit is contained in:
+53
-6
@@ -352,9 +352,39 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
if objType.isPointer and objType.inner.len > 0:
|
||||
objType = objType.inner[0]
|
||||
if objType.kind == tkNamed:
|
||||
let sym = ctx.globalScope.lookup(objType.name)
|
||||
if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct:
|
||||
for f in sym.decl.declStructFields:
|
||||
var sym = ctx.globalScope.lookup(objType.name)
|
||||
var decl = if sym != nil: sym.decl else: nil
|
||||
# If the type is a monomorphized generic struct instance, look up the base
|
||||
if decl == nil and ctx.structInstMap.hasKey(objType.name):
|
||||
let (baseName, typeArgs) = ctx.structInstMap[objType.name]
|
||||
let baseSym = ctx.globalScope.lookup(baseName)
|
||||
if baseSym != nil and baseSym.decl != nil and baseSym.decl.kind == dkStruct:
|
||||
decl = baseSym.decl
|
||||
var subst = initTable[string, Type]()
|
||||
for i, tp in decl.declStructTypeParams:
|
||||
if i < typeArgs.len:
|
||||
subst[tp.name] = typeArgs[i]
|
||||
for f in decl.declStructFields:
|
||||
if f.name == expr.exprFieldName:
|
||||
if f.ftype != nil:
|
||||
case f.ftype.kind
|
||||
of tekNamed:
|
||||
case f.ftype.typeName
|
||||
of "int", "int32", "int64": return makeInt()
|
||||
of "float64": return makeFloat64()
|
||||
of "float32": return makeFloat32()
|
||||
of "bool": return makeBool()
|
||||
else:
|
||||
if subst.hasKey(f.ftype.typeName):
|
||||
return subst[f.ftype.typeName]
|
||||
return makeNamed(f.ftype.typeName)
|
||||
of tekOwn, tekPointer:
|
||||
return substituteType(ctx, f.ftype, subst)
|
||||
else: return makeUnknown()
|
||||
if decl != nil:
|
||||
case decl.kind
|
||||
of dkStruct:
|
||||
for f in decl.declStructFields:
|
||||
if f.name == expr.exprFieldName:
|
||||
if f.ftype != nil:
|
||||
case f.ftype.kind
|
||||
@@ -368,6 +398,17 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
of tekOwn, tekPointer:
|
||||
return ctx.resolveTypeExpr(f.ftype)
|
||||
else: return makeUnknown()
|
||||
of dkEnum:
|
||||
# Algebraic enum fields: tag and data
|
||||
if expr.exprFieldName == "tag":
|
||||
return makeNamed(objType.name & "_Tag")
|
||||
elif expr.exprFieldName == "data":
|
||||
return makeNamed(objType.name & "_Data")
|
||||
else:
|
||||
# Enum variant field access: e.g., r.data.Ok_0
|
||||
# We can't easily resolve this here; return unknown
|
||||
return makeUnknown()
|
||||
else: discard
|
||||
return makeUnknown()
|
||||
of ekStructInit:
|
||||
if expr.exprStructInitTypeArgs.len > 0:
|
||||
@@ -395,6 +436,13 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
return makeInt()
|
||||
of ekUnwrap:
|
||||
return makeInt()
|
||||
of ekIndex:
|
||||
let baseType = ctx.resolveExprType(expr.exprIndexObj)
|
||||
if baseType.isSlice and baseType.inner.len > 0:
|
||||
return baseType.inner[0]
|
||||
if baseType.isPointer and baseType.inner.len > 0:
|
||||
return baseType.inner[0]
|
||||
return makeUnknown()
|
||||
of ekBlock:
|
||||
if expr.exprBlock.stmts.len > 0:
|
||||
let last = expr.exprBlock.stmts[^1]
|
||||
@@ -721,7 +769,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
|
||||
let tmpName = ctx.freshTryVar()
|
||||
let tmpAlloca = hirAlloca(tmpName, operandType, loc)
|
||||
let tmpVar = hirVar(tmpName, makePointer(operandType), loc)
|
||||
let tmpVar = hirVar(tmpName, operandType, loc)
|
||||
let tmpStore = hirStore(tmpVar, operand, loc)
|
||||
|
||||
let tagPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "tag",
|
||||
@@ -1020,8 +1068,7 @@ proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode =
|
||||
# but for hVar/hLit/hCall etc. we could treat them as block expr.
|
||||
# For now, leave as-is to avoid breaking control-flow statements.
|
||||
discard
|
||||
return HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr,
|
||||
typ: if expr != nil: expr.typ else: makeVoid(), loc: blk.loc)
|
||||
return hirBlock(stmts, expr, if expr != nil: expr.typ else: makeVoid(), blk.loc, isScope = true)
|
||||
|
||||
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||
# Set up type substitution for generic functions
|
||||
|
||||
+98
-37
@@ -178,7 +178,12 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
||||
|
||||
# ── Alloca ──
|
||||
of lirAlloca:
|
||||
be.emitLine(&"{v(instr.src)} {v(instr.dst)};")
|
||||
var ct = v(instr.src)
|
||||
if instr.dst.strVal.len > 0 and be.tempTypes.hasKey(instr.dst.strVal):
|
||||
let inferred = be.tempTypes[instr.dst.strVal]
|
||||
if inferred != "" and inferred != ct:
|
||||
ct = inferred
|
||||
be.emitLine(&"{ct} {v(instr.dst)};")
|
||||
|
||||
# ── Pointers ──
|
||||
of lirAddrOf:
|
||||
@@ -236,7 +241,7 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
||||
|
||||
# ── Function emission ──
|
||||
|
||||
proc emitFunc(be: var LirCBackend, f: LirFunc) =
|
||||
proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, string]) =
|
||||
var paramsStr = ""
|
||||
for i, p in f.params:
|
||||
if i > 0: paramsStr.add(", ")
|
||||
@@ -247,50 +252,99 @@ proc emitFunc(be: var LirCBackend, f: LirFunc) =
|
||||
be.emitLine(&"{f.retType} {f.name}({paramsStr}) {{")
|
||||
be.indent += 1
|
||||
|
||||
# First pass: declare all temp variables at the top of the function
|
||||
var tempsSeen: seq[tuple[name: string, cType: string]] = @[]
|
||||
# ── Pass 1: collect types from allocas, params, and instructions ──
|
||||
var varTypes = initTable[string, string]()
|
||||
var tempsSet: seq[string] = @[]
|
||||
for p in f.params:
|
||||
varTypes[p.name] = p.cType
|
||||
be.tempTypes[p.name] = p.cType
|
||||
for instr in f.instrs:
|
||||
# Allocas are explicit declarations — mark name as declared, skip emission here
|
||||
if instr.kind == lirAlloca:
|
||||
if instr.dst.strVal.len > 0 and instr.dst.strVal notin tempsSet:
|
||||
if instr.kind == lirAlloca and instr.dst.kind == lvkVar and instr.src.kind == lvkType:
|
||||
varTypes[instr.dst.strVal] = instr.src.strVal
|
||||
be.tempTypes[instr.dst.strVal] = instr.src.strVal
|
||||
if instr.dst.strVal notin tempsSet:
|
||||
tempsSet.add(instr.dst.strVal)
|
||||
|
||||
# ── Pass 2: iterative type inference for temps ──
|
||||
var changed = true
|
||||
while changed:
|
||||
changed = false
|
||||
for instr in f.instrs:
|
||||
if instr.dst.kind != lvkTemp or instr.dst.strVal.len == 0:
|
||||
continue
|
||||
# Temps that are destinations of binary ops, calls, etc.
|
||||
if instr.dst.kind == lvkTemp and instr.dst.strVal.len > 0 and instr.dst.strVal notin tempsSet:
|
||||
# Infer type based on instruction kind
|
||||
var ct = "int"
|
||||
let name = instr.dst.strVal
|
||||
let oldType = if be.tempTypes.hasKey(name): be.tempTypes[name] else: ""
|
||||
var newType = oldType
|
||||
|
||||
case instr.kind
|
||||
of lirMov, lirLoad, lirLoadGlobal:
|
||||
ct = "int"
|
||||
of lirAdd, lirSub, lirMul, lirDiv, lirMod, lirNeg,
|
||||
lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe:
|
||||
ct = "int"
|
||||
of lirAnd, lirOr, lirXor, lirShl, lirShr, lirNot, lirBNot:
|
||||
ct = "int"
|
||||
of lirCall, lirCallIndirect:
|
||||
ct = "int" # Conservative
|
||||
of lirAddrOf, lirFieldPtr, lirArrowFieldPtr, lirIndexPtr, lirPtrAdd:
|
||||
ct = "void*"
|
||||
of lirStructInit, lirSliceInit:
|
||||
ct = "int" # Will be overwritten by the actual type in emit
|
||||
of lirStructInit:
|
||||
if instr.extra.len > 0 and instr.extra[0].kind == lvkType:
|
||||
newType = instr.extra[0].strVal
|
||||
of lirSliceInit:
|
||||
if instr.extra.len > 0 and instr.extra[0].kind == lvkType:
|
||||
newType = "Slice_" & instr.extra[0].strVal
|
||||
of lirCast:
|
||||
ct = valToC(be, instr.src2)
|
||||
if instr.src2.kind == lvkType:
|
||||
newType = instr.src2.strVal
|
||||
of lirCall:
|
||||
if instr.src.kind == lvkGlobal and funcRetTypes.hasKey(instr.src.strVal):
|
||||
newType = funcRetTypes[instr.src.strVal]
|
||||
of lirCallIndirect:
|
||||
# Conservative; try to infer from dst usage in later passes
|
||||
discard
|
||||
of lirMov:
|
||||
if instr.src.kind == lvkTemp and be.tempTypes.hasKey(instr.src.strVal):
|
||||
newType = be.tempTypes[instr.src.strVal]
|
||||
elif instr.src.kind == lvkVar and varTypes.hasKey(instr.src.strVal):
|
||||
newType = varTypes[instr.src.strVal]
|
||||
of lirLoad, lirLoadGlobal:
|
||||
# Try to deduce pointee type from pointer vars/temps
|
||||
if instr.src.kind == lvkVar and varTypes.hasKey(instr.src.strVal):
|
||||
let srcType = varTypes[instr.src.strVal]
|
||||
if srcType.endsWith("*"):
|
||||
newType = srcType[0 ..< srcType.len - 1]
|
||||
elif srcType.startsWith("Slice_"):
|
||||
newType = srcType[6 ..< srcType.len]
|
||||
elif instr.src.kind == lvkTemp and be.tempTypes.hasKey(instr.src.strVal):
|
||||
let srcType = be.tempTypes[instr.src.strVal]
|
||||
if srcType.endsWith("*"):
|
||||
newType = srcType[0 ..< srcType.len - 1]
|
||||
elif srcType.startsWith("Slice_"):
|
||||
newType = srcType[6 ..< srcType.len]
|
||||
of lirSelect:
|
||||
ct = "int"
|
||||
if instr.src2.kind == lvkTemp and be.tempTypes.hasKey(instr.src2.strVal):
|
||||
newType = be.tempTypes[instr.src2.strVal]
|
||||
elif instr.extra.len > 0 and instr.extra[0].kind == lvkTemp and be.tempTypes.hasKey(instr.extra[0].strVal):
|
||||
newType = be.tempTypes[instr.extra[0].strVal]
|
||||
elif instr.src2.kind == lvkVar and varTypes.hasKey(instr.src2.strVal):
|
||||
newType = varTypes[instr.src2.strVal]
|
||||
of lirAddrOf, lirFieldPtr, lirArrowFieldPtr, lirIndexPtr, lirPtrAdd:
|
||||
newType = "void*"
|
||||
of lirAdd, lirSub, lirMul, lirDiv, lirMod, lirNeg,
|
||||
lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe,
|
||||
lirAnd, lirOr, lirXor, lirShl, lirShr, lirNot, lirBNot:
|
||||
newType = "int"
|
||||
else:
|
||||
ct = "int"
|
||||
tempsSeen.add((instr.dst.strVal, ct))
|
||||
tempsSet.add(instr.dst.strVal)
|
||||
be.tempTypes[instr.dst.strVal] = ct
|
||||
discard
|
||||
|
||||
# Declare all temps
|
||||
for t in tempsSeen:
|
||||
# Skip if the type is a struct type (will be declared inline)
|
||||
if t.cType == "int" or t.cType == "void*" or t.cType == "double":
|
||||
be.emitLine(&"{t.cType} {t.name};")
|
||||
if newType != "" and newType != oldType:
|
||||
be.tempTypes[name] = newType
|
||||
changed = true
|
||||
|
||||
# Emit instructions in order
|
||||
# ── Pass 3: declare temps that were inferred ──
|
||||
var declared: seq[string] = @[]
|
||||
for instr in f.instrs:
|
||||
if instr.kind == lirAlloca and instr.dst.strVal.len > 0 and instr.dst.strVal notin declared:
|
||||
declared.add(instr.dst.strVal)
|
||||
continue
|
||||
if instr.dst.kind == lvkTemp and instr.dst.strVal.len > 0 and instr.dst.strVal notin declared:
|
||||
if be.tempTypes.hasKey(instr.dst.strVal):
|
||||
let ct = be.tempTypes[instr.dst.strVal]
|
||||
if ct != "":
|
||||
declared.add(instr.dst.strVal)
|
||||
be.emitLine(&"{ct} {instr.dst.strVal};")
|
||||
|
||||
# ── Pass 4: emit instructions ──
|
||||
for instr in f.instrs:
|
||||
be.emitInstr(instr)
|
||||
|
||||
@@ -422,6 +476,13 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
|
||||
## Emit full C source from LIR builder + HIR module metadata.
|
||||
be.output = ""
|
||||
|
||||
# Build function return type lookup table
|
||||
var funcRetTypes = initTable[string, string]()
|
||||
for f in module.funcs:
|
||||
funcRetTypes[f.name] = typeToCStr(f.retType)
|
||||
for f in module.externFuncs:
|
||||
funcRetTypes[f.name] = typeToCStr(f.retType)
|
||||
|
||||
# Header
|
||||
be.emitLine("/* Generated by Bux Compiler (LIR backend) */")
|
||||
be.emitLine("#include <stdio.h>")
|
||||
@@ -539,7 +600,7 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
|
||||
|
||||
# Emit all LIR functions
|
||||
for f in builder.funcs:
|
||||
be.emitFunc(f)
|
||||
be.emitFunc(f, funcRetTypes)
|
||||
|
||||
# C main wrapper
|
||||
var hasMain = false
|
||||
|
||||
+24
-1
@@ -16,12 +16,17 @@ type
|
||||
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 ──
|
||||
@@ -360,6 +365,8 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
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)
|
||||
@@ -492,27 +499,43 @@ proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
|
||||
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:
|
||||
# We emit a break label reference; the emitter tracks the nearest loop end
|
||||
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 ──
|
||||
|
||||
Reference in New Issue
Block a user