atomics A.0b: real seq_cst load/store emission (green)
Replace the A.0a emit bail with real LLVM atomic codegen: - emitAtomicLoad: LLVMBuildLoad2 + LLVMSetOrdering + LLVMSetAlignment - emitAtomicStore: LLVMBuildStore + LLVMSetOrdering + LLVMSetAlignment (value coerced to the pointee type, mirroring emitStore) - llvmOrdering: explicit sx AtomicOrdering -> LLVMAtomicOrdering map (LLVM's enum is non-contiguous; never an identity cast) examples/1700 now prints 7/42/43; IR is 'load atomic i64, ptr .. seq_cst, align 8' + 'store atomic ..'. Unit test 'emit: atomic load/store (seq_cst, aligned)' locks the emission shape (load atomic/store atomic/seq_cst/align 8) without a fragile full-module .ir snapshot. Suite green (710 examples + units).
This commit is contained in:
@@ -4,9 +4,17 @@ Companion to [PLAN-ATOMICS.md](PLAN-ATOMICS.md). Update after every step (one st
|
||||
time, per the cadence rule). New corpus category: `17xx`.
|
||||
|
||||
## Last completed step
|
||||
**A.0a (lock commit) — DONE.** Full atomic load/store plumbing landed with LLVM emission
|
||||
deliberately bailing loudly; `examples/1700-atomics-load-store.sx` locked to the bail
|
||||
diagnostic (exit 1). Suite green (710 examples, 0 failed; 476 units).
|
||||
**A.0b (green) — DONE.** Real atomic load/store emission: `LLVMBuildLoad2`/`LLVMBuildStore`
|
||||
+ `LLVMSetOrdering` + mandatory `LLVMSetAlignment`, ordering via an explicit
|
||||
sx-tag→`LLVMAtomicOrdering` switch (`llvmOrdering`). `examples/1700` green (7/42/43); IR
|
||||
shows `load atomic i64, ptr … seq_cst, align 8` + `store atomic …`. Added unit test
|
||||
`emit: atomic load/store (seq_cst, aligned)` in `emit_llvm.test.zig` (asserts `load
|
||||
atomic`/`store atomic`/`seq_cst`/`align 8`). No fragile full-module `.ir` snapshot for 1700
|
||||
(it uses `print`); the unit test is the emission-shape gate. Suite green (710 + units).
|
||||
|
||||
### Earlier — A.0a (lock commit)
|
||||
Full atomic load/store plumbing with LLVM emission deliberately bailing loudly;
|
||||
`examples/1700` locked to the bail diagnostic.
|
||||
- `library/modules/std/atomic.sx`: `Ordering` enum, `Atomic($T)` struct (`init`/`load`/
|
||||
`store`, **seq_cst-only** — see capability gap below), `atomic_load`/`atomic_store`
|
||||
`#builtin` decls. **Opt-in import**, NOT in the universal `std.sx` facade (mirrors
|
||||
@@ -64,3 +72,6 @@ diagnostic (exit 1). Suite green (710 examples, 0 failed; 476 units).
|
||||
AtomicOrdering) + recognizer + print/vm arms + emit BAIL; locked `examples/1700` to the
|
||||
bail diagnostic. Reverted a universal-facade wiring that churned 37 `.ir` snapshots
|
||||
(Ordering would bloat every program's type table). Suite green (710/0).
|
||||
- **A.0b** — real atomic load/store emission (LLVMBuildLoad2/Store + SetOrdering +
|
||||
SetAlignment; explicit sx→LLVM ordering switch). 1700 green (7/42/43, `load atomic …
|
||||
seq_cst, align 8`). Unit test added. Suite green (710 + units).
|
||||
|
||||
@@ -1 +1 @@
|
||||
1
|
||||
0
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
error: atomic load LLVM emission not yet implemented (Stream A, A.0b)
|
||||
error: atomic store LLVM emission not yet implemented (Stream A, A.0b)
|
||||
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
|
||||
init: 7
|
||||
after store: 42
|
||||
incremented: 43
|
||||
|
||||
@@ -366,25 +366,55 @@ pub const Ops = struct {
|
||||
}
|
||||
|
||||
// ── Atomics ───────────────────────────────────────────
|
||||
// A.0a (Stream A) lock: the IR ops, lowering, and comptime VM are wired,
|
||||
// but LLVM emission deliberately BAILS LOUDLY (clean diagnostic + build
|
||||
// abort via `comptime_failed`) rather than silently emitting a non-atomic
|
||||
// load/store. A.0b replaces these bodies with the real builders:
|
||||
// load: LLVMBuildLoad2 + LLVMSetOrdering + LLVMSetAlignment
|
||||
// store: LLVMBuildStore + LLVMSetOrdering + LLVMSetAlignment
|
||||
// (ordering via an explicit sx-tag → LLVMAtomicOrdering switch).
|
||||
// Atomic load/store = ordinary LLVMBuildLoad2/Store made atomic via
|
||||
// LLVMSetOrdering, with a MANDATORY explicit alignment (the LLVM verifier
|
||||
// rejects atomic load/store without it). singleThread stays 0 (cross-thread
|
||||
// ordering). The sx ordering tag → LLVM ordering map is explicit (LLVM's
|
||||
// enum is non-contiguous), never an identity cast.
|
||||
fn llvmOrdering(o: ir_inst.AtomicOrdering) c.LLVMAtomicOrdering {
|
||||
return switch (o) {
|
||||
.relaxed => c.LLVMAtomicOrderingMonotonic,
|
||||
.acquire => c.LLVMAtomicOrderingAcquire,
|
||||
.release => c.LLVMAtomicOrderingRelease,
|
||||
.acq_rel => c.LLVMAtomicOrderingAcquireRelease,
|
||||
.seq_cst => c.LLVMAtomicOrderingSequentiallyConsistent,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn emitAtomicLoad(self: Ops, instruction: *const Inst, a: AtomicLoad) void {
|
||||
_ = a;
|
||||
std.debug.print("error: atomic load LLVM emission not yet implemented (Stream A, A.0b)\n", .{});
|
||||
self.e.comptime_failed = true;
|
||||
// Keep emit from crashing downstream: yield an undef of the result type.
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(if (instruction.ty == .void) .i64 else instruction.ty)));
|
||||
const ptr = self.e.resolveRef(a.ptr);
|
||||
const ptr_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(ptr));
|
||||
if (ptr_kind == c.LLVMPointerTypeKind and instruction.ty != .void) {
|
||||
const llvm_ty = self.e.toLLVMType(instruction.ty);
|
||||
const result = c.LLVMBuildLoad2(self.e.builder, llvm_ty, ptr, "atomic_load");
|
||||
c.LLVMSetOrdering(result, llvmOrdering(a.ordering));
|
||||
c.LLVMSetAlignment(result, @intCast(self.e.ir_mod.types.typeSizeBytes(instruction.ty)));
|
||||
self.e.mapRef(result);
|
||||
} else {
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(if (instruction.ty == .void) .i64 else instruction.ty)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emitAtomicStore(self: Ops, a: AtomicStore) void {
|
||||
_ = a;
|
||||
std.debug.print("error: atomic store LLVM emission not yet implemented (Stream A, A.0b)\n", .{});
|
||||
self.e.comptime_failed = true;
|
||||
const ptr = self.e.resolveRef(a.ptr);
|
||||
var val = self.e.resolveRef(a.val);
|
||||
const ptr_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(ptr));
|
||||
const val_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(val));
|
||||
if (ptr_kind == c.LLVMPointerTypeKind and val_kind != c.LLVMVoidTypeKind) {
|
||||
// Coerce the value to the pointer's IR target type, mirroring emitStore.
|
||||
if (self.e.getRefIRType(a.ptr)) |ptr_ir_ty| {
|
||||
const pointee_info = self.e.ir_mod.types.get(ptr_ir_ty);
|
||||
const target_ty: ?c.LLVMTypeRef = switch (pointee_info) {
|
||||
.pointer => |p| self.e.toLLVMType(p.pointee),
|
||||
else => null,
|
||||
};
|
||||
if (target_ty) |tt| val = self.e.coerceArg(val, tt);
|
||||
}
|
||||
const st = c.LLVMBuildStore(self.e.builder, val, ptr);
|
||||
c.LLVMSetOrdering(st, llvmOrdering(a.ordering));
|
||||
const align_ty = if (a.val_ty != .void) a.val_ty else .i64;
|
||||
c.LLVMSetAlignment(st, @intCast(self.e.ir_mod.types.typeSizeBytes(align_ty)));
|
||||
}
|
||||
self.e.advanceRefCounter();
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,40 @@ test "emit: alloca, store, load" {
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i64") != null);
|
||||
}
|
||||
|
||||
test "emit: atomic load/store (seq_cst, aligned)" {
|
||||
const alloc = std.testing.allocator;
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> i64 { var x: i64; atomic_store(&x, 10, seq_cst);
|
||||
// return atomic_load(&x, seq_cst); }
|
||||
_ = b.beginFunction(str(&module, "f"), &.{}, .i64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const x_ptr = b.alloca(.i64);
|
||||
const ten = b.constInt(10, .i64);
|
||||
b.emitVoid(.{ .atomic_store = .{ .ptr = x_ptr, .val = ten, .val_ty = .i64, .ordering = .seq_cst } }, .void);
|
||||
const loaded = b.emit(.{ .atomic_load = .{ .ptr = x_ptr, .ordering = .seq_cst } }, .i64);
|
||||
b.ret(loaded, .i64);
|
||||
b.finalize();
|
||||
|
||||
var emitter = LLVMEmitter.init(alloc, &module, "test_atomic", .{});
|
||||
defer emitter.deinit();
|
||||
emitter.emit();
|
||||
|
||||
try std.testing.expect(emitter.verify());
|
||||
|
||||
const ir_str = emitter.dumpToString();
|
||||
// Atomic load/store with seq_cst ordering AND a mandatory alignment.
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "load atomic") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "store atomic") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "seq_cst") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "align 8") != null);
|
||||
}
|
||||
|
||||
test "emit: comparison and branch" {
|
||||
const alloc = std.testing.allocator;
|
||||
var module = Module.init(alloc);
|
||||
|
||||
Reference in New Issue
Block a user