936 lines
30 KiB
Plaintext
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 "b"; }
|
|
if kind == tyChar16 { return "h"; }
|
|
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, 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
|