From 290cbc8f984a062669cae27e450d8ec8a429c4c9 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Thu, 11 Jun 2026 09:39:11 +0300 Subject: [PATCH] feat(release): add @[Release] attribute and --release CLI flag - @[Release] disables borrow checking and bounds checking in the function - --release passes -O3 -flto to the C compiler for optimized builds - Selfhost loop still deterministic --- src/ast.bux | 1 + src/cli.bux | 57 ++++++++++++++++++++++++++++++++++------------- src/hir_lower.bux | 8 +++++-- src/parser.bux | 8 ++++++- src/sema.bux | 16 ++++++++----- 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src/ast.bux b/src/ast.bux index 25bf60e..b4c0afb 100644 --- a/src/ast.bux +++ b/src/ast.bux @@ -275,6 +275,7 @@ struct Decl { isAsync: bool, isChecked: int, // @[Checked] attribute (0/1) isDrop: int, // @[Drop] attribute (0/1) + isRelease: int, // @[Release] attribute (0/1) // Names strValue: String, // decl name strValue2: String, // interface name, dll name, module path diff --git a/src/cli.bux b/src/cli.bux index a50843d..d6b2117 100644 --- a/src/cli.bux +++ b/src/cli.bux @@ -190,11 +190,11 @@ func Cli_Compile(source: String, sourceName: String, targetTriple: String) -> St // Build command // --------------------------------------------------------------------------- -func Cli_Build(srcPath: String, outPath: String, targetTriple: String) -> int { +func Cli_Build(srcPath: String, outPath: String, targetTriple: String, isRelease: bool) -> int { // If building the standard project entry point, use full project build // which merges stdlib and supports multi-file projects if String_Eq(srcPath, "src/Main.bux") || String_Eq(srcPath, "src/main.bux") { - let rc: int = Cli_BuildProject(".", targetTriple); + let rc: int = Cli_BuildProject(".", targetTriple, isRelease); if rc == 0 && !String_Eq(outPath, "build/main") { // Rename output if custom path was requested bux_system(String_Concat(String_Concat("mv build/main ", outPath), " 2>/dev/null || true")); @@ -248,12 +248,20 @@ func Cli_Build(srcPath: String, outPath: String, targetTriple: String) -> int { // Compile with cc or clang for cross-compilation PrintLine("Compiling C..."); var cmdBuf: StringBuilder = StringBuilder_NewCap(512); + var optFlags: String = "-O2"; + if isRelease { + optFlags = "-O3 -flto"; + } if !String_Eq(targetTriple, "") { - StringBuilder_Append(&cmdBuf, "clang -O2 -pthread -target "); + StringBuilder_Append(&cmdBuf, "clang "); + StringBuilder_Append(&cmdBuf, optFlags); + StringBuilder_Append(&cmdBuf, " -pthread -target "); StringBuilder_Append(&cmdBuf, targetTriple); StringBuilder_Append(&cmdBuf, " "); } else { - StringBuilder_Append(&cmdBuf, "cc -O2 -pthread "); + StringBuilder_Append(&cmdBuf, "cc "); + StringBuilder_Append(&cmdBuf, optFlags); + StringBuilder_Append(&cmdBuf, " -pthread "); } StringBuilder_Append(&cmdBuf, "-o "); StringBuilder_Append(&cmdBuf, outPath); @@ -860,7 +868,7 @@ func Cli_Test(projectDir: String) -> int { Print("Testing project: "); PrintLine(projectDir); // Build the project first - let rc: int = Cli_BuildProject(projectDir, ""); + let rc: int = Cli_BuildProject(projectDir, "", false); if rc != 0 { PrintLine("Test build failed"); return rc; @@ -886,7 +894,7 @@ func Cli_Test(projectDir: String) -> int { return result; } -func Cli_BuildProject(projectDir: String, targetTriple: String) -> int { +func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool) -> int { let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml")); var outName: String = man.name; if String_Eq(outName, "") { @@ -1147,12 +1155,20 @@ func Cli_BuildProject(projectDir: String, targetTriple: String) -> int { PrintLine("Compiling C..."); let outBin: String = bux_path_join(buildDir, outName); var ccBuf: StringBuilder = StringBuilder_NewCap(512); + var optFlags2: String = "-O2"; + if isRelease { + optFlags2 = "-O3 -flto"; + } if !String_Eq(targetTriple, "") { - StringBuilder_Append(&ccBuf, "clang -O2 -pthread -target "); + StringBuilder_Append(&ccBuf, "clang "); + StringBuilder_Append(&ccBuf, optFlags2); + StringBuilder_Append(&ccBuf, " -pthread -target "); StringBuilder_Append(&ccBuf, targetTriple); StringBuilder_Append(&ccBuf, " "); } else { - StringBuilder_Append(&ccBuf, "cc -O2 -pthread "); + StringBuilder_Append(&ccBuf, "cc "); + StringBuilder_Append(&ccBuf, optFlags2); + StringBuilder_Append(&ccBuf, " -pthread "); } StringBuilder_Append(&ccBuf, "-o "); StringBuilder_Append(&ccBuf, outBin); @@ -1184,8 +1200,8 @@ func Cli_BuildProject(projectDir: String, targetTriple: String) -> int { // Run command — build project and execute // --------------------------------------------------------------------------- -func Cli_RunProject(projectDir: String, targetTriple: String) -> int { - let rc: int = Cli_BuildProject(projectDir, targetTriple); +func Cli_RunProject(projectDir: String, targetTriple: String, isRelease: bool) -> int { + let rc: int = Cli_BuildProject(projectDir, targetTriple, isRelease); if rc != 0 { return rc; } @@ -1205,8 +1221,9 @@ func Cli_RunProject(projectDir: String, targetTriple: String) -> int { // --------------------------------------------------------------------------- func Cli_Run(args: *String, argCount: int) -> int { - /* Scan for --target flag before processing command */ + /* Scan for --target and --release flags before processing command */ var targetTriple: String = ""; + var isRelease: bool = false; var ai: int = 1; while ai < argCount { if String_Eq(args[ai], "--target") && ai + 1 < argCount { @@ -1218,7 +1235,17 @@ func Cli_Run(args: *String, argCount: int) -> int { j = j + 1; } argCount = argCount - 2; - break; + ai = ai - 1; + } else if String_Eq(args[ai], "--release") { + isRelease = true; + /* Remove --release from args by shifting */ + var j: int = ai; + while j + 1 < argCount { + args[j] = args[j + 1]; + j = j + 1; + } + argCount = argCount - 1; + ai = ai - 1; } ai = ai + 1; } @@ -1301,13 +1328,13 @@ func Cli_Run(args: *String, argCount: int) -> int { let out: String = "build/main"; if argCount >= 3 { src = args[2]; } if argCount >= 4 { out = args[3]; } - return Cli_Build(src, out, targetTriple); + return Cli_Build(src, out, targetTriple, isRelease); } if String_Eq(cmd, "project") { let dir: String = "."; if argCount >= 3 { dir = args[2]; } - return Cli_BuildProject(dir, targetTriple); + return Cli_BuildProject(dir, targetTriple, isRelease); } if String_Eq(cmd, "test") { @@ -1319,7 +1346,7 @@ func Cli_Run(args: *String, argCount: int) -> int { if String_Eq(cmd, "run") { let dir: String = "."; if argCount >= 3 { dir = args[2]; } - return Cli_RunProject(dir, targetTriple); + return Cli_RunProject(dir, targetTriple, isRelease); } Print("Unknown command: "); diff --git a/src/hir_lower.bux b/src/hir_lower.bux index cd0181a..05667b3 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -33,6 +33,7 @@ struct LowerCtx { substArg1: String, // Borrow checker state checkedFunc: bool, + releaseFunc: bool, } // --------------------------------------------------------------------------- @@ -775,7 +776,7 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { // Index: arr[idx] if kind == ekIndex { // In @[Checked] functions, Array access goes through Array_Get with bounds check - if ctx.checkedFunc && expr.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.refType) { + if ctx.checkedFunc && !ctx.releaseFunc && expr.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.refType) { let elemType: String = Lcx_GetArrayElemType(expr.child1.refType); if !String_Eq(elemType, "") { let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1); @@ -906,7 +907,7 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { if kind == ekAssign { // Array bounds-checking for write in @[Checked]: arr[idx] = val → Array_Set_T(&arr, idx, val) // Only for plain assignment (=), not compound operators (+=, -=, etc.) - if expr.intValue == tkAssign && ctx.checkedFunc && expr.child1 != null as *Expr && expr.child1.kind == ekIndex && expr.child1.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.child1.refType) { + if expr.intValue == tkAssign && ctx.checkedFunc && !ctx.releaseFunc && expr.child1 != null as *Expr && expr.child1.kind == ekIndex && expr.child1.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.child1.refType) { let elemType: String = Lcx_GetArrayElemType(expr.child1.child1.refType); if !String_Eq(elemType, "") { let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1); @@ -1944,6 +1945,8 @@ func Lcx_LowerParam(out: *HirParam, p: *Param, ctx: *LowerCtx) { func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc { let oldChecked: bool = ctx.checkedFunc; ctx.checkedFunc = decl.isChecked != 0; + let oldRelease: bool = ctx.releaseFunc; + ctx.releaseFunc = decl.isRelease != 0; let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc; f.name = decl.strValue; @@ -2029,6 +2032,7 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc { } ctx.scope = prevScope; ctx.checkedFunc = oldChecked; + ctx.releaseFunc = oldRelease; return f; } diff --git a/src/parser.bux b/src/parser.bux index 3a5fb66..b853680 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -1571,9 +1571,10 @@ func parserParseDecl(p: *Parser) -> *Decl { } let isPublic: bool = parserMatch(p, tkPub); - // Parse @[Checked] / @[Drop] attribute + // Parse @[Checked] / @[Drop] / @[Release] attribute var isChecked: int = 0; var isDrop: int = 0; + var isRelease: int = 0; if parserCheck(p, tkAt) { discard parserAdvance(p); // @ if parserCheck(p, tkLBracket) { @@ -1586,6 +1587,9 @@ func parserParseDecl(p: *Parser) -> *Decl { if String_Eq(attrName.text, "Drop") { isDrop = 1; } + if String_Eq(attrName.text, "Release") { + isRelease = 1; + } discard parserAdvance(p); // attribute name } if parserCheck(p, tkRBracket) { @@ -1604,11 +1608,13 @@ func parserParseDecl(p: *Parser) -> *Decl { discard parserAdvance(p); // async let d: *Decl = parserParseFuncDecl(p, isPublic, false, true); d.isChecked = isChecked; + d.isRelease = isRelease; return d; } if kind == tkFunc { let d: *Decl = parserParseFuncDecl(p, isPublic, false, false); d.isChecked = isChecked; + d.isRelease = isRelease; return d; } if kind == tkStruct { diff --git a/src/sema.bux b/src/sema.bux index c41cb7a..21c228d 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -20,6 +20,7 @@ struct Sema { hasError: bool; currentRetType: int; // return type of the function being checked checkedFunc: bool; // true inside @[Checked] function + releaseFunc: bool; // true inside @[Release] function movedCount: int; // number of moved variables movedName0: String; // inline moved var names (up to 8) movedName1: String; @@ -424,7 +425,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { // Identifier — look up in scope if kind == ekIdent { // Borrow check: use-after-move in @[Checked] functions - if sema.checkedFunc && Sema_IsMoved(sema, expr.strValue) { + if sema.checkedFunc && !sema.releaseFunc && Sema_IsMoved(sema, expr.strValue) { Sema_EmitError(sema, expr.line, expr.column, String_Concat("use of moved value '", String_Concat(expr.strValue, "'"))); } @@ -473,7 +474,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { let op: int = expr.intValue; // Borrow check: reject assignment through deref in @[Checked] functions - if sema.checkedFunc && op == tkAssign { + if sema.checkedFunc && !sema.releaseFunc && op == tkAssign { if expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar { Sema_EmitError(sema, expr.line, expr.column, "cannot assign through raw pointer in checked function — use '&mut T' instead"); @@ -481,7 +482,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { } // Borrow check: reinitialization after move - if sema.checkedFunc && op == tkAssign { + if sema.checkedFunc && !sema.releaseFunc && op == tkAssign { if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { Sema_RemoveMoved(sema, expr.child1.strValue); } @@ -567,7 +568,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { let target: int = Sema_CheckExpr(sema, expr.child1); let value: int = Sema_CheckExpr(sema, expr.child2); // Borrow check: reject assignment through deref in @[Checked] functions - if sema.checkedFunc && expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar { + if sema.checkedFunc && !sema.releaseFunc && expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar { Sema_EmitError(sema, expr.line, expr.column, "cannot assign through raw pointer in checked function — use '&mut T' instead"); } @@ -584,7 +585,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { arg = arg.next; } // Borrow check: reject double mutable borrow in @[Checked] functions - if sema.checkedFunc { + if sema.checkedFunc && !sema.releaseFunc { var a: *ExprList = expr.callArgs; var ai: int = 0; while a != null as *ExprList { @@ -853,7 +854,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) { if kind == skLet { let initType: int = Sema_CheckExpr(sema, stmt.child1); // Borrow check: move tracking - if sema.checkedFunc && stmt.child1 != null as *Expr && stmt.child1.kind == ekIdent { + if sema.checkedFunc && !sema.releaseFunc && stmt.child1 != null as *Expr && stmt.child1.kind == ekIdent { Sema_AddMoved(sema, stmt.child1.strValue); } // Register variable in scope @@ -1515,6 +1516,8 @@ func Sema_Analyze(mod: *Module) -> *Sema { // Enable borrow checking for @[Checked] functions let wasChecked: bool = s.checkedFunc; s.checkedFunc = decl.isChecked != 0; + let wasRelease: bool = s.releaseFunc; + s.releaseFunc = decl.isRelease != 0; // Check body statements var stmt: *Stmt = decl.refBody.firstStmt; @@ -1524,6 +1527,7 @@ func Sema_Analyze(mod: *Module) -> *Sema { } s.checkedFunc = wasChecked; + s.releaseFunc = wasRelease; s.scope = prevScope; } decl = decl.childDecl2;