From 8f8708c5dfa3945d586930d05e553905803cef64 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Wed, 10 Jun 2026 19:08:15 +0300 Subject: [PATCH] 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. --- src/c_backend.bux | 109 +++++++++++++++++++++++++++++++++++++++++++--- src/hir_lower.bux | 11 +++-- 2 files changed, 110 insertions(+), 10 deletions(-) diff --git a/src/c_backend.bux b/src/c_backend.bux index 7e7e516..5838b6e 100644 --- a/src/c_backend.bux +++ b/src/c_backend.bux @@ -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); diff --git a/src/hir_lower.bux b/src/hir_lower.bux index f1f1d6b..6a2ce2c 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -2264,10 +2264,13 @@ func Lcx_BuildAutoDropFree(ctx: *LowerCtx, typeName: String) -> String { } } // User-defined types with @[Drop]: look for TypeName_Drop function - let dropName: String = String_Concat(typeName, "_Drop"); - let dropSym: Symbol = Scope_Lookup(ctx.scope, dropName); - if dropSym.decl != null as *Decl { - return dropName; + 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 ""; }