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,
|
defer5: *HirNode,
|
||||||
defer6: *HirNode,
|
defer6: *HirNode,
|
||||||
defer7: *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) {
|
func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
|
||||||
@@ -100,16 +109,76 @@ func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
|
|||||||
cbe.deferCount = cbe.deferCount + 1;
|
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 {
|
func CBE_EmitDefers(cbe: *CEmitter) -> int {
|
||||||
if cbe.deferCount == 0 { return 0; }
|
if cbe.deferCount == 0 { return 0; }
|
||||||
var i: int = cbe.deferCount - 1;
|
var i: int = cbe.deferCount - 1;
|
||||||
while i >= 0 {
|
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;
|
var dn: *HirNode = null as *HirNode;
|
||||||
if i == 0 { dn = cbe.defer0; }
|
if i == 0 { dn = cbe.defer0; }
|
||||||
if i == 1 { dn = cbe.defer1; }
|
if i == 1 { dn = cbe.defer1; }
|
||||||
@@ -119,6 +188,18 @@ func CBE_EmitDefers(cbe: *CEmitter) -> int {
|
|||||||
if i == 5 { dn = cbe.defer5; }
|
if i == 5 { dn = cbe.defer5; }
|
||||||
if i == 6 { dn = cbe.defer6; }
|
if i == 6 { dn = cbe.defer6; }
|
||||||
if i == 7 { dn = cbe.defer7; }
|
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);
|
CBE_EmitExpr(cbe, dn);
|
||||||
StringBuilder_Append(&cbe.sb, ";");
|
StringBuilder_Append(&cbe.sb, ";");
|
||||||
i = i - 1;
|
i = i - 1;
|
||||||
@@ -316,6 +397,10 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
|
|
||||||
// Return
|
// Return
|
||||||
if kind == hReturn {
|
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);
|
let hadDefers: int = CBE_EmitDefers(cbe);
|
||||||
if hadDefers != 0 {
|
if hadDefers != 0 {
|
||||||
StringBuilder_Append(&cbe.sb, "\n");
|
StringBuilder_Append(&cbe.sb, "\n");
|
||||||
@@ -345,6 +430,14 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
|
|||||||
|
|
||||||
// Store: combine alloca + value into single declaration
|
// Store: combine alloca + value into single declaration
|
||||||
if kind == hStore {
|
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 {
|
if node.child1 != null as *HirNode && node.child1.kind == hAlloca {
|
||||||
// Declaration with initializer: Type x = value;
|
// Declaration with initializer: Type x = value;
|
||||||
var ct: String = CBackend_TypeToC(node.child1.intValue);
|
var ct: String = CBackend_TypeToC(node.child1.intValue);
|
||||||
@@ -833,6 +926,8 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
cbe.sb = StringBuilder_NewCap(8192);
|
cbe.sb = StringBuilder_NewCap(8192);
|
||||||
cbe.indent = 0;
|
cbe.indent = 0;
|
||||||
cbe.mod = mod;
|
cbe.mod = mod;
|
||||||
|
cbe.deferCount = 0;
|
||||||
|
cbe.movedCount = 0;
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
StringBuilder_Append(&cbe.sb, "// Generated by Bux C Backend v2\n");
|
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");
|
StringBuilder_Append(&cbe.sb, " {\n");
|
||||||
// Body
|
// Body
|
||||||
cbe.checkedFunc = mod.funcs[i].checkedFunc;
|
cbe.checkedFunc = mod.funcs[i].checkedFunc;
|
||||||
|
cbe.deferCount = 0;
|
||||||
|
cbe.movedCount = 0;
|
||||||
var hasReturn: bool = false;
|
var hasReturn: bool = false;
|
||||||
cbe.indent = 1;
|
cbe.indent = 1;
|
||||||
CBE_EmitExpr(cbe, body);
|
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
|
// 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 dropName: String = String_Concat(typeName, "_Drop");
|
||||||
let dropSym: Symbol = Scope_Lookup(ctx.scope, dropName);
|
let dropSym: Symbol = Scope_Lookup(ctx.scope, dropName);
|
||||||
if dropSym.decl != null as *Decl {
|
if dropSym.decl != null as *Decl {
|
||||||
return dropName;
|
return dropName;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user