From 604439aaedc4fe5342d09c8c71b3497522578970 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Sat, 12 Oct 2024 00:19:59 -0700 Subject: [PATCH 1/2] implement packed struct equality --- src/Type.zig | 3 +- src/arch/riscv64/CodeGen.zig | 8 +++++ src/codegen/llvm.zig | 7 ++++ test/behavior/packed-struct.zig | 20 +++++++++++ .../packed_struct_comparison.zig | 35 +++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 test/cases/compile_errors/packed_struct_comparison.zig diff --git a/src/Type.zig b/src/Type.zig index 808c8cfdb3ce..c6feb9ddd405 100644 --- a/src/Type.zig +++ b/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; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 7309160488ed..5d76db0104bc 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/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, }; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 623aa995d427..bd59cef56910 100644 --- a/src/codegen/llvm.zig +++ b/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); diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig index 8386f62f7782..f4ece2fe043a 100644 --- a/test/behavior/packed-struct.zig +++ b/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); +} diff --git a/test/cases/compile_errors/packed_struct_comparison.zig b/test/cases/compile_errors/packed_struct_comparison.zig new file mode 100644 index 000000000000..8a3af3a8b224 --- /dev/null +++ b/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 From a7930d86217ab62a8a0b855d9401de14ecdf2127 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Sat, 12 Oct 2024 00:20:16 -0700 Subject: [PATCH 2/2] add langref docs --- doc/langref.html.in | 7 +++++++ doc/langref/test_packed_struct_equality.zig | 14 ++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 doc/langref/test_packed_struct_equality.zig diff --git a/doc/langref.html.in b/doc/langref.html.in index 54b923d0d953..0221f1826eaa 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2190,6 +2190,7 @@ or
  • An {#link|enum#} field uses exactly the bit width of its integer tag type.
  • A {#link|packed union#} field uses exactly the bit width of the union field with the largest bit width.
  • +
  • Packed structs support equality operators.
  • This means that a {#syntax#}packed struct{#endsyntax#} can participate @@ -2240,6 +2241,12 @@ or

    {#code|test_aligned_struct_fields.zig#} +

    + Equating packed structs results in a comparison of the backing integer, + and only works for the `==` and `!=` operators. +

    + {#code|test_packed_struct_equality.zig#} +

    Using packed structs with {#link|volatile#} is problematic, and may be a compile error in the future. For details on this subscribe to diff --git a/doc/langref/test_packed_struct_equality.zig b/doc/langref/test_packed_struct_equality.zig new file mode 100644 index 000000000000..d1c755af195f --- /dev/null +++ b/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