no more action on ref or cast, but add new MIR statement for escaping a ptr to raw

This commit is contained in:
Ralf Jung 2018-11-06 11:04:10 +01:00
parent 8315b11b63
commit f27cd60ae1
23 changed files with 159 additions and 110 deletions

View file

@ -257,6 +257,9 @@ for mir::StatementKind<'gcx> {
mir::StatementKind::EndRegion(ref region_scope) => {
region_scope.hash_stable(hcx, hasher);
}
mir::StatementKind::EscapeToRaw(ref place) => {
place.hash_stable(hcx, hasher);
}
mir::StatementKind::Retag { fn_entry, ref place } => {
fn_entry.hash_stable(hcx, hasher);
place.hash_stable(hcx, hasher);

View file

@ -1766,6 +1766,13 @@ pub enum StatementKind<'tcx> {
place: Place<'tcx>,
},
/// Escape the given reference to a raw pointer, so that it can be accessed
/// without precise provenance tracking. These statements are currently only interpreted
/// by miri and only generated when "-Z mir-emit-retag" is passed.
/// See <https://internals.rust-lang.org/t/stacked-borrows-an-aliasing-model-for-rust/8153/>
/// for more details.
EscapeToRaw(Operand<'tcx>),
/// Mark one terminating point of a region scope (i.e. static region).
/// (The starting point(s) arise implicitly from borrows.)
EndRegion(region::Scope),
@ -1827,6 +1834,7 @@ impl<'tcx> Debug for Statement<'tcx> {
EndRegion(ref ce) => write!(fmt, "EndRegion({})", ty::ReScope(*ce)),
Retag { fn_entry, ref place } =>
write!(fmt, "Retag({}{:?})", if fn_entry { "[fn entry] " } else { "" }, place),
EscapeToRaw(ref place) => write!(fmt, "EscapeToRaw({:?})", place),
StorageLive(ref place) => write!(fmt, "StorageLive({:?})", place),
StorageDead(ref place) => write!(fmt, "StorageDead({:?})", place),
SetDiscriminant {
@ -2968,6 +2976,7 @@ EnumTypeFoldableImpl! {
(StatementKind::StorageDead)(a),
(StatementKind::InlineAsm) { asm, outputs, inputs },
(StatementKind::Retag) { fn_entry, place },
(StatementKind::EscapeToRaw)(place),
(StatementKind::EndRegion)(a),
(StatementKind::AscribeUserType)(a, v, b),
(StatementKind::Nop),

View file

@ -385,6 +385,9 @@ macro_rules! make_mir_visitor {
location
);
}
StatementKind::EscapeToRaw(ref $($mutability)* op) => {
self.visit_operand(op, location);
}
StatementKind::StorageLive(ref $($mutability)* local) => {
self.visit_local(
local,
@ -1022,7 +1025,7 @@ pub enum MutatingUseContext<'tcx> {
/// f(&mut x.y);
///
Projection,
/// Retagging (updating the "Stacked Borrows" tag)
/// Retagging, a "Stacked Borrows" shadow state operation
Retag,
}

View file

@ -105,8 +105,9 @@ impl FunctionCx<'a, 'll, 'tcx> {
bx
}
mir::StatementKind::FakeRead(..) |
mir::StatementKind::EndRegion(_) |
mir::StatementKind::EndRegion(..) |
mir::StatementKind::Retag { .. } |
mir::StatementKind::EscapeToRaw { .. } |
mir::StatementKind::AscribeUserType(..) |
mir::StatementKind::Nop => bx,
}

View file

@ -601,6 +601,7 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
StatementKind::Nop
| StatementKind::AscribeUserType(..)
| StatementKind::Retag { .. }
| StatementKind::EscapeToRaw { .. }
| StatementKind::StorageLive(..) => {
// `Nop`, `AscribeUserType`, `Retag`, and `StorageLive` are irrelevant
// to borrow check.

View file

@ -137,6 +137,7 @@ impl<'cx, 'tcx, 'gcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx, 'gcx> {
StatementKind::Nop |
StatementKind::AscribeUserType(..) |
StatementKind::Retag { .. } |
StatementKind::EscapeToRaw { .. } |
StatementKind::StorageLive(..) => {
// `Nop`, `AscribeUserType`, `Retag`, and `StorageLive` are irrelevant
// to borrow check.

View file

@ -1290,11 +1290,12 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
}
}
StatementKind::FakeRead(..)
| StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::StorageLive(..)
| StatementKind::StorageDead(..)
| StatementKind::InlineAsm { .. }
| StatementKind::EndRegion(_)
| StatementKind::Retag { .. }
| StatementKind::EscapeToRaw { .. }
| StatementKind::Nop => {}
}
}

View file

@ -339,6 +339,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for Borrows<'a, 'gcx, 'tcx> {
mir::StatementKind::SetDiscriminant { .. } |
mir::StatementKind::StorageLive(..) |
mir::StatementKind::Retag { .. } |
mir::StatementKind::EscapeToRaw { .. } |
mir::StatementKind::AscribeUserType(..) |
mir::StatementKind::Nop => {}

View file

@ -301,8 +301,9 @@ impl<'b, 'a, 'gcx, 'tcx> Gatherer<'b, 'a, 'gcx, 'tcx> {
span_bug!(stmt.source_info.span,
"SetDiscriminant should not exist during borrowck");
}
StatementKind::EndRegion(_) |
StatementKind::EndRegion(..) |
StatementKind::Retag { .. } |
StatementKind::EscapeToRaw { .. } |
StatementKind::AscribeUserType(..) |
StatementKind::Nop => {}
}

