Skip to content

Commit ede955a

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 ede955a

File tree

4 files changed

+61
-38
lines changed

4 files changed

+61
-38
lines changed

compiler/rustc_const_eval/src/const_eval/machine.rs

+2-2
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

+2-2
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

+53-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::borrow::Cow;
22

3+
use either::Either;
4+
35
use rustc_ast::ast::InlineAsmOptions;
46
use rustc_middle::{
57
mir,
@@ -30,28 +32,25 @@ pub enum FnArg<'tcx, Prov: Provenance = CtfeProvenance> {
3032
Copy(OpTy<'tcx, Prov>),
3133
/// Allow for the argument to be passed in-place: destroy the value originally stored at that place and
3234
/// make the place inaccessible for the duration of the function call.
33-
InPlace(PlaceTy<'tcx, Prov>),
35+
InPlace(MPlaceTy<'tcx, Prov>),
3436
}
3537

3638
impl<'tcx, Prov: Provenance> FnArg<'tcx, Prov> {
3739
pub fn layout(&self) -> &TyAndLayout<'tcx> {
3840
match self {
3941
FnArg::Copy(op) => &op.layout,
40-
FnArg::InPlace(place) => &place.layout,
42+
FnArg::InPlace(mplace) => &mplace.layout,
4143
}
4244
}
4345
}
4446

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

@@ -60,7 +59,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
6059
pub fn copy_fn_args(
6160
&self,
6261
args: &[FnArg<'tcx, M::Provenance>],
63-
) -> InterpResult<'tcx, Vec<OpTy<'tcx, M::Provenance>>> {
62+
) -> Vec<OpTy<'tcx, M::Provenance>> {
6463
args.iter().map(|fn_arg| self.copy_fn_arg(fn_arg)).collect()
6564
}
6665

@@ -71,7 +70,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
7170
) -> InterpResult<'tcx, FnArg<'tcx, M::Provenance>> {
7271
Ok(match arg {
7372
FnArg::Copy(op) => FnArg::Copy(self.project_field(op, field)?),
74-
FnArg::InPlace(place) => FnArg::InPlace(self.project_field(place, field)?),
73+
FnArg::InPlace(mplace) => FnArg::InPlace(self.project_field(mplace, field)?),
7574
})
7675
}
7776

@@ -246,10 +245,36 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
246245
) -> InterpResult<'tcx, Vec<FnArg<'tcx, M::Provenance>>> {
247246
ops.iter()
248247
.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-
})
248+
let arg = match &op.node {
249+
mir::Operand::Copy(_) | mir::Operand::Constant(_) => {
250+
// Make a regular copy.
251+
let op = self.eval_operand(&op.node, None)?;
252+
FnArg::Copy(op)
253+
}
254+
mir::Operand::Move(place) => {
255+
// If this place lives in memory, preserve its location.
256+
// We call `place_to_op` which will be an `MPlaceTy` whenever there exists
257+
// an mplace for this place. (This is in contrast to `PlaceTy::as_mplace_or_local`
258+
// which can return a local even if that has an mplace.)
259+
let place = self.eval_place(*place)?;
260+
let op = self.place_to_op(&place)?;
261+
262+
match op.as_mplace_or_imm() {
263+
Either::Left(mplace) => FnArg::InPlace(mplace),
264+
Either::Right(_imm) => {
265+
// This argument doesn't live in memory, so there's no place
266+
// to make inaccessible during the call.
267+
// We rely on there not being any stray `PlaceTy` that would let the
268+
// caller directly access this local!
269+
// This is also crucial for tail calls, where we want the `FnArg` to
270+
// stay valid when the old stack frame gets popped.
271+
FnArg::Copy(op)
272+
}
273+
}
274+
}
275+
};
276+
277+
Ok(arg)
253278
})
254279
.collect()
255280
}
@@ -459,7 +484,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
459484
// We work with a copy of the argument for now; if this is in-place argument passing, we
460485
// will later protect the source it comes from. This means the callee cannot observe if we
461486
// did in-place of by-copy argument passing, except for pointer equality tests.
462-
let caller_arg_copy = self.copy_fn_arg(caller_arg)?;
487+
let caller_arg_copy = self.copy_fn_arg(caller_arg);
463488
if !already_live {
464489
let local = callee_arg.as_local().unwrap();
465490
let meta = caller_arg_copy.meta();
@@ -477,8 +502,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
477502
// specifically.)
478503
self.copy_op_allow_transmute(&caller_arg_copy, &callee_arg)?;
479504
// 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)?;
505+
if let FnArg::InPlace(mplace) = caller_arg {
506+
M::protect_in_place_function_argument(self, mplace)?;
482507
}
483508
Ok(())
484509
}
@@ -525,7 +550,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
525550
M::call_intrinsic(
526551
self,
527552
instance,
528-
&self.copy_fn_args(args)?,
553+
&self.copy_fn_args(args),
529554
destination,
530555
target,
531556
unwind,
@@ -602,8 +627,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
602627
.map(|arg| (
603628
arg.layout().ty,
604629
match arg {
605-
FnArg::Copy(op) => format!("copy({:?})", *op),
606-
FnArg::InPlace(place) => format!("in-place({:?})", *place),
630+
FnArg::Copy(op) => format!("copy({op:?})"),
631+
FnArg::InPlace(mplace) => format!("in-place({mplace:?})"),
607632
}
608633
))
609634
.collect::<Vec<_>>()
@@ -725,8 +750,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
725750
callee_ty: callee_fn_abi.ret.layout.ty
726751
});
727752
}
728-
// Protect return place for in-place return value passing.
729-
M::protect_in_place_function_argument(self, destination)?;
753+
754+
if let Either::Left(destination) =
755+
self.place_to_op(&destination)?.as_mplace_or_imm()
756+
{
757+
// Protect return place for in-place return value passing.
758+
M::protect_in_place_function_argument(self, &destination)?;
759+
}
730760

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

src/tools/miri/src/machine.rs

+4-11
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)