add TerminatorKind::FalseEdges and use it in matches

This commit is contained in:
Mikhail Modin 2017-10-13 16:36:15 +03:00
parent 2379faa933
commit 2d71c5f10c
21 changed files with 377 additions and 48 deletions

View File

@ -62,7 +62,8 @@ for mir::Terminator<'gcx> {
mir::TerminatorKind::Drop { .. } |
mir::TerminatorKind::DropAndReplace { .. } |
mir::TerminatorKind::Yield { .. } |
mir::TerminatorKind::Call { .. } => false,
mir::TerminatorKind::Call { .. } |
mir::TerminatorKind::FalseEdges { .. } => false,
};
if hash_spans_unconditionally {
@ -210,6 +211,12 @@ for mir::TerminatorKind<'gcx> {
target.hash_stable(hcx, hasher);
cleanup.hash_stable(hcx, hasher);
}
mir::TerminatorKind::FalseEdges { ref real_target, ref imaginary_targets } => {
real_target.hash_stable(hcx, hasher);
for target in imaginary_targets {
target.hash_stable(hcx, hasher);
}
}
}
}
}

View File

@ -682,6 +682,11 @@ pub enum TerminatorKind<'tcx> {
/// Indicates the end of the dropping of a generator
GeneratorDrop,
FalseEdges {
real_target: BasicBlock,
imaginary_targets: Vec<BasicBlock>
},
}
impl<'tcx> Terminator<'tcx> {
@ -731,6 +736,11 @@ impl<'tcx> TerminatorKind<'tcx> {
}
Assert { target, cleanup: Some(unwind), .. } => vec![target, unwind].into_cow(),
Assert { ref target, .. } => slice::ref_slice(target).into_cow(),
FalseEdges { ref real_target, ref imaginary_targets } => {
let mut s = vec![*real_target];
s.extend_from_slice(imaginary_targets);
s.into_cow()
}
}
}
@ -757,7 +767,12 @@ impl<'tcx> TerminatorKind<'tcx> {
vec![target]
}
Assert { ref mut target, cleanup: Some(ref mut unwind), .. } => vec![target, unwind],
Assert { ref mut target, .. } => vec![target]
Assert { ref mut target, .. } => vec![target],
FalseEdges { ref mut real_target, ref mut imaginary_targets } => {
let mut s = vec![real_target];
s.extend(imaginary_targets.iter_mut());
s
}
}
}
}
@ -874,7 +889,8 @@ impl<'tcx> TerminatorKind<'tcx> {
}
write!(fmt, ")")
}
},
FalseEdges { .. } => write!(fmt, "falseEdges")
}
}
@ -910,7 +926,12 @@ impl<'tcx> TerminatorKind<'tcx> {
}
Assert { cleanup: None, .. } => vec!["".into()],
Assert { .. } =>
vec!["success".into_cow(), "unwind".into_cow()]
vec!["success".into_cow(), "unwind".into_cow()],
FalseEdges { ref imaginary_targets, .. } => {
let mut l = vec!["real".into()];
l.resize(imaginary_targets.len() + 1, "imaginary".into());
l
}
}
}
}
@ -1878,6 +1899,8 @@ impl<'tcx> TypeFoldable<'tcx> for Terminator<'tcx> {
Resume => Resume,
Return => Return,
Unreachable => Unreachable,
FalseEdges { real_target, ref imaginary_targets } =>
FalseEdges { real_target, imaginary_targets: imaginary_targets.clone() }
};
Terminator {
source_info: self.source_info,
@ -1917,7 +1940,8 @@ impl<'tcx> TypeFoldable<'tcx> for Terminator<'tcx> {
Resume |
Return |
GeneratorDrop |
Unreachable => false
Unreachable |
FalseEdges { .. } => false
}
}
}

View File

@ -486,8 +486,15 @@ macro_rules! make_mir_visitor {
self.visit_operand(value, source_location);
self.visit_branch(block, resume);
drop.map(|t| self.visit_branch(block, t));
}
TerminatorKind::FalseEdges { real_target, ref imaginary_targets } => {
self.visit_branch(block, real_target);
for target in imaginary_targets {
self.visit_branch(block, *target);
}
}
}
}