View file

@ -44,28 +44,16 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
}
Misc => {
let src_layout = src.layout;
let src = self.read_immediate(src)?;
let src = if M::ENABLE_PTR_TRACKING_HOOKS && src_layout.ty.is_region_ptr() {
// The only `Misc` casts on references are those creating raw pointers.
assert!(dest.layout.ty.is_unsafe_ptr());
// For the purpose of the "ptr tag hooks", treat this as creating
// a new, raw reference.
let place = self.ref_to_mplace(src)?;
self.create_ref(place, None)?
} else {
*src
};
if self.type_is_fat_ptr(src_layout.ty) {
match (src, self.type_is_fat_ptr(dest.layout.ty)) {
if self.type_is_fat_ptr(src.layout.ty) {
match (*src, self.type_is_fat_ptr(dest.layout.ty)) {
// pointers to extern types
(Immediate::Scalar(_),_) |
// slices and trait objects to other slices/trait objects
(Immediate::ScalarPair(..), true) => {
// No change to immediate
self.write_immediate(src, dest)?;
self.write_immediate(*src, dest)?;
}
// slices and trait objects to thin pointers (dropping the metadata)
(Immediate::ScalarPair(data, _), false) => {
@ -73,11 +61,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
}
}
} else {
match src_layout.variants {
match src.layout.variants {
layout::Variants::Single { index } => {
if let Some(def) = src_layout.ty.ty_adt_def() {
if let Some(def) = src.layout.ty.ty_adt_def() {
// Cast from a univariant enum
assert!(src_layout.is_zst());
assert!(src.layout.is_zst());
let discr_val = def
.discriminant_for_variant(*self.tcx, index)
.val;
@ -90,8 +78,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
layout::Variants::NicheFilling { .. } => {},
}
let src = src.to_scalar()?;
let dest_val = self.cast_scalar(src, src_layout, dest.layout)?;
let dest_val = self.cast_scalar(src.to_scalar()?, src.layout, dest.layout)?;
self.write_scalar(dest_val, dest)?;
}
}

View file

@ -211,18 +211,6 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
kind: MemoryKind<Self::MemoryKinds>,
) -> EvalResult<'tcx, Pointer<Self::PointerTag>>;
/// Executed when evaluating the `&` operator: Creating a new reference.
/// This has the chance to adjust the tag. It should not change anything else!
/// `mutability` can be `None` in case a raw ptr is being created.
#[inline]
fn tag_reference(
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
place: MPlaceTy<'tcx, Self::PointerTag>,
_mutability: Option<hir::Mutability>,
) -> EvalResult<'tcx, Scalar<Self::PointerTag>> {
Ok(place.ptr)
}
/// Executed when evaluating the `*` operator: Following a reference.
/// This has the chance to adjust the tag. It should not change anything else!
/// `mutability` can be `None` in case a raw ptr is being dereferenced.
@ -235,7 +223,7 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
Ok(place.ptr)
}
/// Execute a validation operation
/// Execute a retagging operation
#[inline]
fn retag(
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
@ -244,4 +232,13 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
) -> EvalResult<'tcx> {
Ok(())
}
/// Execute an escape-to-raw operation
#[inline]
fn escape_to_raw(
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
_ptr: OpTy<'tcx, Self::PointerTag>,
) -> EvalResult<'tcx> {
Ok(())
}
}

