Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reading from the return place is fine #71005

Merged
merged 10 commits into from
Apr 23, 2020
3 changes: 0 additions & 3 deletions src/librustc_middle/mir/interpret/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,6 @@ pub enum UndefinedBehaviorInfo {
InvalidUndefBytes(Option<Pointer>),
/// Working with a local that is not currently live.
DeadLocal,
/// Trying to read from the return place of a function.
ReadFromReturnPlace,
}

impl fmt::Debug for UndefinedBehaviorInfo {
Expand Down Expand Up @@ -424,7 +422,6 @@ impl fmt::Debug for UndefinedBehaviorInfo {
"using uninitialized data, but this operation requires initialized memory"
),
DeadLocal => write!(f, "accessing a dead local variable"),
ReadFromReturnPlace => write!(f, "reading from return place"),
}
}
}
Expand Down
5 changes: 0 additions & 5 deletions src/librustc_middle/mir/interpret/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,6 @@ impl<'tcx, Tag> Scalar<Tag> {
}
}

#[inline]
pub fn null_ptr(cx: &impl HasDataLayout) -> Self {
Scalar::Raw { data: 0, size: cx.data_layout().pointer_size.bytes() as u8 }
}

#[inline]
pub fn zst() -> Self {
Scalar::Raw { data: 0, size: 0 }
Expand Down
76 changes: 31 additions & 45 deletions src/librustc_mir/interpret/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,35 +623,30 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let frame = M::init_frame_extra(self, pre_frame)?;
self.stack_mut().push(frame);

// don't allocate at all for trivial constants
if body.local_decls.len() > 1 {
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
// Locals are initially uninitialized.
let dummy = LocalState { value: LocalValue::Uninitialized, layout: Cell::new(None) };
let mut locals = IndexVec::from_elem(dummy, &body.local_decls);
// Return place is handled specially by the `eval_place` functions, and the
// entry in `locals` should never be used. Make it dead, to be sure.
locals[mir::RETURN_PLACE].value = LocalValue::Dead;
// Now mark those locals as dead that we do not want to initialize
match self.tcx.def_kind(instance.def_id()) {
// statics and constants don't have `Storage*` statements, no need to look for them
//
// FIXME: The above is likely untrue. See
// <https://github.com/rust-lang/rust/pull/70004#issuecomment-602022110>. Is it
// okay to ignore `StorageDead`/`StorageLive` annotations during CTFE?
Some(DefKind::Static | DefKind::Const | DefKind::AssocConst) => {}
_ => {
// Mark locals that use `Storage*` annotations as dead on function entry.
let always_live = AlwaysLiveLocals::new(self.body());
for local in locals.indices() {
if !always_live.contains(local) {
locals[local].value = LocalValue::Dead;
}
// Locals are initially uninitialized.
let dummy = LocalState { value: LocalValue::Uninitialized, layout: Cell::new(None) };
let mut locals = IndexVec::from_elem(dummy, &body.local_decls);

// Now mark those locals as dead that we do not want to initialize
match self.tcx.def_kind(instance.def_id()) {
// statics and constants don't have `Storage*` statements, no need to look for them
//
// FIXME: The above is likely untrue. See
// <https://github.com/rust-lang/rust/pull/70004#issuecomment-602022110>. Is it
// okay to ignore `StorageDead`/`StorageLive` annotations during CTFE?
Some(DefKind::Static | DefKind::Const | DefKind::AssocConst) => {}
_ => {
// Mark locals that use `Storage*` annotations as dead on function entry.
let always_live = AlwaysLiveLocals::new(self.body());
for local in locals.indices() {
if !always_live.contains(local) {
locals[local].value = LocalValue::Dead;
}
}
}
// done
self.frame_mut().locals = locals;
}
// done
self.frame_mut().locals = locals;

M::after_stack_push(self)?;
info!("ENTERING({}) {}", self.frame_idx(), self.frame().instance);
Expand Down Expand Up @@ -729,6 +724,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let frame =
self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");

if !unwinding {
// Copy the return value to the caller's stack frame.
if let Some(return_place) = frame.return_place {
let op = self.access_local(&frame, mir::RETURN_PLACE, None)?;
self.copy_op_transmute(op, return_place)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we can pull this off, but maybe we can (for non-immediates) make the return place alias with the frame's RETURN_PLACE local. This would be doable by changing the LocalState of the RETURN_PLACE to Live(Operand::Indirect(return_place)) (well you still need to convert the https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.PlaceTy.html to an MPlaceTy, but that should be doable (and where not, we have an immediate, which is cheap and we can just keep copying it here)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was wondering the same thing. We need to be careful then when popping the frame to not do copy_op (as that would overlap itself) but still do validation.

The main subtlety here is making sure this is a Miri-internal optimization that cannot be observed by the program. The fact that we reborrow the return place should exclude any observations by accessing the return place directly... so I think the only remaining possible observation is through address equality tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... about this PR. I'd be fine merging it with the 8% regression on the stress tests (with an open issue for fixing it again). Then we can experiment with this local aliasing trick in a PR that does nothing but the optimization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be nice. I don't think I have the bandwidth for these deeper changes to const eval.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that something we should nominate for T-compiler decision?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @rust-lang/wg-const-eval can make this decision on our own, since it's just a stress test being regressed. Regressing the test is not an issue, we just have the test to make sure we don't do so accidentally

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, fine for me. @ecstatic-morse what do you think?

@jonas-schievink can you create an issue describing the perf regression and linking to this discussion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #71463

self.dump_place(*return_place);
} else {
throw_ub!(Unreachable);
}
}

// Now where do we jump next?

// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
Expand All @@ -754,7 +760,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
self.deallocate_local(local.value)?;
}

let return_place = frame.return_place;
if M::after_stack_pop(self, frame, unwinding)? == StackPopJump::NoJump {
// The hook already did everything.
// We want to skip the `info!` below, hence early return.
Expand All @@ -767,25 +772,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
self.unwind_to_block(unwind);
} else {
// Follow the normal return edge.
// Validate the return value. Do this after deallocating so that we catch dangling
// references.
if let Some(return_place) = return_place {
if M::enforce_validity(self) {
// Data got changed, better make sure it matches the type!
// It is still possible that the return place held invalid data while
// the function is running, but that's okay because nobody could have
// accessed that same data from the "outside" to observe any broken
// invariant -- that is, unless a function somehow has a ptr to
// its return place... but the way MIR is currently generated, the
// return place is always a local and then this cannot happen.
self.validate_operand(self.place_to_op(return_place)?)?;
}
} else {
// Uh, that shouldn't happen... the function did not intend to return
throw_ub!(Unreachable);
}

// Jump to new block -- *after* validation so that the spans make more sense.
if let Some(ret) = next_block {
self.return_to_block(ret)?;
}
Expand Down
16 changes: 5 additions & 11 deletions src/librustc_mir/interpret/operand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
local: mir::Local,
layout: Option<TyAndLayout<'tcx>>,
) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
assert_ne!(local, mir::RETURN_PLACE);
let layout = self.layout_of_local(frame, local, layout)?;
let op = if layout.is_zst() {
// Do not read from ZST, they might not be initialized
Expand Down Expand Up @@ -454,16 +453,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
place: mir::Place<'tcx>,
layout: Option<TyAndLayout<'tcx>>,
) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
let base_op = match place.local {
mir::RETURN_PLACE => throw_ub!(ReadFromReturnPlace),
local => {
// Do not use the layout passed in as argument if the base we are looking at
// here is not the entire place.
let layout = if place.projection.is_empty() { layout } else { None };

self.access_local(self.frame(), local, layout)?
}
};
// Do not use the layout passed in as argument if the base we are looking at
// here is not the entire place.
let layout = if place.projection.is_empty() { layout } else { None };

let base_op = self.access_local(self.frame(), place.local, layout)?;

let op = place
.projection
Expand Down
45 changes: 4 additions & 41 deletions src/librustc_mir/interpret/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,6 @@ impl<Tag> MemPlace<Tag> {
MemPlace { ptr, align, meta: MemPlaceMeta::None }
}

/// Produces a Place that will error if attempted to be read from or written to
#[inline(always)]
fn null(cx: &impl HasDataLayout) -> Self {
Self::from_scalar_ptr(Scalar::null_ptr(cx), Align::from_bytes(1).unwrap())
}

#[inline(always)]
pub fn from_ptr(ptr: Pointer<Tag>, align: Align) -> Self {
Self::from_scalar_ptr(ptr.into(), align)
Expand Down Expand Up @@ -260,12 +254,6 @@ impl<'tcx, Tag: ::std::fmt::Debug + Copy> OpTy<'tcx, Tag> {
}

impl<Tag: ::std::fmt::Debug> Place<Tag> {
/// Produces a Place that will error if attempted to be read from or written to
#[inline(always)]
fn null(cx: &impl HasDataLayout) -> Self {
Place::Ptr(MemPlace::null(cx))
}

#[inline]
pub fn assert_mem_place(self) -> MemPlace<Tag> {
match self {
Expand Down Expand Up @@ -636,35 +624,10 @@ where
&mut self,
place: mir::Place<'tcx>,
) -> InterpResult<'tcx, PlaceTy<'tcx, M::PointerTag>> {
let mut place_ty = match place.local {
mir::RETURN_PLACE => {
// `return_place` has the *caller* layout, but we want to use our
// `layout to verify our assumption. The caller will validate
// their layout on return.
PlaceTy {
place: match self.frame().return_place {
Some(p) => *p,
// Even if we don't have a return place, we sometimes need to
// create this place, but any attempt to read from / write to it
// (even a ZST read/write) needs to error, so let us make this
// a NULL place.
//
// FIXME: Ideally we'd make sure that the place projections also
// bail out.
None => Place::null(&*self),
},
layout: self.layout_of(
self.subst_from_current_frame_and_normalize_erasing_regions(
self.frame().body.return_ty(),
),
)?,
}
}
local => PlaceTy {
// This works even for dead/uninitialized locals; we check further when writing
place: Place::Local { frame: self.frame_idx(), local },
layout: self.layout_of_local(self.frame(), local, None)?,
},
let mut place_ty = PlaceTy {
// This works even for dead/uninitialized locals; we check further when writing
place: Place::Local { frame: self.frame_idx(), local: place.local },
layout: self.layout_of_local(self.frame(), place.local, None)?,
};

for elem in place.projection.iter() {
Expand Down
1 change: 0 additions & 1 deletion src/librustc_mir/interpret/terminator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
use rustc_middle::mir::TerminatorKind::*;
match terminator.kind {
Return => {
self.frame().return_place.map(|r| self.dump_place(*r));
jonas-schievink marked this conversation as resolved.
Show resolved Hide resolved
self.pop_stack_frame(/* unwinding */ false)?
}

Expand Down
18 changes: 7 additions & 11 deletions src/librustc_mir/transform/const_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,6 @@ struct ConstPropagator<'mir, 'tcx> {
// by accessing them through `ecx` instead.
source_scopes: IndexVec<SourceScope, SourceScopeData>,
local_decls: IndexVec<Local, LocalDecl<'tcx>>,
ret: Option<OpTy<'tcx, ()>>,
// Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
// the last known `SourceInfo` here and just keep revisiting it.
source_info: Option<SourceInfo>,
Expand Down Expand Up @@ -403,22 +402,19 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
source_scopes: body.source_scopes.clone(),
//FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_body()` needs it
local_decls: body.local_decls.clone(),
ret: ret.map(Into::into),
source_info: None,
}
}

fn get_const(&self, local: Local) -> Option<OpTy<'tcx>> {
if local == RETURN_PLACE {
// Try to read the return place as an immediate so that if it is representable as a
// scalar, we can handle it as such, but otherwise, just return the value as is.
return match self.ret.map(|ret| self.ecx.try_read_immediate(ret)) {
Some(Ok(Ok(imm))) => Some(imm.into()),
_ => self.ret,
};
}
let op = self.ecx.access_local(self.ecx.frame(), local, None).ok();

self.ecx.access_local(self.ecx.frame(), local, None).ok()
// Try to read the local as an immediate so that if it is representable as a scalar, we can
// handle it as such, but otherwise, just return the value as is.
match op.map(|ret| self.ecx.try_read_immediate(ret)) {
Some(Ok(Ok(imm))) => Some(imm.into()),
_ => op,
}
}

fn remove_const(&mut self, local: Local) {
Expand Down
4 changes: 2 additions & 2 deletions src/test/codegen/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
// CHECK: @STATIC = {{.*}}, align 4

// This checks the constants from inline_enum_const
// CHECK: @alloc5 = {{.*}}, align 2
// CHECK: @alloc7 = {{.*}}, align 2

// This checks the constants from {low,high}_align_const, they share the same
// constant, but the alignment differs, so the higher one should be used
// CHECK: [[LOW_HIGH:@[0-9]+]] = {{.*}} getelementptr inbounds (<{ [8 x i8] }>, <{ [8 x i8] }>* @alloc15, i32 0, i32 0, i32 0), {{.*}}
// CHECK: [[LOW_HIGH:@[0-9]+]] = {{.*}} getelementptr inbounds (<{ [8 x i8] }>, <{ [8 x i8] }>* @alloc19, i32 0, i32 0, i32 0), {{.*}}

#[derive(Copy, Clone)]
// repr(i16) is required for the {low,high}_align_const test
Expand Down
8 changes: 4 additions & 4 deletions src/test/incremental/hashes/enum_constructors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,29 +274,29 @@ pub enum Clike2 {
// Change constructor path (C-like) --------------------------------------
#[cfg(cfail1)]
pub fn change_constructor_path_c_like() {
let _ = Clike::B;
let _x = Clike::B;
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="hir_owner_nodes,optimized_mir,mir_built,typeck_tables_of")]
#[rustc_clean(cfg="cfail3")]
pub fn change_constructor_path_c_like() {
let _ = Clike2::B;
let _x = Clike2::B;
}



// Change constructor variant (C-like) --------------------------------------
#[cfg(cfail1)]
pub fn change_constructor_variant_c_like() {
let _ = Clike::A;
let _x = Clike::A;
}

#[cfg(not(cfail1))]
#[rustc_clean(cfg="cfail2", except="hir_owner_nodes,optimized_mir,mir_built")]
#[rustc_clean(cfg="cfail3")]
pub fn change_constructor_variant_c_like() {
let _ = Clike::C;
let _x = Clike::C;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,41 +30,41 @@ fn main() -> () {
}

alloc0 (static: FOO, size: 8, align: 4) {
alloc24+0╼ 03 00 00 00 │ ╾──╼....
alloc25+0╼ 03 00 00 00 │ ╾──╼....
}

alloc24 (size: 48, align: 4) {
0x00 │ 00 00 00 00 __ __ __ __ ╾alloc9+0─╼ 00 00 00 00 │ ....░░░░╾──╼....
0x10 │ 00 00 00 00 __ __ __ __ ╾alloc14+0╼ 02 00 00 00 │ ....░░░░╾──╼....
0x20 │ 01 00 00 00 2a 00 00 00 ╾alloc22+0╼ 03 00 00 00 │ ....*...╾──╼....
alloc25 (size: 48, align: 4) {
0x00 │ 00 00 00 00 __ __ __ __ ╾alloc10+0╼ 00 00 00 00 │ ....░░░░╾──╼....
0x10 │ 00 00 00 00 __ __ __ __ ╾alloc15+0╼ 02 00 00 00 │ ....░░░░╾──╼....
0x20 │ 01 00 00 00 2a 00 00 00 ╾alloc23+0╼ 03 00 00 00 │ ....*...╾──╼....
}

alloc9 (size: 0, align: 4) {}
alloc10 (size: 0, align: 4) {}

alloc14 (size: 8, align: 4) {
alloc12+0╼ ╾alloc13+0╼ │ ╾──╼╾──╼
alloc15 (size: 8, align: 4) {
alloc13+0╼ ╾alloc14+0╼ │ ╾──╼╾──╼
}

alloc12 (size: 1, align: 1) {
alloc13 (size: 1, align: 1) {
05 │ .
}

alloc13 (size: 1, align: 1) {
alloc14 (size: 1, align: 1) {
06 │ .
}

alloc22 (size: 12, align: 4) {
alloc18+3╼ ╾alloc19+0╼ ╾alloc21+2╼ │ ╾──╼╾──╼╾──╼
alloc23 (size: 12, align: 4) {
alloc19+3╼ ╾alloc20+0╼ ╾alloc22+2╼ │ ╾──╼╾──╼╾──╼
}

alloc18 (size: 4, align: 1) {
alloc19 (size: 4, align: 1) {
2a 45 15 6f │ *E.o
}

alloc19 (size: 1, align: 1) {
alloc20 (size: 1, align: 1) {
2a │ *
}

alloc21 (size: 4, align: 1) {
alloc22 (size: 4, align: 1) {
2a 45 15 6f │ *E.o
}
Loading