authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-10-09 22:06:49-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-10-09 22:06:49-07:00
logaaf46187ab7bf23c87a275e2755a9bd01f3c93cf
tree7359cf8adeca3493e9e9865093ba68e0763d4ee1
parentfa08e49f839d47441af9a1bcf23dc1701849379e
parent703fe0f121df9df44721250528e508762e161780
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #17391 from xxxbxxx/load-i4

codegen/llvm: truncate padding bits when loading a non-byte-sized value

5 files changed, 321 insertions(+), 12 deletions(-)

src/arch/wasm/CodeGen.zig+13-3
......@@ -4353,9 +4353,21 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro
43534353 if (op_bits > 32 and op_bits <= 64 and wanted_bits == 32) {
43544354 try func.emitWValue(operand);
43554355 try func.addTag(.i32_wrap_i64);
4356 if (given.isSignedInt(mod) and wanted_bitsize < 32)
4357 return func.wrapOperand(.{ .stack = {} }, wanted)
4358 else
4359 return WValue{ .stack = {} };
43564360 } else if (op_bits == 32 and wanted_bits > 32 and wanted_bits <= 64) {
4357 try func.emitWValue(operand);
4361 const operand32 = if (given_bitsize < 32 and wanted.isSignedInt(mod))
4362 try func.signExtendInt(operand, given)
4363 else
4364 operand;
4365 try func.emitWValue(operand32);
43584366 try func.addTag(if (wanted.isSignedInt(mod)) .i64_extend_i32_s else .i64_extend_i32_u);
4367 if (given.isSignedInt(mod) and wanted_bitsize < 64)
4368 return func.wrapOperand(.{ .stack = {} }, wanted)
4369 else
4370 return WValue{ .stack = {} };
43594371 } else if (wanted_bits == 128) {
43604372 // for 128bit integers we store the integer in the virtual stack, rather than a local
43614373 const stack_ptr = try func.allocStack(wanted);
......@@ -4381,8 +4393,6 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro
43814393 }
43824394 return stack_ptr;
43834395 } else return func.load(operand, wanted, 0);
4384
4385 return WValue{ .stack = {} };
43864396}
43874397
43884398fn airIsNull(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode, op_kind: enum { value, ptr }) InnerError!void {
src/codegen/llvm.zig+55-8
......@@ -6179,7 +6179,6 @@ pub const FuncGen = struct {
61796179 const elem_alignment = elem_ty.abiAlignment(mod).toLlvm();
61806180 return self.loadByRef(elem_ptr, elem_ty, elem_alignment, .normal);
61816181 } else {
6182 const elem_llvm_ty = try o.lowerType(elem_ty);
61836182 if (Air.refToIndex(bin_op.lhs)) |lhs_index| {
61846183 if (self.air.instructions.items(.tag)[lhs_index] == .load) {
61856184 const load_data = self.air.instructions.items(.data)[lhs_index];
......@@ -6201,7 +6200,7 @@ pub const FuncGen = struct {
62016200 &indices,
62026201 "",
62036202 );
6204 return self.wip.load(.normal, elem_llvm_ty, gep, .default, "");
6203 return self.loadTruncate(.normal, elem_ty, gep, .default);
62056204 },
62066205 else => {},
62076206 }
......@@ -6210,7 +6209,7 @@ pub const FuncGen = struct {
62106209 }
62116210 const elem_ptr =
62126211 try self.wip.gep(.inbounds, array_llvm_ty, array_llvm_val, &indices, "");
6213 return self.wip.load(.normal, elem_llvm_ty, elem_ptr, .default, "");
6212 return self.loadTruncate(.normal, elem_ty, elem_ptr, .default);
62146213 }
62156214 }
62166215
......@@ -6378,13 +6377,12 @@ pub const FuncGen = struct {
63786377 const payload_index = @intFromBool(layout.tag_align.compare(.gte, layout.payload_align));
63796378 const field_ptr =
63806379 try self.wip.gepStruct(union_llvm_ty, struct_llvm_val, payload_index, "");
6381 const llvm_field_ty = try o.lowerType(field_ty);
63826380 const payload_alignment = layout.payload_align.toLlvm();
63836381 if (isByRef(field_ty, mod)) {
63846382 if (canElideLoad(self, body_tail)) return field_ptr;
63856383 return self.loadByRef(field_ptr, field_ty, payload_alignment, .normal);
63866384 } else {
6387 return self.wip.load(.normal, llvm_field_ty, field_ptr, payload_alignment, "");
6385 return self.loadTruncate(.normal, field_ty, field_ptr, payload_alignment);
63886386 }
63896387 },
63906388 else => unreachable,
......@@ -10219,8 +10217,7 @@ pub const FuncGen = struct {
1021910217
1022010218 return fg.loadByRef(payload_ptr, payload_ty, payload_alignment, .normal);
1022110219 }
10222 const payload_llvm_ty = try o.lowerType(payload_ty);
10223 return fg.wip.load(.normal, payload_llvm_ty, payload_ptr, payload_alignment, "");
10220 return fg.loadTruncate(.normal, payload_ty, payload_ptr, payload_alignment);
1022410221 }
1022510222
1022610223 assert(!isByRef(payload_ty, mod));
......@@ -10321,6 +10318,56 @@ pub const FuncGen = struct {
1032110318 }
1032210319 }
1032310320
10321 /// Load a value and, if needed, mask out padding bits for non byte-sized integer values.
10322 fn loadTruncate(
10323 fg: *FuncGen,
10324 access_kind: Builder.MemoryAccessKind,
10325 payload_ty: Type,
10326 payload_ptr: Builder.Value,
10327 payload_alignment: Builder.Alignment,
10328 ) !Builder.Value {
10329 // from https://llvm.org/docs/LangRef.html#load-instruction :
10330 // "When loading a value of a type like i20 with a size that is not an integral number of bytes, the result is undefined if the value was not originally written using a store of the same type. "
10331 // => so load the byte aligned value and trunc the unwanted bits.
10332
10333 const o = fg.dg.object;
10334 const mod = o.module;
10335 const payload_llvm_ty = try o.lowerType(payload_ty);
10336 const abi_size = payload_ty.abiSize(mod);
10337
10338 // llvm bug workarounds:
10339 const workaround_explicit_mask = o.target.cpu.arch == .powerpc and abi_size >= 4;
10340 const workaround_disable_truncate = o.target.cpu.arch == .wasm32 and abi_size >= 4;
10341
10342 if (workaround_disable_truncate) {
10343 // see https://github.com/llvm/llvm-project/issues/64222
10344 // disable the truncation codepath for larger that 32bits value - with this heuristic, the backend passes the test suite.
10345 return try fg.wip.load(access_kind, payload_llvm_ty, payload_ptr, payload_alignment, "");
10346 }
10347
10348 const load_llvm_ty = if (payload_ty.isAbiInt(mod))
10349 try o.builder.intType(@intCast(abi_size * 8))
10350 else
10351 payload_llvm_ty;
10352 const loaded = try fg.wip.load(access_kind, load_llvm_ty, payload_ptr, payload_alignment, "");
10353 const shifted = if (payload_llvm_ty != load_llvm_ty and o.target.cpu.arch.endian() == .Big)
10354 try fg.wip.bin(.lshr, loaded, try o.builder.intValue(
10355 load_llvm_ty,
10356 (payload_ty.abiSize(mod) - (std.math.divCeil(u64, payload_ty.bitSize(mod), 8) catch unreachable)) * 8,
10357 ), "")
10358 else
10359 loaded;
10360
10361 const anded = if (workaround_explicit_mask and payload_llvm_ty != load_llvm_ty) blk: {
10362 // this is rendundant with llvm.trunc. But without it, llvm17 emits invalid code for powerpc.
10363 var mask_val = try o.builder.intConst(payload_llvm_ty, -1);
10364 mask_val = try o.builder.castConst(.zext, mask_val, load_llvm_ty);
10365 break :blk try fg.wip.bin(.@"and", shifted, mask_val.toValue(), "");
10366 } else shifted;
10367
10368 return fg.wip.conv(.unneeded, anded, payload_llvm_ty, "");
10369 }
10370
1032410371 /// Load a by-ref type by constructing a new alloca and performing a memcpy.
1032510372 fn loadByRef(
1032610373 fg: *FuncGen,
......@@ -10378,7 +10425,7 @@ pub const FuncGen = struct {
1037810425 if (isByRef(elem_ty, mod)) {
1037910426 return self.loadByRef(ptr, elem_ty, ptr_alignment, access_kind);
1038010427 }
10381 return self.wip.load(access_kind, try o.lowerType(elem_ty), ptr, ptr_alignment, "");
10428 return self.loadTruncate(access_kind, elem_ty, ptr, ptr_alignment);
1038210429 }
1038310430
1038410431 const containing_int_ty = try o.builder.intType(@intCast(info.packed_offset.host_size * 8));
test/behavior/cast_int.zig+195
......@@ -1,6 +1,7 @@
11const builtin = @import("builtin");
22const std = @import("std");
33const expect = std.testing.expect;
4const expectEqual = std.testing.expectEqual;
45const maxInt = std.math.maxInt;
56
67test "@intCast i32 to u7" {
......@@ -28,3 +29,197 @@ test "coerce i8 to i32 and @intCast back" {
2829 var y2: i8 = -5;
2930 try expect(y2 == @as(i8, @intCast(x2)));
3031}
32
33test "coerce non byte-sized integers accross 32bits boundary" {
34 {
35 var v: u21 = 6417;
36 const a: u32 = v;
37 const b: u64 = v;
38 const c: u64 = a;
39 var w: u64 = 0x1234567812345678;
40 const d: u21 = @truncate(w);
41 const e: u60 = d;
42 try expectEqual(@as(u32, 6417), a);
43 try expectEqual(@as(u64, 6417), b);
44 try expectEqual(@as(u64, 6417), c);
45 try expectEqual(@as(u21, 0x145678), d);
46 try expectEqual(@as(u60, 0x145678), e);
47 }
48
49 {
50 var v: u10 = 234;
51 const a: u32 = v;
52 const b: u64 = v;
53 const c: u64 = a;
54 var w: u64 = 0x1234567812345678;
55 const d: u10 = @truncate(w);
56 const e: u60 = d;
57 try expectEqual(@as(u32, 234), a);
58 try expectEqual(@as(u64, 234), b);
59 try expectEqual(@as(u64, 234), c);
60 try expectEqual(@as(u21, 0x278), d);
61 try expectEqual(@as(u60, 0x278), e);
62 }
63 {
64 var v: u7 = 11;
65 const a: u32 = v;
66 const b: u64 = v;
67 const c: u64 = a;
68 var w: u64 = 0x1234567812345678;
69 const d: u7 = @truncate(w);
70 const e: u60 = d;
71 try expectEqual(@as(u32, 11), a);
72 try expectEqual(@as(u64, 11), b);
73 try expectEqual(@as(u64, 11), c);
74 try expectEqual(@as(u21, 0x78), d);
75 try expectEqual(@as(u60, 0x78), e);
76 }
77
78 {
79 var v: i21 = -6417;
80 const a: i32 = v;
81 const b: i64 = v;
82 const c: i64 = a;
83 var w: i64 = -12345;
84 const d: i21 = @intCast(w);
85 const e: i60 = d;
86 try expectEqual(@as(i32, -6417), a);
87 try expectEqual(@as(i64, -6417), b);
88 try expectEqual(@as(i64, -6417), c);
89 try expectEqual(@as(i21, -12345), d);
90 try expectEqual(@as(i60, -12345), e);
91 }
92
93 {
94 var v: i10 = -234;
95 const a: i32 = v;
96 const b: i64 = v;
97 const c: i64 = a;
98 var w: i64 = -456;
99 const d: i10 = @intCast(w);
100 const e: i60 = d;
101 try expectEqual(@as(i32, -234), a);
102 try expectEqual(@as(i64, -234), b);
103 try expectEqual(@as(i64, -234), c);
104 try expectEqual(@as(i10, -456), d);
105 try expectEqual(@as(i60, -456), e);
106 }
107 {
108 var v: i7 = -11;
109 const a: i32 = v;
110 const b: i64 = v;
111 const c: i64 = a;
112 var w: i64 = -42;
113 const d: i7 = @intCast(w);
114 const e: i60 = d;
115 try expectEqual(@as(i32, -11), a);
116 try expectEqual(@as(i64, -11), b);
117 try expectEqual(@as(i64, -11), c);
118 try expectEqual(@as(i7, -42), d);
119 try expectEqual(@as(i60, -42), e);
120 }
121}
122
123const Piece = packed struct {
124 color: Color,
125 type: Type,
126
127 const Type = enum { KING, QUEEN, BISHOP, KNIGHT, ROOK, PAWN };
128 const Color = enum { WHITE, BLACK };
129
130 fn charToPiece(c: u8) !@This() {
131 return .{
132 .type = try charToPieceType(c),
133 .color = if (std.ascii.isUpper(c)) Color.WHITE else Color.BLACK,
134 };
135 }
136
137 fn charToPieceType(c: u8) !Type {
138 return switch (std.ascii.toLower(c)) {
139 'p' => .PAWN,
140 'k' => .KING,
141 'q' => .QUEEN,
142 'b' => .BISHOP,
143 'n' => .KNIGHT,
144 'r' => .ROOK,
145 else => error.UnexpectedCharError,
146 };
147 }
148};
149
150test "load non byte-sized optional value" {
151 // Originally reported at https://github.com/ziglang/zig/issues/14200
152 // note: this bug is triggered by the == operator, expectEqual will hide it
153 var opt: ?Piece = try Piece.charToPiece('p');
154 try expect(opt.?.type == .PAWN);
155 try expect(opt.?.color == .BLACK);
156
157 var p: Piece = undefined;
158 @as(*u8, @ptrCast(&p)).* = 0b11111011;
159 try expect(p.type == .PAWN);
160 try expect(p.color == .BLACK);
161}
162
163test "load non byte-sized value in struct" {
164 if (builtin.cpu.arch.endian() != .Little) return error.SkipZigTest; // packed struct TODO
165
166 // note: this bug is triggered by the == operator, expectEqual will hide it
167 // using ptrCast not to depend on unitialised memory state
168
169 var struct0: struct {
170 p: Piece,
171 int: u8,
172 } = undefined;
173 @as(*u8, @ptrCast(&struct0.p)).* = 0b11111011;
174 try expect(struct0.p.type == .PAWN);
175 try expect(struct0.p.color == .BLACK);
176
177 var struct1: packed struct {
178 p0: Piece,
179 p1: Piece,
180 pad: u1,
181 p2: Piece,
182 } = undefined;
183 @as(*u8, @ptrCast(&struct1.p0)).* = 0b11111011;
184 struct1.p1 = try Piece.charToPiece('p');
185 struct1.p2 = try Piece.charToPiece('p');
186 try expect(struct1.p0.type == .PAWN);
187 try expect(struct1.p0.color == .BLACK);
188 try expect(struct1.p1.type == .PAWN);
189 try expect(struct1.p1.color == .BLACK);
190 try expect(struct1.p2.type == .PAWN);
191 try expect(struct1.p2.color == .BLACK);
192}
193
194test "load non byte-sized value in union" {
195 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
196 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
197 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
198 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
199 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
200 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
201
202 // note: this bug is triggered by the == operator, expectEqual will hide it
203 // using ptrCast not to depend on unitialised memory state
204
205 var union0: packed union {
206 p: Piece,
207 int: u8,
208 } = .{ .int = 0 };
209 union0.int = 0b11111011;
210 try expect(union0.p.type == .PAWN);
211 try expect(union0.p.color == .BLACK);
212
213 var union1: union {
214 p: Piece,
215 int: u8,
216 } = .{ .p = .{ .color = .WHITE, .type = .KING } };
217 @as(*u8, @ptrCast(&union1.p)).* = 0b11111011;
218 try expect(union1.p.type == .PAWN);
219 try expect(union1.p.color == .BLACK);
220
221 var pieces: [3]Piece = undefined;
222 @as(*u8, @ptrCast(&pieces[1])).* = 0b11111011;
223 try expect(pieces[1].type == .PAWN);
224 try expect(pieces[1].color == .BLACK);
225}
test/behavior/packed-struct.zig+30-1
......@@ -991,7 +991,7 @@ test "bitcast back and forth" {
991991
992992test "field access of packed struct smaller than its abi size inside struct initialized with rls" {
993993 // Originally reported at https://github.com/ziglang/zig/issues/14200
994 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .arm) return error.SkipZigTest;
994
995995 const S = struct {
996996 ps: packed struct { x: i2, y: i2 },
997997
......@@ -1036,3 +1036,32 @@ test "modify nested packed struct aligned field" {
10361036 try std.testing.expectEqual(@as(u8, 1), opts.pretty_print.indent);
10371037 try std.testing.expect(!opts.baz);
10381038}
1039
1040test "assigning packed struct inside another packed struct" {
1041 // Originally reported at https://github.com/ziglang/zig/issues/9674
1042
1043 const S = struct {
1044 const Inner = packed struct {
1045 bits: u3,
1046 more_bits: u6,
1047 };
1048
1049 const Outer = packed struct {
1050 padding: u5,
1051 inner: Inner,
1052 };
1053 fn t(inner: Inner) void {
1054 r.inner = inner;
1055 }
1056
1057 var mem: Outer = undefined;
1058 var r: *volatile Outer = &mem;
1059 };
1060
1061 const val = S.Inner{ .bits = 1, .more_bits = 11 };
1062 S.mem.padding = 0;
1063 S.t(val);
1064
1065 try expectEqual(val, S.mem.inner);
1066 try expect(S.mem.padding == 0);
1067}
test/behavior/packed-union.zig+28
......@@ -85,3 +85,31 @@ test "flags in packed union at offset" {
8585 try expectEqual(false, test_bits.adv_flags.adv.flags.enable_1);
8686 try expectEqual(false, test_bits.adv_flags.adv.flags.enable_2);
8787}
88
89test "packed union in packed struct" {
90 // Originally reported at https://github.com/ziglang/zig/issues/16581
91
92 const ReadRequest = packed struct { key: i32 };
93 const RequestType = enum {
94 read,
95 insert,
96 };
97 const RequestUnion = packed union {
98 read: ReadRequest,
99 };
100
101 const Request = packed struct {
102 active_type: RequestType,
103 request: RequestUnion,
104 const Self = @This();
105
106 fn init(read: ReadRequest) Self {
107 return .{
108 .active_type = .read,
109 .request = RequestUnion{ .read = read },
110 };
111 }
112 };
113
114 try std.testing.expectEqual(RequestType.read, Request.init(.{ .key = 3 }).active_type);
115}