View File

@ -364,7 +364,8 @@ impl<'c, 'b, 'a: 'b+'c, 'gcx, 'tcx: 'a> DataflowResultsConsumer<'b, 'tcx>
TerminatorKind::Resume |
TerminatorKind::Return |
TerminatorKind::GeneratorDrop |
TerminatorKind::Unreachable => {
TerminatorKind::Unreachable |
TerminatorKind::FalseEdges { .. } => {
// no data used, thus irrelevant to borrowck
}
}

View File

@ -21,7 +21,7 @@ use rustc::mir::*;
use rustc::hir;
use hair::*;
use syntax::ast::{Name, NodeId};
use syntax_pos::{DUMMY_SP, Span};
use syntax_pos::Span;
// helper functions, broken out by category:
mod simplify;
@ -54,11 +54,17 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
(body, scope.unwrap_or(self.visibility_scope))
}).collect();
// create binding start block for link them by false edges
let candidate_count = arms.iter().fold(0, |ac, c| ac + c.patterns.len());
let binding_start_blocks: Vec<_> = (0..candidate_count + 1)
.map(|_| self.cfg.start_new_block()).collect();
// assemble a list of candidates: there is one candidate per
// pattern, which means there may be more than one candidate
// *per arm*. These candidates are kept sorted such that the
// highest priority candidate comes first in the list.
// (i.e. same order as in source)
let candidates: Vec<_> =
arms.iter()
.enumerate()
@ -66,17 +72,25 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
arm.patterns.iter()
.map(move |pat| (arm_index, pat, arm.guard.clone()))
})
.map(|(arm_index, pattern, guard)| {
.zip(binding_start_blocks.iter().zip(binding_start_blocks.iter().skip(1)))
.map(|((arm_index, pattern, guard),
(binding_start_block, next_candidate_binding_start_block))| {
Candidate {
span: pattern.span,
match_pairs: vec![MatchPair::new(discriminant_lvalue.clone(), pattern)],
bindings: vec![],
guard,
arm_index,
binding_start_block: *binding_start_block,
next_candidate_binding_start_block: *next_candidate_binding_start_block,
}
})
.collect();
let outer_source_info = self.source_info(span);
self.cfg.terminate(*binding_start_blocks.last().unwrap(),
outer_source_info, TerminatorKind::Unreachable);
// this will generate code to test discriminant_lvalue and
// branch to the appropriate arm block
let otherwise = self.match_candidates(span, &mut arm_blocks, candidates, block);
@ -148,7 +162,11 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
match_pairs: vec![MatchPair::new(initializer.clone(), &irrefutable_pat)],
bindings: vec![],
guard: None,
arm_index: 0, // since we don't call `match_candidates`, this field is unused
// since we don't call `match_candidates`, next fields is unused
arm_index: 0,
binding_start_block: block,
next_candidate_binding_start_block: block
};
// Simplify the candidate. Since the pattern is irrefutable, this should
@ -278,6 +296,10 @@ pub struct Candidate<'pat, 'tcx:'pat> {
// ...and then we branch to arm with this index.
arm_index: usize,
// ...and the blocks for add false edges between candidates
binding_start_block: BasicBlock,
next_candidate_binding_start_block: BasicBlock,
}
#[derive(Clone, Debug)]
@ -398,17 +420,43 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
candidates.iter().take_while(|c| c.match_pairs.is_empty()).count();
debug!("match_candidates: {:?} candidates fully matched", fully_matched);
let mut unmatched_candidates = candidates.split_off(fully_matched);
for (index, candidate) in candidates.into_iter().enumerate() {
let fully_matched_with_guard =
candidates.iter().take_while(|c| c.guard.is_some()).count();
let unreachable_candidates = if fully_matched_with_guard + 1 < candidates.len() {
candidates.split_off(fully_matched_with_guard + 1)
} else {
vec![]
};
for candidate in candidates {
// If so, apply any bindings, test the guard (if any), and
// branch to the arm.
let is_last = index == fully_matched - 1;
if let Some(b) = self.bind_and_guard_matched_candidate(block, arm_blocks,
candidate, is_last) {
if let Some(b) = self.bind_and_guard_matched_candidate(block, arm_blocks, candidate) {
block = b;
} else {
// if None is returned, then any remaining candidates
// are unreachable (at least not through this path).
return vec![];
// Link them with false edges.
debug!("match_candidates: add false edges for unreachable {:?} and unmatched {:?}",
unreachable_candidates, unmatched_candidates);
for candidate in unreachable_candidates {
let source_info = self.source_info(candidate.span);
let target = self.cfg.start_new_block();
if let Some(otherwise) = self.bind_and_guard_matched_candidate(target,
arm_blocks,
candidate) {
self.cfg.terminate(otherwise, source_info, TerminatorKind::Unreachable);
}
}
if unmatched_candidates.is_empty() {
return vec![]
} else {
let target = self.cfg.start_new_block();
return self.match_candidates(span, arm_blocks, unmatched_candidates, target);
}
}
}
@ -423,9 +471,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
self.test_candidates(span, arm_blocks, &unmatched_candidates, block);
// If the target candidates were exhaustive, then we are done.
if otherwise.is_empty() {
return vec![];
}
// But for borrowck continue build decision tree.
// If all candidates were sorted into `target_candidates` somewhere, then
// the initial set was inexhaustive.
@ -666,17 +712,24 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
fn bind_and_guard_matched_candidate<'pat>(&mut self,
mut block: BasicBlock,
arm_blocks: &mut ArmBlocks,
candidate: Candidate<'pat, 'tcx>,
is_last_arm: bool)
candidate: Candidate<'pat, 'tcx>)
-> Option<BasicBlock> {
debug!("bind_and_guard_matched_candidate(block={:?}, candidate={:?})",
block, candidate);
debug_assert!(candidate.match_pairs.is_empty());
self.bind_matched_candidate(block, candidate.bindings);
let arm_block = arm_blocks.blocks[candidate.arm_index];
let candidate_source_info = self.source_info(candidate.span);
self.cfg.terminate(block, candidate_source_info,
TerminatorKind::FalseEdges {
real_target: candidate.binding_start_block,
imaginary_targets:
vec![candidate.next_candidate_binding_start_block]});
block = candidate.binding_start_block;
self.bind_matched_candidate(block, candidate.bindings);
if let Some(guard) = candidate.guard {
// the block to branch to if the guard fails; if there is no
@ -684,30 +737,25 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
let guard = self.hir.mirror(guard);
let source_info = self.source_info(guard.span);
let cond = unpack!(block = self.as_local_operand(block, guard));
let otherwise = self.cfg.start_new_block();
let false_edge_block = self.cfg.start_new_block();
self.cfg.terminate(block, source_info,
TerminatorKind::if_(self.hir.tcx(), cond, arm_block, otherwise));
Some(otherwise)
} else if !is_last_arm {
// Add always true guard in case of more than one arm
// it creates false edges and allow MIR borrowck detects errors
// FIXME(#45184) -- permit "false edges"
let source_info = self.source_info(candidate.span);
let true_expr = Expr {
temp_lifetime: None,
ty: self.hir.tcx().types.bool,
span: DUMMY_SP,
kind: ExprKind::Literal{literal: self.hir.true_literal()},
};
let cond = unpack!(block = self.as_local_operand(block, true_expr));
TerminatorKind::if_(self.hir.tcx(), cond, arm_block,
false_edge_block));
let otherwise = self.cfg.start_new_block();
self.cfg.terminate(block, source_info,
TerminatorKind::if_(self.hir.tcx(), cond, arm_block, otherwise));
self.cfg.terminate(false_edge_block, source_info,
TerminatorKind::FalseEdges {
real_target: otherwise,
imaginary_targets:
vec![candidate.next_candidate_binding_start_block] });
Some(otherwise)
} else {
let source_info = self.source_info(candidate.span);
self.cfg.terminate(block, source_info,
TerminatorKind::Goto { target: arm_block });
self.cfg.terminate(block, candidate_source_info,
TerminatorKind::FalseEdges {
real_target: arm_block,
imaginary_targets:
vec![candidate.next_candidate_binding_start_block]});
None
}
}

