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:
@@ -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
|
||||
|
||||
+42
-15
@@ -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: ");
|
||||
|
||||
+6
-2
@@ -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;
|
||||
}
|
||||
|
||||
+7
-1
@@ -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 {
|
||||
|
||||
+10
-6
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user