Commit 8c367ef99a

Xavier Bouchoux <xavierb@gmail.com>
2023-07-29 11:50:25
codegen: fix access to byte-aligned nested packed struct elems
When acessing a packed struct member via a byte aligned ptr (from the optimisation in Sema.structFieldPtrByIndex()) the codegen must apply the parent ptr packed_offset in addition to the field offset itself. resolves https://github.com/ziglang/zig/issues/16609
1 parent 46abf20
Changed files (5)
src
arch
codegen
test
src/arch/wasm/CodeGen.zig
@@ -3675,8 +3675,9 @@ fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     const extra = func.air.extraData(Air.StructField, ty_pl.payload);
 
     const struct_ptr = try func.resolveInst(extra.data.struct_operand);
-    const struct_ty = func.typeOf(extra.data.struct_operand).childType(mod);
-    const result = try func.structFieldPtr(inst, extra.data.struct_operand, struct_ptr, struct_ty, extra.data.field_index);
+    const struct_ptr_ty = func.typeOf(extra.data.struct_operand);
+    const struct_ty = struct_ptr_ty.childType(mod);
+    const result = try func.structFieldPtr(inst, extra.data.struct_operand, struct_ptr, struct_ptr_ty, struct_ty, extra.data.field_index);
     func.finishAir(inst, result, &.{extra.data.struct_operand});
 }
 
@@ -3684,9 +3685,10 @@ fn airStructFieldPtrIndex(func: *CodeGen, inst: Air.Inst.Index, index: u32) Inne
     const mod = func.bin_file.base.options.module.?;
     const ty_op = func.air.instructions.items(.data)[inst].ty_op;
     const struct_ptr = try func.resolveInst(ty_op.operand);
-    const struct_ty = func.typeOf(ty_op.operand).childType(mod);
+    const struct_ptr_ty = func.typeOf(ty_op.operand);
+    const struct_ty = struct_ptr_ty.childType(mod);
 
-    const result = try func.structFieldPtr(inst, ty_op.operand, struct_ptr, struct_ty, index);
+    const result = try func.structFieldPtr(inst, ty_op.operand, struct_ptr, struct_ptr_ty, struct_ty, index);
     func.finishAir(inst, result, &.{ty_op.operand});
 }
 