View File

@ -598,6 +598,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
bindings: candidate.bindings.clone(),
guard: candidate.guard.clone(),
arm_index: candidate.arm_index,
binding_start_block: candidate.binding_start_block,
next_candidate_binding_start_block: candidate.next_candidate_binding_start_block,
}
}
@ -659,6 +661,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
bindings: candidate.bindings.clone(),
guard: candidate.guard.clone(),
arm_index: candidate.arm_index,
binding_start_block: candidate.binding_start_block,
next_candidate_binding_start_block: candidate.next_candidate_binding_start_block,
}
}

View File

@ -721,6 +721,12 @@ impl<'a, 'tcx: 'a, D> DataflowAnalysis<'a, 'tcx, D> where D: BitDenotation
self.propagate_bits_into_entry_set_for(in_out, changed, dest_bb);
}
}
mir::TerminatorKind::FalseEdges { ref real_target, ref imaginary_targets } => {
self.propagate_bits_into_entry_set_for(in_out, changed, real_target);
for target in imaginary_targets {
self.propagate_bits_into_entry_set_for(in_out, changed, target);
}
}
}
}

View File

@ -305,6 +305,7 @@ impl<'b, 'a, 'gcx, 'tcx> Gatherer<'b, 'a, 'gcx, 'tcx> {
TerminatorKind::Goto { target: _ } |
TerminatorKind::Resume |
TerminatorKind::GeneratorDrop |
TerminatorKind::FalseEdges { .. } |
TerminatorKind::Unreachable => { }
TerminatorKind::Return => {

View File

@ -73,7 +73,8 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
TerminatorKind::GeneratorDrop |
TerminatorKind::Resume |
TerminatorKind::Return |
TerminatorKind::Unreachable => {
TerminatorKind::Unreachable |
TerminatorKind::FalseEdges { .. } => {
// safe (at least as emitted during MIR construction)
}

View File

@ -720,6 +720,12 @@ impl<'a, 'tcx> MutVisitor<'tcx> for Integrator<'a, 'tcx> {
}
}
TerminatorKind::Unreachable => { }
TerminatorKind::FalseEdges { ref mut real_target, ref mut imaginary_targets } => {
*real_target = self.update_target(*real_target);
for target in imaginary_targets {
*target = self.update_target(*target);
}
}
}
}

