authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-09-21 02:00:52+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-09-21 14:48:41-07:00
log1b672e41c528b0aa225cbe07c61203d04b2d9034
tree080cac4fbde495c9b216fac1cd67e84799740059
parentcd242b7440e11d9997c33296b3974dfb1fbd5d95

InternPool,Sema,type,llvm: alignment fixes

This changeset fixes the handling of alignment in several places. The new rules are: * `@alignOf(T)` where `T` is a runtime zero-bit type is at least 1, maybe greater. * Zero-bit fields in `extern` structs *do* force alignment, potentially offsetting following fields. * Zero-bit fields *do* have addresses within structs which can be observed and are consistent with `@offsetOf`. These are not necessarily all implemented correctly yet (see disabled test), but this commit fixes all regressions compared to master, and makes one new test pass.

7 files changed, 127 insertions(+), 44 deletions(-)

src/Sema.zig+13-6
......@@ -34325,6 +34325,7 @@ pub fn resolveStructAlignment(
3432534325 struct_type.flagsPtr(ip).alignment = result;
3432634326 return result;
3432734327 }
34328 defer struct_type.clearAlignmentWip(ip);
3432834329
3432934330 var result: Alignment = .@"1";
3433034331
......@@ -34337,7 +34338,7 @@ pub fn resolveStructAlignment(
3433734338 field_ty,
3433834339 struct_type.layout,
3433934340 );
34340 result = result.max(field_align);
34341 result = result.maxStrict(field_align);
3434134342 }
3434234343
3434334344 struct_type.flagsPtr(ip).alignment = result;
......@@ -34373,6 +34374,8 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void {
3437334374 const aligns = try sema.arena.alloc(Alignment, struct_type.field_types.len);
3437434375 const sizes = try sema.arena.alloc(u64, struct_type.field_types.len);
3437534376
34377 var big_align: Alignment = .@"1";
34378
3437634379 for (aligns, sizes, 0..) |*field_align, *field_size, i| {
3437734380 const field_ty = struct_type.field_types.get(ip)[i].toType();
3437834381 if (struct_type.fieldIsComptime(ip, i) or try sema.typeRequiresComptime(field_ty)) {
......@@ -34381,6 +34384,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void {
3438134384 field_align.* = .none;
3438234385 continue;
3438334386 }
34387
3438434388 field_size.* = sema.typeAbiSize(field_ty) catch |err| switch (err) {
3438534389 error.AnalysisFail => {
3438634390 const msg = sema.err orelse return err;
......@@ -34394,6 +34398,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void {
3439434398 field_ty,
3439534399 struct_type.layout,
3439634400 );
34401 big_align = big_align.maxStrict(field_align.*);
3439734402 }
3439834403
3439934404 if (struct_type.flagsPtr(ip).assumed_runtime_bits and !(try sema.typeHasRuntimeBits(ty))) {
......@@ -34409,8 +34414,13 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void {
3440934414 if (struct_type.hasReorderedFields()) {
3441034415 const runtime_order = struct_type.runtime_order.get(ip);
3441134416
34412 for (sizes, runtime_order, 0..) |size, *ro, i| {
34413 ro.* = if (size != 0) @enumFromInt(i) else .omitted;
34417 for (runtime_order, 0..) |*ro, i| {
34418 const field_ty = struct_type.field_types.get(ip)[i].toType();
34419 if (struct_type.fieldIsComptime(ip, i) or try sema.typeRequiresComptime(field_ty)) {
34420 ro.* = .omitted;
34421 } else {
34422 ro.* = @enumFromInt(i);
34423 }
3441434424 }
3441534425
3441634426 const RuntimeOrder = InternPool.Key.StructType.RuntimeOrder;
......@@ -34454,10 +34464,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void {
3445434464 const offsets = struct_type.offsets.get(ip);
3445534465 var it = struct_type.iterateRuntimeOrder(ip);
3445634466 var offset: u64 = 0;
34457 var big_align: Alignment = .@"1";
3445834467 while (it.next()) |i| {
34459 if (aligns[i] == .none) continue;
34460 big_align = big_align.maxStrict(aligns[i]);
3446134468 offsets[i] = @intCast(aligns[i].forward(offset));
3446234469 offset = offsets[i] + sizes[i];
3446334470 }
src/codegen/llvm.zig+41-8
......@@ -833,7 +833,10 @@ pub const Object = struct {
833833
834834 /// When an LLVM struct type is created, an entry is inserted into this
835835 /// table for every zig source field of the struct that has a corresponding
836 /// LLVM struct field. comptime fields and 0 bit fields are not included.
836 /// LLVM struct field. comptime fields are not included. Zero-bit fields are
837 /// mapped to a field at the correct byte, which may be a padding field, or
838 /// are not mapped, in which case they are sematically at the end of the
839 /// struct.
837840 /// The value is the LLVM struct field index.
838841 /// This is denormalized data.
839842 struct_field_map: std.AutoHashMapUnmanaged(ZigStructField, c_uint),
......@@ -2500,7 +2503,6 @@ pub const Object = struct {
25002503 try di_fields.ensureUnusedCapacity(gpa, field_types.len);
25012504
25022505 comptime assert(struct_layout_version == 2);
2503 var offset: u64 = 0;
25042506 var it = struct_type.iterateRuntimeOrder(ip);
25052507 while (it.next()) |field_index| {
25062508 const field_ty = field_types[field_index].toType();
......@@ -2511,8 +2513,7 @@ pub const Object = struct {
25112513 field_ty,
25122514 struct_type.layout,
25132515 );
2514 const field_offset = field_align.forward(offset);
2515 offset = field_offset + field_size;
2516 const field_offset = ty.structFieldOffset(field_index, mod);
25162517
25172518 const field_name = struct_type.fieldName(ip, field_index).unwrap() orelse
25182519 try ip.getOrPutStringFmt(gpa, "{d}", .{field_index});
......@@ -3304,10 +3305,10 @@ pub const Object = struct {
33043305 var offset: u64 = 0;
33053306 var big_align: InternPool.Alignment = .@"1";
33063307 var struct_kind: Builder.Type.Structure.Kind = .normal;
3308 // When we encounter a zero-bit field, we place it here so we know to map it to the next non-zero-bit field (if any).
33073309 var it = struct_type.iterateRuntimeOrder(ip);
33083310 while (it.next()) |field_index| {
33093311 const field_ty = struct_type.field_types.get(ip)[field_index].toType();
3310 if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
33113312 const field_align = mod.structFieldAlignment(
33123313 struct_type.fieldAlign(ip, field_index),
33133314 field_ty,
......@@ -3324,6 +3325,20 @@ pub const Object = struct {
33243325 o.gpa,
33253326 try o.builder.arrayType(padding_len, .i8),
33263327 );
3328
3329 if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) {
3330 // This is a zero-bit field. If there are runtime bits after this field,
3331 // map to the next LLVM field (which we know exists): otherwise, don't
3332 // map the field, indicating it's at the end of the struct.
3333 if (offset != struct_type.size(ip).*) {
3334 try o.struct_field_map.put(o.gpa, .{
3335 .struct_ty = t.toIntern(),
3336 .field_index = field_index,
3337 }, @intCast(llvm_field_types.items.len));
3338 }
3339 continue;
3340 }
3341
33273342 try o.struct_field_map.put(o.gpa, .{
33283343 .struct_ty = t.toIntern(),
33293344 .field_index = field_index,
......@@ -3360,12 +3375,14 @@ pub const Object = struct {
33603375 var offset: u64 = 0;
33613376 var big_align: InternPool.Alignment = .none;
33623377
3378 const struct_size = t.abiSize(mod);
3379
33633380 for (
33643381 anon_struct_type.types.get(ip),
33653382 anon_struct_type.values.get(ip),
33663383 0..,
33673384 ) |field_ty, field_val, field_index| {
3368 if (field_val != .none or !field_ty.toType().hasRuntimeBits(mod)) continue;
3385 if (field_val != .none) continue;
33693386
33703387 const field_align = field_ty.toType().abiAlignment(mod);
33713388 big_align = big_align.max(field_align);
......@@ -3377,6 +3394,18 @@ pub const Object = struct {
33773394 o.gpa,
33783395 try o.builder.arrayType(padding_len, .i8),
33793396 );
3397 if (!field_ty.toType().hasRuntimeBitsIgnoreComptime(mod)) {
3398 // This is a zero-bit field. If there are runtime bits after this field,
3399 // map to the next LLVM field (which we know exists): otherwise, don't
3400 // map the field, indicating it's at the end of the struct.
3401 if (offset != struct_size) {
3402 try o.struct_field_map.put(o.gpa, .{
3403 .struct_ty = t.toIntern(),
3404 .field_index = @intCast(field_index),
3405 }, @intCast(llvm_field_types.items.len));
3406 }
3407 continue;
3408 }
33803409 try o.struct_field_map.put(o.gpa, .{
33813410 .struct_ty = t.toIntern(),
33823411 .field_index = @intCast(field_index),
......@@ -4019,7 +4048,6 @@ pub const Object = struct {
40194048 var field_it = struct_type.iterateRuntimeOrder(ip);
40204049 while (field_it.next()) |field_index| {
40214050 const field_ty = struct_type.field_types.get(ip)[field_index].toType();
4022 if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
40234051 const field_align = mod.structFieldAlignment(
40244052 struct_type.fieldAlign(ip, field_index),
40254053 field_ty,
......@@ -4040,6 +4068,11 @@ pub const Object = struct {
40404068 llvm_index += 1;
40414069 }
40424070
4071 if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) {
4072 // This is a zero-bit field - we only needed it for the alignment.
4073 continue;
4074 }
4075
40434076 vals[llvm_index] = try o.lowerValue(
40444077 (try val.fieldValue(mod, field_index)).toIntern(),
40454078 );
......@@ -6122,7 +6155,7 @@ pub const FuncGen = struct {
61226155 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
61236156 const ptr_ty = self.typeOf(bin_op.lhs);
61246157 const elem_ty = ptr_ty.childType(mod);
6125 if (!elem_ty.hasRuntimeBitsIgnoreComptime(mod)) return (try o.lowerPtrToVoid(ptr_ty)).toValue();
6158 if (!elem_ty.hasRuntimeBitsIgnoreComptime(mod)) return self.resolveInst(bin_op.lhs);
61266159
61276160 const base_ptr = try self.resolveInst(bin_op.lhs);
61286161 const rhs = try self.resolveInst(bin_op.rhs);
src/type.zig+8-10
......@@ -833,7 +833,7 @@ pub const Type = struct {
833833 };
834834 }
835835
836 /// Returns `none` for 0-bit types.
836 /// Never returns `none`. Asserts that all necessary type resolution is already done.
837837 pub fn abiAlignment(ty: Type, mod: *Module) Alignment {
838838 return (ty.abiAlignmentAdvanced(mod, .eager) catch unreachable).scalar;
839839 }
......@@ -878,10 +878,10 @@ pub const Type = struct {
878878 };
879879
880880 switch (ty.toIntern()) {
881 .empty_struct_type => return AbiAlignmentAdvanced{ .scalar = .none },
881 .empty_struct_type => return AbiAlignmentAdvanced{ .scalar = .@"1" },
882882 else => switch (ip.indexToKey(ty.toIntern())) {
883883 .int_type => |int_type| {
884 if (int_type.bits == 0) return AbiAlignmentAdvanced{ .scalar = .none };
884 if (int_type.bits == 0) return AbiAlignmentAdvanced{ .scalar = .@"1" };
885885 return .{ .scalar = intAbiAlignment(int_type.bits, target) };
886886 },
887887 .ptr_type, .anyframe_type => {
......@@ -929,6 +929,7 @@ pub const Type = struct {
929929 .isize,
930930 .export_options,
931931 .extern_options,
932 .type_info,
932933 => return .{
933934 .scalar = Alignment.fromByteUnits(@divExact(target.ptrBitWidth(), 8)),
934935 },
......@@ -974,8 +975,7 @@ pub const Type = struct {
974975 .null,
975976 .undefined,
976977 .enum_literal,
977 .type_info,
978 => return .{ .scalar = .none },
978 => return .{ .scalar = .@"1" },
979979
980980 .noreturn => unreachable,
981981 .generic_poison => unreachable,
......@@ -1010,11 +1010,9 @@ pub const Type = struct {
10101010 };
10111011 },
10121012 .anon_struct_type => |tuple| {
1013 var big_align: Alignment = .none;
1013 var big_align: Alignment = .@"1";
10141014 for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, val| {
10151015 if (val != .none) continue; // comptime field
1016 if (!(field_ty.toType().hasRuntimeBits(mod))) continue;
1017
10181016 switch (try field_ty.toType().abiAlignmentAdvanced(mod, strat)) {
10191017 .scalar => |field_align| big_align = big_align.max(field_align),
10201018 .val => switch (strat) {
......@@ -1059,7 +1057,7 @@ pub const Type = struct {
10591057 }
10601058 }
10611059
1062 var max_align: Alignment = .none;
1060 var max_align: Alignment = .@"1";
10631061 if (union_obj.hasTag(ip)) max_align = union_obj.enum_tag_ty.toType().abiAlignment(mod);
10641062 for (0..union_obj.field_names.len) |field_index| {
10651063 const field_ty = union_obj.field_types.get(ip)[field_index].toType();
......@@ -1172,7 +1170,7 @@ pub const Type = struct {
11721170 .scalar = Alignment.fromByteUnits(@divExact(target.ptrBitWidth(), 8)),
11731171 },
11741172 .ErrorSet => return abiAlignmentAdvanced(Type.anyerror, mod, strat),
1175 .NoReturn => return .{ .scalar = .none },
1173 .NoReturn => return .{ .scalar = .@"1" },
11761174 else => {},
11771175 }
11781176
src/value.zig+2-1
......@@ -1265,7 +1265,8 @@ pub const Value = struct {
12651265 .int => |int| switch (int.storage) {
12661266 .big_int => |big_int| big_int.orderAgainstScalar(0),
12671267 inline .u64, .i64 => |x| std.math.order(x, 0),
1268 .lazy_align, .lazy_size => |ty| return if (ty.toType().hasRuntimeBitsAdvanced(
1268 .lazy_align => .gt, // alignment is never 0
1269 .lazy_size => |ty| return if (ty.toType().hasRuntimeBitsAdvanced(
12691270 mod,
12701271 false,
12711272 if (opt_sema) |sema| .{ .sema = sema } else .eager,
test/behavior/align.zig+55
......@@ -619,3 +619,58 @@ test "sub-aligned pointer field access" {
619619 .Little => try expect(x == 0x09080706),
620620 }
621621}
622
623test "alignment of zero-bit types is respected" {
624 if (true) return error.SkipZigTest; // TODO
625
626 const S = struct { arr: [0]usize = .{} };
627
628 comptime assert(@alignOf(void) == 1);
629 comptime assert(@alignOf(u0) == 1);
630 comptime assert(@alignOf([0]usize) == @alignOf(usize));
631 comptime assert(@alignOf(S) == @alignOf(usize));
632
633 var s: S = .{};
634 var v32: void align(32) = {};
635 var x32: u0 align(32) = 0;
636 var s32: S align(32) = .{};
637
638 var zero: usize = 0;
639
640 try expect(@intFromPtr(&s) % @alignOf(usize) == 0);
641 try expect(@intFromPtr(&s.arr) % @alignOf(usize) == 0);
642 try expect(@intFromPtr(s.arr[zero..zero].ptr) % @alignOf(usize) == 0);
643 try expect(@intFromPtr(&v32) % 32 == 0);
644 try expect(@intFromPtr(&x32) % 32 == 0);
645 try expect(@intFromPtr(&s32) % 32 == 0);
646 try expect(@intFromPtr(&s32.arr) % 32 == 0);
647 try expect(@intFromPtr(s32.arr[zero..zero].ptr) % 32 == 0);
648}
649
650test "zero-bit fields in extern struct pad fields appropriately" {
651 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
652 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
653 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
654 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
655 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
656
657 const S = extern struct {
658 x: u8,
659 a: [0]u16 = .{},
660 y: u8,
661 };
662
663 // `a` should give `S` alignment 2, and pad the `arr` field.
664 comptime assert(@alignOf(S) == 2);
665 comptime assert(@sizeOf(S) == 4);
666 comptime assert(@offsetOf(S, "x") == 0);
667 comptime assert(@offsetOf(S, "a") == 2);
668 comptime assert(@offsetOf(S, "y") == 2);
669
670 var s: S = .{ .x = 100, .y = 200 };
671
672 try expect(@intFromPtr(&s) % 2 == 0);
673 try expect(@intFromPtr(&s.y) - @intFromPtr(&s.x) == 2);
674 try expect(@intFromPtr(&s.y) == @intFromPtr(&s.a));
675 try expect(@fieldParentPtr(S, "a", &s.a) == &s);
676}
test/behavior/alignof.zig+7-18
......@@ -18,24 +18,13 @@ test "@alignOf(T) before referencing T" {
1818}
1919
2020test "comparison of @alignOf(T) against zero" {
21 {
22 const T = struct { x: u32 };
23 try expect(!(@alignOf(T) == 0));
24 try expect(@alignOf(T) != 0);
25 try expect(!(@alignOf(T) < 0));
26 try expect(!(@alignOf(T) <= 0));
27 try expect(@alignOf(T) > 0);
28 try expect(@alignOf(T) >= 0);
29 }
30 {
31 const T = struct {};
32 try expect(@alignOf(T) == 0);
33 try expect(!(@alignOf(T) != 0));
34 try expect(!(@alignOf(T) < 0));
35 try expect(@alignOf(T) <= 0);
36 try expect(!(@alignOf(T) > 0));
37 try expect(@alignOf(T) >= 0);
38 }
21 const T = struct { x: u32 };
22 try expect(!(@alignOf(T) == 0));
23 try expect(@alignOf(T) != 0);
24 try expect(!(@alignOf(T) < 0));
25 try expect(!(@alignOf(T) <= 0));
26 try expect(@alignOf(T) > 0);
27 try expect(@alignOf(T) >= 0);
3928}
4029
4130test "correct alignment for elements and slices of aligned array" {
test/behavior/empty_union.zig+1-1
......@@ -37,7 +37,7 @@ test "switch on empty tagged union" {
3737test "empty union" {
3838 const U = union {};
3939 try expect(@sizeOf(U) == 0);
40 try expect(@alignOf(U) == 0);
40 try expect(@alignOf(U) == 1);
4141}
4242
4343test "empty extern union" {