fibers B1.0a: plumb abi(.pure), emit bails (lock)
First implementation step of Stream B1 (fibers). Make the inert abi(.pure) ABI carry an is_pure flag through lowering, with LLVM emission deliberately bailing loudly until B1.0b — the lock half of the lock->green cadence. - IR Function.is_pure, set from fd.abi == .pure at both declareFunction decl sites. - funcWantsImplicitCtx skips .pure (no synthetic __sx_ctx, mirroring the .c skip): a pure fn reads args from ABI registers, an implicit ctx would occupy a register slot the asm doesn't expect. - both body-lowering paths bypass lowerValueBody for .pure: lower the asm body as statements + cap with unreachable. A pure body has no sx return (the asm rets itself), so the implicit-return diagnostic must not fire. - emit_llvm Pass 2 bails loudly when func.is_pure (build-gating nonzero exit) rather than emit a framed body, whose epilogue would corrupt a context switch's deliberate SP-in != SP-out. examples/1800-concurrency-pure-asm.sx: one host example (no .build pin -- the bail fires before instruction selection, so it is host-independent), locked to the bail snapshot. B1.0b flips emit to LLVM's naked attribute + asm-only body and pins the example per-arch. The sx-facing name is "pure" throughout (field, diagnostic); LLVM's naked attribute is only the B1.0b lowering mechanism. Suite green (722/0).
This commit is contained in:
@@ -408,6 +408,17 @@ pub const LLVMEmitter = struct {
|
||||
// its only references are in comptime code, so DCE drops the leftover
|
||||
// declaration. See current/PLAN-COMPILER-VM.md (S3).
|
||||
if (func.is_compiler_domain) continue;
|
||||
// B1.0a (lock): `abi(.pure)` emission is not implemented yet — the
|
||||
// LLVM `naked` attribute + asm-only body land in B1.0b. Bail LOUDLY
|
||||
// (build-gating, like a comptime failure) rather than emit a framed
|
||||
// body, whose prologue/epilogue would corrupt the deliberate
|
||||
// SP-in ≠ SP-out of a context switch. See current/PLAN-FIBERS.md.
|
||||
if (func.is_pure) {
|
||||
const fname = self.ir_mod.types.getString(func.name);
|
||||
std.debug.print("error: `abi(.pure)` function '{s}' LLVM emission not yet implemented\n", .{fname});
|
||||
self.comptime_failed = true;
|
||||
continue;
|
||||
}
|
||||
self.emitFunction(&func, @intCast(i));
|
||||
}
|
||||
|
||||
|
||||
@@ -640,6 +640,16 @@ pub const Function = struct {
|
||||
/// drops the leftover declaration. See current/PLAN-COMPILER-VM.md (S3).
|
||||
is_compiler_domain: bool = false,
|
||||
|
||||
/// True for an `abi(.pure)` function — no calling-convention
|
||||
/// prologue/epilogue/frame, no implicit `__sx_ctx`. Its body is a single
|
||||
/// inline-asm block that reads args from ABI registers and emits its own
|
||||
/// `ret` (the context-switch primitive; design §4.6). emit_llvm lowers this
|
||||
/// via LLVM's `naked` function attribute and generates no frame setup. A
|
||||
/// `.c` epilogue would restore SP from the wrong stack across a context
|
||||
/// switch (SP-in ≠ SP-out by design), which is why `.pure` is distinct
|
||||
/// from `.c`.
|
||||
is_pure: bool = false,
|
||||
|
||||
pub const Param = struct {
|
||||
name: StringId,
|
||||
ty: TypeId,
|
||||
|
||||
@@ -513,6 +513,11 @@ pub fn detectContextDecl(decls: []const *const Node) bool {
|
||||
pub fn funcWantsImplicitCtx(self: *const Lowering, fd: *const ast.FnDecl) bool {
|
||||
if (!self.implicit_ctx_enabled) return false;
|
||||
if (fd.abi == .c) return false;
|
||||
// An `abi(.pure)` function has no frame and no synthetic params — its body
|
||||
// is a single asm block reading args from ABI registers. No implicit
|
||||
// `__sx_ctx` (it would occupy a register slot the asm doesn't expect).
|
||||
// See Function.is_pure.
|
||||
if (fd.abi == .pure) return false;
|
||||
// A BODILESS `abi(.compiler)` decl (compiler-API surface) is dispatched by name
|
||||
// to a Zig/VM handler with exactly the declared args; an implicit `__sx_ctx`
|
||||
// prepend would shift every arg (breaking the handler's arity check). No sx
|
||||
@@ -2310,6 +2315,7 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
|
||||
func.source_file = self.current_source_file;
|
||||
func.is_variadic = is_variadic;
|
||||
func.has_implicit_ctx = wants_ctx;
|
||||
func.is_pure = (fd.abi == .pure);
|
||||
self.extern_name_map.put(name, c_name) catch {};
|
||||
self.fn_decl_fids.put(fd, fid) catch {};
|
||||
return;
|
||||
@@ -2323,6 +2329,7 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
|
||||
func.source_file = self.current_source_file;
|
||||
func.is_variadic = is_variadic;
|
||||
func.has_implicit_ctx = wants_ctx;
|
||||
func.is_pure = (fd.abi == .pure);
|
||||
if (weldedCompilerFn(self, fd, name)) func.compiler_welded = true;
|
||||
// A BODIED `abi(.compiler)` function is a user compiler-domain function (e.g. a
|
||||
// post-link callback): the VM runs its sx body, but it NEVER runs in the binary
|
||||
@@ -2672,7 +2679,15 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId
|
||||
// Lower the function body (set target_type to return type for implicit returns)
|
||||
const saved_target = self.target_type;
|
||||
self.target_type = if (ret_ty != .void and ret_ty != .noreturn) ret_ty else null;
|
||||
if (ret_ty != .void and ret_ty != .noreturn) {
|
||||
if (self.builder.currentFunc().is_pure) {
|
||||
// `abi(.pure)`: the body is a single asm block that emits its own `ret`.
|
||||
// There is no sx-level value return — lower the statements and cap the
|
||||
// block with `unreachable` (control never falls back into sx). This
|
||||
// bypasses the implicit-return machinery, which would otherwise reject
|
||||
// the missing return. LLVM emission lands in B1.0b.
|
||||
self.lowerBlock(fd.body);
|
||||
if (!self.currentBlockHasTerminator()) self.builder.emitUnreachable();
|
||||
} else if (ret_ty != .void and ret_ty != .noreturn) {
|
||||
self.lowerValueBody(fd.body, ret_ty);
|
||||
} else {
|
||||
// void / noreturn: no value to return — lower as statements and let
|
||||
@@ -2819,7 +2834,12 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i
|
||||
// Lower the function body, capturing the last expression's value for implicit return
|
||||
const saved_target = self.target_type;
|
||||
self.target_type = if (ret_ty != .void and ret_ty != .noreturn) ret_ty else null;
|
||||
if (ret_ty != .void and ret_ty != .noreturn) {
|
||||
if (self.builder.currentFunc().is_pure) {
|
||||
// `abi(.pure)`: asm-only body that rets itself — see the sibling path
|
||||
// above. Lower statements, cap with `unreachable`; emission is B1.0b.
|
||||
self.lowerBlock(fd.body);
|
||||
if (!self.currentBlockHasTerminator()) self.builder.emitUnreachable();
|
||||
} else if (ret_ty != .void and ret_ty != .noreturn) {
|
||||
self.lowerValueBody(fd.body, ret_ty);
|
||||
} else {
|
||||
// void / noreturn: no value to return — lower as statements and
|
||||
|
||||
Reference in New Issue
Block a user