librustc: Implement def-use chains and trivial copy propagation on MIR.

This only supports trivial cases in which there is exactly one def and
one use.
This commit is contained in:
Patrick Walton 2016-09-15 18:18:40 -07:00
parent 2e6a91812c
commit 480287ec3b
14 changed files with 560 additions and 32 deletions

View File

@ -188,6 +188,24 @@ impl<'tcx> Mir<'tcx> {
self.temp_decls.len() + 1
}
pub fn format_local(&self, local: Local) -> String {
let mut index = local.index();
index = match index.checked_sub(self.arg_decls.len()) {
None => return format!("{:?}", Arg::new(index)),
Some(index) => index,
};
index = match index.checked_sub(self.var_decls.len()) {
None => return format!("{:?}", Var::new(index)),
Some(index) => index,
};
index = match index.checked_sub(self.temp_decls.len()) {
None => return format!("{:?}", Temp::new(index)),
Some(index) => index,
};
debug_assert!(index == 0);
return "ReturnPointer".to_string()
}
/// Changes a statement to a nop. This is both faster than deleting instructions and avoids
/// invalidating statement indices in `Location`s.
pub fn make_statement_nop(&mut self, location: Location) {
@ -844,6 +862,24 @@ impl<'tcx> Lvalue<'tcx> {
elem: elem,
}))
}
pub fn from_local(mir: &Mir<'tcx>, local: Local) -> Lvalue<'tcx> {
let mut index = local.index();
index = match index.checked_sub(mir.arg_decls.len()) {
None => return Lvalue::Arg(Arg(index as u32)),
Some(index) => index,
};
index = match index.checked_sub(mir.var_decls.len()) {
None => return Lvalue::Var(Var(index as u32)),
Some(index) => index,
};
index = match index.checked_sub(mir.temp_decls.len()) {
None => return Lvalue::Temp(Temp(index as u32)),
Some(index) => index,
};
debug_assert!(index == 0);
Lvalue::ReturnPointer
}
}
impl<'tcx> Debug for Lvalue<'tcx> {
@ -1278,3 +1314,13 @@ impl fmt::Debug for Location {
write!(fmt, "{:?}[{}]", self.block, self.statement_index)
}
}
impl Location {
pub fn dominates(&self, other: &Location, dominators: &Dominators<BasicBlock>) -> bool {
if self.block == other.block {
self.statement_index <= other.statement_index
} else {
dominators.is_dominated_by(other.block, self.block)
}
}
}

View File

