| author | |
| committer | |
| log | 1b672e41c528b0aa225cbe07c61203d04b2d9034 |
| tree | 080cac4fbde495c9b216fac1cd67e84799740059 |
| parent | cd242b7440e11d9997c33296b3974dfb1fbd5d95 |
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( |
| 34325 | 34325 | struct_type.flagsPtr(ip).alignment = result; |
| 34326 | 34326 | return result; |
| 34327 | 34327 | } |
| 34328 | defer struct_type.clearAlignmentWip(ip); | |
| 34328 | 34329 | |
| 34329 | 34330 | var result: Alignment = .@"1"; |
| 34330 | 34331 | |
| ... | ... | @@ -34337,7 +34338,7 @@ pub fn resolveStructAlignment( |
| 34337 | 34338 | field_ty, |
| 34338 | 34339 | struct_type.layout, |
| 34339 | 34340 | ); |
| 34340 | result = result.max(field_align); | |
| 34341 | result = result.maxStrict(field_align); | |
| 34341 | 34342 | } |
| 34342 | 34343 | |
| 34343 | 34344 | struct_type.flagsPtr(ip).alignment = result; |
| ... | ... | @@ -34373,6 +34374,8 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void { |
| 34373 | 34374 | const aligns = try sema.arena.alloc(Alignment, struct_type.field_types.len); |
| 34374 | 34375 | const sizes = try sema.arena.alloc(u64, struct_type.field_types.len); |
| 34375 | 34376 | |
| 34377 | var big_align: Alignment = .@"1"; | |
| 34378 | ||
| 34376 | 34379 | for (aligns, sizes, 0..) |*field_align, *field_size, i| { |
| 34377 | 34380 | const field_ty = struct_type.field_types.get(ip)[i].toType(); |
| 34378 | 34381 | if (struct_type.fieldIsComptime(ip, i) or try sema.typeRequiresComptime(field_ty)) { |
| ... | ... | @@ -34381,6 +34384,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void { |
| 34381 | 34384 | field_align.* = .none; |
| 34382 | 34385 | continue; |
| 34383 | 34386 | } |
| 34387 | ||
| 34384 | 34388 | field_size.* = sema.typeAbiSize(field_ty) catch |err| switch (err) { |
| 34385 | 34389 | error.AnalysisFail => { |
| 34386 | 34390 | const msg = sema.err orelse return err; |
| ... | ... | @@ -34394,6 +34398,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void { |
| 34394 | 34398 | field_ty, |
| 34395 | 34399 | struct_type.layout, |
| 34396 | 34400 | ); |
| 34401 | big_align = big_align.maxStrict(field_align.*); | |
| 34397 | 34402 | } |
| 34398 | 34403 | |
| 34399 | 34404 | 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 { |
| 34409 | 34414 | if (struct_type.hasReorderedFields()) { |
| 34410 | 34415 | const runtime_order = struct_type.runtime_order.get(ip); |
| 34411 | 34416 | |
| 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 | } | |
| 34414 | 34424 | } |
| 34415 | 34425 | |
| 34416 | 34426 | const RuntimeOrder = InternPool.Key.StructType.RuntimeOrder; |
| ... | ... | @@ -34454,10 +34464,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void { |
| 34454 | 34464 | const offsets = struct_type.offsets.get(ip); |
| 34455 | 34465 | var it = struct_type.iterateRuntimeOrder(ip); |
| 34456 | 34466 | var offset: u64 = 0; |
| 34457 | var big_align: Alignment = .@"1"; | |
| 34458 | 34467 | while (it.next()) |i| { |
| 34459 | if (aligns[i] == .none) continue; | |
| 34460 | big_align = big_align.maxStrict(aligns[i]); | |
| 34461 | 34468 | offsets[i] = @intCast(aligns[i].forward(offset)); |
| 34462 | 34469 | offset = offsets[i] + sizes[i]; |
| 34463 | 34470 | } |
src/codegen/llvm.zig+41-8| ... | ... | @@ -833,7 +833,10 @@ pub const Object = struct { |
| 833 | 833 | |
| 834 | 834 | /// When an LLVM struct type is created, an entry is inserted into this |
| 835 | 835 | /// 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. | |
| 837 | 840 | /// The value is the LLVM struct field index. |
| 838 | 841 | /// This is denormalized data. |
| 839 | 842 | struct_field_map: std.AutoHashMapUnmanaged(ZigStructField, c_uint), |
| ... | ... | @@ -2500,7 +2503,6 @@ pub const Object = struct { |
| 2500 | 2503 | try di_fields.ensureUnusedCapacity(gpa, field_types.len); |
| 2501 | 2504 | |
| 2502 | 2505 | comptime assert(struct_layout_version == 2); |
| 2503 | var offset: u64 = 0; | |
| 2504 | 2506 | var it = struct_type.iterateRuntimeOrder(ip); |
| 2505 | 2507 | while (it.next()) |field_index| { |
| 2506 | 2508 | const field_ty = field_types[field_index].toType(); |
| ... | ... | @@ -2511,8 +2513,7 @@ pub const Object = struct { |
| 2511 | 2513 | field_ty, |
| 2512 | 2514 | struct_type.layout, |
| 2513 | 2515 | ); |
| 2514 | const field_offset = field_align.forward(offset); | |
| 2515 | offset = field_offset + field_size; | |
| 2516 | const field_offset = ty.structFieldOffset(field_index, mod); | |
| 2516 | 2517 | |
| 2517 | 2518 | const field_name = struct_type.fieldName(ip, field_index).unwrap() orelse |
| 2518 | 2519 | try ip.getOrPutStringFmt(gpa, "{d}", .{field_index}); |
| ... | ... | @@ -3304,10 +3305,10 @@ pub const Object = struct { |
| 3304 | 3305 | var offset: u64 = 0; |
| 3305 | 3306 | var big_align: InternPool.Alignment = .@"1"; |
| 3306 | 3307 | 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). | |
| 3307 | 3309 | var it = struct_type.iterateRuntimeOrder(ip); |
| 3308 | 3310 | while (it.next()) |field_index| { |
| 3309 | 3311 | const field_ty = struct_type.field_types.get(ip)[field_index].toType(); |
| 3310 | if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue; | |
| 3311 | 3312 | const field_align = mod.structFieldAlignment( |
| 3312 | 3313 | struct_type.fieldAlign(ip, field_index), |
| 3313 | 3314 | field_ty, |
| ... | ... | @@ -3324,6 +3325,20 @@ pub const Object = struct { |
| 3324 | 3325 | o.gpa, |
| 3325 | 3326 | try o.builder.arrayType(padding_len, .i8), |
| 3326 | 3327 | ); |
| 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 | ||
| 3327 | 3342 | try o.struct_field_map.put(o.gpa, .{ |
| 3328 | 3343 | .struct_ty = t.toIntern(), |
| 3329 | 3344 | .field_index = field_index, |
| ... | ... | @@ -3360,12 +3375,14 @@ pub const Object = struct { |
| 3360 | 3375 | var offset: u64 = 0; |
| 3361 | 3376 | var big_align: InternPool.Alignment = .none; |
| 3362 | 3377 | |
| 3378 | const struct_size = t.abiSize(mod); | |
| 3379 | ||
| 3363 | 3380 | for ( |
| 3364 | 3381 | anon_struct_type.types.get(ip), |
| 3365 | 3382 | anon_struct_type.values.get(ip), |
| 3366 | 3383 | 0.., |
| 3367 | 3384 | ) |field_ty, field_val, field_index| { |
| 3368 | if (field_val != .none or !field_ty.toType().hasRuntimeBits(mod)) continue; | |
| 3385 | if (field_val != .none) continue; | |
| 3369 | 3386 | |
| 3370 | 3387 | const field_align = field_ty.toType().abiAlignment(mod); |
| 3371 | 3388 | big_align = big_align.max(field_align); |
| ... | ... | @@ -3377,6 +3394,18 @@ pub const Object = struct { |
| 3377 | 3394 | o.gpa, |
| 3378 | 3395 | try o.builder.arrayType(padding_len, .i8), |
| 3379 | 3396 | ); |
| 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 | } | |
| 3380 | 3409 | try o.struct_field_map.put(o.gpa, .{ |
| 3381 | 3410 | .struct_ty = t.toIntern(), |
| 3382 | 3411 | .field_index = @intCast(field_index), |
| ... | ... | @@ -4019,7 +4048,6 @@ pub const Object = struct { |
| 4019 | 4048 | var field_it = struct_type.iterateRuntimeOrder(ip); |
| 4020 | 4049 | while (field_it.next()) |field_index| { |
| 4021 | 4050 | const field_ty = struct_type.field_types.get(ip)[field_index].toType(); |
| 4022 | if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue; | |
| 4023 | 4051 | const field_align = mod.structFieldAlignment( |
| 4024 | 4052 | struct_type.fieldAlign(ip, field_index), |
| 4025 | 4053 | field_ty, |
| ... | ... | @@ -4040,6 +4068,11 @@ pub const Object = struct { |
| 4040 | 4068 | llvm_index += 1; |
| 4041 | 4069 | } |
| 4042 | 4070 | |
| 4071 | if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) { | |
| 4072 | // This is a zero-bit field - we only needed it for the alignment. | |
| 4073 | continue; | |
| 4074 | } | |
| 4075 | ||
| 4043 | 4076 | vals[llvm_index] = try o.lowerValue( |
| 4044 | 4077 | (try val.fieldValue(mod, field_index)).toIntern(), |
| 4045 | 4078 | ); |
| ... | ... | @@ -6122,7 +6155,7 @@ pub const FuncGen = struct { |
| 6122 | 6155 | const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; |
| 6123 | 6156 | const ptr_ty = self.typeOf(bin_op.lhs); |
| 6124 | 6157 | 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); | |
| 6126 | 6159 | |
| 6127 | 6160 | const base_ptr = try self.resolveInst(bin_op.lhs); |
| 6128 | 6161 | const rhs = try self.resolveInst(bin_op.rhs); |
src/type.zig+8-10| ... | ... | @@ -833,7 +833,7 @@ pub const Type = struct { |
| 833 | 833 | }; |
| 834 | 834 | } |
| 835 | 835 | |
| 836 | /// Returns `none` for 0-bit types. | |
| 836 | /// Never returns `none`. Asserts that all necessary type resolution is already done. | |
| 837 | 837 | pub fn abiAlignment(ty: Type, mod: *Module) Alignment { |
| 838 | 838 | return (ty.abiAlignmentAdvanced(mod, .eager) catch unreachable).scalar; |
| 839 | 839 | } |
| ... | ... | @@ -878,10 +878,10 @@ pub const Type = struct { |
| 878 | 878 | }; |
| 879 | 879 | |
| 880 | 880 | switch (ty.toIntern()) { |
| 881 | .empty_struct_type => return AbiAlignmentAdvanced{ .scalar = .none }, | |
| 881 | .empty_struct_type => return AbiAlignmentAdvanced{ .scalar = .@"1" }, | |
| 882 | 882 | else => switch (ip.indexToKey(ty.toIntern())) { |
| 883 | 883 | .int_type => |int_type| { |
| 884 | if (int_type.bits == 0) return AbiAlignmentAdvanced{ .scalar = .none }; | |
| 884 | if (int_type.bits == 0) return AbiAlignmentAdvanced{ .scalar = .@"1" }; | |
| 885 | 885 | return .{ .scalar = intAbiAlignment(int_type.bits, target) }; |
| 886 | 886 | }, |
| 887 | 887 | .ptr_type, .anyframe_type => { |
| ... | ... | @@ -929,6 +929,7 @@ pub const Type = struct { |
| 929 | 929 | .isize, |
| 930 | 930 | .export_options, |
| 931 | 931 | .extern_options, |
| 932 | .type_info, | |
| 932 | 933 | => return .{ |
| 933 | 934 | .scalar = Alignment.fromByteUnits(@divExact(target.ptrBitWidth(), 8)), |
| 934 | 935 | }, |
| ... | ... | @@ -974,8 +975,7 @@ pub const Type = struct { |
| 974 | 975 | .null, |
| 975 | 976 | .undefined, |
| 976 | 977 | .enum_literal, |
| 977 | .type_info, | |
| 978 | => return .{ .scalar = .none }, | |
| 978 | => return .{ .scalar = .@"1" }, | |
| 979 | 979 | |
| 980 | 980 | .noreturn => unreachable, |
| 981 | 981 | .generic_poison => unreachable, |
| ... | ... | @@ -1010,11 +1010,9 @@ pub const Type = struct { |
| 1010 | 1010 | }; |
| 1011 | 1011 | }, |
| 1012 | 1012 | .anon_struct_type => |tuple| { |
| 1013 | var big_align: Alignment = .none; | |
| 1013 | var big_align: Alignment = .@"1"; | |
| 1014 | 1014 | for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, val| { |
| 1015 | 1015 | if (val != .none) continue; // comptime field |
| 1016 | if (!(field_ty.toType().hasRuntimeBits(mod))) continue; | |
| 1017 | ||
| 1018 | 1016 | switch (try field_ty.toType().abiAlignmentAdvanced(mod, strat)) { |
| 1019 | 1017 | .scalar => |field_align| big_align = big_align.max(field_align), |
| 1020 | 1018 | .val => switch (strat) { |
| ... | ... | @@ -1059,7 +1057,7 @@ pub const Type = struct { |
| 1059 | 1057 | } |
| 1060 | 1058 | } |
| 1061 | 1059 | |
| 1062 | var max_align: Alignment = .none; | |
| 1060 | var max_align: Alignment = .@"1"; | |
| 1063 | 1061 | if (union_obj.hasTag(ip)) max_align = union_obj.enum_tag_ty.toType().abiAlignment(mod); |
| 1064 | 1062 | for (0..union_obj.field_names.len) |field_index| { |
| 1065 | 1063 | const field_ty = union_obj.field_types.get(ip)[field_index].toType(); |
| ... | ... | @@ -1172,7 +1170,7 @@ pub const Type = struct { |
| 1172 | 1170 | .scalar = Alignment.fromByteUnits(@divExact(target.ptrBitWidth(), 8)), |
| 1173 | 1171 | }, |
| 1174 | 1172 | .ErrorSet => return abiAlignmentAdvanced(Type.anyerror, mod, strat), |
| 1175 | .NoReturn => return .{ .scalar = .none }, | |
| 1173 | .NoReturn => return .{ .scalar = .@"1" }, | |
| 1176 | 1174 | else => {}, |
| 1177 | 1175 | } |
| 1178 | 1176 |
src/value.zig+2-1| ... | ... | @@ -1265,7 +1265,8 @@ pub const Value = struct { |
| 1265 | 1265 | .int => |int| switch (int.storage) { |
| 1266 | 1266 | .big_int => |big_int| big_int.orderAgainstScalar(0), |
| 1267 | 1267 | 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( | |
| 1269 | 1270 | mod, |
| 1270 | 1271 | false, |
| 1271 | 1272 | if (opt_sema) |sema| .{ .sema = sema } else .eager, |
test/behavior/align.zig+55| ... | ... | @@ -619,3 +619,58 @@ test "sub-aligned pointer field access" { |
| 619 | 619 | .Little => try expect(x == 0x09080706), |
| 620 | 620 | } |
| 621 | 621 | } |
| 622 | ||
| 623 | test "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 | ||
| 650 | test "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" { |
| 18 | 18 | } |
| 19 | 19 | |
| 20 | 20 | test "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); | |
| 39 | 28 | } |
| 40 | 29 | |
| 41 | 30 | test "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" { |
| 37 | 37 | test "empty union" { |
| 38 | 38 | const U = union {}; |
| 39 | 39 | try expect(@sizeOf(U) == 0); |
| 40 | try expect(@alignOf(U) == 0); | |
| 40 | try expect(@alignOf(U) == 1); | |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | test "empty extern union" { |