Skip to content

Commit bc8415b

Browse files
authored
Rollup merge of #122619 - erikdesjardins:cast, r=compiler-errors
Fix some unsoundness with PassMode::Cast ABI Fixes #122617 Reviewable commit-by-commit. More info in each commit message.
2 parents 32c8c5c + dec81ac commit bc8415b

File tree

8 files changed

+476
-89
lines changed

8 files changed

+476
-89
lines changed

compiler/rustc_codegen_llvm/src/abi.rs

+51-69
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ pub use rustc_middle::ty::layout::{FAT_PTR_ADDR, FAT_PTR_EXTRA};
1616
use rustc_middle::ty::Ty;
1717
use rustc_session::config;
1818
pub use rustc_target::abi::call::*;
19-
use rustc_target::abi::{self, HasDataLayout, Int};
19+
use rustc_target::abi::{self, HasDataLayout, Int, Size};
2020
pub use rustc_target::spec::abi::Abi;
2121
use rustc_target::spec::SanitizerSet;
2222

2323
use libc::c_uint;
2424
use smallvec::SmallVec;
2525

26+
use std::cmp;
27+
2628
pub trait ArgAttributesExt {
2729
fn apply_attrs_to_llfn(&self, idx: AttributePlace, cx: &CodegenCx<'_, '_>, llfn: &Value);
2830
fn apply_attrs_to_callsite(
@@ -130,42 +132,36 @@ impl LlvmType for Reg {
130132
impl LlvmType for CastTarget {
131133
fn llvm_type<'ll>(&self, cx: &CodegenCx<'ll, '_>) -> &'ll Type {
132134
let rest_ll_unit = self.rest.unit.llvm_type(cx);
133-
let (rest_count, rem_bytes) = if self.rest.unit.size.bytes() == 0 {
134-
(0, 0)
135+
let rest_count = if self.rest.total == Size::ZERO {
136+
0
135137
} else {
136-
(
137-
self.rest.total.bytes() / self.rest.unit.size.bytes(),
138-
self.rest.total.bytes() % self.rest.unit.size.bytes(),
139-
)
138+
assert_ne!(
139+
self.rest.unit.size,
140+
Size::ZERO,
141+
"total size {:?} cannot be divided into units of zero size",
142+
self.rest.total
143+
);
144+
if self.rest.total.bytes() % self.rest.unit.size.bytes() != 0 {
145+
assert_eq!(self.rest.unit.kind, RegKind::Integer, "only int regs can be split");
146+
}
147+
self.rest.total.bytes().div_ceil(self.rest.unit.size.bytes())
140148
};
141149

150+
// Simplify to a single unit or an array if there's no prefix.
151+
// This produces the same layout, but using a simpler type.
142152
if self.prefix.iter().all(|x| x.is_none()) {
143-
// Simplify to a single unit when there is no prefix and size <= unit size
144-
if self.rest.total <= self.rest.unit.size {
153+
if rest_count == 1 {
145154
return rest_ll_unit;
146155
}
147156

148-
// Simplify to array when all chunks are the same size and type
149-
if rem_bytes == 0 {
150-
return cx.type_array(rest_ll_unit, rest_count);
151-
}
152-
}
153-
154-
// Create list of fields in the main structure
155-
let mut args: Vec<_> = self
156-
.prefix
157-
.iter()
158-
.flat_map(|option_reg| option_reg.map(|reg| reg.llvm_type(cx)))
159-
.chain((0..rest_count).map(|_| rest_ll_unit))
160-
.collect();
161-
162-
// Append final integer
163-
if rem_bytes != 0 {
164-
// Only integers can be really split further.
165-
assert_eq!(self.rest.unit.kind, RegKind::Integer);
166-
args.push(cx.type_ix(rem_bytes * 8));
157+
return cx.type_array(rest_ll_unit, rest_count);
167158
}
168159

160+
// Generate a struct type with the prefix and the "rest" arguments.
161+
let prefix_args =
162+
self.prefix.iter().flat_map(|option_reg| option_reg.map(|reg| reg.llvm_type(cx)));
163+
let rest_args = (0..rest_count).map(|_| rest_ll_unit);
164+
let args: Vec<_> = prefix_args.chain(rest_args).collect();
169165
cx.type_struct(&args, false)
170166
}
171167
}
@@ -215,47 +211,33 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
215211
bug!("unsized `ArgAbi` must be handled through `store_fn_arg`");
216212
}
217213
PassMode::Cast { cast, pad_i32: _ } => {
218-
// FIXME(eddyb): Figure out when the simpler Store is safe, clang
219-
// uses it for i16 -> {i8, i8}, but not for i24 -> {i8, i8, i8}.
220-
let can_store_through_cast_ptr = false;
221-
if can_store_through_cast_ptr {
222-
bx.store(val, dst.llval, self.layout.align.abi);
223-
} else {
224-
// The actual return type is a struct, but the ABI
225-
// adaptation code has cast it into some scalar type. The
226-
// code that follows is the only reliable way I have
227-
// found to do a transform like i64 -> {i32,i32}.
228-
// Basically we dump the data onto the stack then memcpy it.
229-
//
230-
// Other approaches I tried:
231-
// - Casting rust ret pointer to the foreign type and using Store
232-
// is (a) unsafe if size of foreign type > size of rust type and
233-
// (b) runs afoul of strict aliasing rules, yielding invalid
234-
// assembly under -O (specifically, the store gets removed).
235-
// - Truncating foreign type to correct integral type and then
236-
// bitcasting to the struct type yields invalid cast errors.
237-
238-
// We instead thus allocate some scratch space...
239-
let scratch_size = cast.size(bx);
240-
let scratch_align = cast.align(bx);
241-
let llscratch = bx.alloca(cast.llvm_type(bx), scratch_align);
242-
bx.lifetime_start(llscratch, scratch_size);
243-
244-
// ... where we first store the value...
245-
bx.store(val, llscratch, scratch_align);
246-
247-
// ... and then memcpy it to the intended destination.
248-
bx.memcpy(
249-
dst.llval,
250-
self.layout.align.abi,
251-
llscratch,
252-
scratch_align,
253-
bx.const_usize(self.layout.size.bytes()),
254-
MemFlags::empty(),
255-
);
256-
257-
bx.lifetime_end(llscratch, scratch_size);
258-
}
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);
259241
}
260242
_ => {
261243
OperandRef::from_immediate_or_packed_pair(bx, val, self.layout).val.store(bx, dst);

compiler/rustc_codegen_ssa/src/mir/block.rs

+29-3
Original file line numberDiff line numberDiff line change
@@ -1505,9 +1505,35 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15051505

15061506
if by_ref && !arg.is_indirect() {
15071507
// Have to load the argument, maybe while casting it.
1508-
if let PassMode::Cast { cast: ty, .. } = &arg.mode {
1509-
let llty = bx.cast_backend_type(ty);
1510-
llval = bx.load(llty, llval, align.min(arg.layout.align.abi));
1508+
if let PassMode::Cast { cast, pad_i32: _ } = &arg.mode {
1509+
// The ABI mandates that the value is passed as a different struct representation.
1510+
// Spill and reload it from the stack to convert from the Rust representation to
1511+
// the ABI representation.
1512+
let scratch_size = cast.size(bx);
1513+
let scratch_align = cast.align(bx);
1514+
// Note that the ABI type may be either larger or smaller than the Rust type,
1515+
// due to the presence or absence of trailing padding. For example:
1516+
// - On some ABIs, the Rust layout { f64, f32, <f32 padding> } may omit padding
1517+
// when passed by value, making it smaller.
1518+
// - On some ABIs, the Rust layout { u16, u16, u16 } may be padded up to 8 bytes
1519+
// when passed by value, making it larger.
1520+
let copy_bytes = cmp::min(scratch_size.bytes(), arg.layout.size.bytes());
1521+
// Allocate some scratch space...
1522+
let llscratch = bx.alloca(bx.cast_backend_type(cast), scratch_align);
1523+
bx.lifetime_start(llscratch, scratch_size);
1524+
// ...memcpy the value...
1525+
bx.memcpy(
1526+
llscratch,
1527+
scratch_align,
1528+
llval,
1529+
align,
1530+
bx.const_usize(copy_bytes),
1531+
MemFlags::empty(),
1532+
);
1533+
// ...and then load it with the ABI type.
1534+
let cast_ty = bx.cast_backend_type(cast);
1535+
llval = bx.load(cast_ty, llscratch, scratch_align);
1536+
bx.lifetime_end(llscratch, scratch_size);
15111537
} else {
15121538
// We can't use `PlaceRef::load` here because the argument
15131539
// may have a type we don't treat as immediate, but the ABI

compiler/rustc_target/src/abi/call/mod.rs

+14-11
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,9 @@ pub struct Uniform {
251251
/// The total size of the argument, which can be:
252252
/// * equal to `unit.size` (one scalar/vector),
253253
/// * a multiple of `unit.size` (an array of scalar/vectors),
254-
/// * if `unit.kind` is `Integer`, the last element
255-
/// can be shorter, i.e., `{ i64, i64, i32 }` for
256-
/// 64-bit integers with a total size of 20 bytes.
254+
/// * if `unit.kind` is `Integer`, the last element can be shorter, i.e., `{ i64, i64, i32 }`
255+
/// for 64-bit integers with a total size of 20 bytes. When the argument is actually passed,
256+
/// this size will be rounded up to the nearest multiple of `unit.size`.
257257
pub total: Size,
258258
}
259259

@@ -319,14 +319,17 @@ impl CastTarget {
319319
}
320320

321321
pub fn size<C: HasDataLayout>(&self, _cx: &C) -> Size {
322-
let mut size = self.rest.total;
323-
for i in 0..self.prefix.iter().count() {
324-
match self.prefix[i] {
325-
Some(v) => size += v.size,
326-
None => {}
327-
}
328-
}
329-
return size;
322+
// Prefix arguments are passed in specific designated registers
323+
let prefix_size = self
324+
.prefix
325+
.iter()
326+
.filter_map(|x| x.map(|reg| reg.size))
327+
.fold(Size::ZERO, |acc, size| acc + size);
328+
// Remaining arguments are passed in chunks of the unit size
329+
let rest_size =
330+
self.rest.unit.size * self.rest.total.bytes().div_ceil(self.rest.unit.size.bytes());
331+
332+
prefix_size + rest_size
330333
}
331334

332335
pub fn align<C: HasDataLayout>(&self, cx: &C) -> Align {

tests/auxiliary/rust_test_helpers.c

+24
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,30 @@ rust_dbg_extern_identity_TwoDoubles(struct TwoDoubles u) {
118118
return u;
119119
}
120120

121+
struct FiveU16s {
122+
uint16_t one;
123+
uint16_t two;
124+
uint16_t three;
125+
uint16_t four;
126+
uint16_t five;
127+
};
128+
129+
struct FiveU16s
130+
rust_dbg_extern_return_FiveU16s() {
131+
struct FiveU16s s;
132+
s.one = 10;
133+
s.two = 20;
134+
s.three = 30;
135+
s.four = 40;
136+
s.five = 50;
137+
return s;
138+
}
139+
140+
struct FiveU16s
141+
rust_dbg_extern_identity_FiveU16s(struct FiveU16s u) {
142+
return u;
143+
}
144+
121145
struct ManyInts {
122146
int8_t arg1;
123147
int16_t arg2;

0 commit comments

Comments
 (0)