@ -150,7 +150,7 @@ macro_rules! make_mir_visitor {
fn visit_lvalue(&mut self,
lvalue: & $($mutability)* Lvalue<'tcx>,
context: LvalueContext,
context: LvalueContext<'tcx>,
location: Location) {
self.super_lvalue(lvalue, context, location);
}
@ -581,7 +581,7 @@ macro_rules! make_mir_visitor {
fn super_lvalue(&mut self,
lvalue: & $($mutability)* Lvalue<'tcx>,
context: LvalueContext,
context: LvalueContext<'tcx>,
location: Location) {
match *lvalue {
Lvalue::Var(_) |
@ -606,7 +606,12 @@ macro_rules! make_mir_visitor {
ref $($mutability)* base,
ref $($mutability)* elem,
} = *proj;
self.visit_lvalue(base, LvalueContext::Projection, location);
let context = if context.is_mutating_use() {
LvalueContext::Projection(Mutability::Mut)
} else {
LvalueContext::Projection(Mutability::Not)
};
self.visit_lvalue(base, context, location);
self.visit_projection_elem(elem, context, location);
}
@ -751,6 +756,21 @@ macro_rules! make_mir_visitor {
fn super_const_usize(&mut self, _substs: & $($mutability)* ConstUsize) {
}
// Convenience methods
fn visit_location(&mut self, mir: & $($mutability)* Mir<'tcx>, location: Location) {
let basic_block = & $($mutability)* mir[location.block];
if basic_block.statements.len() == location.statement_index {
if let Some(ref $($mutability)* terminator) = basic_block.terminator {
self.visit_terminator(location.block, terminator, location)
}
} else {
let statement = & $($mutability)*
basic_block.statements[location.statement_index];
self.visit_statement(location.block, statement, location)
}
}
}
}
}
@ -775,8 +795,20 @@ pub enum LvalueContext<'tcx> {
// Being borrowed
Borrow { region: &'tcx Region, kind: BorrowKind },
// Used as base for another lvalue, e.g. `x` in `x.y`
Projection,
// Used as base for another lvalue, e.g. `x` in `x.y`.
//
// The `Mutability` argument specifies whether the projection is being performed in order to
// (potentially) mutate the lvalue. For example, the projection `x.y` is marked as a mutation
// in these cases:
//
// x.y = ...;
// f(&mut x.y);
//
// But not in these cases:
//
// z = x.y;
// f(&x.y);
Projection(Mutability),
// Consumed as part of an operand
Consume,
@ -785,3 +817,69 @@ pub enum LvalueContext<'tcx> {
StorageLive,
StorageDead,
}
impl<'tcx> LvalueContext<'tcx> {
/// Returns true if this lvalue context represents a drop.
pub fn is_drop(&self) -> bool {
match *self {
LvalueContext::Drop => true,
_ => false,
}
}
/// Returns true if this lvalue context represents a storage live or storage dead marker.
pub fn is_storage_marker(&self) -> bool {
match *self {
LvalueContext::StorageLive | LvalueContext::StorageDead => true,
_ => false,
}
}
/// Returns true if this lvalue context represents a storage live marker.
pub fn is_storage_live_marker(&self) -> bool {
match *self {
LvalueContext::StorageLive => true,
_ => false,
}
}
/// Returns true if this lvalue context represents a storage dead marker.
pub fn is_storage_dead_marker(&self) -> bool {
match *self {
LvalueContext::StorageDead => true,
_ => false,
}
}
/// Returns true if this lvalue context represents a use that potentially changes the value.
pub fn is_mutating_use(&self) -> bool {
match *self {
LvalueContext::Store | LvalueContext::Call |
LvalueContext::Borrow { kind: BorrowKind::Mut, .. } |
LvalueContext::Projection(Mutability::Mut) |
LvalueContext::Drop => true,
LvalueContext::Inspect |
LvalueContext::Borrow { kind: BorrowKind::Shared, .. } |
LvalueContext::Borrow { kind: BorrowKind::Unique, .. } |
LvalueContext::Projection(Mutability::Not) | LvalueContext::Consume |
LvalueContext::StorageLive | LvalueContext::StorageDead => false,
}
}
/// Returns true if this lvalue context represents a use that does not change the value.
pub fn is_nonmutating_use(&self) -> bool {
match *self {
LvalueContext::Inspect | LvalueContext::Borrow { kind: BorrowKind::Shared, .. } |
LvalueContext::Borrow { kind: BorrowKind::Unique, .. } |
LvalueContext::Projection(Mutability::Not) | LvalueContext::Consume => true,
LvalueContext::Borrow { kind: BorrowKind::Mut, .. } | LvalueContext::Store |
LvalueContext::Call | LvalueContext::Projection(Mutability::Mut) |
LvalueContext::Drop | LvalueContext::StorageLive | LvalueContext::StorageDead => false,
}
}
pub fn is_use(&self) -> bool {
self.is_mutating_use() || self.is_nonmutating_use()
}
}

View File

@ -438,6 +438,7 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
span_bug!(stmt.source_info.span,
"SetDiscriminant should not exist during borrowck");
}
StatementKind::Nop => {}
}
}

View File

@ -1028,6 +1028,7 @@ pub fn phase_4_translate_to_llvm<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
// No lifetime analysis based on borrowing can be done from here on out.
passes.push_pass(box mir::transform::instcombine::InstCombine::new());
passes.push_pass(box mir::transform::deaggregator::Deaggregator);
passes.push_pass(box mir::transform::copy_prop::CopyPropagation);
passes.push_pass(box mir::transform::add_call_guards::AddCallGuards);
passes.push_pass(box mir::transform::dump_mir::Marker("PreTrans"));

197
src/librustc_mir/def_use.rs Normal file
View File

