From dca396ed1fd723ec32d41e7ded4f2f6e33215baf Mon Sep 17 00:00:00 2001 From: agra Date: Sat, 20 Jun 2026 10:44:31 +0300 Subject: [PATCH] atomics A.2a: CAS ops + recognizer + methods, emit bails (lock) 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, failure carries the actual value for retry). print arm; emit dispatch -> emitAtomicCmpxchg (BAILS). comptime_vm arm does real single-thread CAS (read actual / compare / store-on-equal / build ?T: success->none, failure->some; 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 a rejected ordering pair. Suite green (718/0). --- current/CHECKPOINT-ATOMICS.md | 32 +++++--- .../1186-diagnostics-atomic-cas-ordering.sx | 11 +++ examples/1702-atomics-cas.sx | 34 ++++++++ .../1186-diagnostics-atomic-cas-ordering.exit | 1 + ...186-diagnostics-atomic-cas-ordering.stderr | 5 ++ ...186-diagnostics-atomic-cas-ordering.stdout | 1 + examples/expected/1702-atomics-cas.exit | 1 + examples/expected/1702-atomics-cas.stderr | 3 + examples/expected/1702-atomics-cas.stdout | 1 + library/modules/std/atomic.sx | 17 ++++ src/backend/llvm/ops.zig | 10 +++ src/ir/comptime_vm.zig | 26 +++++++ src/ir/emit_llvm.zig | 1 + src/ir/inst.zig | 17 ++++ src/ir/lower/call.zig | 77 ++++++++++++++++++- src/ir/print.zig | 1 + 16 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 examples/1186-diagnostics-atomic-cas-ordering.sx create mode 100644 examples/1702-atomics-cas.sx create mode 100644 examples/expected/1186-diagnostics-atomic-cas-ordering.exit create mode 100644 examples/expected/1186-diagnostics-atomic-cas-ordering.stderr create mode 100644 examples/expected/1186-diagnostics-atomic-cas-ordering.stdout create mode 100644 examples/expected/1702-atomics-cas.exit create mode 100644 examples/expected/1702-atomics-cas.stderr create mode 100644 examples/expected/1702-atomics-cas.stdout diff --git a/current/CHECKPOINT-ATOMICS.md b/current/CHECKPOINT-ATOMICS.md index c30e5cd9..cc871331 100644 --- a/current/CHECKPOINT-ATOMICS.md +++ b/current/CHECKPOINT-ATOMICS.md @@ -4,18 +4,30 @@ 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.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 `atomic_rmw` + `RmwKind` (no `nand`); -`LLVMBuildAtomicRMW` with binop from kind, signed/unsigned `Min/Max` from `val_ty`. RMW -restricted to INTEGER T (float fadd / pointer RMW out of scope, rejected loudly); all five -orderings valid for RMW. comptime_vm does real single-thread RMW. `examples/1701` (add/sub/ -and/or/xor/min/max) green; unit test locks `atomicrmw add` + signed `min` vs unsigned `umin`. -Suite green (716/0). +**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.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 +`atomic_rmw` + `RmwKind` (no `nand`); `LLVMBuildAtomicRMW` with binop from kind, signed/unsigned +`Min/Max` from `val_ty`. RMW restricted to INTEGER T (float fadd / pointer RMW out of scope, +rejected loudly); all five orderings valid for RMW. comptime_vm does real single-thread RMW. +`examples/1701` green; unit test locks `atomicrmw add` + signed `min` vs unsigned `umin`. ## Next step -**A.2 — CAS**: `compare_exchange`/`_weak` → LLVM `cmpxchg` (returns **`?T`, null = success**). -New IR op `atomic_cmpxchg` (ptr, cmp, new, success_ord, failure_ord, weak). Validate the two -orderings (failure may not be release/acq_rel nor stronger than success). `_weak` → `LLVMSetWeak`. +**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. ### Earlier — A.0c (guard hardening) Adversarial review of A.0 found two CRITICAL silent-wrong defects (raw LLVM verifier errors diff --git a/examples/1186-diagnostics-atomic-cas-ordering.sx b/examples/1186-diagnostics-atomic-cas-ordering.sx new file mode 100644 index 00000000..4ecad15d --- /dev/null +++ b/examples/1186-diagnostics-atomic-cas-ordering.sx @@ -0,0 +1,11 @@ +// Atomic compare-exchange dual-ordering validation: the FAILURE ordering may not +// be stronger than the SUCCESS ordering (LLVM rule). Here failure=.seq_cst (rank +// 3) is stronger than success=.relaxed (rank 0) → loud diagnostic, not invalid IR. +// Stream A (atomics) A.2. +#import "modules/std.sx"; +#import "modules/std/atomic.sx"; + +main :: () { + a := Atomic(i64).init(0); + _ := a.compare_exchange(0, 1, .relaxed, .seq_cst); +} diff --git a/examples/1702-atomics-cas.sx b/examples/1702-atomics-cas.sx new file mode 100644 index 00000000..fb93a59b --- /dev/null +++ b/examples/1702-atomics-cas.sx @@ -0,0 +1,34 @@ +// Atomic($T) compare-exchange: compare_exchange / compare_exchange_weak → LLVM +// cmpxchg. Result is `?T` — null = SUCCESS; a present value is the ACTUAL current +// value on failure (for a retry loop). Stream A (atomics) A.2. Single-thread. +#import "modules/std.sx"; +#import "modules/std/atomic.sx"; + +main :: () { + // Successful CAS: 10 == 10 → store 20, returns null. + a := Atomic(i64).init(10); + got := a.compare_exchange(10, 20, .acq_rel, .acquire); + if got == null { + print("cas ok, now: {}\n", a.load(.acquire)); // 20 + } else { + print("cas unexpected fail: {}\n", got!); + } + + // Failing CAS: 99 != 20 → no store, returns the actual value (20), unchanged. + got2 := a.compare_exchange(99, 0, .seq_cst, .seq_cst); + if got2 == null { + print("cas unexpected ok\n"); + } else { + print("cas failed, actual: {}, still: {}\n", got2!, a.load(.seq_cst)); // 20, 20 + } + + // Retry loop with the weak variant: increment a counter by 5. + counter := Atomic(i64).init(100); + cur := counter.load(.relaxed); + while true { + r := counter.compare_exchange_weak(cur, cur + 5, .acq_rel, .acquire); + if r == null { break; } + cur = r!; // retry with the observed value + } + print("after loop: {}\n", counter.load(.seq_cst)); // 105 +} diff --git a/examples/expected/1186-diagnostics-atomic-cas-ordering.exit b/examples/expected/1186-diagnostics-atomic-cas-ordering.exit new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/examples/expected/1186-diagnostics-atomic-cas-ordering.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1186-diagnostics-atomic-cas-ordering.stderr b/examples/expected/1186-diagnostics-atomic-cas-ordering.stderr new file mode 100644 index 00000000..d5ac71d7 --- /dev/null +++ b/examples/expected/1186-diagnostics-atomic-cas-ordering.stderr @@ -0,0 +1,5 @@ +error: atomic compare-exchange failure ordering ('.seq_cst') cannot be stronger than the success ordering ('.relaxed') + --> /Users/agra/projects/sx/library/modules/std/atomic.sx:79:188 + | +79 | compare_exchange :: (self: *Atomic(T), expected: T, desired: T, $success: Ordering, $failure: Ordering) -> ?T { return atomic_cmpxchg(T, @self.value, expected, desired, success, failure); } + | ^^^^^^^ diff --git a/examples/expected/1186-diagnostics-atomic-cas-ordering.stdout b/examples/expected/1186-diagnostics-atomic-cas-ordering.stdout new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/1186-diagnostics-atomic-cas-ordering.stdout @@ -0,0 +1 @@ + diff --git a/examples/expected/1702-atomics-cas.exit b/examples/expected/1702-atomics-cas.exit new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/examples/expected/1702-atomics-cas.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1702-atomics-cas.stderr b/examples/expected/1702-atomics-cas.stderr new file mode 100644 index 00000000..9cc08078 --- /dev/null +++ b/examples/expected/1702-atomics-cas.stderr @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/1702-atomics-cas.stdout @@ -0,0 +1 @@ + diff --git a/library/modules/std/atomic.sx b/library/modules/std/atomic.sx index 89441f1a..62a4af95 100644 --- a/library/modules/std/atomic.sx +++ b/library/modules/std/atomic.sx @@ -33,6 +33,16 @@ atomic_fetch_xor :: ($T: Type, ptr: *T, operand: T, o: Ordering) -> T #builtin; atomic_fetch_min :: ($T: Type, ptr: *T, operand: T, o: Ordering) -> T #builtin; atomic_fetch_max :: ($T: Type, ptr: *T, operand: T, o: Ordering) -> T #builtin; +// Compare-exchange intrinsics — integer T only. The result is `?T`: +// `null` = SUCCESS (the stored value equalled `expected`, replaced by `desired`); +// a present value is the ACTUAL current value on failure (for a retry loop). +// `_weak` may fail spuriously (LLVM `cmpxchg weak`) — use it inside a retry loop. +// Two orderings: `success` (applied when the exchange happens) and `failure` +// (the load when it doesn't). The failure ordering may not be .release / .acq_rel +// and may not be stronger than the success ordering (LLVM rule, enforced at lower). +atomic_cmpxchg :: ($T: Type, ptr: *T, expected: T, desired: T, success: Ordering, failure: Ordering) -> ?T #builtin; +atomic_cmpxchg_weak :: ($T: Type, ptr: *T, expected: T, desired: T, success: Ordering, failure: Ordering) -> ?T #builtin; + // The ordering is a COMPTIME value param (`$o`): it must be known at compile // time because LLVM atomic ordering is an instruction attribute, not a runtime // operand. It is explicit (Rust-style — no default), so the caller always states @@ -61,4 +71,11 @@ Atomic :: struct ($T: Type) { fetch_xor :: (self: *Atomic(T), v: T, $o: Ordering) -> T { return atomic_fetch_xor(T, @self.value, v, o); } fetch_min :: (self: *Atomic(T), v: T, $o: Ordering) -> T { return atomic_fetch_min(T, @self.value, v, o); } fetch_max :: (self: *Atomic(T), v: T, $o: Ordering) -> T { return atomic_fetch_max(T, @self.value, v, o); } + + // Compare-exchange (integer T). Returns `?T`: `null` on success (the value + // equalled `expected` and is now `desired`); on failure the ACTUAL current + // value (retry with it). `compare_exchange_weak` may fail spuriously — use it + // inside a retry loop. `$success` / `$failure` are comptime ordering params. + compare_exchange :: (self: *Atomic(T), expected: T, desired: T, $success: Ordering, $failure: Ordering) -> ?T { return atomic_cmpxchg(T, @self.value, expected, desired, success, failure); } + compare_exchange_weak :: (self: *Atomic(T), expected: T, desired: T, $success: Ordering, $failure: Ordering) -> ?T { return atomic_cmpxchg_weak(T, @self.value, expected, desired, success, failure); } } diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index 2e0d3800..c6dd05fb 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -18,6 +18,7 @@ const Store = ir_inst.Store; const AtomicLoad = ir_inst.AtomicLoad; const AtomicStore = ir_inst.AtomicStore; const AtomicRmw = ir_inst.AtomicRmw; +const AtomicCmpxchg = ir_inst.AtomicCmpxchg; const Conversion = ir_inst.Conversion; const GlobalId = ir_inst.GlobalId; const GlobalSet = ir_inst.GlobalSet; @@ -425,6 +426,15 @@ pub const Ops = struct { } } + // A.2a (Stream A) lock: emission BAILS LOUDLY until A.2b wires the real + // LLVMBuildAtomicCmpXchg + the `?T` result (null = success). + 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))); + } + pub fn emitAtomicStore(self: Ops, a: AtomicStore) void { const ptr = self.e.resolveRef(a.ptr); var val = self.e.resolveRef(a.val); diff --git a/src/ir/comptime_vm.zig b/src/ir/comptime_vm.zig index 3c1ecc82..b631ef63 100644 --- a/src/ir/comptime_vm.zig +++ b/src/ir/comptime_vm.zig @@ -715,6 +715,32 @@ pub const Vm = struct { try self.writeField(table, frame.get(a.ptr.index()), vty, new_val); return .{ .value = old }; }, + // Compare-exchange at comptime (single-thread): read actual, compare + // to cmp, and on equality store new. The ordering is a no-op; `weak` + // behaves as a strong exchange (no spurious failure with one thread). + // Result is `?T` (ins.ty): SUCCESS → none, FAILURE → some(actual). + // Integer T only (the recognizer's guard rules out pointer optionals), + // so the optional is laid out as payload@0 + has_value flag — exactly + // like the `optional_wrap` arm below. + .atomic_cmpxchg => |a| { + const table = try self.requireTable(); + const elem_ty = if (a.val_ty != .void) a.val_ty else return self.failMsg("comptime compare_exchange: missing element type"); + const ptr = frame.get(a.ptr.index()); + const actual = try self.readField(table, ptr, elem_ty); + const cmp_val = frame.get(a.cmp.index()); + const success = actual == cmp_val; + if (success) try self.writeField(table, ptr, elem_ty, frame.get(a.new.index())); + // Build the `?T` result in VM memory. + const opt_ty = ins.ty; // ?T + const addr = self.machine.allocBytes(table.typeSizeBytes(opt_ty), table.typeAlignBytes(opt_ty)); + if (success) { + try self.machine.writeWord(addr + table.typeSizeBytes(elem_ty), 0, 1); // has_value = 0 (null) + } else { + try self.writeField(table, addr, elem_ty, actual); // payload = actual + try self.machine.writeWord(addr + table.typeSizeBytes(elem_ty), 1, 1); // has_value = 1 + } + return .{ .value = addr }; + }, .struct_init => |agg| { const table = try self.requireTable(); const sty = ins.ty; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index e434c7a8..1e8ee91f 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -1568,6 +1568,7 @@ pub const LLVMEmitter = struct { .atomic_load => |a| self.ops().emitAtomicLoad(instruction, a), .atomic_store => |a| self.ops().emitAtomicStore(a), .atomic_rmw => |a| self.ops().emitAtomicRmw(instruction, a), + .atomic_cmpxchg => |a| self.ops().emitAtomicCmpxchg(instruction, a), // ── Globals ─────────────────────────────────────────── .global_get => |gid| self.ops().emitGlobalGet(instruction, gid), .global_addr => |gid| self.ops().emitGlobalAddr(gid), diff --git a/src/ir/inst.zig b/src/ir/inst.zig index e557f54e..a7235a3f 100644 --- a/src/ir/inst.zig +++ b/src/ir/inst.zig @@ -165,6 +165,7 @@ pub const Op = union(enum) { atomic_load: AtomicLoad, // atomic load from pointer with memory ordering atomic_store: AtomicStore, // atomic store to pointer with memory ordering atomic_rmw: AtomicRmw, // atomic read-modify-write; result is the OLD value + atomic_cmpxchg: AtomicCmpxchg, // atomic compare-exchange; result is ?T (null = success) // ── Struct ops ────────────────────────────────────────────────── struct_init: Aggregate, // construct struct from field values @@ -334,6 +335,22 @@ pub const AtomicRmw = struct { kind: RmwKind, }; +/// Atomic compare-exchange. The result is `?T` (an Optional of `val_ty`): +/// `null` means SUCCESS (the stored value equalled `cmp`, replaced by `new`); +/// a present value is the ACTUAL current value on failure (for a retry loop). +/// `weak` permits spurious failure (LLVM `cmpxchg weak`) — at comptime it +/// behaves as a strong exchange (single-thread, no spurious failure). +pub const AtomicCmpxchg = struct { + ptr: Ref, + cmp: Ref, + new: Ref, + /// Declared element type `T` (drives byte width; the result type is `?T`). + val_ty: TypeId = .void, + success_ordering: AtomicOrdering, + failure_ordering: AtomicOrdering, + weak: bool, +}; + pub const Conversion = struct { operand: Ref, from: TypeId, diff --git a/src/ir/lower/call.zig b/src/ir/lower/call.zig index 0ba01c05..12f04059 100644 --- a/src/ir/lower/call.zig +++ b/src/ir/lower/call.zig @@ -1726,10 +1726,14 @@ pub fn tryLowerAtomicIntrinsic(self: *Lowering, name: []const u8, c: *const ast. const is_load = std.mem.eql(u8, name, "atomic_load"); const is_store = std.mem.eql(u8, name, "atomic_store"); const rmw_kind = rmwKindFromName(name); // atomic_fetch_add/sub/and/or/xor/min/max - if (!is_load and !is_store and rmw_kind == null) return null; + const is_cmpxchg = std.mem.eql(u8, name, "atomic_cmpxchg"); + const is_cmpxchg_weak = std.mem.eql(u8, name, "atomic_cmpxchg_weak"); + const is_cas = is_cmpxchg or is_cmpxchg_weak; + if (!is_load and !is_store and rmw_kind == null and !is_cas) return null; // ($T, ptr[, operand/val], ordering): load=3, store/rmw=4. - const expected: usize = if (is_load) 3 else 4; + // cmpxchg ($T, ptr, expected, desired, success, failure): 6. + const expected: usize = if (is_load) 3 else if (is_cas) 6 else 4; if (c.args.len != expected) { if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "{s} expects {d} arguments", .{ name, expected }); return Ref.none; @@ -1763,6 +1767,63 @@ pub fn tryLowerAtomicIntrinsic(self: *Lowering, name: []const u8, c: *const ast. return Ref.none; } } + // CAS (A.2) is likewise restricted to INTEGER types: the `?T` result is laid + // out as `{ T, i1 }` (null = success); pointer/niche optionals are out of + // scope, so a non-integer T is rejected LOUDLY here. + if (is_cas) { + const int_ok = switch (self.module.types.get(elem_ty)) { + .signed, .unsigned => true, + else => false, + }; + if (!int_ok) { + if (self.diagnostics) |d| d.addFmt(.err, c.args[0].span, "atomic compare-exchange requires an integer type — '{s}' is not eligible", .{self.formatTypeName(elem_ty)}); + return Ref.none; + } + } + + // CAS resolves TWO orderings (success, failure) and validates the LLVM rule + // that the failure ordering may not be .release / .acq_rel and may not be + // stronger than the success ordering. Handled in its own branch (different + // arity + dual-ordering shape) before the single-ordering path below. + if (is_cas) { + const succ_node = c.args[4]; + const fail_node = c.args[5]; + const success_ordering = atomicOrderingFromNode(self, succ_node) orelse { + if (self.diagnostics) |d| d.addFmt(.err, succ_node.span, "atomic ordering must be a constant ordering literal (.relaxed / .acquire / .release / .acq_rel / .seq_cst)", .{}); + return Ref.none; + }; + const failure_ordering = atomicOrderingFromNode(self, fail_node) orelse { + if (self.diagnostics) |d| d.addFmt(.err, fail_node.span, "atomic ordering must be a constant ordering literal (.relaxed / .acquire / .release / .acq_rel / .seq_cst)", .{}); + return Ref.none; + }; + // The FAILURE ordering describes a load that does NOT write, so LLVM + // forbids .release / .acq_rel there, and forbids it being stronger than + // the SUCCESS ordering. Strength rank: relaxed=0 < acquire=release=1 < + // acq_rel=2 < seq_cst=3. + if (failure_ordering == .release or failure_ordering == .acq_rel) { + if (self.diagnostics) |d| d.addFmt(.err, fail_node.span, "atomic compare-exchange failure ordering cannot be .release or .acq_rel (use .relaxed / .acquire / .seq_cst)", .{}); + return Ref.none; + } + if (atomicOrderingRank(failure_ordering) > atomicOrderingRank(success_ordering)) { + if (self.diagnostics) |d| d.addFmt(.err, fail_node.span, "atomic compare-exchange failure ordering ('.{s}') cannot be stronger than the success ordering ('.{s}')", .{ @tagName(failure_ordering), @tagName(success_ordering) }); + return Ref.none; + } + + const cas_ptr = self.lowerExpr(c.args[1]); + const cmp = self.lowerExpr(c.args[2]); + const new = self.lowerExpr(c.args[3]); + // Result type is `?T` (null = success; failure carries the actual value). + const opt_ty = self.module.types.optionalOf(elem_ty); + return self.builder.emit(.{ .atomic_cmpxchg = .{ + .ptr = cas_ptr, + .cmp = cmp, + .new = new, + .val_ty = elem_ty, + .success_ordering = success_ordering, + .failure_ordering = failure_ordering, + .weak = is_cmpxchg_weak, + } }, opt_ty); + } const ord_node = c.args[expected - 1]; const ordering = atomicOrderingFromNode(self, ord_node) orelse { @@ -1793,6 +1854,18 @@ pub fn tryLowerAtomicIntrinsic(self: *Lowering, name: []const u8, c: *const ast. return Ref.none; // store has a void result } +/// Strength rank of an atomic ordering, for the compare-exchange rule that the +/// failure ordering may not be stronger than the success ordering. +/// relaxed=0 < acquire=release=1 < acq_rel=2 < seq_cst=3. +fn atomicOrderingRank(o: inst_mod.AtomicOrdering) u8 { + return switch (o) { + .relaxed => 0, + .acquire, .release => 1, + .acq_rel => 2, + .seq_cst => 3, + }; +} + /// Map an `atomic_fetch_*` intrinsic name to its RMW kind (null if not one). fn rmwKindFromName(name: []const u8) ?inst_mod.RmwKind { if (std.mem.eql(u8, name, "atomic_fetch_add")) return .add; diff --git a/src/ir/print.zig b/src/ir/print.zig index 70980fe0..c51a9798 100644 --- a/src/ir/print.zig +++ b/src/ir/print.zig @@ -239,6 +239,7 @@ fn printInst(instruction: *const Inst, ref_idx: u32, tt: *const TypeTable, write return; }, .atomic_rmw => |a| try writer.print("atomic_rmw.{s} %{d}, %{d} {s} : ", .{ @tagName(a.kind), a.ptr.index(), a.operand.index(), @tagName(a.ordering) }), + .atomic_cmpxchg => |a| try writer.print("atomic_cmpxchg{s} %{d}, %{d}, %{d} {s} {s} : ", .{ if (a.weak) "_weak" else "", a.ptr.index(), a.cmp.index(), a.new.index(), @tagName(a.success_ordering), @tagName(a.failure_ordering) }), // ── Struct ops ────────────────────────────────────────── .struct_init => |agg| { try writer.writeAll("struct_init [");