View file

@ -151,6 +151,16 @@ impl<Tag> MemPlace<Tag> {
// it now must be aligned.
self.to_scalar_ptr_align().0.to_ptr()
}
/// Turn a mplace into a (thin or fat) pointer, as a reference, pointing to the same space.
/// This is the inverse of `ref_to_mplace`.
#[inline(always)]
pub fn to_ref(self) -> Immediate<Tag> {
match self.meta {
None => Immediate::Scalar(self.ptr.into()),
Some(meta) => Immediate::ScalarPair(self.ptr.into(), meta.into()),
}
}
}
impl<'tcx, Tag> MPlaceTy<'tcx, Tag> {
@ -266,7 +276,7 @@ where
M::MemoryMap: AllocMap<AllocId, (MemoryKind<M::MemoryKinds>, Allocation<Tag, M::AllocExtra>)>,
{
/// Take a value, which represents a (thin or fat) reference, and make it a place.
/// Alignment is just based on the type. This is the inverse of `create_ref`.
/// Alignment is just based on the type. This is the inverse of `MemPlace::to_ref()`.
pub fn ref_to_mplace(
&self,
val: ImmTy<'tcx, M::PointerTag>,
@ -294,24 +304,6 @@ where
Ok(mplace)
}
/// Turn a mplace into a (thin or fat) pointer, as a reference, pointing to the same space.
/// This is the inverse of `ref_to_mplace`.
/// `mutbl` indicates whether we are create a shared or mutable ref, or a raw pointer (`None`).
pub fn create_ref(
&mut self,
mut place: MPlaceTy<'tcx, M::PointerTag>,
mutbl: Option<hir::Mutability>,
) -> EvalResult<'tcx, Immediate<M::PointerTag>> {
// Pointer tag tracking might want to adjust the tag
if M::ENABLE_PTR_TRACKING_HOOKS {
place.mplace.ptr = M::tag_reference(self, place, mutbl)?
}
Ok(match place.meta {
None => Immediate::Scalar(place.ptr.into()),
Some(meta) => Immediate::ScalarPair(place.ptr.into(), meta.into()),
})
}
/// Offset a pointer to project to a field. Unlike place_field, this is always
/// possible without allocating, so it can take &self. Also return the field's layout.
/// This supports both struct and array fields.

View file

@ -12,7 +12,7 @@
//!
//! The main entry point is the `step` method.
use rustc::{hir, mir};
use rustc::mir;
use rustc::ty::layout::LayoutOf;
use rustc::mir::interpret::{EvalResult, Scalar, PointerArithmetic};
@ -118,12 +118,17 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
// interpreter is solely intended for borrowck'ed code.
FakeRead(..) => {}
// Retagging.
// Stacked Borrows.
Retag { fn_entry, ref place } => {
let dest = self.eval_place(place)?;
M::retag(self, fn_entry, dest)?;
}
EscapeToRaw(ref op) => {
let op = self.eval_operand(op, None)?;
M::escape_to_raw(self, op)?;
}
// Statements we do not track.
EndRegion(..) => {}
AscribeUserType(..) => {}
@ -247,19 +252,10 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
)?;
}
Ref(_, borrow_kind, ref place) => {
Ref(_, _, ref place) => {
let src = self.eval_place(place)?;
let val = self.force_allocation(src)?;
let mutbl = match borrow_kind {
mir::BorrowKind::Mut { .. } |
mir::BorrowKind::Unique =>
hir::MutMutable,
mir::BorrowKind::Shared |
mir::BorrowKind::Shallow =>
hir::MutImmutable,
};
let val = self.create_ref(val, Some(mutbl))?;
self.write_immediate(val, dest)?;
self.write_immediate(val.to_ref(), dest)?;
}
NullaryOp(mir::NullOp::Box, _) => {

View file

@ -447,10 +447,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
};
let arg = OpTy {
op: Operand::Immediate(self.create_ref(
place,
None // this is a "raw reference"
)?),
op: Operand::Immediate(place.to_ref()),
layout: self.layout_of(self.tcx.mk_mut_ptr(place.layout.ty))?,
};

View file

@ -20,20 +20,22 @@ use transform::{MirPass, MirSource};
pub struct AddRetag;
/// Determines whether this place is local: If it is part of a local variable.
/// We do not consider writes to pointers local, only writes that immediately assign
/// to a local variable.
/// One important property here is that evaluating the place immediately after
/// the assignment must produce the same place as what was used during the assignment.
fn is_local<'tcx>(
/// Determines whether this place is "stable": Whether, if we evaluate it again
/// after the assignment, we can be sure to obtain the same place value.
/// (Concurrent accesses by other threads are no problem as these are anyway non-atomic
/// copies. Data races are UB.)
fn is_stable<'tcx>(
place: &Place<'tcx>,
) -> bool {
use rustc::mir::Place::*;
match *place {
Local { .. } => true,
Promoted(_) |
Static(_) => false,
// Locals and statics have stable addresses, for sure
Local { .. } |
Promoted { .. } |
Static { .. } =>
true,
// Recurse for projections
Projection(ref proj) => {
match proj.elem {
ProjectionElem::Deref |
@ -47,15 +49,15 @@ fn is_local<'tcx>(
ProjectionElem::Subslice { .. } |
ProjectionElem::Downcast { .. } =>
// These just offset by a constant, entirely independent of everything else.
is_local(&proj.base),
is_stable(&proj.base),
}
}
}
}
/// Determine whether this type has a reference in it, recursing below compound types but
/// Determine whether this type may have a reference in it, recursing below compound types but
/// not below references.
fn has_reference<'a, 'gcx, 'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> bool {
fn may_have_reference<'a, 'gcx, 'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> bool {
match ty.sty {
// Primitive types that are not references
ty::Bool | ty::Char |
@ -68,12 +70,12 @@ fn has_reference<'a, 'gcx, 'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> b
ty::Adt(..) if ty.is_box() => true,
// Compound types
ty::Array(ty, ..) | ty::Slice(ty) =>
has_reference(ty, tcx),
may_have_reference(ty, tcx),
ty::Tuple(tys) =>
tys.iter().any(|ty| has_reference(ty, tcx)),
tys.iter().any(|ty| may_have_reference(ty, tcx)),
ty::Adt(adt, substs) =>
adt.variants.iter().any(|v| v.fields.iter().any(|f|
has_reference(f.ty(tcx, substs), tcx)
may_have_reference(f.ty(tcx, substs), tcx)
)),
// Conservative fallback
_ => true,
@ -92,7 +94,9 @@ impl MirPass for AddRetag {
let (span, arg_count) = (mir.span, mir.arg_count);
let (basic_blocks, local_decls) = mir.basic_blocks_and_local_decls_mut();
let needs_retag = |place: &Place<'tcx>| {
is_local(place) && has_reference(place.ty(&*local_decls, tcx).to_ty(tcx), tcx)
// FIXME: Instead of giving up for unstable places, we should introduce
// a temporary and retag on that.
is_stable(place) && may_have_reference(place.ty(&*local_decls, tcx).to_ty(tcx), tcx)
};
// PART 1
@ -118,23 +122,29 @@ impl MirPass for AddRetag {
}
// PART 2
// Retag return values of functions.
// Retag return values of functions. Also escape-to-raw the argument of `drop`.
// We collect the return destinations because we cannot mutate while iterating.
let mut returns: Vec<(SourceInfo, Place<'tcx>, BasicBlock)> = Vec::new();
for block_data in basic_blocks.iter_mut() {
match block_data.terminator {
Some(Terminator { kind: TerminatorKind::Call { ref destination, .. },
source_info }) => {
match block_data.terminator().kind {
TerminatorKind::Call { ref destination, .. } => {
// Remember the return destination for later
if let Some(ref destination) = destination {
if needs_retag(&destination.0) {
returns.push((source_info, destination.0.clone(), destination.1));
returns.push((
block_data.terminator().source_info,
destination.0.clone(),
destination.1,
));
}
}
}
TerminatorKind::Drop { .. } |
TerminatorKind::DropAndReplace { .. } => {
// `Drop` is also a call, but it doesn't return anything so we are good.
}
_ => {
// Not a block ending in a Call -> ignore.
// `Drop` is also a call, but it doesn't return anything so we are good.
}
}
}
@ -153,21 +163,43 @@ impl MirPass for AddRetag {
// iterate backwards using indices.
for i in (0..block_data.statements.len()).rev() {
match block_data.statements[i].kind {
// Assignments can make values obtained elsewhere "local".
// We could try to be smart here and e.g. only retag if the assignment
// loaded from memory, but that seems risky: We might miss a subtle corner
// case.
StatementKind::Assign(ref place, box Rvalue::Use(..))
if needs_retag(place) => {
// If we are casting *from* a reference, we may have to escape-to-raw.
StatementKind::Assign(_, box Rvalue::Cast(
CastKind::Misc,
ref src,
dest_ty,
)) => {
let src_ty = src.ty(&*local_decls, tcx);
if src_ty.is_region_ptr() {
// The only `Misc` casts on references are those creating raw pointers.
assert!(dest_ty.is_unsafe_ptr());
// Insert escape-to-raw before the cast. We are not concerned
// with stability here: Our EscapeToRaw will not change the value
// that the cast will then use.
// `src` might be a "move", but we rely on this not actually moving
// but just doing a memcpy. It is crucial that we do EscapeToRaw
// on the src because we need it with its original type.
let source_info = block_data.statements[i].source_info;
block_data.statements.insert(i, Statement {
source_info,
kind: StatementKind::EscapeToRaw(src.clone()),
});
}
}
// Assignments of reference or ptr type are the ones where we may have
// to update tags. This includes `x = &[mut] ...` and hence
// we also retag after taking a reference!
StatementKind::Assign(ref place, _) if needs_retag(place) => {
// Insert a retag after the assignment.
let source_info = block_data.statements[i].source_info;
block_data.statements.insert(i+1,Statement {
block_data.statements.insert(i+1, Statement {
source_info,
kind: StatementKind::Retag { fn_entry: false, place: place.clone() },
});
}
// Do nothing for the rest
_ => {},
}
};
}
}
}

View file

@ -114,6 +114,7 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
StatementKind::StorageDead(..) |
StatementKind::EndRegion(..) |
StatementKind::Retag { .. } |
StatementKind::EscapeToRaw { .. } |
StatementKind::AscribeUserType(..) |
StatementKind::Nop => {
// safe (at least as emitted during MIR construction)

View file

@ -1168,6 +1168,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
StatementKind::InlineAsm {..} |
StatementKind::EndRegion(_) |
StatementKind::Retag { .. } |
StatementKind::EscapeToRaw { .. } |
StatementKind::AscribeUserType(..) |
StatementKind::Nop => {}
}

View file

@ -242,6 +242,7 @@ fn check_statement(
| StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::Retag { .. }
| StatementKind::EscapeToRaw { .. }
| StatementKind::EndRegion(_)
| StatementKind::AscribeUserType(..)
| StatementKind::Nop => Ok(()),

View file

@ -65,10 +65,11 @@ impl RemoveNoopLandingPads {
// turn a landing pad to a non-nop
}
StatementKind::Assign(_, _) |
StatementKind::Assign { .. } |
StatementKind::SetDiscriminant { .. } |
StatementKind::InlineAsm { .. } |
StatementKind::Retag { .. } => {
StatementKind::Retag { .. } |
StatementKind::EscapeToRaw { .. } => {
return false;
}
}

View file

@ -163,6 +163,7 @@ fn each_block<'a, 'tcx, O>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
mir::StatementKind::InlineAsm { .. } |
mir::StatementKind::EndRegion(_) |
mir::StatementKind::Retag { .. } |
mir::StatementKind::EscapeToRaw { .. } |
mir::StatementKind::AscribeUserType(..) |
mir::StatementKind::Nop => continue,
mir::StatementKind::SetDiscriminant{ .. } =>

View file

@ -85,6 +85,7 @@ impl<'a, 'tcx> mir_visit::Visitor<'tcx> for StatCollector<'a, 'tcx> {
StatementKind::FakeRead(..) => "StatementKind::FakeRead",
StatementKind::EndRegion(..) => "StatementKind::EndRegion",
StatementKind::Retag { .. } => "StatementKind::Retag",
StatementKind::EscapeToRaw { .. } => "StatementKind::EscapeToRaw",
StatementKind::SetDiscriminant { .. } => "StatementKind::SetDiscriminant",
StatementKind::StorageLive(..) => "StatementKind::StorageLive",
StatementKind::StorageDead(..) => "StatementKind::StorageDead",

View file

@ -32,6 +32,8 @@ fn bar() -> bool {
// bb0: {
// ...
// Retag(_3);
// ...
// Retag(_3);
// Retag(_6);
// StorageLive(_9);
// _9 = (*_3);

View file

@ -26,7 +26,9 @@ fn main() {
{
let v = Test(0).foo(&mut x); // just making sure we do not panic when there is a tuple struct ctor
let w = { v }; // assignment
let _w = w; // reborrow
let w = w; // reborrow
// escape-to-raw (mut)
let _w = w as *mut _;
}
// Also test closures
@ -35,6 +37,9 @@ fn main() {
// need to call `foo_shr` or it doesn't even get generated
Test(0).foo_shr(&0);
// escape-to-raw (shr)
let _w = _w as *const _;
}
// END RUST SOURCE
@ -44,6 +49,7 @@ fn main() {
// Retag([fn entry] _2);
// ...
// _0 = &mut (*_3);
// Retag(_0);
// ...
// return;
// }
@ -73,23 +79,36 @@ fn main() {
// _9 = move _3;
// Retag(_9);
// _8 = &mut (*_9);
// Retag(_8);
// StorageDead(_9);
// StorageLive(_10);
// _10 = move _8;
// Retag(_10);
// ...
// _13 = move _14(move _15) -> bb2;
// _14 = &mut (*_10);
// Retag(_14);
// EscapeToRaw(move _14);
// _13 = move _14 as *mut i32 (Misc);
// ...
// _17 = move _18(move _19) -> bb2;
// }
//
// bb2: {
// Retag(_13);
// Retag(_17);
// ...
// _21 = const Test::foo_shr(move _22, move _24) -> bb3;
// }
//
// bb3: {
// ...
// return;
// }
//
// ...
// }
// END rustc.main.EraseRegions.after.mir
// START rustc.main-{{closure}}.EraseRegions.after.mir
// fn main::{{closure}}(_1: &[closure@NodeId(117)], _2: &i32) -> &i32 {
// fn main::{{closure}}(_1: &[closure@NodeId(124)], _2: &i32) -> &i32 {
// ...
// bb0: {
// Retag([fn entry] _1);