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);
|
PrintInt(hirMod.funcCount);
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
// Phase 5: C code generation
|
// Phase 5: QBE SSA code generation
|
||||||
let nimCode: String = NimBackend_Generate(hirMod);
|
let qbeCode: String = QbeBackend_Generate(hirMod);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
Lexer_Free(lex);
|
Lexer_Free(lex);
|
||||||
Sema_Free(sema);
|
Sema_Free(sema);
|
||||||
|
|
||||||
return nimCode;
|
return qbeCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -123,17 +123,17 @@ func Cli_Build(srcPath: String, outPath: String) -> int {
|
|||||||
Print("Compiling ");
|
Print("Compiling ");
|
||||||
PrintLine(srcPath);
|
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");
|
PrintLine("Compilation failed");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write C output
|
// Write SSA output
|
||||||
let ok: bool = WriteFile(outPath, cCode);
|
let ok: bool = WriteFile(outPath, ssaCode);
|
||||||
if ok {
|
if ok {
|
||||||
Print(" → Nim code written to ");
|
Print(" → SSA written to ");
|
||||||
PrintLine(outPath);
|
PrintLine(outPath);
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -233,6 +233,11 @@ func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Cli_BuildProject(projectDir: String) -> int {
|
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");
|
let srcDir: String = bux_path_join(projectDir, "src");
|
||||||
if !DirExists(srcDir) {
|
if !DirExists(srcDir) {
|
||||||
Print("Error: no src/ directory in ");
|
Print("Error: no src/ directory in ");
|
||||||
@@ -327,30 +332,79 @@ func Cli_BuildProject(projectDir: String) -> int {
|
|||||||
PrintLine("Lowering to HIR...");
|
PrintLine("Lowering to HIR...");
|
||||||
let hirMod: *HirModule = HirLower_LowerModule(merged, sema);
|
let hirMod: *HirModule = HirLower_LowerModule(merged, sema);
|
||||||
|
|
||||||
// Nim code generation
|
// QBE SSA code generation
|
||||||
PrintLine("Generating Nim code...");
|
PrintLine("Generating QBE SSA...");
|
||||||
let nimCode: String = NimBackend_Generate(hirMod);
|
let qbeCode: String = QbeBackend_Generate(hirMod);
|
||||||
|
|
||||||
// Create build directory
|
// Create build directory
|
||||||
let buildDir: String = bux_path_join(projectDir, "build");
|
let buildDir: String = bux_path_join(projectDir, "build");
|
||||||
discard bux_mkdir_if_needed(buildDir);
|
discard bux_mkdir_if_needed(buildDir);
|
||||||
|
|
||||||
// Write Nim output
|
// Write SSA output
|
||||||
let nimFile: String = bux_path_join(buildDir, "main.nim");
|
let ssaFile: String = bux_path_join(buildDir, "main.ssa");
|
||||||
if !WriteFile(nimFile, nimCode) {
|
if !WriteFile(ssaFile, qbeCode) {
|
||||||
PrintLine("Error: cannot write main.nim");
|
PrintLine("Error: cannot write main.ssa");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Print("Nim code written to ");
|
Print("SSA written to ");
|
||||||
PrintLine(nimFile);
|
PrintLine(ssaFile);
|
||||||
|
|
||||||
// Invoke Nim compiler
|
// Invoke QBE to generate assembly
|
||||||
PrintLine("Invoking Nim compiler...");
|
PrintLine("Invoking QBE...");
|
||||||
let outBin: String = bux_path_join(buildDir, "buxc3");
|
let asmFile: String = bux_path_join(buildDir, "main.s");
|
||||||
let rc: int = bux_run_nim(nimFile, outBin);
|
var qbeCmd: String = bux_path_join(projectDir, "vendor/qbe/qbe");
|
||||||
if rc != 0 {
|
// Try relative to project first, then absolute
|
||||||
PrintLine("Error: Nim compilation failed");
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -412,6 +412,8 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
|
|||||||
hm.funcs = ctx.funcs;
|
hm.funcs = ctx.funcs;
|
||||||
hm.structCount = 0;
|
hm.structCount = 0;
|
||||||
hm.structs = bux_alloc(64 as uint * sizeof(HirStruct)) as *HirStruct;
|
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)
|
// First pass: count structs (to allocate field arrays later)
|
||||||
// Second pass: actually collect them
|
// Second pass: actually collect them
|
||||||
@@ -468,6 +470,18 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
|
|||||||
}
|
}
|
||||||
hm.structCount = hm.structCount + 1;
|
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 {
|
if decl.kind == dkEnum {
|
||||||
hm.enumCount = hm.enumCount + 1;
|
hm.enumCount = hm.enumCount + 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,6 +384,20 @@ func lexScanNumber(lex: *Lexer) {
|
|||||||
// Strings and chars
|
// 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) {
|
func lexScanString(lex: *Lexer) {
|
||||||
lexMarkStart(lex);
|
lexMarkStart(lex);
|
||||||
if lexPeek(lex, 0) == 34 { discard lexAdvance(lex); } // opening "
|
if lexPeek(lex, 0) == 34 { discard lexAdvance(lex); } // opening "
|
||||||
@@ -618,6 +632,11 @@ func lexNextToken(lex: *Lexer) {
|
|||||||
lexScanString(lex); return;
|
lexScanString(lex); return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backtick raw string
|
||||||
|
if c == 96 { // backtick
|
||||||
|
lexScanBacktickString(lex); return;
|
||||||
|
}
|
||||||
|
|
||||||
// Char prefixes: c8' c16' c32'
|
// Char prefixes: c8' c16' c32'
|
||||||
if c == 99 { // 'c'
|
if c == 99 { // 'c'
|
||||||
let d: uint32 = lexPeek(lex, 1);
|
let d: uint32 = lexPeek(lex, 1);
|
||||||
|
|||||||
@@ -78,10 +78,12 @@ func parserExpect(p: *Parser, kind: int, msg: String) -> LexToken {
|
|||||||
return parserAdvance(p);
|
return parserAdvance(p);
|
||||||
}
|
}
|
||||||
let tok: LexToken = parserCurToken(p);
|
let tok: LexToken = parserCurToken(p);
|
||||||
|
if p.diagCount < 256 {
|
||||||
p.diags[p.diagCount] = ParserDiag {
|
p.diags[p.diagCount] = ParserDiag {
|
||||||
line: tok.line, column: tok.column, message: msg
|
line: tok.line, column: tok.column, message: msg
|
||||||
};
|
};
|
||||||
p.diagCount = p.diagCount + 1;
|
p.diagCount = p.diagCount + 1;
|
||||||
|
}
|
||||||
return tok;
|
return tok;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,10 +105,12 @@ func parserExpectIdentOrKeyword(p: *Parser, msg: String) -> LexToken {
|
|||||||
if tok.kind == tkIdent || parserIsKeyword(tok.kind) {
|
if tok.kind == tkIdent || parserIsKeyword(tok.kind) {
|
||||||
return parserAdvance(p);
|
return parserAdvance(p);
|
||||||
}
|
}
|
||||||
|
if p.diagCount < 256 {
|
||||||
p.diags[p.diagCount] = ParserDiag {
|
p.diags[p.diagCount] = ParserDiag {
|
||||||
line: tok.line, column: tok.column, message: msg
|
line: tok.line, column: tok.column, message: msg
|
||||||
};
|
};
|
||||||
p.diagCount = p.diagCount + 1;
|
p.diagCount = p.diagCount + 1;
|
||||||
|
}
|
||||||
return tok;
|
return tok;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,14 +483,33 @@ func parserIsBinaryOp(kind: int) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
func parserParseBinary(p: *Parser) -> *Expr {
|
func parserPrecedence(op: int) -> int {
|
||||||
var left: *Expr = parserParsePostfix(p);
|
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 opTok: LexToken = parserAdvance(p);
|
||||||
let line: uint32 = opTok.line;
|
let line: uint32 = opTok.line;
|
||||||
let col: uint32 = opTok.column;
|
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);
|
let e: *Expr = parserMakeExpr(ekBinary, line, col);
|
||||||
e.intValue = opTok.kind;
|
e.intValue = opTok.kind;
|
||||||
e.child1 = left;
|
e.child1 = left;
|
||||||
@@ -496,6 +519,10 @@ func parserParseBinary(p: *Parser) -> *Expr {
|
|||||||
return left;
|
return left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parserParseBinary(p: *Parser) -> *Expr {
|
||||||
|
return parserParseBinaryPrec(p, 1);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Ternary: cond ? then : else
|
// 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
|
||||||
+4
-1
@@ -160,8 +160,11 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
|||||||
else: return "false"
|
else: return "false"
|
||||||
of tkStringLiteral:
|
of tkStringLiteral:
|
||||||
var text = node.litToken.text
|
var text = node.litToken.text
|
||||||
|
# Backtick raw string: strip backticks, escape content for C
|
||||||
|
if text.len >= 2 and text[0] == '`' and text[text.len-1] == '`':
|
||||||
|
text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
|
||||||
# If text has no surrounding quotes, it's from constFoldConstDecl (already unescaped)
|
# If text has no surrounding quotes, it's from constFoldConstDecl (already unescaped)
|
||||||
if text.len >= 2 and text[0] == '"' and text[text.len-1] == '"':
|
elif text.len >= 2 and text[0] == '"' and text[text.len-1] == '"':
|
||||||
# Strip c8" c16" c32" prefixes — in C they are just regular string literals
|
# Strip c8" c16" c32" prefixes — in C they are just regular string literals
|
||||||
if text.startsWith("c32\""):
|
if text.startsWith("c32\""):
|
||||||
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
|
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
|
||||||
|
|||||||
@@ -277,6 +277,18 @@ proc scanString(lex: var Lexer, startLoc: SourceLocation, prefixLen: int): Token
|
|||||||
discard lex.advance() # closing "
|
discard lex.advance() # closing "
|
||||||
result = lex.makeToken(tkStringLiteral, startLoc, startPos)
|
result = lex.makeToken(tkStringLiteral, startLoc, startPos)
|
||||||
|
|
||||||
|
proc scanBacktickString(lex: var Lexer, startLoc: SourceLocation): Token =
|
||||||
|
## Scan a backtick-delimited raw string literal: content is literal,
|
||||||
|
## no escape processing, newlines are preserved.
|
||||||
|
let startPos = lex.pos - 1 # include the opening backtick
|
||||||
|
while not lex.isAtEnd() and lex.peek() != '`':
|
||||||
|
discard lex.advance()
|
||||||
|
if lex.isAtEnd():
|
||||||
|
lex.emitError(startLoc, "unterminated backtick string literal")
|
||||||
|
else:
|
||||||
|
discard lex.advance() # closing backtick
|
||||||
|
result = lex.makeToken(tkStringLiteral, startLoc, startPos)
|
||||||
|
|
||||||
proc scanChar(lex: var Lexer, startLoc: SourceLocation, prefixLen: int): Token =
|
proc scanChar(lex: var Lexer, startLoc: SourceLocation, prefixLen: int): Token =
|
||||||
let startPos = lex.pos - prefixLen
|
let startPos = lex.pos - prefixLen
|
||||||
if lex.peek() == '\'':
|
if lex.peek() == '\'':
|
||||||
@@ -523,6 +535,11 @@ proc nextToken(lex: var Lexer): Token =
|
|||||||
if c == '"':
|
if c == '"':
|
||||||
return lex.scanString(startLoc, 0)
|
return lex.scanString(startLoc, 0)
|
||||||
|
|
||||||
|
# Backtick-delimited raw string: `...`
|
||||||
|
if c == '`':
|
||||||
|
discard lex.advance()
|
||||||
|
return lex.scanBacktickString(startLoc)
|
||||||
|
|
||||||
# Char prefixes: c8' c16' c32' — must come before ident check
|
# Char prefixes: c8' c16' c32' — must come before ident check
|
||||||
if c == 'c' and lex.peek(1) in {'8', '1', '3'}:
|
if c == 'c' and lex.peek(1) in {'8', '1', '3'}:
|
||||||
let d = lex.peek(1)
|
let d = lex.peek(1)
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ type
|
|||||||
##Literals
|
##Literals
|
||||||
tkIntLiteral # 42 0xFF 0b1010 0o77
|
tkIntLiteral # 42 0xFF 0b1010 0o77
|
||||||
tkFloatLiteral # 3.14 1.0e-9
|
tkFloatLiteral # 3.14 1.0e-9
|
||||||
tkStringLiteral # "hello" c8"hello" c16"hello" c32"hello"
|
tkStringLiteral # "hello" c8"hello" c16"hello" c32"hello" `raw multi-line`
|
||||||
tkCharLiteral # 'A' c8'A' c16'A' c32'A'
|
tkCharLiteral # 'A' c8'A' c16'A' c32'A'
|
||||||
tkBoolLiteral # true false
|
tkBoolLiteral # true false
|
||||||
|
|
||||||
|
|||||||
+77
-23
@@ -98,14 +98,14 @@ func Cli_Compile(source: String, sourceName: String) -> String {
|
|||||||
PrintInt(hirMod.funcCount);
|
PrintInt(hirMod.funcCount);
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
// Phase 5: C code generation
|
// Phase 5: QBE SSA code generation
|
||||||
let nimCode: String = NimBackend_Generate(hirMod);
|
let qbeCode: String = QbeBackend_Generate(hirMod);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
Lexer_Free(lex);
|
Lexer_Free(lex);
|
||||||
Sema_Free(sema);
|
Sema_Free(sema);
|
||||||
|
|
||||||
return nimCode;
|
return qbeCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -123,17 +123,17 @@ func Cli_Build(srcPath: String, outPath: String) -> int {
|
|||||||
Print("Compiling ");
|
Print("Compiling ");
|
||||||
PrintLine(srcPath);
|
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");
|
PrintLine("Compilation failed");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write C output
|
// Write SSA output
|
||||||
let ok: bool = WriteFile(outPath, cCode);
|
let ok: bool = WriteFile(outPath, ssaCode);
|
||||||
if ok {
|
if ok {
|
||||||
Print(" → Nim code written to ");
|
Print(" → SSA written to ");
|
||||||
PrintLine(outPath);
|
PrintLine(outPath);
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -233,6 +233,11 @@ func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Cli_BuildProject(projectDir: String) -> int {
|
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");
|
let srcDir: String = bux_path_join(projectDir, "src");
|
||||||
if !DirExists(srcDir) {
|
if !DirExists(srcDir) {
|
||||||
Print("Error: no src/ directory in ");
|
Print("Error: no src/ directory in ");
|
||||||
@@ -327,30 +332,79 @@ func Cli_BuildProject(projectDir: String) -> int {
|
|||||||
PrintLine("Lowering to HIR...");
|
PrintLine("Lowering to HIR...");
|
||||||
let hirMod: *HirModule = HirLower_LowerModule(merged, sema);
|
let hirMod: *HirModule = HirLower_LowerModule(merged, sema);
|
||||||
|
|
||||||
// Nim code generation
|
// QBE SSA code generation
|
||||||
PrintLine("Generating Nim code...");
|
PrintLine("Generating QBE SSA...");
|
||||||
let nimCode: String = NimBackend_Generate(hirMod);
|
let qbeCode: String = QbeBackend_Generate(hirMod);
|
||||||
|
|
||||||
// Create build directory
|
// Create build directory
|
||||||
let buildDir: String = bux_path_join(projectDir, "build");
|
let buildDir: String = bux_path_join(projectDir, "build");
|
||||||
discard bux_mkdir_if_needed(buildDir);
|
discard bux_mkdir_if_needed(buildDir);
|
||||||
|
|
||||||
// Write Nim output
|
// Write SSA output
|
||||||
let nimFile: String = bux_path_join(buildDir, "main.nim");
|
let ssaFile: String = bux_path_join(buildDir, "main.ssa");
|
||||||
if !WriteFile(nimFile, nimCode) {
|
if !WriteFile(ssaFile, qbeCode) {
|
||||||
PrintLine("Error: cannot write main.nim");
|
PrintLine("Error: cannot write main.ssa");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Print("Nim code written to ");
|
Print("SSA written to ");
|
||||||
PrintLine(nimFile);
|
PrintLine(ssaFile);
|
||||||
|
|
||||||
// Invoke Nim compiler
|
// Invoke QBE to generate assembly
|
||||||
PrintLine("Invoking Nim compiler...");
|
PrintLine("Invoking QBE...");
|
||||||
let outBin: String = bux_path_join(buildDir, "buxc3");
|
let asmFile: String = bux_path_join(buildDir, "main.s");
|
||||||
let rc: int = bux_run_nim(nimFile, outBin);
|
var qbeCmd: String = bux_path_join(projectDir, "vendor/qbe/qbe");
|
||||||
if rc != 0 {
|
// Try relative to project first, then absolute
|
||||||
PrintLine("Error: Nim compilation failed");
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -412,6 +412,8 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
|
|||||||
hm.funcs = ctx.funcs;
|
hm.funcs = ctx.funcs;
|
||||||
hm.structCount = 0;
|
hm.structCount = 0;
|
||||||
hm.structs = bux_alloc(64 as uint * sizeof(HirStruct)) as *HirStruct;
|
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)
|
// First pass: count structs (to allocate field arrays later)
|
||||||
// Second pass: actually collect them
|
// Second pass: actually collect them
|
||||||
@@ -468,6 +470,18 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
|
|||||||
}
|
}
|
||||||
hm.structCount = hm.structCount + 1;
|
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 {
|
if decl.kind == dkEnum {
|
||||||
hm.enumCount = hm.enumCount + 1;
|
hm.enumCount = hm.enumCount + 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,6 +384,20 @@ func lexScanNumber(lex: *Lexer) {
|
|||||||
// Strings and chars
|
// 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) {
|
func lexScanString(lex: *Lexer) {
|
||||||
lexMarkStart(lex);
|
lexMarkStart(lex);
|
||||||
if lexPeek(lex, 0) == 34 { discard lexAdvance(lex); } // opening "
|
if lexPeek(lex, 0) == 34 { discard lexAdvance(lex); } // opening "
|
||||||
@@ -618,6 +632,11 @@ func lexNextToken(lex: *Lexer) {
|
|||||||
lexScanString(lex); return;
|
lexScanString(lex); return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backtick raw string
|
||||||
|
if c == 96 { // backtick
|
||||||
|
lexScanBacktickString(lex); return;
|
||||||
|
}
|
||||||
|
|
||||||
// Char prefixes: c8' c16' c32'
|
// Char prefixes: c8' c16' c32'
|
||||||
if c == 99 { // 'c'
|
if c == 99 { // 'c'
|
||||||
let d: uint32 = lexPeek(lex, 1);
|
let d: uint32 = lexPeek(lex, 1);
|
||||||
|
|||||||
+27
-4
@@ -483,14 +483,33 @@ func parserIsBinaryOp(kind: int) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
func parserParseBinary(p: *Parser) -> *Expr {
|
func parserPrecedence(op: int) -> int {
|
||||||
var left: *Expr = parserParsePostfix(p);
|
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 opTok: LexToken = parserAdvance(p);
|
||||||
let line: uint32 = opTok.line;
|
let line: uint32 = opTok.line;
|
||||||
let col: uint32 = opTok.column;
|
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);
|
let e: *Expr = parserMakeExpr(ekBinary, line, col);
|
||||||
e.intValue = opTok.kind;
|
e.intValue = opTok.kind;
|
||||||
e.child1 = left;
|
e.child1 = left;
|
||||||
@@ -500,6 +519,10 @@ func parserParseBinary(p: *Parser) -> *Expr {
|
|||||||
return left;
|
return left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parserParseBinary(p: *Parser) -> *Expr {
|
||||||
|
return parserParseBinaryPrec(p, 1);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Ternary: cond ? then : else
|
// 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
|
||||||
@@ -479,6 +479,7 @@ char* bux_read_file(const char* path) {
|
|||||||
long size = ftell(f);
|
long size = ftell(f);
|
||||||
fseek(f, 0, SEEK_SET);
|
fseek(f, 0, SEEK_SET);
|
||||||
char* buf = (char*)bux_alloc((size_t)size + 1);
|
char* buf = (char*)bux_alloc((size_t)size + 1);
|
||||||
|
if (!buf) { fclose(f); return NULL; }
|
||||||
size_t read = fread(buf, 1, (size_t)size, f);
|
size_t read = fread(buf, 1, (size_t)size, f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
buf[read] = '\0';
|
buf[read] = '\0';
|
||||||
|
|||||||
Reference in New Issue
Block a user