@@ -3695,18 +3697,21 @@ fn structFieldPtr(
     inst: Air.Inst.Index,
     ref: Air.Inst.Ref,
     struct_ptr: WValue,
+    struct_ptr_ty: Type,
     struct_ty: Type,
     index: u32,
 ) InnerError!WValue {
     const mod = func.bin_file.base.options.module.?;
     const result_ty = func.typeOfIndex(inst);
+    const struct_ptr_ty_info = struct_ptr_ty.ptrInfo(mod);
+
     const offset = switch (struct_ty.containerLayout(mod)) {
         .Packed => switch (struct_ty.zigTypeTag(mod)) {
             .Struct => offset: {
                 if (result_ty.ptrInfo(mod).packed_offset.host_size != 0) {
                     break :offset @as(u32, 0);
                 }
-                break :offset struct_ty.packedStructFieldByteOffset(index, mod);
+                break :offset struct_ty.packedStructFieldByteOffset(index, mod) + @divExact(struct_ptr_ty_info.packed_offset.bit_offset, 8);
             },
             .Union => 0,
             else => unreachable,
src/arch/x86_64/CodeGen.zig
@@ -5556,12 +5556,14 @@ fn fieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, index: u32
     const mod = self.bin_file.options.module.?;
     const ptr_field_ty = self.typeOfIndex(inst);
     const ptr_container_ty = self.typeOf(operand);
+    const ptr_container_ty_info = ptr_container_ty.ptrInfo(mod);
     const container_ty = ptr_container_ty.childType(mod);
+
     const field_offset: i32 = @intCast(switch (container_ty.containerLayout(mod)) {
         .Auto, .Extern => container_ty.structFieldOffset(index, mod),
         .Packed => if (container_ty.zigTypeTag(mod) == .Struct and
             ptr_field_ty.ptrInfo(mod).packed_offset.host_size == 0)
-            container_ty.packedStructFieldByteOffset(index, mod)
+            container_ty.packedStructFieldByteOffset(index, mod) + @divExact(ptr_container_ty_info.packed_offset.bit_offset, 8)
         else
             0,
     });
src/codegen/c.zig
@@ -661,7 +661,7 @@ pub const DeclGen = struct {
                     try dg.renderCType(writer, ptr_cty);
                     try writer.writeByte(')');
                 }
-                switch (fieldLocation(base_ty, ptr_ty, @as(u32, @intCast(field.index)), mod)) {
+                switch (fieldLocation(ptr_base_ty, ptr_ty, @as(u32, @intCast(field.index)), mod)) {
                     .begin => try dg.renderParentPtr(writer, field.base, location),
                     .field => |name| {
                         try writer.writeAll("&(");
@@ -5187,7 +5187,7 @@ fn airOptionalPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
 }
 
 fn fieldLocation(
-    container_ty: Type,
+    container_ptr_ty: Type,
     field_ptr_ty: Type,
     field_index: u32,
     mod: *Module,
@@ -5198,6 +5198,7 @@ fn fieldLocation(
     end: void,
 } {
     const ip = &mod.intern_pool;
+    const container_ty = container_ptr_ty.childType(mod);
     return switch (container_ty.zigTypeTag(mod)) {
         .Struct => switch (container_ty.containerLayout(mod)) {
             .Auto, .Extern => for (field_index..container_ty.structFieldCount(mod)) |next_field_index| {
@@ -5211,7 +5212,7 @@ fn fieldLocation(
                     .{ .identifier = ip.stringToSlice(container_ty.structFieldName(next_field_index, mod)) } };
             } else if (container_ty.hasRuntimeBitsIgnoreComptime(mod)) .end else .begin,
             .Packed => if (field_ptr_ty.ptrInfo(mod).packed_offset.host_size == 0)
-                .{ .byte_offset = container_ty.packedStructFieldByteOffset(field_index, mod) }
+                .{ .byte_offset = container_ty.packedStructFieldByteOffset(field_index, mod) + @divExact(container_ptr_ty.ptrInfo(mod).packed_offset.bit_offset, 8) }
             else
                 .begin,
         },
@@ -5282,7 +5283,7 @@ fn airFieldParentPtr(f: *Function, inst: Air.Inst.Index) !CValue {
     try f.renderType(writer, container_ptr_ty);
     try writer.writeByte(')');
 
-    switch (fieldLocation(container_ty, field_ptr_ty, extra.field_index, mod)) {
+    switch (fieldLocation(container_ptr_ty, field_ptr_ty, extra.field_index, mod)) {
         .begin => try f.writeCValue(writer, field_ptr_val, .Initializer),
         .field => |field| {
             const u8_ptr_ty = try mod.adjustPtrTypeChild(field_ptr_ty, Type.u8);
@@ -5339,7 +5340,7 @@ fn fieldPtr(
     try f.renderType(writer, field_ptr_ty);
     try writer.writeByte(')');
 
-    switch (fieldLocation(container_ty, field_ptr_ty, field_index, mod)) {
+    switch (fieldLocation(container_ptr_ty, field_ptr_ty, field_index, mod)) {
         .begin => try f.writeCValue(writer, container_ptr_val, .Initializer),
         .field => |field| {
             try writer.writeByte('&');
src/codegen/llvm.zig
@@ -10611,6 +10611,7 @@ pub const FuncGen = struct {
                 .Packed => {
                     const result_ty = self.typeOfIndex(inst);
                     const result_ty_info = result_ty.ptrInfo(mod);
+                    const struct_ptr_ty_info = struct_ptr_ty.ptrInfo(mod);
 
                     if (result_ty_info.packed_offset.host_size != 0) {
                         // From LLVM's perspective, a pointer to a packed struct and a pointer
@@ -10622,7 +10623,7 @@ pub const FuncGen = struct {
 
                     // We have a pointer to a packed struct field that happens to be byte-aligned.
                     // Offset our operand pointer by the correct number of bytes.
-                    const byte_offset = struct_ty.packedStructFieldByteOffset(field_index, mod);
+                    const byte_offset = struct_ty.packedStructFieldByteOffset(field_index, mod) + @divExact(struct_ptr_ty_info.packed_offset.bit_offset, 8);
                     if (byte_offset == 0) return struct_ptr;
                     const usize_ty = try o.lowerType(Type.usize);
                     const llvm_index = try o.builder.intValue(usize_ty, byte_offset);
test/behavior/packed-struct.zig
@@ -528,6 +528,100 @@ test "nested packed struct field access test" {
     try std.testing.expect(arg.g.i == 8);
 }
 
+test "nested packed struct at non-zero offset" {
+    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
+
+    const Pair = packed struct(u24) {
+        a: u16 = 0,
+        b: u8 = 0,
+    };
+    const A = packed struct {
+        p1: Pair,
+        p2: Pair,
+    };
+
+    var k: u8 = 123;
+    var v: A = .{
+        .p1 = .{ .a = k + 1, .b = k },
+        .p2 = .{ .a = k + 1, .b = k },
+    };
+
+    try expect(v.p1.a == k + 1 and v.p1.b == k);
+    try expect(v.p2.a == k + 1 and v.p2.b == k);
+
+    v.p2.a -= v.p1.a;
+    v.p2.b -= v.p1.b;
+    try expect(v.p2.a == 0 and v.p2.b == 0);
+    try expect(v.p1.a == k + 1 and v.p1.b == k);
+}
+
+test "nested packed struct at non-zero offset 2" {
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+
+    const S = struct {
+        const Pair = packed struct(u40) {
+            a: u32 = 0,
+            b: u8 = 0,
+        };
+        const A = packed struct {
+            p1: Pair,
+            p2: Pair,
+            c: C,
+        };
+        const C = packed struct {
+            p1: Pair,
+            pad1: u5,
+            p2: Pair,
+            pad2: u3,
+            last: i16,
+        };
+
+        fn doTheTest() !void {
+            var k: u8 = 123;
+            var v: A = .{
+                .p1 = .{ .a = k + 1, .b = k },
+                .p2 = .{ .a = k + 1, .b = k },
+                .c = .{
+                    .pad1 = 11,
+                    .pad2 = 2,
+                    .p1 = .{ .a = k + 1, .b = k },
+                    .p2 = .{ .a = k + 1, .b = k },
+                    .last = -12345,
+                },
+            };
+
+            try expect(v.p1.a == k + 1 and v.p1.b == k);
+            try expect(v.p2.a == k + 1 and v.p2.b == k);
+            try expect(v.c.p2.a == k + 1 and v.c.p2.b == k);
+            try expect(v.c.p2.a == k + 1 and v.c.p2.b == k);
+            try expect(v.c.last == -12345);
+            try expect(v.c.pad1 == 11 and v.c.pad2 == 2);
+
+            v.p2.a -= v.p1.a;
+            v.p2.b -= v.p1.b;
+            v.c.p2.a -= v.c.p1.a;
+            v.c.p2.b -= v.c.p1.b;
+            v.c.last -|= 32000;
+            try expect(v.p2.a == 0 and v.p2.b == 0);
+            try expect(v.p1.a == k + 1 and v.p1.b == k);
+            try expect(v.c.p2.a == 0 and v.c.p2.b == 0);
+            try expect(v.c.p1.a == k + 1 and v.c.p1.b == k);
+            try expect(v.c.last == -32768);
+            try expect(v.c.pad1 == 11 and v.c.pad2 == 2);
+        }
+    };
+
+    try S.doTheTest();
+    try comptime S.doTheTest();
+}
+
 test "runtime init of unnamed packed struct type" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;