authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-03 23:44:32-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-04 17:34:16-07:00
log259f784241fb44e0a1b570daaf31ba2b9f164106
treeff024852310df27dfad242c12af8d8a77a290988
parent080e870a717124204cbfa58a1f29061a701b0362

stage2: improve `@sizeOf` and `@alignOf` integers

Prior to this commit, the logic for ABI size and ABI alignment for integers was naive and incorrect. This results in wasted hardware as well as undefined behavior in the LLVM backend when we memset an incorrect number of bytes to 0xaa due to disagreeing with LLVM about the ABI size of integers. This commit introduces a "max int align" value which is different per Target. This value is used to derive the ABI size and alignment of all integers. This commit makes an interesting change from stage1, which treats 128-bit integers as 16-bytes aligned for x86_64-linux. stage1 is incorrect. The maximum integer alignment on this system is only 8 bytes. This change breaks the behavior test called "128-bit cmpxchg" because on that target, 128-bit cmpxchg does require a 16-bytes aligned pointer to a 128 bit integer. However, this alignment property does not belong on *all* 128 bit integers - only on the pointer type in the `@cmpxchg` builtin function prototype. The user can then use an alignment override annotation on a 128-bit integer variable or struct field to obtain such a pointer.

6 files changed, 212 insertions(+), 61 deletions(-)

