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).
This commit is contained in:
@@ -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
|
||||
|
||||
11
examples/1186-diagnostics-atomic-cas-ordering.sx
Normal file
11
examples/1186-diagnostics-atomic-cas-ordering.sx
Normal file
@@ -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);
|
||||
}
|
||||
34
examples/1702-atomics-cas.sx
Normal file
34
examples/1702-atomics-cas.sx
Normal file
@@ -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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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); }
|
||||
| ^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
1
examples/expected/1702-atomics-cas.exit
Normal file
1
examples/expected/1702-atomics-cas.exit
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
3
examples/expected/1702-atomics-cas.stderr
Normal file
3
examples/expected/1702-atomics-cas.stderr
Normal file
@@ -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)
|
||||
1
examples/expected/1702-atomics-cas.stdout
Normal file
1
examples/expected/1702-atomics-cas.stdout
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 [");
|
||||
|
||||
Reference in New Issue
Block a user