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:
+77
-23
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user