View File

@ -45,7 +45,8 @@ impl<'tcx> MutVisitor<'tcx> for NoLandingPads {
TerminatorKind::Unreachable |
TerminatorKind::GeneratorDrop |
TerminatorKind::Yield { .. } |
TerminatorKind::SwitchInt { .. } => {
TerminatorKind::SwitchInt { .. } |
TerminatorKind::FalseEdges { .. } => {
/* nothing to do */
},
TerminatorKind::Call { cleanup: ref mut unwind, .. } |

View File

@ -317,7 +317,8 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
TerminatorKind::Resume |
TerminatorKind::GeneratorDrop |
TerminatorKind::Yield { .. } |
TerminatorKind::Unreachable => None,
TerminatorKind::Unreachable |
TerminatorKind::FalseEdges { .. } => None,
TerminatorKind::Return => {
// Check for unused values. This usually means

View File

@ -61,6 +61,9 @@ impl MirPass for SimplifyBranches {
}), expected, .. } if cond == expected => {
TerminatorKind::Goto { target: target }
},
TerminatorKind::FalseEdges { real_target, .. } => {
TerminatorKind::Goto { target: real_target }
},
_ => continue
};
}

View File

@ -441,7 +441,8 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
TerminatorKind::Return |
TerminatorKind::GeneratorDrop |
TerminatorKind::Unreachable |
TerminatorKind::Drop { .. } => {
TerminatorKind::Drop { .. } |
TerminatorKind::FalseEdges { .. } => {
// no checks needed for these
}
@ -685,6 +686,12 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
self.assert_iscleanup(mir, block, cleanup, true);
}
}
TerminatorKind::FalseEdges { real_target, ref imaginary_targets } => {
self.assert_iscleanup(mir, block, real_target, is_cleanup);
for target in imaginary_targets {
self.assert_iscleanup(mir, block, *target, is_cleanup);
}
}
}
}

