selfhost: auto-Drop for Array<T> in @[Checked] functions

In @[Checked] functions, variables of type Array<T> automatically get
a defer Array_Free_T(&var) injected at declaration site. This ensures
Array values are freed when they go out of scope, preventing leaks.

Implementation:
- HIR lowering detects Array_<T> types in skLet statements
- Generates Array_Free_T generic instance if needed
- Builds synthetic defer node: defer Array_Free_T(&var)
- Wraps let+defer in hBlock for proper statement chaining

Also adds checkedFunc flag to HirFunc and CEmitter for future use.
This commit is contained in:
2026-06-10 11:40:11 +03:00
parent 5eec4328f3
commit a099999e02
3 changed files with 47 additions and 0 deletions
+2
View File
@@ -76,6 +76,7 @@ struct CEmitter {
sb: StringBuilder, sb: StringBuilder,
indent: int, indent: int,
mod: *HirModule, mod: *HirModule,
checkedFunc: bool,
deferCount: int, deferCount: int,
defer0: *HirNode, defer0: *HirNode,
defer1: *HirNode, defer1: *HirNode,
@@ -1081,6 +1082,7 @@ func CBackend_Generate(mod: *HirModule) -> String {
CBE_EmitFuncDecl(cbe, &mod.funcs[i]); CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, " {\n"); StringBuilder_Append(&cbe.sb, " {\n");
// Body // Body
cbe.checkedFunc = mod.funcs[i].checkedFunc;
var hasReturn: bool = false; var hasReturn: bool = false;
cbe.indent = 1; cbe.indent = 1;
CBE_EmitExpr(cbe, body); CBE_EmitExpr(cbe, body);
+1
View File
@@ -114,6 +114,7 @@ struct HirFunc {
captureType7: int; captureType7: int;
envStructName: String; envStructName: String;
envInstanceName: String; envInstanceName: String;
checkedFunc: bool;
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
+44
View File
@@ -973,6 +973,34 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
storeNode.child1 = alloca; storeNode.child1 = alloca;
storeNode.child2 = init; storeNode.child2 = init;
// Auto-Drop for Array<T> in @[Checked] functions
var deferNode: *HirNode = null as *HirNode;
if ctx.checkedFunc && !String_Eq(alloca.typeName, "") {
if String_StartsWith(alloca.typeName, "Array_") {
let elemType: String = bux_str_slice(alloca.typeName, 6, bux_strlen(alloca.typeName) - 6);
// Generate Array_Free instance
let genFree: *Decl = Lcx_FindGenericFunc(ctx, "Array_Free");
if genFree != null as *Decl {
Lcx_GenerateFuncInstance(ctx, genFree, elemType, "", 1);
}
// Build defer Array_Free_T(&var)
let varRef: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
varRef.kind = hVar;
varRef.strValue = stmt.strValue;
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = varRef;
let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
callNode.kind = hCall;
callNode.strValue = String_Concat("Array_Free_", elemType);
callNode.child1 = addrNode;
deferNode = bux_alloc(sizeof(HirNode)) as *HirNode;
deferNode.kind = hDefer;
deferNode.child1 = callNode;
}
}
// If init is a closure with captures, emit capture assignments before the let // If init is a closure with captures, emit capture assignments before the let
if stmt.child1 != null as *Expr && stmt.child1.kind == ekClosure && stmt.child1.captureCount > 0 { if stmt.child1 != null as *Expr && stmt.child1.kind == ekClosure && stmt.child1.captureCount > 0 {
let closureIdx: int = ctx.funcCount - 1; let closureIdx: int = ctx.funcCount - 1;
@@ -1029,9 +1057,24 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
lastStmt.child3 = storeNode; lastStmt.child3 = storeNode;
lastStmt = storeNode; lastStmt = storeNode;
} }
// Append auto-Drop defer if present
if deferNode != null as *HirNode {
lastStmt.child3 = deferNode;
lastStmt = deferNode;
}
blockNode.child1 = firstStmt; blockNode.child1 = firstStmt;
return blockNode; return blockNode;
} }
// Non-closure: wrap with defer if present
if deferNode != null as *HirNode {
storeNode.child3 = deferNode;
let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
blockNode.kind = hBlock;
blockNode.line = line;
blockNode.column = col;
blockNode.child1 = storeNode;
return blockNode;
}
return storeNode; return storeNode;
} }
@@ -1625,6 +1668,7 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc; let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc;
f.name = decl.strValue; f.name = decl.strValue;
f.isPublic = decl.isPublic; f.isPublic = decl.isPublic;
f.checkedFunc = ctx.checkedFunc;
f.paramCount = decl.paramCount; f.paramCount = decl.paramCount;
f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam; f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam;
Lcx_LowerParam(f.param0, &decl.param0, ctx); Lcx_LowerParam(f.param0, &decl.param0, ctx);