@ -0,0 +1,197 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Def-use analysis.
use rustc::mir::repr::{Local, Location, Lvalue, Mir};
use rustc::mir::visit::{LvalueContext, MutVisitor, Visitor};
use rustc_data_structures::indexed_vec::{Idx, IndexVec};
use std::marker::PhantomData;
use std::mem;
pub struct DefUseAnalysis<'tcx> {
info: IndexVec<Local, Info<'tcx>>,
mir_summary: MirSummary,
}
#[derive(Clone)]
pub struct Info<'tcx> {
pub defs_and_uses: Vec<Use<'tcx>>,
}
#[derive(Clone)]
pub struct Use<'tcx> {
pub context: LvalueContext<'tcx>,
pub location: Location,
}
impl<'tcx> DefUseAnalysis<'tcx> {
pub fn new(mir: &Mir<'tcx>) -> DefUseAnalysis<'tcx> {
DefUseAnalysis {
info: IndexVec::from_elem_n(Info::new(), mir.count_locals()),
mir_summary: MirSummary::new(mir),
}
}
pub fn analyze(&mut self, mir: &Mir<'tcx>) {
let mut finder = DefUseFinder {
info: mem::replace(&mut self.info, IndexVec::new()),
mir_summary: self.mir_summary,
};
finder.visit_mir(mir);
self.info = finder.info
}
pub fn local_info(&self, local: Local) -> &Info<'tcx> {
&self.info[local]
}
pub fn local_info_mut(&mut self, local: Local) -> &mut Info<'tcx> {
&mut self.info[local]
}
fn mutate_defs_and_uses<F>(&self, local: Local, mir: &mut Mir<'tcx>, mut callback: F)
where F: for<'a> FnMut(&'a mut Lvalue<'tcx>,
LvalueContext<'tcx>,
Location) {
for lvalue_use in &self.info[local].defs_and_uses {
MutateUseVisitor::new(local,
&mut callback,
self.mir_summary,
mir).visit_location(mir, lvalue_use.location)
}
}
/// FIXME(pcwalton): This should update the def-use chains.
pub fn replace_all_defs_and_uses_with(&self,
local: Local,
mir: &mut Mir<'tcx>,
new_lvalue: Lvalue<'tcx>) {
self.mutate_defs_and_uses(local, mir, |lvalue, _, _| *lvalue = new_lvalue.clone())
}
}
struct DefUseFinder<'tcx> {
info: IndexVec<Local, Info<'tcx>>,
mir_summary: MirSummary,
}
impl<'tcx> DefUseFinder<'tcx> {
fn lvalue_mut_info(&mut self, lvalue: &Lvalue<'tcx>) -> Option<&mut Info<'tcx>> {
let info = &mut self.info;
self.mir_summary.local_index(lvalue).map(move |local| &mut info[local])
}
}
impl<'tcx> Visitor<'tcx> for DefUseFinder<'tcx> {
fn visit_lvalue(&mut self,
lvalue: &Lvalue<'tcx>,
context: LvalueContext<'tcx>,
location: Location) {
if let Some(ref mut info) = self.lvalue_mut_info(lvalue) {
info.defs_and_uses.push(Use {
context: context,
location: location,
})
}
self.super_lvalue(lvalue, context, location)
}
}
impl<'tcx> Info<'tcx> {
fn new() -> Info<'tcx> {
Info {
defs_and_uses: vec![],
}
}
pub fn def_count(&self) -> usize {
self.defs_and_uses.iter().filter(|lvalue_use| lvalue_use.context.is_mutating_use()).count()
}
pub fn def_count_not_including_drop(&self) -> usize {
self.defs_and_uses.iter().filter(|lvalue_use| {
lvalue_use.context.is_mutating_use() && !lvalue_use.context.is_drop()
}).count()
}
pub fn use_count(&self) -> usize {
self.defs_and_uses.iter().filter(|lvalue_use| {
lvalue_use.context.is_nonmutating_use()
}).count()
}
}
struct MutateUseVisitor<'tcx, F> {
query: Local,
callback: F,
mir_summary: MirSummary,
phantom: PhantomData<&'tcx ()>,
}
impl<'tcx, F> MutateUseVisitor<'tcx, F> {
fn new(query: Local, callback: F, mir_summary: MirSummary, _: &Mir<'tcx>)
-> MutateUseVisitor<'tcx, F>
where F: for<'a> FnMut(&'a mut Lvalue<'tcx>, LvalueContext<'tcx>, Location) {
MutateUseVisitor {
query: query,
callback: callback,
mir_summary: mir_summary,
phantom: PhantomData,
}
}
}
impl<'tcx, F> MutVisitor<'tcx> for MutateUseVisitor<'tcx, F>
where F: for<'a> FnMut(&'a mut Lvalue<'tcx>, LvalueContext<'tcx>, Location) {
fn visit_lvalue(&mut self,
lvalue: &mut Lvalue<'tcx>,
context: LvalueContext<'tcx>,
location: Location) {
if self.mir_summary.local_index(lvalue) == Some(self.query) {
(self.callback)(lvalue, context, location)
}
self.super_lvalue(lvalue, context, location)
}
}
/// A small structure that enables various metadata of the MIR to be queried
/// without a reference to the MIR itself.
#[derive(Clone, Copy)]
struct MirSummary {
arg_count: usize,
var_count: usize,
temp_count: usize,
}
impl MirSummary {
fn new(mir: &Mir) -> MirSummary {
MirSummary {
arg_count: mir.arg_decls.len(),
var_count: mir.var_decls.len(),
temp_count: mir.temp_decls.len(),
}
}
fn local_index<'tcx>(&self, lvalue: &Lvalue<'tcx>) -> Option<Local> {
match *lvalue {
Lvalue::Arg(arg) => Some(Local::new(arg.index())),
Lvalue::Var(var) => Some(Local::new(var.index() + self.arg_count)),
Lvalue::Temp(temp) => {
Some(Local::new(temp.index() + self.arg_count + self.var_count))
}
Lvalue::ReturnPointer => {
Some(Local::new(self.arg_count + self.var_count + self.temp_count))
}
_ => None,
}
}
}

