authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-10 00:06:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-20 18:33:01-07:00
log0681bf06abfccdc3c215b712950f17e0bbeb95a0
tree7628178f7d290c350c03be867133ddfd265205a1
parente1a750655e05875046abf6a4819062b4f008c330

frontend: packed struct field ptr no longer finds byte borders

technically breaking, but I doubt anyone will notice.

5 files changed, 75 insertions(+), 153 deletions(-)

src/Air/Legalize.zig+4-6
......@@ -2682,12 +2682,10 @@ const Block = struct {
26822682 },
26832683 .@"packed" => switch (agg_ty.zigTypeTag(zcu)) {
26842684 else => unreachable,
2685 .@"struct" => switch (agg_ty.packedStructFieldPtrInfo(agg_ptr_ty, @intCast(field_index), pt)) {
2686 .bit_ptr => |packed_offset| {
2687 field_ptr_info.packed_offset = packed_offset;
2688 break :field_ptr_align agg_ptr_align;
2689 },
2690 .byte_ptr => |ptr_info| ptr_info.alignment,
2685 .@"struct" => {
2686 const packed_offset = agg_ty.packedStructFieldPtrInfo(agg_ptr_ty, @intCast(field_index), pt);
2687 field_ptr_info.packed_offset = packed_offset;
2688 break :field_ptr_align agg_ptr_align;
26912689 },
26922690 .@"union" => {
26932691 field_ptr_info.packed_offset = .{
src/Sema.zig+3-9
......@@ -27457,15 +27457,9 @@ fn structFieldPtrByIndex(
2745727457
2745827458 if (struct_type.layout == .@"packed") {
2745927459 assert(!field_is_comptime);
27460 switch (struct_ty.packedStructFieldPtrInfo(struct_ptr_ty, field_index, pt)) {
27461 .bit_ptr => |packed_offset| {
27462 ptr_ty_data.flags.alignment = parent_align;
27463 ptr_ty_data.packed_offset = packed_offset;
27464 },
27465 .byte_ptr => |ptr_info| {
27466 ptr_ty_data.flags.alignment = ptr_info.alignment;
27467 },
27468 }
27460 const packed_offset = struct_ty.packedStructFieldPtrInfo(struct_ptr_ty, field_index, pt);
27461 ptr_ty_data.flags.alignment = parent_align;
27462 ptr_ty_data.packed_offset = packed_offset;
2746927463 } else if (struct_type.layout == .@"extern") {
2747027464 assert(!field_is_comptime);
2747127465 // For extern structs, field alignment might be bigger than type's
src/Type.zig+9-32
......@@ -3514,22 +3514,17 @@ pub fn arrayBase(ty: Type, zcu: *const Zcu) struct { Type, u64 } {
35143514 return .{ cur_ty, cur_len };
35153515}
35163516
3517pub fn packedStructFieldPtrInfo(struct_ty: Type, parent_ptr_ty: Type, field_idx: u32, pt: Zcu.PerThread) union(enum) {
3518 /// The result is a bit-pointer with the same value and a new packed offset.
3519 bit_ptr: InternPool.Key.PtrType.PackedOffset,
3520 /// The result is a standard pointer.
3521 byte_ptr: struct {
3522 /// The byte offset of the field pointer from the parent pointer value.
3523 offset: u64,
3524 /// The alignment of the field pointer type.
3525 alignment: InternPool.Alignment,
3526 },
3527} {
3517/// Returns a bit-pointer with the same value and a new packed offset.
3518pub fn packedStructFieldPtrInfo(
3519 struct_ty: Type,
3520 parent_ptr_ty: Type,
3521 field_idx: u32,
3522 pt: Zcu.PerThread,
3523) InternPool.Key.PtrType.PackedOffset {
35283524 comptime assert(Type.packed_struct_layout_version == 2);
35293525
35303526 const zcu = pt.zcu;
35313527 const parent_ptr_info = parent_ptr_ty.ptrInfo(zcu);
3532 const field_ty = struct_ty.fieldType(field_idx, zcu);
35333528
35343529 var bit_offset: u16 = 0;
35353530 var running_bits: u16 = 0;
......@@ -3552,28 +3547,10 @@ pub fn packedStructFieldPtrInfo(struct_ty: Type, parent_ptr_ty: Type, field_idx:
35523547 bit_offset,
35533548 };
35543549
3555 // If the field happens to be byte-aligned, simplify the pointer type.
3556 // We can only do this if the pointee's bit size matches its ABI byte size,
3557 // so that loads and stores do not interfere with surrounding packed bits.
3558 //
3559 // TODO: we do not attempt this with big-endian targets yet because of nested
3560 // structs and floats. I need to double-check the desired behavior for big endian
3561 // targets before adding the necessary complications to this code. This will not
3562 // cause miscompilations; it only means the field pointer uses bit masking when it
3563 // might not be strictly necessary.
3564 if (res_bit_offset % 8 == 0 and field_ty.bitSize(zcu) == field_ty.abiSize(zcu) * 8 and zcu.getTarget().cpu.arch.endian() == .little) {
3565 const byte_offset = res_bit_offset / 8;
3566 const new_align = Alignment.fromLog2Units(@ctz(byte_offset | parent_ptr_ty.ptrAlignment(zcu).toByteUnits().?));
3567 return .{ .byte_ptr = .{
3568 .offset = byte_offset,
3569 .alignment = new_align,
3570 } };
3571 }
3572
3573 return .{ .bit_ptr = .{
3550 return .{
35743551 .host_size = res_host_size,
35753552 .bit_offset = res_bit_offset,
3576 } };
3553 };
35773554}
35783555
35793556pub fn resolveLayout(ty: Type, pt: Zcu.PerThread) SemaError!void {
src/Value.zig+12-26
......@@ -2255,32 +2255,18 @@ pub fn ptrField(parent_ptr: Value, field_idx: u32, pt: Zcu.PerThread) !Value {
22552255 });
22562256 return parent_ptr.getOffsetPtr(byte_off, result_ty, pt);
22572257 },
2258 .@"packed" => switch (aggregate_ty.packedStructFieldPtrInfo(parent_ptr_ty, field_idx, pt)) {
2259 .bit_ptr => |packed_offset| {
2260 const result_ty = try pt.ptrType(info: {
2261 var new = parent_ptr_info;
2262 new.packed_offset = packed_offset;
2263 new.child = field_ty.toIntern();
2264 if (new.flags.alignment == .none) {
2265 new.flags.alignment = try aggregate_ty.abiAlignmentSema(pt);
2266 }
2267 break :info new;
2268 });
2269 return pt.getCoerced(parent_ptr, result_ty);
2270 },
2271 .byte_ptr => |ptr_info| {
2272 const result_ty = try pt.ptrTypeSema(info: {
2273 var new = parent_ptr_info;
2274 new.child = field_ty.toIntern();
2275 new.packed_offset = .{
2276 .host_size = 0,
2277 .bit_offset = 0,
2278 };
2279 new.flags.alignment = ptr_info.alignment;
2280 break :info new;
2281 });
2282 return parent_ptr.getOffsetPtr(ptr_info.offset, result_ty, pt);
2283 },
2258 .@"packed" => {
2259 const packed_offset = aggregate_ty.packedStructFieldPtrInfo(parent_ptr_ty, field_idx, pt);
2260 const result_ty = try pt.ptrType(info: {
2261 var new = parent_ptr_info;
2262 new.packed_offset = packed_offset;
2263 new.child = field_ty.toIntern();
2264 if (new.flags.alignment == .none) {
2265 new.flags.alignment = try aggregate_ty.abiAlignmentSema(pt);
2266 }
2267 break :info new;
2268 });
2269 return pt.getCoerced(parent_ptr, result_ty);
22842270 },
22852271 }
22862272 },
test/behavior/packed-struct.zig+47-80
......@@ -3,7 +3,6 @@ const builtin = @import("builtin");
33const assert = std.debug.assert;
44const expect = std.testing.expect;
55const expectEqual = std.testing.expectEqual;
6const native_endian = builtin.cpu.arch.endian();
76
87test "flags in packed structs" {
98 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
......@@ -163,26 +162,24 @@ test "correct sizeOf and offsets in packed structs" {
163162 try expectEqual(22, @bitOffsetOf(PStruct, "u10_b"));
164163 try expectEqual(4, @sizeOf(PStruct));
165164
166 if (native_endian == .little) {
167 const s1 = @as(PStruct, @bitCast(@as(u32, 0x12345678)));
168 try expectEqual(false, s1.bool_a);
169 try expectEqual(false, s1.bool_b);
170 try expectEqual(false, s1.bool_c);
171 try expectEqual(true, s1.bool_d);
172 try expectEqual(true, s1.bool_e);
173 try expectEqual(true, s1.bool_f);
174 try expectEqual(1, s1.u1_a);
175 try expectEqual(false, s1.bool_g);
176 try expectEqual(0, s1.u1_b);
177 try expectEqual(3, s1.u3_a);
178 try expectEqual(0b1101000101, s1.u10_a);
179 try expectEqual(0b0001001000, s1.u10_b);
180
181 const s2 = @as(packed struct { x: u1, y: u7, z: u24 }, @bitCast(@as(u32, 0xd5c71ff4)));
182 try expectEqual(0, s2.x);
183 try expectEqual(0b1111010, s2.y);
184 try expectEqual(0xd5c71f, s2.z);
185 }
165 const s1 = @as(PStruct, @bitCast(@as(u32, 0x12345678)));
166 try expectEqual(false, s1.bool_a);
167 try expectEqual(false, s1.bool_b);
168 try expectEqual(false, s1.bool_c);
169 try expectEqual(true, s1.bool_d);
170 try expectEqual(true, s1.bool_e);
171 try expectEqual(true, s1.bool_f);
172 try expectEqual(1, s1.u1_a);
173 try expectEqual(false, s1.bool_g);
174 try expectEqual(0, s1.u1_b);
175 try expectEqual(3, s1.u3_a);
176 try expectEqual(0b1101000101, s1.u10_a);
177 try expectEqual(0b0001001000, s1.u10_b);
178
179 const s2 = @as(packed struct { x: u1, y: u7, z: u24 }, @bitCast(@as(u32, 0xd5c71ff4)));
180 try expectEqual(0, s2.x);
181 try expectEqual(0b1111010, s2.y);
182 try expectEqual(0xd5c71f, s2.z);
186183}
187184
188185test "nested packed structs" {
......@@ -202,15 +199,13 @@ test "nested packed structs" {
202199 try expectEqual(3, @offsetOf(S3, "y"));
203200 try expectEqual(24, @bitOffsetOf(S3, "y"));
204201
205 if (native_endian == .little) {
206 const s3 = @as(S3Padded, @bitCast(@as(u64, 0xe952d5c71ff4))).s3;
207 try expectEqual(0xf4, s3.x.a);
208 try expectEqual(0x1f, s3.x.b);
209 try expectEqual(0xc7, s3.x.c);
210 try expectEqual(0xd5, s3.y.d);
211 try expectEqual(0x52, s3.y.e);
212 try expectEqual(0xe9, s3.y.f);
213 }
202 const s3 = @as(S3Padded, @bitCast(@as(u64, 0xe952d5c71ff4))).s3;
203 try expectEqual(0xf4, s3.x.a);
204 try expectEqual(0x1f, s3.x.b);
205 try expectEqual(0xc7, s3.x.c);
206 try expectEqual(0xd5, s3.y.d);
207 try expectEqual(0x52, s3.y.e);
208 try expectEqual(0xe9, s3.y.f);
214209
215210 const S4 = packed struct { a: i32, b: i8 };
216211 const S5 = packed struct { a: i32, b: i8, c: S4 };
......@@ -252,7 +247,6 @@ test "nested packed struct unaligned" {
252247 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
253248 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
254249 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
255 if (native_endian != .little) return error.SkipZigTest; // Byte aligned packed struct field pointers have not been implemented yet
256250
257251 const S1 = packed struct {
258252 a: u4,
......@@ -344,21 +338,12 @@ test "byte-aligned field pointer offsets" {
344338 .c = 3,
345339 .d = 4,
346340 };
347 switch (comptime builtin.cpu.arch.endian()) {
348 .little => {
349 comptime assert(@TypeOf(&a.a) == *align(4) u8);
350 comptime assert(@TypeOf(&a.b) == *u8);
351 comptime assert(@TypeOf(&a.c) == *align(2) u8);
352 comptime assert(@TypeOf(&a.d) == *u8);
353 },
354 .big => {
355 // TODO re-evaluate packed struct endianness
356 comptime assert(@TypeOf(&a.a) == *align(4:0:4) u8);
357 comptime assert(@TypeOf(&a.b) == *align(4:8:4) u8);
358 comptime assert(@TypeOf(&a.c) == *align(4:16:4) u8);
359 comptime assert(@TypeOf(&a.d) == *align(4:24:4) u8);
360 },
361 }
341
342 comptime assert(@TypeOf(&a.a) == *align(4:0:4) u8);
343 comptime assert(@TypeOf(&a.b) == *align(4:8:4) u8);
344 comptime assert(@TypeOf(&a.c) == *align(4:16:4) u8);
345 comptime assert(@TypeOf(&a.d) == *align(4:24:4) u8);
346
362347 try expect(a.a == 1);
363348 try expect(a.b == 2);
364349 try expect(a.c == 3);
......@@ -392,16 +377,10 @@ test "byte-aligned field pointer offsets" {
392377 .a = 1,
393378 .b = 2,
394379 };
395 switch (comptime builtin.cpu.arch.endian()) {
396 .little => {
397 comptime assert(@TypeOf(&b.a) == *align(4) u16);
398 comptime assert(@TypeOf(&b.b) == *u16);
399 },
400 .big => {
401 comptime assert(@TypeOf(&b.a) == *align(4:0:4) u16);
402 comptime assert(@TypeOf(&b.b) == *align(4:16:4) u16);
403 },
404 }
380
381 comptime assert(@TypeOf(&b.a) == *align(4:0:4) u16);
382 comptime assert(@TypeOf(&b.b) == *align(4:16:4) u16);
383
405384 try expect(b.a == 1);
406385 try expect(b.b == 2);
407386
......@@ -426,7 +405,6 @@ test "nested packed struct field pointers" {
426405 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
427406 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // ubsan unaligned pointer access
428407 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO
429 if (native_endian != .little) return error.SkipZigTest; // Byte aligned packed struct field pointers have not been implemented yet
430408
431409 const S2 = packed struct {
432410 base: u8,
......@@ -483,7 +461,6 @@ test "@intFromPtr on a packed struct field" {
483461 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
484462 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
485463 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
486 if (native_endian != .little) return error.SkipZigTest;
487464
488465 const S = struct {
489466 const P = packed struct {
......@@ -498,14 +475,13 @@ test "@intFromPtr on a packed struct field" {
498475 .z = 0,
499476 };
500477 };
501 try expect(@intFromPtr(&S.p0.z) - @intFromPtr(&S.p0.x) == 2);
478 try expect(@intFromPtr(&S.p0.z) - @intFromPtr(&S.p0.x) == 0);
502479}
503480
504481test "@intFromPtr on a packed struct field unaligned and nested" {
505482 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
506483 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
507484 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
508 if (native_endian != .little) return error.SkipZigTest; // Byte aligned packed struct field pointers have not been implemented yet
509485
510486 const S1 = packed struct {
511487 a: u4,
......@@ -565,16 +541,16 @@ test "@intFromPtr on a packed struct field unaligned and nested" {
565541 else => {},
566542 }
567543 try expect(@intFromPtr(&S2.s.base) - @intFromPtr(&S2.s) == 0);
568 try expect(@intFromPtr(&S2.s.p0.a) - @intFromPtr(&S2.s) == 1);
569 try expect(@intFromPtr(&S2.s.p0.b) - @intFromPtr(&S2.s) == 1);
570 try expect(@intFromPtr(&S2.s.p0.c) - @intFromPtr(&S2.s) == 2);
544 try expect(@intFromPtr(&S2.s.p0.a) - @intFromPtr(&S2.s) == 0);
545 try expect(@intFromPtr(&S2.s.p0.b) - @intFromPtr(&S2.s) == 0);
546 try expect(@intFromPtr(&S2.s.p0.c) - @intFromPtr(&S2.s) == 0);
571547 try expect(@intFromPtr(&S2.s.bit0) - @intFromPtr(&S2.s) == 0);
572548 try expect(@intFromPtr(&S2.s.p1.a) - @intFromPtr(&S2.s) == 0);
573549 try expect(@intFromPtr(&S2.s.p2.a) - @intFromPtr(&S2.s) == 0);
574 try expect(@intFromPtr(&S2.s.p2.b) - @intFromPtr(&S2.s) == 5);
575 try expect(@intFromPtr(&S2.s.p3.a) - @intFromPtr(&S2.s) == 6);
576 try expect(@intFromPtr(&S2.s.p3.b) - @intFromPtr(&S2.s) == 6);
577 try expect(@intFromPtr(&S2.s.p3.c) - @intFromPtr(&S2.s) == 7);
550 try expect(@intFromPtr(&S2.s.p2.b) - @intFromPtr(&S2.s) == 0);
551 try expect(@intFromPtr(&S2.s.p3.a) - @intFromPtr(&S2.s) == 0);
552 try expect(@intFromPtr(&S2.s.p3.b) - @intFromPtr(&S2.s) == 0);
553 try expect(@intFromPtr(&S2.s.p3.c) - @intFromPtr(&S2.s) == 0);
578554
579555 const S3 = packed struct {
580556 pad: u8,
......@@ -597,7 +573,7 @@ test "@intFromPtr on a packed struct field unaligned and nested" {
597573 comptime assert(@TypeOf(&S3.v0.s.v) == *align(4:10:4) u3);
598574 comptime assert(@TypeOf(&S3.v0.s.s.v) == *align(4:13:4) u2);
599575 comptime assert(@TypeOf(&S3.v0.s.s.s.bit0) == *align(4:15:4) u1);
600 comptime assert(@TypeOf(&S3.v0.s.s.s.byte) == *align(2) u8);
576 comptime assert(@TypeOf(&S3.v0.s.s.s.byte) == *align(4:16:4) u8);
601577 comptime assert(@TypeOf(&S3.v0.s.s.s.bit1) == *align(4:24:4) u1);
602578 try expect(@intFromPtr(&S3.v0.v) - @intFromPtr(&S3.v0) == 0);
603579 try expect(@intFromPtr(&S3.v0.s) - @intFromPtr(&S3.v0) == 0);
......@@ -606,7 +582,7 @@ test "@intFromPtr on a packed struct field unaligned and nested" {
606582 try expect(@intFromPtr(&S3.v0.s.s.v) - @intFromPtr(&S3.v0) == 0);
607583 try expect(@intFromPtr(&S3.v0.s.s.s) - @intFromPtr(&S3.v0) == 0);
608584 try expect(@intFromPtr(&S3.v0.s.s.s.bit0) - @intFromPtr(&S3.v0) == 0);
609 try expect(@intFromPtr(&S3.v0.s.s.s.byte) - @intFromPtr(&S3.v0) == 2);
585 try expect(@intFromPtr(&S3.v0.s.s.s.byte) - @intFromPtr(&S3.v0) == 0);
610586 try expect(@intFromPtr(&S3.v0.s.s.s.bit1) - @intFromPtr(&S3.v0) == 0);
611587}
612588
......@@ -915,17 +891,8 @@ test "overaligned pointer to packed struct" {
915891 const S = packed struct { a: u32, b: u32 };
916892 var foo: S align(4) = .{ .a = 123, .b = 456 };
917893 const ptr: *align(4) S = &foo;
918 switch (comptime builtin.cpu.arch.endian()) {
919 .little => {
920 const ptr_to_b: *u32 = &ptr.b;
921 try expect(ptr_to_b.* == 456);
922 },
923 .big => {
924 // Byte aligned packed struct field pointers have not been implemented yet.
925 const ptr_to_a: *align(4:0:8) u32 = &ptr.a;
926 try expect(ptr_to_a.* == 123);
927 },
928 }
894 const ptr_to_a: *align(4:0:8) u32 = &ptr.a;
895 try expect(ptr_to_a.* == 123);
929896}
930897
931898test "packed struct initialized in bitcast" {