fix(bootstrap): resolve lir lower crashes and struct-copy bugs
- Fix lirValToC crash on lvkInt in hStore raw C emission - Add hAlloca handling to lowerExpr (returns &name, avoids unhandled fallback) - Fix nested comment syntax in unhandled expr fallback - Rewrite buildLval to handle hIndexPtr and nested hFieldPtr/hArrowField so assignments like arr[i].field = val emit direct C lvalues - Make &&/|| temporaries unique (__and_tmp_N/__or_tmp_N) to avoid duplicate declarations in the same function - Selfhost null-deref fix in sema.bux (unchained nested conditions)
This commit is contained in:
+1
-1
@@ -488,7 +488,7 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||
# Compile with cc
|
||||
let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out"
|
||||
let outputFile = buildDir / outputName
|
||||
let ccCmd = &"cc -O2 -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
|
||||
let ccCmd = &"cc -O0 -g -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
|
||||
if opts.verbose:
|
||||
printInfo(&"running: {ccCmd}", useColor)
|
||||
let (output, exitCode) = execCmdEx(ccCmd)
|
||||
|
||||
@@ -203,6 +203,10 @@ proc hirReturn*(value: HirNode, loc: SourceLocation): HirNode =
|
||||
proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation, isScope: bool = false): HirNode =
|
||||
HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc, isScope: isScope)
|
||||
|
||||
proc hirIf*(cond, thenBranch, elseBranch: HirNode, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hIf, ifCond: cond, ifThen: thenBranch, ifElse: elseBranch, typ: makeVoid(), loc: loc)
|
||||
|
||||
|
||||
proc hirAlloca*(name: string, typ: Type, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hAlloca, allocaType: typ, allocaName: name, typ: makePointer(typ), loc: loc)
|
||||
|
||||
|
||||
+25
-3
@@ -539,9 +539,31 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
return hirUnary(expr.exprUnaryOp, operand, typ, loc)
|
||||
|
||||
of ekBinary:
|
||||
let left = ctx.lowerExpr(expr.exprBinaryLeft)
|
||||
let right = ctx.lowerExpr(expr.exprBinaryRight)
|
||||
return hirBinary(expr.exprBinaryOp, left, right, typ, loc)
|
||||
case expr.exprBinaryOp
|
||||
of tkAmpAmp:
|
||||
# Short-circuit &&: use if-then-else to avoid evaluating right when left is false
|
||||
let tmp = hirAlloca("__and_tmp_" & $ctx.varCounter, makeBool(), loc)
|
||||
inc ctx.varCounter
|
||||
let left = ctx.lowerExpr(expr.exprBinaryLeft)
|
||||
let thenBlock = hirBlock(@[hirStore(tmp, ctx.lowerExpr(expr.exprBinaryRight), loc)], nil, makeVoid(), loc)
|
||||
let falseTok = Token(kind: tkBoolLiteral, text: "false", loc: loc)
|
||||
let elseBlock = hirBlock(@[hirStore(tmp, hirLit(falseTok, makeBool(), loc), loc)], nil, makeVoid(), loc)
|
||||
let ifNode = hirIf(left, thenBlock, elseBlock, loc)
|
||||
return hirBlock(@[tmp, ifNode], hirLoad(tmp, makeBool(), loc), makeBool(), loc)
|
||||
of tkPipePipe:
|
||||
# Short-circuit ||: use if-then-else to avoid evaluating right when left is true
|
||||
let tmp = hirAlloca("__or_tmp_" & $ctx.varCounter, makeBool(), loc)
|
||||
inc ctx.varCounter
|
||||
let left = ctx.lowerExpr(expr.exprBinaryLeft)
|
||||
let trueTok = Token(kind: tkBoolLiteral, text: "true", loc: loc)
|
||||
let thenBlock = hirBlock(@[hirStore(tmp, hirLit(trueTok, makeBool(), loc), loc)], nil, makeVoid(), loc)
|
||||
let elseBlock = hirBlock(@[hirStore(tmp, ctx.lowerExpr(expr.exprBinaryRight), loc)], nil, makeVoid(), loc)
|
||||
let ifNode = hirIf(left, thenBlock, elseBlock, loc)
|
||||
return hirBlock(@[tmp, ifNode], hirLoad(tmp, makeBool(), loc), makeBool(), loc)
|
||||
else:
|
||||
let left = ctx.lowerExpr(expr.exprBinaryLeft)
|
||||
let right = ctx.lowerExpr(expr.exprBinaryRight)
|
||||
return hirBinary(expr.exprBinaryOp, left, right, typ, loc)
|
||||
|
||||
of ekCall:
|
||||
# Method call desugaring: obj.method(args) → Type_method(obj, args)
|
||||
|
||||
+157
-25
@@ -5,6 +5,20 @@
|
||||
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
|
||||
@@ -172,6 +186,16 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
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")
|
||||
@@ -198,7 +222,37 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
b.emitLoad(t, operand)
|
||||
return t
|
||||
of tkAmp:
|
||||
# Address of: &var
|
||||
# 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
|
||||
@@ -215,18 +269,30 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
b.emitCmp(cmpOpToLir(node.binaryOp), t, left, right)
|
||||
return t
|
||||
of tkAmpAmp, tkPipePipe:
|
||||
# Logical and/or: lowered to select
|
||||
# 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 → left ? (right != 0) : 0
|
||||
let rhsBool = b.freshTemp()
|
||||
b.emitCmp(lirCmpNe, rhsBool, right, lirInt(0))
|
||||
b.emitSelect(t, left, rhsBool, lirInt(0))
|
||||
# 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 → left ? 1 : (right != 0)
|
||||
let rhsBool = b.freshTemp()
|
||||
b.emitCmp(lirCmpNe, rhsBool, right, lirInt(0))
|
||||
b.emitSelect(t, left, lirInt(1), rhsBool)
|
||||
# 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()
|
||||
@@ -269,16 +335,16 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, "void*")
|
||||
if isPtr:
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}->{node.fieldName});")
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}->{node.fieldName});")
|
||||
else:
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}.{node.fieldName});")
|
||||
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*)&({base.strVal}->{node.arrowFieldName});")
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}->{node.arrowFieldName});")
|
||||
return t
|
||||
|
||||
of hIndexPtr:
|
||||
@@ -286,7 +352,7 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
let idx = lowerExpr(ctx, node.indexPtrIndex)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, "void*")
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({base.strVal}[{idx.strVal}]);")
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}[{lirValToC(idx)}]);")
|
||||
return t
|
||||
|
||||
# ── Load ──
|
||||
@@ -298,7 +364,7 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
let cType = hirTypeToC(ctx, node)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
b.emitRawC(&"{t.strVal} = {base.strVal}->{node.loadPtr.arrowFieldName};")
|
||||
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)
|
||||
@@ -308,9 +374,9 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
if isPtr:
|
||||
b.emitRawC(&"{t.strVal} = {base.strVal}->{node.loadPtr.fieldName};")
|
||||
b.emitRawC(&"{t.strVal} = {lirValToC(base)}->{node.loadPtr.fieldName};")
|
||||
else:
|
||||
b.emitRawC(&"{t.strVal} = {base.strVal}.{node.loadPtr.fieldName};")
|
||||
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)
|
||||
@@ -318,14 +384,14 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
let cType = hirTypeToC(ctx, node)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
b.emitRawC(&"{t.strVal} = {base.strVal}[{idx.strVal}];")
|
||||
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}*){ptrVal.strVal};")
|
||||
b.emitRawC(&"{t.strVal} = *({cType}*){lirValToC(ptrVal)};")
|
||||
return t
|
||||
|
||||
# ── Slice Index ──
|
||||
@@ -335,7 +401,7 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
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.emitRawC(&"bux_bounds_check((size_t)({lirValToC(idx)}), ({lirValToC(base)}).len)")
|
||||
b.emit(LirInstr(kind: lirLoad, dst: t, src: base, src2: idx))
|
||||
return t
|
||||
|
||||
@@ -389,7 +455,7 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
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(\", \")});")
|
||||
b.emitRawC(&"{t.strVal} = {lirValToC(receiver)}.vtable->{node.dynCallMethod}({args.mapIt($it).join(\", \")});")
|
||||
return t
|
||||
|
||||
# ── StructInit ──
|
||||
@@ -453,9 +519,39 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
|
||||
else:
|
||||
# Fallback for unhandled expression kinds
|
||||
b.emitComment(&"/* unhandled expr kind: {node.kind} */")
|
||||
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) =
|
||||
@@ -557,24 +653,60 @@ proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
|
||||
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};")
|
||||
b.emitRawC(&"*({valCType}*){lirValToC(ptrVal)} = {lirValToC(val)};")
|
||||
|
||||
# ── Assign ──
|
||||
of hAssign:
|
||||
let target = lowerExpr(ctx, node.assignTarget)
|
||||
let value = lowerExpr(ctx, node.assignValue)
|
||||
case node.assignOp
|
||||
of tkAssign:
|
||||
b.emitMov(target, value)
|
||||
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) ──
|
||||
|
||||
Reference in New Issue
Block a user