View File

@ -46,8 +46,10 @@ extern crate rustc_const_eval;
pub mod diagnostics;
pub mod build;
pub mod def_use;
pub mod graphviz;
mod hair;
pub mod mir_map;
pub mod pretty;
pub mod transform;

View File

@ -0,0 +1,180 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Trivial copy propagation pass.
//!
//! This uses def-use analysis to remove values that have exactly one def and one use, which must
//! be an assignment.
//!
//! To give an example, we look for patterns that look like:
//!
//! DEST = SRC
//! ...
//! USE(DEST)
//!
//! where `DEST` and `SRC` are both locals of some form. We replace that with:
//!
//! NOP
//! ...
//! USE(SRC)
//!
//! The assignment `DEST = SRC` must be (a) the only mutation of `DEST` and (b) the only
//! (non-mutating) use of `SRC`. These restrictions are conservative and may be relaxed in the
//! future.
use def_use::DefUseAnalysis;
use rustc::mir::repr::{Local, Lvalue, Mir, Operand, Rvalue, StatementKind};
use rustc::mir::transform::{MirPass, MirSource, Pass};
use rustc::ty::TyCtxt;
use rustc_data_structures::indexed_vec::Idx;
pub struct CopyPropagation;
impl Pass for CopyPropagation {}
impl<'tcx> MirPass<'tcx> for CopyPropagation {
fn run_pass<'a>(&mut self, _: TyCtxt<'a, 'tcx, 'tcx>, _: MirSource, mir: &mut Mir<'tcx>) {
loop {
let mut def_use_analysis = DefUseAnalysis::new(mir);
def_use_analysis.analyze(mir);
let mut changed = false;
for dest_local_index in 0..mir.count_locals() {
let dest_local = Local::new(dest_local_index);
debug!("Considering destination local: {}", mir.format_local(dest_local));
let src_local;
let location;
{
// The destination must have exactly one def.
let dest_use_info = def_use_analysis.local_info(dest_local);
let dest_def_count = dest_use_info.def_count_not_including_drop();
if dest_def_count == 0 {
debug!(" Can't copy-propagate local: dest {} undefined",
mir.format_local(dest_local));
continue
}
if dest_def_count > 1 {
debug!(" Can't copy-propagate local: dest {} defined {} times",
mir.format_local(dest_local),
dest_use_info.def_count());
continue
}
if dest_use_info.use_count() == 0 {
debug!(" Can't copy-propagate local: dest {} unused",
mir.format_local(dest_local));
continue
}
let dest_lvalue_def = dest_use_info.defs_and_uses.iter().filter(|lvalue_def| {
lvalue_def.context.is_mutating_use() && !lvalue_def.context.is_drop()
}).next().unwrap();
location = dest_lvalue_def.location;
let basic_block = &mir[location.block];
let statement_index = location.statement_index;
let statement = match basic_block.statements.get(statement_index) {
Some(statement) => statement,
None => {
debug!(" Can't copy-propagate local: used in terminator");
continue
}
};
// That use of the source must be an assignment.
let src_lvalue = match statement.kind {
StatementKind::Assign(
ref dest_lvalue,
Rvalue::Use(Operand::Consume(ref src_lvalue)))
if Some(dest_local) == mir.local_index(dest_lvalue) => {
src_lvalue
}
_ => {
debug!(" Can't copy-propagate local: source use is not an \
assignment");
continue
}
};
src_local = match mir.local_index(src_lvalue) {
Some(src_local) => src_local,
None => {
debug!(" Can't copy-propagate local: source is not a local");
continue
}
};
// There must be exactly one use of the source used in a statement (not in a
// terminator).
let src_use_info = def_use_analysis.local_info(src_local);
let src_use_count = src_use_info.use_count();
if src_use_count == 0 {
debug!(" Can't copy-propagate local: no uses");
continue
}
if src_use_count != 1 {
debug!(" Can't copy-propagate local: {} uses", src_use_info.use_count());
continue
}
// Verify that the source doesn't change in between. This is done
// conservatively for now, by ensuring that the source has exactly one
// mutation. The goal is to prevent things like:
//
// DEST = SRC;
// SRC = X;
// USE(DEST);
//
// From being misoptimized into:
//
// SRC = X;
// USE(SRC);
let src_def_count = src_use_info.def_count_not_including_drop();
if src_def_count != 1 {
debug!(" Can't copy-propagate local: {} defs of src",
src_use_info.def_count_not_including_drop());
continue
}
}
// If all checks passed, then we can eliminate the destination and the assignment.
//
// First, remove all markers.
//
// FIXME(pcwalton): Don't do this. Merge live ranges instead.
debug!(" Replacing all uses of {}", mir.format_local(dest_local));
for lvalue_use in &def_use_analysis.local_info(dest_local).defs_and_uses {
if lvalue_use.context.is_storage_marker() {
mir.make_statement_nop(lvalue_use.location)
}
}
for lvalue_use in &def_use_analysis.local_info(src_local).defs_and_uses {
if lvalue_use.context.is_storage_marker() {
mir.make_statement_nop(lvalue_use.location)
}
}
// Now replace all uses of the destination local with the source local.
let src_lvalue = Lvalue::from_local(mir, src_local);
def_use_analysis.replace_all_defs_and_uses_with(dest_local, mir, src_lvalue);
// Finally, zap the now-useless assignment instruction.
mir.make_statement_nop(location);
changed = true;
// FIXME(pcwalton): Update the use-def chains to delete the instructions instead of
// regenerating the chains.
break
}
if !changed {
break
}
}
}
}

