From 47cb276ab887b9c869febde7b76ab7aec9bdbcee Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 2 Jul 2022 17:09:40 -0400 Subject: [PATCH] support passing unsized fn arguments --- .../rustc_const_eval/src/interpret/place.rs | 30 +++++++++++----- .../src/interpret/terminator.rs | 36 +++++++++++++++++-- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index 5ec6c79a86f..522f55bf565 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -183,6 +183,18 @@ impl MemPlace { } } +impl Place { + /// Asserts that this points to some local variable. + /// Returns the frame idx and the variable idx. + #[inline] + pub fn assert_local(&self) -> (usize, mir::Local) { + match self { + Place::Local { frame, local } => (*frame, *local), + _ => bug!("assert_local: expected Place::Local, got {:?}", self), + } + } +} + impl<'tcx, Tag: Provenance> MPlaceTy<'tcx, Tag> { /// Produces a MemPlace that works for ZST but nothing else #[inline] @@ -286,7 +298,7 @@ impl<'tcx, Tag: Provenance> PlaceTy<'tcx, Tag> { } #[inline] - pub fn assert_mem_place(self) -> MPlaceTy<'tcx, Tag> { + pub fn assert_mem_place(&self) -> MPlaceTy<'tcx, Tag> { self.try_as_mplace().unwrap() } } @@ -899,16 +911,16 @@ where trace!("copy_op: {:?} <- {:?}: {}", *dest, src, dest.layout.ty); let dest = self.force_allocation(dest)?; - assert!(!(src.layout.is_unsized() || dest.layout.is_unsized()), "cannot copy unsized data"); - assert_eq!(src.layout.size, dest.layout.size, "Cannot copy differently-sized data"); + let Some((dest_size, _)) = self.size_and_align_of_mplace(&dest)? else { + span_bug!(self.cur_span(), "copy_op needs (dynamically) sized values") + }; + if cfg!(debug_assertions) { + let src_size = self.size_and_align_of_mplace(&src)?.unwrap().0; + assert_eq!(src_size, dest_size, "Cannot copy differently-sized data"); + } self.mem_copy( - src.ptr, - src.align, - dest.ptr, - dest.align, - dest.layout.size, - /*nonoverlapping*/ false, + src.ptr, src.align, dest.ptr, dest.align, dest_size, /*nonoverlapping*/ false, ) } diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 57d06b48ca4..613533f85e9 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -12,8 +12,8 @@ use rustc_target::abi::call::{ArgAbi, ArgAttribute, ArgAttributes, FnAbi, PassMo use rustc_target::spec::abi::Abi; use super::{ - FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, Scalar, - StackPopCleanup, StackPopUnwind, + FnVal, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, Operand, + PlaceTy, Scalar, StackPopCleanup, StackPopUnwind, }; 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> { // No question return true; } + if caller_abi.layout.is_unsized() || callee_abi.layout.is_unsized() { + // No, no, no. We require the types to *exactly* match for unsized arguments. If + // these are somehow unsized "in a different way" (say, `dyn Trait` vs `[i32]`), + // then who knows what happens. + return false; + } if caller_abi.layout.size != callee_abi.layout.size || caller_abi.layout.align.abi != callee_abi.layout.align.abi { // This cannot go well... - // FIXME: What about unsized types? return false; } // 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> { caller_arg.layout.ty ) } + // Special handling for unsized parameters. + if caller_arg.layout.is_unsized() { + // `check_argument_compat` ensures that both have the same type, so we know they will use the metadata the same way. + assert_eq!(caller_arg.layout.ty, callee_arg.layout.ty); + // We have to properly pre-allocate the memory for the callee. + // So let's tear down some wrappers. + // This all has to be in memory, there are no immediate unsized values. + let src = caller_arg.assert_mem_place(); + // The destination cannot be one of these "spread args". + let (dest_frame, dest_local) = callee_arg.assert_local(); + // We are just initializing things, so there can't be anything here yet. + assert!(matches!( + *self.local_to_op(&self.stack()[dest_frame], dest_local, None)?, + Operand::Immediate(Immediate::Uninit) + )); + // Allocate enough memory to hold `src`. + let Some((size, align)) = self.size_and_align_of_mplace(&src)? else { + span_bug!(self.cur_span(), "unsized fn arg with `extern` type tail should not be allowed") + }; + let ptr = self.allocate_ptr(size, align, MemoryKind::Stack)?; + let dest_place = + MPlaceTy::from_aligned_ptr_with_meta(ptr.into(), callee_arg.layout, src.meta); + // Update the local to be that new place. + *M::access_local_mut(self, dest_frame, dest_local)? = Operand::Indirect(*dest_place); + } // We allow some transmutes here. // FIXME: Depending on the PassMode, this should reset some padding to uninitialized. (This // is true for all `copy_op`, but there are a lot of special cases for argument passing