feat: backtick raw/multi-line strings + QBE backend fixes

- Add backtick raw string literals (Go-style ): no escape processing, multi-line
- C backend: handle backtick strings in emitExpr
- Self-hosted lexer: lexScanBacktickString support
- QBE backend: handle backtick strings in data sections
- QBE: remove broken extern declarations (QBE resolves at link time)
- QBE: fix comparison result types (ceql → w, extend to l)
- QBE: fix SSA parameter name clash (_p_ prefix for params)
- QBE: const inlining via HirLower + QBE emitter
- QBE: proper type mapping for bool/char types
- hir_lower: collect dkConst declarations for const propagation
This commit is contained in:
2026-06-02 11:33:29 +03:00
parent d1160ca5d1
commit 70cfa4f721
14 changed files with 2193 additions and 64 deletions
+77 -23
View File
@@ -98,14 +98,14 @@ func Cli_Compile(source: String, sourceName: String) -> String {
PrintInt(hirMod.funcCount);
PrintLine("");
// Phase 5: C code generation
let nimCode: String = NimBackend_Generate(hirMod);
// Phase 5: QBE SSA code generation
let qbeCode: String = QbeBackend_Generate(hirMod);
// Cleanup
Lexer_Free(lex);
Sema_Free(sema);
return nimCode;
return qbeCode;
}
// ---------------------------------------------------------------------------
@@ -123,17 +123,17 @@ func Cli_Build(srcPath: String, outPath: String) -> int {
Print("Compiling ");
PrintLine(srcPath);
let cCode: String = Cli_Compile(source, srcPath);
let ssaCode: String = Cli_Compile(source, srcPath);
if String_Eq(cCode, "") {
if String_Eq(ssaCode, "") {
PrintLine("Compilation failed");
return 1;
}
// Write C output
let ok: bool = WriteFile(outPath, cCode);
// Write SSA output
let ok: bool = WriteFile(outPath, ssaCode);
if ok {
Print(" → Nim code written to ");
Print(" → SSA written to ");
PrintLine(outPath);
return 0;
} else {
@@ -233,6 +233,11 @@ func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
}
func Cli_BuildProject(projectDir: String) -> int {
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") {
outName = "bux_out";
}
let srcDir: String = bux_path_join(projectDir, "src");
if !DirExists(srcDir) {
Print("Error: no src/ directory in ");
@@ -327,30 +332,79 @@ func Cli_BuildProject(projectDir: String) -> int {
PrintLine("Lowering to HIR...");
let hirMod: *HirModule = HirLower_LowerModule(merged, sema);
// Nim code generation
PrintLine("Generating Nim code...");
let nimCode: String = NimBackend_Generate(hirMod);
// QBE SSA code generation
PrintLine("Generating QBE SSA...");
let qbeCode: String = QbeBackend_Generate(hirMod);
// Create build directory
let buildDir: String = bux_path_join(projectDir, "build");
discard bux_mkdir_if_needed(buildDir);
// Write Nim output
let nimFile: String = bux_path_join(buildDir, "main.nim");
if !WriteFile(nimFile, nimCode) {
PrintLine("Error: cannot write main.nim");
// Write SSA output
let ssaFile: String = bux_path_join(buildDir, "main.ssa");
if !WriteFile(ssaFile, qbeCode) {
PrintLine("Error: cannot write main.ssa");
return 1;
}
Print("Nim code written to ");
PrintLine(nimFile);
Print("SSA written to ");
PrintLine(ssaFile);
// Invoke Nim compiler
PrintLine("Invoking Nim compiler...");
let outBin: String = bux_path_join(buildDir, "buxc3");
let rc: int = bux_run_nim(nimFile, outBin);
if rc != 0 {
PrintLine("Error: Nim compilation failed");
// Invoke QBE to generate assembly
PrintLine("Invoking QBE...");
let asmFile: String = bux_path_join(buildDir, "main.s");
var qbeCmd: String = bux_path_join(projectDir, "vendor/qbe/qbe");
// Try relative to project first, then absolute
if !FileExists(qbeCmd) {
// Try from compiler location
qbeCmd = "../vendor/qbe/qbe";
}
if !FileExists(qbeCmd) {
qbeCmd = "vendor/qbe/qbe";
}
// Actually let's use bux_system with a constructed command
var cmdBuf: StringBuilder = StringBuilder_NewCap(256);
StringBuilder_Append(&cmdBuf, qbeCmd);
StringBuilder_Append(&cmdBuf, " -o ");
StringBuilder_Append(&cmdBuf, asmFile);
StringBuilder_Append(&cmdBuf, " ");
StringBuilder_Append(&cmdBuf, ssaFile);
let qbeRc: int = bux_system(StringBuilder_Build(&cmdBuf));
if qbeRc != 0 {
PrintLine("Error: QBE compilation failed");
return 1;
}
// Link with cc
PrintLine("Linking...");
var linkBuf: StringBuilder = StringBuilder_NewCap(512);
StringBuilder_Append(&linkBuf, "cc -o ");
let outBin: String = bux_path_join(buildDir, outName);
StringBuilder_Append(&linkBuf, outBin);
StringBuilder_Append(&linkBuf, " ");
StringBuilder_Append(&linkBuf, asmFile);
StringBuilder_Append(&linkBuf, " ");
// Find runtime.c and io.c
var rtPath: String = bux_path_join(projectDir, "stdlib/runtime.c");
var ioPath: String = bux_path_join(projectDir, "stdlib/io.c");
if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "../stdlib/runtime.c");
}
if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "../stdlib/io.c");
}
if FileExists(rtPath) {
StringBuilder_Append(&linkBuf, rtPath);
StringBuilder_Append(&linkBuf, " ");
}
if FileExists(ioPath) {
StringBuilder_Append(&linkBuf, ioPath);
StringBuilder_Append(&linkBuf, " ");
}
StringBuilder_Append(&linkBuf, "-lm");
let linkRc: int = bux_system(StringBuilder_Build(&linkBuf));
if linkRc != 0 {
PrintLine("Error: Linking failed");
return 1;
}
+14
View File
@@ -412,6 +412,8 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
hm.funcs = ctx.funcs;
hm.structCount = 0;
hm.structs = bux_alloc(64 as uint * sizeof(HirStruct)) as *HirStruct;
hm.constCount = 0;
hm.consts = bux_alloc(512 as uint * sizeof(HirConst)) as *HirConst;
// First pass: count structs (to allocate field arrays later)
// Second pass: actually collect them
@@ -468,6 +470,18 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
}
hm.structCount = hm.structCount + 1;
}
if decl.kind == dkConst && hm.constCount < 512 {
let ci: int = hm.constCount;
hm.consts[ci].name = decl.strValue;
var val: int = 0;
if decl.constValue != null as *Expr {
if decl.constValue.kind == ekLiteral {
val = decl.constValue.intValue;
}
}
hm.consts[ci].value = val;
hm.constCount = hm.constCount + 1;
}
if decl.kind == dkEnum {
hm.enumCount = hm.enumCount + 1;
}
+19
View File
@@ -384,6 +384,20 @@ func lexScanNumber(lex: *Lexer) {
// Strings and chars
// ---------------------------------------------------------------------------
func lexScanBacktickString(lex: *Lexer) {
lexMarkStart(lex);
if lexPeek(lex, 0) == 96 { discard lexAdvance(lex); } // opening backtick
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 96 {
discard lexAdvance(lex);
}
if lexIsAtEnd(lex) {
lexEmitDiag(lex, "unterminated backtick string literal");
} else {
discard lexAdvance(lex); // closing backtick
}
lexEmitToken(lex, tkStringLiteral);
}
func lexScanString(lex: *Lexer) {
lexMarkStart(lex);
if lexPeek(lex, 0) == 34 { discard lexAdvance(lex); } // opening "
@@ -618,6 +632,11 @@ func lexNextToken(lex: *Lexer) {
lexScanString(lex); return;
}
// Backtick raw string
if c == 96 { // backtick
lexScanBacktickString(lex); return;
}
// Char prefixes: c8' c16' c32'
if c == 99 { // 'c'
let d: uint32 = lexPeek(lex, 1);
+39 -12
View File
@@ -78,10 +78,12 @@ func parserExpect(p: *Parser, kind: int, msg: String) -> LexToken {
return parserAdvance(p);
}
let tok: LexToken = parserCurToken(p);
p.diags[p.diagCount] = ParserDiag {
line: tok.line, column: tok.column, message: msg
};
p.diagCount = p.diagCount + 1;
if p.diagCount < 256 {
p.diags[p.diagCount] = ParserDiag {
line: tok.line, column: tok.column, message: msg
};
p.diagCount = p.diagCount + 1;
}
return tok;
}
@@ -103,10 +105,12 @@ func parserExpectIdentOrKeyword(p: *Parser, msg: String) -> LexToken {
if tok.kind == tkIdent || parserIsKeyword(tok.kind) {
return parserAdvance(p);
}
p.diags[p.diagCount] = ParserDiag {
line: tok.line, column: tok.column, message: msg
};
p.diagCount = p.diagCount + 1;
if p.diagCount < 256 {
p.diags[p.diagCount] = ParserDiag {
line: tok.line, column: tok.column, message: msg
};
p.diagCount = p.diagCount + 1;
}
return tok;
}
@@ -479,14 +483,33 @@ func parserIsBinaryOp(kind: int) -> bool {
return false;
}
func parserParseBinary(p: *Parser) -> *Expr {
var left: *Expr = parserParsePostfix(p);
func parserPrecedence(op: int) -> int {
if op == tkAssign || op == tkPlusAssign || op == tkMinusAssign || op == tkStarAssign || op == tkSlashAssign || op == tkPercentAssign || op == tkAmpAssign || op == tkPipeAssign || op == tkCaretAssign || op == tkShlAssign || op == tkShrAssign { return 1; }
if op == tkPipePipe { return 2; }
if op == tkAmpAmp { return 3; }
if op == tkPipe { return 4; }
if op == tkCaret { return 5; }
if op == tkAmp { return 6; }
if op == tkEq || op == tkNe { return 7; }
if op == tkLt || op == tkLe || op == tkGt || op == tkGe { return 8; }
if op == tkShl || op == tkShr { return 9; }
if op == tkPlus || op == tkMinus { return 10; }
if op == tkStar || op == tkSlash || op == tkPercent { return 11; }
if op == tkStarStar { return 12; }
return 0;
}
while parserIsBinaryOp(parserPeek(p, 0)) {
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr {
var left: *Expr = parserParsePostfix(p);
while true {
let op: int = parserPeek(p, 0);
let prec: int = parserPrecedence(op);
if prec < minPrec { break; }
let opTok: LexToken = parserAdvance(p);
let line: uint32 = opTok.line;
let col: uint32 = opTok.column;
let right: *Expr = parserParsePostfix(p);
let nextMinPrec: int = prec + 1;
let right: *Expr = parserParseBinaryPrec(p, nextMinPrec);
let e: *Expr = parserMakeExpr(ekBinary, line, col);
e.intValue = opTok.kind;
e.child1 = left;
@@ -496,6 +519,10 @@ func parserParseBinary(p: *Parser) -> *Expr {
return left;
}
func parserParseBinary(p: *Parser) -> *Expr {
return parserParseBinaryPrec(p, 1);
}
// ---------------------------------------------------------------------------
// Ternary: cond ? then : else
// ---------------------------------------------------------------------------
+942
View File
@@ -0,0 +1,942 @@
// 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);
}
// ---------------------------------------------------------------------------
// 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);
}
let val: String = QBE_EmitExpr(qbe, node.child2);
let ty: String = QbeBackend_TypeWord(node.typeKind);
QBE_EmitIndent(qbe);
QBE_Emit(qbe, "store");
QBE_Emit(qbe, ty);
QBE_Emit(qbe, " ");
QBE_Emit(qbe, val);
QBE_Emit(qbe, ", ");
QBE_EmitLine(qbe, 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_EmitLine(qbe, " =w copy 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, 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 = 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, 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, 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
if kind == hCast {
let src: String = QBE_EmitExpr(qbe, node.child1);
let dstTy: String = QbeBackend_TypeWord(node.typeKind);
let srcTy: String = QbeBackend_TypeWord(node.child1.typeKind);
if String_Eq(dstTy, srcTy) {
return src;
}
let tmp: String = QBE_FreshTemp(qbe);
QBE_EmitIndent(qbe);
QBE_Emit(qbe, tmp);
QBE_Emit(qbe, " =");
QBE_Emit(qbe, dstTy);
// QBE cast instructions: extsw, extuw, extsl, extul, truncl, stosi, etc.
// For simplicity, use copy for now and let QBE handle it
QBE_Emit(qbe, " copy ");
QBE_Emit(qbe, src);
QBE_EmitLine(qbe, "");
return tmp;
}
// 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);
QBE_EmitIndent(qbe);
QBE_Emit(qbe, "store");
QBE_Emit(qbe, varTy);
QBE_Emit(qbe, " ");
QBE_Emit(qbe, val);
QBE_Emit(qbe, ", ");
QBE_EmitLine(qbe, 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);
QBE_EmitIndent(qbe);
QBE_Emit(qbe, "store");
QBE_Emit(qbe, ty);
QBE_Emit(qbe, " ");
QBE_Emit(qbe, val);
QBE_Emit(qbe, ", ");
QBE_EmitLine(qbe, target);
return;
}
// Assign: same as Store without alloca
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);
}
let val: String = QBE_EmitExpr(qbe, node.child2);
let ty: String = QbeBackend_TypeWord(node.typeKind);
QBE_EmitIndent(qbe);
QBE_Emit(qbe, "store");
QBE_Emit(qbe, ty);
QBE_Emit(qbe, " ");
QBE_Emit(qbe, val);
QBE_Emit(qbe, ", ");
QBE_EmitLine(qbe, target);
return;
}
// 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, 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);
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