Skip to content

Commit 47cb276

Browse files
committed
support passing unsized fn arguments
1 parent 8ef0caa commit 47cb276

File tree

2 files changed

+54
-12
lines changed

2 files changed

+54
-12
lines changed

compiler/rustc_const_eval/src/interpret/place.rs

+21-9
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,18 @@ impl<Tag: Provenance> MemPlace<Tag> {
183183
}
184184
}
185185

186+
impl<Tag: Provenance> Place<Tag> {
187+
/// Asserts that this points to some local variable.
188+
/// Returns the frame idx and the variable idx.
189+
#[inline]
190+
pub fn assert_local(&self) -> (usize, mir::Local) {
191+
match self {
192+
Place::Local { frame, local } => (*frame, *local),
193+
_ => bug!("assert_local: expected Place::Local, got {:?}", self),
194+
}
195+
}
196+
}
197+
186198
impl<'tcx, Tag: Provenance> MPlaceTy<'tcx, Tag> {
187199
/// Produces a MemPlace that works for ZST but nothing else
188200
#[inline]
@@ -286,7 +298,7 @@ impl<'tcx, Tag: Provenance> PlaceTy<'tcx, Tag> {
286298
}
287299

288300
#[inline]
289-
pub fn assert_mem_place(self) -> MPlaceTy<'tcx, Tag> {
301+
pub fn assert_mem_place(&self) -> MPlaceTy<'tcx, Tag> {
290302
self.try_as_mplace().unwrap()
291303
}
292304
}
@@ -899,16 +911,16 @@ where
899911
trace!("copy_op: {:?} <- {:?}: {}", *dest, src, dest.layout.ty);
900912

901913
let dest = self.force_allocation(dest)?;
902-
assert!(!(src.layout.is_unsized() || dest.layout.is_unsized()), "cannot copy unsized data");
903-
assert_eq!(src.layout.size, dest.layout.size, "Cannot copy differently-sized data");
914+
let Some((dest_size, _)) = self.size_and_align_of_mplace(&dest)? else {
915+
span_bug!(self.cur_span(), "copy_op needs (dynamically) sized values")
916+
};
917+
if cfg!(debug_assertions) {
918+
let src_size = self.size_and_align_of_mplace(&src)?.unwrap().0;
919+
assert_eq!(src_size, dest_size, "Cannot copy differently-sized data");
920+
}
904921

905922
self.mem_copy(
906-
src.ptr,
907-
src.align,
908-
dest.ptr,
909-
dest.align,
910-
dest.layout.size,
911-
/*nonoverlapping*/ false,
923+
src.ptr, src.align, dest.ptr, dest.align, dest_size, /*nonoverlapping*/ false,
912924
)
913925
}
914926

compiler/rustc_const_eval/src/interpret/terminator.rs

+33-3
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use rustc_target::abi::call::{ArgAbi, ArgAttribute, ArgAttributes, FnAbi, PassMo
1212
use rustc_target::spec::abi::Abi;
1313

1414
use super::{
15-
FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, Scalar,
16-
StackPopCleanup, StackPopUnwind,
15+
FnVal, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, Operand,
16+
PlaceTy, Scalar, StackPopCleanup, StackPopUnwind,
1717
};
1818

1919
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
@@ -185,11 +185,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
185185
// No question
186186
return true;
187187
}
188+
if caller_abi.layout.is_unsized() || callee_abi.layout.is_unsized() {
189+
// No, no, no. We require the types to *exactly* match for unsized arguments. If
190+
// these are somehow unsized "in a different way" (say, `dyn Trait` vs `[i32]`),
191+
// then who knows what happens.
192+
return false;
193+
}
188194
if caller_abi.layout.size != callee_abi.layout.size
189195
|| caller_abi.layout.align.abi != callee_abi.layout.align.abi
190196
{
191197
// This cannot go well...
192-
// FIXME: What about unsized types?
193198
return false;
194199
}
195200
// The rest *should* be okay, but we are extra conservative.
@@ -287,6 +292,31 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
287292
caller_arg.layout.ty
288293
)
289294
}
295+
// Special handling for unsized parameters.
296+
if caller_arg.layout.is_unsized() {
297+
// `check_argument_compat` ensures that both have the same type, so we know they will use the metadata the same way.
298+
assert_eq!(caller_arg.layout.ty, callee_arg.layout.ty);
299+
// We have to properly pre-allocate the memory for the callee.
300+
// So let's tear down some wrappers.
301+
// This all has to be in memory, there are no immediate unsized values.
302+
let src = caller_arg.assert_mem_place();
303+
// The destination cannot be one of these "spread args".
304+
let (dest_frame, dest_local) = callee_arg.assert_local();
305+
// We are just initializing things, so there can't be anything here yet.
306+
assert!(matches!(
307+
*self.local_to_op(&self.stack()[dest_frame], dest_local, None)?,
308+
Operand::Immediate(Immediate::Uninit)
309+
));
310+
// Allocate enough memory to hold `src`.
311+
let Some((size, align)) = self.size_and_align_of_mplace(&src)? else {
312+
span_bug!(self.cur_span(), "unsized fn arg with `extern` type tail should not be allowed")
313+
};
314+
let ptr = self.allocate_ptr(size, align, MemoryKind::Stack)?;
315+
let dest_place =
316+
MPlaceTy::from_aligned_ptr_with_meta(ptr.into(), callee_arg.layout, src.meta);
317+
// Update the local to be that new place.
318+
*M::access_local_mut(self, dest_frame, dest_local)? = Operand::Indirect(*dest_place);
319+
}
290320
// We allow some transmutes here.
291321
// FIXME: Depending on the PassMode, this should reset some padding to uninitialized. (This
292322
// is true for all `copy_op`, but there are a lot of special cases for argument passing

0 commit comments

Comments
 (0)