Skip to content

Commit b9f4e0d

Browse files
committed
Erase all block-only locals at the end of every block, even if they have not been touched.
1 parent 394e1b4 commit b9f4e0d

File tree

5 files changed

+90
-16
lines changed

5 files changed

+90
-16
lines changed

src/librustc_mir/interpret/eval_context.rs

+7
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ pub enum LocalValue<Tag = ()> {
131131
}
132132

133133
impl<'tcx, Tag: Copy + 'static> LocalState<'tcx, Tag> {
134+
/// Read the local's value or error if the local is not yet live or not live anymore.
135+
///
136+
/// Note: This may only be invoked from the `Machine::access_local` hook and not from
137+
/// anywhere else. You may be invalidating machine invariants if you do!
134138
pub fn access(&self) -> InterpResult<'tcx, Operand<Tag>> {
135139
match self.value {
136140
LocalValue::Dead => throw_ub!(DeadLocal),
@@ -143,6 +147,9 @@ impl<'tcx, Tag: Copy + 'static> LocalState<'tcx, Tag> {
143147

144148
/// Overwrite the local. If the local can be overwritten in place, return a reference
145149
/// to do so; otherwise return the `MemPlace` to consult instead.
150+
///
151+
/// Note: This may only be invoked from the `Machine::access_local_mut` hook and not from
152+
/// anywhere else. You may be invalidating machine invariants if you do!
146153
pub fn access_mut(
147154
&mut self,
148155
) -> InterpResult<'tcx, Result<&mut LocalValue<Tag>, MemPlace<Tag>>> {

src/librustc_mir/interpret/machine.rs

+18-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_span::def_id::DefId;
1111

1212
use super::{
1313
AllocId, Allocation, AllocationExtra, CheckInAllocMsg, Frame, ImmTy, InterpCx, InterpResult,
14-
Memory, MemoryKind, OpTy, Operand, PlaceTy, Pointer, Scalar,
14+
LocalValue, MemPlace, Memory, MemoryKind, OpTy, Operand, PlaceTy, Pointer, Scalar,
1515
};
1616

1717
/// Data returned by Machine::stack_pop,
@@ -192,6 +192,8 @@ pub trait Machine<'mir, 'tcx>: Sized {
192192
) -> InterpResult<'tcx>;
193193

194194
/// Called to read the specified `local` from the `frame`.
195+
/// Since reading a ZST is not actually accessing memory or locals, this is never invoked
196+
/// for ZST reads.
195197
#[inline]
196198
fn access_local(
197199
_ecx: &InterpCx<'mir, 'tcx, Self>,
@@ -201,6 +203,21 @@ pub trait Machine<'mir, 'tcx>: Sized {
201203
frame.locals[local].access()
202204
}
203205

206+
/// Called to write the specified `local` from the `frame`.
207+
/// Since writing a ZST is not actually accessing memory or locals, this is never invoked
208+
/// for ZST reads.
209+
#[inline]
210+
fn access_local_mut<'a>(
211+
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
212+
frame: usize,
213+
local: mir::Local,
214+
) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>>
215+
where
216+
'tcx: 'mir,
217+
{
218+
ecx.stack_mut()[frame].locals[local].access_mut()
219+
}
220+
204221
/// Called before a basic block terminator is executed.
205222
/// You can use this to detect endlessly running programs.
206223
#[inline]

src/librustc_mir/interpret/operand.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
432432
})
433433
}
434434

