diff --git a/Makefile b/Makefile index 0823d3e..280dc1d 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ all: build build: $(NIM) c -o:$(OUT) -d:release --opt:size $(SRC) - strip $(OUT) + # strip $(OUT) dev: $(NIM) c -o:buxc_debug -d:debug --stackTrace:on --lineTrace:on $(SRC) @@ -72,5 +72,5 @@ selfhost: build @cp src/bux.toml build/selfhost/ @mv build/selfhost/src/main.bux build/selfhost/src/Main.bux 2>/dev/null || true @cd build/selfhost && ../../$(OUT) build - @strip build/selfhost/build/buxc2 2>/dev/null || true + # strip removed for debug @echo "=== Self-hosted compiler built successfully ===" diff --git a/bootstrap/cli.nim b/bootstrap/cli.nim index b893013..226f6c9 100644 --- a/bootstrap/cli.nim +++ b/bootstrap/cli.nim @@ -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) diff --git a/bootstrap/hir.nim b/bootstrap/hir.nim index 8b1afa5..20e2da9 100644 --- a/bootstrap/hir.nim +++ b/bootstrap/hir.nim @@ -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) diff --git a/bootstrap/hir_lower.nim b/bootstrap/hir_lower.nim index 3f807e6..7f06885 100644 --- a/bootstrap/hir_lower.nim +++ b/bootstrap/hir_lower.nim @@ -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) diff --git a/bootstrap/lir_lower.nim b/bootstrap/lir_lower.nim index 056e6a9..3428e99 100644 --- a/bootstrap/lir_lower.nim +++ b/bootstrap/lir_lower.nim @@ -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) ── diff --git a/src/sema.bux b/src/sema.bux index df37759..d1e9775 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -239,9 +239,13 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { // Try to resolve return type from function declaration if expr.child1.kind == ekIdent { let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue); - if sym.kind == skFunc && sym.decl != null as *Decl && sym.decl.retType != null as *TypeExpr { + if sym.kind == skFunc { + if sym.decl != null as *Decl { + if sym.decl.retType != null as *TypeExpr { return Sema_ResolveType(sema, sym.decl.retType); } + } + } } return tyUnknown; }