// qbe_backend.bux — QBE SSA IL generator backend // Generates QBE SSA assembly from HIR. Replaces both C and Nim backends. // Pipeline: HirModule → .ssa → qbe → .s → cc → executable import Std::String; import Std::Io::{Print, PrintLine}; module QbeBackend { // --------------------------------------------------------------------------- // QBE type mapping // QBE base types: w (32b), l (64b), s (32f), d (64f), b (8b), h (16b) // Aggregate: :Name = { type, type, ... } // --------------------------------------------------------------------------- func QbeBackend_TypeBase(kind: int) -> String { if kind == tyVoid { return ""; } if kind == tyBool { return "w"; } if kind == tyBool8 { return "b"; } if kind == tyBool16 { return "h"; } if kind == tyBool32 { return "w"; } if kind == tyChar8 { return "b"; } if kind == tyChar16 { return "h"; } if kind == tyChar32 { return "w"; } if kind == tyStr { return "l"; } if kind == tyInt8 { return "b"; } if kind == tyInt16 { return "h"; } if kind == tyInt32 { return "w"; } if kind == tyInt64 { return "l"; } if kind == tyInt { return "l"; } if kind == tyUInt8 { return "b"; } if kind == tyUInt16 { return "h"; } if kind == tyUInt32 { return "w"; } if kind == tyUInt64 { return "l"; } if kind == tyUInt { return "l"; } if kind == tyFloat32 { return "s"; } if kind == tyFloat64 { return "d"; } if kind == tyPointer { return "l"; } return "l"; } // Return the "word" type for a given kind (used for comparisons, arithmetic) func QbeBackend_TypeWord(kind: int) -> String { if kind == tyFloat32 { return "s"; } if kind == tyFloat64 { return "d"; } if kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32 { return "w"; } if kind == tyInt32 || kind == tyUInt32 || kind == tyChar32 { return "w"; } if kind == tyChar8 { return "w"; } if kind == tyChar16 { return "w"; } return "l"; } // --------------------------------------------------------------------------- // Emitter state // --------------------------------------------------------------------------- struct QbeEmitter { dataSb: StringBuilder; codeSb: StringBuilder; tempId: int; labelId: int; strId: int; indent: int; // Track if we're currently in a block (after @label) inBlock: bool; // 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, currentFunc: null as *HirFunc, mod: null as *HirModule }; } func QBE_FreshTemp(qbe: *QbeEmitter) -> String { let id: int = qbe.tempId; qbe.tempId = id + 1; var prefix: String = "%t"; return String_Concat(prefix, String_FromInt(id as int64)); } func QBE_FreshLabel(qbe: *QbeEmitter) -> String { let id: int = qbe.labelId; qbe.labelId = id + 1; var prefix: String = "@L"; return String_Concat(prefix, String_FromInt(id as int64)); } func QBE_FreshStr(qbe: *QbeEmitter) -> String { let id: int = qbe.strId; qbe.strId = id + 1; var prefix: String = "$str_"; return String_Concat(prefix, String_FromInt(id as int64)); } func QBE_EmitData(qbe: *QbeEmitter, text: String) { StringBuilder_Append(&qbe.dataSb, text); } func QBE_EmitDataLine(qbe: *QbeEmitter, text: String) { StringBuilder_Append(&qbe.dataSb, text); StringBuilder_Append(&qbe.dataSb, "\n"); } func QBE_EmitDataChar(qbe: *QbeEmitter, c: char8) { var tmp: *char8 = bux_alloc(2) as *char8; tmp[0] = c; tmp[1] = 0 as char8; StringBuilder_Append(&qbe.dataSb, tmp); } func QBE_Emit(qbe: *QbeEmitter, text: String) { StringBuilder_Append(&qbe.codeSb, text); } func QBE_EmitLine(qbe: *QbeEmitter, text: String) { StringBuilder_Append(&qbe.codeSb, text); StringBuilder_Append(&qbe.codeSb, "\n"); } func QBE_EmitChar(qbe: *QbeEmitter, c: char8) { var tmp: *char8 = bux_alloc(2) as *char8; tmp[0] = c; tmp[1] = 0 as char8; StringBuilder_Append(&qbe.codeSb, tmp); } func QBE_EmitIndent(qbe: *QbeEmitter) { var i: int = 0; while i < qbe.indent { StringBuilder_Append(&qbe.codeSb, " "); i = i + 1; } } func QBE_Build(qbe: *QbeEmitter) -> String { let dataPart: String = StringBuilder_Build(&qbe.dataSb); let codePart: String = StringBuilder_Build(&qbe.codeSb); var result: StringBuilder = StringBuilder_NewCap(2048); StringBuilder_Append(&result, dataPart); StringBuilder_Append(&result, codePart); return StringBuilder_Build(&result); } // --------------------------------------------------------------------------- // Escape identifier for QBE // QBE names: $global, %temp, @label // --------------------------------------------------------------------------- func QBE_EscapeIdent(name: String) -> String { if name == null as String || String_Eq(name, "") { return "_anon"; } return name; } func QBE_LocalName(name: String) -> String { if name == null as String || String_Eq(name, "") { return "%_anon"; } var prefix: String = "%"; return String_Concat(prefix, name); } // Ensure a value used as variable/argument has % prefix for bare identifiers func QBE_EnsureLocal(val: String) -> String { if String_Eq(val, "") { return "0"; } let c: char8 = val[0]; if c == 37 as char8 || c == 36 as char8 || c == 64 as char8 { return val; } // %, $, @ if c == 45 as char8 { return val; } // negative number if c >= 48 as char8 && c <= 57 as char8 { return val; } // digit // Uppercase: assume unresolved global/type, use 0 if c >= 65 as char8 && c <= 90 as char8 { return "0"; } // Lowercase: assume local, add % var prefix: String = "%"; return String_Concat(prefix, val); } // --------------------------------------------------------------------------- // String literal → data section // --------------------------------------------------------------------------- func QBE_EmitStringLiteral(qbe: *QbeEmitter, text: String) -> String { let sym: String = QBE_FreshStr(qbe); var content: String = text; var start: int = 0; var end: int = 0; if content != null as String { let slen: int = bux_strlen(content) as int; end = slen; if slen >= 2 { // Regular string: "content" if content[0] == 34 as char8 && content[slen - 1] == 34 as char8 { start = 1; end = slen - 1; } // Backtick raw string: `content` if content[0] == 96 as char8 && content[slen - 1] == 96 as char8 { start = 1; end = slen - 1; } } } // Build data declaration into dataSb // data $str = { b "content", b 0 } QBE_EmitData(qbe, "data "); QBE_EmitData(qbe, sym); QBE_EmitData(qbe, " = { b \""); var i: int = start; while i < end { let c: char8 = content[i]; if c == 10 as char8 { QBE_EmitData(qbe, "\\n"); } else if c == 13 as char8 { QBE_EmitData(qbe, "\\r"); } else if c == 9 as char8 { QBE_EmitData(qbe, "\\t"); } else if c == 34 as char8 { QBE_EmitData(qbe, "\\\""); } else if c == 92 as char8 { QBE_EmitData(qbe, "\\\\"); } else { QBE_EmitDataChar(qbe, c); } i = i + 1; } QBE_EmitDataLine(qbe, "\", b 0 }"); return sym; } // --------------------------------------------------------------------------- // Type definitions for structs // --------------------------------------------------------------------------- func QBE_EmitStructType(qbe: *QbeEmitter, st: *HirStruct) { QBE_Emit(qbe, "type :"); QBE_Emit(qbe, QBE_EscapeIdent(st.name)); QBE_Emit(qbe, " = { "); var fi: int = 0; while fi < st.fieldCount { if fi > 0 { QBE_Emit(qbe, ", "); } // Map field type; default to l if unknown var ft: String = "l"; if st.fields != null as *HirStructField { let ftype: String = st.fields[fi].typeName; if String_Eq(ftype, "int") || String_Eq(ftype, "int64") || String_Eq(ftype, "uint") || String_Eq(ftype, "uint64") || String_Eq(ftype, "String") || String_Eq(ftype, "*char8") { ft = "l"; } else if String_Eq(ftype, "int32") || String_Eq(ftype, "uint32") { ft = "w"; } else if String_Eq(ftype, "bool") || String_Eq(ftype, "char8") { ft = "b"; } else if String_Eq(ftype, "float64") { ft = "d"; } else if String_Eq(ftype, "float32") { ft = "s"; } } QBE_Emit(qbe, ft); fi = fi + 1; } QBE_EmitLine(qbe, " }"); } // --------------------------------------------------------------------------- // Helper: check if a statement node is a block terminator // --------------------------------------------------------------------------- func QBE_IsTerminator(node: *HirNode) -> bool { if node == null as *HirNode { return false; } if node.kind == hReturn { return true; } if node.kind == hBreak { return true; } if node.kind == hContinue { return true; } if node.kind == hIf { if node.extraData != null as *void { return QBE_IsTerminator(node.child2) && QBE_IsTerminator(node.extraData as *HirNode); } return false; } if node.kind == hBlock { var child: *HirNode = node.child1; while child != null as *HirNode { if QBE_IsTerminator(child) { return true; } child = child.child3; } return false; } 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: find struct by name in module // --------------------------------------------------------------------------- func QBE_FindStruct(qbe: *QbeEmitter, name: String) -> *HirStruct { if qbe.mod == null as *HirModule || name == null as String { return null as *HirStruct; } var i: int = 0; while i < qbe.mod.structCount { if String_Eq(qbe.mod.structs[i].name, name) { return &qbe.mod.structs[i]; } i = i + 1; } return null as *HirStruct; } // --------------------------------------------------------------------------- // Helper: compute field offset in a struct // --------------------------------------------------------------------------- func QBE_FieldOffset(qbe: *QbeEmitter, structName: String, fieldName: String) -> int { let st: *HirStruct = QBE_FindStruct(qbe, structName); if st == null as *HirStruct { return 0; } var off: int = 0; var fi: int = 0; while fi < st.fieldCount { if String_Eq(st.fields[fi].name, fieldName) { return off; } let ftype: String = st.fields[fi].typeName; var fsz: int = 8; if String_Eq(ftype, "int8") || String_Eq(ftype, "uint8") || String_Eq(ftype, "bool8") || String_Eq(ftype, "char8") { fsz = 1; } else if String_Eq(ftype, "int16") || String_Eq(ftype, "uint16") || String_Eq(ftype, "bool16") || String_Eq(ftype, "char16") { fsz = 2; } else if String_Eq(ftype, "int32") || String_Eq(ftype, "uint32") || String_Eq(ftype, "bool32") || String_Eq(ftype, "char32") || String_Eq(ftype, "float32") { fsz = 4; } off = off + fsz; fi = fi + 1; } return 0; } // --------------------------------------------------------------------------- // Helper: type size in bytes // --------------------------------------------------------------------------- func QBE_TypeSize(qbe: *QbeEmitter, kind: int, typeName: String) -> int { if kind == tyNamed && typeName != null as String && !String_Eq(typeName, "") { let st: *HirStruct = QBE_FindStruct(qbe, typeName); if st != null as *HirStruct && st.fields != null as *HirStructField { var sz: int = 0; var fi: int = 0; while fi < st.fieldCount { let ftype: String = st.fields[fi].typeName; var fsz: int = 8; if String_Eq(ftype, "int8") || String_Eq(ftype, "uint8") || String_Eq(ftype, "bool8") || String_Eq(ftype, "char8") { fsz = 1; } else if String_Eq(ftype, "int16") || String_Eq(ftype, "uint16") || String_Eq(ftype, "bool16") || String_Eq(ftype, "char16") { fsz = 2; } else if String_Eq(ftype, "int32") || String_Eq(ftype, "uint32") || String_Eq(ftype, "bool32") || String_Eq(ftype, "char32") || String_Eq(ftype, "float32") { fsz = 4; } sz = sz + fsz; fi = fi + 1; } return sz; } } if kind == tyBool8 || kind == tyChar8 || kind == tyInt8 || kind == tyUInt8 { return 1; } if kind == tyBool16 || kind == tyChar16 || kind == tyInt16 || kind == tyUInt16 { return 2; } if kind == tyBool32 || kind == tyChar32 || kind == tyInt32 || kind == tyUInt32 || kind == tyFloat32 { return 4; } return 8; } // --------------------------------------------------------------------------- // 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. // --------------------------------------------------------------------------- func QBE_EmitExpr(qbe: *QbeEmitter, node: *HirNode) -> String { if node == null as *HirNode { return "0"; } let kind: int = node.kind; // Literal if kind == hLit { if node.intValue == tkNull { return "0"; } if node.intValue == tkStringLiteral { return QBE_EmitStringLiteral(qbe, node.strValue); } if node.intValue == tkBoolLiteral { if String_Eq(node.strValue, "true") { return "1"; } return "0"; } // Numeric literal — emit as-is return node.strValue; } // Variable if kind == hVar { // Check if this is a const reference var ci: int = 0; while ci < qbe.constCount { if String_Eq(qbe.consts[ci].name, node.strValue) { var cv: String = String_FromInt(qbe.consts[ci].value as int64); return cv; } ci = ci + 1; } // Uppercase names are unresolved types/modules — return 0 if node.strValue != null as String && !String_Eq(node.strValue, "") { let fc: char8 = node.strValue[0]; if fc >= 65 as char8 && fc <= 90 as char8 { 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(varTy); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " ="); QBE_Emit(qbe, ty); QBE_Emit(qbe, " load"); QBE_Emit(qbe, ty); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_LocalName(node.strValue)); QBE_EmitLine(qbe, ""); return tmp; } // Assign (used as expression — e.g. inside another binary op) if kind == hAssign { var target: String = ""; if node.child1 != null as *HirNode && node.child1.kind == hVar { target = QBE_LocalName(node.child1.strValue); } else if node.child1 != null as *HirNode && node.child1.kind == hFieldPtr { // Field assignment: generate pointer, not auto-load value let base: String = QBE_EmitExpr(qbe, node.child1.child1); let ptrTmp: String = QBE_FreshTemp(qbe); var structName: String = node.child1.typeName; if structName == null as String || String_Eq(structName, "") { structName = node.child1.child1.typeName; } let offset: int = QBE_FieldOffset(qbe, structName, node.child1.strValue); QBE_EmitIndent(qbe); QBE_Emit(qbe, ptrTmp); QBE_Emit(qbe, " =l add "); QBE_Emit(qbe, QBE_EnsureLocal(base)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, String_FromInt(offset as int64)); QBE_EmitLine(qbe, ""); target = ptrTmp; } else { target = QBE_EmitExpr(qbe, node.child1); if String_Eq(target, "") { return "0"; } } let val: String = QBE_EmitExpr(qbe, node.child2); if String_Eq(val, "") { val = "0"; } let ty: String = QBE_NodeType(qbe, node.child2); QBE_EmitIndent(qbe); QBE_Emit(qbe, "store"); QBE_Emit(qbe, ty); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(val)); QBE_Emit(qbe, ", "); QBE_EmitLine(qbe, QBE_EnsureLocal(target)); return val; } // Binary operation if kind == hBinary { let op: int = node.intValue; let left: String = QBE_EmitExpr(qbe, node.child1); let right: String = QBE_EmitExpr(qbe, node.child2); 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 = ""; 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"; } else if op == tkLe { cmpOp = "csle"; } else if op == tkGt { cmpOp = "csgt"; } else if op == tkGe { cmpOp = "csge"; } if !String_Eq(cmpOp, "") { let cmpTmp: String = QBE_FreshTemp(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, cmpTmp); QBE_Emit(qbe, " =w "); QBE_Emit(qbe, cmpOp); QBE_Emit(qbe, cmpTy); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(left)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, QBE_EnsureLocal(right)); QBE_EmitLine(qbe, ""); // QBE comparisons produce word results; extend if needed if !String_Eq(ty, "w") && !String_Eq(ty, "b") && !String_Eq(ty, "h") { let tmp: String = QBE_FreshTemp(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " ="); QBE_Emit(qbe, ty); QBE_Emit(qbe, " extsw "); QBE_Emit(qbe, cmpTmp); QBE_EmitLine(qbe, ""); return tmp; } return cmpTmp; } // Arithmetic var opName: String = ""; if op == tkPlus { opName = "add"; } else if op == tkMinus { opName = "sub"; } else if op == tkStar { opName = "mul"; } else if op == tkSlash { opName = "div"; } else if op == tkPercent { opName = "rem"; } else if op == tkAmp { opName = "and"; } else if op == tkPipe { opName = "or"; } else if op == tkCaret { opName = "xor"; } else if op == tkShl { opName = "shl"; } else if op == tkShr { opName = "shr"; } if !String_Eq(opName, "") { let tmp: String = QBE_FreshTemp(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " ="); QBE_Emit(qbe, ty); QBE_Emit(qbe, " "); QBE_Emit(qbe, opName); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(left)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, QBE_EnsureLocal(right)); QBE_EmitLine(qbe, ""); return tmp; } // Logical and/or — convert to branch sequences if op == tkAmpAmp { let tmp: String = QBE_FreshTemp(qbe); let lTrue: String = QBE_FreshLabel(qbe); let lFalse: String = QBE_FreshLabel(qbe); let lEnd: String = QBE_FreshLabel(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jnz "); QBE_Emit(qbe, QBE_EnsureLocal(left)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, lTrue); QBE_Emit(qbe, ", "); QBE_EmitLine(qbe, lFalse); QBE_EmitLine(qbe, lTrue); let rVal: String = QBE_EmitExpr(qbe, node.child2); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " =w copy "); QBE_EmitLine(qbe, rVal); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lEnd); QBE_EmitLine(qbe, lFalse); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_EmitLine(qbe, " =w copy 0"); QBE_EmitLine(qbe, lEnd); return tmp; } if op == tkPipePipe { let tmp: String = QBE_FreshTemp(qbe); let lTrue: String = QBE_FreshLabel(qbe); let lFalse: String = QBE_FreshLabel(qbe); let lEnd: String = QBE_FreshLabel(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jnz "); QBE_Emit(qbe, QBE_EnsureLocal(left)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, lTrue); QBE_Emit(qbe, ", "); QBE_EmitLine(qbe, lFalse); QBE_EmitLine(qbe, lTrue); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_EmitLine(qbe, " =w copy 1"); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lEnd); QBE_EmitLine(qbe, lFalse); let rVal: String = QBE_EmitExpr(qbe, node.child2); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " =w copy "); QBE_EmitLine(qbe, rVal); QBE_EmitLine(qbe, lEnd); return tmp; } return left; } // Unary if kind == hUnary { let op: int = node.intValue; let operand: String = QBE_EmitExpr(qbe, node.child1); let ty: String = QBE_NodeType(qbe, node.child1); if op == tkBang || op == tkMinus { let tmp: String = QBE_FreshTemp(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " ="); QBE_Emit(qbe, ty); if op == tkBang { // xor with 1 to invert boolean QBE_Emit(qbe, " xor "); QBE_Emit(qbe, QBE_EnsureLocal(operand)); QBE_Emit(qbe, ", 1"); } else { QBE_Emit(qbe, " sub 0, "); QBE_Emit(qbe, QBE_EnsureLocal(operand)); } QBE_EmitLine(qbe, ""); return tmp; } if op == tkAmp { // Address-of: just return the variable name (it's already a pointer) // The operand should be a variable if node.child1 != null as *HirNode && node.child1.kind == hVar { return QBE_EscapeIdent(node.child1.strValue); } return operand; } return operand; } // Call if kind == hCall { // Pre-evaluate arguments to avoid emitting instructions inside the call let tmp: String = QBE_FreshTemp(qbe); 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 { arg1 = QBE_EmitExpr(qbe, node.child1); } if node.child2 != null as *HirNode { arg2 = QBE_EmitExpr(qbe, node.child2); } QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " ="); QBE_Emit(qbe, retTy); QBE_Emit(qbe, " call $"); QBE_Emit(qbe, QBE_EscapeIdent(node.strValue)); QBE_Emit(qbe, "("); if node.child1 != null as *HirNode { 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 = QBE_NodeType(qbe, node.child2); QBE_Emit(qbe, ", "); QBE_Emit(qbe, a2ty); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(arg2)); } QBE_EmitLine(qbe, ")"); return tmp; } // Load (dereference) if kind == hLoad { let ptr: String = QBE_EmitExpr(qbe, node.child1); let tmp: String = QBE_FreshTemp(qbe); let ty: String = QbeBackend_TypeWord(node.typeKind); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " ="); QBE_Emit(qbe, ty); QBE_Emit(qbe, " load"); QBE_Emit(qbe, ty); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(ptr)); QBE_EmitLine(qbe, ""); return tmp; } // Field pointer: &obj.field (with auto-load for rvalue contexts) if kind == hFieldPtr { let base: String = QBE_EmitExpr(qbe, node.child1); let ptrTmp: String = QBE_FreshTemp(qbe); var structName: String = node.typeName; if structName == null as String || String_Eq(structName, "") { structName = node.child1.typeName; } let offset: int = QBE_FieldOffset(qbe, structName, node.strValue); QBE_EmitIndent(qbe); QBE_Emit(qbe, ptrTmp); QBE_Emit(qbe, " =l add "); QBE_Emit(qbe, QBE_EnsureLocal(base)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, String_FromInt(offset as int64)); QBE_EmitLine(qbe, ""); // Auto-load value from field address (rvalue context) let valTmp: String = QBE_FreshTemp(qbe); let fty: String = QbeBackend_TypeWord(node.typeKind); QBE_EmitIndent(qbe); QBE_Emit(qbe, valTmp); QBE_Emit(qbe, " ="); QBE_Emit(qbe, fty); QBE_Emit(qbe, " load"); QBE_Emit(qbe, fty); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(ptrTmp)); QBE_EmitLine(qbe, ""); return valTmp; } // Struct init: allocate and fill fields if kind == hStructInit { let st: *HirStruct = QBE_FindStruct(qbe, node.strValue); var structSize: int = 0; if st != null as *HirStruct { var si: int = 0; while si < st.fieldCount { let ftype: String = st.fields[si].typeName; var fsz: int = 8; if String_Eq(ftype, "int8") || String_Eq(ftype, "uint8") || String_Eq(ftype, "bool8") || String_Eq(ftype, "char8") { fsz = 1; } else if String_Eq(ftype, "int16") || String_Eq(ftype, "uint16") || String_Eq(ftype, "bool16") || String_Eq(ftype, "char16") { fsz = 2; } else if String_Eq(ftype, "int32") || String_Eq(ftype, "uint32") || String_Eq(ftype, "bool32") || String_Eq(ftype, "char32") || String_Eq(ftype, "float32") { fsz = 4; } structSize = structSize + fsz; si = si + 1; } } let ptr: String = QBE_FreshTemp(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, ptr); QBE_Emit(qbe, " =l alloc4 "); let szStr: String = String_FromInt(structSize as int64); QBE_Emit(qbe, szStr); QBE_EmitLine(qbe, ""); // Store each field var field: *HirNode = node.child1; while field != null as *HirNode { let fval: String = QBE_EmitExpr(qbe, field.child1); let foff: int = QBE_FieldOffset(qbe, node.strValue, field.strValue); let fptr: String = QBE_FreshTemp(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, fptr); QBE_Emit(qbe, " =l add "); QBE_Emit(qbe, QBE_EnsureLocal(ptr)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, String_FromInt(foff as int64)); QBE_EmitLine(qbe, ""); let fty: String = QbeBackend_TypeWord(field.child1.typeKind); QBE_EmitIndent(qbe); QBE_Emit(qbe, "store"); QBE_Emit(qbe, fty); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(fval)); QBE_Emit(qbe, ", "); QBE_EmitLine(qbe, QBE_EnsureLocal(fptr)); field = field.child3; } return ptr; } // 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); } // SizeOf if kind == hSizeOf { let tmp: String = QBE_FreshTemp(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, tmp); QBE_Emit(qbe, " =l copy 8"); QBE_EmitLine(qbe, ""); return tmp; } // Fallback return "0"; } // --------------------------------------------------------------------------- // Statement emission — no return value, just side effects // --------------------------------------------------------------------------- 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 { // Declaration: alloca + init let varName: String = QBE_LocalName(node.child1.strValue); let varTy: String = QbeBackend_TypeWord(node.child1.typeKind); // Emit alloca QBE_EmitIndent(qbe); QBE_Emit(qbe, varName); QBE_Emit(qbe, " =l alloc4 8"); QBE_EmitLine(qbe, ""); if node.child2 != null as *HirNode { let val: String = QBE_EmitExpr(qbe, node.child2); if String_Eq(val, "") { val = "0"; } QBE_EmitIndent(qbe); QBE_Emit(qbe, "store"); QBE_Emit(qbe, varTy); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(val)); QBE_Emit(qbe, ", "); QBE_EmitLine(qbe, QBE_EnsureLocal(varName)); } return; } // Field assignment: target is hFieldPtr, need pointer not value if node.child1 != null as *HirNode && node.child1.kind == hFieldPtr { let base: String = QBE_EmitExpr(qbe, node.child1.child1); let ptrTmp: String = QBE_FreshTemp(qbe); var structName: String = node.child1.typeName; if structName == null as String || String_Eq(structName, "") { structName = node.child1.child1.typeName; } let offset: int = QBE_FieldOffset(qbe, structName, node.child1.strValue); QBE_EmitIndent(qbe); QBE_Emit(qbe, ptrTmp); QBE_Emit(qbe, " =l add "); QBE_Emit(qbe, QBE_EnsureLocal(base)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, String_FromInt(offset as int64)); QBE_EmitLine(qbe, ""); let val: String = QBE_EmitExpr(qbe, node.child2); let ty: String = QBE_NodeType(qbe, node.child2); if String_Eq(val, "") { val = "0"; } QBE_EmitIndent(qbe); QBE_Emit(qbe, "store"); QBE_Emit(qbe, ty); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(val)); QBE_Emit(qbe, ", "); QBE_EmitLine(qbe, QBE_EnsureLocal(ptrTmp)); return; } // Plain assignment let target: String = QBE_EmitExpr(qbe, node.child1); let val: String = QBE_EmitExpr(qbe, node.child2); let ty: String = QBE_NodeType(qbe, node.child2); if String_Eq(val, "") { val = "0"; } if String_Eq(target, "") { return; } QBE_EmitIndent(qbe); QBE_Emit(qbe, "store"); QBE_Emit(qbe, ty); QBE_Emit(qbe, " "); QBE_Emit(qbe, QBE_EnsureLocal(val)); QBE_Emit(qbe, ", "); QBE_EmitLine(qbe, QBE_EnsureLocal(target)); return; } if kind == hReturn { if node.child1 != null as *HirNode { let val: String = QBE_EmitExpr(qbe, node.child1); QBE_EmitIndent(qbe); QBE_Emit(qbe, "ret "); QBE_EmitLine(qbe, QBE_EnsureLocal(val)); } else { QBE_EmitIndent(qbe); QBE_EmitLine(qbe, "ret"); } return; } // If if kind == hIf { let condVal: String = QBE_EmitExpr(qbe, node.child1); let lThen: String = QBE_FreshLabel(qbe); let lElse: String = QBE_FreshLabel(qbe); let lEnd: String = QBE_FreshLabel(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jnz "); QBE_Emit(qbe, QBE_EnsureLocal(condVal)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, lThen); QBE_Emit(qbe, ", "); var hasElse: bool = node.extraData != null as *void; if hasElse { QBE_EmitLine(qbe, lElse); } else { QBE_EmitLine(qbe, lEnd); } QBE_EmitLine(qbe, lThen); QBE_EmitStmt(qbe, node.child2); if !QBE_IsTerminator(node.child2) { QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lEnd); } if hasElse { QBE_EmitLine(qbe, lElse); 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); } } // Only emit @lEnd if at least one branch falls through var needEnd: bool = !QBE_IsTerminator(node.child2); if hasElse && !QBE_IsTerminator(node.extraData as *HirNode) { needEnd = true; } if needEnd { QBE_EmitLine(qbe, lEnd); } else { // lEnd is unreferenced, but was used in jnz when !hasElse — need to emit it if !hasElse { QBE_EmitLine(qbe, lEnd); } } return; } // While if kind == hWhile { let lCond: String = QBE_FreshLabel(qbe); let lBody: String = QBE_FreshLabel(qbe); let lEnd: String = QBE_FreshLabel(qbe); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lCond); QBE_EmitLine(qbe, lCond); let condVal: String = QBE_EmitExpr(qbe, node.child1); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jnz "); QBE_Emit(qbe, QBE_EnsureLocal(condVal)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, lBody); QBE_Emit(qbe, ", "); QBE_EmitLine(qbe, lEnd); QBE_EmitLine(qbe, lBody); QBE_EmitStmt(qbe, node.child2); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lCond); QBE_EmitLine(qbe, lEnd); return; } // Loop (infinite) if kind == hLoop { let lBody: String = QBE_FreshLabel(qbe); let lEnd: String = QBE_FreshLabel(qbe); // Save loop labels for break/continue (not implemented yet) QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lBody); QBE_EmitLine(qbe, lBody); QBE_EmitStmt(qbe, node.child1); QBE_EmitIndent(qbe); QBE_Emit(qbe, "jmp "); QBE_EmitLine(qbe, lBody); QBE_EmitLine(qbe, lEnd); return; } // Break / Continue if kind == hBreak { // TODO: need loop label stack return; } if kind == hContinue { // TODO: need loop label stack return; } // Block — sequence of statements if kind == hBlock { var child: *HirNode = node.child1; while child != null as *HirNode { QBE_EmitStmt(qbe, child); if QBE_IsTerminator(child) { break; } child = child.child3; } return; } // Call as statement if kind == hCall { QBE_EmitExpr(qbe, node); return; } // Alloca as standalone statement if kind == hAlloca { // Already handled in Store; standalone alloca is a no-op return; } // Fallback: any other expression used as a statement discard QBE_EmitExpr(qbe, node); } // --------------------------------------------------------------------------- // Function emission // --------------------------------------------------------------------------- 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); QBE_Emit(qbe, retTy); QBE_Emit(qbe, " $"); QBE_Emit(qbe, QBE_EscapeIdent(f.name)); QBE_Emit(qbe, "("); // Parameters var pi: int = 0; var pnames: *HirParam = &f.param0; while pi < f.paramCount { if pi > 0 { QBE_Emit(qbe, ", "); } let pty: String = QbeBackend_TypeWord(pnames[pi].typeKind); QBE_Emit(qbe, pty); QBE_Emit(qbe, " %_p_"); QBE_Emit(qbe, QBE_EscapeIdent(pnames[pi].name)); pi = pi + 1; } QBE_EmitLine(qbe, ") {"); // Entry block QBE_EmitLine(qbe, "@start"); // Allocate local slots for parameters (QBE params are values, not pointers) // Use _p_ prefix to avoid SSA name conflict with the local allocation pi = 0; pnames = &f.param0; while pi < f.paramCount { let pName: String = pnames[pi].name; let pTy: String = QbeBackend_TypeWord(pnames[pi].typeKind); QBE_EmitIndent(qbe); QBE_Emit(qbe, QBE_LocalName(pName)); QBE_Emit(qbe, " =l alloc4 8"); QBE_EmitLine(qbe, ""); QBE_EmitIndent(qbe); QBE_Emit(qbe, "store"); QBE_Emit(qbe, pTy); QBE_Emit(qbe, " %_p_"); QBE_Emit(qbe, QBE_EscapeIdent(pName)); QBE_Emit(qbe, ", "); QBE_Emit(qbe, QBE_LocalName(pName)); QBE_EmitLine(qbe, ""); pi = pi + 1; } // Body QBE_EmitStmt(qbe, f.body); if !QBE_IsTerminator(f.body) { if !String_Eq(retTy, "") { QBE_EmitIndent(qbe); QBE_Emit(qbe, "ret 0"); QBE_EmitLine(qbe, ""); } else { QBE_EmitIndent(qbe); QBE_EmitLine(qbe, "ret"); } } QBE_EmitLine(qbe, "}"); QBE_EmitLine(qbe, ""); } // --------------------------------------------------------------------------- // Extern function declarations // --------------------------------------------------------------------------- func QBE_EmitExtern(qbe: *QbeEmitter, f: *HirFunc) { QBE_Emit(qbe, "function "); let retTy: String = QbeBackend_TypeWord(f.retTypeKind); QBE_Emit(qbe, retTy); QBE_Emit(qbe, " $"); QBE_Emit(qbe, QBE_EscapeIdent(f.name)); QBE_Emit(qbe, "("); var pi: int = 0; var pnames: *HirParam = &f.param0; while pi < f.paramCount { if pi > 0 { QBE_Emit(qbe, ", "); } let pty: String = QbeBackend_TypeWord(pnames[pi].typeKind); QBE_Emit(qbe, pty); pi = pi + 1; } QBE_EmitLine(qbe, ")"); } // --------------------------------------------------------------------------- // Generate complete QBE module // --------------------------------------------------------------------------- func QbeBackend_Generate(mod: *HirModule) -> String { let qbe: *QbeEmitter = bux_alloc(sizeof(QbeEmitter)) as *QbeEmitter; qbe.dataSb = StringBuilder_NewCap(4096); qbe.codeSb = StringBuilder_NewCap(32768); qbe.tempId = 0; qbe.labelId = 0; qbe.strId = 0; qbe.indent = 0; qbe.inBlock = false; qbe.constCount = mod.constCount; qbe.consts = mod.consts; qbe.mod = mod; // Header QBE_EmitLine(qbe, "# Generated by Bux QBE Backend"); QBE_EmitLine(qbe, ""); // Struct types if mod.structCount > 0 { QBE_EmitLine(qbe, "# Struct types"); var si: int = 0; while si < mod.structCount { QBE_EmitStructType(qbe, &mod.structs[si]); si = si + 1; } QBE_EmitLine(qbe, ""); } // QBE resolves extern functions at link time — no declarations needed // Functions QBE_EmitLine(qbe, "# Functions"); var i: int = 0; while i < mod.funcCount { QBE_EmitFunc(qbe, &mod.funcs[i]); i = i + 1; } // C entry point wrapper: main() -> Main() QBE_EmitLine(qbe, "# C entry point"); QBE_EmitLine(qbe, "export function w $main() {"); QBE_EmitLine(qbe, "@start"); QBE_EmitIndent(qbe); QBE_Emit(qbe, "%r =w call $Main()"); QBE_EmitLine(qbe, ""); QBE_EmitIndent(qbe); QBE_EmitLine(qbe, "ret %r"); QBE_EmitLine(qbe, "}"); let result: String = QBE_Build(qbe); return result; } } // module QbeBackend