diff --git a/current/CHECKPOINT-ATOMICS.md b/current/CHECKPOINT-ATOMICS.md index c7955a17..fd669784 100644 --- a/current/CHECKPOINT-ATOMICS.md +++ b/current/CHECKPOINT-ATOMICS.md @@ -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 + 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 - **Comptime-constant ordering propagation MISSING (blocks the full surface).** A runtime `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 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.) +- **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). diff --git a/examples/1700-atomics-load-store.sx b/examples/1700-atomics-load-store.sx index 4130c5fa..7b549218 100644 --- a/examples/1700-atomics-load-store.sx +++ b/examples/1700-atomics-load-store.sx @@ -1,17 +1,17 @@ -// Atomic($T) load/store (seq_cst), single-thread. -// Stream A (atomics) A.0 — the first net-new atomic codegen example. -// Explicit orderings (a.load(.acquire)) arrive in A.0.5; see PLAN-ATOMICS.md. -// Atomics is an opt-in import (not in the universal prelude) — like `trace`. +// Atomic($T) load/store with explicit memory orderings, single-thread. +// Stream A (atomics) A.0 + A.0.5 — the ordering is a comptime `$o: Ordering` +// param (explicit, Rust-style): a.load(.acquire) emits `load atomic … acquire`. +// An invalid combination (a.load(.release)) is a compile error (see 1131). #import "modules/std.sx"; #import "modules/std/atomic.sx"; main :: () { a := Atomic(i64).init(7); - print("init: {}\n", a.load()); + print("init: {}\n", a.load(.seq_cst)); - a.store(42); - print("after store: {}\n", a.load()); + a.store(42, .release); + print("after store: {}\n", a.load(.acquire)); - a.store(a.load() + 1); - print("incremented: {}\n", a.load()); + a.store(a.load(.relaxed) + 1, .seq_cst); + print("incremented: {}\n", a.load(.seq_cst)); } diff --git a/library/modules/std/atomic.sx b/library/modules/std/atomic.sx index af4812bc..30049728 100644 --- a/library/modules/std/atomic.sx +++ b/library/modules/std/atomic.sx @@ -23,11 +23,11 @@ Ordering :: enum { atomic_load :: ($T: Type, ptr: *T, o: Ordering) -> T #builtin; atomic_store :: ($T: Type, ptr: *T, v: T, o: Ordering) #builtin; -// NOTE (A.0): the methods bake a literal `.seq_cst` (strongest, conservative) -// rather than taking an `o: Ordering` parameter. A runtime ordering param can't -// reach the intrinsic as the compile-time constant LLVM requires, and comptime -// enum value params don't exist yet — so explicit orderings (`a.load(.acquire)`) -// land in A.0.5 once that capability does. See current/PLAN-ATOMICS.md. +// 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 +// the ordering: `a.load(.acquire)`, `a.store(v, .release)`. An invalid +// combination (`a.load(.release)`) is a compile error. Atomic :: struct ($T: Type) { value: T; @@ -35,11 +35,11 @@ Atomic :: struct ($T: Type) { return .{ value = v }; } - load :: (self: *Atomic(T)) -> T { - return atomic_load(T, @self.value, .seq_cst); + load :: (self: *Atomic(T), $o: Ordering) -> T { + return atomic_load(T, @self.value, o); } - store :: (self: *Atomic(T), v: T) { - atomic_store(T, @self.value, v, .seq_cst); + store :: (self: *Atomic(T), v: T, $o: Ordering) { + atomic_store(T, @self.value, v, o); } } diff --git a/src/ir/lower/call.zig b/src/ir/lower/call.zig index c56a2ccf..8369f1d5 100644 --- a/src/ir/lower/call.zig +++ b/src/ir/lower/call.zig @@ -1678,18 +1678,39 @@ pub fn lowerRuntimeDispatchCall( return self.builder.constInt(0, .void); } -/// Map a bare ordering enum literal (`.seq_cst`) to the IR `AtomicOrdering`. -/// Returns null for anything that is not one of the five constant literals — -/// the caller turns that into a loud "must be a constant ordering literal" -/// diagnostic (never a silent default). -fn atomicOrderingFromNode(node: *const Node) ?inst_mod.AtomicOrdering { - if (node.data != .enum_literal) return null; - 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; +/// The five `Ordering` variants by declaration-order tag. INVARIANT: the sx +/// `Ordering` enum (library/modules/std/atomic.sx) and the IR `AtomicOrdering` +/// enum (inst.zig) declare these variants in the SAME order, so a comptime-bound +/// ordering's tag indexes this list. Keep all three in sync. +fn atomicOrderingFromTag(tag: i64) ?inst_mod.AtomicOrdering { + return switch (tag) { + 0 => .relaxed, + 1 => .acquire, + 2 => .release, + 3 => .acq_rel, + 4 => .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; } @@ -1729,7 +1750,7 @@ pub fn tryLowerAtomicIntrinsic(self: *Lowering, name: []const u8, c: *const ast. } 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)", .{}); return Ref.none; };