Skip to content

Commit 2c3ca09

Browse files
authored
Rollup merge of #122076 - WaffleLapkin:mplace-args, r=RalfJung
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). r? `@RalfJung` cc `@oli-obk` see #121273 (comment)
2 parents 948d32d + a984322 commit 2c3ca09

File tree

4 files changed

+56
-37
lines changed

4 files changed

+56
-37
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
@@ -472,11 +472,11 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
472472
/// argument/return value was actually copied or passed in-place..
473473
fn protect_in_place_function_argument(
474474
ecx: &mut InterpCx<'mir, 'tcx, Self>,
475-
place: &PlaceTy<'tcx, Self::Provenance>,
475+
mplace: &MPlaceTy<'tcx, Self::Provenance>,
476476
) -> InterpResult<'tcx> {
477477
// Without an aliasing model, all we can do is put `Uninit` into the place.
478478
// Conveniently this also ensures that the place actually points to suitable memory.
479-
ecx.write_uninit(place)
479+
ecx.write_uninit(mplace)
480480
}
481481

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

compiler/rustc_const_eval/src/interpret/terminator.rs

+48-22
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_middle::{
46
mir,
57
ty::{
@@ -29,28 +31,25 @@ pub enum FnArg<'tcx, Prov: Provenance = CtfeProvenance> {
2931
Copy(OpTy<'tcx, Prov>),
3032
/// Allow for the argument to be passed in-place: destroy the value originally stored at that place and
3133
/// make the place inaccessible for the duration of the function call.
32-
InPlace(PlaceTy<'tcx, Prov>),
34+
InPlace(MPlaceTy<'tcx, Prov>),
3335
}
3436

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

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

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

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

@@ -238,10 +237,36 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
238237
) -> InterpResult<'tcx, Vec<FnArg<'tcx, M::Provenance>>> {
239238
ops.iter()
240239
.map(|op| {
241-
Ok(match &op.node {
242-
mir::Operand::Move(place) => FnArg::InPlace(self.eval_place(*place)?),
243-
_ => FnArg::Copy(self.eval_operand(&op.node, None)?),
244-
})
240+
let arg = match &op.node {
241+
mir::Operand::Copy(_) | mir::Operand::Constant(_) => {
242+
// Make a regular copy.
243+
let op = self.eval_operand(&op.node, None)?;
244+
FnArg::Copy(op)
245+
}
246+
mir::Operand::Move(place) => {
247+
// If this place lives in memory, preserve its location.
248+
// We call `place_to_op` which will be an `MPlaceTy` whenever there exists
249+
// an mplace for this place. (This is in contrast to `PlaceTy::as_mplace_or_local`
250+
// which can return a local even if that has an mplace.)
251+
let place = self.eval_place(*place)?;
252+
let op = self.place_to_op(&place)?;
253+
254+
match op.as_mplace_or_imm() {
255+
Either::Left(mplace) => FnArg::InPlace(mplace),
256+
Either::Right(_imm) => {
257+
// This argument doesn't live in memory, so there's no place
258+
// to make inaccessible during the call.
259+
// We rely on there not being any stray `PlaceTy` that would let the
260+
// caller directly access this local!
261+
// This is also crucial for tail calls, where we want the `FnArg` to
262+
// stay valid when the old stack frame gets popped.
263+
FnArg::Copy(op)
264+
}
265+
}
266+
}
267+
};
268+
269+
Ok(arg)
245270
})
246271
.collect()
247272
}
@@ -451,7 +476,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
451476
// We work with a copy of the argument for now; if this is in-place argument passing, we
452477
// will later protect the source it comes from. This means the callee cannot observe if we
453478
// did in-place of by-copy argument passing, except for pointer equality tests.
454-
let caller_arg_copy = self.copy_fn_arg(caller_arg)?;
479+
let caller_arg_copy = self.copy_fn_arg(caller_arg);
455480
if !already_live {
456481
let local = callee_arg.as_local().unwrap();
457482
let meta = caller_arg_copy.meta();
@@ -469,8 +494,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
469494
// specifically.)
470495
self.copy_op_allow_transmute(&caller_arg_copy, &callee_arg)?;
471496
// If this was an in-place pass, protect the place it comes from for the duration of the call.
472-
if let FnArg::InPlace(place) = caller_arg {
473-
M::protect_in_place_function_argument(self, place)?;
497+
if let FnArg::InPlace(mplace) = caller_arg {
498+
M::protect_in_place_function_argument(self, mplace)?;
474499
}
475500
Ok(())
476501
}
@@ -517,7 +542,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
517542
M::call_intrinsic(
518543
self,
519544
instance,
520-
&self.copy_fn_args(args)?,
545+
&self.copy_fn_args(args),
521546
destination,
522547
target,
523548
unwind,
@@ -594,8 +619,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
594619
.map(|arg| (
595620
arg.layout().ty,
596621
match arg {
597-
FnArg::Copy(op) => format!("copy({:?})", *op),
598-
FnArg::InPlace(place) => format!("in-place({:?})", *place),
622+
FnArg::Copy(op) => format!("copy({op:?})"),
623+
FnArg::InPlace(mplace) => format!("in-place({mplace:?})"),
599624
}
600625
))
601626
.collect::<Vec<_>>()
@@ -717,8 +742,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
717742
callee_ty: callee_fn_abi.ret.layout.ty
718743
});
719744
}
745+
720746
// Protect return place for in-place return value passing.
721-
M::protect_in_place_function_argument(self, &destination.clone().into())?;
747+
M::protect_in_place_function_argument(self, &destination)?;
722748

723749
// Don't forget to mark "initially live" locals as live.
724750
self.storage_live_for_always_live_locals()?;
@@ -741,7 +767,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
741767
// An `InPlace` does nothing here, we keep the original receiver intact. We can't
742768
// really pass the argument in-place anyway, and we are constructing a new
743769
// `Immediate` receiver.
744-
let mut receiver = self.copy_fn_arg(&args[0])?;
770+
let mut receiver = self.copy_fn_arg(&args[0]);
745771
let receiver_place = loop {
746772
match receiver.layout.ty.kind() {
747773
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)