authorgravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2024-10-12 20:59:12-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-10-12 20:59:12-07:00
loge131a2c8e20de13256954cbb38ca3502cdfca07b
tree4734479898bf5a21d931f52d84039136351e17ee
parentba1331090c19662dc0eff4d38f80df6ec58c675a
signaturebadge-check Signed by PGP key B5690EEEBB952194

implement packed struct equality (#21679)


7 files changed, 93 insertions(+), 1 deletions(-)

doc/langref.html.in+7
...@@ -2190,6 +2190,7 @@ or...@@ -2190,6 +2190,7 @@ or
2190 <li>An {#link|enum#} field uses exactly the bit width of its integer tag type.</li>2190 <li>An {#link|enum#} field uses exactly the bit width of its integer tag type.</li>
2191 <li>A {#link|packed union#} field uses exactly the bit width of the union field with2191 <li>A {#link|packed union#} field uses exactly the bit width of the union field with
2192 the largest bit width.</li>2192 the largest bit width.</li>
2193 <li>Packed structs support equality operators.</li>
2193 </ul>2194 </ul>
2194 <p>2195 <p>
2195 This means that a {#syntax#}packed struct{#endsyntax#} can participate2196 This means that a {#syntax#}packed struct{#endsyntax#} can participate
...@@ -2240,6 +2241,12 @@ or...@@ -2240,6 +2241,12 @@ or
2240 </p>2241 </p>
2241 {#code|test_aligned_struct_fields.zig#}2242 {#code|test_aligned_struct_fields.zig#}
22422243
2244 <p>
2245 Equating packed structs results in a comparison of the backing integer,
2246 and only works for the `==` and `!=` operators.
2247 </p>
2248 {#code|test_packed_struct_equality.zig#}
2249
2243 <p>2250 <p>
2244 Using packed structs with {#link|volatile#} is problematic, and may be a compile error in the future.2251 Using packed structs with {#link|volatile#} is problematic, and may be a compile error in the future.
2245 For details on this subscribe to2252 For details on this subscribe to
doc/langref/test_packed_struct_equality.zig created+14
...@@ -0,0 +1,14 @@
1const std = @import("std");
2const expect = std.testing.expect;
3
4test "packed struct equality" {
5 const S = packed struct {
6 a: u4,
7 b: u4,
8 };
9 const x: S = .{ .a = 1, .b = 2 };
10 const y: S = .{ .b = 2, .a = 1 };
11 try expect(x == y);
12}
13
14// test
src/Type.zig+2-1
...@@ -39,6 +39,7 @@ pub fn baseZigTypeTag(self: Type, mod: *Zcu) std.builtin.TypeId {...@@ -39,6 +39,7 @@ pub fn baseZigTypeTag(self: Type, mod: *Zcu) std.builtin.TypeId {
39 };39 };
40}40}
4141
42/// Asserts the type is resolved.
42pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {43pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {
43 return switch (ty.zigTypeTag(zcu)) {44 return switch (ty.zigTypeTag(zcu)) {
44 .int,45 .int,
...@@ -62,7 +63,6 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {...@@ -62,7 +63,6 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {
6263
63 .noreturn,64 .noreturn,
64 .array,65 .array,
65 .@"struct",
66 .undefined,66 .undefined,
67 .null,67 .null,
68 .error_union,68 .error_union,
...@@ -70,6 +70,7 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {...@@ -70,6 +70,7 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {
70 .frame,70 .frame,
71 => false,71 => false,
7272
73 .@"struct" => is_equality_cmp and ty.containerLayout(zcu) == .@"packed",
73 .pointer => !ty.isSlice(zcu) and (is_equality_cmp or ty.isCPtr(zcu)),74 .pointer => !ty.isSlice(zcu) and (is_equality_cmp or ty.isCPtr(zcu)),
74 .optional => {75 .optional => {
75 if (!is_equality_cmp) return false;76 if (!is_equality_cmp) return false;
src/arch/riscv64/CodeGen.zig+8
...@@ -5162,6 +5162,7 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {...@@ -5162,6 +5162,7 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
5162 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;5162 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
5163 const pt = func.pt;5163 const pt = func.pt;
5164 const zcu = pt.zcu;5164 const zcu = pt.zcu;
5165 const ip = &zcu.intern_pool;
51655166
5166 const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: {5167 const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: {
5167 const lhs_ty = func.typeOf(bin_op.lhs);5168 const lhs_ty = func.typeOf(bin_op.lhs);
...@@ -5173,6 +5174,7 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {...@@ -5173,6 +5174,7 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
5173 .pointer,5174 .pointer,
5174 .error_set,5175 .error_set,
5175 .optional,5176 .optional,
5177 .@"struct",
5176 => {5178 => {
5177 const int_ty = switch (lhs_ty.zigTypeTag(zcu)) {5179 const int_ty = switch (lhs_ty.zigTypeTag(zcu)) {
5178 .@"enum" => lhs_ty.intTagType(zcu),5180 .@"enum" => lhs_ty.intTagType(zcu),
...@@ -5190,6 +5192,12 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {...@@ -5190,6 +5192,12 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
5190 return func.fail("TODO riscv cmp non-pointer optionals", .{});5192 return func.fail("TODO riscv cmp non-pointer optionals", .{});
5191 }5193 }
5192 },5194 },
5195 .@"struct" => blk: {
5196 const struct_obj = ip.loadStructType(lhs_ty.toIntern());
5197 assert(struct_obj.layout == .@"packed");
5198 const backing_index = struct_obj.backingIntTypeUnordered(ip);
5199 break :blk Type.fromInterned(backing_index);
5200 },
5193 else => unreachable,5201 else => unreachable,
5194 };5202 };
51955203
src/codegen/llvm.zig+7
...@@ -6032,6 +6032,7 @@ pub const FuncGen = struct {...@@ -6032,6 +6032,7 @@ pub const FuncGen = struct {
6032 const o = self.ng.object;6032 const o = self.ng.object;
6033 const pt = o.pt;6033 const pt = o.pt;
6034 const zcu = pt.zcu;6034 const zcu = pt.zcu;
6035 const ip = &zcu.intern_pool;
6035 const scalar_ty = operand_ty.scalarType(zcu);6036 const scalar_ty = operand_ty.scalarType(zcu);
6036 const int_ty = switch (scalar_ty.zigTypeTag(zcu)) {6037 const int_ty = switch (scalar_ty.zigTypeTag(zcu)) {
6037 .@"enum" => scalar_ty.intTagType(zcu),6038 .@"enum" => scalar_ty.intTagType(zcu),
...@@ -6110,6 +6111,12 @@ pub const FuncGen = struct {...@@ -6110,6 +6111,12 @@ pub const FuncGen = struct {
6110 return phi.toValue();6111 return phi.toValue();
6111 },6112 },
6112 .float => return self.buildFloatCmp(fast, op, operand_ty, .{ lhs, rhs }),6113 .float => return self.buildFloatCmp(fast, op, operand_ty, .{ lhs, rhs }),
6114 .@"struct" => blk: {
6115 const struct_obj = ip.loadStructType(scalar_ty.toIntern());
6116 assert(struct_obj.layout == .@"packed");
6117 const backing_index = struct_obj.backingIntTypeUnordered(ip);
6118 break :blk Type.fromInterned(backing_index);
6119 },
6113 else => unreachable,6120 else => unreachable,
6114 };6121 };
6115 const is_signed = int_ty.isSignedInt(zcu);6122 const is_signed = int_ty.isSignedInt(zcu);
test/behavior/packed-struct.zig+20
...@@ -1297,3 +1297,23 @@ test "packed struct contains optional pointer" {...@@ -1297,3 +1297,23 @@ test "packed struct contains optional pointer" {
1297 } = .{};1297 } = .{};
1298 try expect(foo.a == null);1298 try expect(foo.a == null);
1299}1299}
1300
1301test "packed struct equality" {
1302 const Foo = packed struct {
1303 a: u4,
1304 b: u4,
1305 };
1306
1307 const S = struct {
1308 fn doTest(x: Foo, y: Foo) !void {
1309 try expect(x == y);
1310 try expect(!(x != y));
1311 }
1312 };
1313
1314 const x: Foo = .{ .a = 1, .b = 2 };
1315 const y: Foo = .{ .b = 2, .a = 1 };
1316
1317 try S.doTest(x, y);
1318 comptime try S.doTest(x, y);
1319}
test/cases/compile_errors/packed_struct_comparison.zig created+35
...@@ -0,0 +1,35 @@
1const x: Foo = .{};
2const y: Foo = .{};
3
4export fn a() void {
5 _ = x > y;
6}
7
8export fn b() void {
9 _ = x < y;
10}
11
12export fn c() void {
13 _ = x >= y;
14}
15export fn d() void {
16 _ = x <= y;
17}
18
19const Foo = packed struct {
20 a: u4 = 10,
21 b: u4 = 5,
22};
23
24// error
25// backend=stage2
26// target=native
27//
28// :5:11: error: operator > not allowed for type 'tmp.Foo'
29// :19:20: note: struct declared here
30// :9:11: error: operator < not allowed for type 'tmp.Foo'
31// :19:20: note: struct declared here
32// :13:11: error: operator >= not allowed for type 'tmp.Foo'
33// :19:20: note: struct declared here
34// :16:11: error: operator <= not allowed for type 'tmp.Foo'
35// :19:20: note: struct declared here