Skip to content

Commit 97d4c08

Browse files
committed
Improve error reporting around atomics
1 parent 02c7ba7 commit 97d4c08

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
@@ -23591,39 +23591,17 @@ fn checkAtomicPtrOperand(
2359123591
const pt = sema.pt;
2359223592
const zcu = pt.zcu;
2359323593

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

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

2362923607
try sema.addDeclaredHereNote(msg, elem_ty);
@@ -23700,6 +23678,52 @@ fn checkAtomicPtrOperand(
2370023678
return casted_ptr;
2370123679
}
2370223680

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

2456724577
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)