feat(drop): enforce @[Drop] attribute and add basic move semantics
- Auto-drop now only triggers for user-defined types explicitly marked with @[Drop] on the struct declaration. - Added move tracking in C backend to skip auto-drop for variables that have been moved (via let y = x, return x, or assignment). - Prevents double-free when ownership is transferred. - Selfhost bootstrap loop verified: C output is deterministic.
This commit is contained in:
+103
-6
@@ -86,6 +86,15 @@ struct CEmitter {
|
||||
defer5: *HirNode,
|
||||
defer6: *HirNode,
|
||||
defer7: *HirNode,
|
||||
movedCount: int,
|
||||
movedName0: String,
|
||||
movedName1: String,
|
||||
movedName2: String,
|
||||
movedName3: String,
|
||||
movedName4: String,
|
||||
movedName5: String,
|
||||
movedName6: String,
|
||||
movedName7: String,
|
||||
}
|
||||
|
||||
func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
|
||||
@@ -100,16 +109,76 @@ func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
|
||||
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;
|
||||
}
|
||||
|
||||
func CBE_EmitDefers(cbe: *CEmitter) -> int {
|
||||
if cbe.deferCount == 0 { return 0; }
|
||||
var i: int = cbe.deferCount - 1;
|
||||
while i >= 0 {
|
||||
StringBuilder_Append(&cbe.sb, "\n");
|
||||
var sp: int = 0;
|
||||
while sp < cbe.indent {
|
||||
StringBuilder_Append(&cbe.sb, " ");
|
||||
sp = sp + 1;
|
||||
}
|
||||
var dn: *HirNode = null as *HirNode;
|
||||
if i == 0 { dn = cbe.defer0; }
|
||||
if i == 1 { dn = cbe.defer1; }
|
||||
@@ -119,6 +188,18 @@ func CBE_EmitDefers(cbe: *CEmitter) -> int {
|
||||
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) {
|
||||
i = i - 1;
|
||||
continue;
|
||||
}
|
||||
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, ";");
|
||||
i = i - 1;
|
||||
@@ -316,6 +397,10 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
||||
|
||||
// Return
|
||||
if kind == hReturn {
|
||||
// Track moved variables via return
|
||||
if node.child1 != null as *HirNode && node.child1.kind == hVar {
|
||||
CBE_AddMoved(cbe, node.child1.strValue);
|
||||
}
|
||||
let hadDefers: int = CBE_EmitDefers(cbe);
|
||||
if hadDefers != 0 {
|
||||
StringBuilder_Append(&cbe.sb, "\n");
|
||||
@@ -345,6 +430,14 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
||||
|
||||
// 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);
|
||||
@@ -833,6 +926,8 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
||||
cbe.sb = StringBuilder_NewCap(8192);
|
||||
cbe.indent = 0;
|
||||
cbe.mod = mod;
|
||||
cbe.deferCount = 0;
|
||||
cbe.movedCount = 0;
|
||||
|
||||
// Header
|
||||
StringBuilder_Append(&cbe.sb, "// Generated by Bux C Backend v2\n");
|
||||
@@ -1084,6 +1179,8 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
||||
StringBuilder_Append(&cbe.sb, " {\n");
|
||||
// Body
|
||||
cbe.checkedFunc = mod.funcs[i].checkedFunc;
|
||||
cbe.deferCount = 0;
|
||||
cbe.movedCount = 0;
|
||||
var hasReturn: bool = false;
|
||||
cbe.indent = 1;
|
||||
CBE_EmitExpr(cbe, body);
|
||||
|
||||
@@ -2264,11 +2264,14 @@ func Lcx_BuildAutoDropFree(ctx: *LowerCtx, typeName: String) -> String {
|
||||
}
|
||||
}
|
||||
// User-defined types with @[Drop]: look for TypeName_Drop function
|
||||
let typeSym: Symbol = Scope_Lookup(ctx.scope, typeName);
|
||||
if typeSym.kind == skType && typeSym.decl != null as *Decl && typeSym.decl.isDrop != 0 {
|
||||
let dropName: String = String_Concat(typeName, "_Drop");
|
||||
let dropSym: Symbol = Scope_Lookup(ctx.scope, dropName);
|
||||
if dropSym.decl != null as *Decl {
|
||||
return dropName;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user