authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-10-03 11:13:40-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-10-03 11:13:40-07:00
log5c3393dc2efc836b0ec23e3c2c5ad3da4f8d6a59
tree6cce7dc2cb662d5b85d75d77cca12a84f46fa63c
parent77338947617f5aed0bfe50ca4975d284736a5d90
parent405705cb76914072b9a91ac29f7cf0bf67b255f4
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #17375 from xxxbxxx/packed-struct

codegen: fix field offsets in packed structs

7 files changed, 398 insertions(+), 71 deletions(-)

src/arch/wasm/CodeGen.zig+13-5
......@@ -3090,12 +3090,19 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue
30903090 return func.lowerParentPtr(elem.base.toValue(), @as(u32, @intCast(elem_offset + offset)));
30913091 },
30923092 .field => |field| {
3093 const parent_ty = mod.intern_pool.typeOf(field.base).toType().childType(mod);
3093 const parent_ptr_ty = mod.intern_pool.typeOf(field.base).toType();
3094 const parent_ty = parent_ptr_ty.childType(mod);
3095 const field_index: u32 = @intCast(field.index);
30943096
30953097 const field_offset = switch (parent_ty.zigTypeTag(mod)) {
3096 .Struct => switch (parent_ty.containerLayout(mod)) {
3097 .Packed => parent_ty.packedStructFieldByteOffset(@as(usize, @intCast(field.index)), mod),
3098 else => parent_ty.structFieldOffset(@as(usize, @intCast(field.index)), mod),
3098 .Struct => blk: {
3099 if (mod.typeToPackedStruct(parent_ty)) |struct_type| {
3100 if (ptr.ty.toType().ptrInfo(mod).packed_offset.host_size == 0)
3101 break :blk @divExact(mod.structPackedFieldBitOffset(struct_type, field_index) + parent_ptr_ty.ptrInfo(mod).packed_offset.bit_offset, 8)
3102 else
3103 break :blk 0;
3104 }
3105 break :blk parent_ty.structFieldOffset(field_index, mod);
30993106 },
31003107 .Union => switch (parent_ty.containerLayout(mod)) {
31013108 .Packed => 0,
......@@ -3821,7 +3828,8 @@ fn structFieldPtr(
38213828 if (result_ty.ptrInfo(mod).packed_offset.host_size != 0) {
38223829 break :offset @as(u32, 0);
38233830 }
3824 break :offset struct_ty.packedStructFieldByteOffset(index, mod) + @divExact(struct_ptr_ty_info.packed_offset.bit_offset, 8);
3831 const struct_type = mod.typeToStruct(struct_ty).?;
3832 break :offset @divExact(mod.structPackedFieldBitOffset(struct_type, index) + struct_ptr_ty_info.packed_offset.bit_offset, 8);
38253833 },
38263834 .Union => 0,
38273835 else => unreachable,
src/arch/x86_64/CodeGen.zig+9-8
......@@ -5774,14 +5774,15 @@ fn fieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, index: u32
57745774 const ptr_container_ty_info = ptr_container_ty.ptrInfo(mod);
57755775 const container_ty = ptr_container_ty.childType(mod);
57765776
5777 const field_offset: i32 = @intCast(switch (container_ty.containerLayout(mod)) {
5778 .Auto, .Extern => container_ty.structFieldOffset(index, mod),
5779 .Packed => if (container_ty.zigTypeTag(mod) == .Struct and
5780 ptr_field_ty.ptrInfo(mod).packed_offset.host_size == 0)
5781 container_ty.packedStructFieldByteOffset(index, mod) + @divExact(ptr_container_ty_info.packed_offset.bit_offset, 8)
5782 else
5783 0,
5784 });
5777 const field_offset: i32 = blk: {
5778 if (mod.typeToPackedStruct(container_ty)) |struct_type| {
5779 break :blk if (ptr_field_ty.ptrInfo(mod).packed_offset.host_size == 0)
5780 @divExact(mod.structPackedFieldBitOffset(struct_type, index) + ptr_container_ty_info.packed_offset.bit_offset, 8)
5781 else
5782 0;
5783 }
5784 break :blk @intCast(container_ty.structFieldOffset(index, mod));
5785 };
57855786
57865787 const src_mcv = try self.resolveInst(operand);
57875788 const dst_mcv = if (switch (src_mcv) {
src/codegen/c.zig+12-8
......@@ -5269,22 +5269,26 @@ fn fieldLocation(
52695269 const ip = &mod.intern_pool;
52705270 const container_ty = container_ptr_ty.childType(mod);
52715271 return switch (container_ty.zigTypeTag(mod)) {
5272 .Struct => switch (container_ty.containerLayout(mod)) {
5273 .Auto, .Extern => for (field_index..container_ty.structFieldCount(mod)) |next_field_index_usize| {
5272 .Struct => blk: {
5273 if (mod.typeToPackedStruct(container_ty)) |struct_type| {
5274 if (field_ptr_ty.ptrInfo(mod).packed_offset.host_size == 0)
5275 break :blk .{ .byte_offset = @divExact(mod.structPackedFieldBitOffset(struct_type, field_index) + container_ptr_ty.ptrInfo(mod).packed_offset.bit_offset, 8) }
5276 else
5277 break :blk .begin;
5278 }
5279
5280 for (field_index..container_ty.structFieldCount(mod)) |next_field_index_usize| {
52745281 const next_field_index: u32 = @intCast(next_field_index_usize);
52755282 if (container_ty.structFieldIsComptime(next_field_index, mod)) continue;
52765283 const field_ty = container_ty.structFieldType(next_field_index, mod);
52775284 if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
52785285
5279 break .{ .field = if (container_ty.isSimpleTuple(mod))
5286 break :blk .{ .field = if (container_ty.isSimpleTuple(mod))
52805287 .{ .field = next_field_index }
52815288 else
52825289 .{ .identifier = ip.stringToSlice(container_ty.legacyStructFieldName(next_field_index, mod)) } };
5283 } else if (container_ty.hasRuntimeBitsIgnoreComptime(mod)) .end else .begin,
5284 .Packed => if (field_ptr_ty.ptrInfo(mod).packed_offset.host_size == 0)
5285 .{ .byte_offset = container_ty.packedStructFieldByteOffset(field_index, mod) + @divExact(container_ptr_ty.ptrInfo(mod).packed_offset.bit_offset, 8) }
5286 else
5287 .begin,
5290 }
5291 break :blk if (container_ty.hasRuntimeBitsIgnoreComptime(mod)) .end else .begin;
52885292 },
52895293 .Union => {
52905294 const union_obj = mod.typeToUnion(container_ty).?;
src/codegen/llvm.zig+19-26
......@@ -4,7 +4,6 @@ const assert = std.debug.assert;
44const Allocator = std.mem.Allocator;
55const log = std.log.scoped(.codegen);
66const math = std.math;
7const native_endian = builtin.cpu.arch.endian();
87const DW = std.dwarf;
98
109const Builder = @import("llvm/Builder.zig");
......@@ -3770,7 +3769,7 @@ pub const Object = struct {
37703769 .opt_payload,
37713770 .elem,
37723771 .field,
3773 => try o.lowerParentPtr(val, ty.ptrInfo(mod).packed_offset.bit_offset % 8 == 0),
3772 => try o.lowerParentPtr(val),
37743773 .comptime_field => unreachable,
37753774 };
37763775 switch (ptr.len) {
......@@ -4230,15 +4229,16 @@ pub const Object = struct {
42304229 return o.lowerDeclRefValue(ptr_ty, decl_index);
42314230 }
42324231
4233 fn lowerParentPtr(o: *Object, ptr_val: Value, byte_aligned: bool) Allocator.Error!Builder.Constant {
4232 fn lowerParentPtr(o: *Object, ptr_val: Value) Allocator.Error!Builder.Constant {
42344233 const mod = o.module;
42354234 const ip = &mod.intern_pool;
4236 return switch (ip.indexToKey(ptr_val.toIntern()).ptr.addr) {
4235 const ptr = ip.indexToKey(ptr_val.toIntern()).ptr;
4236 return switch (ptr.addr) {
42374237 .decl => |decl| o.lowerParentPtrDecl(decl),
42384238 .mut_decl => |mut_decl| o.lowerParentPtrDecl(mut_decl.decl),
42394239 .int => |int| try o.lowerIntAsPtr(int),
42404240 .eu_payload => |eu_ptr| {
4241 const parent_ptr = try o.lowerParentPtr(eu_ptr.toValue(), true);
4241 const parent_ptr = try o.lowerParentPtr(eu_ptr.toValue());
42424242
42434243 const eu_ty = ip.typeOf(eu_ptr).toType().childType(mod);
42444244 const payload_ty = eu_ty.errorUnionPayload(mod);
......@@ -4256,7 +4256,7 @@ pub const Object = struct {
42564256 });
42574257 },
42584258 .opt_payload => |opt_ptr| {
4259 const parent_ptr = try o.lowerParentPtr(opt_ptr.toValue(), true);
4259 const parent_ptr = try o.lowerParentPtr(opt_ptr.toValue());
42604260
42614261 const opt_ty = ip.typeOf(opt_ptr).toType().childType(mod);
42624262 const payload_ty = opt_ty.optionalChild(mod);
......@@ -4274,7 +4274,7 @@ pub const Object = struct {
42744274 },
42754275 .comptime_field => unreachable,
42764276 .elem => |elem_ptr| {
4277 const parent_ptr = try o.lowerParentPtr(elem_ptr.base.toValue(), true);
4277 const parent_ptr = try o.lowerParentPtr(elem_ptr.base.toValue());
42784278 const elem_ty = ip.typeOf(elem_ptr.base).toType().elemType2(mod);
42794279
42804280 return o.builder.gepConst(.inbounds, try o.lowerType(elem_ty), parent_ptr, null, &.{
......@@ -4282,9 +4282,9 @@ pub const Object = struct {
42824282 });
42834283 },
42844284 .field => |field_ptr| {
4285 const parent_ptr = try o.lowerParentPtr(field_ptr.base.toValue(), byte_aligned);
4286 const parent_ty = ip.typeOf(field_ptr.base).toType().childType(mod);
4287
4285 const parent_ptr = try o.lowerParentPtr(field_ptr.base.toValue());
4286 const parent_ptr_ty = ip.typeOf(field_ptr.base).toType();
4287 const parent_ty = parent_ptr_ty.childType(mod);
42884288 const field_index: u32 = @intCast(field_ptr.index);
42894289 switch (parent_ty.zigTypeTag(mod)) {
42904290 .Union => {
......@@ -4309,22 +4309,14 @@ pub const Object = struct {
43094309 },
43104310 .Struct => {
43114311 if (mod.typeToPackedStruct(parent_ty)) |struct_type| {
4312 if (!byte_aligned) return parent_ptr;
4312 const ptr_info = ptr.ty.toType().ptrInfo(mod);
4313 if (ptr_info.packed_offset.host_size != 0) return parent_ptr;
4314
4315 const parent_ptr_info = parent_ptr_ty.ptrInfo(mod);
4316 const bit_offset = mod.structPackedFieldBitOffset(struct_type, field_index) + parent_ptr_info.packed_offset.bit_offset;
43134317 const llvm_usize = try o.lowerType(Type.usize);
4314 const base_addr =
4315 try o.builder.castConst(.ptrtoint, parent_ptr, llvm_usize);
4316 // count bits of fields before this one
4317 // TODO https://github.com/ziglang/zig/issues/17178
4318 const prev_bits = b: {
4319 var b: usize = 0;
4320 for (0..field_index) |i| {
4321 const field_ty = struct_type.field_types.get(ip)[i].toType();
4322 if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
4323 b += @intCast(field_ty.bitSize(mod));
4324 }
4325 break :b b;
4326 };
4327 const byte_offset = try o.builder.intConst(llvm_usize, prev_bits / 8);
4318 const base_addr = try o.builder.castConst(.ptrtoint, parent_ptr, llvm_usize);
4319 const byte_offset = try o.builder.intConst(llvm_usize, @divExact(bit_offset, 8));
43284320 const field_addr = try o.builder.binConst(.add, base_addr, byte_offset);
43294321 return o.builder.castConst(.inttoptr, field_addr, .ptr);
43304322 }
......@@ -10148,6 +10140,7 @@ pub const FuncGen = struct {
1014810140 const result_ty = self.typeOfIndex(inst);
1014910141 const result_ty_info = result_ty.ptrInfo(mod);
1015010142 const struct_ptr_ty_info = struct_ptr_ty.ptrInfo(mod);
10143 const struct_type = mod.typeToStruct(struct_ty).?;
1015110144
1015210145 if (result_ty_info.packed_offset.host_size != 0) {
1015310146 // From LLVM's perspective, a pointer to a packed struct and a pointer
......@@ -10159,7 +10152,7 @@ pub const FuncGen = struct {
1015910152
1016010153 // We have a pointer to a packed struct field that happens to be byte-aligned.
1016110154 // Offset our operand pointer by the correct number of bytes.
10162 const byte_offset = struct_ty.packedStructFieldByteOffset(field_index, mod) + @divExact(struct_ptr_ty_info.packed_offset.bit_offset, 8);
10155 const byte_offset = @divExact(mod.structPackedFieldBitOffset(struct_type, field_index) + struct_ptr_ty_info.packed_offset.bit_offset, 8);
1016310156 if (byte_offset == 0) return struct_ptr;
1016410157 const usize_ty = try o.lowerType(Type.usize);
1016510158 const llvm_index = try o.builder.intValue(usize_ty, byte_offset);
src/type.zig-20
......@@ -3028,26 +3028,6 @@ pub const Type = struct {
30283028 };
30293029 }
30303030
3031 pub fn packedStructFieldBitOffset(ty: Type, field_index: usize, mod: *Module) u32 {
3032 const ip = &mod.intern_pool;
3033 const struct_type = ip.indexToKey(ty.toIntern()).struct_type;
3034 assert(struct_type.layout == .Packed);
3035 comptime assert(Type.packed_struct_layout_version == 2);
3036
3037 var running_bits: u32 = 0;
3038 for (struct_type.field_types.get(ip), 0..) |field_ty, i| {
3039 if (i == field_index) break;
3040 if (!field_ty.toType().hasRuntimeBits(mod)) continue;
3041 const field_bits: u32 = @intCast(field_ty.toType().bitSize(mod));
3042 running_bits += field_bits;
3043 }
3044 return running_bits;
3045 }
3046
3047 pub fn packedStructFieldByteOffset(ty: Type, field_index: usize, mod: *Module) u32 {
3048 return packedStructFieldBitOffset(ty, field_index, mod) / 8;
3049 }
3050
30513031 pub const FieldOffset = struct {
30523032 field: usize,
30533033 offset: u64,
test/behavior/packed-struct.zig+298-4
......@@ -253,6 +253,79 @@ test "regular in irregular packed struct" {
253253 try expectEqual(@as(u8, 42), foo.bar.b);
254254}
255255
256test "nested packed struct unaligned" {
257 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
258 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
259 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
260 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
261 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
262 if (native_endian != .Little) return error.SkipZigTest; // Byte aligned packed struct field pointers have not been implemented yet
263
264 const S1 = packed struct {
265 a: u4,
266 b: u4,
267 c: u8,
268 };
269 const S2 = packed struct {
270 base: u8,
271 p0: S1,
272 bit0: u1,
273 p1: packed struct {
274 a: u8,
275 },
276 p2: packed struct {
277 a: u7,
278 b: u8,
279 },
280 p3: S1,
281
282 var s: @This() = .{
283 .base = 1,
284 .p0 = .{ .a = 2, .b = 3, .c = 4 },
285 .bit0 = 0,
286 .p1 = .{ .a = 5 },
287 .p2 = .{ .a = 6, .b = 7 },
288 .p3 = .{ .a = 8, .b = 9, .c = 10 },
289 };
290 };
291
292 try expect(S2.s.base == 1);
293 try expect(S2.s.p0.a == 2);
294 try expect(S2.s.p0.b == 3);
295 try expect(S2.s.p0.c == 4);
296 try expect(S2.s.bit0 == 0);
297 try expect(S2.s.p1.a == 5);
298 try expect(S2.s.p2.a == 6);
299 try expect(S2.s.p2.b == 7);
300 try expect(S2.s.p3.a == 8);
301 try expect(S2.s.p3.b == 9);
302 try expect(S2.s.p3.c == 10);
303
304 const S3 = packed struct {
305 pad: u8,
306 v: u2,
307 s: packed struct {
308 v: u3,
309 s: packed struct {
310 v: u2,
311 s: packed struct {
312 bit0: u1,
313 byte: u8,
314 bit1: u1,
315 },
316 },
317 },
318 var v0: @This() = .{ .pad = 0, .v = 1, .s = .{ .v = 2, .s = .{ .v = 3, .s = .{ .bit0 = 0, .byte = 4, .bit1 = 1 } } } };
319 };
320
321 try expect(S3.v0.v == 1);
322 try expect(S3.v0.s.v == 2);
323 try expect(S3.v0.s.s.v == 3);
324 try expect(S3.v0.s.s.s.bit0 == 0);
325 try expect(S3.v0.s.s.s.byte == 4);
326 try expect(S3.v0.s.s.s.bit1 == 1);
327}
328
256329test "byte-aligned field pointer offsets" {
257330 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
258331 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
......@@ -354,6 +427,45 @@ test "byte-aligned field pointer offsets" {
354427 try comptime S.doTheTest();
355428}
356429
430test "nested packed struct field pointers" {
431 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
432 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
433 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
434 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
435 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // ubsan unaligned pointer access
436 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
437 if (native_endian != .Little) return error.SkipZigTest; // Byte aligned packed struct field pointers have not been implemented yet
438
439 const S2 = packed struct {
440 base: u8,
441 p0: packed struct {
442 a: u4,
443 b: u4,
444 c: u8,
445 },
446 bit: u1,
447 p1: packed struct {
448 a: u7,
449 b: u8,
450 },
451
452 var s: @This() = .{ .base = 1, .p0 = .{ .a = 2, .b = 3, .c = 4 }, .bit = 0, .p1 = .{ .a = 5, .b = 6 } };
453 };
454
455 const ptr_base = &S2.s.base;
456 const ptr_p0_a = &S2.s.p0.a;
457 const ptr_p0_b = &S2.s.p0.b;
458 const ptr_p0_c = &S2.s.p0.c;
459 const ptr_p1_a = &S2.s.p1.a;
460 const ptr_p1_b = &S2.s.p1.b;
461 try expectEqual(@as(u8, 1), ptr_base.*);
462 try expectEqual(@as(u4, 2), ptr_p0_a.*);
463 try expectEqual(@as(u4, 3), ptr_p0_b.*);
464 try expectEqual(@as(u8, 4), ptr_p0_c.*);
465 try expectEqual(@as(u7, 5), ptr_p1_a.*);
466 try expectEqual(@as(u8, 6), ptr_p1_b.*);
467}
468
357469test "load pointer from packed struct" {
358470 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
359471 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
......@@ -380,6 +492,7 @@ test "@intFromPtr on a packed struct field" {
380492 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
381493 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
382494 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
495 if (native_endian != .Little) return error.SkipZigTest;
383496
384497 const S = struct {
385498 const P = packed struct {
......@@ -387,6 +500,7 @@ test "@intFromPtr on a packed struct field" {
387500 y: u8,
388501 z: u32,
389502 };
503
390504 var p0: P = P{
391505 .x = 1,
392506 .y = 2,
......@@ -396,6 +510,140 @@ test "@intFromPtr on a packed struct field" {
396510 try expect(@intFromPtr(&S.p0.z) - @intFromPtr(&S.p0.x) == 2);
397511}
398512
513test "@intFromPtr on a packed struct field unaligned and nested" {
514 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
515 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
516 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
517 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
518 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
519 if (native_endian != .Little) return error.SkipZigTest; // Byte aligned packed struct field pointers have not been implemented yet
520
521 const S1 = packed struct {
522 a: u4,
523 b: u4,
524 c: u8,
525 };
526 const S2 = packed struct {
527 base: u8,
528 p0: S1,
529 bit0: u1,
530 p1: packed struct {
531 a: u8,
532 },
533 p2: packed struct {
534 a: u7,
535 b: u8,
536 },
537 p3: S1,
538
539 var s: @This() = .{
540 .base = 1,
541 .p0 = .{ .a = 2, .b = 3, .c = 4 },
542 .bit0 = 0,
543 .p1 = .{ .a = 5 },
544 .p2 = .{ .a = 6, .b = 7 },
545 .p3 = .{ .a = 8, .b = 9, .c = 10 },
546 };
547 };
548
549 switch (comptime @alignOf(S2)) {
550 4 => {
551 comptime assert(@TypeOf(&S2.s.base) == *align(4) u8);
552 comptime assert(@TypeOf(&S2.s.p0.a) == *align(1:0:2) u4);
553 comptime assert(@TypeOf(&S2.s.p0.b) == *align(1:4:2) u4);
554 comptime assert(@TypeOf(&S2.s.p0.c) == *u8);
555 comptime assert(@TypeOf(&S2.s.bit0) == *align(4:24:8) u1);
556 comptime assert(@TypeOf(&S2.s.p1.a) == *align(4:25:8) u8);
557 comptime assert(@TypeOf(&S2.s.p2.a) == *align(4:33:8) u7);
558 comptime assert(@TypeOf(&S2.s.p2.b) == *u8);
559 comptime assert(@TypeOf(&S2.s.p3.a) == *align(2:0:2) u4);
560 comptime assert(@TypeOf(&S2.s.p3.b) == *align(2:4:2) u4);
561 comptime assert(@TypeOf(&S2.s.p3.c) == *u8);
562 },
563 8 => {
564 comptime assert(@TypeOf(&S2.s.base) == *align(8) u8);
565 comptime assert(@TypeOf(&S2.s.p0.a) == *align(1:0:2) u4);
566 comptime assert(@TypeOf(&S2.s.p0.b) == *align(1:4:2) u4);
567 comptime assert(@TypeOf(&S2.s.p0.c) == *u8);
568 comptime assert(@TypeOf(&S2.s.bit0) == *align(8:24:8) u1);
569 comptime assert(@TypeOf(&S2.s.p1.a) == *align(8:25:8) u8);
570 comptime assert(@TypeOf(&S2.s.p2.a) == *align(8:33:8) u7);
571 comptime assert(@TypeOf(&S2.s.p2.b) == *u8);
572 comptime assert(@TypeOf(&S2.s.p3.a) == *align(2:0:2) u4);
573 comptime assert(@TypeOf(&S2.s.p3.b) == *align(2:4:2) u4);
574 comptime assert(@TypeOf(&S2.s.p3.c) == *u8);
575 },
576 else => {},
577 }
578 try expect(@intFromPtr(&S2.s.base) - @intFromPtr(&S2.s) == 0);
579 try expect(@intFromPtr(&S2.s.p0.a) - @intFromPtr(&S2.s) == 1);
580 try expect(@intFromPtr(&S2.s.p0.b) - @intFromPtr(&S2.s) == 1);
581 try expect(@intFromPtr(&S2.s.p0.c) - @intFromPtr(&S2.s) == 2);
582 try expect(@intFromPtr(&S2.s.bit0) - @intFromPtr(&S2.s) == 0);
583 try expect(@intFromPtr(&S2.s.p1.a) - @intFromPtr(&S2.s) == 0);
584 try expect(@intFromPtr(&S2.s.p2.a) - @intFromPtr(&S2.s) == 0);
585 try expect(@intFromPtr(&S2.s.p2.b) - @intFromPtr(&S2.s) == 5);
586 try expect(@intFromPtr(&S2.s.p3.a) - @intFromPtr(&S2.s) == 6);
587 try expect(@intFromPtr(&S2.s.p3.b) - @intFromPtr(&S2.s) == 6);
588 try expect(@intFromPtr(&S2.s.p3.c) - @intFromPtr(&S2.s) == 7);
589
590 const S3 = packed struct {
591 pad: u8,
592 v: u2,
593 s: packed struct {
594 v: u3,
595 s: packed struct {
596 v: u2,
597 s: packed struct {
598 bit0: u1,
599 byte: u8,
600 bit1: u1,
601 },
602 },
603 },
604 var v0: @This() = .{ .pad = 0, .v = 1, .s = .{ .v = 2, .s = .{ .v = 3, .s = .{ .bit0 = 0, .byte = 4, .bit1 = 1 } } } };
605 };
606
607 comptime assert(@TypeOf(&S3.v0.v) == *align(4:8:4) u2);
608 comptime assert(@TypeOf(&S3.v0.s.v) == *align(4:10:4) u3);
609 comptime assert(@TypeOf(&S3.v0.s.s.v) == *align(4:13:4) u2);
610 comptime assert(@TypeOf(&S3.v0.s.s.s.bit0) == *align(4:15:4) u1);
611 comptime assert(@TypeOf(&S3.v0.s.s.s.byte) == *align(2) u8);
612 comptime assert(@TypeOf(&S3.v0.s.s.s.bit1) == *align(4:24:4) u1);
613 try expect(@intFromPtr(&S3.v0.v) - @intFromPtr(&S3.v0) == 0);
614 try expect(@intFromPtr(&S3.v0.s) - @intFromPtr(&S3.v0) == 0);
615 try expect(@intFromPtr(&S3.v0.s.v) - @intFromPtr(&S3.v0) == 0);
616 try expect(@intFromPtr(&S3.v0.s.s) - @intFromPtr(&S3.v0) == 0);
617 try expect(@intFromPtr(&S3.v0.s.s.v) - @intFromPtr(&S3.v0) == 0);
618 try expect(@intFromPtr(&S3.v0.s.s.s) - @intFromPtr(&S3.v0) == 0);
619 try expect(@intFromPtr(&S3.v0.s.s.s.bit0) - @intFromPtr(&S3.v0) == 0);
620 try expect(@intFromPtr(&S3.v0.s.s.s.byte) - @intFromPtr(&S3.v0) == 2);
621 try expect(@intFromPtr(&S3.v0.s.s.s.bit1) - @intFromPtr(&S3.v0) == 0);
622}
623
624test "packed struct fields modification" {
625 // Originally reported at https://github.com/ziglang/zig/issues/16615
626
627 const Small = packed struct {
628 val: u8 = 0,
629 lo: u4 = 0,
630 hi: u4 = 0,
631
632 var p: @This() = undefined;
633 };
634 Small.p = .{
635 .val = 0x12,
636 .lo = 3,
637 .hi = 4,
638 };
639 try expect(@as(u16, @bitCast(Small.p)) == 0x4312);
640
641 Small.p.val -= Small.p.lo;
642 Small.p.val += Small.p.hi;
643 Small.p.hi -= Small.p.lo;
644 try expect(@as(u16, @bitCast(Small.p)) == 0x1313);
645}
646
399647test "optional pointer in packed struct" {
400648 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
401649 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
......@@ -410,7 +658,7 @@ test "optional pointer in packed struct" {
410658
411659test "nested packed struct field access test" {
412660 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
413 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
661 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO packed structs larger than 64 bits
414662 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
415663 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
416664 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
......@@ -562,7 +810,7 @@ test "nested packed struct at non-zero offset 2" {
562810 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
563811 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
564812 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
565 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
813 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO packed structs larger than 64 bits
566814 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
567815
568816 const S = struct {
......@@ -700,7 +948,6 @@ test "packed struct initialized in bitcast" {
700948test "pointer to container level packed struct field" {
701949 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
702950 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
703 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
704951 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
705952 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
706953
......@@ -727,7 +974,6 @@ test "store undefined to packed result location" {
727974 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
728975 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
729976 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
730 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
731977
732978 var x: u4 = 0;
733979 var s = packed struct { x: u4, y: u4 }{ .x = x, .y = if (x > 0) x else undefined };
......@@ -743,3 +989,51 @@ test "bitcast back and forth" {
743989 try expect(s.one == s2.one);
744990 try expect(s.two == s2.two);
745991}
992
993test "field access of packed struct smaller than its abi size inside struct initialized with rls" {
994 // Originally reported at https://github.com/ziglang/zig/issues/14200
995 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .arm) return error.SkipZigTest;
996 const S = struct {
997 ps: packed struct { x: i2, y: i2 },
998
999 fn init(cond: bool) @This() {
1000 return .{ .ps = .{ .x = 0, .y = if (cond) 1 else 0 } };
1001 }
1002 };
1003
1004 var s = S.init(true);
1005 // note: this bug is triggered by the == operator, expectEqual will hide it
1006 try expect(@as(i2, 0) == s.ps.x);
1007 try expect(@as(i2, 1) == s.ps.y);
1008}
1009
1010test "modify nested packed struct aligned field" {
1011 // Originally reported at https://github.com/ziglang/zig/issues/14632
1012 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
1013 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
1014 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
1015 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1016 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
1017
1018 const Options = packed struct {
1019 foo: bool = false,
1020 bar: bool = false,
1021 pretty_print: packed struct {
1022 enabled: bool = false,
1023 num_spaces: u4 = 4,
1024 space_char: enum { space, tab } = .space,
1025 indent: u8 = 0,
1026 } = .{},
1027 baz: bool = false,
1028 };
1029
1030 var opts = Options{};
1031 opts.pretty_print.indent += 1;
1032 try std.testing.expectEqual(@as(u17, 0b00000000100100000), @bitCast(opts));
1033 try std.testing.expect(!opts.foo);
1034 try std.testing.expect(!opts.bar);
1035 try std.testing.expect(!opts.pretty_print.enabled);
1036 try std.testing.expectEqual(@as(u4, 4), opts.pretty_print.num_spaces);
1037 try std.testing.expectEqual(@as(u8, 1), opts.pretty_print.indent);
1038 try std.testing.expect(!opts.baz);
1039}
test/behavior/packed-union.zig+47
......@@ -38,3 +38,50 @@ test "flags in packed union" {
3838 try expectEqual(true, test_bits.enable_1);
3939 try expectEqual(false, test_bits.other_flags.flags.enable_1);
4040}
41
42test "flags in packed union at offset" {
43 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
44 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
45 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
46 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
47
48 const FlagBits = packed union {
49 base_flags: packed union {
50 flags: packed struct(u4) {
51 enable_1: bool = true,
52 enable_2: bool = false,
53 enable_3: bool = false,
54 enable_4: bool = false,
55 },
56 bits: u4,
57 },
58 adv_flags: packed struct(u12) {
59 pad: u8 = 0,
60 adv: packed union {
61 flags: packed struct(u4) {
62 enable_1: bool = true,
63 enable_2: bool = false,
64 enable_3: bool = false,
65 enable_4: bool = false,
66 },
67 bits: u4,
68 },
69 },
70 };
71 var test_bits: FlagBits = .{ .adv_flags = .{ .adv = .{ .flags = .{} } } };
72
73 try expectEqual(@as(u8, 0), test_bits.adv_flags.pad);
74 try expectEqual(true, test_bits.adv_flags.adv.flags.enable_1);
75 try expectEqual(false, test_bits.adv_flags.adv.flags.enable_2);
76
77 test_bits.adv_flags.adv.flags.enable_1 = false;
78 test_bits.adv_flags.adv.flags.enable_2 = true;
79 try expectEqual(@as(u8, 0), test_bits.adv_flags.pad);
80 try expectEqual(false, test_bits.adv_flags.adv.flags.enable_1);
81 try expectEqual(true, test_bits.adv_flags.adv.flags.enable_2);
82
83 test_bits.adv_flags.adv.bits = 12;
84 try expectEqual(@as(u8, 0), test_bits.adv_flags.pad);
85 try expectEqual(false, test_bits.adv_flags.adv.flags.enable_1);
86 try expectEqual(false, test_bits.adv_flags.adv.flags.enable_2);
87}