Skip to content

Commit 6c92465

Browse files
committed
Improve error reporting around atomics
1 parent 056defa commit 6c92465

File tree

5 files changed

+133
-101
lines changed

5 files changed

+133
-101
lines changed

lib/std/atomic.zig

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -493,10 +493,31 @@ pub const Op = union(enum) {
493493
}
494494
/// Check if the operation is supported on the given type, on a specified CPU.
495495
pub fn supportedOnCpu(op: Op, comptime T: type, cpu: std.Target.Cpu) bool {
496-
if (!op.isValidAtomicType(T)) return false;
496+
const valid_types = op.supportedTypes();
497+
const is_valid_type = switch (@typeInfo(T)) {
498+
.bool => valid_types.bool,
499+
.int => valid_types.integer,
500+
.float => valid_types.float,
501+
.@"enum" => valid_types.@"enum",
502+
.error_set => valid_types.error_set,
503+
.@"struct" => |s| s.layout == .@"packed" and valid_types.packed_struct,
497504

498-
if (!std.math.isPowerOfTwo(@sizeOf(T))) return false;
505+
.optional => |opt| switch (@typeInfo(opt.child)) {
506+
.pointer => |ptr| switch (ptr.size) {
507+
.slice, .c => false,
508+
.one, .many => !ptr.is_allowzero and valid_types.pointer,
509+
},
510+
},
511+
.pointer => |ptr| switch (ptr.size) {
512+
.slice => false,
513+
.one, .many, .c => valid_types.pointer,
514+
},
499515

516+
else => false,
517+
};
518+
if (!is_valid_type) return false;
519+
520+
if (!std.math.isPowerOfTwo(@sizeOf(T))) return false;
500521
const required_features = op.supportedSizes(cpu.arch).get(@sizeOf(T)) orelse {
501522
return false;
502523
};
@@ -682,55 +703,58 @@ pub const Op = union(enum) {
682703
}
683704
}
684705

