fibers B1.0b: abi(.pure) emits a real LLVM naked function (green)

Flip the B1.0a emit bail to real emission. The emit_llvm declaration
pass now adds LLVM's naked + noinline + nounwind attributes for an
is_pure function and skips frame-pointer=all (incompatible with a
frameless function); Pass 2 emits the body normally, and the naked
attribute makes the backend emit it verbatim (the inline asm + its own
ret) with no prologue/epilogue.

IR shape verified:
  ; Function Attrs: naked noinline nounwind
  define internal i64 @answer() #0 {
  entry:
    call void asm sideeffect "...ret...", ""()
    unreachable
  }
The caller invokes it as an ordinary () -> i64 call (.pure is
call_conv == .default).

- examples/1800-concurrency-pure-asm.sx: now green, aarch64-pinned
  (.build macos) -> exit 42 + .ir snapshot.
- examples/1801-concurrency-pure-generic.sx (renamed from -bail): the
  generic .pure now emits a correct naked answer__i64 (exit 42),
  proving generic.zig produces a naked body, not a framed one.
- examples/1802-concurrency-pure-asm-x86.sx: x86_64 cross sibling
  (.build x86_64-linux, ir-only here); .ir locks naked + movl $42,%eax.
- unit test in emit_llvm.test.zig asserts the naked attribute is present
  and frame-pointer absent on an abi(.pure) function.

Suite green (724/0).
This commit is contained in:
agra
2026-06-20 16:36:12 +03:00
parent 40424df1b8
commit 4b384788e6
21 changed files with 172 additions and 59 deletions

View File

@@ -1324,3 +1324,40 @@ test "emit: reflectArgRepr surfaces .unresolved for an unresolvable reflection a
try std.testing.expectEqual(LLVMEmitter.ReflectArgRepr.unresolved, emitter.reflectArgRepr(bogus));
try std.testing.expect(emitter.reflectArgRepr(bogus) != .bare);
}
test "emit: abi(.pure) function gets the naked attribute (no frame-pointer)" {
const alloc = std.testing.allocator;
var module = Module.init(alloc);
defer module.deinit();
var b = Builder.init(&module);
// func answer() -> i64 abi(.pure) { asm volatile { "ret" }; unreachable }
// The naked attribute is keyed off Function.is_pure in the declaration pass,
// independent of the body — a minimal asm + unreachable body suffices.
_ = b.beginFunction(str(&module, "answer"), &.{}, .i64);
b.currentFunc().is_pure = true;
const entry = b.appendBlock(str(&module, "entry"), &.{});
b.switchToBlock(entry);
b.emitVoid(.{ .inline_asm = .{
.template = str(&module, "ret"),
.operands = &.{},
.clobbers = &.{},
.has_side_effects = true,
} }, .void);
b.emitUnreachable();
b.finalize();
var emitter = LLVMEmitter.init(alloc, &module, "test_pure", .{});
defer emitter.deinit();
emitter.emit();
try std.testing.expect(emitter.verify());
const ir_str = emitter.dumpToString();
// The naked attribute is present; a naked function carries no frame-pointer
// attribute (incompatible with a frameless function).
try std.testing.expect(std.mem.indexOf(u8, ir_str, "naked") != null);
try std.testing.expect(std.mem.indexOf(u8, ir_str, "frame-pointer") == null);
}

View File

@@ -408,17 +408,9 @@ 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;
}
// `abi(.pure)` functions emit normally — the `naked` attribute (set
// in the declaration pass) makes the backend emit the body (inline
// asm + its own `ret`) with no prologue/epilogue. See Function.is_pure.
self.emitFunction(&func, @intCast(i));
}
@@ -1334,22 +1326,37 @@ pub const LLVMEmitter = struct {
// Add frame-pointer and nounwind attributes for correct ARM64 codegen
{
const fp_kind = "frame-pointer";
const fp_val = "all";
const fp_attr = c.LLVMCreateStringAttribute(
self.context,
fp_kind.ptr,
@intCast(fp_kind.len),
fp_val.ptr,
@intCast(fp_val.len),
);
const func_idx_attr: c.LLVMAttributeIndex = @bitCast(@as(i32, -1));
c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, fp_attr);
if (func.is_pure) {
// `abi(.pure)`: emit via LLVM's `naked` attribute — the backend
// emits the body verbatim (our inline asm + its own `ret`) with
// NO prologue/epilogue/frame. Do NOT request `frame-pointer`
// (incompatible with a frameless function). `noinline` keeps the
// asm body out of a framed caller; `nounwind` — naked asm never
// unwinds. See Function.is_pure / current/PLAN-FIBERS.md.
const naked_id = c.LLVMGetEnumAttributeKindForName("naked", 5);
c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, c.LLVMCreateEnumAttribute(self.context, naked_id, 0));
const noinline_id = c.LLVMGetEnumAttributeKindForName("noinline", 8);
c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, c.LLVMCreateEnumAttribute(self.context, noinline_id, 0));
const nounwind_id = c.LLVMGetEnumAttributeKindForName("nounwind", 8);
c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, c.LLVMCreateEnumAttribute(self.context, nounwind_id, 0));
} else {
const fp_kind = "frame-pointer";
const fp_val = "all";
const fp_attr = c.LLVMCreateStringAttribute(
self.context,
fp_kind.ptr,
@intCast(fp_kind.len),
fp_val.ptr,
@intCast(fp_val.len),
);
c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, fp_attr);
// Add nounwind
const nounwind_id = c.LLVMGetEnumAttributeKindForName("nounwind", 8);
const nounwind_attr = c.LLVMCreateEnumAttribute(self.context, nounwind_id, 0);
c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, nounwind_attr);
// Add nounwind
const nounwind_id = c.LLVMGetEnumAttributeKindForName("nounwind", 8);
const nounwind_attr = c.LLVMCreateEnumAttribute(self.context, nounwind_id, 0);
c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, nounwind_attr);
}
}
// Apple ARM64 ABI for >16B non-HFA composites: pass by reference