atomics A.0.5: full ordering surface (comptime $o: Ordering)

Migrate Atomic methods from seq_cst-only to the explicit ordering surface now
that comptime value params work on generic-struct methods (workers 3c4305f /
d7a6857 / d95ba0a):

- atomic.sx: load/store take a comptime $o: Ordering (explicit, Rust-style; no
  default, matching design 4.6). a.load(.acquire) -> 'load atomic .. acquire'.
- call.zig: atomicOrderingFromNode resolves a comptime-bound ordering identifier
  via comptimeIntNamed (+ atomicOrderingFromTag); documents the sx-Ordering <->
  IR-AtomicOrdering declaration-order invariant. The per-op validity guard fires
  through the method path (a.load(.release) is a compile error).
- 1700 migrated to explicit orderings (output unchanged 7/42/43).

Suite green (715/0).
This commit is contained in:
agra
2026-06-20 10:04:39 +03:00
parent d95ba0a937
commit acf31839ea
4 changed files with 72 additions and 31 deletions

View File

@@ -59,6 +59,22 @@ Full atomic load/store plumbing with LLVM emission deliberately bailing loudly;
`.ir`; add `emit_llvm.test.zig` unit. Then adversarial review, then the comptime-enum worker `.ir`; add `emit_llvm.test.zig` unit. Then adversarial review, then the comptime-enum worker
+ A.0.5 migration to the full ordering surface. + A.0.5 migration to the full ordering surface.
## A.0.5 — full ordering surface (DONE)
`Atomic($T).load($o: Ordering)` / `store(v, $o)` — ordering is a COMPTIME value param,
explicit (Rust-style, no default; design §4.6). `a.load(.acquire)` emits `load atomic …
acquire`; `a.store(v, .release)` emits `store atomic … release`; `a.load(.release)` is a
compile error (per-op validity guard fires through the method path). Recognizer
`atomicOrderingFromNode` now resolves a comptime-bound ordering identifier via
`comptimeIntNamed` (+ `atomicOrderingFromTag`, with the sx-Ordering ↔ IR-AtomicOrdering
declaration-order invariant documented). 1700 migrated to explicit orderings (output
unchanged 7/42/43). Suite green (715/0).
**Unblocked by three comptime-value-param commits (workers):** enum (3c4305f), tagged_union
(d7a6857), generic-struct methods (d95ba0a). NOTE: default VALUES for comptime params on
generic-struct methods are NOT bound (orthogonal gap — free-fn defaults work); atomics
sidesteps it cleanly by requiring explicit ordering (matches the design). Candidate
follow-up, not an atomics blocker.
## Known issues / capability gaps ## Known issues / capability gaps
- **Comptime-constant ordering propagation MISSING (blocks the full surface).** A runtime - **Comptime-constant ordering propagation MISSING (blocks the full surface).** A runtime
`Ordering` method param can't reach LLVM (orderings are instruction attributes, not `Ordering` method param can't reach LLVM (orderings are instruction attributes, not
@@ -98,3 +114,7 @@ Full atomic load/store plumbing with LLVM emission deliberately bailing loudly;
- **A.0c** — guard hardening from the adversarial review: scalar-kind allowlist + per-op - **A.0c** — guard hardening from the adversarial review: scalar-kind allowlist + per-op
ordering validity (call.zig), val_ty align bail (ops.zig), + diagnostic examples ordering validity (call.zig), val_ty align bail (ops.zig), + diagnostic examples
1130/1131. Suite green (713/0). (comptime enum value params landed via worker 3c4305f.) 1130/1131. Suite green (713/0). (comptime enum value params landed via worker 3c4305f.)
- **A.0.5** — full ordering surface: `Atomic($T).load/store($o: Ordering)` comptime ordering
(explicit). Recognizer resolves comptime-bound ordering via `comptimeIntNamed`. 1700
migrated to explicit orderings (acquire/release/relaxed/seq_cst). Unblocked by
comptime-value-param workers (3c4305f/d7a6857/d95ba0a). Suite green (715/0).

View File

@@ -1,17 +1,17 @@
// Atomic($T) load/store (seq_cst), single-thread. // Atomic($T) load/store with explicit memory orderings, single-thread.
// Stream A (atomics) A.0 — the first net-new atomic codegen example. // Stream A (atomics) A.0 + A.0.5 — the ordering is a comptime `$o: Ordering`
// Explicit orderings (a.load(.acquire)) arrive in A.0.5; see PLAN-ATOMICS.md. // param (explicit, Rust-style): a.load(.acquire) emits `load atomic … acquire`.
// Atomics is an opt-in import (not in the universal prelude) — like `trace`. // An invalid combination (a.load(.release)) is a compile error (see 1131).
#import "modules/std.sx"; #import "modules/std.sx";
#import "modules/std/atomic.sx"; #import "modules/std/atomic.sx";
main :: () { main :: () {
a := Atomic(i64).init(7); a := Atomic(i64).init(7);
print("init: {}\n", a.load()); print("init: {}\n", a.load(.seq_cst));
a.store(42); a.store(42, .release);
print("after store: {}\n", a.load()); print("after store: {}\n", a.load(.acquire));
a.store(a.load() + 1); a.store(a.load(.relaxed) + 1, .seq_cst);
print("incremented: {}\n", a.load()); print("incremented: {}\n", a.load(.seq_cst));
} }

View File

@@ -23,11 +23,11 @@ Ordering :: enum {
atomic_load :: ($T: Type, ptr: *T, o: Ordering) -> T #builtin; atomic_load :: ($T: Type, ptr: *T, o: Ordering) -> T #builtin;
atomic_store :: ($T: Type, ptr: *T, v: T, o: Ordering) #builtin; atomic_store :: ($T: Type, ptr: *T, v: T, o: Ordering) #builtin;
// NOTE (A.0): the methods bake a literal `.seq_cst` (strongest, conservative) // The ordering is a COMPTIME value param (`$o`): it must be known at compile
// rather than taking an `o: Ordering` parameter. A runtime ordering param can't // time because LLVM atomic ordering is an instruction attribute, not a runtime
// reach the intrinsic as the compile-time constant LLVM requires, and comptime // operand. It is explicit (Rust-style — no default), so the caller always states
// enum value params don't exist yet — so explicit orderings (`a.load(.acquire)`) // the ordering: `a.load(.acquire)`, `a.store(v, .release)`. An invalid
// land in A.0.5 once that capability does. See current/PLAN-ATOMICS.md. // combination (`a.load(.release)`) is a compile error.
Atomic :: struct ($T: Type) { Atomic :: struct ($T: Type) {
value: T; value: T;
@@ -35,11 +35,11 @@ Atomic :: struct ($T: Type) {
return .{ value = v }; return .{ value = v };
} }
load :: (self: *Atomic(T)) -> T { load :: (self: *Atomic(T), $o: Ordering) -> T {
return atomic_load(T, @self.value, .seq_cst); return atomic_load(T, @self.value, o);
} }
store :: (self: *Atomic(T), v: T) { store :: (self: *Atomic(T), v: T, $o: Ordering) {
atomic_store(T, @self.value, v, .seq_cst); atomic_store(T, @self.value, v, o);
} }
} }

View File

@@ -1678,18 +1678,39 @@ pub fn lowerRuntimeDispatchCall(
return self.builder.constInt(0, .void); return self.builder.constInt(0, .void);
} }
/// Map a bare ordering enum literal (`.seq_cst`) to the IR `AtomicOrdering`. /// The five `Ordering` variants by declaration-order tag. INVARIANT: the sx
/// Returns null for anything that is not one of the five constant literals — /// `Ordering` enum (library/modules/std/atomic.sx) and the IR `AtomicOrdering`
/// the caller turns that into a loud "must be a constant ordering literal" /// enum (inst.zig) declare these variants in the SAME order, so a comptime-bound
/// diagnostic (never a silent default). /// ordering's tag indexes this list. Keep all three in sync.
fn atomicOrderingFromNode(node: *const Node) ?inst_mod.AtomicOrdering { fn atomicOrderingFromTag(tag: i64) ?inst_mod.AtomicOrdering {
if (node.data != .enum_literal) return null; return switch (tag) {
const n = node.data.enum_literal.name; 0 => .relaxed,
if (std.mem.eql(u8, n, "relaxed")) return .relaxed; 1 => .acquire,
if (std.mem.eql(u8, n, "acquire")) return .acquire; 2 => .release,
if (std.mem.eql(u8, n, "release")) return .release; 3 => .acq_rel,
if (std.mem.eql(u8, n, "acq_rel")) return .acq_rel; 4 => .seq_cst,
if (std.mem.eql(u8, n, "seq_cst")) return .seq_cst; else => null,
};
}
/// Resolve an ordering argument to the IR `AtomicOrdering`. Accepts a bare enum
/// literal (`.seq_cst`) OR a comptime-bound identifier (a `$o: Ordering` param
/// forwarded into the intrinsic — read its bound variant tag via
/// `comptimeIntNamed`). Returns null for a non-constant ordering — the caller
/// turns that into a loud diagnostic (never a silent default).
fn atomicOrderingFromNode(self: *Lowering, node: *const Node) ?inst_mod.AtomicOrdering {
if (node.data == .enum_literal) {
const n = node.data.enum_literal.name;
if (std.mem.eql(u8, n, "relaxed")) return .relaxed;
if (std.mem.eql(u8, n, "acquire")) return .acquire;
if (std.mem.eql(u8, n, "release")) return .release;
if (std.mem.eql(u8, n, "acq_rel")) return .acq_rel;
if (std.mem.eql(u8, n, "seq_cst")) return .seq_cst;
return null;
}
if (node.data == .identifier) {
if (self.comptimeIntNamed(node.data.identifier.name)) |tag| return atomicOrderingFromTag(tag);
}
return null; return null;
} }
@@ -1729,7 +1750,7 @@ pub fn tryLowerAtomicIntrinsic(self: *Lowering, name: []const u8, c: *const ast.
} }
const ord_node = c.args[expected - 1]; const ord_node = c.args[expected - 1];
const ordering = atomicOrderingFromNode(ord_node) orelse { const ordering = atomicOrderingFromNode(self, ord_node) orelse {
if (self.diagnostics) |d| d.addFmt(.err, ord_node.span, "atomic ordering must be a constant ordering literal (.relaxed / .acquire / .release / .acq_rel / .seq_cst)", .{}); if (self.diagnostics) |d| d.addFmt(.err, ord_node.span, "atomic ordering must be a constant ordering literal (.relaxed / .acquire / .release / .acq_rel / .seq_cst)", .{});
return Ref.none; return Ref.none;
}; };