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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user