| author | |
| committer | |
| log | e131a2c8e20de13256954cbb38ca3502cdfca07b |
| tree | 4734479898bf5a21d931f52d84039136351e17ee |
| parent | ba1331090c19662dc0eff4d38f80df6ec58c675a |
| signature |
7 files changed, 93 insertions(+), 1 deletions(-)
doc/langref.html.in+7| ... | @@ -2190,6 +2190,7 @@ or | ... | @@ -2190,6 +2190,7 @@ or |
| 2190 | <li>An {#link|enum#} field uses exactly the bit width of its integer tag type.</li> | 2190 | <li>An {#link|enum#} field uses exactly the bit width of its integer tag type.</li> |
| 2191 | <li>A {#link|packed union#} field uses exactly the bit width of the union field with | 2191 | <li>A {#link|packed union#} field uses exactly the bit width of the union field with |
| 2192 | the largest bit width.</li> | 2192 | the largest bit width.</li> |
| 2193 | <li>Packed structs support equality operators.</li> | ||
| 2193 | </ul> | 2194 | </ul> |
| 2194 | <p> | 2195 | <p> |
| 2195 | This means that a {#syntax#}packed struct{#endsyntax#} can participate | 2196 | This means that a {#syntax#}packed struct{#endsyntax#} can participate |
| ... | @@ -2240,6 +2241,12 @@ or | ... | @@ -2240,6 +2241,12 @@ or |
| 2240 | </p> | 2241 | </p> |
| 2241 | {#code|test_aligned_struct_fields.zig#} | 2242 | {#code|test_aligned_struct_fields.zig#} |
| 2242 | 2243 | ||
| 2244 | <p> | ||
| 2245 | Equating packed structs results in a comparison of the backing integer, | ||
| 2246 | and only works for the `==` and `!=` operators. | ||
| 2247 | </p> | ||
| 2248 | {#code|test_packed_struct_equality.zig#} | ||
| 2249 | |||
| 2243 | <p> | 2250 | <p> |
| 2244 | Using packed structs with {#link|volatile#} is problematic, and may be a compile error in the future. | 2251 | Using packed structs with {#link|volatile#} is problematic, and may be a compile error in the future. |
| 2245 | For details on this subscribe to | 2252 | For details on this subscribe to |
doc/langref/test_packed_struct_equality.zig created+14| ... | @@ -0,0 +1,14 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const expect = std.testing.expect; | ||
| 3 | |||
| 4 | test "packed struct equality" { | ||
| 5 | const S = packed struct { | ||
| 6 | a: u4, | ||
| 7 | b: u4, | ||
| 8 | }; | ||
| 9 | const x: S = .{ .a = 1, .b = 2 }; | ||
| 10 | const y: S = .{ .b = 2, .a = 1 }; | ||
| 11 | try expect(x == y); | ||
| 12 | } | ||
| 13 | |||
| 14 | // test | ||
src/Type.zig+2-1| ... | @@ -39,6 +39,7 @@ pub fn baseZigTypeTag(self: Type, mod: *Zcu) std.builtin.TypeId { | ... | @@ -39,6 +39,7 @@ pub fn baseZigTypeTag(self: Type, mod: *Zcu) std.builtin.TypeId { |
| 39 | }; | 39 | }; |
| 40 | } | 40 | } |
| 41 | 41 | ||
| 42 | /// Asserts the type is resolved. | ||
| 42 | pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool { | 43 | pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool { |
| 43 | return switch (ty.zigTypeTag(zcu)) { | 44 | return switch (ty.zigTypeTag(zcu)) { |
| 44 | .int, | 45 | .int, |
| ... | @@ -62,7 +63,6 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool { | ... | @@ -62,7 +63,6 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool { |
| 62 | 63 | ||
| 63 | .noreturn, | 64 | .noreturn, |
| 64 | .array, | 65 | .array, |
| 65 | .@"struct", | ||
| 66 | .undefined, | 66 | .undefined, |
| 67 | .null, | 67 | .null, |
| 68 | .error_union, | 68 | .error_union, |
| ... | @@ -70,6 +70,7 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool { | ... | @@ -70,6 +70,7 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool { |
| 70 | .frame, | 70 | .frame, |
| 71 | => false, | 71 | => false, |
| 72 | 72 | ||
| 73 | .@"struct" => is_equality_cmp and ty.containerLayout(zcu) == .@"packed", | ||
| 73 | .pointer => !ty.isSlice(zcu) and (is_equality_cmp or ty.isCPtr(zcu)), | 74 | .pointer => !ty.isSlice(zcu) and (is_equality_cmp or ty.isCPtr(zcu)), |
| 74 | .optional => { | 75 | .optional => { |
| 75 | if (!is_equality_cmp) return false; | 76 | if (!is_equality_cmp) return false; |
src/arch/riscv64/CodeGen.zig+8| ... | @@ -5162,6 +5162,7 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { | ... | @@ -5162,6 +5162,7 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { |
| 5162 | const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; | 5162 | const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 5163 | const pt = func.pt; | 5163 | const pt = func.pt; |
| 5164 | const zcu = pt.zcu; | 5164 | const zcu = pt.zcu; |
| 5165 | const ip = &zcu.intern_pool; | ||
| 5165 | 5166 | ||
| 5166 | const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: { | 5167 | const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: { |
| 5167 | const lhs_ty = func.typeOf(bin_op.lhs); | 5168 | 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 { | ... | @@ -5173,6 +5174,7 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { |
| 5173 | .pointer, | 5174 | .pointer, |
| 5174 | .error_set, | 5175 | .error_set, |
| 5175 | .optional, | 5176 | .optional, |
| 5177 | .@"struct", | ||
| 5176 | => { | 5178 | => { |
| 5177 | const int_ty = switch (lhs_ty.zigTypeTag(zcu)) { | 5179 | const int_ty = switch (lhs_ty.zigTypeTag(zcu)) { |
| 5178 | .@"enum" => lhs_ty.intTagType(zcu), | 5180 | .@"enum" => lhs_ty.intTagType(zcu), |
| ... | @@ -5190,6 +5192,12 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { | ... | @@ -5190,6 +5192,12 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { |
| 5190 | return func.fail("TODO riscv cmp non-pointer optionals", .{}); | 5192 | return func.fail("TODO riscv cmp non-pointer optionals", .{}); |
| 5191 | } | 5193 | } |
| 5192 | }, | 5194 | }, |
| 5195 | .@"struct" => blk: { | ||
| 5196 | const struct_obj = ip.loadStructType(lhs_ty.toIntern()); | ||
| 5197 | assert(struct_obj.layout == .@"packed"); | ||
| 5198 | const backing_index = struct_obj.backingIntTypeUnordered(ip); | ||
| 5199 | break :blk Type.fromInterned(backing_index); | ||
| 5200 | }, | ||
| 5193 | else => unreachable, | 5201 | else => unreachable, |
| 5194 | }; | 5202 | }; |
| 5195 | 5203 |
src/codegen/llvm.zig+7| ... | @@ -6032,6 +6032,7 @@ pub const FuncGen = struct { | ... | @@ -6032,6 +6032,7 @@ pub const FuncGen = struct { |
| 6032 | const o = self.ng.object; | 6032 | const o = self.ng.object; |
| 6033 | const pt = o.pt; | 6033 | const pt = o.pt; |
| 6034 | const zcu = pt.zcu; | 6034 | const zcu = pt.zcu; |
| 6035 | const ip = &zcu.intern_pool; | ||
| 6035 | const scalar_ty = operand_ty.scalarType(zcu); | 6036 | const scalar_ty = operand_ty.scalarType(zcu); |
| 6036 | const int_ty = switch (scalar_ty.zigTypeTag(zcu)) { | 6037 | const int_ty = switch (scalar_ty.zigTypeTag(zcu)) { |
| 6037 | .@"enum" => scalar_ty.intTagType(zcu), | 6038 | .@"enum" => scalar_ty.intTagType(zcu), |
| ... | @@ -6110,6 +6111,12 @@ pub const FuncGen = struct { | ... | @@ -6110,6 +6111,12 @@ pub const FuncGen = struct { |
| 6110 | return phi.toValue(); | 6111 | return phi.toValue(); |
| 6111 | }, | 6112 | }, |
| 6112 | .float => return self.buildFloatCmp(fast, op, operand_ty, .{ lhs, rhs }), | 6113 | .float => return self.buildFloatCmp(fast, op, operand_ty, .{ lhs, rhs }), |
| 6114 | .@"struct" => blk: { | ||
| 6115 | const struct_obj = ip.loadStructType(scalar_ty.toIntern()); | ||
| 6116 | assert(struct_obj.layout == .@"packed"); | ||
| 6117 | const backing_index = struct_obj.backingIntTypeUnordered(ip); | ||
| 6118 | break :blk Type.fromInterned(backing_index); | ||
| 6119 | }, | ||
| 6113 | else => unreachable, | 6120 | else => unreachable, |
| 6114 | }; | 6121 | }; |
| 6115 | const is_signed = int_ty.isSignedInt(zcu); | 6122 | const is_signed = int_ty.isSignedInt(zcu); |
test/behavior/packed-struct.zig+20| ... | @@ -1297,3 +1297,23 @@ test "packed struct contains optional pointer" { | ... | @@ -1297,3 +1297,23 @@ test "packed struct contains optional pointer" { |
| 1297 | } = .{}; | 1297 | } = .{}; |
| 1298 | try expect(foo.a == null); | 1298 | try expect(foo.a == null); |
| 1299 | } | 1299 | } |
| 1300 | |||
| 1301 | test "packed struct equality" { | ||
| 1302 | const Foo = packed struct { | ||
| 1303 | a: u4, | ||
| 1304 | b: u4, | ||
| 1305 | }; | ||
| 1306 | |||
| 1307 | const S = struct { | ||
| 1308 | fn doTest(x: Foo, y: Foo) !void { | ||
| 1309 | try expect(x == y); | ||
| 1310 | try expect(!(x != y)); | ||
| 1311 | } | ||
| 1312 | }; | ||
| 1313 | |||
| 1314 | const x: Foo = .{ .a = 1, .b = 2 }; | ||
| 1315 | const y: Foo = .{ .b = 2, .a = 1 }; | ||
| 1316 | |||
| 1317 | try S.doTest(x, y); | ||
| 1318 | comptime try S.doTest(x, y); | ||
| 1319 | } |
test/cases/compile_errors/packed_struct_comparison.zig created+35| ... | @@ -0,0 +1,35 @@ | ||
| 1 | const x: Foo = .{}; | ||
| 2 | const y: Foo = .{}; | ||
| 3 | |||
| 4 | export fn a() void { | ||
| 5 | _ = x > y; | ||
| 6 | } | ||
| 7 | |||
| 8 | export fn b() void { | ||
| 9 | _ = x < y; | ||
| 10 | } | ||
| 11 | |||
| 12 | export fn c() void { | ||
| 13 | _ = x >= y; | ||
| 14 | } | ||
| 15 | export fn d() void { | ||
| 16 | _ = x <= y; | ||
| 17 | } | ||
| 18 | |||
| 19 | const Foo = packed struct { | ||
| 20 | a: u4 = 10, | ||
| 21 | b: u4 = 5, | ||
| 22 | }; | ||
| 23 | |||
| 24 | // error | ||
| 25 | // backend=stage2 | ||
| 26 | // target=native | ||
| 27 | // | ||
| 28 | // :5:11: error: operator > not allowed for type 'tmp.Foo' | ||
| 29 | // :19:20: note: struct declared here | ||
| 30 | // :9:11: error: operator < not allowed for type 'tmp.Foo' | ||
| 31 | // :19:20: note: struct declared here | ||
| 32 | // :13:11: error: operator >= not allowed for type 'tmp.Foo' | ||
| 33 | // :19:20: note: struct declared here | ||
| 34 | // :16:11: error: operator <= not allowed for type 'tmp.Foo' | ||
| 35 | // :19:20: note: struct declared here | ||