zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit e131a2c8e20de13256954cbb38ca3502cdfca07b (tree)
parent ba1331090c19662dc0eff4d38f80df6ec58c675a
Author: David Rubin <87927264+Rexicon226@users.noreply.github.com>
Date:   Sat, 12 Oct 2024 20:59:12 -0700

implement packed struct equality (#21679)


Diffstat:
Mdoc/langref.html.in | 7+++++++
Adoc/langref/test_packed_struct_equality.zig | 14++++++++++++++
Msrc/Type.zig | 3++-
Msrc/arch/riscv64/CodeGen.zig | 8++++++++
Msrc/codegen/llvm.zig | 7+++++++
Mtest/behavior/packed-struct.zig | 20++++++++++++++++++++
Atest/cases/compile_errors/packed_struct_comparison.zig | 35+++++++++++++++++++++++++++++++++++
7 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/doc/langref.html.in b/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 @@ -2241,6 +2242,12 @@ or {#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 <a href="https://github.com/ziglang/zig/issues/1761">this issue</a>. diff --git a/doc/langref/test_packed_struct_equality.zig 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 diff --git 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 @@ -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 @@ -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 @@ -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 @@ -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