Skip to content

Commit 57a7a0e

Browse files
committed
Tweak the way we protect in-place function arguments in interpreters
Use `MPlaceTy` instead of `PlaceTy` in `FnArg` and ignore (copy) locals in an earlier step ("Locals that don't have their address taken are as protected as they can ever be"). This seems to be crucial for tail call support (as they can't refer to caller's locals which are killed when replacing the stack frame).
1 parent 8039906 commit 57a7a0e

File tree

4 files changed

+50
-38
lines changed

4 files changed

+50
-38
lines changed

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
227227
if self.tcx.has_attr(def_id, sym::rustc_const_panic_str)
228228
|| Some(def_id) == self.tcx.lang_items().begin_panic_fn()
229229
{
230-
let args = self.copy_fn_args(args)?;
230+
let args = self.copy_fn_args(args);
231231
// &str or &&str
232232
assert!(args.len() == 1);
233233

@@ -254,7 +254,7 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
254254

255255
return Ok(Some(new_instance));
256256
} else if Some(def_id) == self.tcx.lang_items().align_offset_fn() {
257-
let args = self.copy_fn_args(args)?;
257+
let args = self.copy_fn_args(args);
258258
// For align_offset, we replace the function call if the pointer has no address.
259259
match self.align_offset(instance, &args, dest, ret)? {
260260
ControlFlow::Continue(()) => return Ok(Some(instance)),

compiler/rustc_const_eval/src/interpret/machine.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,11 +466,11 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
466466
/// argument/return value was actually copied or passed in-place..
467467
fn protect_in_place_function_argument(
468468
ecx: &mut InterpCx<'mir, 'tcx, Self>,
469-
place: &PlaceTy<'tcx, Self::Provenance>,
469+
mplace: &MPlaceTy<'tcx, Self::Provenance>,
470470
) -> InterpResult<'tcx> {
471471
// Without an aliasing model, all we can do is put `Uninit` into the place.
472472
// Conveniently this also ensures that the place actually points to suitable memory.
473-
ecx.write_uninit(place)
473+
ecx.write_uninit(mplace)
474474
}
475475

476476
/// Called immediately before a new stack frame gets pushed.

compiler/rustc_const_eval/src/interpret/terminator.rs

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::borrow::Cow;
22

3+
use either::Either;
34
use rustc_ast::ast::InlineAsmOptions;
45
use rustc_middle::{
56
mir,
@@ -30,28 +31,25 @@ pub enum FnArg<'tcx, Prov: Provenance = CtfeProvenance> {
3031
Copy(OpTy<'tcx, Prov>),
3132
/// Allow for the argument to be passed in-place: destroy the value originally stored at that place and
3233
/// make the place inaccessible for the duration of the function call.
33-
InPlace(PlaceTy<'tcx, Prov>),
34+
InPlace(MPlaceTy<'tcx, Prov>),
3435
}
3536

3637
impl<'tcx, Prov: Provenance> FnArg<'tcx, Prov> {
3738
pub fn layout(&self) -> &TyAndLayout<'tcx> {
3839
match self {
3940
FnArg::Copy(op) => &op.layout,
40-
FnArg::InPlace(place) => &place.layout,
41+
FnArg::InPlace(mplace) => &mplace.layout,
4142
}
4243
}
4344
}
4445

4546
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
4647
/// Make a copy of the given fn_arg. Any `InPlace` are degenerated to copies, no protection of the
4748
/// original memory occurs.
48-
pub fn copy_fn_arg(
49-
&self,
50-
arg: &FnArg<'tcx, M::Provenance>,
51-
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
49+
pub fn copy_fn_arg(&self, arg: &FnArg<'tcx, M::Provenance>) -> OpTy<'tcx, M::Provenance> {
5250
match arg {
53-
FnArg::Copy(op) => Ok(op.clone()),
54-
FnArg::InPlace(place) => self.place_to_op(place),
51+
FnArg::Copy(op) => op.clone(),
52+
FnArg::InPlace(mplace) => mplace.clone().into(),
5553
}
5654
}
5755

@@ -60,7 +58,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
6058
pub fn copy_fn_args(
6159
&self,
6260
args: &[FnArg<'tcx, M::Provenance>],
63-
) -> InterpResult<'tcx, Vec<OpTy<'tcx, M::Provenance>>> {
61+
) -> Vec<OpTy<'tcx, M::Provenance>> {
6462
args.iter().map(|fn_arg| self.copy_fn_arg(fn_arg)).collect()
6563
}
6664

@@ -71,7 +69,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
7169
) -> InterpResult<'tcx, FnArg<'tcx, M::Provenance>> {
7270
Ok(match arg {
7371
FnArg::Copy(op) => FnArg::Copy(self.project_field(op, field)?),
74-
FnArg::InPlace(place) => FnArg::InPlace(self.project_field(place, field)?),
72+
FnArg::InPlace(mplace) => FnArg::InPlace(self.project_field(mplace, field)?),
7573
})
7674
}
7775

@@ -246,10 +244,28 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
246244
) -> InterpResult<'tcx, Vec<FnArg<'tcx, M::Provenance>>> {
247245
ops.iter()
248246
.map(|op| {
249-
Ok(match &op.node {
250-
mir::Operand::Move(place) => FnArg::InPlace(self.eval_place(*place)?),
251-
_ => FnArg::Copy(self.eval_operand(&op.node, None)?),
252-
})
247+
let arg = match &op.node {
248+
mir::Operand::Copy(_) | mir::Operand::Constant(_) => {
249+
let op = self.eval_operand(&op.node, None)?;
250+
FnArg::Copy(op)
251+
}
252+
mir::Operand::Move(place) => {
253+
let place = self.eval_place(*place)?;
254+
255+
match place.as_mplace_or_local() {
256+
Either::Left(mplace) => FnArg::InPlace(mplace),
257+
Either::Right(_local) => {
258+
// This argument doesn't live in memory, so there's no place
259+
// to make inaccessible during the call.
260+
// This is also crucial for tail calls, where we want the `FnArg` to
261+
// stay valid when the old stack frame gets popped.
262+
FnArg::Copy(self.place_to_op(&place)?)
263+
}
264+
}
265+
}
266+
};
267+
268+
Ok(arg)
253269
})
254270
.collect()
255271
}
@@ -459,7 +475,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
459475
// We work with a copy of the argument for now; if this is in-place argument passing, we
460476
// will later protect the source it comes from. This means the callee cannot observe if we
461477
// did in-place of by-copy argument passing, except for pointer equality tests.
462-
let caller_arg_copy = self.copy_fn_arg(caller_arg)?;
478+
let caller_arg_copy = self.copy_fn_arg(caller_arg);
463479
if !already_live {
464480
let local = callee_arg.as_local().unwrap();
465481
let meta = caller_arg_copy.meta();
@@ -477,8 +493,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
477493
// specifically.)
478494
self.copy_op_allow_transmute(&caller_arg_copy, &callee_arg)?;
479495
// If this was an in-place pass, protect the place it comes from for the duration of the call.
480-
if let FnArg::InPlace(place) = caller_arg {
481-
M::protect_in_place_function_argument(self, place)?;
496+
if let FnArg::InPlace(mplace) = caller_arg {
497+
M::protect_in_place_function_argument(self, mplace)?;
482498
}
483499
Ok(())
484500
}
@@ -525,7 +541,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
525541
M::call_intrinsic(
526542
self,
527543
instance,
528-
&self.copy_fn_args(args)?,
544+
&self.copy_fn_args(args),
529545
destination,
530546
target,
531547
unwind,
@@ -602,8 +618,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
602618
.map(|arg| (
603619
arg.layout().ty,
604620
match arg {
605-
FnArg::Copy(op) => format!("copy({:?})", *op),
606-
FnArg::InPlace(place) => format!("in-place({:?})", *place),
621+
FnArg::Copy(op) => format!("copy({op:?})"),
622+
FnArg::InPlace(mplace) => format!("in-place({mplace:?})"),
607623
}
608624
))
609625
.collect::<Vec<_>>()
@@ -725,8 +741,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
725741
callee_ty: callee_fn_abi.ret.layout.ty
726742
});
727743
}
728-
// Protect return place for in-place return value passing.
729-
M::protect_in_place_function_argument(self, destination)?;
744+
745+
if let Either::Left(destination) = destination.as_mplace_or_local() {
746+
// Protect return place for in-place return value passing.
747+
M::protect_in_place_function_argument(self, &destination)?;
748+
}
730749

731750
// Don't forget to mark "initially live" locals as live.
732751
self.storage_live_for_always_live_locals()?;
@@ -749,7 +768,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
749768
// An `InPlace` does nothing here, we keep the original receiver intact. We can't
750769
// really pass the argument in-place anyway, and we are constructing a new
751770
// `Immediate` receiver.
752-
let mut receiver = self.copy_fn_arg(&args[0])?;
771+
let mut receiver = self.copy_fn_arg(&args[0]);
753772
let receiver_place = loop {
754773
match receiver.layout.ty.kind() {
755774
ty::Ref(..) | ty::RawPtr(..) => {

src/tools/miri/src/machine.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use std::fmt;
88
use std::path::Path;
99
use std::process;
1010

11-
use either::Either;
1211
use rand::rngs::StdRng;
1312
use rand::Rng;
1413
use rand::SeedableRng;
@@ -962,7 +961,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
962961
// to run extra MIR), and Ok(Some(body)) if we found MIR to run for the
963962
// foreign function
964963
// Any needed call to `goto_block` will be performed by `emulate_foreign_item`.
965-
let args = ecx.copy_fn_args(args)?; // FIXME: Should `InPlace` arguments be reset to uninit?
964+
let args = ecx.copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit?
966965
let link_name = ecx.item_link_name(instance.def_id());
967966
return ecx.emulate_foreign_item(link_name, abi, &args, dest, ret, unwind);
968967
}
@@ -981,7 +980,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
981980
ret: Option<mir::BasicBlock>,
982981
unwind: mir::UnwindAction,
983982
) -> InterpResult<'tcx> {
984-
let args = ecx.copy_fn_args(args)?; // FIXME: Should `InPlace` arguments be reset to uninit?
983+
let args = ecx.copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit?
985984
ecx.emulate_dyn_sym(fn_val, abi, &args, dest, ret, unwind)
986985
}
987986

@@ -1334,18 +1333,12 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
13341333

13351334
fn protect_in_place_function_argument(
13361335
ecx: &mut InterpCx<'mir, 'tcx, Self>,
1337-
place: &PlaceTy<'tcx, Provenance>,
1336+
place: &MPlaceTy<'tcx, Provenance>,
13381337
) -> InterpResult<'tcx> {
13391338
// If we have a borrow tracker, we also have it set up protection so that all reads *and
13401339
// writes* during this call are insta-UB.
13411340
let protected_place = if ecx.machine.borrow_tracker.is_some() {
1342-
// Have to do `to_op` first because a `Place::Local` doesn't imply the local doesn't have an address.
1343-
if let Either::Left(place) = ecx.place_to_op(place)?.as_mplace_or_imm() {
1344-
ecx.protect_place(&place)?.into()
1345-
} else {
1346-
// Locals that don't have their address taken are as protected as they can ever be.
1347-
place.clone()
1348-
}
1341+
ecx.protect_place(&place)?.into()
13491342
} else {
13501343
// No borrow tracker.
13511344
place.clone()

0 commit comments

Comments
 (0)