atomics A.0a: lib + IR ops + recognizer, emit bails (lock commit)
Stream A (atomics) foundation. Net-new atomic load/store codegen path, wired end-to-end except LLVM emission, which deliberately bails loudly so the example locks to a clean diagnostic (A.0b turns it green — cadence: no commit both adds a test and makes it pass). - library/modules/std/atomic.sx: Ordering enum, Atomic($T) transparent wrapper (init/load/store, seq_cst-only for now), atomic_load/atomic_store #builtin intrinsics. Opt-in import, NOT in the universal std facade (Ordering in the prelude grows every program's type table + churns 37 .ir snapshots). - IR: atomic_load/atomic_store ops + AtomicOrdering (all 5) + structs (inst.zig); print arms; comptime_vm arms reuse load/store (single-thread correct); recognizer tryLowerAtomicIntrinsic (const-ordering + scalar-size guards, both loud); emit dispatch -> emitAtomicLoad/Store bail via comptime_failed. - examples/1700-atomics-load-store.sx locked to the bail diagnostic. Full ordering surface (a.load(.acquire)) blocked on comptime-constant ordering propagation (comptime enum value params) — A.0.5, migrated not legacy.
This commit is contained in:
@@ -80,6 +80,9 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
// Check reflection builtins first (before lowering args — some args are type names, not values)
|
||||
if (c.callee.data == .identifier) {
|
||||
if (self.tryLowerReflectionCall(c.callee.data.identifier.name, c)) |ref| return ref;
|
||||
// Atomic intrinsics (atomic_load/atomic_store): a type arg + value args,
|
||||
// so lower them here (before generic arg lowering) like reflection calls.
|
||||
if (self.tryLowerAtomicIntrinsic(c.callee.data.identifier.name, c)) |ref| return ref;
|
||||
}
|
||||
|
||||
// Check for runtime dispatch pattern BEFORE lowering args.
|
||||
@@ -1667,6 +1670,62 @@ 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;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Recognize the atomic `#builtin` intrinsics and lower them to dedicated atomic
|
||||
/// IR ops:
|
||||
/// atomic_load($T, ptr: *T, o: Ordering) -> T
|
||||
/// atomic_store($T, ptr: *T, v: T, o: Ordering)
|
||||
/// The `Ordering` arg MUST be a constant enum literal — read statically here and
|
||||
/// baked into the op (the op carries no runtime ordering operand). `T` must be a
|
||||
/// scalar of size 1/2/4/8/16. Both constraints are loud diagnostics, never silent
|
||||
/// defaults. Returns null if `name` is not an atomic intrinsic.
|
||||
pub fn tryLowerAtomicIntrinsic(self: *Lowering, name: []const u8, c: *const ast.Call) ?Ref {
|
||||
const is_load = std.mem.eql(u8, name, "atomic_load");
|
||||
const is_store = std.mem.eql(u8, name, "atomic_store");
|
||||
if (!is_load and !is_store) return null;
|
||||
|
||||
const expected: usize = if (is_load) 3 else 4; // ($T, ptr[, val], ordering)
|
||||
if (c.args.len != expected) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "{s} expects {d} arguments", .{ name, expected });
|
||||
return Ref.none;
|
||||
}
|
||||
|
||||
const elem_ty = self.resolveTypeArg(c.args[0]);
|
||||
const size = self.typeSizeBytes(elem_ty);
|
||||
if (size != 1 and size != 2 and size != 4 and size != 8 and size != 16) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, c.args[0].span, "atomic ops require a scalar type of size 1/2/4/8/16 bytes — '{s}' is {d} bytes", .{ self.formatTypeName(elem_ty), size });
|
||||
return Ref.none;
|
||||
}
|
||||
|
||||
const ord_node = c.args[expected - 1];
|
||||
const ordering = atomicOrderingFromNode(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;
|
||||
};
|
||||
|
||||
const ptr = self.lowerExpr(c.args[1]);
|
||||
if (is_load) {
|
||||
return self.builder.emit(.{ .atomic_load = .{ .ptr = ptr, .ordering = ordering } }, elem_ty);
|
||||
}
|
||||
const val = self.lowerExpr(c.args[2]);
|
||||
self.builder.emitVoid(.{ .atomic_store = .{ .ptr = ptr, .val = val, .val_ty = elem_ty, .ordering = ordering } }, .void);
|
||||
return Ref.none; // store has a void result
|
||||
}
|
||||
|
||||
/// Try to lower a call as a reflection builtin (expanded inline during lowering).
|
||||
/// Returns null if the call is not a recognized reflection builtin.
|
||||
pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.Call) ?Ref {
|
||||
|
||||
Reference in New Issue
Block a user