From 797ca7c80ca20be1d3fc45a2b94e4559ab3271fe Mon Sep 17 00:00:00 2001 From: dimgigov Date: Sat, 6 Jun 2026 19:49:24 +0300 Subject: [PATCH] =?UTF-8?q?fix(lir):=20resolve=20all=20LIR=20backend=20reg?= =?UTF-8?q?ressions=20=E2=80=94=2026/26=20examples=20+=20selfhost=20passin?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- bootstrap/hir_lower.nim | 85 +++++++++++++++++----- bootstrap/lir_c_backend.nim | 137 ++++++++++++++++++++++++++---------- bootstrap/lir_lower.nim | 29 +++++++- 3 files changed, 191 insertions(+), 60 deletions(-) diff --git a/bootstrap/hir_lower.nim b/bootstrap/hir_lower.nim index da65535..3f807e6 100644 --- a/bootstrap/hir_lower.nim +++ b/bootstrap/hir_lower.nim @@ -352,22 +352,63 @@ 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: - 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: return makeNamed(f.ftype.typeName) - of tekOwn, tekPointer: - return ctx.resolveTypeExpr(f.ftype) - else: return makeUnknown() + 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 + 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: return makeNamed(f.ftype.typeName) + 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 diff --git a/bootstrap/lir_c_backend.nim b/bootstrap/lir_c_backend.nim index 7acbbfe..b4b6324 100644 --- a/bootstrap/lir_c_backend.nim +++ b/bootstrap/lir_c_backend.nim @@ -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) - 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" + + # ── 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 + 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 ") @@ -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 diff --git a/bootstrap/lir_lower.nim b/bootstrap/lir_lower.nim index 961dd90..056e6a9 100644 --- a/bootstrap/lir_lower.nim +++ b/bootstrap/lir_lower.nim @@ -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,28 +499,44 @@ 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 - b.emitRawC("break;") + if ctx.loopEndLabels.len > 0: + b.emitJmp(lirLabel(ctx.loopEndLabels[^1])) + else: + b.emitRawC("break;") # ── Continue ── of hContinue: - b.emitRawC("continue;") + if ctx.loopStartLabels.len > 0: + b.emitJmp(lirLabel(ctx.loopStartLabels[^1])) + else: + b.emitRawC("continue;") # ── Alloca ── of hAlloca: