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:
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
1
|
||||
0
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
|
||||
cas ok, now: 20
|
||||
cas failed, actual: 20, still: 20
|
||||
after loop: 105
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user