435-
/// This is used by [priroda](https://github.com/oli-obk/priroda) to get an OpTy from a local
435+
/// Read from a local. Will not actually access the local if reading from a ZST.
436+
/// Will not access memory, instead an indirect `Operand` is returned.
437+
///
438+
/// This is public because it is used by [priroda](https://github.com/oli-obk/priroda) to get an
439+
/// OpTy from a local
436440
pub fn access_local(
437441
&self,
438442
frame: &super::Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra>,

src/librustc_mir/interpret/place.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -740,7 +740,7 @@ where
740740
// but not factored as a separate function.
741741
let mplace = match dest.place {
742742
Place::Local { frame, local } => {
743-
match self.stack_mut()[frame].locals[local].access_mut()? {
743+
match M::access_local_mut(self, frame, local)? {
744744
Ok(local) => {
745745
// Local can be updated in-place.
746746
*local = LocalValue::Live(Operand::Immediate(src));
@@ -973,7 +973,7 @@ where
973973
) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::PointerTag>, Option<Size>)> {
974974
let (mplace, size) = match place.place {
975975
Place::Local { frame, local } => {
976-
match self.stack_mut()[frame].locals[local].access_mut()? {
976+
match M::access_local_mut(self, frame, local)? {
977977
Ok(&mut local_val) => {
978978
// We need to make an allocation.
979979

@@ -997,7 +997,7 @@ where
997997
}
998998
// Now we can call `access_mut` again, asserting it goes well,
999999
// and actually overwrite things.
1000-
*self.stack_mut()[frame].locals[local].access_mut().unwrap().unwrap() =
1000+
*M::access_local_mut(self, frame, local).unwrap().unwrap() =
10011001
LocalValue::Live(Operand::Indirect(mplace));
10021002
(mplace, Some(size))
10031003
}

src/librustc_mir/transform/const_prop.rs

+57-11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use std::cell::Cell;
55

66
use rustc_ast::ast::Mutability;
7+
use rustc_data_structures::fx::FxHashSet;
78
use rustc_hir::def::DefKind;
89
use rustc_hir::HirId;
910
use rustc_index::bit_set::BitSet;
@@ -28,7 +29,7 @@ use rustc_trait_selection::traits;
2829
use crate::const_eval::error_to_const_error;
2930
use crate::interpret::{
3031
self, compile_time_machine, AllocId, Allocation, Frame, ImmTy, Immediate, InterpCx, LocalState,
31-
LocalValue, Memory, MemoryKind, OpTy, Operand as InterpOperand, PlaceTy, Pointer,
32+
LocalValue, MemPlace, Memory, MemoryKind, OpTy, Operand as InterpOperand, PlaceTy, Pointer,
3233
ScalarMaybeUninit, StackPopCleanup,
3334
};
3435
use crate::transform::{MirPass, MirSource};
@@ -151,11 +152,19 @@ impl<'tcx> MirPass<'tcx> for ConstProp {
151152
struct ConstPropMachine<'mir, 'tcx> {
152153
/// The virtual call stack.
153154
stack: Vec<Frame<'mir, 'tcx, (), ()>>,
155+
/// `OnlyInsideOwnBlock` locals that were written in the current block get erased at the end.
156+
written_only_inside_own_block_locals: FxHashSet<Local>,
157+
/// Locals that need to be cleared after every block terminates.
158+
only_propagate_inside_block_locals: BitSet<Local>,
154159
}
155160

156161
impl<'mir, 'tcx> ConstPropMachine<'mir, 'tcx> {
157-
fn new() -> Self {
158-
Self { stack: Vec::new() }
162+
fn new(only_propagate_inside_block_locals: BitSet<Local>) -> Self {
163+
Self {
164+
stack: Vec::new(),
165+
written_only_inside_own_block_locals: Default::default(),
166+
only_propagate_inside_block_locals,
167+
}
159168
}
160169
}
161170

@@ -227,6 +236,18 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
227236
l.access()
228237
}
229238

239+
fn access_local_mut<'a>(
240+
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
241+
frame: usize,
242+
local: Local,
243+
) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>>
244+
{
245+
if frame == 0 && ecx.machine.only_propagate_inside_block_locals.contains(local) {
246+
ecx.machine.written_only_inside_own_block_locals.insert(local);
247+
}
248+
ecx.machine.stack[frame].locals[local].access_mut()
249+
}
250+
230251
fn before_access_global(
231252
_memory_extra: &(),
232253
_alloc_id: AllocId,
@@ -274,8 +295,6 @@ struct ConstPropagator<'mir, 'tcx> {
274295
// Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
275296
// the last known `SourceInfo` here and just keep revisiting it.
276297
source_info: Option<SourceInfo>,
277-
// Locals we need to forget at the end of the current block
278-
locals_of_current_block: BitSet<Local>,
279298
}
280299

281300
impl<'mir, 'tcx> LayoutOf for ConstPropagator<'mir, 'tcx> {
@@ -313,8 +332,20 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
313332
let param_env = tcx.param_env(def_id).with_reveal_all();
314333

315334
let span = tcx.def_span(def_id);
316-
let mut ecx = InterpCx::new(tcx, span, param_env, ConstPropMachine::new(), ());
317335
let can_const_prop = CanConstProp::check(body);
336+
let mut only_propagate_inside_block_locals = BitSet::new_empty(can_const_prop.len());
337+
for (l, mode) in can_const_prop.iter_enumerated() {
338+
if *mode == ConstPropMode::OnlyInsideOwnBlock {
339+
only_propagate_inside_block_locals.insert(l);
340+
}
341+
}
342+
let mut ecx = InterpCx::new(
343+
tcx,
344+
span,
345+
param_env,
346+
ConstPropMachine::new(only_propagate_inside_block_locals),
347+
(),
348+
);
318349

319350
let ret = ecx
320351
.layout_of(body.return_ty().subst(tcx, substs))
@@ -345,7 +376,6 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
345376
//FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_body()` needs it
346377
local_decls: body.local_decls.clone(),
347378
source_info: None,
348-
locals_of_current_block: BitSet::new_empty(body.local_decls.len()),
349379
}
350380
}
351381

@@ -899,7 +929,6 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
899929
Will remove it from const-prop after block is finished. Local: {:?}",
900930
place.local
901931
);
902-
self.locals_of_current_block.insert(place.local);
903932
}
904933
ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
905934
trace!("can't propagate into {:?}", place);
@@ -1088,10 +1117,27 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
10881117
}
10891118
}
10901119
}
1091-
// We remove all Locals which are restricted in propagation to their containing blocks.
1092-
for local in self.locals_of_current_block.iter() {
1120+
1121+
// We remove all Locals which are restricted in propagation to their containing blocks and
1122+
// which were modified in the current block.
1123+
// Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`
1124+
let mut locals = std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals);
1125+
for &local in locals.iter() {
10931126
Self::remove_const(&mut self.ecx, local);
10941127
}
1095-
self.locals_of_current_block.clear();
1128+
locals.clear();
1129+
// Put it back so we reuse the heap of the storage
1130+
self.ecx.machine.written_only_inside_own_block_locals = locals;
1131+
if cfg!(debug_assertions) {
1132+
// Ensure we are correctly erasing locals with the non-debug-assert logic.
1133+
for local in self.ecx.machine.only_propagate_inside_block_locals.iter() {
1134+
assert!(
1135+
self.get_const(local.into()).is_none()
1136+
|| self
1137+
.layout_of(self.local_decls[local].ty)
1138+
.map_or(true, |layout| layout.is_zst())
1139+
)
1140+
}
1141+
}
10961142
}
10971143
}

0 commit comments

Comments
 (0)