From 64c7db5eb1ba6bc9e96c4705705d02a21034eb8b Mon Sep 17 00:00:00 2001 From: agra Date: Sat, 20 Jun 2026 09:08:05 +0300 Subject: [PATCH] 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). --- current/CHECKPOINT-ATOMICS.md | 17 +++++- .../expected/1700-atomics-load-store.exit | 2 +- .../expected/1700-atomics-load-store.stderr | 3 +- .../expected/1700-atomics-load-store.stdout | 4 +- src/backend/llvm/ops.zig | 60 ++++++++++++++----- src/ir/emit_llvm.test.zig | 34 +++++++++++ 6 files changed, 98 insertions(+), 22 deletions(-) diff --git a/current/CHECKPOINT-ATOMICS.md b/current/CHECKPOINT-ATOMICS.md index 77232f79..4bc185ef 100644 --- a/current/CHECKPOINT-ATOMICS.md +++ b/current/CHECKPOINT-ATOMICS.md @@ -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). diff --git a/examples/expected/1700-atomics-load-store.exit b/examples/expected/1700-atomics-load-store.exit index d00491fd..573541ac 100644 --- a/examples/expected/1700-atomics-load-store.exit +++ b/examples/expected/1700-atomics-load-store.exit @@ -1 +1 @@ -1 +0 diff --git a/examples/expected/1700-atomics-load-store.stderr b/examples/expected/1700-atomics-load-store.stderr index ef75e5a3..8b137891 100644 --- a/examples/expected/1700-atomics-load-store.stderr +++ b/examples/expected/1700-atomics-load-store.stderr @@ -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) + diff --git a/examples/expected/1700-atomics-load-store.stdout b/examples/expected/1700-atomics-load-store.stdout index 8b137891..186d0a1b 100644 --- a/examples/expected/1700-atomics-load-store.stdout +++ b/examples/expected/1700-atomics-load-store.stdout @@ -1 +1,3 @@ - +init: 7 +after store: 42 +incremented: 43 diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index 5f753983..32c5e606 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -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(); } diff --git a/src/ir/emit_llvm.test.zig b/src/ir/emit_llvm.test.zig index d2a322a5..0bec9773 100644 --- a/src/ir/emit_llvm.test.zig +++ b/src/ir/emit_llvm.test.zig @@ -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);