Files
bux-lang/src/c_backend.bux
T
dimgigov 94cc94f638 feat(selfhost): field-move skip Drop, #line maps, Array field mono
Bring selfhost C backend closer to bootstrap ownership and debug quality.

- Mark locals moved into struct fields / returns (no double Drop)
- Mangle Array/Set/Map/Channel field types so user structs fully emit
- Emit #line N from HIR line; BUX_DEBUG_FILE / BUX_NO_LINE controls
2026-07-19 23:07:01 +03:00

1760 lines
76 KiB
Plaintext

// c_backend.bux — C transpiler backend (ported from c_backend.nim)
// Generates C code from the HIR.
module CBackend {
extern func bux_getenv(name: String) -> String;
// ---------------------------------------------------------------------------
// Type → C type name
// ---------------------------------------------------------------------------
func CBackend_TypeToC(kind: int) -> String {
let cName: String = Type_ToCName(kind);
if !String_Eq(cName, "") { return cName; }
if kind == tyNamed { return "int"; }
return "int";
}
// Emit a C parameter/variable declaration, embedding the name inside function-pointer syntax.
func CBE_CParamDecl(typeStr: String, name: String) -> String {
if String_Contains(typeStr, "(*)") {
let replacement: String = String_Concat("(*", String_Concat(name, ")"));
return String_Replace(typeStr, "(*)", replacement);
}
return String_Concat(typeStr, String_Concat(" ", name));
}
func CBackend_OpToC(op: int) -> String {
if op == tkPlus { return "+"; }
if op == tkMinus { return "-"; }
if op == tkStar { return "*"; }
if op == tkSlash { return "/"; }
if op == tkPercent { return "%"; }
if op == tkEq { return "=="; }
if op == tkNe { return "!="; }
if op == tkLt { return "<"; }
if op == tkLe { return "<="; }
if op == tkGt { return ">"; }
if op == tkGe { return ">="; }
if op == tkAmpAmp { return "&&"; }
if op == tkPipePipe { return "||"; }
if op == tkBang { return "!"; }
if op == tkTilde { return "~"; }
if op == tkAmp { return "&"; }
if op == tkPipe { return "|"; }
if op == tkCaret { return "^"; }
if op == tkShl { return "<<"; }
if op == tkShr { return ">>"; }
if op == tkAssign { return "="; }
return "?";
}
// ---------------------------------------------------------------------------
// StringBuilder-based C emitter
// ---------------------------------------------------------------------------
struct CEmitter {
sb: StringBuilder,
indent: int,
mod: *HirModule,
checkedFunc: bool,
deferCount: int,
defer0: *HirNode,
defer1: *HirNode,
defer2: *HirNode,
defer3: *HirNode,
defer4: *HirNode,
defer5: *HirNode,
defer6: *HirNode,
defer7: *HirNode,
movedCount: int,
movedName0: String,
movedName1: String,
movedName2: String,
movedName3: String,
movedName4: String,
movedName5: String,
movedName6: String,
movedName7: String,
tmpCounter: int,
currentRetType: String,
// #line debug maps (E.4 selfhost parity)
lastDebugLine: int,
emitDebugLines: bool,
currentFile: String,
}
/// Normalize monomorphized type spellings for C (Array<int> → Array_int).
func CBE_NormalizeTypeName(name: String) -> String {
if String_Eq(name, "") { return name; }
if String_StartsWith(name, "Array<") && String_EndsWith(name, ">") {
let n: uint = String_Len(name);
// "Array<" + inner + ">"
let inner: String = String_Slice(name, 6, n - 7);
return String_Concat("Array_", inner);
}
if String_StartsWith(name, "Map<") && String_EndsWith(name, ">") {
// Map<int,String> → Map_int_String (best-effort)
let n: uint = String_Len(name);
let inner: String = String_Slice(name, 4, n - 5);
return String_Concat("Map_", String_ReplaceAll(inner, ",", "_"));
}
if String_StartsWith(name, "Set<") && String_EndsWith(name, ">") {
let n: uint = String_Len(name);
let inner: String = String_Slice(name, 4, n - 5);
return String_Concat("Set_", inner);
}
if String_StartsWith(name, "Channel<") && String_EndsWith(name, ">") {
let n: uint = String_Len(name);
let inner: String = String_Slice(name, 8, n - 9);
return String_Concat("Channel_", inner);
}
return name;
}
/// Mark droppable locals moved by-value (struct fields / nested).
func CBE_MarkMovedFromNode(cbe: *CEmitter, node: *HirNode) {
if node == null as *HirNode { return; }
if node.kind == hVar {
CBE_AddMoved(cbe, node.strValue);
return;
}
if node.kind == hStructInit {
var field: *HirNode = node.child1;
while field != null as *HirNode {
CBE_MarkMovedFromNode(cbe, field.child1);
field = field.child3;
}
return;
}
if node.kind == hTupleInit {
// child1/child2 + linked extras if any
CBE_MarkMovedFromNode(cbe, node.child1);
CBE_MarkMovedFromNode(cbe, node.child2);
return;
}
}
func CBE_EmitDebugLine(cbe: *CEmitter, node: *HirNode) {
if !cbe.emitDebugLines { return; }
if node == null as *HirNode { return; }
if node.line == 0 { return; }
let ln: int = node.line as int;
if ln == cbe.lastDebugLine { return; }
cbe.lastDebugLine = ln;
// #line must start at column 0
StringBuilder_Append(&cbe.sb, "#line ");
StringBuilder_AppendInt(&cbe.sb, ln as int64);
if !String_Eq(cbe.currentFile, "") {
StringBuilder_Append(&cbe.sb, " \"");
StringBuilder_Append(&cbe.sb, cbe.currentFile);
StringBuilder_Append(&cbe.sb, "\"");
}
StringBuilder_Append(&cbe.sb, "\n");
}
func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
if cbe.deferCount == 0 { cbe.defer0 = node; }
if cbe.deferCount == 1 { cbe.defer1 = node; }
if cbe.deferCount == 2 { cbe.defer2 = node; }
if cbe.deferCount == 3 { cbe.defer3 = node; }
if cbe.deferCount == 4 { cbe.defer4 = node; }
if cbe.deferCount == 5 { cbe.defer5 = node; }
if cbe.deferCount == 6 { cbe.defer6 = node; }
if cbe.deferCount == 7 { cbe.defer7 = node; }
cbe.deferCount = cbe.deferCount + 1;
}
func CBE_AddMoved(cbe: *CEmitter, name: String) {
if cbe.movedCount >= 8 { return; }
if cbe.movedCount == 0 { cbe.movedName0 = name; }
else if cbe.movedCount == 1 { cbe.movedName1 = name; }
else if cbe.movedCount == 2 { cbe.movedName2 = name; }
else if cbe.movedCount == 3 { cbe.movedName3 = name; }
else if cbe.movedCount == 4 { cbe.movedName4 = name; }
else if cbe.movedCount == 5 { cbe.movedName5 = name; }
else if cbe.movedCount == 6 { cbe.movedName6 = name; }
else if cbe.movedCount == 7 { cbe.movedName7 = name; }
cbe.movedCount = cbe.movedCount + 1;
}
func CBE_IsMoved(cbe: *CEmitter, name: String) -> bool {
if cbe.movedCount > 0 && String_Eq(cbe.movedName0, name) { return true; }
if cbe.movedCount > 1 && String_Eq(cbe.movedName1, name) { return true; }
if cbe.movedCount > 2 && String_Eq(cbe.movedName2, name) { return true; }
if cbe.movedCount > 3 && String_Eq(cbe.movedName3, name) { return true; }
if cbe.movedCount > 4 && String_Eq(cbe.movedName4, name) { return true; }
if cbe.movedCount > 5 && String_Eq(cbe.movedName5, name) { return true; }
if cbe.movedCount > 6 && String_Eq(cbe.movedName6, name) { return true; }
if cbe.movedCount > 7 && String_Eq(cbe.movedName7, name) { return true; }
return false;
}
func CBE_RemoveMoved(cbe: *CEmitter, name: String) {
var found: int = -1;
if cbe.movedCount > 0 && String_Eq(cbe.movedName0, name) { found = 0; }
else if cbe.movedCount > 1 && String_Eq(cbe.movedName1, name) { found = 1; }
else if cbe.movedCount > 2 && String_Eq(cbe.movedName2, name) { found = 2; }
else if cbe.movedCount > 3 && String_Eq(cbe.movedName3, name) { found = 3; }
else if cbe.movedCount > 4 && String_Eq(cbe.movedName4, name) { found = 4; }
else if cbe.movedCount > 5 && String_Eq(cbe.movedName5, name) { found = 5; }
else if cbe.movedCount > 6 && String_Eq(cbe.movedName6, name) { found = 6; }
else if cbe.movedCount > 7 && String_Eq(cbe.movedName7, name) { found = 7; }
if found >= 0 {
var i: int = found;
while i < cbe.movedCount - 1 {
if i == 0 { cbe.movedName0 = cbe.movedName1; }
else if i == 1 { cbe.movedName1 = cbe.movedName2; }
else if i == 2 { cbe.movedName2 = cbe.movedName3; }
else if i == 3 { cbe.movedName3 = cbe.movedName4; }
else if i == 4 { cbe.movedName4 = cbe.movedName5; }
else if i == 5 { cbe.movedName5 = cbe.movedName6; }
else if i == 6 { cbe.movedName6 = cbe.movedName7; }
i = i + 1;
}
cbe.movedCount = cbe.movedCount - 1;
}
}
func CBE_GetAutoDropVarName(node: *HirNode) -> String {
if node == null as *HirNode { return ""; }
// node is the inner expression stored by CBE_PushDefer (hCall for auto-drop)
let callNode: *HirNode = node;
if callNode.kind != hCall { return ""; }
let addrNode: *HirNode = callNode.child1;
if addrNode == null as *HirNode { return ""; }
if addrNode.kind != hUnary { return ""; }
if addrNode.intValue != tkAmp { return ""; }
let varNode: *HirNode = addrNode.child1;
if varNode == null as *HirNode { return ""; }
if varNode.kind != hVar { return ""; }
return varNode.strValue;
}
// Emit one defer slot (shared by full-stack and scope-pop emitters).
func CBE_EmitOneDefer(cbe: *CEmitter, i: int) {
var dn: *HirNode = null as *HirNode;
if i == 0 { dn = cbe.defer0; }
if i == 1 { dn = cbe.defer1; }
if i == 2 { dn = cbe.defer2; }
if i == 3 { dn = cbe.defer3; }
if i == 4 { dn = cbe.defer4; }
if i == 5 { dn = cbe.defer5; }
if i == 6 { dn = cbe.defer6; }
if i == 7 { dn = cbe.defer7; }
// Skip auto-drop for moved variables
let deferVarName: String = CBE_GetAutoDropVarName(dn);
if !String_Eq(deferVarName, "") && CBE_IsMoved(cbe, deferVarName) {
return;
}
StringBuilder_Append(&cbe.sb, "\n");
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
CBE_EmitExpr(cbe, dn);
StringBuilder_Append(&cbe.sb, ";");
}
// Emit all active defers (LIFO) without clearing the stack.
// Must NOT clear: multiple return paths each need the full defer list.
// (Clearing caused Early(flag) { if (0) return; return 1 } to drop only on first exit.)
// Stack is reset at the start of each function emission.
func CBE_EmitDefers(cbe: *CEmitter) -> int {
if cbe.deferCount == 0 { return 0; }
var i: int = cbe.deferCount - 1;
while i >= 0 {
CBE_EmitOneDefer(cbe, i);
i = i - 1;
}
return 1;
}
// Emit branch/loop-local defers (indices fromIdx..count-1) then pop them.
// Outer defers stay live so sibling branches and later returns still drop correctly.
func CBE_EmitAndPopDefersFrom(cbe: *CEmitter, fromIdx: int) -> int {
if cbe.deferCount <= fromIdx { return 0; }
var i: int = cbe.deferCount - 1;
while i >= fromIdx {
CBE_EmitOneDefer(cbe, i);
i = i - 1;
}
cbe.deferCount = fromIdx;
return 1;
}
func CBE_Emit(cbe: *CEmitter, text: String) {
var i: int = 0;
while i < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
i = i + 1;
}
StringBuilder_Append(&cbe.sb, text);
}
// ---------------------------------------------------------------------------
// Emit HIR node
// ---------------------------------------------------------------------------
func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
if node == null as *HirNode { return; }
let kind: int = node.kind;
// Literal
if kind == hLit {
if node.intValue == tkNull {
StringBuilder_Append(&cbe.sb, "0");
} else {
let s: String = node.strValue;
let slen: int = String_Len(s) as int;
var start: int = 0;
var end: int = slen;
var isQuoted: bool = false;
var isBacktick: bool = false;
// If the literal is wrapped in quotes (from lexer), preserve outer quotes
// and only escape the inner content.
if slen >= 2 && s[0] == 34 as char8 && s[slen - 1] == 34 as char8 {
isQuoted = true;
start = 1;
end = slen - 1;
}
// Backtick raw string: strip backticks, escape inner content for C
if slen >= 2 && s[0] == 96 as char8 && s[slen - 1] == 96 as char8 {
isBacktick = true;
start = 1;
end = slen - 1;
}
if isQuoted || isBacktick {
StringBuilder_Append(&cbe.sb, "\"");
}
var i: int = start;
while i < end {
let c: int = s[i] as int;
if c == 34 {
StringBuilder_Append(&cbe.sb, "\\\"");
} else if c == 92 {
StringBuilder_Append(&cbe.sb, "\\\\");
} else if c == 10 {
StringBuilder_Append(&cbe.sb, "\\n");
} else if c == 9 {
StringBuilder_Append(&cbe.sb, "\\t");
} else if c == 13 {
StringBuilder_Append(&cbe.sb, "\\r");
} else {
StringBuilder_AppendChar(&cbe.sb, c as char8);
}
i = i + 1;
}
if isQuoted || isBacktick {
StringBuilder_Append(&cbe.sb, "\"");
}
}
return;
}
// Variable
if kind == hVar {
if node.intValue != 0 {
StringBuilder_Append(&cbe.sb, "/* hVar tk=");
StringBuilder_AppendInt(&cbe.sb, node.intValue);
StringBuilder_Append(&cbe.sb, " */");
}
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Binary
if kind == hBinary {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue));
StringBuilder_Append(&cbe.sb, " ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Unary
if kind == hUnary {
StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue));
CBE_EmitExpr(cbe, node.child1);
return;
}
// Call
if kind == hCall {
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "(");
var needsComma: bool = false;
if node.child1 != null as *HirNode {
CBE_EmitExpr(cbe, node.child1);
needsComma = true;
}
if node.child2 != null as *HirNode {
if needsComma {
StringBuilder_Append(&cbe.sb, ", ");
}
CBE_EmitExpr(cbe, node.child2);
needsComma = true;
}
// Emit extra args from linked list
var ai: int = 0;
var curExtra: *HirArgList = node.extraData as *HirArgList;
while ai < node.extraCount {
if needsComma {
StringBuilder_Append(&cbe.sb, ", ");
}
CBE_EmitExpr(cbe, curExtra.node);
needsComma = true;
curExtra = curExtra.next;
ai = ai + 1;
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Indirect call through fat function pointer: f.code(f.env, args...)
if kind == hCallIndirect {
StringBuilder_Append(&cbe.sb, "(");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ".code)(");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ".env");
if node.child2 != null as *HirNode {
StringBuilder_Append(&cbe.sb, ", ");
CBE_EmitExpr(cbe, node.child2);
}
if node.child3 != null as *HirNode {
StringBuilder_Append(&cbe.sb, ", ");
CBE_EmitExpr(cbe, node.child3);
}
// Emit extra args from linked list
var ai: int = 0;
var curExtra: *HirArgList = node.extraData as *HirArgList;
while ai < node.extraCount {
StringBuilder_Append(&cbe.sb, ", ");
CBE_EmitExpr(cbe, curExtra.node);
curExtra = curExtra.next;
ai = ai + 1;
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// spawn Callee(args)
if kind == hSpawn {
if node.boolValue {
// Async spawn: callee is an async function
StringBuilder_Append(&cbe.sb, "bux_async_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, ")");
} else {
// Green-thread spawn: callee is a regular task function
StringBuilder_Append(&cbe.sb, "bux_task_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue);
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, ", (void*)");
CBE_EmitExpr(cbe, node.child1);
} else {
StringBuilder_Append(&cbe.sb, ", NULL");
}
StringBuilder_Append(&cbe.sb, ")");
}
return;
}
// await
if kind == hAwait {
StringBuilder_Append(&cbe.sb, "bux_async_await(");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Defer
if kind == hDefer {
CBE_PushDefer(cbe, node.child1);
return;
}
// Return — evaluate value first, then drop live locals, then return.
// (Emitting Drop before the value used to use-after-drop on `return a.id`.)
if kind == hReturn {
CBE_EmitDebugLine(cbe, node);
// Track moved variables via return / field-move into returned struct
if node.child1 != null as *HirNode {
CBE_MarkMovedFromNode(cbe, node.child1);
}
if node.child1 != null as *HirNode && cbe.deferCount > 0 {
// Materialize into a temp so Drop cannot clobber the returned value.
// Prefer the enclosing function return type (field-access HIR often
// carries the base struct typeName, which is wrong for `return a.id`).
cbe.tmpCounter = cbe.tmpCounter + 1;
let tmpName: String = String_Concat("__retdrop_", String_FromInt(cbe.tmpCounter));
var retCt: String = "int";
if cbe.currentRetType != null as String && !String_Eq(cbe.currentRetType, "") && !String_Eq(cbe.currentRetType, "void") {
retCt = cbe.currentRetType;
} else if node.child1.typeKind != 0 {
retCt = CBackend_TypeToC(node.child1.typeKind);
}
StringBuilder_Append(&cbe.sb, CBE_CParamDecl(retCt, tmpName));
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ";");
discard CBE_EmitDefers(cbe);
StringBuilder_Append(&cbe.sb, "\n");
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "return ");
StringBuilder_Append(&cbe.sb, tmpName);
return;
}
let hadDefers: int = CBE_EmitDefers(cbe);
if hadDefers != 0 {
StringBuilder_Append(&cbe.sb, "\n");
var sp2: int = 0;
while sp2 < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp2 = sp2 + 1;
}
}
StringBuilder_Append(&cbe.sb, "return");
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, " ");
CBE_EmitExpr(cbe, node.child1);
}
return;
}
// Alloca
if kind == hAlloca {
var ct: String = "int";
if !String_Eq(node.typeName, "") {
ct = node.typeName;
}
StringBuilder_Append(&cbe.sb, CBE_CParamDecl(ct, node.strValue));
return;
}
// Store: combine alloca + value into single declaration
if kind == hStore {
// Track moved variables via assignment/let
if node.child2 != null as *HirNode && node.child2.kind == hVar {
CBE_AddMoved(cbe, node.child2.strValue);
}
// Reinitialization removes moved status
if node.child1 != null as *HirNode && node.child1.kind == hVar {
CBE_RemoveMoved(cbe, node.child1.strValue);
}
if node.child1 != null as *HirNode && node.child1.kind == hAlloca {
// Declaration with initializer: Type x = value;
var ct: String = CBackend_TypeToC(node.child1.intValue);
if !String_Eq(node.child1.typeName, "") {
ct = node.child1.typeName;
}
StringBuilder_Append(&cbe.sb, CBE_CParamDecl(ct, node.child1.strValue));
if node.child2 != null as *HirNode {
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
}
return;
}
// Plain assignment
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// If — each branch has its own defer scope (locals do not leak)
if kind == hIf {
StringBuilder_Append(&cbe.sb, "if (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
let savedThen: int = cbe.deferCount;
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
discard CBE_EmitAndPopDefersFrom(cbe, savedThen);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
let elseBlock: *HirNode = node.extraData as *HirNode;
if elseBlock != null as *HirNode {
StringBuilder_Append(&cbe.sb, " else {\n");
let savedElse: int = cbe.deferCount;
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, elseBlock);
discard CBE_EmitAndPopDefersFrom(cbe, savedElse);
cbe.indent = cbe.indent - 1;
sp = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}\n");
} else {
StringBuilder_Append(&cbe.sb, "\n");
}
return;
}
// While — loop-body locals dropped each iteration
if kind == hWhile {
StringBuilder_Append(&cbe.sb, "while (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
let savedW: int = cbe.deferCount;
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
discard CBE_EmitAndPopDefersFrom(cbe, savedW);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
return;
}
// Loop (infinite)
if kind == hLoop {
StringBuilder_Append(&cbe.sb, "while (1) {\n");
if node.child1 != null as *HirNode {
let savedL: int = cbe.deferCount;
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child1);
discard CBE_EmitAndPopDefersFrom(cbe, savedL);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
return;
}
// Break / Continue
if kind == hBreak {
StringBuilder_Append(&cbe.sb, "break;");
return;
}
if kind == hContinue {
StringBuilder_Append(&cbe.sb, "continue;");
return;
}
// Field access: obj.field — use -> if base is a pointer
if kind == hFieldAccess {
CBE_EmitExpr(cbe, node.child1);
var isPtr: bool = false;
if node.child1 != null as *HirNode {
let childType: String = CBE_GetExprTypeName(cbe.mod, node.child1);
if String_EndsWith(childType, "*") {
isPtr = true;
}
if node.child1.kind == hVar && node.child1.typeKind == tyPointer {
isPtr = true;
}
}
if isPtr {
StringBuilder_Append(&cbe.sb, "->");
} else {
StringBuilder_Append(&cbe.sb, ".");
}
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Field access: obj.field — use -> if base is pointer, else .
if kind == hFieldPtr {
CBE_EmitExpr(cbe, node.child1);
var isPtr: bool = false;
if node.child1 != null as *HirNode {
let childType: String = CBE_GetExprTypeName(cbe.mod, node.child1);
if String_EndsWith(childType, "*") {
isPtr = true;
}
}
if isPtr {
StringBuilder_Append(&cbe.sb, "->");
} else {
StringBuilder_Append(&cbe.sb, ".");
}
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Sizeof: sizeof(Type) — emit as sizeof(Type)
if kind == hSizeOf {
StringBuilder_Append(&cbe.sb, "sizeof(");
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, "int");
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Index: arr[idx] — emit as arr[idx]
// For Array/Iter desugar pattern (fieldPtr "data"), emit bounds-checked access
if kind == hIndexPtr {
var isArrayAccess: bool = false;
if node.child1 != null as *HirNode {
if node.child1.kind == hFieldPtr && String_Eq(node.child1.strValue, "data") {
isArrayAccess = true;
}
}
if isArrayAccess {
// self.data[i] is a raw pointer index; Bux source inserts
// bux_bounds_check explicitly where needed (Array_Get). Do NOT
// re-check against .len here — Push writes at index == len.
let base: *HirNode = node.child1.child1;
var isPtr: bool = false;
if base != null as *HirNode {
let childType: String = CBE_GetExprTypeName(cbe.mod, base);
if String_EndsWith(childType, "*") {
isPtr = true;
}
if base.kind == hVar && base.typeKind == tyPointer {
isPtr = true;
}
}
let sep: String = ".";
if isPtr { sep = "->"; }
CBE_EmitExpr(cbe, base);
StringBuilder_Append(&cbe.sb, sep);
StringBuilder_Append(&cbe.sb, "data[");
CBE_EmitExpr(cbe, node.child2);
StringBuilder_Append(&cbe.sb, "]");
} else {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, "[");
CBE_EmitExpr(cbe, node.child2);
StringBuilder_Append(&cbe.sb, "]");
}
return;
}
// Load: *ptr — emit as *ptr (dereference)
// Optimize common patterns to avoid & / * temporaries
if kind == hLoad {
if node.child1 != null as *HirNode {
let ptrNode: *HirNode = node.child1;
let ptrKind: int = ptrNode.kind;
// field access: load(field_ptr(base, field)) → base.field
if ptrKind == hFieldPtr {
CBE_EmitExpr(cbe, ptrNode.child1);
var isPtr: bool = false;
if ptrNode.child1 != null as *HirNode {
let childType: String = CBE_GetExprTypeName(cbe.mod, ptrNode.child1);
if String_EndsWith(childType, "*") {
isPtr = true;
}
}
if isPtr {
StringBuilder_Append(&cbe.sb, "->");
} else {
StringBuilder_Append(&cbe.sb, ".");
}
StringBuilder_Append(&cbe.sb, ptrNode.strValue);
return;
}
// arrow field: load(arrow_field(base, field)) → base->field
if ptrKind == hArrowField {
CBE_EmitExpr(cbe, ptrNode.child1);
StringBuilder_Append(&cbe.sb, "->");
StringBuilder_Append(&cbe.sb, ptrNode.strValue);
return;
}
// index: load(index_ptr(base, idx)) → base[idx]
// For Array<T> desugar pattern, emit bounds-checked access
if ptrKind == hIndexPtr {
var isArrayAccess: bool = false;
if ptrNode.child1 != null as *HirNode {
if ptrNode.child1.kind == hFieldPtr && String_Eq(ptrNode.child1.strValue, "data") {
isArrayAccess = true;
}
}
if isArrayAccess {
CBE_EmitExpr(cbe, ptrNode.child1.child1);
StringBuilder_Append(&cbe.sb, ".data[bux_index_check(");
CBE_EmitExpr(cbe, ptrNode.child2);
StringBuilder_Append(&cbe.sb, ", ");
CBE_EmitExpr(cbe, ptrNode.child1.child1);
StringBuilder_Append(&cbe.sb, ".len)]");
} else {
CBE_EmitExpr(cbe, ptrNode.child1);
StringBuilder_Append(&cbe.sb, "[");
CBE_EmitExpr(cbe, ptrNode.child2);
StringBuilder_Append(&cbe.sb, "]");
}
return;
}
}
StringBuilder_Append(&cbe.sb, "(*");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Assign: target = value
if kind == hAssign {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Cast
if kind == hCast {
StringBuilder_Append(&cbe.sb, "((");
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.typeKind));
}
StringBuilder_Append(&cbe.sb, ")");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Struct init: ((TypeName){.field = value, ...})
if kind == hStructInit {
// Field values taken by value → skip auto-Drop of those locals
CBE_MarkMovedFromNode(cbe, node);
StringBuilder_Append(&cbe.sb, "((");
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "){");
// Emit fields (chained via child3)
var field: *HirNode = node.child1;
var first: bool = true;
while field != null as *HirNode {
if !first {
StringBuilder_Append(&cbe.sb, ", ");
}
StringBuilder_Append(&cbe.sb, ".");
StringBuilder_Append(&cbe.sb, field.strValue);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, field.child1);
first = false;
field = field.child3;
}
StringBuilder_Append(&cbe.sb, "})");
return;
}
// Block — emit each statement via child3 linked list
if kind == hBlock {
var child: *HirNode = node.child1;
while child != null as *HirNode {
if child.kind == hDefer {
CBE_EmitExpr(cbe, child);
child = child.child3;
continue;
}
CBE_EmitDebugLine(cbe, child);
// Indent
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
CBE_EmitExpr(cbe, child);
// Add semicolon for statements that need it
if child.kind != hBlock && child.kind != hIf && child.kind != hWhile && child.kind != hLoop && child.kind != hBreak && child.kind != hContinue {
StringBuilder_Append(&cbe.sb, ";");
}
StringBuilder_Append(&cbe.sb, "\n");
child = child.child3;
}
return;
}
}
// ---------------------------------------------------------------------------
// Emit function declaration
// ---------------------------------------------------------------------------
// Infer C type for a BuxFn mangled part (int, cstr, uint, void, ...)
func CBE_FatPartToC(part: String) -> String {
if String_Eq(part, "cstr") { return "const char*"; }
if String_Eq(part, "void") { return "void"; }
if String_Eq(part, "bool") { return "bool"; }
if String_Eq(part, "uint") { return "unsigned int"; }
if String_Eq(part, "float") { return "float"; }
if String_Eq(part, "double") || String_Eq(part, "float64") { return "double"; }
if String_EndsWith(part, "Ptr") {
let base: String = String_Slice(part, 0, String_Len(part) - 3);
return String_Concat(CBE_FatPartToC(base), "*");
}
return part; // int, etc.
}
// Emit typedefs for common BuxFn_* shapes (fat function pointers)
func CBE_EmitFatFuncTypedefs(cbe: *CEmitter, mod: *HirModule) {
StringBuilder_Append(&cbe.sb, "/* Fat function pointer types (code + env) */\n");
// Always emit core shapes
CBE_EmitOneFatTypedef(cbe, "BuxFn_int_int");
CBE_EmitOneFatTypedef(cbe, "BuxFn_int_int_int");
CBE_EmitOneFatTypedef(cbe, "BuxFn_bool_int");
CBE_EmitOneFatTypedef(cbe, "BuxFn_void_void");
CBE_EmitOneFatTypedef(cbe, "BuxFn_int_void");
CBE_EmitOneFatTypedef(cbe, "BuxFn_cstr_int");
CBE_EmitOneFatTypedef(cbe, "BuxFn_int_cstr");
CBE_EmitOneFatTypedef(cbe, "BuxFn_bool_cstr");
CBE_EmitOneFatTypedef(cbe, "BuxFn_int_int_cstr");
// Scan module for any other BuxFn_* names (deduped via #ifndef in EmitOne)
var i: int = 0;
while i < mod.funcCount {
CBE_MaybeEmitExtraFat(cbe, mod.funcs[i].retTypeName);
var p: int = 0;
while p < mod.funcs[i].paramCount {
var ptype: String = "";
if p == 0 { ptype = mod.funcs[i].param0.typeName; }
else if p == 1 { ptype = mod.funcs[i].param1.typeName; }
else if p == 2 { ptype = mod.funcs[i].param2.typeName; }
else if p == 3 { ptype = mod.funcs[i].param3.typeName; }
else if p == 4 { ptype = mod.funcs[i].param4.typeName; }
else if p == 5 { ptype = mod.funcs[i].param5.typeName; }
else if p == 6 { ptype = mod.funcs[i].param6.typeName; }
else if p == 7 { ptype = mod.funcs[i].param7.typeName; }
else if p == 8 { ptype = mod.funcs[i].param8.typeName; }
CBE_MaybeEmitExtraFat(cbe, ptype);
p = p + 1;
}
i = i + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
}
func CBE_MaybeEmitExtraFat(cbe: *CEmitter, name: String) {
if String_Eq(name, "") { return; }
if !String_StartsWith(name, "BuxFn_") { return; }
CBE_EmitOneFatTypedef(cbe, name);
}
func CBE_CollectBuxFn(names: *String, count: *int, name: String) {
if String_Eq(name, "") { return; }
if !String_StartsWith(name, "BuxFn_") { return; }
if *count >= 64 { return; }
var i: int = 0;
while i < *count {
if String_Eq(names[i], name) { return; }
i = i + 1;
}
names[*count] = name;
*count = *count + 1;
}
// BuxFn_ret_p0_p1 → typedef with code pointer
func CBE_EmitOneFatTypedef(cbe: *CEmitter, fatName: String) {
// Split fatName after "BuxFn_" into parts by '_'
let prefixLen: uint = 6; // "BuxFn_"
let rest: String = String_Slice(fatName, prefixLen, String_Len(fatName) - prefixLen);
// Parse parts: first = ret, rest = params (use simple split)
let partCount: uint = String_SplitCount(rest, "_");
if partCount == 0 { return; }
let retPart: String = String_SplitPart(rest, "_", 0);
let retC: String = CBE_FatPartToC(retPart);
// Guard against redefinition if the same name is emitted twice
StringBuilder_Append(&cbe.sb, "#ifndef ");
StringBuilder_Append(&cbe.sb, fatName);
StringBuilder_Append(&cbe.sb, "_DEFINED\n#define ");
StringBuilder_Append(&cbe.sb, fatName);
StringBuilder_Append(&cbe.sb, "_DEFINED\n");
StringBuilder_Append(&cbe.sb, "typedef struct ");
StringBuilder_Append(&cbe.sb, fatName);
StringBuilder_Append(&cbe.sb, " {\n ");
// code field: ret (*code)(void* env, params...)
StringBuilder_Append(&cbe.sb, retC);
StringBuilder_Append(&cbe.sb, " (*code)(void* env");
var pi: uint = 1;
while pi < partCount {
let pPart: String = String_SplitPart(rest, "_", pi);
if !(pi == 1 && String_Eq(pPart, "void") && partCount == 2) {
StringBuilder_Append(&cbe.sb, ", ");
StringBuilder_Append(&cbe.sb, CBE_FatPartToC(pPart));
}
pi = pi + 1;
}
StringBuilder_Append(&cbe.sb, ");\n void* env;\n} ");
StringBuilder_Append(&cbe.sb, fatName);
StringBuilder_Append(&cbe.sb, ";\n#endif\n");
}
func CBE_EmitMakerDecl(cbe: *CEmitter, f: *HirFunc) {
// Infer fat type from thunk: skip __env, use user params + ret
var fatName: String = "BuxFn_";
var retC: String = f.retTypeName;
if String_Eq(retC, "") { retC = "int"; }
fatName = String_Concat(fatName, Lcx_SanitizeFatPart(retC));
var pi: int = 1; // skip __env
while pi < f.paramCount {
var ptype: String = "int";
if pi == 1 { ptype = f.param1.typeName; }
else if pi == 2 { ptype = f.param2.typeName; }
else if pi == 3 { ptype = f.param3.typeName; }
else if pi == 4 { ptype = f.param4.typeName; }
else if pi == 5 { ptype = f.param5.typeName; }
else if pi == 6 { ptype = f.param6.typeName; }
else if pi == 7 { ptype = f.param7.typeName; }
else if pi == 8 { ptype = f.param8.typeName; }
if String_Eq(ptype, "") { ptype = "int"; }
fatName = String_Concat(fatName, "_");
fatName = String_Concat(fatName, Lcx_SanitizeFatPart(ptype));
pi = pi + 1;
}
if f.paramCount <= 1 {
fatName = String_Concat(fatName, "_void");
}
StringBuilder_Append(&cbe.sb, fatName);
StringBuilder_Append(&cbe.sb, " __make_");
StringBuilder_Append(&cbe.sb, f.name);
StringBuilder_Append(&cbe.sb, "(");
var ci: int = 0;
while ci < f.captureCount {
if ci > 0 { StringBuilder_Append(&cbe.sb, ", "); }
var capType: String = "int";
var capName: String = "c";
if ci == 0 { capName = f.captureName0; capType = CBackend_TypeToC(f.captureType0); }
else if ci == 1 { capName = f.captureName1; capType = CBackend_TypeToC(f.captureType1); }
else if ci == 2 { capName = f.captureName2; capType = CBackend_TypeToC(f.captureType2); }
else if ci == 3 { capName = f.captureName3; capType = CBackend_TypeToC(f.captureType3); }
else if ci == 4 { capName = f.captureName4; capType = CBackend_TypeToC(f.captureType4); }
else if ci == 5 { capName = f.captureName5; capType = CBackend_TypeToC(f.captureType5); }
else if ci == 6 { capName = f.captureName6; capType = CBackend_TypeToC(f.captureType6); }
else if ci == 7 { capName = f.captureName7; capType = CBackend_TypeToC(f.captureType7); }
StringBuilder_Append(&cbe.sb, capType);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, capName);
ci = ci + 1;
}
StringBuilder_Append(&cbe.sb, ")");
}
func CBE_EmitMakerFunc(cbe: *CEmitter, f: *HirFunc) {
CBE_EmitMakerDecl(cbe, f);
StringBuilder_Append(&cbe.sb, " {\n");
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, f.envStructName);
StringBuilder_Append(&cbe.sb, "* __e = (");
StringBuilder_Append(&cbe.sb, f.envStructName);
StringBuilder_Append(&cbe.sb, "*)bux_alloc(sizeof(");
StringBuilder_Append(&cbe.sb, f.envStructName);
StringBuilder_Append(&cbe.sb, "));\n");
var ci: int = 0;
while ci < f.captureCount {
var capName: String = "";
if ci == 0 { capName = f.captureName0; }
else if ci == 1 { capName = f.captureName1; }
else if ci == 2 { capName = f.captureName2; }
else if ci == 3 { capName = f.captureName3; }
else if ci == 4 { capName = f.captureName4; }
else if ci == 5 { capName = f.captureName5; }
else if ci == 6 { capName = f.captureName6; }
else if ci == 7 { capName = f.captureName7; }
StringBuilder_Append(&cbe.sb, " __e->");
StringBuilder_Append(&cbe.sb, capName);
StringBuilder_Append(&cbe.sb, " = ");
StringBuilder_Append(&cbe.sb, capName);
StringBuilder_Append(&cbe.sb, ";\n");
ci = ci + 1;
}
// Build fat return type name same as decl
var fatName: String = "BuxFn_";
var retC: String = f.retTypeName;
if String_Eq(retC, "") { retC = "int"; }
fatName = String_Concat(fatName, Lcx_SanitizeFatPart(retC));
var pi: int = 1;
while pi < f.paramCount {
var ptype: String = "int";
if pi == 1 { ptype = f.param1.typeName; }
else if pi == 2 { ptype = f.param2.typeName; }
else if pi == 3 { ptype = f.param3.typeName; }
else if pi == 4 { ptype = f.param4.typeName; }
else if pi == 5 { ptype = f.param5.typeName; }
else if pi == 6 { ptype = f.param6.typeName; }
else if pi == 7 { ptype = f.param7.typeName; }
else if pi == 8 { ptype = f.param8.typeName; }
if String_Eq(ptype, "") { ptype = "int"; }
fatName = String_Concat(fatName, "_");
fatName = String_Concat(fatName, Lcx_SanitizeFatPart(ptype));
pi = pi + 1;
}
if f.paramCount <= 1 {
fatName = String_Concat(fatName, "_void");
}
StringBuilder_Append(&cbe.sb, " return (");
StringBuilder_Append(&cbe.sb, fatName);
StringBuilder_Append(&cbe.sb, "){ .code = ");
StringBuilder_Append(&cbe.sb, f.name);
StringBuilder_Append(&cbe.sb, ", .env = __e };\n}\n\n");
}
// Emit adapters for any non-closure function (used when taken as value)
func CBE_EmitAllAdapters(cbe: *CEmitter, mod: *HirModule) {
StringBuilder_Append(&cbe.sb, "/* Fat-func adapters for named functions */\n");
var i: int = 0;
while i < mod.funcCount {
let fname: String = mod.funcs[i].name;
// Skip closures, makers, adapters themselves
if String_StartsWith(fname, "__closure_") || String_StartsWith(fname, "__make_") || String_StartsWith(fname, "__adapt_") {
i = i + 1;
continue;
}
if CBE_FuncHasGeneric(&mod.funcs[i]) {
i = i + 1;
continue;
}
// Only emit adapter if function has body
if mod.funcs[i].body == null as *HirNode {
i = i + 1;
continue;
}
// Adapter signature: ret __adapt_F(void* env, params...) { return F(params); }
var retC: String = mod.funcs[i].retTypeName;
if String_Eq(retC, "") { retC = "void"; }
StringBuilder_Append(&cbe.sb, "static ");
StringBuilder_Append(&cbe.sb, retC);
StringBuilder_Append(&cbe.sb, " __adapt_");
StringBuilder_Append(&cbe.sb, fname);
StringBuilder_Append(&cbe.sb, "(void* env");
var p: int = 0;
while p < mod.funcs[i].paramCount {
// Skip if first param is already __env (shouldn't for named funcs)
var pname: String = "";
var ptype: String = "int";
if p == 0 { pname = mod.funcs[i].param0.name; ptype = mod.funcs[i].param0.typeName; }
else if p == 1 { pname = mod.funcs[i].param1.name; ptype = mod.funcs[i].param1.typeName; }
else if p == 2 { pname = mod.funcs[i].param2.name; ptype = mod.funcs[i].param2.typeName; }
else if p == 3 { pname = mod.funcs[i].param3.name; ptype = mod.funcs[i].param3.typeName; }
else if p == 4 { pname = mod.funcs[i].param4.name; ptype = mod.funcs[i].param4.typeName; }
else if p == 5 { pname = mod.funcs[i].param5.name; ptype = mod.funcs[i].param5.typeName; }
else if p == 6 { pname = mod.funcs[i].param6.name; ptype = mod.funcs[i].param6.typeName; }
else if p == 7 { pname = mod.funcs[i].param7.name; ptype = mod.funcs[i].param7.typeName; }
else if p == 8 { pname = mod.funcs[i].param8.name; ptype = mod.funcs[i].param8.typeName; }
if String_Eq(ptype, "") { ptype = "int"; }
StringBuilder_Append(&cbe.sb, ", ");
StringBuilder_Append(&cbe.sb, ptype);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, pname);
p = p + 1;
}
StringBuilder_Append(&cbe.sb, ") {\n (void)env;\n");
if String_Eq(retC, "void") {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, fname);
StringBuilder_Append(&cbe.sb, "(");
} else {
StringBuilder_Append(&cbe.sb, " return ");
StringBuilder_Append(&cbe.sb, fname);
StringBuilder_Append(&cbe.sb, "(");
}
p = 0;
while p < mod.funcs[i].paramCount {
if p > 0 { StringBuilder_Append(&cbe.sb, ", "); }
var pname: String = "";
if p == 0 { pname = mod.funcs[i].param0.name; }
else if p == 1 { pname = mod.funcs[i].param1.name; }
else if p == 2 { pname = mod.funcs[i].param2.name; }
else if p == 3 { pname = mod.funcs[i].param3.name; }
else if p == 4 { pname = mod.funcs[i].param4.name; }
else if p == 5 { pname = mod.funcs[i].param5.name; }
else if p == 6 { pname = mod.funcs[i].param6.name; }
else if p == 7 { pname = mod.funcs[i].param7.name; }
else if p == 8 { pname = mod.funcs[i].param8.name; }
StringBuilder_Append(&cbe.sb, pname);
p = p + 1;
}
StringBuilder_Append(&cbe.sb, ");\n}\n\n");
i = i + 1;
}
}
func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
// Return type
if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") {
StringBuilder_Append(&cbe.sb, "void ");
} else {
StringBuilder_Append(&cbe.sb, f.retTypeName);
StringBuilder_Append(&cbe.sb, " ");
}
StringBuilder_Append(&cbe.sb, f.name);
StringBuilder_Append(&cbe.sb, "(");
// Parameters
var i: int = 0;
while i < f.paramCount {
if i > 0 {
StringBuilder_Append(&cbe.sb, ", ");
}
// Use param type info
var pname: String = "";
var ptype: String = "";
if i == 0 { pname = f.param0.name; ptype = f.param0.typeName; }
if i == 1 { pname = f.param1.name; ptype = f.param1.typeName; }
if i == 2 { pname = f.param2.name; ptype = f.param2.typeName; }
if i == 3 { pname = f.param3.name; ptype = f.param3.typeName; }
if i == 4 { pname = f.param4.name; ptype = f.param4.typeName; }
if i == 5 { pname = f.param5.name; ptype = f.param5.typeName; }
if i == 6 { pname = f.param6.name; ptype = f.param6.typeName; }
if i == 7 { pname = f.param7.name; ptype = f.param7.typeName; }
if i == 8 { pname = f.param8.name; ptype = f.param8.typeName; }
// Emit type + name
if String_Eq(ptype, "") {
ptype = "int";
}
StringBuilder_Append(&cbe.sb, CBE_CParamDecl(ptype, pname));
i = i + 1;
}
StringBuilder_Append(&cbe.sb, ")");
}
// ---------------------------------------------------------------------------
// Helpers for detecting generic declarations (not yet monomorphized)
// ---------------------------------------------------------------------------
func CBE_IsGenericTypeName(name: String) -> bool {
let n: String = CBE_NormalizeTypeName(name);
if String_Eq(n, "T") || String_Eq(n, "K") || String_Eq(n, "V") { return true; }
if String_Eq(n, "T*") || String_Eq(n, "K*") || String_Eq(n, "V*") { return true; }
// Bare generic containers (not monomorphized Array_int etc.)
if String_Eq(n, "Array") || String_Eq(n, "Array*") { return true; }
if String_Eq(n, "Channel") || String_Eq(n, "Channel*") { return true; }
if String_Eq(n, "Iter") || String_Eq(n, "Iter*") { return true; }
if String_Eq(n, "Set") || String_Eq(n, "Set*") { return true; }
if String_Eq(n, "SetEntry") || String_Eq(n, "SetEntry*") { return true; }
if String_Eq(n, "Map") || String_Eq(n, "Map*") { return true; }
if String_Eq(n, "MapEntry") || String_Eq(n, "MapEntry*") { return true; }
if String_Eq(n, "StringMap") || String_Eq(n, "StringMap*") { return true; }
if String_Eq(n, "StringMapEntry") || String_Eq(n, "StringMapEntry*") { return true; }
if String_Eq(n, "Slice") || String_Eq(n, "Slice*") { return true; }
// Remaining angle-bracket forms (unresolved generics)
if String_Contains(n, "<") { return true; }
return false;
}
func CBE_StructHasGeneric(st: *HirStruct) -> bool {
var fi: int = 0;
while fi < st.fieldCount {
if CBE_IsGenericTypeName(st.fields[fi].typeName) { return true; }
fi = fi + 1;
}
return false;
}
func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
var pi: int = 0;
while pi < f.paramCount {
var ptype: String = "";
if pi == 0 { ptype = f.param0.typeName; }
if pi == 1 { ptype = f.param1.typeName; }
if pi == 2 { ptype = f.param2.typeName; }
if pi == 3 { ptype = f.param3.typeName; }
if pi == 4 { ptype = f.param4.typeName; }
if pi == 5 { ptype = f.param5.typeName; }
if pi == 6 { ptype = f.param6.typeName; }
if pi == 7 { ptype = f.param7.typeName; }
if pi == 8 { ptype = f.param8.typeName; }
if CBE_IsGenericTypeName(ptype) { return true; }
pi = pi + 1;
}
if CBE_IsGenericTypeName(f.retTypeName) { return true; }
return false;
}
func CBE_IsArrayTypeName(name: String) -> bool {
if String_Eq(name, "") { return false; }
if String_StartsWith(name, "Array") { return true; }
return false;
}
func CBE_IsPrimitiveTypeName(name: String) -> bool {
if String_Eq(name, "int") || String_Eq(name, "") { return true; }
if String_Eq(name, "String") { return true; }
if String_Eq(name, "bool") { return true; }
if String_Eq(name, "uint32") { return true; }
if String_Eq(name, "uint64") { return true; }
if String_Eq(name, "int64") { return true; }
if String_Eq(name, "float32") { return true; }
if String_Eq(name, "float64") { return true; }
if String_Eq(name, "uint8") { return true; }
if String_Eq(name, "uint16") { return true; }
if String_Eq(name, "int8") { return true; }
if String_Eq(name, "int16") { return true; }
if String_Eq(name, "uint") { return true; }
if String_Eq(name, "char8") { return true; }
if String_Eq(name, "void") { return true; }
if String_Eq(name, "size_t") { return true; }
return false;
}
func CBE_StructHasValueStructField(st: *HirStruct) -> bool {
var fi: int = 0;
while fi < st.fieldCount {
let ft: String = CBE_NormalizeTypeName(st.fields[fi].typeName);
if !CBE_IsPrimitiveTypeName(ft) && !String_EndsWith(ft, "*") {
return true;
}
fi = fi + 1;
}
return false;
}
func CBE_EmitStructDef(cbe: *CEmitter, st: *HirStruct) {
if String_Eq(st.name, "") { return; }
StringBuilder_Append(&cbe.sb, "struct ");
StringBuilder_Append(&cbe.sb, st.name);
StringBuilder_Append(&cbe.sb, " {\n");
var fi: int = 0;
while fi < st.fieldCount {
StringBuilder_Append(&cbe.sb, " ");
var ft: String = CBE_NormalizeTypeName(st.fields[fi].typeName);
if String_Eq(ft, "int") || String_Eq(ft, "") {
StringBuilder_Append(&cbe.sb, "int");
} else if String_Eq(ft, "String") {
StringBuilder_Append(&cbe.sb, "String");
} else if String_Eq(ft, "bool") {
StringBuilder_Append(&cbe.sb, "bool");
} else if String_Eq(ft, "uint32") {
StringBuilder_Append(&cbe.sb, "uint32");
} else {
StringBuilder_Append(&cbe.sb, ft);
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, st.fields[fi].name);
StringBuilder_Append(&cbe.sb, ";\n");
fi = fi + 1;
}
StringBuilder_Append(&cbe.sb, "};\n\n");
}
func CBE_LookupFieldType(mod: *HirModule, structName: String, fieldName: String) -> String {
var si: int = 0;
while si < mod.structCount {
if String_Eq(mod.structs[si].name, structName) {
var fi: int = 0;
while fi < mod.structs[si].fieldCount {
if String_Eq(mod.structs[si].fields[fi].name, fieldName) {
let ft: String = mod.structs[si].fields[fi].typeName;
if ft != null as String { return ft; }
return "";
}
fi = fi + 1;
}
}
si = si + 1;
}
return "";
}
func CBE_GetExprTypeName(mod: *HirModule, node: *HirNode) -> String {
if node == null as *HirNode { return ""; }
if node.kind == hVar {
if node.typeName != null as String { return node.typeName; }
return "";
}
if node.kind == hFieldPtr {
let baseType: String = CBE_GetExprTypeName(mod, node.child1);
var structName: String = baseType;
if String_EndsWith(baseType, "*") {
let len: uint = String_Len(baseType);
if len > 1 {
structName = String_Slice(baseType, 0, len - 1);
}
}
if !String_Eq(structName, "") {
return CBE_LookupFieldType(mod, structName, node.strValue);
}
}
if node.typeName != null as String { return node.typeName; }
return "";
}
// ---------------------------------------------------------------------------
// Generate complete C module
// ---------------------------------------------------------------------------
func CBackend_Generate(mod: *HirModule) -> String {
let cbe: *CEmitter = bux_alloc(sizeof(CEmitter)) as *CEmitter;
cbe.sb = StringBuilder_NewCap(8192);
cbe.indent = 0;
cbe.mod = mod;
cbe.deferCount = 0;
cbe.movedCount = 0;
cbe.tmpCounter = 0;
cbe.lastDebugLine = 0;
cbe.emitDebugLines = true;
cbe.currentFile = "";
// Optional: BUX_DEBUG_FILE sets the #line path for gdb (selfhost has no multi-file loc yet)
let dbgFile: String = bux_getenv("BUX_DEBUG_FILE");
if dbgFile != null as String && !String_Eq(dbgFile, "") {
cbe.currentFile = dbgFile;
}
// BUX_NO_LINE=1 disables #line maps
let noLine: String = bux_getenv("BUX_NO_LINE");
if noLine != null as String && !String_Eq(noLine, "") {
cbe.emitDebugLines = false;
}
// Header
StringBuilder_Append(&cbe.sb, "// Generated by Bux C Backend v2\n");
StringBuilder_Append(&cbe.sb, "#include <stdint.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdbool.h>\n");
StringBuilder_Append(&cbe.sb, "#include <string.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdio.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdlib.h>\n\n");
// Type aliases
StringBuilder_Append(&cbe.sb, "typedef const char* String;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned char uint8;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned short uint16;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned int uint32;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned long long uint64;\n");
StringBuilder_Append(&cbe.sb, "typedef signed char int8;\n");
StringBuilder_Append(&cbe.sb, "typedef short int16;\n");
StringBuilder_Append(&cbe.sb, "typedef long long int64;\n");
StringBuilder_Append(&cbe.sb, "typedef float float32;\n");
StringBuilder_Append(&cbe.sb, "typedef double float64;\n");
StringBuilder_Append(&cbe.sb, "typedef char char8;\n\n");
// Runtime declarations
StringBuilder_Append(&cbe.sb, "void* bux_alloc(unsigned int size);\n");
StringBuilder_Append(&cbe.sb, "void bux_free(void* ptr);\n");
StringBuilder_Append(&cbe.sb, "unsigned long long bux_index_check(unsigned long long index, unsigned long long len);\n");
StringBuilder_Append(&cbe.sb, "int64 bux_add_i64_checked(int64 a, int64 b);\n");
StringBuilder_Append(&cbe.sb, "int64 bux_sub_i64_checked(int64 a, int64 b);\n");
StringBuilder_Append(&cbe.sb, "int64 bux_mul_i64_checked(int64 a, int64 b);\n");
StringBuilder_Append(&cbe.sb, "int64 bux_neg_i64_checked(int64 a);\n\n");
// Forward declare all struct types (skip empty names)
var si: int = 0;
while si < mod.structCount {
if !String_Eq(mod.structs[si].name, "") {
StringBuilder_Append(&cbe.sb, "typedef struct ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, ";\n");
}
si = si + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Tuple typedefs before enums/structs that embed them by value
StringBuilder_Append(&cbe.sb, "/* Tuple types */\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int {\n int _0;\n int _1;\n} Tuple_int_int;\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int_int {\n int _0;\n int _1;\n int _2;\n} Tuple_int_int_int;\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_Empty {\n char _pad;\n} Tuple_Empty;\n\n");
// Struct definitions before enums (enums may embed structs by value, e.g. Shape::Dot(Point))
// Pass 1: emit structs with no value-typed struct fields (leaf structs)
si = 0;
while si < mod.structCount {
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
if CBE_StructHasValueStructField(&mod.structs[si]) {
si = si + 1;
continue;
}
CBE_EmitStructDef(cbe, &mod.structs[si]);
si = si + 1;
}
// Pass 2: emit structs that contain value-typed struct fields
si = 0;
while si < mod.structCount {
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
if !CBE_StructHasValueStructField(&mod.structs[si]) {
si = si + 1;
continue;
}
CBE_EmitStructDef(cbe, &mod.structs[si]);
si = si + 1;
}
// Enum definitions
var ei: int = 0;
while ei < mod.enumCount {
let en: *HirEnum = &mod.enums[ei];
var hasData: bool = false;
var vi: int = 0;
while vi < en.variantCount {
if en.variants[vi].fieldCount > 0 {
hasData = true;
}
vi = vi + 1;
}
if !hasData {
// Simple enum: emit as plain C enum (no struct wrapper)
StringBuilder_Append(&cbe.sb, "typedef enum {\n");
vi = 0;
while vi < en.variantCount {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_");
StringBuilder_Append(&cbe.sb, en.variants[vi].name);
if vi < en.variantCount - 1 {
StringBuilder_Append(&cbe.sb, ",");
}
StringBuilder_Append(&cbe.sb, "\n");
vi = vi + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, ";\n\n");
} else {
// Algebraic enum: tag enum + data union + struct
// 1. Tag enum
StringBuilder_Append(&cbe.sb, "typedef enum {\n");
vi = 0;
while vi < en.variantCount {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_");
StringBuilder_Append(&cbe.sb, en.variants[vi].name);
if vi < en.variantCount - 1 {
StringBuilder_Append(&cbe.sb, ",");
}
StringBuilder_Append(&cbe.sb, "\n");
vi = vi + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Tag;\n\n");
// 2. Data union
StringBuilder_Append(&cbe.sb, "typedef union {\n");
vi = 0;
while vi < en.variantCount {
let ev: *HirEnumVariant = &en.variants[vi];
if ev.fieldCount > 0 {
// Prefer stored C type name (handles Tuple_*, named types)
var ft0: String = CBackend_TypeToC(ev.fieldType0);
if !String_Eq(ev.fieldTypeName0, "") { ft0 = ev.fieldTypeName0; }
var ft1: String = CBackend_TypeToC(ev.fieldType1);
if !String_Eq(ev.fieldTypeName1, "") { ft1 = ev.fieldTypeName1; }
if ev.fieldCount == 1 {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ft0);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ev.name);
StringBuilder_Append(&cbe.sb, "_0;\n");
} else {
// Nested anonymous struct (layout matches bootstrap data.Variant.Variant_i)
StringBuilder_Append(&cbe.sb, " struct {\n");
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ft0);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ev.fieldName0);
StringBuilder_Append(&cbe.sb, ";\n");
if ev.fieldCount > 1 {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ft1);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ev.fieldName1);
StringBuilder_Append(&cbe.sb, ";\n");
}
StringBuilder_Append(&cbe.sb, " } ");
StringBuilder_Append(&cbe.sb, ev.name);
StringBuilder_Append(&cbe.sb, ";\n");
}
}
vi = vi + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Data;\n\n");
// 3. Main struct
StringBuilder_Append(&cbe.sb, "typedef struct {\n");
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Tag tag;\n");
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Data data;\n");
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, ";\n\n");
}
ei = ei + 1;
}
// Constant definitions
var ci: int = 0;
while ci < mod.constCount {
StringBuilder_Append(&cbe.sb, "#define ");
StringBuilder_Append(&cbe.sb, mod.consts[ci].name);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, String_FromInt(mod.consts[ci].value as int64));
StringBuilder_Append(&cbe.sb, "\n");
ci = ci + 1;
}
if mod.constCount > 0 {
StringBuilder_Append(&cbe.sb, "\n");
}
// Fat function-pointer typedefs (BuxFn_*) — before forward decls
CBE_EmitFatFuncTypedefs(cbe, mod);
// Env structs for capturing closures (no static instance — heap per value)
var ei2: int = 0;
while ei2 < mod.funcCount {
if mod.funcs[ei2].captureCount > 0 && !String_Eq(mod.funcs[ei2].envStructName, "") {
StringBuilder_Append(&cbe.sb, "typedef struct ");
StringBuilder_Append(&cbe.sb, mod.funcs[ei2].envStructName);
StringBuilder_Append(&cbe.sb, " {\n");
var ci2: int = 0;
while ci2 < mod.funcs[ei2].captureCount {
var capName: String = "";
var capType: String = "int";
if ci2 == 0 { capName = mod.funcs[ei2].captureName0; capType = CBackend_TypeToC(mod.funcs[ei2].captureType0); }
else if ci2 == 1 { capName = mod.funcs[ei2].captureName1; capType = CBackend_TypeToC(mod.funcs[ei2].captureType1); }
else if ci2 == 2 { capName = mod.funcs[ei2].captureName2; capType = CBackend_TypeToC(mod.funcs[ei2].captureType2); }
else if ci2 == 3 { capName = mod.funcs[ei2].captureName3; capType = CBackend_TypeToC(mod.funcs[ei2].captureType3); }
else if ci2 == 4 { capName = mod.funcs[ei2].captureName4; capType = CBackend_TypeToC(mod.funcs[ei2].captureType4); }
else if ci2 == 5 { capName = mod.funcs[ei2].captureName5; capType = CBackend_TypeToC(mod.funcs[ei2].captureType5); }
else if ci2 == 6 { capName = mod.funcs[ei2].captureName6; capType = CBackend_TypeToC(mod.funcs[ei2].captureType6); }
else if ci2 == 7 { capName = mod.funcs[ei2].captureName7; capType = CBackend_TypeToC(mod.funcs[ei2].captureType7); }
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, capType);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, capName);
StringBuilder_Append(&cbe.sb, ";\n");
ci2 = ci2 + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, mod.funcs[ei2].envStructName);
StringBuilder_Append(&cbe.sb, ";\n\n");
}
ei2 = ei2 + 1;
}
// Forward declarations for all functions (skip generics)
var i: int = 0;
while i < mod.funcCount {
if !CBE_FuncHasGeneric(&mod.funcs[i]) {
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, ";\n");
// Maker for capturing closures
if mod.funcs[i].captureCount > 0 {
CBE_EmitMakerDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, ";\n");
}
}
i = i + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Extern declarations
i = 0;
while i < mod.externCount {
CBE_EmitFuncDecl(cbe, &mod.externFuncs[i]);
StringBuilder_Append(&cbe.sb, ";\n");
i = i + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Adapters before function bodies (bodies may take funcs as values)
CBE_EmitAllAdapters(cbe, mod);
// Function definitions (skip generics)
var hasMain: bool = false;
i = 0;
while i < mod.funcCount {
if String_Eq(mod.funcs[i].name, "Main") { hasMain = true; }
// Skip generic functions
if CBE_FuncHasGeneric(&mod.funcs[i]) {
i = i + 1;
continue;
}
// Skip forward declarations (functions without body)
let body: *HirNode = mod.funcs[i].body;
if body == null as *HirNode {
i = i + 1;
continue;
}
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, " {\n");
// Capturing closure thunk: materialize env from fat-func env pointer
if mod.funcs[i].captureCount > 0 && !String_Eq(mod.funcs[i].envStructName, "") && !String_Eq(mod.funcs[i].envInstanceName, "") {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, mod.funcs[i].envStructName);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, mod.funcs[i].envInstanceName);
StringBuilder_Append(&cbe.sb, " = *((");
StringBuilder_Append(&cbe.sb, mod.funcs[i].envStructName);
StringBuilder_Append(&cbe.sb, "*)__env);\n");
} else if mod.funcs[i].paramCount > 0 {
// Capture-less closure still has __env
if String_Eq(mod.funcs[i].param0.name, "__env") {
StringBuilder_Append(&cbe.sb, " (void)__env;\n");
}
}
// Body
cbe.checkedFunc = mod.funcs[i].checkedFunc;
cbe.deferCount = 0;
cbe.movedCount = 0;
cbe.tmpCounter = 0;
cbe.currentRetType = mod.funcs[i].retTypeName;
var hasReturn: bool = false;
cbe.indent = 1;
CBE_EmitExpr(cbe, body);
CBE_EmitDefers(cbe);
cbe.indent = 0;
// Check if body has a return statement
var stmt: *HirNode = body.child1;
while stmt != null as *HirNode {
if stmt.kind == hReturn { hasReturn = true; }
stmt = stmt.child3;
}
// Add default return 0 only if function returns int and has no explicit return
if String_Eq(mod.funcs[i].retTypeName, "int") && !hasReturn {
StringBuilder_Append(&cbe.sb, " return 0;\n");
}
StringBuilder_Append(&cbe.sb, "\n}\n\n");
// After capturing closure thunk, emit heap-env maker
if mod.funcs[i].captureCount > 0 {
CBE_EmitMakerFunc(cbe, &mod.funcs[i]);
}
i = i + 1;
}
// Generate C main wrapper if Main function exists
if hasMain {
StringBuilder_Append(&cbe.sb, "extern int g_argc;\n");
StringBuilder_Append(&cbe.sb, "extern char** g_argv;\n");
StringBuilder_Append(&cbe.sb, "int main(int argc, char** argv) {\n");
StringBuilder_Append(&cbe.sb, " g_argc = argc;\n");
StringBuilder_Append(&cbe.sb, " g_argv = argv;\n");
StringBuilder_Append(&cbe.sb, " return Main();\n");
StringBuilder_Append(&cbe.sb, "}\n");
}
return StringBuilder_Build(&cbe.sb);
}
}