authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-06-07 20:51:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-06-07 21:05:40-07:00
log3e30ba3f20dce2d406253de3fc0eb86934a3eaa7
tree37cde6ce42dc2e326bd7beb599b6ad0d77051c28
parent70dc910086582b028d404d5de5049ceae0a95161

stage2: better codegen for byte-aligned packed struct fields

* Sema: handle overaligned packed struct field pointers * LLVM: handle byte-aligned packed struct field pointers

4 files changed, 136 insertions(+), 13 deletions(-)

src/Sema.zig+21-2
......@@ -18678,8 +18678,6 @@ fn structFieldPtrByIndex(
1867818678
1867918679 const target = sema.mod.getTarget();
1868018680
18681 // TODO handle when the struct pointer is overaligned, we should return a potentially
18682 // over-aligned field pointer too.
1868318681 if (struct_obj.layout == .Packed) {
1868418682 comptime assert(Type.packed_struct_layout_version == 2);
1868518683
......@@ -18700,6 +18698,27 @@ fn structFieldPtrByIndex(
1870018698 ptr_ty_data.host_size = struct_ptr_ty_info.host_size;
1870118699 ptr_ty_data.bit_offset += struct_ptr_ty_info.bit_offset;
1870218700 }
18701
18702 const parent_align = if (struct_ptr_ty_info.@"align" != 0)
18703 struct_ptr_ty_info.@"align"
18704 else
18705 struct_ptr_ty_info.pointee_type.abiAlignment(target);
18706 ptr_ty_data.@"align" = parent_align;
18707
18708 // If the field happens to be byte-aligned, simplify the pointer type.
18709 // The pointee type bit size must match its ABI byte size so that loads and stores
18710 // do not interfere with the surrounding packed bits.
18711 if (parent_align != 0 and ptr_ty_data.bit_offset % 8 == 0) {
18712 const byte_offset = ptr_ty_data.bit_offset / 8;
18713 const elem_size_bytes = ptr_ty_data.pointee_type.abiSize(target);
18714 const elem_size_bits = ptr_ty_data.pointee_type.bitSize(target);
18715 if (elem_size_bytes * 8 == elem_size_bits) {
18716 const new_align = @as(u32, 1) << @intCast(u5, @ctz(u64, byte_offset | parent_align));
18717 ptr_ty_data.bit_offset = 0;
18718 ptr_ty_data.host_size = 0;
18719 ptr_ty_data.@"align" = new_align;
18720 }
18721 }
1870318722 } else {
1870418723 ptr_ty_data.@"align" = field.abi_align;
1870518724 }
src/codegen/llvm.zig+29-11
......@@ -8311,23 +8311,41 @@ pub const FuncGen = struct {
83118311 field_index: u32,
83128312 ) !?*const llvm.Value {
83138313 if (self.liveness.isUnused(inst)) return null;
8314
8315 const target = self.dg.object.target;
83148316 const struct_ty = struct_ptr_ty.childType();
83158317 switch (struct_ty.zigTypeTag()) {
83168318 .Struct => switch (struct_ty.containerLayout()) {
83178319 .Packed => {
8318 // From LLVM's perspective, a pointer to a packed struct and a pointer
8319 // to a field of a packed struct are the same. The difference is in the
8320 // Zig pointer type which provides information for how to mask and shift
8321 // out the relevant bits when accessing the pointee.
8322 // Here we perform a bitcast because we want to use the host_size
8323 // as the llvm pointer element type.
8324 const result_llvm_ty = try self.dg.lowerType(self.air.typeOfIndex(inst));
8325 // TODO this can be removed if we change host_size to be bits instead
8326 // of bytes.
8327 return self.builder.buildBitCast(struct_ptr, result_llvm_ty, "");
8320 const result_ty = self.air.typeOfIndex(inst);
8321 const result_ty_info = result_ty.ptrInfo().data;
8322 const result_llvm_ty = try self.dg.lowerType(result_ty);
8323
8324 if (result_ty_info.host_size != 0) {
8325 // From LLVM's perspective, a pointer to a packed struct and a pointer
8326 // to a field of a packed struct are the same. The difference is in the
8327 // Zig pointer type which provides information for how to mask and shift
8328 // out the relevant bits when accessing the pointee.
8329 // Here we perform a bitcast because we want to use the host_size
8330 // as the llvm pointer element type.
8331 return self.builder.buildBitCast(struct_ptr, result_llvm_ty, "");
8332 }
8333
8334 // We have a pointer to a packed struct field that happens to be byte-aligned.
8335 // Offset our operand pointer by the correct number of bytes.
8336 const byte_offset = struct_ty.packedStructFieldByteOffset(field_index, target);
8337 if (byte_offset == 0) {
8338 return self.builder.buildBitCast(struct_ptr, result_llvm_ty, "");
8339 }
8340 const llvm_bytes_ptr_ty = self.context.intType(8).pointerType(0);
8341 const ptr_as_bytes = self.builder.buildBitCast(struct_ptr, llvm_bytes_ptr_ty, "");
8342 const llvm_usize = try self.dg.lowerType(Type.usize);
8343 const llvm_index = llvm_usize.constInt(byte_offset, .False);
8344 const indices: [1]*const llvm.Value = .{llvm_index};
8345 const new_ptr = self.builder.buildInBoundsGEP(ptr_as_bytes, &indices, indices.len, "");
8346 return self.builder.buildBitCast(new_ptr, result_llvm_ty, "");
83288347 },
83298348 else => {
8330 const target = self.dg.module.getTarget();
83318349 var ty_buf: Type.Payload.Pointer = undefined;
83328350 if (llvmFieldIndex(struct_ty, field_index, target, &ty_buf)) |llvm_field_index| {
83338351 return self.builder.buildStructGEP(struct_ptr, llvm_field_index, "");
src/type.zig+21
......@@ -5591,6 +5591,27 @@ pub const Type = extern union {
55915591 }
55925592 }
55935593
5594 pub fn packedStructFieldByteOffset(ty: Type, field_index: usize, target: Target) u32 {
5595 const struct_obj = ty.castTag(.@"struct").?.data;
5596 assert(struct_obj.layout == .Packed);
5597 comptime assert(Type.packed_struct_layout_version == 2);
5598
5599 var bit_offset: u16 = undefined;
5600 var running_bits: u16 = 0;
5601 for (struct_obj.fields.values()) |f, i| {
5602 if (!f.ty.hasRuntimeBits()) continue;
5603
5604 if (i == field_index) {
5605 bit_offset = running_bits;
5606 }
5607 running_bits += @intCast(u16, f.ty.bitSize(target));
5608 }
5609 const host_size = (running_bits + 7) / 8;
5610 _ = host_size; // TODO big-endian
5611 const byte_offset = bit_offset / 8;
5612 return byte_offset;
5613 }
5614
55945615 pub const FieldOffset = struct {
55955616 field: usize,
55965617 offset: u64,
test/behavior/packed-struct.zig+65
......@@ -1,5 +1,6 @@
11const std = @import("std");
22const builtin = @import("builtin");
3const assert = std.debug.assert;
34const expect = std.testing.expect;
45const expectEqual = std.testing.expectEqual;
56const native_endian = builtin.cpu.arch.endian();
......@@ -305,3 +306,67 @@ test "regular in irregular packed struct" {
305306 try expectEqual(@as(u16, 235), foo.bar.a);
306307 try expectEqual(@as(u8, 42), foo.bar.b);
307308}
309
310test "byte-aligned field pointer offsets" {
311 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
312 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
313 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
314 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
315 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
316 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
317
318 const S = struct {
319 const A = packed struct {
320 a: u8,
321 b: u8,
322 c: u8,
323 d: u8,
324 };
325
326 const B = packed struct {
327 a: u16,
328 b: u16,
329 };
330
331 fn doTheTest() !void {
332 var a: A = .{
333 .a = 1,
334 .b = 2,
335 .c = 3,
336 .d = 4,
337 };
338 comptime assert(@TypeOf(&a.a) == *align(4) u8);
339 comptime assert(@TypeOf(&a.b) == *u8);
340 comptime assert(@TypeOf(&a.c) == *align(2) u8);
341 comptime assert(@TypeOf(&a.d) == *u8);
342 try expect(a.a == 1);
343 try expect(a.b == 2);
344 try expect(a.c == 3);
345 try expect(a.d == 4);
346 a.a += 1;
347 a.b += 1;
348 a.c += 1;
349 a.d += 1;
350 try expect(a.a == 2);
351 try expect(a.b == 3);
352 try expect(a.c == 4);
353 try expect(a.d == 5);
354
355 var b: B = .{
356 .a = 1,
357 .b = 2,
358 };
359 comptime assert(@TypeOf(&b.a) == *align(4) u16);
360 comptime assert(@TypeOf(&b.b) == *u16);
361 try expect(b.a == 1);
362 try expect(b.b == 2);
363 b.a += 1;
364 b.b += 1;
365 try expect(b.a == 2);
366 try expect(b.b == 3);
367 }
368 };
369
370 try S.doTheTest();
371 comptime try S.doTheTest();
372}