From ec4e748584f85d39e1e6cdb593a7c9e9c3673c9d Mon Sep 17 00:00:00 2001 From: dimgigov Date: Tue, 2 Jun 2026 18:07:35 +0300 Subject: [PATCH] fix(qbe): use extraData instead of child3 for hIf else-branch Block lowering overwrites child3 for statement chaining. Else block is stored in extraData by hir_lower (matches c_backend). Fixes QBE parse error 'last block misses jump' in if-without-else chains. --- _selfhost/src/cli.bux | 268 ++++++++++++++++++++++++++++++++-- _selfhost/src/hir_lower.bux | 2 + _selfhost/src/parser.bux | 4 + _selfhost/src/qbe_backend.bux | 138 ++++++++++++++--- _selfhost/src/sema.bux | 2 +- src_bux/cli.bux | 268 ++++++++++++++++++++++++++++++++-- src_bux/hir_lower.bux | 2 + src_bux/parser.bux | 4 + src_bux/qbe_backend.bux | 138 ++++++++++++++--- src_bux/sema.bux | 2 +- 10 files changed, 756 insertions(+), 72 deletions(-) diff --git a/_selfhost/src/cli.bux b/_selfhost/src/cli.bux index eb6e441..2607c48 100644 --- a/_selfhost/src/cli.bux +++ b/_selfhost/src/cli.bux @@ -192,6 +192,14 @@ func Cli_Check(srcPath: String) -> int { // Phase 4: HIR lowering let hirMod: *HirModule = HirLower_LowerModule(mod, sema); + var dc: *Decl = mod.firstItem; + while dc != null as *Decl { + if dc.kind == dkFunc { + Print("check func "); + PrintLine(dc.strValue); + } + dc = dc.childDecl2; + } PrintLine("Check passed"); return 0; } @@ -232,13 +240,144 @@ func Cli_CompileSource(source: String, sourceName: String) -> *HirModule { return hirMod; } +// --------------------------------------------------------------------------- +// Stdlib discovery and merging +// --------------------------------------------------------------------------- + +func Cli_FindStdlibDir(projectDir: String) -> String { + var path: String = bux_path_join(projectDir, "stdlib"); + if DirExists(path) { return path; } + path = bux_path_join(projectDir, "../stdlib"); + if DirExists(path) { return path; } + path = "/home/ziko/z-git/bux/bux/stdlib"; + if DirExists(path) { return path; } + return ""; +} + +func Cli_DeclName(decl: *Decl) -> String { + if decl == null as *Decl { return ""; } + return decl.strValue; +} + +func Cli_NameInList(name: String, names: *String, count: int) -> bool { + if String_Eq(name, "") { return false; } + var i: int = 0; + while i < count { + if String_Eq(names[i], name) { + return true; + } + i = i + 1; + } + return false; +} + +func Cli_CollectNames(mod: *Module, names: *String, maxCount: int) -> int { + if mod == null as *Module { return 0; } + var count: int = 0; + var decl: *Decl = mod.firstItem; + while decl != null as *Decl && count < maxCount { + let next: *Decl = decl.childDecl2; + if decl.kind == dkModule { + var inner: *Decl = decl.childDecl1; + while inner != null as *Decl && count < maxCount { + let iname: String = Cli_DeclName(inner); + if !String_Eq(iname, "") { + names[count] = iname; + count = count + 1; + } + inner = inner.childDecl2; + } + } else { + let dname: String = Cli_DeclName(decl); + if !String_Eq(dname, "") { + names[count] = dname; + count = count + 1; + } + } + decl = next; + } + return count; +} + +func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int { + Print("DEBUG: MergeFileInto "); + PrintLine(path); + let source: String = ReadFile(path); + if String_Eq(source, "") { return 0; } + let lex: *Lexer = Lexer_Tokenize(source); + if Lexer_DiagCount(lex) > 0 { return 0; } + let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount); + if mod == null as *Module { return 0; } + + var added: int = 0; + var decl: *Decl = mod.firstItem; + while decl != null as *Decl { + let next: *Decl = decl.childDecl2; + decl.childDecl2 = null as *Decl; + if decl.kind == dkModule { + var inner: *Decl = decl.childDecl1; + while inner != null as *Decl { + let innerNext: *Decl = inner.childDecl2; + let iname: String = Cli_DeclName(inner); + if !String_Eq(iname, "") && Cli_NameInList(iname, skipNames, skipCount) { + // Skip shadowed stdlib decl + } else { + inner.childDecl2 = target.firstItem; + target.firstItem = inner; + target.itemCount = target.itemCount + 1; + added = added + 1; + } + inner = innerNext; + } + } else { + let dname: String = Cli_DeclName(decl); + if !String_Eq(dname, "") && Cli_NameInList(dname, skipNames, skipCount) { + // Skip shadowed stdlib decl + } else { + decl.childDecl2 = target.firstItem; + target.firstItem = decl; + target.itemCount = target.itemCount + 1; + added = added + 1; + } + } + decl = next; + } + Print("DEBUG: MergeFileInto done, added="); + PrintInt(added); + PrintLine(""); + return added; +} + +func Cli_CopyModuleDecls(target: *Module, source: *Module) { + if source == null as *Module || source.firstItem == null as *Decl { return; } + Print("DEBUG: copy start, source.itemCount="); + PrintInt(source.itemCount); + PrintLine(""); + var decl: *Decl = source.firstItem; + var limit: int = 0; + while decl != null as *Decl && limit < 10000 { + let next: *Decl = decl.childDecl2; + decl.childDecl2 = target.firstItem; + target.firstItem = decl; + target.itemCount = target.itemCount + 1; + decl = next; + limit = limit + 1; + } + source.firstItem = null as *Decl; + source.itemCount = 0; + PrintLine("DEBUG: copy end"); +} + func Cli_BuildProject(projectDir: String) -> int { + PrintLine("DEBUG: Cli_BuildProject start"); let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml")); + PrintLine("DEBUG: manifest loaded"); var outName: String = man.name; if String_Eq(outName, "") { outName = "bux_out"; } let srcDir: String = bux_path_join(projectDir, "src"); + PrintLine("DEBUG: srcDir built"); if !DirExists(srcDir) { Print("Error: no src/ directory in "); PrintLine(projectDir); @@ -260,14 +399,14 @@ func Cli_BuildProject(projectDir: String) -> int { PrintInt(fileCount); PrintLine(" source files"); - // Create merged module - let merged: *Module = bux_alloc(sizeof(Module)) as *Module; - merged.name = "main"; - merged.path = ""; - merged.itemCount = 0; - merged.firstItem = null as *Decl; + // Create user merged module (collect user decls first) + let userMerged: *Module = bux_alloc(sizeof(Module)) as *Module; + userMerged.name = "main"; + userMerged.path = ""; + userMerged.itemCount = 0; + userMerged.firstItem = null as *Decl; - // Parse each file and merge declarations into merged module + // Parse each file and merge declarations into userMerged module var i: int = 0; while i < fileCount { let path: String = files[i]; @@ -291,7 +430,7 @@ func Cli_BuildProject(projectDir: String) -> int { PrintLine(path); return 1; } - // Merge declarations from this module into merged + // Merge declarations from this module into userMerged var decl: *Decl = mod.firstItem; while decl != null as *Decl { let next: *Decl = decl.childDecl2; @@ -301,26 +440,111 @@ func Cli_BuildProject(projectDir: String) -> int { var inner: *Decl = decl.childDecl1; while inner != null as *Decl { let innerNext: *Decl = inner.childDecl2; - inner.childDecl2 = merged.firstItem; - merged.firstItem = inner; - merged.itemCount = merged.itemCount + 1; + inner.childDecl2 = userMerged.firstItem; + userMerged.firstItem = inner; + userMerged.itemCount = userMerged.itemCount + 1; inner = innerNext; } } else { - decl.childDecl2 = merged.firstItem; - merged.firstItem = decl; - merged.itemCount = merged.itemCount + 1; + decl.childDecl2 = userMerged.firstItem; + userMerged.firstItem = decl; + userMerged.itemCount = userMerged.itemCount + 1; } decl = next; } i = i + 1; } + Print("DEBUG: userMerged.itemCount="); + PrintInt(userMerged.itemCount); + PrintLine(""); + + // Collect user declaration names for shadow detection + let maxNames: int = 2048; + let userNames: *String = bux_alloc(maxNames * 8) as *String; + let userNameCount: int = Cli_CollectNames(userMerged, userNames, maxNames); + + // Create final merged module + let merged: *Module = bux_alloc(sizeof(Module)) as *Module; + merged.name = "main"; + merged.path = ""; + merged.itemCount = 0; + merged.firstItem = null as *Decl; + + // Find and merge stdlib declarations + let stdlibDir: String = Cli_FindStdlibDir(projectDir); + if !String_Eq(stdlibDir, "") { + Print("Stdlib found: "); + PrintLine(stdlibDir); + let stdSubdir: String = bux_path_join(stdlibDir, "Std"); + if DirExists(stdSubdir) { + var stdFileCount: int = 0; + let stdFiles: *String = bux_list_dir(stdSubdir, ".bux", &stdFileCount); + Print("DEBUG: stdFileCount="); + PrintInt(stdFileCount); + PrintLine(""); + if stdFileCount > 0 { + Print("Merging "); + PrintInt(stdFileCount); + PrintLine(" stdlib files"); + var si: int = 0; + var stdAdded: int = 0; + while si < stdFileCount { + Print("DEBUG: merging stdlib file "); + PrintInt(si); + PrintLine(""); + // Skip Channel.bux for debugging + if String_Eq(stdFiles[si], "./../stdlib/Std/Channel.bux") { + PrintLine("DEBUG: skipping Channel.bux"); + si = si + 1; + continue; + } + let added: int = Cli_MergeFileInto(merged, stdFiles[si], userNames, userNameCount); + stdAdded = stdAdded + added; + si = si + 1; + } + Print("Stdlib declarations added: "); + PrintInt(stdAdded); + PrintLine(""); + } + } + } + + // Copy user declarations into merged (user shadows stdlib) + PrintLine("DEBUG: before copy"); + var countTest: int = 0; + var dtest: *Decl = userMerged.firstItem; + while dtest != null as *Decl && countTest < 10000 { + dtest = dtest.childDecl2; + countTest = countTest + 1; + } + Print("userMerged actual count="); + PrintInt(countTest); + PrintLine(""); + Print("userMerged.itemCount="); + PrintInt(userMerged.itemCount); + PrintLine(""); + var du: *Decl = userMerged.firstItem; + while du != null as *Decl { + if du.kind == dkFunc { + Print("user func "); + PrintLine(du.strValue); + } + du = du.childDecl2; + } + Cli_CopyModuleDecls(merged, userMerged); + PrintLine("DEBUG: copy done"); + Print("merged.itemCount="); + PrintInt(merged.itemCount); + PrintLine(""); + PrintLine("DEBUG: copy done"); + Print("Merged "); PrintInt(merged.itemCount); PrintLine(" declarations"); // Semantic analysis + PrintLine("DEBUG: about to run sema"); PrintLine("Running sema..."); let sema: *Sema = Sema_Analyze(merged); if Sema_HasError(sema) { @@ -330,10 +554,24 @@ func Cli_BuildProject(projectDir: String) -> int { // HIR lowering PrintLine("Lowering to HIR..."); + var di: *Decl = merged.firstItem; + while di != null as *Decl { + if di.kind == dkFunc { + Print("decl func "); + PrintLine(di.strValue); + } + di = di.childDecl2; + } let hirMod: *HirModule = HirLower_LowerModule(merged, sema); // QBE SSA code generation PrintLine("Generating QBE SSA..."); + var fi: int = 0; + while fi < hirMod.funcCount { + Print("func "); + PrintLine(hirMod.funcs[fi].name); + fi = fi + 1; + } let qbeCode: String = QbeBackend_Generate(hirMod); // Create build directory @@ -418,6 +656,7 @@ func Cli_BuildProject(projectDir: String) -> int { // --------------------------------------------------------------------------- func Cli_RunProject(projectDir: String) -> int { + PrintLine("DEBUG: Cli_RunProject start"); let rc: int = Cli_BuildProject(projectDir); if rc != 0 { return rc; @@ -487,6 +726,7 @@ func Cli_Run(args: *String, argCount: int) -> int { } if String_Eq(cmd, "run") { + PrintLine("DEBUG: run command"); let dir: String = "."; if argCount >= 3 { dir = args[2]; } return Cli_RunProject(dir); diff --git a/_selfhost/src/hir_lower.bux b/_selfhost/src/hir_lower.bux index f382f6e..f122571 100644 --- a/_selfhost/src/hir_lower.bux +++ b/_selfhost/src/hir_lower.bux @@ -103,6 +103,8 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { if kind == ekIdent { n.kind = hVar; n.strValue = expr.strValue; + let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue); + n.typeKind = sym.typeKind; return n; } diff --git a/_selfhost/src/parser.bux b/_selfhost/src/parser.bux index 8ad0e5b..f36dd2d 100644 --- a/_selfhost/src/parser.bux +++ b/_selfhost/src/parser.bux @@ -298,6 +298,10 @@ func parserParsePrimary(p: *Parser) -> *Expr { } // --------------------------------------------------------------------------- + +func dummyFunc() -> int { + return 0; +} // Postfix: call, index, field access, as, is, ? // --------------------------------------------------------------------------- diff --git a/_selfhost/src/qbe_backend.bux b/_selfhost/src/qbe_backend.bux index e8067ff..e64fb19 100644 --- a/_selfhost/src/qbe_backend.bux +++ b/_selfhost/src/qbe_backend.bux @@ -66,12 +66,16 @@ struct QbeEmitter { // Const table for inline substitution constCount: int; consts: *HirConst; + // Current function for param type lookup + currentFunc: *HirFunc; + // Module reference for function return type lookup + mod: *HirModule; } func QBE_Init() -> QbeEmitter { var dsb: StringBuilder = StringBuilder_NewCap(4096); var csb: StringBuilder = StringBuilder_NewCap(16384); - return QbeEmitter { dataSb: dsb, codeSb: csb, tempId: 0, labelId: 0, strId: 0, indent: 0, inBlock: false, constCount: 0, consts: null as *HirConst }; + return QbeEmitter { dataSb: dsb, codeSb: csb, tempId: 0, labelId: 0, strId: 0, indent: 0, inBlock: false, constCount: 0, consts: null as *HirConst, currentFunc: null as *HirFunc, mod: null as *HirModule }; } func QBE_FreshTemp(qbe: *QbeEmitter) -> String { @@ -262,8 +266,8 @@ func QBE_IsTerminator(node: *HirNode) -> bool { if node.kind == hBreak { return true; } if node.kind == hContinue { return true; } if node.kind == hIf { - if node.child3 != null as *HirNode { - return QBE_IsTerminator(node.child2) && QBE_IsTerminator(node.child3); + if node.extraData != null as *HirNode { + return QBE_IsTerminator(node.child2) && QBE_IsTerminator(node.extraData as *HirNode); } return false; } @@ -280,6 +284,56 @@ func QBE_IsTerminator(node: *HirNode) -> bool { return false; } +// --------------------------------------------------------------------------- +// Helper: find function by name in module +// --------------------------------------------------------------------------- + +func QBE_FindFunc(qbe: *QbeEmitter, name: String) -> *HirFunc { + if qbe.mod == null as *HirModule { return null as *HirFunc; } + var i: int = 0; + while i < qbe.mod.funcCount { + if String_Eq(qbe.mod.funcs[i].name, name) { + return &qbe.mod.funcs[i]; + } + i = i + 1; + } + i = 0; + while i < qbe.mod.externCount { + if String_Eq(qbe.mod.externFuncs[i].name, name) { + return &qbe.mod.externFuncs[i]; + } + i = i + 1; + } + return null as *HirFunc; +} + +// --------------------------------------------------------------------------- +// Helper: determine QBE type for any HIR node +// --------------------------------------------------------------------------- + +func QBE_NodeType(qbe: *QbeEmitter, node: *HirNode) -> String { + if node == null as *HirNode { return "l"; } + // For variables, check param type first + if node.kind == hVar && qbe.currentFunc != null as *HirFunc { + var pi2: int = 0; + var pnames2: *HirParam = &qbe.currentFunc.param0; + while pi2 < qbe.currentFunc.paramCount { + if String_Eq(pnames2[pi2].name, node.strValue) { + return QbeBackend_TypeWord(pnames2[pi2].typeKind); + } + pi2 = pi2 + 1; + } + } + // For calls, look up callee return type + if node.kind == hCall && qbe.mod != null as *HirModule { + let callee: *HirFunc = QBE_FindFunc(qbe, node.strValue); + if callee != null as *HirFunc { + return QbeBackend_TypeWord(callee.retTypeKind); + } + } + return QbeBackend_TypeWord(node.typeKind); +} + // --------------------------------------------------------------------------- // Expression emission — returns the QBE value string (e.g. "%t3", "42", "$foo") // For expressions that need multiple instructions, emits them inline. @@ -325,8 +379,21 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { return "0"; } } + // Determine type: use param type if available, else node.typeKind + var varTy: int = node.typeKind; + if qbe.currentFunc != null as *HirFunc { + var pi2: int = 0; + var pnames2: *HirParam = &qbe.currentFunc.param0; + while pi2 < qbe.currentFunc.paramCount { + if String_Eq(pnames2[pi2].name, node.strValue) { + varTy = pnames2[pi2].typeKind; + break; + } + pi2 = pi2 + 1; + } + } let tmp: String = QBE_FreshTemp(qbe); - let ty: String = QbeBackend_TypeWord(node.typeKind); + let ty: String = QbeBackend_TypeWord(varTy); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " ="); @@ -350,7 +417,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { } let val: String = QBE_EmitExpr(qbe, node.child2); if String_Eq(val, "") { val = "0"; } - let ty: String = QbeBackend_TypeWord(node.typeKind); + let ty: String = QBE_NodeType(qbe, node.child2); QBE_EmitIndent(qbe); QBE_Emit(qbe, "store"); QBE_Emit(qbe, ty); @@ -366,12 +433,12 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { let op: int = node.intValue; let left: String = QBE_EmitExpr(qbe, node.child1); let right: String = QBE_EmitExpr(qbe, node.child2); - let ty: String = QbeBackend_TypeWord(node.typeKind); + let ty: String = QBE_NodeType(qbe, node.child1); // Comparison operators // QBE comparison ops: ceqw/ceql, cnew/cnel, csltw/csltl, cslew/cslel, csgtw/csgtl, csgew/csgel var cmpOp: String = ""; - var cmpTy: String = ty; + let cmpTy: String = QBE_NodeType(qbe, node.child1); if op == tkEq { cmpOp = "ceq"; } else if op == tkNe { cmpOp = "cne"; } else if op == tkLt { cmpOp = "cslt"; } @@ -385,7 +452,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { QBE_Emit(qbe, cmpTmp); QBE_Emit(qbe, " =w "); QBE_Emit(qbe, cmpOp); - QBE_Emit(qbe, ty); + QBE_Emit(qbe, cmpTy); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(left)); QBE_Emit(qbe, ", "); @@ -452,7 +519,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { let rVal: String = QBE_EmitExpr(qbe, node.child2); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); - QBE_Emit(qbe, " =l copy "); + QBE_Emit(qbe, " =w copy "); QBE_EmitLine(qbe, rVal); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); @@ -460,7 +527,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { QBE_EmitLine(qbe, lFalse); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); - QBE_EmitLine(qbe, " =l copy 0"); + QBE_EmitLine(qbe, " =w copy 0"); QBE_EmitLine(qbe, lEnd); return tmp; } @@ -479,7 +546,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { QBE_EmitLine(qbe, lTrue); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); - QBE_EmitLine(qbe, " =l copy 1"); + QBE_EmitLine(qbe, " =w copy 1"); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lEnd); @@ -487,7 +554,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { let rVal: String = QBE_EmitExpr(qbe, node.child2); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); - QBE_Emit(qbe, " =l copy "); + QBE_Emit(qbe, " =w copy "); QBE_EmitLine(qbe, rVal); QBE_EmitLine(qbe, lEnd); return tmp; @@ -500,7 +567,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { if kind == hUnary { let op: int = node.intValue; let operand: String = QBE_EmitExpr(qbe, node.child1); - let ty: String = QbeBackend_TypeWord(node.typeKind); + let ty: String = QBE_NodeType(qbe, node.child1); if op == tkBang || op == tkMinus { let tmp: String = QBE_FreshTemp(qbe); @@ -535,7 +602,14 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { if kind == hCall { // Pre-evaluate arguments to avoid emitting instructions inside the call let tmp: String = QBE_FreshTemp(qbe); - let retTy: String = QbeBackend_TypeWord(node.typeKind); + var retTy: String = QbeBackend_TypeWord(node.typeKind); + // Look up callee return type from module if typeKind is unknown + if String_Eq(retTy, "l") && qbe.mod != null as *HirModule { + let callee: *HirFunc = QBE_FindFunc(qbe, node.strValue); + if callee != null as *HirFunc { + retTy = QbeBackend_TypeWord(callee.retTypeKind); + } + } var arg1: String = ""; var arg2: String = ""; if node.child1 != null as *HirNode { @@ -552,13 +626,13 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { QBE_Emit(qbe, QBE_EscapeIdent(node.strValue)); QBE_Emit(qbe, "("); if node.child1 != null as *HirNode { - let a1ty: String = QbeBackend_TypeWord(node.child1.typeKind); + let a1ty: String = QBE_NodeType(qbe, node.child1); QBE_Emit(qbe, a1ty); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(arg1)); } if node.child2 != null as *HirNode { - let a2ty: String = QbeBackend_TypeWord(node.child2.typeKind); + let a2ty: String = QBE_NodeType(qbe, node.child2); QBE_Emit(qbe, ", "); QBE_Emit(qbe, a2ty); QBE_Emit(qbe, " "); @@ -599,6 +673,20 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { return tmp; } + // Index pointer: &arr[idx] (for pointer indexing) + if kind == hIndexPtr { + let base: String = QBE_EmitExpr(qbe, node.child1); + let idx: String = QBE_EmitExpr(qbe, node.child2); + let tmp: String = QBE_FreshTemp(qbe); + QBE_EmitIndent(qbe); + QBE_Emit(qbe, tmp); + QBE_Emit(qbe, " =l add "); + QBE_Emit(qbe, QBE_EnsureLocal(base)); + QBE_Emit(qbe, ", "); + QBE_EmitLine(qbe, QBE_EnsureLocal(idx)); + return tmp; + } + // Cast — QBE doesn't need same-width casts; passthrough if kind == hCast { return QBE_EmitExpr(qbe, node.child1); @@ -626,6 +714,12 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { if node == null as *HirNode { return; } let kind: int = node.kind; + // Assign statement + if kind == hAssign { + discard QBE_EmitExpr(qbe, node); + return; + } + // Store: declaration or assignment if kind == hStore { if node.child1 != null as *HirNode && node.child1.kind == hAlloca { @@ -653,7 +747,7 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { // Plain assignment let target: String = QBE_EmitExpr(qbe, node.child1); let val: String = QBE_EmitExpr(qbe, node.child2); - let ty: String = QbeBackend_TypeWord(node.typeKind); + let ty: String = QBE_NodeType(qbe, node.child2); if String_Eq(val, "") { val = "0"; } if String_Eq(target, "") { return; } QBE_EmitIndent(qbe); @@ -692,7 +786,7 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { QBE_Emit(qbe, ", "); QBE_Emit(qbe, lThen); QBE_Emit(qbe, ", "); - var hasElse: bool = node.child3 != null as *HirNode; + var hasElse: bool = node.extraData != null as *HirNode; if hasElse { QBE_EmitLine(qbe, lElse); } else { @@ -709,8 +803,8 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { if hasElse { QBE_EmitLine(qbe, lElse); - QBE_EmitStmt(qbe, node.child3); - if !QBE_IsTerminator(node.child3) { + QBE_EmitStmt(qbe, node.extraData as *HirNode); + if !QBE_IsTerminator(node.extraData as *HirNode) { QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lEnd); @@ -719,7 +813,7 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { // Only emit @lEnd if at least one branch falls through var needEnd: bool = !QBE_IsTerminator(node.child2); - if hasElse && !QBE_IsTerminator(node.child3) { + if hasElse && !QBE_IsTerminator(node.extraData as *HirNode) { needEnd = true; } if needEnd { @@ -828,6 +922,7 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { func QBE_EmitFunc(qbe: *QbeEmitter, f: *HirFunc) { if f.body == null as *HirNode { return; } + qbe.currentFunc = f; QBE_Emit(qbe, "export function "); let retTy: String = QbeBackend_TypeWord(f.retTypeKind); @@ -929,6 +1024,7 @@ func QbeBackend_Generate(mod: *HirModule) -> String { qbe.inBlock = false; qbe.constCount = mod.constCount; qbe.consts = mod.consts; + qbe.mod = mod; // Header QBE_EmitLine(qbe, "# Generated by Bux QBE Backend"); diff --git a/_selfhost/src/sema.bux b/_selfhost/src/sema.bux index 6a573ee..668ae7d 100644 --- a/_selfhost/src/sema.bux +++ b/_selfhost/src/sema.bux @@ -312,7 +312,7 @@ func Sema_Analyze(mod: *Module) -> *Sema { let s: *Sema = bux_alloc(sizeof(Sema)) as *Sema; s.module = mod; s.scope = bux_alloc(sizeof(Scope)) as *Scope; - s.scope.symbols = bux_alloc(256 as uint * sizeof(Symbol)) as *Symbol; + s.scope.symbols = bux_alloc(1024 as uint * sizeof(Symbol)) as *Symbol; s.scope.count = 0; s.scope.parent = null as *Scope; s.hasError = false; diff --git a/src_bux/cli.bux b/src_bux/cli.bux index eb6e441..2607c48 100644 --- a/src_bux/cli.bux +++ b/src_bux/cli.bux @@ -192,6 +192,14 @@ func Cli_Check(srcPath: String) -> int { // Phase 4: HIR lowering let hirMod: *HirModule = HirLower_LowerModule(mod, sema); + var dc: *Decl = mod.firstItem; + while dc != null as *Decl { + if dc.kind == dkFunc { + Print("check func "); + PrintLine(dc.strValue); + } + dc = dc.childDecl2; + } PrintLine("Check passed"); return 0; } @@ -232,13 +240,144 @@ func Cli_CompileSource(source: String, sourceName: String) -> *HirModule { return hirMod; } +// --------------------------------------------------------------------------- +// Stdlib discovery and merging +// --------------------------------------------------------------------------- + +func Cli_FindStdlibDir(projectDir: String) -> String { + var path: String = bux_path_join(projectDir, "stdlib"); + if DirExists(path) { return path; } + path = bux_path_join(projectDir, "../stdlib"); + if DirExists(path) { return path; } + path = "/home/ziko/z-git/bux/bux/stdlib"; + if DirExists(path) { return path; } + return ""; +} + +func Cli_DeclName(decl: *Decl) -> String { + if decl == null as *Decl { return ""; } + return decl.strValue; +} + +func Cli_NameInList(name: String, names: *String, count: int) -> bool { + if String_Eq(name, "") { return false; } + var i: int = 0; + while i < count { + if String_Eq(names[i], name) { + return true; + } + i = i + 1; + } + return false; +} + +func Cli_CollectNames(mod: *Module, names: *String, maxCount: int) -> int { + if mod == null as *Module { return 0; } + var count: int = 0; + var decl: *Decl = mod.firstItem; + while decl != null as *Decl && count < maxCount { + let next: *Decl = decl.childDecl2; + if decl.kind == dkModule { + var inner: *Decl = decl.childDecl1; + while inner != null as *Decl && count < maxCount { + let iname: String = Cli_DeclName(inner); + if !String_Eq(iname, "") { + names[count] = iname; + count = count + 1; + } + inner = inner.childDecl2; + } + } else { + let dname: String = Cli_DeclName(decl); + if !String_Eq(dname, "") { + names[count] = dname; + count = count + 1; + } + } + decl = next; + } + return count; +} + +func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int { + Print("DEBUG: MergeFileInto "); + PrintLine(path); + let source: String = ReadFile(path); + if String_Eq(source, "") { return 0; } + let lex: *Lexer = Lexer_Tokenize(source); + if Lexer_DiagCount(lex) > 0 { return 0; } + let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount); + if mod == null as *Module { return 0; } + + var added: int = 0; + var decl: *Decl = mod.firstItem; + while decl != null as *Decl { + let next: *Decl = decl.childDecl2; + decl.childDecl2 = null as *Decl; + if decl.kind == dkModule { + var inner: *Decl = decl.childDecl1; + while inner != null as *Decl { + let innerNext: *Decl = inner.childDecl2; + let iname: String = Cli_DeclName(inner); + if !String_Eq(iname, "") && Cli_NameInList(iname, skipNames, skipCount) { + // Skip shadowed stdlib decl + } else { + inner.childDecl2 = target.firstItem; + target.firstItem = inner; + target.itemCount = target.itemCount + 1; + added = added + 1; + } + inner = innerNext; + } + } else { + let dname: String = Cli_DeclName(decl); + if !String_Eq(dname, "") && Cli_NameInList(dname, skipNames, skipCount) { + // Skip shadowed stdlib decl + } else { + decl.childDecl2 = target.firstItem; + target.firstItem = decl; + target.itemCount = target.itemCount + 1; + added = added + 1; + } + } + decl = next; + } + Print("DEBUG: MergeFileInto done, added="); + PrintInt(added); + PrintLine(""); + return added; +} + +func Cli_CopyModuleDecls(target: *Module, source: *Module) { + if source == null as *Module || source.firstItem == null as *Decl { return; } + Print("DEBUG: copy start, source.itemCount="); + PrintInt(source.itemCount); + PrintLine(""); + var decl: *Decl = source.firstItem; + var limit: int = 0; + while decl != null as *Decl && limit < 10000 { + let next: *Decl = decl.childDecl2; + decl.childDecl2 = target.firstItem; + target.firstItem = decl; + target.itemCount = target.itemCount + 1; + decl = next; + limit = limit + 1; + } + source.firstItem = null as *Decl; + source.itemCount = 0; + PrintLine("DEBUG: copy end"); +} + func Cli_BuildProject(projectDir: String) -> int { + PrintLine("DEBUG: Cli_BuildProject start"); let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml")); + PrintLine("DEBUG: manifest loaded"); var outName: String = man.name; if String_Eq(outName, "") { outName = "bux_out"; } let srcDir: String = bux_path_join(projectDir, "src"); + PrintLine("DEBUG: srcDir built"); if !DirExists(srcDir) { Print("Error: no src/ directory in "); PrintLine(projectDir); @@ -260,14 +399,14 @@ func Cli_BuildProject(projectDir: String) -> int { PrintInt(fileCount); PrintLine(" source files"); - // Create merged module - let merged: *Module = bux_alloc(sizeof(Module)) as *Module; - merged.name = "main"; - merged.path = ""; - merged.itemCount = 0; - merged.firstItem = null as *Decl; + // Create user merged module (collect user decls first) + let userMerged: *Module = bux_alloc(sizeof(Module)) as *Module; + userMerged.name = "main"; + userMerged.path = ""; + userMerged.itemCount = 0; + userMerged.firstItem = null as *Decl; - // Parse each file and merge declarations into merged module + // Parse each file and merge declarations into userMerged module var i: int = 0; while i < fileCount { let path: String = files[i]; @@ -291,7 +430,7 @@ func Cli_BuildProject(projectDir: String) -> int { PrintLine(path); return 1; } - // Merge declarations from this module into merged + // Merge declarations from this module into userMerged var decl: *Decl = mod.firstItem; while decl != null as *Decl { let next: *Decl = decl.childDecl2; @@ -301,26 +440,111 @@ func Cli_BuildProject(projectDir: String) -> int { var inner: *Decl = decl.childDecl1; while inner != null as *Decl { let innerNext: *Decl = inner.childDecl2; - inner.childDecl2 = merged.firstItem; - merged.firstItem = inner; - merged.itemCount = merged.itemCount + 1; + inner.childDecl2 = userMerged.firstItem; + userMerged.firstItem = inner; + userMerged.itemCount = userMerged.itemCount + 1; inner = innerNext; } } else { - decl.childDecl2 = merged.firstItem; - merged.firstItem = decl; - merged.itemCount = merged.itemCount + 1; + decl.childDecl2 = userMerged.firstItem; + userMerged.firstItem = decl; + userMerged.itemCount = userMerged.itemCount + 1; } decl = next; } i = i + 1; } + Print("DEBUG: userMerged.itemCount="); + PrintInt(userMerged.itemCount); + PrintLine(""); + + // Collect user declaration names for shadow detection + let maxNames: int = 2048; + let userNames: *String = bux_alloc(maxNames * 8) as *String; + let userNameCount: int = Cli_CollectNames(userMerged, userNames, maxNames); + + // Create final merged module + let merged: *Module = bux_alloc(sizeof(Module)) as *Module; + merged.name = "main"; + merged.path = ""; + merged.itemCount = 0; + merged.firstItem = null as *Decl; + + // Find and merge stdlib declarations + let stdlibDir: String = Cli_FindStdlibDir(projectDir); + if !String_Eq(stdlibDir, "") { + Print("Stdlib found: "); + PrintLine(stdlibDir); + let stdSubdir: String = bux_path_join(stdlibDir, "Std"); + if DirExists(stdSubdir) { + var stdFileCount: int = 0; + let stdFiles: *String = bux_list_dir(stdSubdir, ".bux", &stdFileCount); + Print("DEBUG: stdFileCount="); + PrintInt(stdFileCount); + PrintLine(""); + if stdFileCount > 0 { + Print("Merging "); + PrintInt(stdFileCount); + PrintLine(" stdlib files"); + var si: int = 0; + var stdAdded: int = 0; + while si < stdFileCount { + Print("DEBUG: merging stdlib file "); + PrintInt(si); + PrintLine(""); + // Skip Channel.bux for debugging + if String_Eq(stdFiles[si], "./../stdlib/Std/Channel.bux") { + PrintLine("DEBUG: skipping Channel.bux"); + si = si + 1; + continue; + } + let added: int = Cli_MergeFileInto(merged, stdFiles[si], userNames, userNameCount); + stdAdded = stdAdded + added; + si = si + 1; + } + Print("Stdlib declarations added: "); + PrintInt(stdAdded); + PrintLine(""); + } + } + } + + // Copy user declarations into merged (user shadows stdlib) + PrintLine("DEBUG: before copy"); + var countTest: int = 0; + var dtest: *Decl = userMerged.firstItem; + while dtest != null as *Decl && countTest < 10000 { + dtest = dtest.childDecl2; + countTest = countTest + 1; + } + Print("userMerged actual count="); + PrintInt(countTest); + PrintLine(""); + Print("userMerged.itemCount="); + PrintInt(userMerged.itemCount); + PrintLine(""); + var du: *Decl = userMerged.firstItem; + while du != null as *Decl { + if du.kind == dkFunc { + Print("user func "); + PrintLine(du.strValue); + } + du = du.childDecl2; + } + Cli_CopyModuleDecls(merged, userMerged); + PrintLine("DEBUG: copy done"); + Print("merged.itemCount="); + PrintInt(merged.itemCount); + PrintLine(""); + PrintLine("DEBUG: copy done"); + Print("Merged "); PrintInt(merged.itemCount); PrintLine(" declarations"); // Semantic analysis + PrintLine("DEBUG: about to run sema"); PrintLine("Running sema..."); let sema: *Sema = Sema_Analyze(merged); if Sema_HasError(sema) { @@ -330,10 +554,24 @@ func Cli_BuildProject(projectDir: String) -> int { // HIR lowering PrintLine("Lowering to HIR..."); + var di: *Decl = merged.firstItem; + while di != null as *Decl { + if di.kind == dkFunc { + Print("decl func "); + PrintLine(di.strValue); + } + di = di.childDecl2; + } let hirMod: *HirModule = HirLower_LowerModule(merged, sema); // QBE SSA code generation PrintLine("Generating QBE SSA..."); + var fi: int = 0; + while fi < hirMod.funcCount { + Print("func "); + PrintLine(hirMod.funcs[fi].name); + fi = fi + 1; + } let qbeCode: String = QbeBackend_Generate(hirMod); // Create build directory @@ -418,6 +656,7 @@ func Cli_BuildProject(projectDir: String) -> int { // --------------------------------------------------------------------------- func Cli_RunProject(projectDir: String) -> int { + PrintLine("DEBUG: Cli_RunProject start"); let rc: int = Cli_BuildProject(projectDir); if rc != 0 { return rc; @@ -487,6 +726,7 @@ func Cli_Run(args: *String, argCount: int) -> int { } if String_Eq(cmd, "run") { + PrintLine("DEBUG: run command"); let dir: String = "."; if argCount >= 3 { dir = args[2]; } return Cli_RunProject(dir); diff --git a/src_bux/hir_lower.bux b/src_bux/hir_lower.bux index f382f6e..f122571 100644 --- a/src_bux/hir_lower.bux +++ b/src_bux/hir_lower.bux @@ -103,6 +103,8 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { if kind == ekIdent { n.kind = hVar; n.strValue = expr.strValue; + let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue); + n.typeKind = sym.typeKind; return n; } diff --git a/src_bux/parser.bux b/src_bux/parser.bux index 8ad0e5b..f36dd2d 100644 --- a/src_bux/parser.bux +++ b/src_bux/parser.bux @@ -298,6 +298,10 @@ func parserParsePrimary(p: *Parser) -> *Expr { } // --------------------------------------------------------------------------- + +func dummyFunc() -> int { + return 0; +} // Postfix: call, index, field access, as, is, ? // --------------------------------------------------------------------------- diff --git a/src_bux/qbe_backend.bux b/src_bux/qbe_backend.bux index e8067ff..e64fb19 100644 --- a/src_bux/qbe_backend.bux +++ b/src_bux/qbe_backend.bux @@ -66,12 +66,16 @@ struct QbeEmitter { // Const table for inline substitution constCount: int; consts: *HirConst; + // Current function for param type lookup + currentFunc: *HirFunc; + // Module reference for function return type lookup + mod: *HirModule; } func QBE_Init() -> QbeEmitter { var dsb: StringBuilder = StringBuilder_NewCap(4096); var csb: StringBuilder = StringBuilder_NewCap(16384); - return QbeEmitter { dataSb: dsb, codeSb: csb, tempId: 0, labelId: 0, strId: 0, indent: 0, inBlock: false, constCount: 0, consts: null as *HirConst }; + return QbeEmitter { dataSb: dsb, codeSb: csb, tempId: 0, labelId: 0, strId: 0, indent: 0, inBlock: false, constCount: 0, consts: null as *HirConst, currentFunc: null as *HirFunc, mod: null as *HirModule }; } func QBE_FreshTemp(qbe: *QbeEmitter) -> String { @@ -262,8 +266,8 @@ func QBE_IsTerminator(node: *HirNode) -> bool { if node.kind == hBreak { return true; } if node.kind == hContinue { return true; } if node.kind == hIf { - if node.child3 != null as *HirNode { - return QBE_IsTerminator(node.child2) && QBE_IsTerminator(node.child3); + if node.extraData != null as *HirNode { + return QBE_IsTerminator(node.child2) && QBE_IsTerminator(node.extraData as *HirNode); } return false; } @@ -280,6 +284,56 @@ func QBE_IsTerminator(node: *HirNode) -> bool { return false; } +// --------------------------------------------------------------------------- +// Helper: find function by name in module +// --------------------------------------------------------------------------- + +func QBE_FindFunc(qbe: *QbeEmitter, name: String) -> *HirFunc { + if qbe.mod == null as *HirModule { return null as *HirFunc; } + var i: int = 0; + while i < qbe.mod.funcCount { + if String_Eq(qbe.mod.funcs[i].name, name) { + return &qbe.mod.funcs[i]; + } + i = i + 1; + } + i = 0; + while i < qbe.mod.externCount { + if String_Eq(qbe.mod.externFuncs[i].name, name) { + return &qbe.mod.externFuncs[i]; + } + i = i + 1; + } + return null as *HirFunc; +} + +// --------------------------------------------------------------------------- +// Helper: determine QBE type for any HIR node +// --------------------------------------------------------------------------- + +func QBE_NodeType(qbe: *QbeEmitter, node: *HirNode) -> String { + if node == null as *HirNode { return "l"; } + // For variables, check param type first + if node.kind == hVar && qbe.currentFunc != null as *HirFunc { + var pi2: int = 0; + var pnames2: *HirParam = &qbe.currentFunc.param0; + while pi2 < qbe.currentFunc.paramCount { + if String_Eq(pnames2[pi2].name, node.strValue) { + return QbeBackend_TypeWord(pnames2[pi2].typeKind); + } + pi2 = pi2 + 1; + } + } + // For calls, look up callee return type + if node.kind == hCall && qbe.mod != null as *HirModule { + let callee: *HirFunc = QBE_FindFunc(qbe, node.strValue); + if callee != null as *HirFunc { + return QbeBackend_TypeWord(callee.retTypeKind); + } + } + return QbeBackend_TypeWord(node.typeKind); +} + // --------------------------------------------------------------------------- // Expression emission — returns the QBE value string (e.g. "%t3", "42", "$foo") // For expressions that need multiple instructions, emits them inline. @@ -325,8 +379,21 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { return "0"; } } + // Determine type: use param type if available, else node.typeKind + var varTy: int = node.typeKind; + if qbe.currentFunc != null as *HirFunc { + var pi2: int = 0; + var pnames2: *HirParam = &qbe.currentFunc.param0; + while pi2 < qbe.currentFunc.paramCount { + if String_Eq(pnames2[pi2].name, node.strValue) { + varTy = pnames2[pi2].typeKind; + break; + } + pi2 = pi2 + 1; + } + } let tmp: String = QBE_FreshTemp(qbe); - let ty: String = QbeBackend_TypeWord(node.typeKind); + let ty: String = QbeBackend_TypeWord(varTy); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " ="); @@ -350,7 +417,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { } let val: String = QBE_EmitExpr(qbe, node.child2); if String_Eq(val, "") { val = "0"; } - let ty: String = QbeBackend_TypeWord(node.typeKind); + let ty: String = QBE_NodeType(qbe, node.child2); QBE_EmitIndent(qbe); QBE_Emit(qbe, "store"); QBE_Emit(qbe, ty); @@ -366,12 +433,12 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { let op: int = node.intValue; let left: String = QBE_EmitExpr(qbe, node.child1); let right: String = QBE_EmitExpr(qbe, node.child2); - let ty: String = QbeBackend_TypeWord(node.typeKind); + let ty: String = QBE_NodeType(qbe, node.child1); // Comparison operators // QBE comparison ops: ceqw/ceql, cnew/cnel, csltw/csltl, cslew/cslel, csgtw/csgtl, csgew/csgel var cmpOp: String = ""; - var cmpTy: String = ty; + let cmpTy: String = QBE_NodeType(qbe, node.child1); if op == tkEq { cmpOp = "ceq"; } else if op == tkNe { cmpOp = "cne"; } else if op == tkLt { cmpOp = "cslt"; } @@ -385,7 +452,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { QBE_Emit(qbe, cmpTmp); QBE_Emit(qbe, " =w "); QBE_Emit(qbe, cmpOp); - QBE_Emit(qbe, ty); + QBE_Emit(qbe, cmpTy); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(left)); QBE_Emit(qbe, ", "); @@ -452,7 +519,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { let rVal: String = QBE_EmitExpr(qbe, node.child2); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); - QBE_Emit(qbe, " =l copy "); + QBE_Emit(qbe, " =w copy "); QBE_EmitLine(qbe, rVal); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); @@ -460,7 +527,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { QBE_EmitLine(qbe, lFalse); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); - QBE_EmitLine(qbe, " =l copy 0"); + QBE_EmitLine(qbe, " =w copy 0"); QBE_EmitLine(qbe, lEnd); return tmp; } @@ -479,7 +546,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { QBE_EmitLine(qbe, lTrue); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); - QBE_EmitLine(qbe, " =l copy 1"); + QBE_EmitLine(qbe, " =w copy 1"); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lEnd); @@ -487,7 +554,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { let rVal: String = QBE_EmitExpr(qbe, node.child2); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); - QBE_Emit(qbe, " =l copy "); + QBE_Emit(qbe, " =w copy "); QBE_EmitLine(qbe, rVal); QBE_EmitLine(qbe, lEnd); return tmp; @@ -500,7 +567,7 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { if kind == hUnary { let op: int = node.intValue; let operand: String = QBE_EmitExpr(qbe, node.child1); - let ty: String = QbeBackend_TypeWord(node.typeKind); + let ty: String = QBE_NodeType(qbe, node.child1); if op == tkBang || op == tkMinus { let tmp: String = QBE_FreshTemp(qbe); @@ -535,7 +602,14 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { if kind == hCall { // Pre-evaluate arguments to avoid emitting instructions inside the call let tmp: String = QBE_FreshTemp(qbe); - let retTy: String = QbeBackend_TypeWord(node.typeKind); + var retTy: String = QbeBackend_TypeWord(node.typeKind); + // Look up callee return type from module if typeKind is unknown + if String_Eq(retTy, "l") && qbe.mod != null as *HirModule { + let callee: *HirFunc = QBE_FindFunc(qbe, node.strValue); + if callee != null as *HirFunc { + retTy = QbeBackend_TypeWord(callee.retTypeKind); + } + } var arg1: String = ""; var arg2: String = ""; if node.child1 != null as *HirNode { @@ -552,13 +626,13 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { QBE_Emit(qbe, QBE_EscapeIdent(node.strValue)); QBE_Emit(qbe, "("); if node.child1 != null as *HirNode { - let a1ty: String = QbeBackend_TypeWord(node.child1.typeKind); + let a1ty: String = QBE_NodeType(qbe, node.child1); QBE_Emit(qbe, a1ty); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(arg1)); } if node.child2 != null as *HirNode { - let a2ty: String = QbeBackend_TypeWord(node.child2.typeKind); + let a2ty: String = QBE_NodeType(qbe, node.child2); QBE_Emit(qbe, ", "); QBE_Emit(qbe, a2ty); QBE_Emit(qbe, " "); @@ -599,6 +673,20 @@ func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { return tmp; } + // Index pointer: &arr[idx] (for pointer indexing) + if kind == hIndexPtr { + let base: String = QBE_EmitExpr(qbe, node.child1); + let idx: String = QBE_EmitExpr(qbe, node.child2); + let tmp: String = QBE_FreshTemp(qbe); + QBE_EmitIndent(qbe); + QBE_Emit(qbe, tmp); + QBE_Emit(qbe, " =l add "); + QBE_Emit(qbe, QBE_EnsureLocal(base)); + QBE_Emit(qbe, ", "); + QBE_EmitLine(qbe, QBE_EnsureLocal(idx)); + return tmp; + } + // Cast — QBE doesn't need same-width casts; passthrough if kind == hCast { return QBE_EmitExpr(qbe, node.child1); @@ -626,6 +714,12 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { if node == null as *HirNode { return; } let kind: int = node.kind; + // Assign statement + if kind == hAssign { + discard QBE_EmitExpr(qbe, node); + return; + } + // Store: declaration or assignment if kind == hStore { if node.child1 != null as *HirNode && node.child1.kind == hAlloca { @@ -653,7 +747,7 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { // Plain assignment let target: String = QBE_EmitExpr(qbe, node.child1); let val: String = QBE_EmitExpr(qbe, node.child2); - let ty: String = QbeBackend_TypeWord(node.typeKind); + let ty: String = QBE_NodeType(qbe, node.child2); if String_Eq(val, "") { val = "0"; } if String_Eq(target, "") { return; } QBE_EmitIndent(qbe); @@ -692,7 +786,7 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { QBE_Emit(qbe, ", "); QBE_Emit(qbe, lThen); QBE_Emit(qbe, ", "); - var hasElse: bool = node.child3 != null as *HirNode; + var hasElse: bool = node.extraData != null as *HirNode; if hasElse { QBE_EmitLine(qbe, lElse); } else { @@ -709,8 +803,8 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { if hasElse { QBE_EmitLine(qbe, lElse); - QBE_EmitStmt(qbe, node.child3); - if !QBE_IsTerminator(node.child3) { + QBE_EmitStmt(qbe, node.extraData as *HirNode); + if !QBE_IsTerminator(node.extraData as *HirNode) { QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lEnd); @@ -719,7 +813,7 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { // Only emit @lEnd if at least one branch falls through var needEnd: bool = !QBE_IsTerminator(node.child2); - if hasElse && !QBE_IsTerminator(node.child3) { + if hasElse && !QBE_IsTerminator(node.extraData as *HirNode) { needEnd = true; } if needEnd { @@ -828,6 +922,7 @@ func QBE_EmitStmt(qbe: *QbeEmitter, node: *HirNode) { func QBE_EmitFunc(qbe: *QbeEmitter, f: *HirFunc) { if f.body == null as *HirNode { return; } + qbe.currentFunc = f; QBE_Emit(qbe, "export function "); let retTy: String = QbeBackend_TypeWord(f.retTypeKind); @@ -929,6 +1024,7 @@ func QbeBackend_Generate(mod: *HirModule) -> String { qbe.inBlock = false; qbe.constCount = mod.constCount; qbe.consts = mod.consts; + qbe.mod = mod; // Header QBE_EmitLine(qbe, "# Generated by Bux QBE Backend"); diff --git a/src_bux/sema.bux b/src_bux/sema.bux index 6a573ee..668ae7d 100644 --- a/src_bux/sema.bux +++ b/src_bux/sema.bux @@ -312,7 +312,7 @@ func Sema_Analyze(mod: *Module) -> *Sema { let s: *Sema = bux_alloc(sizeof(Sema)) as *Sema; s.module = mod; s.scope = bux_alloc(sizeof(Scope)) as *Scope; - s.scope.symbols = bux_alloc(256 as uint * sizeof(Symbol)) as *Symbol; + s.scope.symbols = bux_alloc(1024 as uint * sizeof(Symbol)) as *Symbol; s.scope.count = 0; s.scope.parent = null as *Scope; s.hasError = false;