View File

@ -121,6 +121,7 @@ impl<'a, 'tcx> mir_visit::Visitor<'tcx> for StatCollector<'a, 'tcx> {
TerminatorKind::Assert { .. } => "TerminatorKind::Assert",
TerminatorKind::GeneratorDrop => "TerminatorKind::GeneratorDrop",
TerminatorKind::Yield { .. } => "TerminatorKind::Yield",
TerminatorKind::FalseEdges { .. } => "TerminatorKind::FalseEdges",
}, kind);
self.super_terminator_kind(block, kind, location);
}

View File

@ -228,7 +228,8 @@ pub fn cleanup_kinds<'a, 'tcx>(mir: &mir::Mir<'tcx>) -> IndexVec<mir::BasicBlock
TerminatorKind::GeneratorDrop |
TerminatorKind::Unreachable |
TerminatorKind::SwitchInt { .. } |
TerminatorKind::Yield { .. } => {
TerminatorKind::Yield { .. } |
TerminatorKind::FalseEdges { .. } => {
/* nothing to do */
}
TerminatorKind::Call { cleanup: unwind, .. } |

View File

@ -583,7 +583,8 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
cleanup);
}
mir::TerminatorKind::GeneratorDrop |
mir::TerminatorKind::Yield { .. } => bug!("generator ops in trans"),
mir::TerminatorKind::Yield { .. } |
mir::TerminatorKind::FalseEdges { .. } => bug!("generator ops in trans"),
}
}

View File

@ -623,7 +623,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> {
mir::TerminatorKind::Unreachable |
mir::TerminatorKind::Assert { .. } => {}
mir::TerminatorKind::GeneratorDrop |
mir::TerminatorKind::Yield { .. } => bug!(),
mir::TerminatorKind::Yield { .. } |
mir::TerminatorKind::FalseEdges { .. } => bug!(),
}
self.super_terminator_kind(block, kind, location);

View File

@ -0,0 +1,24 @@
// Copyright 2012 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.
//compile-flags: -Z emit-end-regions -Z borrowck-mir
fn foo(_:String) {}
fn main()
{
let my_str = "hello".to_owned();
match Some(42) {
Some(_) if { drop(my_str); false } => {}
Some(_) => {}
None => { foo(my_str); } //~ ERROR (Mir) [E0381]
}
}

View File

