authorgravatar for xavierb@gmail.comXavier Bouchoux <xavierb@gmail.com> 2023-10-08 11:35:33+02:00
committergravatar for xavierb@gmail.comXavier Bouchoux <xavierb@gmail.com> 2023-10-08 11:37:49+02:00
log370662c565ba541157e3c69d12625c28787d642e
tree066de0ff7c0e27aeeb154b795c90096ae87028d5
parent6bb10c72712377fb38764a2df0c7abfebcebf3ad

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


3 files changed, 139 insertions(+), 23 deletions(-)

src/codegen/llvm.zig+35-22
...@@ -6179,7 +6179,6 @@ pub const FuncGen = struct {...@@ -6179,7 +6179,6 @@ pub const FuncGen = struct {
6179 const elem_alignment = elem_ty.abiAlignment(mod).toLlvm();6179 const elem_alignment = elem_ty.abiAlignment(mod).toLlvm();
6180 return self.loadByRef(elem_ptr, elem_ty, elem_alignment, .normal);6180 return self.loadByRef(elem_ptr, elem_ty, elem_alignment, .normal);
6181 } else {6181 } else {
6182 const elem_llvm_ty = try o.lowerType(elem_ty);
6183 if (Air.refToIndex(bin_op.lhs)) |lhs_index| {6182 if (Air.refToIndex(bin_op.lhs)) |lhs_index| {
6184 if (self.air.instructions.items(.tag)[lhs_index] == .load) {6183 if (self.air.instructions.items(.tag)[lhs_index] == .load) {
6185 const load_data = self.air.instructions.items(.data)[lhs_index];6184 const load_data = self.air.instructions.items(.data)[lhs_index];
...@@ -6201,7 +6200,7 @@ pub const FuncGen = struct {...@@ -6201,7 +6200,7 @@ pub const FuncGen = struct {
6201 &indices,6200 &indices,
6202 "",6201 "",
6203 );6202 );
6204 return self.wip.load(.normal, elem_llvm_ty, gep, .default, "");6203 return self.loadTruncate(.normal, elem_ty, gep, .default);
6205 },6204 },
6206 else => {},6205 else => {},
6207 }6206 }
...@@ -6210,7 +6209,7 @@ pub const FuncGen = struct {...@@ -6210,7 +6209,7 @@ pub const FuncGen = struct {
6210 }6209 }
6211 const elem_ptr =6210 const elem_ptr =
6212 try self.wip.gep(.inbounds, array_llvm_ty, array_llvm_val, &indices, "");6211 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);
6214 }6213 }
6215 }6214 }
62166215
...@@ -6378,13 +6377,12 @@ pub const FuncGen = struct {...@@ -6378,13 +6377,12 @@ pub const FuncGen = struct {
6378 const payload_index = @intFromBool(layout.tag_align.compare(.gte, layout.payload_align));6377 const payload_index = @intFromBool(layout.tag_align.compare(.gte, layout.payload_align));
6379 const field_ptr =6378 const field_ptr =
6380 try self.wip.gepStruct(union_llvm_ty, struct_llvm_val, payload_index, "");6379 try self.wip.gepStruct(union_llvm_ty, struct_llvm_val, payload_index, "");
6381 const llvm_field_ty = try o.lowerType(field_ty);
6382 const payload_alignment = layout.payload_align.toLlvm();6380 const payload_alignment = layout.payload_align.toLlvm();
6383 if (isByRef(field_ty, mod)) {6381 if (isByRef(field_ty, mod)) {
6384 if (canElideLoad(self, body_tail)) return field_ptr;6382 if (canElideLoad(self, body_tail)) return field_ptr;
6385 return self.loadByRef(field_ptr, field_ty, payload_alignment, .normal);6383 return self.loadByRef(field_ptr, field_ty, payload_alignment, .normal);
6386 } else {6384 } 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);
6388 }6386 }
6389 },6387 },
6390 else => unreachable,6388 else => unreachable,
...@@ -10219,8 +10217,7 @@ pub const FuncGen = struct {...@@ -10219,8 +10217,7 @@ pub const FuncGen = struct {
1021910217
10220 return fg.loadByRef(payload_ptr, payload_ty, payload_alignment, .normal);10218 return fg.loadByRef(payload_ptr, payload_ty, payload_alignment, .normal);
10221 }10219 }
10222 const payload_llvm_ty = try o.lowerType(payload_ty);10220 return fg.loadTruncate(.normal, payload_ty, payload_ptr, payload_alignment);
10223 return fg.wip.load(.normal, payload_llvm_ty, payload_ptr, payload_alignment, "");
10224 }10221 }
1022510222
10226 assert(!isByRef(payload_ty, mod));10223 assert(!isByRef(payload_ty, mod));
...@@ -10321,6 +10318,36 @@ pub const FuncGen = struct {...@@ -10321,6 +10318,36 @@ pub const FuncGen = struct {
10321 }10318 }
10322 }10319 }
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 load_llvm_ty = if (payload_ty.isAbiInt(mod))
10337 try o.builder.intType(@intCast(payload_ty.abiSize(mod) * 8))
10338 else
10339 payload_llvm_ty;
10340 const loaded = try fg.wip.load(access_kind, load_llvm_ty, payload_ptr, payload_alignment, "");
10341 const shifted = if (payload_llvm_ty != load_llvm_ty and o.target.cpu.arch.endian() == .Big)
10342 try fg.wip.bin(.lshr, loaded, try o.builder.intValue(
10343 load_llvm_ty,
10344 (payload_ty.abiSize(mod) - (std.math.divCeil(u64, payload_ty.bitSize(mod), 8) catch unreachable)) * 8,
10345 ), "")
10346 else
10347 loaded;
10348 return fg.wip.conv(.unneeded, shifted, payload_llvm_ty, "");
10349 }
10350
10324 /// Load a by-ref type by constructing a new alloca and performing a memcpy.10351 /// Load a by-ref type by constructing a new alloca and performing a memcpy.
10325 fn loadByRef(10352 fn loadByRef(
10326 fg: *FuncGen,10353 fg: *FuncGen,
...@@ -10378,21 +10405,7 @@ pub const FuncGen = struct {...@@ -10378,21 +10405,7 @@ pub const FuncGen = struct {
10378 if (isByRef(elem_ty, mod)) {10405 if (isByRef(elem_ty, mod)) {
10379 return self.loadByRef(ptr, elem_ty, ptr_alignment, access_kind);10406 return self.loadByRef(ptr, elem_ty, ptr_alignment, access_kind);
10380 }10407 }
10381 const llvm_elem_ty = try o.lowerType(elem_ty);10408 return self.loadTruncate(access_kind, elem_ty, ptr, ptr_alignment);
10382 const llvm_load_ty = if (elem_ty.isAbiInt(mod))
10383 try o.builder.intType(@intCast(elem_ty.abiSize(mod) * 8))
10384 else
10385 llvm_elem_ty;
10386 const loaded = try self.wip.load(access_kind, llvm_load_ty, ptr, ptr_alignment, "");
10387 const shifted = if (llvm_elem_ty != llvm_load_ty and o.target.cpu.arch.endian() == .Big)
10388 try self.wip.bin(.lshr, loaded, try o.builder.intValue(
10389 llvm_load_ty,
10390 (elem_ty.abiSize(mod) - (std.math.divCeil(u64, elem_ty.bitSize(mod), 8) catch
10391 unreachable)) * 8,
10392 ), "")
10393 else
10394 loaded;
10395 return self.wip.conv(.unneeded, shifted, llvm_elem_ty, "");
10396 }10409 }
1039710410
10398 const containing_int_ty = try o.builder.intType(@intCast(info.packed_offset.host_size * 8));10411 const containing_int_ty = try o.builder.intType(@intCast(info.packed_offset.host_size * 8));
test/behavior/cast_int.zig+103
...@@ -120,3 +120,106 @@ test "coerce non byte-sized integers accross 32bits boundary" {...@@ -120,3 +120,106 @@ test "coerce non byte-sized integers accross 32bits boundary" {
120 }120 }
121}121}
122122
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+1-1
...@@ -991,7 +991,7 @@ test "bitcast back and forth" {...@@ -991,7 +991,7 @@ test "bitcast back and forth" {
991991
992test "field access of packed struct smaller than its abi size inside struct initialized with rls" {992test "field access of packed struct smaller than its abi size inside struct initialized with rls" {
993 // Originally reported at https://github.com/ziglang/zig/issues/14200993 // 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
995 const S = struct {995 const S = struct {
996 ps: packed struct { x: i2, y: i2 },996 ps: packed struct { x: i2, y: i2 },
997997