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