lib/std/target.zig+77
......@@ -1773,6 +1773,83 @@ pub const Target = struct {
17731773 else => false,
17741774 };
17751775 }
1776
1777 pub inline fn maxIntAlignment(target: Target) u16 {
1778 return switch (target.cpu.arch) {
1779 .avr => 1,
1780 .msp430 => 2,
1781 .xcore => 4,
1782
1783 .arm,
1784 .armeb,
1785 .thumb,
1786 .thumbeb,
1787 .x86_64,
1788 .hexagon,
1789 .mips,
1790 .mipsel,
1791 .mips64,
1792 .mips64el,
1793 .powerpc,
1794 .powerpcle,
1795 .powerpc64,
1796 .powerpc64le,
1797 .r600,
1798 .amdgcn,
1799 .riscv32,
1800 .riscv64,
1801 .sparc,
1802 .sparcv9,
1803 .sparcel,
1804 .s390x,
1805 .lanai,
1806 .wasm32,
1807 .wasm64,
1808 => 8,
1809
1810 .i386 => return switch (target.os.tag) {
1811 .windows => 8,
1812 else => 4,
1813 },
1814 .aarch64,
1815 .aarch64_be,
1816 .aarch64_32,
1817 .bpfel,
1818 .bpfeb,
1819 .nvptx,
1820 .nvptx64,
1821 => 16,
1822
1823 // Below this comment are unverified and I have chosen a number
1824 // based on ptrBitWidth.
1825
1826 .spu_2 => 2,
1827
1828 .csky,
1829 .arc,
1830 .m68k,
1831 .tce,
1832 .tcele,
1833 .le32,
1834 .amdil,
1835 .hsail,
1836 .spir,
1837 .kalimba,
1838 .renderscript32,
1839 .spirv32,
1840 .shave,
1841 => 4,
1842
1843 .le64,
1844 .amdil64,
1845 .hsail64,
1846 .spir64,
1847 .renderscript64,
1848 .ve,
1849 .spirv64,
1850 => 8,
1851 };
1852 }
17761853};
17771854
17781855test {
src/codegen/llvm.zig+1-1
......@@ -7583,7 +7583,7 @@ pub const FuncGen = struct {
75837583 const size_bytes = elem_ty.abiSize(target);
75847584 _ = self.builder.buildMemCpy(
75857585 self.builder.buildBitCast(ptr, llvm_ptr_u8, ""),
7586 ptr_ty.ptrAlignment(target),
7586 ptr_alignment,
75877587 self.builder.buildBitCast(elem, llvm_ptr_u8, ""),
75887588 elem_ty.abiAlignment(target),
75897589 self.context.intType(Type.usize.intInfo(target).bits).constInt(size_bytes, .False),
src/type.zig+23-15
......@@ -2788,11 +2788,6 @@ pub const Type = extern union {
27882788 return AbiAlignmentAdvanced{ .scalar = target_util.defaultFunctionAlignment(target) };
27892789 },
27902790
2791 .i16, .u16 => return AbiAlignmentAdvanced{ .scalar = 2 },
2792 .i32, .u32 => return AbiAlignmentAdvanced{ .scalar = 4 },
2793 .i64, .u64 => return AbiAlignmentAdvanced{ .scalar = 8 },
2794 .u128, .i128 => return AbiAlignmentAdvanced{ .scalar = 16 },
2795
27962791 .isize,
27972792 .usize,
27982793 .single_const_pointer_to_comptime_int,
......@@ -2865,14 +2860,15 @@ pub const Type = extern union {
28652860 // ABI alignment of vectors?
28662861 .vector => return AbiAlignmentAdvanced{ .scalar = 16 },
28672862
2863 .i16, .u16 => return AbiAlignmentAdvanced{ .scalar = intAbiAlignment(16, target) },
2864 .i32, .u32 => return AbiAlignmentAdvanced{ .scalar = intAbiAlignment(32, target) },
2865 .i64, .u64 => return AbiAlignmentAdvanced{ .scalar = intAbiAlignment(64, target) },
2866 .u128, .i128 => return AbiAlignmentAdvanced{ .scalar = intAbiAlignment(128, target) },
2867
28682868 .int_signed, .int_unsigned => {
28692869 const bits: u16 = ty.cast(Payload.Bits).?.data;
28702870 if (bits == 0) return AbiAlignmentAdvanced{ .scalar = 0 };
2871 if (bits <= 8) return AbiAlignmentAdvanced{ .scalar = 1 };
2872 if (bits <= 16) return AbiAlignmentAdvanced{ .scalar = 2 };
2873 if (bits <= 32) return AbiAlignmentAdvanced{ .scalar = 4 };
2874 if (bits <= 64) return AbiAlignmentAdvanced{ .scalar = 8 };
2875 return AbiAlignmentAdvanced{ .scalar = 16 };
2871 return AbiAlignmentAdvanced{ .scalar = intAbiAlignment(bits, target) };
28762872 },
28772873
28782874 .optional => {
......@@ -3113,10 +3109,6 @@ pub const Type = extern union {
31133109 assert(elem_size >= payload.elem_type.abiAlignment(target));
31143110 return (payload.len + 1) * elem_size;
31153111 },
3116 .i16, .u16 => return 2,
3117 .i32, .u32 => return 4,
3118 .i64, .u64 => return 8,
3119 .u128, .i128 => return 16,
31203112
31213113 .isize,
31223114 .usize,
......@@ -3189,10 +3181,14 @@ pub const Type = extern union {
31893181 .error_set_merged,
31903182 => return 2, // TODO revisit this when we have the concept of the error tag type
31913183
3184 .i16, .u16 => return intAbiSize(16, target),
3185 .i32, .u32 => return intAbiSize(32, target),
3186 .i64, .u64 => return intAbiSize(64, target),
3187 .u128, .i128 => return intAbiSize(128, target),
31923188 .int_signed, .int_unsigned => {
31933189 const bits: u16 = self.cast(Payload.Bits).?.data;
31943190 if (bits == 0) return 0;
3195 return std.math.ceilPowerOfTwoPromote(u16, (bits + 7) / 8);
3191 return intAbiSize(bits, target);
31963192 },
31973193
31983194 .optional => {
......@@ -3234,6 +3230,18 @@ pub const Type = extern union {
32343230 };
32353231 }
32363232
3233 fn intAbiSize(bits: u16, target: Target) u64 {
3234 const alignment = intAbiAlignment(bits, target);
3235 return std.mem.alignForwardGeneric(u64, (bits + 7) / 8, alignment);
3236 }
3237
3238 fn intAbiAlignment(bits: u16, target: Target) u32 {
3239 return @minimum(
3240 std.math.ceilPowerOfTwoPromote(u16, (bits + 7) / 8),
3241 target.maxIntAlignment(),
3242 );
3243 }
3244
32373245 /// Asserts the type has the bit size already resolved.
32383246 pub fn bitSize(ty: Type, target: Target) u64 {
32393247 return switch (ty.tag()) {
test/behavior/align.zig+105-27
......@@ -47,41 +47,121 @@ fn expects4(x: *align(4) u32) void {
4747 x.* += 1;
4848}
4949
50test "alignment of structs" {
50test "alignment of struct with pointer has same alignment as usize" {
5151 try expect(@alignOf(struct {
5252 a: i32,
5353 b: *i32,
5454 }) == @alignOf(usize));
5555}
5656
57test "alignment of >= 128-bit integer type" {
58 try expect(@alignOf(u128) == 16);
59 try expect(@alignOf(u129) == 16);
60}
61
62test "alignment of struct with 128-bit field" {
63 try expect(@alignOf(struct {
57test "alignment and size of structs with 128-bit fields" {
58 const A = struct {
6459 x: u128,
65 }) == 16);
66
67 comptime {
68 try expect(@alignOf(struct {
69 x: u128,
70 }) == 16);
71 }
72}
73
74test "size of extern struct with 128-bit field" {
75 try expect(@sizeOf(extern struct {
60 };
61 const B = extern struct {
7662 x: u128,
7763 y: u8,
78 }) == 32);
79
64 };
65 const expected = switch (builtin.cpu.arch) {
66 .arm,
67 .armeb,
68 .thumb,
69 .thumbeb,
70 .x86_64,
71 .hexagon,
72 .mips,
73 .mipsel,
74 .mips64,
75 .mips64el,
76 .powerpc,
77 .powerpcle,
78 .powerpc64,
79 .powerpc64le,
80 .r600,
81 .amdgcn,
82 .riscv32,
83 .riscv64,
84 .sparc,
85 .sparcv9,
86 .sparcel,
87 .s390x,
88 .lanai,
89 .wasm32,
90 .wasm64,
91 => .{
92 .a_align = 8,
93 .a_size = 16,
94
95 .b_align = 8,
96 .b_size = 24,
97
98 .u128_align = 8,
99 .u128_size = 16,
100 .u129_align = 8,
101 .u129_size = 24,
102 },
103
104 .i386 => switch (builtin.os.tag) {
105 .windows => .{
106 .a_align = 8,
107 .a_size = 16,
108
109 .b_align = 8,
110 .b_size = 24,
111
112 .u128_align = 8,
113 .u128_size = 16,
114 .u129_align = 8,
115 .u129_size = 24,
116 },
117 else => .{
118 .a_align = 4,
119 .a_size = 16,
120
121 .b_align = 4,
122 .b_size = 20,
123
124 .u128_align = 4,
125 .u128_size = 16,
126 .u129_align = 4,
127 .u129_size = 20,
128 },
129 },
130
131 .aarch64,
132 .aarch64_be,
133 .aarch64_32,
134 .bpfel,
135 .bpfeb,
136 .nvptx,
137 .nvptx64,
138 => .{
139 .a_align = 16,
140 .a_size = 16,
141
142 .b_align = 16,
143 .b_size = 32,
144
145 .u128_align = 16,
146 .u128_size = 16,
147 .u129_align = 16,
148 .u129_size = 32,
149 },
150
151 else => return error.SkipZigTest,
152 };
80153 comptime {
81 try expect(@sizeOf(extern struct {
82 x: u128,
83 y: u8,
84 }) == 32);
154 std.debug.assert(@alignOf(A) == expected.a_align);
155 std.debug.assert(@sizeOf(A) == expected.a_size);
156
157 std.debug.assert(@alignOf(B) == expected.b_align);
158 std.debug.assert(@sizeOf(B) == expected.b_size);
159
160 std.debug.assert(@alignOf(u128) == expected.u128_align);
161 std.debug.assert(@sizeOf(u128) == expected.u128_size);
162
163 std.debug.assert(@alignOf(u129) == expected.u129_align);
164 std.debug.assert(@sizeOf(u129) == expected.u129_size);
85165 }
86166}
87167
......@@ -328,7 +408,6 @@ test "read 128-bit field from default aligned struct in stack memory" {
328408 .nevermind = 1,
329409 .badguy = 12,
330410 };
331 try expect((@ptrToInt(&default_aligned.badguy) % 16) == 0);
332411 try expect(12 == default_aligned.badguy);
333412}
334413
......@@ -345,7 +424,6 @@ test "read 128-bit field from default aligned struct in global memory" {
345424 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
346425 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
347426
348 try expect((@ptrToInt(&default_aligned_global.badguy) % 16) == 0);
349427 try expect(12 == default_aligned_global.badguy);
350428}
351429
test/behavior/bitcast.zig+3-12
......@@ -138,18 +138,9 @@ test "@bitCast packed structs at runtime and comptime" {
138138 fn doTheTest() !void {
139139 var full = Full{ .number = 0x1234 };
140140 var two_halves = @bitCast(Divided, full);
141 switch (native_endian) {
142 .Big => {
143 try expect(two_halves.half1 == 0x12);
144 try expect(two_halves.quarter3 == 0x3);
145 try expect(two_halves.quarter4 == 0x4);
146 },
147 .Little => {
148 try expect(two_halves.half1 == 0x34);
149 try expect(two_halves.quarter3 == 0x2);
150 try expect(two_halves.quarter4 == 0x1);
151 },
152 }
141 try expect(two_halves.half1 == 0x34);
142 try expect(two_halves.quarter3 == 0x2);
143 try expect(two_halves.quarter4 == 0x1);
153144 }
154145 };
155146 try S.doTheTest();
test/behavior/struct.zig+3-6
......@@ -499,17 +499,14 @@ const Bitfields = packed struct {
499499 f7: u8,
500500};
501501
502test "native bit field understands endianness" {
503 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
502test "packed struct fields are ordered from LSB to MSB" {
503 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
504504 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
505505 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
506506 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
507507 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
508508
509 var all: u64 = if (native_endian != .Little)
510 0x1111222233445677
511 else
512 0x7765443322221111;
509 var all: u64 = 0x7765443322221111;
513510 var bytes: [8]u8 = undefined;
514511 @memcpy(&bytes, @ptrCast([*]u8, &all), 8);
515512 var bitfields = @ptrCast(*Bitfields, &bytes).*;