Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement packed struct equality #21679

Merged
merged 2 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions doc/langref/test_packed_struct_equality.zig
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion src/Type.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -62,14 +63,14 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {

.noreturn,
.array,
.@"struct",
.undefined,
.null,
.error_union,
.@"union",
.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;
Expand Down
8 changes: 8 additions & 0 deletions src/arch/riscv64/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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),
Expand All @@ -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,
};

Expand Down
7 changes: 7 additions & 0 deletions src/codegen/llvm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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);
Expand Down
20 changes: 20 additions & 0 deletions test/behavior/packed-struct.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
35 changes: 35 additions & 0 deletions test/cases/compile_errors/packed_struct_comparison.zig
Original file line number Diff line number Diff line change
@@ -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
Loading