685-
/// Returns true if the type may be usable with this atomic operation.
686-
/// This does not check that the type actually fits within the target's atomic size constriants.
687-
/// This function must be kept in sync with the compiler implementation.
688-
fn isValidAtomicType(op: Op, comptime T: type) bool {
689-
const supports_floats = switch (op) {
690-
.load, .store => true,
706+
/// Returns a description of the kinds of type supported by this operation.
707+
pub fn supportedTypes(op: Op) Types {
708+
return switch (op) {
709+
.load, .store => .{},
691710
.rmw => |rmw| switch (rmw) {
692-
.Xchg, .Add, .Sub, .Min, .Max => true,
693-
.And, .Nand, .Or, .Xor => false,
694-
},
695-
// floats are not supported for cmpxchg because float equality differs from bitwise equality
696-
.cmpxchg => false,
697-
};
698-
699-
return switch (@typeInfo(T)) {
700-
.bool, .int, .@"enum", .error_set => true,
701-
.float => supports_floats,
702-
.@"struct" => |s| s.layout == .@"packed",
703-
704-
.optional => |opt| switch (@typeInfo(opt.child)) {
705-
.pointer => |ptr| switch (ptr.size) {
706-
.slice, .c => false,
707-
.one, .many => !ptr.is_allowzero,
711+
.Xchg => .{},
712+
.Add, .Sub, .Min, .Max => .{
713+
.bool = false,
714+
.@"enum" = false,
715+
.error_set = false,
716+
},
717+
.And, .Nand, .Or, .Xor => .{
718+
.float = false,
719+
.bool = false,
720+
.@"enum" = false,
721+
.error_set = false,
708722
},
709723
},
710-
.pointer => |ptr| switch (ptr.size) {
711-
.slice => false,
712-
.one, .many, .c => true,
724+
.cmpxchg => .{
725+
// floats are not supported for cmpxchg because float equality differs from bitwise equality
726+
.float = false,
713727
},
714-
715-
else => false,
716728
};
717729
}
718-
719-
test isValidAtomicType {
720-
try testing.expect(isValidAtomicType(.load, u8));
721-
try testing.expect(isValidAtomicType(.load, f32));
722-
try testing.expect(isValidAtomicType(.load, bool));
723-
try testing.expect(isValidAtomicType(.load, enum { a, b, c }));
724-
try testing.expect(isValidAtomicType(.load, packed struct { a: u8, b: u8 }));
725-
try testing.expect(isValidAtomicType(.load, error{OutOfMemory}));
726-
try testing.expect(isValidAtomicType(.load, u200)); // doesn't check size
727-
728-
try testing.expect(!isValidAtomicType(.load, struct { a: u8 }));
729-
try testing.expect(!isValidAtomicType(.load, union { a: u8 }));
730-
731-
// cmpxchg doesn't support floats
732-
try testing.expect(!isValidAtomicType(.{ .cmpxchg = .weak }, f32));
733-
}
730+
pub const Types = packed struct {
731+
bool: bool = true,
732+
integer: bool = true,
733+
float: bool = true,
734+
@"enum": bool = true,
735+
error_set: bool = true,
736+
packed_struct: bool = true,
737+
pointer: bool = true,
738+
739+
pub fn format(types: Types, writer: *std.io.Writer) !void {
740+
const bits: @typeInfo(Types).@"struct".backing_integer.? = @bitCast(types);
741+
var count = @popCount(bits);
742+
inline for (@typeInfo(Types).@"struct".fields) |field| {
743+
if (@field(types, field.name)) {
744+
var name = field.name[0..].*;
745+
std.mem.replaceScalar(u8, &name, '_', ' ');
746+
try writer.writeAll(&name);
747+
748+
count -= 1;
749+
switch (count) {
750+
0 => {},
751+
1 => try writer.writeAll(", or "),
752+
else => try writer.writeAll(", "),
753+
}
754+
}
755+
}
756+
}
757+
};
734758

735759
test supportedOnCpu {
736760
const x86 = std.Target.x86;

src/Sema.zig

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23592,39 +23592,17 @@ fn checkAtomicPtrOperand(
2359223592
const pt = sema.pt;
2359323593
const zcu = pt.zcu;
2359423594

23595-
// This logic must be kept in sync with `std.atomic.Op.isValidAtomicType`.
23596-
const supports_floats = switch (atomic_op) {
23597-
.load, .store => true,
23598-
.cmpxchg => false,
23599-
.rmw => |rmw| switch (rmw) {
23600-
.Xchg, .Add, .Sub, .Min, .Max => true,
23601-
.And, .Nand, .Or, .Xor => false,
23602-
},
23603-
};
23604-
const is_valid_atomic_type =
23605-
elem_ty.toIntern() == .bool_type or
23606-
elem_ty.isAbiInt(zcu) or
23607-
elem_ty.isPtrAtRuntime(zcu) or
23608-
(elem_ty.isRuntimeFloat() and supports_floats);
23609-
if (!is_valid_atomic_type) {
23595+
if (sema.isInvalidAtomicType(atomic_op, elem_ty)) |why| {
2361023596
const msg = msg: {
2361123597
const msg = try sema.errMsg(
2361223598
elem_ty_src,
23613-
"expected bool, integer,{s} enum, error set, packed struct, or pointer type; found '{f}'",
23614-
.{
23615-
if (atomic_op == .cmpxchg) "" else " float,",
23616-
elem_ty.fmt(pt),
23617-
},
23599+
"expected {f} type; found '{f}'",
23600+
.{ atomic_op.supportedTypes(), elem_ty.fmt(pt) },
2361823601
);
2361923602
errdefer msg.destroy(sema.gpa);
2362023603

23621-
if (elem_ty.isRuntimeFloat() and atomic_op == .cmpxchg) {
23622-
try sema.errNote(
23623-
elem_ty_src,
23624-
msg,
23625-
"floats are not supported for cmpxchg because float equality differs from bitwise equality",
23626-
.{},
23627-
);
23604+
if (why.len != 0) {
23605+
try sema.errNote(elem_ty_src, msg, "{s}", .{why});
2362823606
}
2362923607

2363023608
try sema.addDeclaredHereNote(msg, elem_ty);
@@ -23701,6 +23679,52 @@ fn checkAtomicPtrOperand(
2370123679
return casted_ptr;
2370223680
}
2370323681

23682+
/// If the type is invalid for this atomic operation, returns an error note explaining why.
23683+
fn isInvalidAtomicType(sema: *Sema, op: std.atomic.Op, elem_ty: Type) ?[]const u8 {
23684+
const zcu = sema.pt.zcu;
23685+
const valid_types = op.supportedTypes();
23686+
if (elem_ty.isRuntimeFloat()) {
23687+
if (!valid_types.float) {
23688+
switch (op) {
23689+
.load, .store => unreachable,
23690+
.rmw => return "@atomicRmw with float only allowed with .Xchg, .Add, .Sub, .Max, and .Min",
23691+
.cmpxchg => return "floats are not supported for cmpxchg because float equality differs from bitwise equality",
23692+
}
23693+
}
23694+
} else if (elem_ty.isPtrAtRuntime(zcu)) {
23695+
// TODO: pointers are currently supported for things like rmw add, but maybe this shouldn't be the case?
23696+
std.debug.assert(valid_types.pointer);
23697+
} else switch (elem_ty.zigTypeTag(zcu)) {
23698+
.bool => if (!valid_types.bool) {
23699+
switch (op) {
23700+
.load, .store, .cmpxchg => unreachable,
23701+
.rmw => return "@atomicRmw with bool only allowed with .Xchg",
23702+
}
23703+
},
23704+
.int => std.debug.assert(valid_types.integer),
23705+
.@"enum" => if (!valid_types.@"enum") {
23706+
switch (op) {
23707+
.load, .store, .cmpxchg => unreachable,
23708+
.rmw => return "@atomicRmw with enum only allowed with .Xchg",
23709+
}
23710+
},
23711+
.error_set => if (!valid_types.error_set) {
23712+
switch (op) {
23713+
.load, .store, .cmpxchg => unreachable,
23714+
.rmw => return "@atomicRmw with error set only allowed with .Xchg",
23715+
}
23716+
},
23717+
.@"struct" => if (elem_ty.containerLayout(zcu) != .@"packed") {
23718+
return ""; // No notes
23719+
} else {
23720+
std.debug.assert(valid_types.packed_struct);
23721+
},
23722+
else => return "", // No notes
23723+
}
23724+
23725+
return null; // All ok!
23726+
}
23727+
2370423728
fn checkPtrIsNotComptimeMutable(
2370523729
sema: *Sema,
2370623730
block: *Block,
@@ -24549,20 +24573,6 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
2454924573
const uncasted_ptr = try sema.resolveInst(extra.ptr);
2455024574
const op = try sema.resolveAtomicRmwOp(block, op_src, extra.operation);
2455124575
const ptr = try sema.checkAtomicPtrOperand(block, .{ .rmw = op }, src, elem_ty, elem_ty_src, uncasted_ptr, ptr_src, false);
24552-
24553-
switch (elem_ty.zigTypeTag(zcu)) {
24554-
.@"enum" => if (op != .Xchg) {
24555-
return sema.fail(block, op_src, "@atomicRmw with enum only allowed with .Xchg", .{});
24556-
},
24557-
.bool => if (op != .Xchg) {
24558-
return sema.fail(block, op_src, "@atomicRmw with bool only allowed with .Xchg", .{});
24559-
},
24560-
.float => switch (op) {
24561-
.Xchg, .Add, .Sub, .Max, .Min => {},
24562-
else => return sema.fail(block, op_src, "@atomicRmw with float only allowed with .Xchg, .Add, .Sub, .Max, and .Min", .{}),
24563-
},
24564-
else => {},
24565-
}
2456624576
const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering, .{ .simple = .atomic_order });
2456724577

2456824578
if (order == .unordered) {

test/cases/compile_errors/atomicrmw_with_bool_op_not_.Xchg.zig

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ export fn entry() void {
44
}
55

66
// error
7-
// backend=stage2
8-
// target=native
97
//
10-
// :3:31: error: @atomicRmw with bool only allowed with .Xchg
8+
// :3:20: error: expected integer, float, packed struct, or pointer type; found 'bool'
9+
// :3:20: note: @atomicRmw with bool only allowed with .Xchg
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1+
const E = enum(u8) {
2+
a,
3+
b,
4+
c,
5+
d,
6+
};
17
export fn entry() void {
2-
const E = enum(u8) {
3-
a,
4-
b,
5-
c,
6-
d,
7-
};
88
var x: E = .a;
99
_ = @atomicRmw(E, &x, .Add, .b, .seq_cst);
1010
}
1111

1212
// error
13-
// backend=stage2
14-
// target=native
1513
//
16-
// :9:28: error: @atomicRmw with enum only allowed with .Xchg
14+
// :9:20: error: expected integer, float, packed struct, or pointer type; found 'tmp.E'
15+
// :9:20: note: @atomicRmw with enum only allowed with .Xchg
16+
// :1:11: note: enum declared here

test/cases/compile_errors/atomicrmw_with_float_op_not_.Xchg_.Add_.Sub_.Max_or_.Min.zig

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ export fn entry() void {
44
}
55

66
// error
7-
// backend=stage2
8-
// target=native
97
//
10-
// :3:30: error: @atomicRmw with float only allowed with .Xchg, .Add, .Sub, .Max, and .Min
8+
// :3:20: error: expected integer, packed struct, or pointer type; found 'f32'
9+
// :3:20: note: @atomicRmw with float only allowed with .Xchg, .Add, .Sub, .Max, and .Min

0 commit comments

Comments
 (0)