implement packed struct equality (#21679)

This commit is contained in:
David Rubin 2024-10-12 20:59:12 -07:00 committed by GitHub
parent ba1331090c
commit e131a2c8e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 93 additions and 1 deletions

View File

@ -2190,6 +2190,7 @@ or
<li>An {#link|enum#} field uses exactly the bit width of its integer tag type.</li> <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 <li>A {#link|packed union#} field uses exactly the bit width of the union field with
the largest bit width.</li> the largest bit width.</li>
<li>Packed structs support equality operators.</li>
</ul> </ul>
<p> <p>
This means that a {#syntax#}packed struct{#endsyntax#} can participate This means that a {#syntax#}packed struct{#endsyntax#} can participate
@ -2240,6 +2241,12 @@ or
</p> </p>
{#code|test_aligned_struct_fields.zig#} {#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> <p>
Using packed structs with {#link|volatile#} is problematic, and may be a compile error in the future. Using packed structs with {#link|volatile#} is problematic, and may be a compile error in the future.
For details on this subscribe to For details on this subscribe to

View File

@ -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

View File

@ -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 { pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {
return switch (ty.zigTypeTag(zcu)) { return switch (ty.zigTypeTag(zcu)) {
.int, .int,
@ -62,7 +63,6 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {
.noreturn, .noreturn,
.array, .array,
.@"struct",
.undefined, .undefined,
.null, .null,
.error_union, .error_union,
@ -70,6 +70,7 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {
.frame, .frame,
=> false, => false,
.@"struct" => is_equality_cmp and ty.containerLayout(zcu) == .@"packed",
.pointer => !ty.isSlice(zcu) and (is_equality_cmp or ty.isCPtr(zcu)), .pointer => !ty.isSlice(zcu) and (is_equality_cmp or ty.isCPtr(zcu)),
.optional => { .optional => {
if (!is_equality_cmp) return false; if (!is_equality_cmp) return false;

View File

@ -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 bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
const pt = func.pt; const pt = func.pt;
const zcu = pt.zcu; const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: { const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: {
const lhs_ty = func.typeOf(bin_op.lhs); 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, .pointer,
.error_set, .error_set,
.optional, .optional,
.@"struct",
=> { => {
const int_ty = switch (lhs_ty.zigTypeTag(zcu)) { const int_ty = switch (lhs_ty.zigTypeTag(zcu)) {
.@"enum" => lhs_ty.intTagType(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", .{}); 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, else => unreachable,
}; };

View File

@ -6032,6 +6032,7 @@ pub const FuncGen = struct {
const o = self.ng.object; const o = self.ng.object;
const pt = o.pt; const pt = o.pt;
const zcu = pt.zcu; const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const scalar_ty = operand_ty.scalarType(zcu); const scalar_ty = operand_ty.scalarType(zcu);
const int_ty = switch (scalar_ty.zigTypeTag(zcu)) { const int_ty = switch (scalar_ty.zigTypeTag(zcu)) {
.@"enum" => scalar_ty.intTagType(zcu), .@"enum" => scalar_ty.intTagType(zcu),
@ -6110,6 +6111,12 @@ pub const FuncGen = struct {
return phi.toValue(); return phi.toValue();
}, },
.float => return self.buildFloatCmp(fast, op, operand_ty, .{ lhs, rhs }), .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, else => unreachable,
}; };
const is_signed = int_ty.isSignedInt(zcu); const is_signed = int_ty.isSignedInt(zcu);

View File

@ -1297,3 +1297,23 @@ test "packed struct contains optional pointer" {
} = .{}; } = .{};
try expect(foo.a == null); 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);
}

View File

@ -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