@ -11,6 +11,24 @@
// revisions: ast mir
//[mir]compile-flags: -Z emit-end-regions -Z borrowck-mir
enum Foo {
A(i32),
B
}
fn match_enum() {
let mut foo = Foo::B;
let p = &mut foo;
let _ = match foo {
Foo::B => 1, //[mir]~ ERROR (Mir) [E0503]
_ => 2,
Foo::A(x) => x //[ast]~ ERROR [E0503]
//[mir]~^ ERROR (Ast) [E0503]
//[mir]~| ERROR (Mir) [E0503]
};
}
fn main() {
let mut x = 1;
let _x = &mut x;

View File

@ -0,0 +1,166 @@
// Copyright 2012-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.
// compile-flags: -Z emit-end-regions -Z borrowck-mir
fn guard() -> bool {
false
}
fn guard2(_:i32) -> bool {
true
}
fn full_tested_match()
{
let _ = match Some(42) {
Some(_) if guard() => 1,
Some(_) => 2,
None => 3
};
}
fn main() {
let _ = match Some(1) {
Some(_w) if guard() => 1,
_x => 2,
Some(y) if guard2(y) => 3,
_z => 4,
};
}
// END RUST SOURCE
//
// START rustc.node17.NLL.before.mir
// bb0: {
// ...
// _2 = std::option::Option<i32>::Some(const 42i32,);
// _3 = discriminant(_2);
// switchInt(_3) -> [0isize: bb8, otherwise: bb9];
// }
// bb1: { // arm1
// _1 = const 1i32;
// goto -> bb13;
// }
// bb2: { // arm2
// _1 = const 2i32;
// goto -> bb13;
// }
// bb3: { // arm3
// _1 = const 3i32;
// goto -> bb13;
// }
// bb4: { // binding1
// ...
// _4 = const guard() -> bb10;
// }
// bb5: { // binding2
// falseEdges -> [real: bb2, imaginary: bb6];
// }
// bb6: { // binding3
// falseEdges -> [real: bb3, imaginary: bb7];
// }
// bb7: {
// unreachable;
// }
// bb8: {
// falseEdges -> [real: bb6, imaginary: bb7]; // from before_binding3 to unreachable
// }
// bb9: {
// falseEdges -> [real: bb4, imaginary: bb5]; // from before_binding1 to binding2
// }
// bb10: {
// switchInt(_4) -> [0u8: bb11, otherwise: bb1]; // end of guard
// }
// bb11: {
// falseEdges -> [real: bb12, imaginary: bb5]; // after_guard to binding2
// }
// bb12: {
// falseEdges -> [real: bb5, imaginary: bb6]; // from before_binding2 to binding3
// }
// bb13: {
// ...
// return;
// }
//
//
// END rustc.node17.NLL.before.mir
//
// START rustc.node36.NLL.before.mir
// bb0: {
// ...
// _2 = std::option::Option<i32>::Some(const 1i32,);
// _7 = discriminant(_2);
// switchInt(_7) -> [1isize: bb10, otherwise: bb13];
// }
// bb1: { // arm1
// _1 = const 1i32;
// goto -> bb17;
// }
// bb2: { // arm2
// _1 = const 2i32;
// goto -> bb17;
// }
// bb3: { // arm3
// _1 = const 3i32;
// goto -> bb17;
// }
// bb4: { // arm4
// _1 = const 4i32;
// goto -> bb17;
// }
// bb5: { // binding1: Some(w) if guard() =>
// ...
// _8 = const guard() -> bb11;
// }
// bb6: { // binding2: x =>
// ...
// _4 = _2;
// falseEdges -> [real: bb2, imaginary: bb7]; // after binding2 to binding3
// }
// bb7: { // binding3: Some(y) if guard2(y) =>
// ...
// _10 = const guard2(_11) -> bb14;
// }
// bb8: { // binding4: z_ =>
// ...
// _6 = _2;
// falseEdges -> [real: bb4, imaginary: bb9]; // after binding3 to unreachable
// }
// bb9: {
// unreachable;
// }
// bb10: {
// falseEdges -> [real: bb5, imaginary: bb6]; // from before_binding1 to binding2
// }
// bb11: {
// switchInt(_8) -> [0u8: bb12, otherwise: bb1]; // end of gurard
// }
// bb12: {
// falseEdges -> [real: bb13, imaginary: bb6]; // after guard to binding2
// }
// bb13: {
// falseEdges -> [real: bb6, imaginary: bb7]; // from before_binding2 to binding3
// }
// bb14: {
// ...
// switchInt(_10) -> [0u8: bb15, otherwise: bb3]; // end of guard2
// }
// bb15: {
// falseEdges -> [real: bb16, imaginary: bb8]; // after guard2 to binding4
// }
// bb16: {
// falseEdges -> [real: bb8, imaginary: bb9]; // from befor binding3 to binding4
// }
// bb17: {
// ...
// return;
// }
// END rustc.node36.NLL.before.mir