authorgravatar for xavierb@gmail.comXavier Bouchoux <xavierb@gmail.com> 2023-07-29 20:08:08+02:00
committergravatar for xavierb@gmail.comXavier Bouchoux <xavierb@gmail.com> 2023-10-03 06:39:20+02:00
log62d178e91af57c19d0ac000fe6930039a23e53a3
treef849674dd8aa13839319e05585b8513a0192b7ba
parent412d863ba5801c1376af7ab8f04a71b839a820a6

codegen: fix field offsets in packed structs

* add nested packed struct/union behavior tests * use ptr_info.packed_offset rather than trying to duplicate the logic from Sema.structFieldPtrByIndex() * use the container_ptr_info.packed_offset to account for non-aligned nested structs. * dedup type.packedStructFieldBitOffset() and module.structPackedFieldBitOffset()

7 files changed, 362 insertions(+), 65 deletions(-)

src/arch/wasm/CodeGen.zig+11-4
...@@ -3090,12 +3090,19 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue...@@ -3090,12 +3090,19 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue
3090 return func.lowerParentPtr(elem.base.toValue(), @as(u32, @intCast(elem_offset + offset)));3090 return func.lowerParentPtr(elem.base.toValue(), @as(u32, @intCast(elem_offset + offset)));
3091 },3091 },
3092 .field => |field| {3092 .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
3095 const field_offset = switch (parent_ty.zigTypeTag(mod)) {3097 const field_offset = switch (parent_ty.zigTypeTag(mod)) {
3096 .Struct => switch (parent_ty.containerLayout(mod)) {3098 .Struct => blk: {
3097 .Packed => parent_ty.packedStructFieldByteOffset(@as(usize, @intCast(field.index)), mod),3099 if (mod.typeToPackedStruct(parent_ty)) |struct_type| {
3098 else => parent_ty.structFieldOffset(@as(usize, @intCast(field.index)), mod),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);
3099 },3106 },
3100 .Union => switch (parent_ty.containerLayout(mod)) {3107 .Union => switch (parent_ty.containerLayout(mod)) {
3101 .Packed => 0,3108 .Packed => 0,
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...@@ -5774,14 +5774,15 @@ fn fieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, index: u32
5774 const ptr_container_ty_info = ptr_container_ty.ptrInfo(mod);5774 const ptr_container_ty_info = ptr_container_ty.ptrInfo(mod);
5775 const container_ty = ptr_container_ty.childType(mod);5775 const container_ty = ptr_container_ty.childType(mod);
57765776
5777 const field_offset: i32 = @intCast(switch (container_ty.containerLayout(mod)) {5777 const field_offset: i32 = blk: {
5778 .Auto, .Extern => container_ty.structFieldOffset(index, mod),5778 if (mod.typeToPackedStruct(container_ty)) |struct_type| {
5779 .Packed => if (container_ty.zigTypeTag(mod) == .Struct and5779 break :blk if (ptr_field_ty.ptrInfo(mod).packed_offset.host_size == 0)
5780 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 container_ty.packedStructFieldByteOffset(index, mod) + @divExact(ptr_container_ty_info.packed_offset.bit_offset, 8)5781 else
5782 else5782 0;
5783 0,5783 }
5784 });5784 break :blk @intCast(container_ty.structFieldOffset(index, mod));
5785 };
57855786
5786 const src_mcv = try self.resolveInst(operand);5787 const src_mcv = try self.resolveInst(operand);
5787 const dst_mcv = if (switch (src_mcv) {5788 const dst_mcv = if (switch (src_mcv) {
src/codegen/c.zig+12-8
...@@ -5269,22 +5269,26 @@ fn fieldLocation(...@@ -5269,22 +5269,26 @@ fn fieldLocation(
5269 const ip = &mod.intern_pool;5269 const ip = &mod.intern_pool;
5270 const container_ty = container_ptr_ty.childType(mod);5270 const container_ty = container_ptr_ty.childType(mod);
5271 return switch (container_ty.zigTypeTag(mod)) {5271 return switch (container_ty.zigTypeTag(mod)) {
5272 .Struct => switch (container_ty.containerLayout(mod)) {5272 .Struct => blk: {
5273 .Auto, .Extern => for (field_index..container_ty.structFieldCount(mod)) |next_field_index_usize| {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| {
5274 const next_field_index: u32 = @intCast(next_field_index_usize);5281 const next_field_index: u32 = @intCast(next_field_index_usize);
5275 if (container_ty.structFieldIsComptime(next_field_index, mod)) continue;5282 if (container_ty.structFieldIsComptime(next_field_index, mod)) continue;
5276 const field_ty = container_ty.structFieldType(next_field_index, mod);5283 const field_ty = container_ty.structFieldType(next_field_index, mod);
5277 if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;5284 if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
52785285
5279 break .{ .field = if (container_ty.isSimpleTuple(mod))5286 break :blk .{ .field = if (container_ty.isSimpleTuple(mod))
5280 .{ .field = next_field_index }5287 .{ .field = next_field_index }
5281 else5288 else
5282 .{ .identifier = ip.stringToSlice(container_ty.legacyStructFieldName(next_field_index, mod)) } };5289 .{ .identifier = ip.stringToSlice(container_ty.legacyStructFieldName(next_field_index, mod)) } };
5283 } else if (container_ty.hasRuntimeBitsIgnoreComptime(mod)) .end else .begin,5290 }
5284 .Packed => if (field_ptr_ty.ptrInfo(mod).packed_offset.host_size == 0)5291 break :blk if (container_ty.hasRuntimeBitsIgnoreComptime(mod)) .end else .begin;
5285 .{ .byte_offset = container_ty.packedStructFieldByteOffset(field_index, mod) + @divExact(container_ptr_ty.ptrInfo(mod).packed_offset.bit_offset, 8) }
5286 else
5287 .begin,
5288 },5292 },
5289 .Union => {5293 .Union => {
5290 const union_obj = mod.typeToUnion(container_ty).?;5294 const union_obj = mod.typeToUnion(container_ty).?;
src/codegen/llvm.zig+17-25
...@@ -4,7 +4,6 @@ const assert = std.debug.assert;...@@ -4,7 +4,6 @@ const assert = std.debug.assert;
4const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
5const log = std.log.scoped(.codegen);5const log = std.log.scoped(.codegen);
6const math = std.math;6const math = std.math;
7const native_endian = builtin.cpu.arch.endian();
8const DW = std.dwarf;7const DW = std.dwarf;
98
10const Builder = @import("llvm/Builder.zig");9const Builder = @import("llvm/Builder.zig");
...@@ -3770,7 +3769,7 @@ pub const Object = struct {...@@ -3770,7 +3769,7 @@ pub const Object = struct {
3770 .opt_payload,3769 .opt_payload,
3771 .elem,3770 .elem,
3772 .field,3771 .field,
3773 => try o.lowerParentPtr(val, ty.ptrInfo(mod).packed_offset.bit_offset % 8 == 0),3772 => try o.lowerParentPtr(val),
3774 .comptime_field => unreachable,3773 .comptime_field => unreachable,
3775 };3774 };
3776 switch (ptr.len) {3775 switch (ptr.len) {
...@@ -4230,15 +4229,16 @@ pub const Object = struct {...@@ -4230,15 +4229,16 @@ pub const Object = struct {
4230 return o.lowerDeclRefValue(ptr_ty, decl_index);4229 return o.lowerDeclRefValue(ptr_ty, decl_index);
4231 }4230 }
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 {
4234 const mod = o.module;4233 const mod = o.module;
4235 const ip = &mod.intern_pool;4234 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) {
4237 .decl => |decl| o.lowerParentPtrDecl(decl),4237 .decl => |decl| o.lowerParentPtrDecl(decl),
4238 .mut_decl => |mut_decl| o.lowerParentPtrDecl(mut_decl.decl),4238 .mut_decl => |mut_decl| o.lowerParentPtrDecl(mut_decl.decl),
4239 .int => |int| try o.lowerIntAsPtr(int),4239 .int => |int| try o.lowerIntAsPtr(int),
4240 .eu_payload => |eu_ptr| {4240 .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
4243 const eu_ty = ip.typeOf(eu_ptr).toType().childType(mod);4243 const eu_ty = ip.typeOf(eu_ptr).toType().childType(mod);
4244 const payload_ty = eu_ty.errorUnionPayload(mod);4244 const payload_ty = eu_ty.errorUnionPayload(mod);
...@@ -4256,7 +4256,7 @@ pub const Object = struct {...@@ -4256,7 +4256,7 @@ pub const Object = struct {
4256 });4256 });
4257 },4257 },
4258 .opt_payload => |opt_ptr| {4258 .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
4261 const opt_ty = ip.typeOf(opt_ptr).toType().childType(mod);4261 const opt_ty = ip.typeOf(opt_ptr).toType().childType(mod);
4262 const payload_ty = opt_ty.optionalChild(mod);4262 const payload_ty = opt_ty.optionalChild(mod);
...@@ -4274,7 +4274,7 @@ pub const Object = struct {...@@ -4274,7 +4274,7 @@ pub const Object = struct {
4274 },4274 },
4275 .comptime_field => unreachable,4275 .comptime_field => unreachable,
4276 .elem => |elem_ptr| {4276 .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());
4278 const elem_ty = ip.typeOf(elem_ptr.base).toType().elemType2(mod);4278 const elem_ty = ip.typeOf(elem_ptr.base).toType().elemType2(mod);
42794279
4280 return o.builder.gepConst(.inbounds, try o.lowerType(elem_ty), parent_ptr, null, &.{4280 return o.builder.gepConst(.inbounds, try o.lowerType(elem_ty), parent_ptr, null, &.{
...@@ -4282,9 +4282,9 @@ pub const Object = struct {...@@ -4282,9 +4282,9 @@ pub const Object = struct {
4282 });4282 });
4283 },4283 },
4284 .field => |field_ptr| {4284 .field => |field_ptr| {
4285 const parent_ptr = try o.lowerParentPtr(field_ptr.base.toValue(), byte_aligned);4285 const parent_ptr = try o.lowerParentPtr(field_ptr.base.toValue());
4286 const parent_ty = ip.typeOf(field_ptr.base).toType().childType(mod);4286 const parent_ptr_ty = ip.typeOf(field_ptr.base).toType();
42874287 const parent_ty = parent_ptr_ty.childType(mod);
4288 const field_index: u32 = @intCast(field_ptr.index);4288 const field_index: u32 = @intCast(field_ptr.index);
4289 switch (parent_ty.zigTypeTag(mod)) {4289 switch (parent_ty.zigTypeTag(mod)) {
4290 .Union => {4290 .Union => {
...@@ -4309,22 +4309,14 @@ pub const Object = struct {...@@ -4309,22 +4309,14 @@ pub const Object = struct {
4309 },4309 },
4310 .Struct => {4310 .Struct => {
4311 if (mod.typeToPackedStruct(parent_ty)) |struct_type| {4311 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;
4313 const llvm_usize = try o.lowerType(Type.usize);4317 const llvm_usize = try o.lowerType(Type.usize);
4314 const base_addr =4318 const base_addr = try o.builder.castConst(.ptrtoint, parent_ptr, llvm_usize);
4315 try o.builder.castConst(.ptrtoint, parent_ptr, llvm_usize);4319 const byte_offset = try o.builder.intConst(llvm_usize, @divExact(bit_offset, 8));
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);
4328 const field_addr = try o.builder.binConst(.add, base_addr, byte_offset);4320 const field_addr = try o.builder.binConst(.add, base_addr, byte_offset);
4329 return o.builder.castConst(.inttoptr, field_addr, .ptr);4321 return o.builder.castConst(.inttoptr, field_addr, .ptr);
4330 }4322 }
src/type.zig+2-16
...@@ -3028,24 +3028,10 @@ pub const Type = struct {...@@ -3028,24 +3028,10 @@ pub const Type = struct {
3028 };3028 };
3029 }3029 }
30303030
3031 pub fn packedStructFieldBitOffset(ty: Type, field_index: usize, mod: *Module) u32 {3031 pub fn packedStructFieldByteOffset(ty: Type, field_index: u32, mod: *Module) u32 {
3032 const ip = &mod.intern_pool;3032 const ip = &mod.intern_pool;
3033 const struct_type = ip.indexToKey(ty.toIntern()).struct_type;3033 const struct_type = ip.indexToKey(ty.toIntern()).struct_type;
3034 assert(struct_type.layout == .Packed);3034 return @divExact(mod.structPackedFieldBitOffset(struct_type, field_index), 8);
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 }3035 }
30503036
3051 pub const FieldOffset = struct {3037 pub const FieldOffset = struct {
test/behavior/packed-struct.zig+264-4
...@@ -253,6 +253,79 @@ test "regular in irregular packed struct" {...@@ -253,6 +253,79 @@ test "regular in irregular packed struct" {
253 try expectEqual(@as(u8, 42), foo.bar.b);253 try expectEqual(@as(u8, 42), foo.bar.b);
254}254}
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
256test "byte-aligned field pointer offsets" {329test "byte-aligned field pointer offsets" {
257 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;330 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
258 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;331 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
...@@ -354,6 +427,45 @@ test "byte-aligned field pointer offsets" {...@@ -354,6 +427,45 @@ test "byte-aligned field pointer offsets" {
354 try comptime S.doTheTest();427 try comptime S.doTheTest();
355}428}
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
357test "load pointer from packed struct" {469test "load pointer from packed struct" {
358 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;470 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
359 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;471 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
...@@ -380,6 +492,7 @@ test "@intFromPtr on a packed struct field" {...@@ -380,6 +492,7 @@ test "@intFromPtr on a packed struct field" {
380 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;492 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
381 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO493 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
382 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;494 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
495 if (native_endian != .Little) return error.SkipZigTest;
383496
384 const S = struct {497 const S = struct {
385 const P = packed struct {498 const P = packed struct {
...@@ -387,6 +500,7 @@ test "@intFromPtr on a packed struct field" {...@@ -387,6 +500,7 @@ test "@intFromPtr on a packed struct field" {
387 y: u8,500 y: u8,
388 z: u32,501 z: u32,
389 };502 };
503
390 var p0: P = P{504 var p0: P = P{
391 .x = 1,505 .x = 1,
392 .y = 2,506 .y = 2,
...@@ -396,6 +510,138 @@ test "@intFromPtr on a packed struct field" {...@@ -396,6 +510,138 @@ test "@intFromPtr on a packed struct field" {
396 try expect(@intFromPtr(&S.p0.z) - @intFromPtr(&S.p0.x) == 2);510 try expect(@intFromPtr(&S.p0.z) - @intFromPtr(&S.p0.x) == 2);
397}511}
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 const Small = packed struct {
626 val: u8 = 0,
627 lo: u4 = 0,
628 hi: u4 = 0,
629
630 var p: @This() = undefined;
631 };
632 Small.p = .{
633 .val = 0x12,
634 .lo = 3,
635 .hi = 4,
636 };
637 try expect(@as(u16, @bitCast(Small.p)) == 0x4312);
638
639 Small.p.val -= Small.p.lo;
640 Small.p.val += Small.p.hi;
641 Small.p.hi -= Small.p.lo;
642 try expect(@as(u16, @bitCast(Small.p)) == 0x1313);
643}
644
399test "optional pointer in packed struct" {645test "optional pointer in packed struct" {
400 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;646 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
401 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;647 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
...@@ -410,7 +656,7 @@ test "optional pointer in packed struct" {...@@ -410,7 +656,7 @@ test "optional pointer in packed struct" {
410656
411test "nested packed struct field access test" {657test "nested packed struct field access test" {
412 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO658 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
413 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO659 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO packed structs larger than 64 bits
414 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO660 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
415 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO661 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
416 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO662 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
...@@ -562,7 +808,7 @@ test "nested packed struct at non-zero offset 2" {...@@ -562,7 +808,7 @@ test "nested packed struct at non-zero offset 2" {
562 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;808 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
563 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO809 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
564 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;810 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
565 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO811 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO packed structs larger than 64 bits
566 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;812 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
567813
568 const S = struct {814 const S = struct {
...@@ -700,7 +946,6 @@ test "packed struct initialized in bitcast" {...@@ -700,7 +946,6 @@ test "packed struct initialized in bitcast" {
700test "pointer to container level packed struct field" {946test "pointer to container level packed struct field" {
701 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;947 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
702 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;948 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
703 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
704 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO949 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
705 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;950 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
706951
...@@ -727,7 +972,6 @@ test "store undefined to packed result location" {...@@ -727,7 +972,6 @@ test "store undefined to packed result location" {
727 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;972 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
728 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;973 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
729 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;974 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
730 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
731975
732 var x: u4 = 0;976 var x: u4 = 0;
733 var s = packed struct { x: u4, y: u4 }{ .x = x, .y = if (x > 0) x else undefined };977 var s = packed struct { x: u4, y: u4 }{ .x = x, .y = if (x > 0) x else undefined };
...@@ -743,3 +987,19 @@ test "bitcast back and forth" {...@@ -743,3 +987,19 @@ test "bitcast back and forth" {
743 try expect(s.one == s2.one);987 try expect(s.one == s2.one);
744 try expect(s.two == s2.two);988 try expect(s.two == s2.two);
745}989}
990
991test "field access of packed struct smaller than its abi size inside struct initialized with rls" {
992 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .arm) return error.SkipZigTest;
993 const S = struct {
994 ps: packed struct { x: i2, y: i2 },
995
996 fn init(cond: bool) @This() {
997 return .{ .ps = .{ .x = 0, .y = if (cond) 1 else 0 } };
998 }
999 };
1000
1001 var s = S.init(true);
1002 // note: this bug is triggered by the == operator, expectEqual will hide it
1003 try expect(@as(i2, 0) == s.ps.x);
1004 try expect(@as(i2, 1) == s.ps.y);
1005}
test/behavior/packed-union.zig+47
...@@ -38,3 +38,50 @@ test "flags in packed union" {...@@ -38,3 +38,50 @@ test "flags in packed union" {
38 try expectEqual(true, test_bits.enable_1);38 try expectEqual(true, test_bits.enable_1);
39 try expectEqual(false, test_bits.other_flags.flags.enable_1);39 try expectEqual(false, test_bits.other_flags.flags.enable_1);
40}40}
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}