Files
bux-lang/src_bux/qbe_backend.bux
T

936 lines
30 KiB
Plaintext

// 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;
}
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 };
}
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
// Bare identifier: add % prefix
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 == hBlock {
var last: *HirNode = node.child1;
if last == null as *HirNode { return false; }
while last.child3 != null as *HirNode {
last = last.child3;
}
return QBE_IsTerminator(last);
}
return false;
}
// ---------------------------------------------------------------------------
// 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;
}
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_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 {
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 = QbeBackend_TypeWord(node.typeKind);
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 = QbeBackend_TypeWord(node.typeKind);
// Comparison operators
// QBE comparison ops: ceqw/ceql, cnew/cnel, csltw/csltl, cslew/cslel, csgtw/csgtl, csgew/csgel
var cmpOp: String = "";
var cmpTy: String = ty;
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, ty);
QBE_Emit(qbe, " ");
QBE_Emit(qbe, left);
QBE_Emit(qbe, ", ");
QBE_Emit(qbe, 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, left);
QBE_Emit(qbe, ", ");
QBE_Emit(qbe, 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, 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, " =l 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, " =l 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, 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, " =l 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, " =l 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 = QbeBackend_TypeWord(node.typeKind);
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, operand);
QBE_Emit(qbe, ", 1");
} else {
QBE_Emit(qbe, " sub 0, ");
QBE_Emit(qbe, 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);
let retTy: String = QbeBackend_TypeWord(node.typeKind);
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 = QbeBackend_TypeWord(node.child1.typeKind);
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);
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, ptr);
QBE_EmitLine(qbe, "");
return tmp;
}
// Field pointer: &obj.field
if kind == hFieldPtr {
let base: String = QBE_EmitExpr(qbe, node.child1);
let tmp: String = QBE_FreshTemp(qbe);
// add base, offset — need to know offset; for now assume 0 for first field
QBE_EmitIndent(qbe);
QBE_Emit(qbe, tmp);
QBE_Emit(qbe, " =l add ");
QBE_Emit(qbe, QBE_EnsureLocal(base));
QBE_Emit(qbe, ", 0");
QBE_EmitLine(qbe, "");
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;
// 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;
}
// 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);
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, condVal);
QBE_Emit(qbe, ", ");
QBE_Emit(qbe, lThen);
QBE_Emit(qbe, ", ");
QBE_EmitLine(qbe, lElse);
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);
}
QBE_EmitLine(qbe, lElse);
QBE_EmitStmt(qbe, node.child3);
if !QBE_IsTerminator(node.child3) {
QBE_EmitIndent(qbe);
QBE_Emit(qbe, "jmp ");
QBE_EmitLine(qbe, lEnd);
}
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, 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);
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_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);
// Implicit ret 0 for functions without explicit return
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;
// 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