Commit e131a2c8e2

David Rubin <87927264+Rexicon226@users.noreply.github.com>
2024-10-13 05:59:12
implement packed struct equality (#21679)
1 parent ba13310
Changed files (7)
doc/langref/test_packed_struct_equality.zig
@@ -0,0 +1,14 @@
+const std = @import("std");
+const expect = std.testing.expect;
+
+test "packed struct equality" {
+    const S = packed struct {
+        a: u4,
+        b: u4,
+    };
+    const x: S = .{ .a = 1, .b = 2 };
+    const y: S = .{ .b = 2, .a = 1 };
+    try expect(x == y);
+}
+
+// test
doc/langref.html.in
@@ -2190,6 +2190,7 @@ or
         <li>An {#link|enum#} field uses exactly the bit width of its integer tag type.</li>
         <li>A {#link|packed union#} field uses exactly the bit width of the union field with
         the largest bit width.</li>
+        <li>Packed structs support equality operators.</li>
       </ul>
       <p>
       This means that a {#syntax#}packed struct{#endsyntax#} can participate
@@ -2240,6 +2241,12 @@ or
       </p>
       {#code|test_aligned_struct_fields.zig#}
 
+      <p>
+      Equating packed structs results in a comparison of the backing integer, 
+      and only works for the `==` and `!=` operators.
+      </p>
+      {#code|test_packed_struct_equality.zig#}
+
       <p>
       Using packed structs with {#link|volatile#} is problematic, and may be a compile error in the future.
       For details on this subscribe to
src/arch/riscv64/CodeGen.zig
@@ -5162,6 +5162,7 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
     const pt = func.pt;
     const zcu = pt.zcu;
+    const ip = &zcu.intern_pool;
 
     const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: {
         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 {
             .pointer,
             .error_set,
             .optional,
+            .@"struct",
             => {
                 const int_ty = switch (lhs_ty.zigTypeTag(zcu)) {
                     .@"enum" => lhs_ty.intTagType(zcu),
@@ -5190,6 +5192,12 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
                             return func.fail("TODO riscv cmp non-pointer optionals", .{});
                         }
                     },
+                    .@"struct" => blk: {
+                        const struct_obj = ip.loadStructType(lhs_ty.toIntern());
+                        assert(struct_obj.layout == .@"packed");
+                        const backing_index = struct_obj.backingIntTypeUnordered(ip);
+                        break :blk Type.fromInterned(backing_index);
+                    },
                     else => unreachable,
                 };
 
src/codegen/llvm.zig
@@ -6032,6 +6032,7 @@ pub const FuncGen = struct {
         const o = self.ng.object;
         const pt = o.pt;
         const zcu = pt.zcu;
+        const ip = &zcu.intern_pool;
         const scalar_ty = operand_ty.scalarType(zcu);
         const int_ty = switch (scalar_ty.zigTypeTag(zcu)) {
             .@"enum" => scalar_ty.intTagType(zcu),
@@ -6110,6 +6111,12 @@ pub const FuncGen = struct {
                 return phi.toValue();
             },
             .float => return self.buildFloatCmp(fast, op, operand_ty, .{ lhs, rhs }),
+            .@"struct" => blk: {
+                const struct_obj = ip.loadStructType(scalar_ty.toIntern());
+                assert(struct_obj.layout == .@"packed");
+                const backing_index = struct_obj.backingIntTypeUnordered(ip);
+                break :blk Type.fromInterned(backing_index);
+            },
             else => unreachable,
         };
         const is_signed = int_ty.isSignedInt(zcu);
src/Type.zig
@@ -39,6 +39,7 @@ pub fn baseZigTypeTag(self: Type, mod: *Zcu) std.builtin.TypeId {
     };
 }
 
+/// Asserts the type is resolved.
 pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {
     return switch (ty.zigTypeTag(zcu)) {
         .int,
@@ -62,7 +63,6 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {
 
         .noreturn,
         .array,
-        .@"struct",
         .undefined,
         .null,
         .error_union,
@@ -70,6 +70,7 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {
         .frame,
         => false,
 
+        .@"struct" => is_equality_cmp and ty.containerLayout(zcu) == .@"packed",
         .pointer => !ty.isSlice(zcu) and (is_equality_cmp or ty.isCPtr(zcu)),
         .optional => {
             if (!is_equality_cmp) return false;
test/behavior/packed-struct.zig
@@ -1297,3 +1297,23 @@ test "packed struct contains optional pointer" {
     } = .{};
     try expect(foo.a == null);
 }
+
+test "packed struct equality" {
+    const Foo = packed struct {
+        a: u4,
+        b: u4,
+    };
+
+    const S = struct {
+        fn doTest(x: Foo, y: Foo) !void {
+            try expect(x == y);
+            try expect(!(x != y));
+        }
+    };
+
+    const x: Foo = .{ .a = 1, .b = 2 };
+    const y: Foo = .{ .b = 2, .a = 1 };
+
+    try S.doTest(x, y);
+    comptime try S.doTest(x, y);
+}
test/cases/compile_errors/packed_struct_comparison.zig
@@ -0,0 +1,35 @@
+const x: Foo = .{};
+const y: Foo = .{};
+
+export fn a() void {
+    _ = x > y;
+}
+
+export fn b() void {
+    _ = x < y;
+}
+
+export fn c() void {
+    _ = x >= y;
+}
+export fn d() void {
+    _ = x <= y;
+}
+
+const Foo = packed struct {
+    a: u4 = 10,
+    b: u4 = 5,
+};
+
+// error
+// backend=stage2
+// target=native
+//
+// :5:11: error: operator > not allowed for type 'tmp.Foo'
+// :19:20: note: struct declared here
+// :9:11: error: operator < not allowed for type 'tmp.Foo'
+// :19:20: note: struct declared here
+// :13:11: error: operator >= not allowed for type 'tmp.Foo'
+// :19:20: note: struct declared here
+// :16:11: error: operator <= not allowed for type 'tmp.Foo'
+// :19:20: note: struct declared here