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:
2026-06-10 19:08:15 +03:00
parent 7392013301
commit 8f8708c5df
2 changed files with 110 additions and 10 deletions
+103 -6
View File
@@ -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);