Commit 80e493cb36

mlugg <mlugg@mlugg.co.uk>
2023-06-17 00:08:33
Sema: copy pointer alignment to struct field pointers
1 parent 2611d97
Changed files (3)
src/Module.zig
@@ -991,6 +991,7 @@ pub const Struct = struct {
         is_comptime: bool,
 
         /// Returns the field alignment. If the struct is packed, returns 0.
+        /// Keep implementation in sync with `Sema.structFieldAlignment`.
         pub fn alignment(
             field: Field,
             mod: *Module,
src/Sema.zig
@@ -25842,6 +25842,9 @@ fn structFieldPtrByIndex(
 
     const target = mod.getTarget();
 
+    const parent_align = struct_ptr_ty_info.flags.alignment.toByteUnitsOptional() orelse
+        try sema.typeAbiAlignment(struct_ptr_ty_info.child.toType());
+
     if (struct_obj.layout == .Packed) {
         comptime assert(Type.packed_struct_layout_version == 2);
 
@@ -25863,8 +25866,6 @@ fn structFieldPtrByIndex(
             ptr_ty_data.packed_offset.bit_offset += struct_ptr_ty_info.packed_offset.bit_offset;
         }
 
-        const parent_align = struct_ptr_ty_info.flags.alignment.toByteUnitsOptional() orelse
-            struct_ptr_ty_info.child.toType().abiAlignment(mod);
         ptr_ty_data.flags.alignment = Alignment.fromByteUnits(parent_align);
 
         // If the field happens to be byte-aligned, simplify the pointer type.
@@ -25888,8 +25889,13 @@ fn structFieldPtrByIndex(
                 ptr_ty_data.packed_offset = .{ .host_size = 0, .bit_offset = 0 };
             }
         }
+    } else if (struct_obj.layout == .Extern and field_index == 0) {
+        // This is the first field in memory, so can inherit the struct alignment
+        ptr_ty_data.flags.alignment = Alignment.fromByteUnits(parent_align);
     } else {
-        ptr_ty_data.flags.alignment = field.abi_align;
+        // Our alignment is capped at the field alignment
+        const field_align = try sema.structFieldAlignment(field, struct_obj.layout);
+        ptr_ty_data.flags.alignment = Alignment.fromByteUnits(@min(field_align, parent_align));
     }
 
     const ptr_field_ty = try mod.ptrType(ptr_ty_data);
@@ -35952,6 +35958,28 @@ fn unionFieldAlignment(sema: *Sema, field: Module.Union.Field) !u32 {
         field.abi_align.toByteUnitsOptional() orelse try sema.typeAbiAlignment(field.ty)));
 }
 
+/// Keep implementation in sync with `Module.Struct.Field.alignment`.
+fn structFieldAlignment(sema: *Sema, field: Module.Struct.Field, layout: std.builtin.Type.ContainerLayout) !u32 {
+    const mod = sema.mod;
+    if (field.abi_align.toByteUnitsOptional()) |a| {
+        assert(layout != .Packed);
+        return @as(u32, @intCast(a));
+    }
+    switch (layout) {
+        .Packed => return 0,
+        .Auto => if (mod.getTarget().ofmt != .c) {
+            return sema.typeAbiAlignment(field.ty);
+        },
+        .Extern => {},
+    }
+    // extern
+    const ty_abi_align = try sema.typeAbiAlignment(field.ty);
+    if (field.ty.isAbiInt(mod) and field.ty.intInfo(mod).bits >= 128) {
+        return @max(ty_abi_align, 16);
+    }
+    return ty_abi_align;
+}
+
 /// Synchronize logic with `Type.isFnOrHasRuntimeBits`.
 pub fn fnHasRuntimeBits(sema: *Sema, ty: Type) CompileError!bool {
     const mod = sema.mod;
test/behavior/struct.zig
@@ -1,6 +1,7 @@
 const std = @import("std");
 const builtin = @import("builtin");
 const native_endian = builtin.target.cpu.arch.endian();
+const assert = std.debug.assert;
 const expect = std.testing.expect;
 const expectEqual = std.testing.expectEqual;
 const expectEqualSlices = std.testing.expectEqualSlices;
@@ -1637,3 +1638,79 @@ test "instantiate struct with comptime field" {
         comptime std.debug.assert(things.foo == 1);
     }
 }
+
+test "struct field pointer has correct alignment" {
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
+
+    const S = struct {
+        fn doTheTest() !void {
+            var a: struct { x: u32 } = .{ .x = 123 };
+            var b: struct { x: u32 } align(1) = .{ .x = 456 };
+            var c: struct { x: u32 } align(64) = .{ .x = 789 };
+
+            const ap = &a.x;
+            const bp = &b.x;
+            const cp = &c.x;
+
+            comptime assert(@TypeOf(ap) == *u32);
+            comptime assert(@TypeOf(bp) == *align(1) u32);
+            comptime assert(@TypeOf(cp) == *u32); // undefined layout, cannot inherit larger alignment
+
+            try expectEqual(@as(u32, 123), ap.*);
+            try expectEqual(@as(u32, 456), bp.*);
+            try expectEqual(@as(u32, 789), cp.*);
+        }
+    };
+
+    try S.doTheTest();
+    try comptime S.doTheTest();
+}
+
+test "extern struct field pointer has correct alignment" {
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
+
+    const S = struct {
+        fn doTheTest() !void {
+            var a: extern struct { x: u32, y: u32 } = .{ .x = 1, .y = 2 };
+            var b: extern struct { x: u32, y: u32 } align(1) = .{ .x = 3, .y = 4 };
+            var c: extern struct { x: u32, y: u32 } align(64) = .{ .x = 5, .y = 6 };
+
+            const axp = &a.x;
+            const bxp = &b.x;
+            const cxp = &c.x;
+            const ayp = &a.y;
+            const byp = &b.y;
+            const cyp = &c.y;
+
+            comptime assert(@TypeOf(axp) == *u32);
+            comptime assert(@TypeOf(bxp) == *align(1) u32);
+            comptime assert(@TypeOf(cxp) == *align(64) u32); // first field, inherits larger alignment
+            comptime assert(@TypeOf(ayp) == *u32);
+            comptime assert(@TypeOf(byp) == *align(1) u32);
+            comptime assert(@TypeOf(cyp) == *u32);
+
+            try expectEqual(@as(u32, 1), axp.*);
+            try expectEqual(@as(u32, 3), bxp.*);
+            try expectEqual(@as(u32, 5), cxp.*);
+
+            try expectEqual(@as(u32, 2), ayp.*);
+            try expectEqual(@as(u32, 4), byp.*);
+            try expectEqual(@as(u32, 6), cyp.*);
+        }
+    };
+
+    try S.doTheTest();
+    try comptime S.doTheTest();
+}