// 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
