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
This commit is contained in:
+112
-20
@@ -2,6 +2,7 @@
|
||||
// Generates C code from the HIR.
|
||||
module CBackend {
|
||||
|
||||
extern func bux_getenv(name: String) -> String;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Type → C type name
|
||||
@@ -77,6 +78,79 @@ module CBackend {
|
||||
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) {
|
||||
@@ -406,9 +480,10 @@ module CBackend {
|
||||
// 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 {
|
||||
// Track moved variables via return (skip auto-drop of moved-out locals)
|
||||
if node.child1 != null as *HirNode && node.child1.kind == hVar {
|
||||
CBE_AddMoved(cbe, node.child1.strValue);
|
||||
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.
|
||||
@@ -762,6 +837,8 @@ module CBackend {
|
||||
|
||||
// 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, "){");
|
||||
@@ -792,6 +869,7 @@ module CBackend {
|
||||
child = child.child3;
|
||||
continue;
|
||||
}
|
||||
CBE_EmitDebugLine(cbe, child);
|
||||
// Indent
|
||||
var sp: int = 0;
|
||||
while sp < cbe.indent {
|
||||
@@ -1158,21 +1236,22 @@ module CBackend {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func CBE_IsGenericTypeName(name: String) -> bool {
|
||||
if String_Eq(name, "T") || String_Eq(name, "K") || String_Eq(name, "V") { return true; }
|
||||
if String_Eq(name, "T*") || String_Eq(name, "K*") || String_Eq(name, "V*") { return true; }
|
||||
// Any type name containing '<' is a generic instantiation or parameter
|
||||
if String_Contains(name, "<") { return true; }
|
||||
// Generic container structs and their pointer variants
|
||||
if String_Eq(name, "Array") || String_Eq(name, "Array*") { return true; }
|
||||
if String_Eq(name, "Channel") || String_Eq(name, "Channel*") { return true; }
|
||||
if String_Eq(name, "Iter") || String_Eq(name, "Iter*") { return true; }
|
||||
if String_Eq(name, "Set") || String_Eq(name, "Set*") { return true; }
|
||||
if String_Eq(name, "SetEntry") || String_Eq(name, "SetEntry*") { return true; }
|
||||
if String_Eq(name, "Map") || String_Eq(name, "Map*") { return true; }
|
||||
if String_Eq(name, "MapEntry") || String_Eq(name, "MapEntry*") { return true; }
|
||||
if String_Eq(name, "StringMap") || String_Eq(name, "StringMap*") { return true; }
|
||||
if String_Eq(name, "StringMapEntry") || String_Eq(name, "StringMapEntry*") { return true; }
|
||||
if String_Eq(name, "Slice") || String_Eq(name, "Slice*") { return true; }
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1234,7 +1313,7 @@ module CBackend {
|
||||
func CBE_StructHasValueStructField(st: *HirStruct) -> bool {
|
||||
var fi: int = 0;
|
||||
while fi < st.fieldCount {
|
||||
let ft: String = st.fields[fi].typeName;
|
||||
let ft: String = CBE_NormalizeTypeName(st.fields[fi].typeName);
|
||||
if !CBE_IsPrimitiveTypeName(ft) && !String_EndsWith(ft, "*") {
|
||||
return true;
|
||||
}
|
||||
@@ -1251,7 +1330,7 @@ module CBackend {
|
||||
var fi: int = 0;
|
||||
while fi < st.fieldCount {
|
||||
StringBuilder_Append(&cbe.sb, " ");
|
||||
var ft: String = st.fields[fi].typeName;
|
||||
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") {
|
||||
@@ -1325,6 +1404,19 @@ module CBackend {
|
||||
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");
|
||||
|
||||
+12
-1
@@ -4073,7 +4073,18 @@ module HirLower {
|
||||
hm.structs[si].fields[fi].typeName = String_Concat(ftype.pointerPointee.typeName, "*");
|
||||
}
|
||||
} else if !String_Eq(ftype.typeName, "") {
|
||||
hm.structs[si].fields[fi].typeName = ftype.typeName;
|
||||
// Monomorphize container fields: Array<int> → Array_int
|
||||
// so C backend does not skip the parent struct as "generic".
|
||||
var tn: String = ftype.typeName;
|
||||
if ftype.typeArgCount >= 1 && !String_Eq(ftype.typeArgName0, "") {
|
||||
if String_Eq(tn, "Array") || String_Eq(tn, "Set") ||
|
||||
String_Eq(tn, "Channel") || String_Eq(tn, "Iter") {
|
||||
tn = String_Concat(String_Concat(tn, "_"), ftype.typeArgName0);
|
||||
} else if String_Eq(tn, "Map") && ftype.typeArgCount >= 2 {
|
||||
tn = String_Concat(String_Concat(String_Concat("Map_", ftype.typeArgName0), "_"), ftype.typeArgName1);
|
||||
}
|
||||
}
|
||||
hm.structs[si].fields[fi].typeName = tn;
|
||||
}
|
||||
}
|
||||
fi = fi + 1;
|
||||
|
||||
Reference in New Issue
Block a user