44
55use either:: Either ;
66use rustc_abi:: { FIRST_VARIANT , FieldIdx } ;
7+ use rustc_data_structures:: fx:: FxHashSet ;
78use rustc_index:: IndexSlice ;
89use rustc_middle:: ty:: { self , Instance , Ty } ;
910use rustc_middle:: { bug, mir, span_bug} ;
@@ -389,8 +390,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
389390
390391 /// Evaluate the arguments of a function call
391392 fn eval_fn_call_argument (
392- & self ,
393+ & mut self ,
393394 op : & mir:: Operand < ' tcx > ,
395+ move_definitely_disjoint : bool ,
394396 ) -> InterpResult < ' tcx , FnArg < ' tcx , M :: Provenance > > {
395397 interp_ok ( match op {
396398 mir:: Operand :: Copy ( _) | mir:: Operand :: Constant ( _) => {
@@ -399,24 +401,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
399401 FnArg :: Copy ( op)
400402 }
401403 mir:: Operand :: Move ( place) => {
402- // If this place lives in memory, preserve its location.
403- // We call `place_to_op` which will be an `MPlaceTy` whenever there exists
404- // an mplace for this place. (This is in contrast to `PlaceTy::as_mplace_or_local`
405- // which can return a local even if that has an mplace.)
406404 let place = self . eval_place ( * place) ?;
407- let op = self . place_to_op ( & place) ?;
408-
409- match op. as_mplace_or_imm ( ) {
410- Either :: Left ( mplace) => FnArg :: InPlace ( mplace) ,
411- Either :: Right ( _imm) => {
412- // This argument doesn't live in memory, so there's no place
413- // to make inaccessible during the call.
414- // We rely on there not being any stray `PlaceTy` that would let the
415- // caller directly access this local!
416- // This is also crucial for tail calls, where we want the `FnArg` to
417- // stay valid when the old stack frame gets popped.
418- FnArg :: Copy ( op)
405+ if move_definitely_disjoint {
406+ // We still have to ensure that no *other* pointers are used to access this place,
407+ // so *if* it is in memory then we have to treat it as `InPlace`.
408+ // Use `place_to_op` to guarantee that we notice it being in memory.
409+ let op = self . place_to_op ( & place) ?;
410+ match op. as_mplace_or_imm ( ) {
411+ Either :: Left ( mplace) => FnArg :: InPlace ( mplace) ,
412+ Either :: Right ( _imm) => FnArg :: Copy ( op) ,
419413 }
414+ } else {
415+ // We have to force this into memory to detect aliasing among `Move` arguments.
416+ FnArg :: InPlace ( self . force_allocation ( & place) ?)
420417 }
421418 }
422419 } )
@@ -425,18 +422,46 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
425422 /// Shared part of `Call` and `TailCall` implementation — finding and evaluating all the
426423 /// necessary information about callee and arguments to make a call.
427424 fn eval_callee_and_args (
428- & self ,
425+ & mut self ,
429426 terminator : & mir:: Terminator < ' tcx > ,
430427 func : & mir:: Operand < ' tcx > ,
431428 args : & [ Spanned < mir:: Operand < ' tcx > > ] ,
432429 ) -> InterpResult < ' tcx , EvaluatedCalleeAndArgs < ' tcx , M > > {
433430 let func = self . eval_operand ( func, None ) ?;
431+
432+ // Evaluating function call arguments. The tricky part here is dealing with `Move`
433+ // arguments: we have to ensure no two such arguments alias. This would be most easily done
434+ // by just forcing them all into memory and then doing the usual in-place argument
435+ // protection, but then we'd force *a lot* of arguments into memory. So we do some syntactic
436+ // pre-processing here where if all `move` arguments are syntactically distinct local
437+ // variables (and none is indirect), we can skip the in-memory forcing.
438+ let move_definitely_disjoint = ' move_definitely_disjoint: {
439+ let mut previous_locals = FxHashSet :: < mir:: Local > :: default ( ) ;
440+ for arg in args {
441+ let mir:: Operand :: Move ( place) = arg. node else {
442+ continue ; // we can skip non-`Move` arguments.
443+ } ;
444+ if place. is_indirect_first_projection ( ) {
445+ // An indirect `Move` argument could alias with anything else...
446+ break ' move_definitely_disjoint false ;
447+ }
448+ if !previous_locals. insert ( place. local ) {
449+ // This local is the base for two arguments! They might overlap.
450+ break ' move_definitely_disjoint false ;
451+ }
452+ }
453+ // We found no violation so they are all definitely disjoint.
454+ true
455+ } ;
434456 let args = args
435457 . iter ( )
436- . map ( |arg| self . eval_fn_call_argument ( & arg. node ) )
458+ . map ( |arg| self . eval_fn_call_argument ( & arg. node , move_definitely_disjoint ) )
437459 . collect :: < InterpResult < ' tcx , Vec < _ > > > ( ) ?;
438460
439- let fn_sig_binder = func. layout . ty . fn_sig ( * self . tcx ) ;
461+ let fn_sig_binder = {
462+ let _trace = enter_trace_span ! ( M , "fn_sig" , ty = ?func. layout. ty. kind( ) ) ;
463+ func. layout . ty . fn_sig ( * self . tcx )
464+ } ;
440465 let fn_sig = self . tcx . normalize_erasing_late_bound_regions ( self . typing_env , fn_sig_binder) ;
441466 let extra_args = & args[ fn_sig. inputs ( ) . len ( ) ..] ;
442467 let extra_args =
0 commit comments