fibers B1.0c: support params in abi(.pure) (read from registers)
Adversarial review of B1.0b found a param-bearing abi(.pure) function
emitted invalid LLVM ("cannot use argument of naked function" — loud
verifier error, not silent) because the param-alloca loop spilled the
args to stack slots, which a naked function cannot have.
Fixed forward — this ENABLES the B1.3 context-switch use case rather
than rejecting it: gate the param-alloca loop on fd.abi != .pure in
decl.zig (both body-lowering paths) and generic.zig. A naked function's
args stay in their ABI registers and are read directly by the asm body
(e.g. swap_context reads from/to from x0/x1); the LLVM args are
declared-but-unused, which the verifier allows.
examples/1803-concurrency-pure-asm-param.sx: naked add(a, b) reads x0/x1
(add x0, x0, x1; ret) -> 40 + 2 = 42. aarch64-pinned.
Pack abi(.pure) (variadic + naked — nonsensical, can't read a runtime
pack from registers) left unsupported: pack.zig's param loop is
intertwined with comptime-param/#insert handling, so that case still
hits the loud verifier error. Documented in the checkpoint.
Also updates PLAN-FIBERS / CHECKPOINT-FIBERS for B1.0 completion.
B1.0 complete. Suite green (725/0).
This commit is contained in:
@@ -2660,13 +2660,19 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId
|
||||
const user_param_base: u32 = if (wants_ctx) 1 else 0;
|
||||
if (wants_ctx) self.current_ctx_ref = Ref.fromIndex(0);
|
||||
|
||||
for (fd.params, 0..) |p, i| {
|
||||
// An `abi(.pure)` (naked) function has no frame: its params arrive in ABI
|
||||
// registers and are read directly by the asm body (e.g. `swap_context`'s
|
||||
// `from`/`to`). Spilling them to allocas would (a) need a frame and (b) emit
|
||||
// `store i64 %0, …` — "cannot use argument of naked function" (LLVM verifier).
|
||||
// Leave the LLVM args declared-but-unused (the verifier allows that); the asm
|
||||
// references the registers.
|
||||
if (fd.abi != .pure) for (fd.params, 0..) |p, i| {
|
||||
const pty = self.resolveParamType(&p);
|
||||
const slot = self.builder.alloca(pty);
|
||||
const param_ref = Ref.fromIndex(@intCast(i + user_param_base));
|
||||
self.builder.store(slot, param_ref);
|
||||
scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true });
|
||||
}
|
||||
};
|
||||
|
||||
// Inbound entry points + abi(.c) sx functions: bind current_ctx_ref
|
||||
// to the static default before any user code runs.
|
||||
@@ -2812,7 +2818,10 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i
|
||||
const user_param_base_lf: u32 = if (wants_ctx_lf) 1 else 0;
|
||||
if (wants_ctx_lf) self.current_ctx_ref = Ref.fromIndex(0);
|
||||
|
||||
for (fd.params, 0..) |p, i| {
|
||||
// `abi(.pure)` (naked): params arrive in registers, read directly by the asm
|
||||
// body — no frame, no alloca/store (which the LLVM verifier rejects on a
|
||||
// naked function). See the sibling guard in the other body-lowering path.
|
||||
if (fd.abi != .pure) for (fd.params, 0..) |p, i| {
|
||||
const pty = self.resolveParamType(&p);
|
||||
// Allocate stack slot for param, store initial value.
|
||||
// Refs 0..N-1 are reserved for function parameters by beginFunction.
|
||||
@@ -2820,7 +2829,7 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i
|
||||
const param_ref = Ref.fromIndex(@intCast(i + user_param_base_lf));
|
||||
self.builder.store(slot, param_ref);
|
||||
scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true });
|
||||
}
|
||||
};
|
||||
|
||||
// Inbound entry points + abi(.c) sx functions: bind
|
||||
// current_ctx_ref to &__sx_default_context. See companion comment
|
||||
|
||||
@@ -128,7 +128,10 @@ pub fn monomorphizeFunction(self: *Lowering, fd: *const ast.FnDecl, mangled_name
|
||||
defer scope.deinit();
|
||||
self.scope = &scope;
|
||||
|
||||
{
|
||||
// `abi(.pure)` (naked): no frame — params arrive in registers, read by the
|
||||
// asm body, never spilled to allocas (the LLVM verifier rejects a naked
|
||||
// function that uses its arguments). Mirrors the decl-path guard.
|
||||
if (fd.abi != .pure) {
|
||||
var param_idx: u32 = if (wants_ctx) 1 else 0;
|
||||
for (fd.params) |p| {
|
||||
if (isTypeParamDecl(&p, fd.type_params)) continue;
|
||||
|
||||
Reference in New Issue
Block a user