feat(selfhost): HirNode.sourceFile for mid-function #line maps
Stamp each HIR node with Decl source path; C backend tracks lastDebugFile so #line re-emits when file or line changes. Closures inherit the enclosing function path. Smoke checks Util_Double body maps to Util.bux.
This commit is contained in:
+23
-6
@@ -80,6 +80,7 @@ module CBackend {
|
||||
currentRetType: String,
|
||||
// #line debug maps (E.4 selfhost parity)
|
||||
lastDebugLine: int,
|
||||
lastDebugFile: String, // re-emit when file changes mid-function (HirNode.sourceFile)
|
||||
emitDebugLines: bool,
|
||||
currentFile: String,
|
||||
}
|
||||
@@ -140,14 +141,24 @@ module CBackend {
|
||||
if node == null as *HirNode { return; }
|
||||
if node.line == 0 { return; }
|
||||
let ln: int = node.line as int;
|
||||
if ln == cbe.lastDebugLine { return; }
|
||||
// Prefer per-node sourceFile (multi-file spans); else function-level currentFile
|
||||
var file: String = cbe.currentFile;
|
||||
if node.sourceFile != null as String && !String_Eq(node.sourceFile, "") {
|
||||
file = node.sourceFile;
|
||||
}
|
||||
if file == null as String { file = ""; }
|
||||
// Skip only when both line and file unchanged
|
||||
if ln == cbe.lastDebugLine && String_Eq(file, cbe.lastDebugFile) {
|
||||
return;
|
||||
}
|
||||
cbe.lastDebugLine = ln;
|
||||
cbe.lastDebugFile = file;
|
||||
// #line must start at column 0
|
||||
StringBuilder_Append(&cbe.sb, "#line ");
|
||||
StringBuilder_AppendInt(&cbe.sb, ln as int64);
|
||||
if !String_Eq(cbe.currentFile, "") {
|
||||
if !String_Eq(file, "") {
|
||||
StringBuilder_Append(&cbe.sb, " \"");
|
||||
StringBuilder_Append(&cbe.sb, cbe.currentFile);
|
||||
StringBuilder_Append(&cbe.sb, file);
|
||||
StringBuilder_Append(&cbe.sb, "\"");
|
||||
}
|
||||
StringBuilder_Append(&cbe.sb, "\n");
|
||||
@@ -1405,6 +1416,7 @@ module CBackend {
|
||||
cbe.movedCount = 0;
|
||||
cbe.tmpCounter = 0;
|
||||
cbe.lastDebugLine = 0;
|
||||
cbe.lastDebugFile = "";
|
||||
cbe.emitDebugLines = true;
|
||||
cbe.currentFile = "";
|
||||
// Optional override for all #line paths (default: per-func Decl.sourceFile)
|
||||
@@ -1697,12 +1709,17 @@ module CBackend {
|
||||
continue;
|
||||
}
|
||||
// Per-function source path for multi-file #line (overrides BUX_DEBUG_FILE only if set)
|
||||
if !String_Eq(mod.funcs[i].sourceFile, "") {
|
||||
cbe.currentFile = mod.funcs[i].sourceFile;
|
||||
// BUX_DEBUG_FILE forces one path for the whole unit; skip per-func when set.
|
||||
let forcedDbg: String = bux_getenv("BUX_DEBUG_FILE");
|
||||
if forcedDbg == null as String || String_Eq(forcedDbg, "") {
|
||||
if mod.funcs[i].sourceFile != null as String && !String_Eq(mod.funcs[i].sourceFile, "") {
|
||||
cbe.currentFile = mod.funcs[i].sourceFile;
|
||||
}
|
||||
}
|
||||
cbe.lastDebugLine = 0;
|
||||
cbe.lastDebugFile = "";
|
||||
// #line before the function itself
|
||||
if cbe.emitDebugLines && !String_Eq(cbe.currentFile, "") {
|
||||
if cbe.emitDebugLines && cbe.currentFile != null as String && !String_Eq(cbe.currentFile, "") {
|
||||
StringBuilder_Append(&cbe.sb, "#line 1 \"");
|
||||
StringBuilder_Append(&cbe.sb, cbe.currentFile);
|
||||
StringBuilder_Append(&cbe.sb, "\"\n");
|
||||
|
||||
+4
-1
@@ -66,6 +66,9 @@ module Hir {
|
||||
// Extra data pointer (for children arrays, field lists, etc.)
|
||||
extraData: *void;
|
||||
extraCount: int;
|
||||
// Per-node source path for #line (multi-file / rare mid-function spans)
|
||||
// Empty → C backend falls back to HirFunc.sourceFile / currentFile
|
||||
sourceFile: String;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -182,7 +185,7 @@ module Hir {
|
||||
typeKind: 0, typeName: "",
|
||||
strValue: "", intValue: 0, boolValue: false,
|
||||
child1: null as *HirNode, child2: null as *HirNode, child3: null as *HirNode,
|
||||
extraData: null as *void, extraCount: 0 };
|
||||
extraData: null as *void, extraCount: 0, sourceFile: "" };
|
||||
}
|
||||
|
||||
func Hir_MakeLit(tokKind: int, tokText: String, line: uint32, col: uint32) -> HirNode {
|
||||
|
||||
@@ -52,6 +52,33 @@ module HirLower {
|
||||
patMapTo6: String,
|
||||
patMapFrom7: String,
|
||||
patMapTo7: String,
|
||||
// Active source path for #line stamping (from Decl.sourceFile)
|
||||
currentSourceFile: String,
|
||||
}
|
||||
|
||||
/// Fill empty HirNode.sourceFile from `file` (does not overwrite set paths).
|
||||
/// Walks child1/2/3, call arg lists, and if-else via extraData.
|
||||
func Lcx_StampSourceFile(node: *HirNode, file: String) {
|
||||
if node == null as *HirNode { return; }
|
||||
if file == null as String || String_Eq(file, "") { return; }
|
||||
if node.sourceFile == null as String || String_Eq(node.sourceFile, "") {
|
||||
node.sourceFile = file;
|
||||
}
|
||||
Lcx_StampSourceFile(node.child1, file);
|
||||
Lcx_StampSourceFile(node.child2, file);
|
||||
Lcx_StampSourceFile(node.child3, file);
|
||||
// hCall / multi-arg: linked HirArgList in extraData
|
||||
if (node.kind == hCall || node.kind == hCallIndirect) && node.extraData != null as *void {
|
||||
var cur: *HirArgList = node.extraData as *HirArgList;
|
||||
while cur != null as *HirArgList {
|
||||
Lcx_StampSourceFile(cur.node, file);
|
||||
cur = cur.next;
|
||||
}
|
||||
}
|
||||
// hIf else branch may live in extraData (*HirNode)
|
||||
if node.kind == hIf && node.extraData != null as *void {
|
||||
Lcx_StampSourceFile(node.extraData as *HirNode, file);
|
||||
}
|
||||
}
|
||||
|
||||
func Lcx_PatLookup(ctx: *LowerCtx, src: String) -> String {
|
||||
@@ -3424,6 +3451,8 @@ module HirLower {
|
||||
f.isPublic = decl.isPublic;
|
||||
f.checkedFunc = ctx.checkedFunc;
|
||||
f.sourceFile = decl.sourceFile;
|
||||
let oldSourceFile: String = ctx.currentSourceFile;
|
||||
ctx.currentSourceFile = decl.sourceFile;
|
||||
f.paramCount = decl.paramCount;
|
||||
f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
||||
Lcx_LowerParam(f.param0, &decl.param0, ctx);
|
||||
@@ -3507,12 +3536,16 @@ module HirLower {
|
||||
ctx.scope = &funcScope;
|
||||
if decl.refBody != null as *Block {
|
||||
f.body = Lcx_LowerBlock(ctx, decl.refBody, f.retTypeKind);
|
||||
// Stamp every node with this function's source path (nodes may already
|
||||
// carry a different file for rare multi-file spans; those are kept).
|
||||
Lcx_StampSourceFile(f.body, f.sourceFile);
|
||||
} else {
|
||||
f.body = null as *HirNode;
|
||||
}
|
||||
ctx.scope = prevScope;
|
||||
ctx.checkedFunc = oldChecked;
|
||||
ctx.releaseFunc = oldRelease;
|
||||
ctx.currentSourceFile = oldSourceFile;
|
||||
|
||||
return f;
|
||||
}
|
||||
@@ -3528,6 +3561,8 @@ module HirLower {
|
||||
let numStr: String = String_FromInt(ctx.funcCount);
|
||||
f.name = String_Concat("__closure_", numStr);
|
||||
f.isPublic = false;
|
||||
// Closures inherit the enclosing function's source path
|
||||
f.sourceFile = ctx.currentSourceFile;
|
||||
|
||||
let params: *Decl = expr.closureParams;
|
||||
// Fat-func ABI: leading void* __env, then user params
|
||||
@@ -3641,6 +3676,7 @@ module HirLower {
|
||||
ctx.envInstanceName = envInstanceName;
|
||||
if expr.refBlock != null as *Block {
|
||||
f.body = Lcx_LowerBlock(ctx, expr.refBlock, -1);
|
||||
Lcx_StampSourceFile(f.body, f.sourceFile);
|
||||
} else {
|
||||
f.body = null as *HirNode;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user