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
This commit is contained in:
2026-06-11 09:39:11 +03:00
parent 224542df7f
commit 290cbc8f98
5 changed files with 66 additions and 24 deletions
+1
View File
@@ -275,6 +275,7 @@ struct Decl {
isAsync: bool, isAsync: bool,
isChecked: int, // @[Checked] attribute (0/1) isChecked: int, // @[Checked] attribute (0/1)
isDrop: int, // @[Drop] attribute (0/1) isDrop: int, // @[Drop] attribute (0/1)
isRelease: int, // @[Release] attribute (0/1)
// Names // Names
strValue: String, // decl name strValue: String, // decl name
strValue2: String, // interface name, dll name, module path strValue2: String, // interface name, dll name, module path
+42 -15
View File
@@ -190,11 +190,11 @@ func Cli_Compile(source: String, sourceName: String, targetTriple: String) -> St
// Build command // 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 // If building the standard project entry point, use full project build
// which merges stdlib and supports multi-file projects // which merges stdlib and supports multi-file projects
if String_Eq(srcPath, "src/Main.bux") || String_Eq(srcPath, "src/main.bux") { 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") { if rc == 0 && !String_Eq(outPath, "build/main") {
// Rename output if custom path was requested // Rename output if custom path was requested
bux_system(String_Concat(String_Concat("mv build/main ", outPath), " 2>/dev/null || true")); 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 // Compile with cc or clang for cross-compilation
PrintLine("Compiling C..."); PrintLine("Compiling C...");
var cmdBuf: StringBuilder = StringBuilder_NewCap(512); var cmdBuf: StringBuilder = StringBuilder_NewCap(512);
var optFlags: String = "-O2";
if isRelease {
optFlags = "-O3 -flto";
}
if !String_Eq(targetTriple, "") { 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, targetTriple);
StringBuilder_Append(&cmdBuf, " "); StringBuilder_Append(&cmdBuf, " ");
} else { } 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, "-o ");
StringBuilder_Append(&cmdBuf, outPath); StringBuilder_Append(&cmdBuf, outPath);
@@ -860,7 +868,7 @@ func Cli_Test(projectDir: String) -> int {
Print("Testing project: "); Print("Testing project: ");
PrintLine(projectDir); PrintLine(projectDir);
// Build the project first // Build the project first
let rc: int = Cli_BuildProject(projectDir, ""); let rc: int = Cli_BuildProject(projectDir, "", false);
if rc != 0 { if rc != 0 {
PrintLine("Test build failed"); PrintLine("Test build failed");
return rc; return rc;
@@ -886,7 +894,7 @@ func Cli_Test(projectDir: String) -> int {
return result; 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")); let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name; var outName: String = man.name;
if String_Eq(outName, "") { if String_Eq(outName, "") {
@@ -1147,12 +1155,20 @@ func Cli_BuildProject(projectDir: String, targetTriple: String) -> int {
PrintLine("Compiling C..."); PrintLine("Compiling C...");
let outBin: String = bux_path_join(buildDir, outName); let outBin: String = bux_path_join(buildDir, outName);
var ccBuf: StringBuilder = StringBuilder_NewCap(512); var ccBuf: StringBuilder = StringBuilder_NewCap(512);
var optFlags2: String = "-O2";
if isRelease {
optFlags2 = "-O3 -flto";
}
if !String_Eq(targetTriple, "") { 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, targetTriple);
StringBuilder_Append(&ccBuf, " "); StringBuilder_Append(&ccBuf, " ");
} else { } 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, "-o ");
StringBuilder_Append(&ccBuf, outBin); StringBuilder_Append(&ccBuf, outBin);
@@ -1184,8 +1200,8 @@ func Cli_BuildProject(projectDir: String, targetTriple: String) -> int {
// Run command — build project and execute // Run command — build project and execute
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func Cli_RunProject(projectDir: String, targetTriple: String) -> int { func Cli_RunProject(projectDir: String, targetTriple: String, isRelease: bool) -> int {
let rc: int = Cli_BuildProject(projectDir, targetTriple); let rc: int = Cli_BuildProject(projectDir, targetTriple, isRelease);
if rc != 0 { if rc != 0 {
return rc; return rc;
} }
@@ -1205,8 +1221,9 @@ func Cli_RunProject(projectDir: String, targetTriple: String) -> int {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func Cli_Run(args: *String, argCount: int) -> 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 targetTriple: String = "";
var isRelease: bool = false;
var ai: int = 1; var ai: int = 1;
while ai < argCount { while ai < argCount {
if String_Eq(args[ai], "--target") && ai + 1 < 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; j = j + 1;
} }
argCount = argCount - 2; 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; ai = ai + 1;
} }
@@ -1301,13 +1328,13 @@ func Cli_Run(args: *String, argCount: int) -> int {
let out: String = "build/main"; let out: String = "build/main";
if argCount >= 3 { src = args[2]; } if argCount >= 3 { src = args[2]; }
if argCount >= 4 { out = args[3]; } if argCount >= 4 { out = args[3]; }
return Cli_Build(src, out, targetTriple); return Cli_Build(src, out, targetTriple, isRelease);
} }
if String_Eq(cmd, "project") { if String_Eq(cmd, "project") {
let dir: String = "."; let dir: String = ".";
if argCount >= 3 { dir = args[2]; } if argCount >= 3 { dir = args[2]; }
return Cli_BuildProject(dir, targetTriple); return Cli_BuildProject(dir, targetTriple, isRelease);
} }
if String_Eq(cmd, "test") { if String_Eq(cmd, "test") {
@@ -1319,7 +1346,7 @@ func Cli_Run(args: *String, argCount: int) -> int {
if String_Eq(cmd, "run") { if String_Eq(cmd, "run") {
let dir: String = "."; let dir: String = ".";
if argCount >= 3 { dir = args[2]; } if argCount >= 3 { dir = args[2]; }
return Cli_RunProject(dir, targetTriple); return Cli_RunProject(dir, targetTriple, isRelease);
} }
Print("Unknown command: "); Print("Unknown command: ");
+6 -2
View File
@@ -33,6 +33,7 @@ struct LowerCtx {
substArg1: String, substArg1: String,
// Borrow checker state // Borrow checker state
checkedFunc: bool, checkedFunc: bool,
releaseFunc: bool,
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -775,7 +776,7 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// Index: arr[idx] // Index: arr[idx]
if kind == ekIndex { if kind == ekIndex {
// In @[Checked] functions, Array access goes through Array_Get with bounds check // 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); let elemType: String = Lcx_GetArrayElemType(expr.child1.refType);
if !String_Eq(elemType, "") { if !String_Eq(elemType, "") {
let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1); let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
@@ -906,7 +907,7 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
if kind == ekAssign { if kind == ekAssign {
// Array bounds-checking for write in @[Checked]: arr[idx] = val → Array_Set_T(&arr, idx, val) // Array bounds-checking for write in @[Checked]: arr[idx] = val → Array_Set_T(&arr, idx, val)
// Only for plain assignment (=), not compound operators (+=, -=, etc.) // 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); let elemType: String = Lcx_GetArrayElemType(expr.child1.child1.refType);
if !String_Eq(elemType, "") { if !String_Eq(elemType, "") {
let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1); 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 { func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
let oldChecked: bool = ctx.checkedFunc; let oldChecked: bool = ctx.checkedFunc;
ctx.checkedFunc = decl.isChecked != 0; ctx.checkedFunc = decl.isChecked != 0;
let oldRelease: bool = ctx.releaseFunc;
ctx.releaseFunc = decl.isRelease != 0;
let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc; let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc;
f.name = decl.strValue; f.name = decl.strValue;
@@ -2029,6 +2032,7 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
} }
ctx.scope = prevScope; ctx.scope = prevScope;
ctx.checkedFunc = oldChecked; ctx.checkedFunc = oldChecked;
ctx.releaseFunc = oldRelease;
return f; return f;
} }
+7 -1
View File
@@ -1571,9 +1571,10 @@ func parserParseDecl(p: *Parser) -> *Decl {
} }
let isPublic: bool = parserMatch(p, tkPub); let isPublic: bool = parserMatch(p, tkPub);
// Parse @[Checked] / @[Drop] attribute // Parse @[Checked] / @[Drop] / @[Release] attribute
var isChecked: int = 0; var isChecked: int = 0;
var isDrop: int = 0; var isDrop: int = 0;
var isRelease: int = 0;
if parserCheck(p, tkAt) { if parserCheck(p, tkAt) {
discard parserAdvance(p); // @ discard parserAdvance(p); // @
if parserCheck(p, tkLBracket) { if parserCheck(p, tkLBracket) {
@@ -1586,6 +1587,9 @@ func parserParseDecl(p: *Parser) -> *Decl {
if String_Eq(attrName.text, "Drop") { if String_Eq(attrName.text, "Drop") {
isDrop = 1; isDrop = 1;
} }
if String_Eq(attrName.text, "Release") {
isRelease = 1;
}
discard parserAdvance(p); // attribute name discard parserAdvance(p); // attribute name
} }
if parserCheck(p, tkRBracket) { if parserCheck(p, tkRBracket) {
@@ -1604,11 +1608,13 @@ func parserParseDecl(p: *Parser) -> *Decl {
discard parserAdvance(p); // async discard parserAdvance(p); // async
let d: *Decl = parserParseFuncDecl(p, isPublic, false, true); let d: *Decl = parserParseFuncDecl(p, isPublic, false, true);
d.isChecked = isChecked; d.isChecked = isChecked;
d.isRelease = isRelease;
return d; return d;
} }
if kind == tkFunc { if kind == tkFunc {
let d: *Decl = parserParseFuncDecl(p, isPublic, false, false); let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
d.isChecked = isChecked; d.isChecked = isChecked;
d.isRelease = isRelease;
return d; return d;
} }
if kind == tkStruct { if kind == tkStruct {
+10 -6
View File
@@ -20,6 +20,7 @@ struct Sema {
hasError: bool; hasError: bool;
currentRetType: int; // return type of the function being checked currentRetType: int; // return type of the function being checked
checkedFunc: bool; // true inside @[Checked] function checkedFunc: bool; // true inside @[Checked] function
releaseFunc: bool; // true inside @[Release] function
movedCount: int; // number of moved variables movedCount: int; // number of moved variables
movedName0: String; // inline moved var names (up to 8) movedName0: String; // inline moved var names (up to 8)
movedName1: String; movedName1: String;
@@ -424,7 +425,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
// Identifier — look up in scope // Identifier — look up in scope
if kind == ekIdent { if kind == ekIdent {
// Borrow check: use-after-move in @[Checked] functions // 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, Sema_EmitError(sema, expr.line, expr.column,
String_Concat("use of moved value '", String_Concat(expr.strValue, "'"))); 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; let op: int = expr.intValue;
// Borrow check: reject assignment through deref in @[Checked] functions // 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 { if expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar {
Sema_EmitError(sema, expr.line, expr.column, Sema_EmitError(sema, expr.line, expr.column,
"cannot assign through raw pointer in checked function — use '&mut T' instead"); "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 // 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 { if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
Sema_RemoveMoved(sema, expr.child1.strValue); 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 target: int = Sema_CheckExpr(sema, expr.child1);
let value: int = Sema_CheckExpr(sema, expr.child2); let value: int = Sema_CheckExpr(sema, expr.child2);
// Borrow check: reject assignment through deref in @[Checked] functions // 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, Sema_EmitError(sema, expr.line, expr.column,
"cannot assign through raw pointer in checked function — use '&mut T' instead"); "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; arg = arg.next;
} }
// Borrow check: reject double mutable borrow in @[Checked] functions // Borrow check: reject double mutable borrow in @[Checked] functions
if sema.checkedFunc { if sema.checkedFunc && !sema.releaseFunc {
var a: *ExprList = expr.callArgs; var a: *ExprList = expr.callArgs;
var ai: int = 0; var ai: int = 0;
while a != null as *ExprList { while a != null as *ExprList {
@@ -853,7 +854,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
if kind == skLet { if kind == skLet {
let initType: int = Sema_CheckExpr(sema, stmt.child1); let initType: int = Sema_CheckExpr(sema, stmt.child1);
// Borrow check: move tracking // 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); Sema_AddMoved(sema, stmt.child1.strValue);
} }
// Register variable in scope // Register variable in scope
@@ -1515,6 +1516,8 @@ func Sema_Analyze(mod: *Module) -> *Sema {
// Enable borrow checking for @[Checked] functions // Enable borrow checking for @[Checked] functions
let wasChecked: bool = s.checkedFunc; let wasChecked: bool = s.checkedFunc;
s.checkedFunc = decl.isChecked != 0; s.checkedFunc = decl.isChecked != 0;
let wasRelease: bool = s.releaseFunc;
s.releaseFunc = decl.isRelease != 0;
// Check body statements // Check body statements
var stmt: *Stmt = decl.refBody.firstStmt; var stmt: *Stmt = decl.refBody.firstStmt;
@@ -1524,6 +1527,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
} }
s.checkedFunc = wasChecked; s.checkedFunc = wasChecked;
s.releaseFunc = wasRelease;
s.scope = prevScope; s.scope = prevScope;
} }
decl = decl.childDecl2; decl = decl.childDecl2;