View File

@ -19,4 +19,4 @@ pub mod qualify_consts;
pub mod dump_mir;
pub mod deaggregator;
pub mod instcombine;
pub mod copy_prop;

View File

@ -328,7 +328,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
impl<'a, 'tcx> MutVisitor<'tcx> for Promoter<'a, 'tcx> {
fn visit_lvalue(&mut self,
lvalue: &mut Lvalue<'tcx>,
context: LvalueContext,
context: LvalueContext<'tcx>,
location: Location) {
if let Lvalue::Temp(ref mut temp) = *lvalue {
*temp = self.promote_temp(*temp);

View File

@ -475,7 +475,10 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
/// For functions (constant or not), it also records
/// candidates for promotion in promotion_candidates.
impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
fn visit_lvalue(&mut self, lvalue: &Lvalue<'tcx>, context: LvalueContext, location: Location) {
fn visit_lvalue(&mut self,
lvalue: &Lvalue<'tcx>,
context: LvalueContext<'tcx>,
location: Location) {
match *lvalue {
Lvalue::Arg(_) => {
self.add(Qualif::FN_ARGUMENT);

View File

@ -523,7 +523,7 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> {
fn visit_lvalue(&mut self,
lvalue: &mir::Lvalue<'tcx>,
context: mir_visit::LvalueContext,
context: mir_visit::LvalueContext<'tcx>,
location: Location) {
debug!("visiting lvalue {:?}", *lvalue);

View File

@ -147,7 +147,7 @@ impl<'mir, 'bcx, 'tcx> Visitor<'tcx> for LocalAnalyzer<'mir, 'bcx, 'tcx> {
fn visit_lvalue(&mut self,
lvalue: &mir::Lvalue<'tcx>,
context: LvalueContext,
context: LvalueContext<'tcx>,
location: Location) {
debug!("visit_lvalue(lvalue={:?}, context={:?})", lvalue, context);
@ -180,7 +180,7 @@ impl<'mir, 'bcx, 'tcx> Visitor<'tcx> for LocalAnalyzer<'mir, 'bcx, 'tcx> {
LvalueContext::Store |
LvalueContext::Inspect |
LvalueContext::Borrow { .. } |
LvalueContext::Projection => {
LvalueContext::Projection(..) => {
self.mark_as_lvalue(index);
}

View File

@ -23,9 +23,9 @@ fn helper(_: usize) {
pub fn ref_dst(s: &[u8]) {
// We used to generate an extra alloca and memcpy to ref the dst, so check that we copy
// directly to the alloca for "x"
// CHECK: [[X0:%[0-9]+]] = getelementptr {{.*}} { i8*, [[USIZE]] }* %x, i32 0, i32 0
// CHECK: [[X0:%[0-9]+]] = getelementptr {{.*}} { i8*, [[USIZE]] }* %s, i32 0, i32 0
// CHECK: store i8* %0, i8** [[X0]]
// CHECK: [[X1:%[0-9]+]] = getelementptr {{.*}} { i8*, [[USIZE]] }* %x, i32 0, i32 1
// CHECK: [[X1:%[0-9]+]] = getelementptr {{.*}} { i8*, [[USIZE]] }* %s, i32 0, i32 1
// CHECK: store [[USIZE]] %1, [[USIZE]]* [[X1]]
let x = &*s;

View File

@ -21,27 +21,27 @@ fn main() {
// END RUST SOURCE
// START rustc.node4.PreTrans.after.mir
// bb0: {
// StorageLive(var0); // scope 0 at storage_ranges.rs:12:9: 12:10
// var0 = const 0i32; // scope 0 at storage_ranges.rs:12:13: 12:14
// StorageLive(var1); // scope 1 at storage_ranges.rs:14:13: 14:14
// StorageLive(tmp1); // scope 1 at storage_ranges.rs:14:18: 14:25
// StorageLive(tmp2); // scope 1 at storage_ranges.rs:14:23: 14:24
// tmp2 = var0; // scope 1 at storage_ranges.rs:14:23: 14:24
// tmp1 = std::option::Option<i32>::Some(tmp2,); // scope 1 at storage_ranges.rs:14:18: 14:25
// var1 = &tmp1; // scope 1 at storage_ranges.rs:14:17: 14:25
// StorageDead(tmp2); // scope 1 at storage_ranges.rs:14:23: 14:24
// tmp0 = (); // scope 2 at storage_ranges.rs:13:5: 15:6
// StorageDead(tmp1); // scope 1 at storage_ranges.rs:14:18: 14:25
// StorageDead(var1); // scope 1 at storage_ranges.rs:14:13: 14:14
// StorageLive(var2); // scope 1 at storage_ranges.rs:16:9: 16:10
// var2 = const 1i32; // scope 1 at storage_ranges.rs:16:13: 16:14
// return = (); // scope 3 at storage_ranges.rs:11:11: 17:2
// StorageDead(var2); // scope 1 at storage_ranges.rs:16:9: 16:10
// StorageDead(var0); // scope 0 at storage_ranges.rs:12:9: 12:10
// goto -> bb1; // scope 0 at storage_ranges.rs:11:1: 17:2
// nop; // scope 0 at storage_ranges.rs:14:9: 14:10
// var0 = const 0i32; // scope 0 at storage_ranges.rs:14:13: 14:14
// StorageLive(var1); // scope 1 at storage_ranges.rs:16:13: 16:14
// StorageLive(tmp1); // scope 1 at storage_ranges.rs:16:18: 16:25
// nop; // scope 1 at storage_ranges.rs:16:23: 16:24
// nop; // scope 1 at storage_ranges.rs:16:23: 16:24
// tmp1 = std::option::Option<i32>::Some(var0,); // scope 1 at storage_ranges.rs:16:18: 16:25
// var1 = &tmp1; // scope 1 at storage_ranges.rs:16:17: 16:25
// nop; // scope 1 at storage_ranges.rs:16:23: 16:24
// tmp0 = (); // scope 2 at storage_ranges.rs:15:5: 17:6
// StorageDead(tmp1); // scope 1 at storage_ranges.rs:16:18: 16:25
// StorageDead(var1); // scope 1 at storage_ranges.rs:16:13: 16:14
// StorageLive(var2); // scope 1 at storage_ranges.rs:18:9: 18:10
// var2 = const 1i32; // scope 1 at storage_ranges.rs:18:13: 18:14
// return = (); // scope 3 at storage_ranges.rs:13:11: 19:2
// StorageDead(var2); // scope 1 at storage_ranges.rs:18:9: 18:10
// nop; // scope 0 at storage_ranges.rs:14:9: 14:10
// goto -> bb1; // scope 0 at storage_ranges.rs:13:1: 19:2
// }
//
// bb1: {
// return; // scope 0 at storage_ranges.rs:11:1: 17:2
// return; // scope 0 at storage_ranges.rs:13:1: 19:2
// }
// END rustc.node4.PreTrans.after.mir