feat: async/await/spawn + fix else-if lowering bug

Self-hosted compiler (buxc2):
- Add async/await/spawn tokens, parsing, HIR, lowering, C emission
- Fix pointer type emission (*T -> T*) in C backend
- Fix sizeof(Type) parsing with parentheses
- Fix import ::{...} infinite loop guard
- Fix int64 emission by avoiding else-if chain workaround
- Add debug-free cli and hir_lower

Bootstrap compiler (Nim):
- Fix critical else-if lowering bug in hir_lower.nim:
  when both elseIfs and else block exist, elseIfs were dropped
  causing all else-if chains to collapse to if+else only

Runtime:
- Fix bux_async_await memory corruption (don't free in run)
- Add bux_remove_from_ready for safe cleanup
- Fix forward declaration and deduplicate bux_now_ms

Docs & examples:
- Update async.bux example with proper int64 params
- Update README, LanguageRef, Stdlib, PLAN for async features
- Mark Phase 7.10 bootstrap loop as completed

All 27 examples pass. buxc2 check/build work on all examples.
This commit is contained in:
2026-06-01 02:57:46 +03:00
parent 42041dfd74
commit 21f8b2e85a
24 changed files with 448 additions and 110 deletions
+3
View File
@@ -93,6 +93,8 @@ const ekTry: int = 20;
const ekUnwrap: int = 23;
const ekBlock: int = 21;
const ekMatch: int = 22;
const ekSpawn: int = 24;
const ekAwait: int = 25;
struct Expr {
kind: int,
@@ -223,6 +225,7 @@ struct Decl {
line: uint32,
column: uint32,
isPublic: bool,
isAsync: bool,
// Names
strValue: String, // decl name
strValue2: String, // interface name, dll name, module path
+21 -16
View File
@@ -145,6 +145,26 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return;
}
// spawn Callee(args)
if kind == hSpawn {
StringBuilder_Append(&cbe.sb, "bux_async_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue);
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, ", (void*)");
CBE_EmitExpr(cbe, node.child1);
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// await
if kind == hAwait {
StringBuilder_Append(&cbe.sb, "bux_async_await(");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Return
if kind == hReturn {
StringBuilder_Append(&cbe.sb, "return");
@@ -371,24 +391,9 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
// Return type
if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") {
StringBuilder_Append(&cbe.sb, "void ");
} else if String_Eq(f.retTypeName, "String") || String_Eq(f.retTypeName, "str") {
StringBuilder_Append(&cbe.sb, "String ");
} else if String_Eq(f.retTypeName, "bool") {
StringBuilder_Append(&cbe.sb, "bool ");
} else if String_Eq(f.retTypeName, "int") {
StringBuilder_Append(&cbe.sb, "int ");
} else if String_Eq(f.retTypeName, "int64") {
StringBuilder_Append(&cbe.sb, "int64 ");
} else if String_Eq(f.retTypeName, "uint") {
StringBuilder_Append(&cbe.sb, "uint ");
} else if String_Eq(f.retTypeName, "float64") {
StringBuilder_Append(&cbe.sb, "float64 ");
} else if f.retTypeKind == tyNamed || !String_Eq(f.retTypeName, "") {
// Use the struct name directly (e.g., "Point", "Type")
} else {
StringBuilder_Append(&cbe.sb, f.retTypeName);
StringBuilder_Append(&cbe.sb, " ");
} else {
StringBuilder_Append(&cbe.sb, "int "); // fallback
}
StringBuilder_Append(&cbe.sb, f.name);
+3
View File
@@ -76,6 +76,9 @@ func Cli_Compile(source: String, sourceName: String) -> String {
PrintLine("HIR lowering failed");
return "";
}
Print("HIR funcCount=");
PrintInt(hirMod.funcCount);
PrintLine("");
// Phase 5: C code generation
let cCode: String = CBackend_Generate(hirMod);
+2
View File
@@ -31,6 +31,8 @@ const hSliceInit: int = 25;
const hRange: int = 26;
const hTupleInit: int = 27;
const hMatch: int = 28;
const hSpawn: int = 29;
const hAwait: int = 30;
// ---------------------------------------------------------------------------
// HirNode — unified struct with tagged union pattern
+19
View File
@@ -122,6 +122,25 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
return n;
}
// spawn Callee(args)
if kind == ekSpawn {
n.kind = hSpawn;
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue;
}
if expr.child2 != null as *Expr {
n.child1 = Lcx_LowerExpr(ctx, expr.child2);
}
return n;
}
// expr.await
if kind == ekAwait {
n.kind = hAwait;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
return n;
}
// Index: arr[idx]
if kind == ekIndex {
n.kind = hIndexPtr;
+3
View File
@@ -294,6 +294,9 @@ func lexKeywordKind(text: String) -> int {
if String_Eq(text, "self") { return tkSelf; }
if String_Eq(text, "super") { return tkSuper; }
if String_Eq(text, "sizeof") { return tkSizeOf; }
if String_Eq(text, "async") { return tkAsync; }
if String_Eq(text, "await") { return tkAwait; }
if String_Eq(text, "spawn") { return tkSpawn; }
return tkIdent;
}
+50 -13
View File
@@ -127,15 +127,9 @@ func parserParseType(p: *Parser) -> *TypeExpr {
te.line = line;
te.column = col;
te.pointerPointee = parserParseType(p);
// Set typeName to "String*" for String pointer, else "int*"
// Set typeName to "Pointee*"
if te.pointerPointee != null as *TypeExpr {
if String_Eq(te.pointerPointee.typeName, "String") {
te.typeName = "String*";
} else if String_Eq(te.pointerPointee.typeName, "int") {
te.typeName = "int*";
} else {
te.typeName = "int*";
}
te.typeName = String_Concat(te.pointerPointee.typeName, "*");
}
return te;
}
@@ -241,8 +235,32 @@ func parserParsePrimary(p: *Parser) -> *Expr {
// sizeof(Type)
if kind == tkSizeOf {
discard parserAdvance(p);
discard parserExpect(p, tkLParen, "expected '(' after sizeof");
let e: *Expr = parserMakeExpr(ekSizeOf, line, col);
e.refType = parserParseType(p);
discard parserExpect(p, tkRParen, "expected ')' after sizeof type");
return e;
}
// spawn Callee(args)
if kind == tkSpawn {
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekSpawn, line, col);
e.child1 = parserParsePrimary(p);
// Optional call arguments
if parserCheck(p, tkLParen) {
discard parserAdvance(p);
if !parserCheck(p, tkRParen) {
e.child2 = parserParseExpr(p);
if parserMatch(p, tkComma) {
e.child3 = parserParseExpr(p);
while parserMatch(p, tkComma) {
discard parserParseExpr(p);
}
}
}
discard parserExpect(p, tkRParen, "expected ')' after spawn arguments");
}
return e;
}
@@ -322,6 +340,20 @@ func parserParsePostfix(p: *Parser) -> *Expr {
continue;
}
// .await
if kind == tkDot {
if parserPeek(p, 1) == tkAwait {
discard parserAdvance(p); // .
discard parserAdvance(p); // await
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let e: *Expr = parserMakeExpr(ekAwait, line, col);
e.child1 = left;
left = e;
continue;
}
}
// Field: expr.name
if kind == tkDot {
discard parserAdvance(p);
@@ -765,7 +797,7 @@ func parserParseParamList(p: *Parser) -> *Decl {
// Declarations
// ---------------------------------------------------------------------------
func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool) -> *Decl {
func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkFunc, "expected 'func'");
@@ -776,6 +808,7 @@ func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool) -> *Decl {
d.line = line;
d.column = col;
d.isPublic = isPublic;
d.isAsync = isAsync;
d.strValue = nameTok.text;
// Type params <T, U>
@@ -953,7 +986,7 @@ func parserParseImportDecl(p: *Parser, isPublic: bool) -> *Decl {
// Parse path: Std::Io::PrintLine
var pathStr: String = "";
var segCount: int = 0;
while parserCheck(p, tkIdent) || (segCount > 0 && parserCheck(p, tkColonColon)) {
while parserCheck(p, tkIdent) || (segCount > 0 && parserCheck(p, tkColonColon) && parserPeek(p, 1) != tkLBrace) {
if segCount > 0 {
discard parserAdvance(p); // ::
if String_Len(pathStr) > 0 {
@@ -995,7 +1028,7 @@ func parserParseExternDecl(p: *Parser, isPublic: bool) -> *Decl {
discard parserExpect(p, tkExtern, "expected 'extern'");
if parserCheck(p, tkFunc) {
let d: *Decl = parserParseFuncDecl(p, isPublic, true);
let d: *Decl = parserParseFuncDecl(p, isPublic, true, false);
d.kind = dkExternFunc;
return d;
}
@@ -1016,7 +1049,11 @@ func parserParseDecl(p: *Parser) -> *Decl {
let isPublic: bool = parserMatch(p, tkPub);
let kind: int = parserPeek(p, 0);
if kind == tkFunc { return parserParseFuncDecl(p, isPublic, false); }
if kind == tkAsync && parserPeek(p, 1) == tkFunc {
discard parserAdvance(p); // async
return parserParseFuncDecl(p, isPublic, false, true);
}
if kind == tkFunc { return parserParseFuncDecl(p, isPublic, false, false); }
if kind == tkStruct { return parserParseStructDecl(p, isPublic); }
if kind == tkEnum { return parserParseEnumDecl(p, isPublic); }
if kind == tkImport { return parserParseImportDecl(p, isPublic); }
@@ -1048,7 +1085,7 @@ func parserParseDecl(p: *Parser) -> *Decl {
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
if parserCheck(p, tkFunc) {
let m: *Decl = parserParseFuncDecl(p, false, false);
let m: *Decl = parserParseFuncDecl(p, false, false, false);
// Store method in linked list
if d.methodCount == 0 {
d.childDecl1 = m;
+7
View File
@@ -133,6 +133,11 @@ const tkNewLine: int = 99;
const tkEndOfFile: int = 100;
const tkUnknown: int = 101;
// Async / concurrency
const tkAsync: int = 102;
const tkAwait: int = 103;
const tkSpawn: int = 104;
// ---------------------------------------------------------------------------
// Token struct
// ---------------------------------------------------------------------------
@@ -154,6 +159,8 @@ func Token_IsKeyword(kind: int) -> bool {
if kind >= tkBreak && kind <= tkMatch { return true; }
if kind >= tkFunc && kind <= tkExtern { return true; }
if kind >= tkAs && kind <= tkSuper { return true; }
if kind == tkSizeOf { return true; }
if kind >= tkAsync && kind <= tkSpawn { return true; }
return false;
}