Skip to content

Commit 8841315

Browse files
make PassMode::Cast consistently copy between Rust/ABI representation
Previously, we did this slightly incorrectly for return values, and didn't do it at all for arguments.
1 parent 74ef47e commit 8841315

File tree

2 files changed

+56
-44
lines changed

2 files changed

+56
-44
lines changed

compiler/rustc_codegen_llvm/src/abi.rs

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -211,47 +211,33 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
211211
bug!("unsized `ArgAbi` must be handled through `store_fn_arg`");
212212
}
213213
PassMode::Cast { cast, pad_i32: _ } => {
214-
// FIXME(eddyb): Figure out when the simpler Store is safe, clang
215-
// uses it for i16 -> {i8, i8}, but not for i24 -> {i8, i8, i8}.
216-
let can_store_through_cast_ptr = false;
217-
if can_store_through_cast_ptr {
218-
bx.store(val, dst.llval, self.layout.align.abi);
219-
} else {
220-
// The actual return type is a struct, but the ABI
221-
// adaptation code has cast it into some scalar type. The
222-
// code that follows is the only reliable way I have
223-
// found to do a transform like i64 -> {i32,i32}.
224-
// Basically we dump the data onto the stack then memcpy it.
225-
//
226-
// Other approaches I tried:
227-
// - Casting rust ret pointer to the foreign type and using Store
228-
// is (a) unsafe if size of foreign type > size of rust type and
229-
// (b) runs afoul of strict aliasing rules, yielding invalid
230-
// assembly under -O (specifically, the store gets removed).
231-
// - Truncating foreign type to correct integral type and then
232-
// bitcasting to the struct type yields invalid cast errors.
233-
234-
// We instead thus allocate some scratch space...
235-
let scratch_size = cast.size(bx);
236-
let scratch_align = cast.align(bx);
237-
let llscratch = bx.alloca(cast.llvm_type(bx), scratch_align);
238-
bx.lifetime_start(llscratch, scratch_size);
239-
240-
// ... where we first store the value...
241-
bx.store(val, llscratch, scratch_align);
242-
243-
// ... and then memcpy it to the intended destination.
244-
bx.memcpy(
245-
dst.llval,
246-
self.layout.align.abi,
247-
llscratch,
248-
scratch_align,
249-
bx.const_usize(self.layout.size.bytes()),
250-
MemFlags::empty(),
251-
);
252-
253-
bx.lifetime_end(llscratch, scratch_size);
254-
}
214+
// The ABI mandates that the value is passed as a different struct representation.
215+
// Spill and reload it from the stack to convert from the ABI representation to
216+
// the Rust representation.
217+
let scratch_size = cast.size(bx);
218+
let scratch_align = cast.align(bx);
219+
// Note that the ABI type may be either larger or smaller than the Rust type,
220+
// due to the presence or absence of trailing padding. For example:
221+
// - On some ABIs, the Rust layout { f64, f32, <f32 padding> } may omit padding
222+
// when passed by value, making it smaller.
223+
// - On some ABIs, the Rust layout { u16, u16, u16 } may be padded up to 8 bytes
224+
// when passed by value, making it larger.
225+
let copy_bytes = cmp::min(scratch_size.bytes(), self.layout.size.bytes());
226+
// Allocate some scratch space...
227+
let llscratch = bx.alloca(cast.llvm_type(bx), scratch_align);
228+
bx.lifetime_start(llscratch, scratch_size);
229+
// ...store the value...
230+
bx.store(val, llscratch, scratch_align);
231+
// ... and then memcpy it to the intended destination.
232+
bx.memcpy(
233+
dst.llval,
234+
self.layout.align.abi,
235+
llscratch,
236+
scratch_align,
237+
bx.const_usize(copy_bytes),
238+
MemFlags::empty(),
239+
);
240+
bx.lifetime_end(llscratch, scratch_size);
255241
}
256242
_ => {
257243
OperandRef::from_immediate_or_packed_pair(bx, val, self.layout).val.store(bx, dst);

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,9 +1471,35 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
14711471

14721472
if by_ref && !arg.is_indirect() {
14731473
// Have to load the argument, maybe while casting it.
1474-
if let PassMode::Cast { cast: ty, .. } = &arg.mode {
1475-
let llty = bx.cast_backend_type(ty);
1476-
llval = bx.load(llty, llval, align.min(arg.layout.align.abi));
1474+
if let PassMode::Cast { cast, pad_i32: _ } = &arg.mode {
1475+
// The ABI mandates that the value is passed as a different struct representation.
1476+
// Spill and reload it from the stack to convert from the Rust representation to
1477+
// the ABI representation.
1478+
let scratch_size = cast.size(bx);
1479+
let scratch_align = cast.align(bx);
1480+
// Note that the ABI type may be either larger or smaller than the Rust type,
1481+
// due to the presence or absence of trailing padding. For example:
1482+
// - On some ABIs, the Rust layout { f64, f32, <f32 padding> } may omit padding
1483+
// when passed by value, making it smaller.
1484+
// - On some ABIs, the Rust layout { u16, u16, u16 } may be padded up to 8 bytes
1485+
// when passed by value, making it larger.
1486+
let copy_bytes = cmp::min(scratch_size.bytes(), arg.layout.size.bytes());
1487+
// Allocate some scratch space...
1488+
let llscratch = bx.alloca(bx.cast_backend_type(cast), scratch_align);
1489+
bx.lifetime_start(llscratch, scratch_size);
1490+
// ...memcpy the value...
1491+
bx.memcpy(
1492+
llscratch,
1493+
scratch_align,
1494+
llval,
1495+
align,
1496+
bx.const_usize(copy_bytes),
1497+
MemFlags::empty(),
1498+
);
1499+
// ...and then load it with the ABI type.
1500+
let cast_ty = bx.cast_backend_type(cast);
1501+
llval = bx.load(cast_ty, llscratch, scratch_align);
1502+
bx.lifetime_end(llscratch, scratch_size);
14771503
} else {
14781504
// We can't use `PlaceRef::load` here because the argument
14791505
// may have a type we don't treat as immediate, but the ABI

0 commit comments

Comments
 (0)