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).
This commit is contained in:
agra
2026-06-20 10:57:01 +03:00
parent dca396ed1f
commit 79895be401
6 changed files with 91 additions and 26 deletions

View File

@@ -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 {

View File

@@ -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);