From 79895be4014d0a7ae9c1477bc1ff12be06def36f Mon Sep 17 00:00:00 2001 From: agra Date: Sat, 20 Jun 2026 10:57:01 +0300 Subject: [PATCH] atomics A.2b: real CAS emission (cmpxchg) + unit test (green) emitAtomicCmpxchg: LLVMBuildAtomicCmpXchg (success/failure orderings, singleThread=0) returns a {T, i1} pair; LLVMSetWeak for the weak variant. The sx ?T result (null = SUCCESS) is built as { extractvalue 0 (actual value), xor(extractvalue 1 (success), true) } -- has_value = NOT success. Integer-only (recognizer guard), so never a pointer/niche optional. examples/1702 green: successful CAS returns null (value updated), failing CAS returns the actual value (unchanged), weak retry loop increments a counter (100 -> 105). LLVM IR shows `cmpxchg ... acq_rel acquire` and `cmpxchg weak`. Unit test `emit: atomic cmpxchg (strong + weak)` locks `cmpxchg` + the weak marker. Suite green (718/0). --- current/CHECKPOINT-ATOMICS.md | 32 +++++++++--------- examples/expected/1702-atomics-cas.exit | 2 +- examples/expected/1702-atomics-cas.stderr | 4 +-- examples/expected/1702-atomics-cas.stdout | 4 ++- src/backend/llvm/ops.zig | 41 +++++++++++++++++++---- src/ir/emit_llvm.test.zig | 34 +++++++++++++++++++ 6 files changed, 91 insertions(+), 26 deletions(-) diff --git a/current/CHECKPOINT-ATOMICS.md b/current/CHECKPOINT-ATOMICS.md index cc871331..5e957c09 100644 --- a/current/CHECKPOINT-ATOMICS.md +++ b/current/CHECKPOINT-ATOMICS.md @@ -4,17 +4,19 @@ 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.2a (CAS lock) — DONE**. `compare_exchange`/`_weak` wired end-to-end EXCEPT LLVM emission -(bails loudly; A.2b makes it real). New IR op `atomic_cmpxchg` + `AtomicCmpxchg{ptr, cmp, new, -val_ty, success_ordering, failure_ordering, weak}`; result type = `?T` (null = success). print -arm; emit dispatch → `emitAtomicCmpxchg` (BAILS). comptime_vm arm implements real single-thread -CAS (read actual / compare / store-on-equal / build `?T`: success→none, failure→some(actual); -weak == strong at comptime). Recognizer extended (`atomic_cmpxchg`/`_weak`, 6 args) — CAS -restricted to INTEGER T (loud reject); BOTH orderings resolved via `atomicOrderingFromNode`; -dual-ordering validation (failure may not be release/acq_rel nor stronger than success, via -`atomicOrderingRank`). Methods `compare_exchange`/`_weak` on `Atomic($T)` with comptime -`$success`/`$failure: Ordering`. `examples/1702` locked to the bail; `examples/1186` locks the -rejected ordering pair (failure=.seq_cst > success=.relaxed). Suite green (718/0). +**A.2 (CAS) — DONE** (A.2a lock + A.2b green). `compare_exchange`/`_weak` → LLVM `cmpxchg` +(result **`?T`, null = SUCCESS**; failure carries the actual value for retry). New IR op +`atomic_cmpxchg` + `AtomicCmpxchg{ptr, cmp, new, val_ty, success_ordering, failure_ordering, +weak}`. `emitAtomicCmpxchg`: `LLVMBuildAtomicCmpXchg` (success/failure orderings, singleThread=0) +→ `{T, i1}` pair; `LLVMSetWeak` for weak; `?T` result = `{ extractvalue 0 (actual), +xor(extractvalue 1, true) }` (has_value = NOT success). comptime_vm arm does real single-thread +CAS (read/compare/store-on-equal, build `?T`; weak == strong at comptime). Recognizer +(`atomic_cmpxchg`/`_weak`, 6 args) — CAS restricted to INTEGER T; BOTH orderings via +`atomicOrderingFromNode`; dual-ordering validation (failure may not be release/acq_rel nor +stronger than success, `atomicOrderingRank`). Methods `compare_exchange`/`_weak` on `Atomic($T)` +with comptime `$success`/`$failure: Ordering`. `examples/1702` green (CAS ok→20 / fail actual=20 / +weak retry loop 100→105); `examples/1186` locks a rejected ordering pair; unit test `emit: atomic +cmpxchg (strong + weak)` asserts `cmpxchg` + `cmpxchg weak`. Suite green (718/0). ### A.1 (RMW) — DONE (A.1a lock + A.1b green) `fetch_add/sub/and/or/xor` + `fetch_min/max` → LLVM `atomicrmw` (returns OLD value). New IR op @@ -24,10 +26,10 @@ rejected loudly); all five orderings valid for RMW. comptime_vm does real single `examples/1701` green; unit test locks `atomicrmw add` + signed `min` vs unsigned `umin`. ## Next step -**A.2b — CAS green**: replace the `emitAtomicCmpxchg` bail with real `LLVMBuildAtomicCmpXchg` -(success/failure orderings; `LLVMSetWeak` for weak). Build the `?T` result: `extractvalue 1` = -success (i1); `has = xor(success, true)` (null = success); `insertvalue {actual, has}`. Green -`examples/1702`; unit test asserting `cmpxchg` + the weak marker. +**A.3 — fence** (`atomic_fence($o: Ordering)` → LLVM `fence`), per PLAN-ATOMICS. No value +result; ordering must be acquire/release/acq_rel/seq_cst (relaxed is meaningless for a fence — +reject loudly). New IR op `atomic_fence` + dispatch/print/comptime_vm (no-op single-thread) + +`LLVMBuildFence`. Lock-then-green cadence as before. ### Earlier — A.0c (guard hardening) Adversarial review of A.0 found two CRITICAL silent-wrong defects (raw LLVM verifier errors diff --git a/examples/expected/1702-atomics-cas.exit b/examples/expected/1702-atomics-cas.exit index d00491fd..573541ac 100644 --- a/examples/expected/1702-atomics-cas.exit +++ b/examples/expected/1702-atomics-cas.exit @@ -1 +1 @@ -1 +0 diff --git a/examples/expected/1702-atomics-cas.stderr b/examples/expected/1702-atomics-cas.stderr index 9cc08078..8b137891 100644 --- a/examples/expected/1702-atomics-cas.stderr +++ b/examples/expected/1702-atomics-cas.stderr @@ -1,3 +1 @@ -error: atomic compare-exchange LLVM emission not yet implemented (Stream A, A.2b) -error: atomic compare-exchange LLVM emission not yet implemented (Stream A, A.2b) -error: atomic compare-exchange LLVM emission not yet implemented (Stream A, A.2b) + diff --git a/examples/expected/1702-atomics-cas.stdout b/examples/expected/1702-atomics-cas.stdout index 8b137891..3db0a82f 100644 --- a/examples/expected/1702-atomics-cas.stdout +++ b/examples/expected/1702-atomics-cas.stdout @@ -1 +1,3 @@ - +cas ok, now: 20 +cas failed, actual: 20, still: 20 +after loop: 105 diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index c6dd05fb..1917cb52 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -426,13 +426,42 @@ pub const Ops = struct { } } - // A.2a (Stream A) lock: emission BAILS LOUDLY until A.2b wires the real - // LLVMBuildAtomicCmpXchg + the `?T` result (null = success). + // LLVMBuildAtomicCmpXchg returns a `{ T, i1 }` pair: field 0 = the value + // that was loaded (the ACTUAL current value), field 1 = a `success` i1. + // sx's `?T` (integer T) is also `{ T, i1 }` = `{ payload, has_value }`, but + // with the OPPOSITE convention: null = SUCCESS. So we build the result as + // `{ actual, NOT success }` — has_value = xor(success, true). singleThread + // stays 0; `weak` is set via LLVMSetWeak. Integer-only (recognizer guard), + // so the optional is never a pointer/niche optional. pub fn emitAtomicCmpxchg(self: Ops, instruction: *const Inst, a: AtomicCmpxchg) void { - _ = a; - std.debug.print("error: atomic compare-exchange LLVM emission not yet implemented (Stream A, A.2b)\n", .{}); - self.e.comptime_failed = true; - self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(if (instruction.ty == .void) .i64 else instruction.ty))); + const ptr = self.e.resolveRef(a.ptr); + const cmp = self.e.resolveRef(a.cmp); + const new = self.e.resolveRef(a.new); + const ptr_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(ptr)); + if (ptr_kind == c.LLVMPointerTypeKind and instruction.ty != .void) { + const pair = c.LLVMBuildAtomicCmpXchg( + self.e.builder, + ptr, + cmp, + new, + llvmOrdering(a.success_ordering), + llvmOrdering(a.failure_ordering), + 0, // singleThread = false + ); + if (a.weak) c.LLVMSetWeak(pair, 1); + const actual = c.LLVMBuildExtractValue(self.e.builder, pair, 0, "cas.actual"); + const success = c.LLVMBuildExtractValue(self.e.builder, pair, 1, "cas.success"); + // has_value = NOT success (sx `?T`: null = success). + const has = c.LLVMBuildXor(self.e.builder, success, c.LLVMConstInt(self.e.cached_i1, 1, 0), "cas.has"); + // Assemble the `?T` = `{ T, i1 }` result. + const opt_ty = self.e.toLLVMType(instruction.ty); + var result = c.LLVMGetUndef(opt_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, actual, 0, "cas.val"); + result = c.LLVMBuildInsertValue(self.e.builder, result, has, 1, "cas.opt"); + 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 { diff --git a/src/ir/emit_llvm.test.zig b/src/ir/emit_llvm.test.zig index 0f1034b6..10a5a627 100644 --- a/src/ir/emit_llvm.test.zig +++ b/src/ir/emit_llvm.test.zig @@ -280,6 +280,40 @@ test "emit: atomic rmw (add + signed/unsigned min)" { try std.testing.expect(std.mem.indexOf(u8, ir_str, "atomicrmw umin") != null); // unsigned u64 } +test "emit: atomic cmpxchg (strong + weak)" { + const alloc = std.testing.allocator; + var module = Module.init(alloc); + defer module.deinit(); + + var b = Builder.init(&module); + + const opt_i64 = module.types.optionalOf(.i64); // ?i64 result type + + _ = b.beginFunction(str(&module, "f"), &.{}, .i64); + const entry = b.appendBlock(str(&module, "entry"), &.{}); + b.switchToBlock(entry); + + const p = b.alloca(.i64); + const exp = b.constInt(1, .i64); + const des = b.constInt(2, .i64); + // strong CAS + _ = b.emit(.{ .atomic_cmpxchg = .{ .ptr = p, .cmp = exp, .new = des, .val_ty = .i64, .success_ordering = .acq_rel, .failure_ordering = .acquire, .weak = false } }, opt_i64); + // weak CAS + _ = b.emit(.{ .atomic_cmpxchg = .{ .ptr = p, .cmp = exp, .new = des, .val_ty = .i64, .success_ordering = .seq_cst, .failure_ordering = .seq_cst, .weak = true } }, opt_i64); + b.ret(exp, .i64); + b.finalize(); + + var emitter = LLVMEmitter.init(alloc, &module, "test_cas", .{}); + defer emitter.deinit(); + emitter.emit(); + + try std.testing.expect(emitter.verify()); + + const ir_str = emitter.dumpToString(); + try std.testing.expect(std.mem.indexOf(u8, ir_str, "cmpxchg") != null); + try std.testing.expect(std.mem.indexOf(u8, ir_str, "cmpxchg weak") != null); // the weak marker +} + test "emit: comparison and branch" { const alloc = std.testing.allocator; var module = Module.init(alloc);