Simplify the canonical enum clone branches to a copy statement

This commit is contained in:
dianqk
2025-11-16 23:10:38 +08:00
parent 50c30220c3
commit adf64bd305
9 changed files with 524 additions and 88 deletions
@@ -1,12 +1,14 @@
use rustc_abi::Integer;
use rustc_const_eval::const_eval::mk_eval_cx_for_const_val;
use rustc_middle::mir::*;
use rustc_middle::ty::layout::{IntegerExt, TyAndLayout};
use rustc_middle::ty::util::Discr;
use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt};
use super::simplify::simplify_cfg;
use crate::patch::MirPatch;
/// Merges all targets into one basic block if each statement can have the same statement.
/// Unifies all targets into one basic block if each statement can have the same statement.
pub(super) struct MatchBranchSimplification;
impl<'tcx> crate::MirPass<'tcx> for MatchBranchSimplification {
@@ -40,6 +42,7 @@ struct SimplifyMatch<'tcx, 'a> {
patch: MirPatch<'tcx>,
body: &'a Body<'tcx>,
switch_bb: BasicBlock,
discr: &'a Operand<'tcx>,
discr_local: Option<Local>,
discr_ty: Ty<'tcx>,
}
@@ -53,8 +56,8 @@ fn discr_local(&mut self) -> Local {
})
}
/// Merges the assignments if all rvalues are constants and equal.
fn merge_if_equal_const(
/// Unifies the assignments if all rvalues are constants and equal.
fn unify_if_equal_const(
&self,
dest: Place<'tcx>,
consts: &[(u128, &ConstOperand<'tcx>)],
@@ -76,7 +79,7 @@ fn merge_if_equal_const(
/// If a source block is found that switches between two blocks that are exactly
/// the same modulo const bool assignments (e.g., one assigns true another false
/// to the same place), merge a target block statements into the source block,
/// to the same place), unify a target block statements into the source block,
/// using Eq / Ne comparison with switch value where const bools value differ.
///
/// For example:
@@ -105,7 +108,7 @@ fn merge_if_equal_const(
/// goto -> bb3;
/// }
/// ```
fn merge_by_eq_op(
fn unify_by_eq_op(
&mut self,
dest: Place<'tcx>,
consts: &[(u128, &ConstOperand<'tcx>)],
@@ -140,7 +143,7 @@ fn merge_by_eq_op(
}
}
/// Merges the assignments if all rvalues can be cast from the discriminant value by IntToInt.
/// Unifies the assignments if all rvalues can be cast from the discriminant value by IntToInt.
///
/// For example:
///
@@ -177,7 +180,7 @@ fn merge_by_eq_op(
/// goto -> bb5;
/// }
/// ```
fn merge_by_int_to_int(
fn unify_by_int_to_int(
&mut self,
dest: Place<'tcx>,
consts: &[(u128, &ConstOperand<'tcx>)],
@@ -207,10 +210,91 @@ fn merge_by_int_to_int(
}
}
/// This is primarily used to unify these copy statements that simplified the canonical enum clone method by GVN.
/// The GVN simplified
/// ```ignore (syntax-highlighting-only)
/// match a {
/// Foo::A(x) => Foo::A(*x),
/// Foo::B => Foo::B
/// }
/// ```
/// to
/// ```ignore (syntax-highlighting-only)
/// match a {
/// Foo::A(_x) => a, // copy a
/// Foo::B => Foo::B
/// }
/// ```
/// This will simplify into a copy statement.
fn unify_by_copy(
&self,
dest: Place<'tcx>,
rvals: &[(u128, &Rvalue<'tcx>)],
) -> Option<StatementKind<'tcx>> {
let bbs = &self.body.basic_blocks;
// Check if the copy source matches the following pattern.
// _2 = discriminant(*_1); // "*_1" is the expected the copy source.
// switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1];
let &Statement {
kind: StatementKind::Assign(box (discr_place, Rvalue::Discriminant(copy_src_place))),
..
} = bbs[self.switch_bb].statements.last()?
else {
return None;
};
if self.discr.place() != Some(discr_place) {
return None;
}
let src_ty = copy_src_place.ty(self.body.local_decls(), self.tcx);
if !src_ty.ty.is_enum() || src_ty.variant_index.is_some() {
return None;
}
let dest_ty = dest.ty(self.body.local_decls(), self.tcx);
if dest_ty.ty != src_ty.ty || dest_ty.variant_index.is_some() {
return None;
}
let ty::Adt(def, _) = dest_ty.ty.kind() else {
return None;
};
for &(case, rvalue) in rvals.iter() {
match rvalue {
// Check if `_3 = const Foo::B` can be transformed to `_3 = copy *_1`.
Rvalue::Use(Operand::Constant(box constant))
if let Const::Val(const_, ty) = constant.const_ =>
{
let (ecx, op) = mk_eval_cx_for_const_val(
self.tcx.at(constant.span),
self.typing_env,
const_,
ty,
)?;
let variant = ecx.read_discriminant(&op).discard_err()?;
if !def.variants()[variant].fields.is_empty() {
return None;
}
let Discr { val, .. } = ty.discriminant_for_variant(self.tcx, variant)?;
if val != case {
return None;
}
}
Rvalue::Use(Operand::Copy(src_place)) if *src_place == copy_src_place => {}
// Check if `_3 = Foo::B` can be transformed to `_3 = copy *_1`.
Rvalue::Aggregate(box AggregateKind::Adt(_, variant_index, _, _, None), fields)
if fields.is_empty()
&& let Some(Discr { val, .. }) =
src_ty.ty.discriminant_for_variant(self.tcx, *variant_index)
&& val == case => {}
_ => return None,
}
}
Some(StatementKind::Assign(Box::new((dest, Rvalue::Use(Operand::Copy(copy_src_place))))))
}
/// Returns a new statement if we can use the statement replace all statements.
fn try_merge_stmts(
fn try_unify_stmts(
&mut self,
_index: usize,
index: usize,
stmts: &[(u128, &StatementKind<'tcx>)],
otherwise: Option<&StatementKind<'tcx>>,
) -> Option<StatementKind<'tcx>> {
@@ -220,25 +304,37 @@ fn try_merge_stmts(
let (dest, rvals, otherwise) = candidate_assign(stmts, otherwise)?;
if let Some((consts, otherwise)) = candidate_const(&rvals, otherwise) {
if let Some(new_stmt) = self.merge_if_equal_const(dest, &consts, otherwise) {
if let Some(new_stmt) = self.unify_if_equal_const(dest, &consts, otherwise) {
return Some(new_stmt);
}
if let Some(new_stmt) = self.merge_by_eq_op(dest, &consts, otherwise) {
if let Some(new_stmt) = self.unify_by_eq_op(dest, &consts, otherwise) {
return Some(new_stmt);
}
// Requires the otherwise is unreachable.
if otherwise.is_none()
&& let Some(new_stmt) = self.merge_by_int_to_int(dest, &consts)
&& let Some(new_stmt) = self.unify_by_int_to_int(dest, &consts)
{
return Some(new_stmt);
}
}
// We only know the first statement is safe to introduce new dereferences.
if index == 0
// We cannot create overlapping assignments.
&& dest.is_stable_offset()
// Requires the otherwise is unreachable.
&& otherwise.is_none()
&& let Some(new_stmt) = self.unify_by_copy(dest, &rvals)
{
return Some(new_stmt);
}
None
}
}
/// Returns the first case target if all targets have an equal number of statements and identical destination.
fn candidate_match<'tcx>(body: &Body<'tcx>, switch_bb: BasicBlock) -> bool {
use itertools::Itertools;
let targets = match &body.basic_blocks[switch_bb].terminator().kind {
TerminatorKind::SwitchInt {
discr: Operand::Copy(_) | Operand::Move(_), targets, ..
@@ -254,21 +350,14 @@ fn candidate_match<'tcx>(body: &Body<'tcx>, switch_bb: BasicBlock) -> bool {
if !targets.is_distinct() {
return false;
}
let &[first, ref others @ .., otherwise] = targets.all_targets() else {
return false;
};
let first_case_bb = &body.basic_blocks[first];
let first_case_terminator_kind = &first_case_bb.terminator().kind;
let first_case_stmts_len = first_case_bb.statements.len();
let otherwise =
if body.basic_blocks[otherwise].is_empty_unreachable() { None } else { Some(&otherwise) };
// Check that destinations are identical, and if not, then don't optimize this block
others.iter().chain(otherwise).all(|&bb| {
let bb = &body.basic_blocks[bb];
first_case_stmts_len == bb.statements.len()
&& first_case_terminator_kind == &bb.terminator().kind
})
targets
.all_targets()
.iter()
.map(|&bb| &body.basic_blocks[bb])
.filter(|bb| !bb.is_empty_unreachable())
.map(|bb| (bb.statements.len(), &bb.terminator().kind))
.all_equal()
}
fn simplify_match<'tcx>(
@@ -287,31 +376,41 @@ fn simplify_match<'tcx>(
patch: MirPatch::new(body),
body,
switch_bb,
discr,
discr_local: None,
discr_ty: discr.ty(body.local_decls(), tcx),
};
let stmts: Vec<_> = targets
.iter()
.map(|(case, bb)| (case, simplify_match.body.basic_blocks[bb].statements.as_slice()))
.collect();
let reachable_cases: Vec<_> =
targets.iter().filter(|&(_, bb)| !body.basic_blocks[bb].is_empty_unreachable()).collect();
let mut new_stmts = Vec::new();
let otherwise_stmts = if body.basic_blocks[targets.otherwise()].is_empty_unreachable() {
let otherwise = if body.basic_blocks[targets.otherwise()].is_empty_unreachable() {
None
} else {
Some(body.basic_blocks[targets.otherwise()].statements.as_slice())
Some(targets.otherwise())
};
// We can patch the terminator to goto because there is a single target.
match (&reachable_cases[..], otherwise) {
(&[(_, single_target)], None) | (&[], Some(single_target)) => {
let mut patch = simplify_match.patch;
patch.patch_terminator(switch_bb, TerminatorKind::Goto { target: single_target });
patch.apply(body);
return true;
}
_ => {}
}
let Some(&(_, first_case_bb)) = reachable_cases.first() else {
return false;
};
let first_case_bb = targets.all_targets()[0];
let stmt_len = body.basic_blocks[first_case_bb].statements.len();
let mut cases = Vec::with_capacity(stmt_len);
// Check at each position in the basic blocks whether these statements can be merged.
// Check at each position in the basic blocks whether these statements can be unified.
for index in 0..stmt_len {
let otherwise = otherwise_stmts.map(|stmt| &stmt[index].kind);
cases.clear();
for &(case, stmts) in &stmts {
cases.push((case, &stmts[index].kind));
let otherwise = otherwise.map(|bb| &body.basic_blocks[bb].statements[index].kind);
for &(case, bb) in &reachable_cases {
cases.push((case, &body.basic_blocks[bb].statements[index].kind));
}
let Some(new_stmt) = simplify_match.try_merge_stmts(index, cases.as_slice(), otherwise)
else {
let Some(new_stmt) = simplify_match.try_unify_stmts(index, &cases, otherwise) else {
return false;
};
new_stmts.push(new_stmt);
@@ -4,7 +4,6 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
debug slf => _1;
let mut _0: T;
scope 1 (inlined #[track_caller] Option::<T>::unwrap_unchecked) {
let mut _2: isize;
scope 2 {
}
scope 3 (inlined #[track_caller] unreachable_unchecked) {
@@ -16,18 +15,7 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
}
bb0: {
StorageLive(_2);
_2 = discriminant(_1);
switchInt(move _2) -> [0: bb2, 1: bb1, otherwise: bb2];
}
bb1: {
_0 = copy ((_1 as Some).0: T);
StorageDead(_2);
return;
}
bb2: {
unreachable;
}
}
@@ -4,7 +4,6 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
debug slf => _1;
let mut _0: T;
scope 1 (inlined #[track_caller] Option::<T>::unwrap_unchecked) {
let mut _2: isize;
scope 2 {
}
scope 3 (inlined #[track_caller] unreachable_unchecked) {
@@ -16,18 +15,7 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
}
bb0: {
StorageLive(_2);
_2 = discriminant(_1);
switchInt(move _2) -> [0: bb2, 1: bb1, otherwise: bb2];
}
bb1: {
_0 = copy ((_1 as Some).0: T);
StorageDead(_2);
return;
}
bb2: {
unreachable;
}
}
@@ -0,0 +1,32 @@
- // MIR for `match_option` before MatchBranchSimplification
+ // MIR for `match_option` after MatchBranchSimplification
fn match_option(_1: &Option<i32>) -> Option<i32> {
debug i => _1;
let mut _0: std::option::Option<i32>;
let mut _2: isize;
bb0: {
_2 = discriminant((*_1));
- switchInt(move _2) -> [0: bb2, 1: bb3, otherwise: bb1];
- }
-
- bb1: {
- unreachable;
- }
-
- bb2: {
- _0 = Option::<i32>::None;
- goto -> bb4;
- }
-
- bb3: {
_0 = copy (*_1);
- goto -> bb4;
- }
-
- bb4: {
return;
}
}
@@ -0,0 +1,39 @@
- // MIR for `match_option2_mut` before MatchBranchSimplification
+ // MIR for `match_option2_mut` after MatchBranchSimplification
fn match_option2_mut(_1: &mut Option2<i32>) -> Option2<i32> {
let mut _0: Option2<i32>;
let mut _2: isize;
bb0: {
_2 = discriminant((*_1));
switchInt(copy _2) -> [0: bb1, 1: bb2, 2: bb3, otherwise: bb4];
}
bb1: {
(*_1) = Option2::<i32>::None2;
_0 = Option2::<i32>::None1;
goto -> bb5;
}
bb2: {
(*_1) = Option2::<i32>::None2;
_0 = Option2::<i32>::None2;
goto -> bb5;
}
bb3: {
(*_1) = Option2::<i32>::None2;
_0 = copy (*_1);
goto -> bb5;
}
bb4: {
unreachable;
}
bb5: {
return;
}
}
+60
View File
@@ -675,6 +675,23 @@ fn match_i128_u128(i: EnumAi128) -> u128 {
}
}
// EMIT_MIR matches_reduce_branches.match_option.MatchBranchSimplification.diff
fn match_option(i: &Option<i32>) -> Option<i32> {
// CHECK-LABEL: fn match_option(
// CHECK-NOT: switchInt
// CHECK: _0 = copy (*_1);
match i {
Some(_) => *i,
None => None,
}
}
enum Option2<T> {
None1,
None2,
Some(T),
}
// EMIT_MIR matches_reduce_branches.single_case.MatchBranchSimplification.diff
#[custom_mir(dialect = "runtime")]
fn single_case(i: Option<i32>) -> i32 {
@@ -698,6 +715,47 @@ fn single_case(i: Option<i32>) -> i32 {
}
}
// We cannot dereference `i` after the value has been changed.
// EMIT_MIR matches_reduce_branches.match_option2_mut.MatchBranchSimplification.diff
#[custom_mir(dialect = "runtime")]
fn match_option2_mut(i: &mut Option2<i32>) -> Option2<i32> {
// CHECK-LABEL: fn match_option2_mut(
// CHECK: switchInt
// CHECK: return
mir! {
{
let discr = Discriminant(*i);
match discr {
0 => none1_bb,
1 => none2_bb,
2 => some_bb,
_ => unreachable_bb,
}
}
none1_bb = {
*i = Option2::None2;
RET = Option2::None1;
Goto(ret)
}
none2_bb = {
*i = Option2::None2;
RET = Option2::None2;
Goto(ret)
}
some_bb = {
*i = Option2::None2;
RET = *i;
Goto(ret)
}
unreachable_bb = {
Unreachable()
}
ret = {
Return()
}
}
}
// EMIT_MIR matches_reduce_branches.match_non_int_failed.MatchBranchSimplification.diff
#[custom_mir(dialect = "runtime")]
fn match_non_int_failed(i: char) -> u8 {
@@ -767,4 +825,6 @@ fn main() {
let _ = my_is_some(None);
let _ = match_non_int_failed('a');
let _ = match_option(&None);
let _ = match_option2_mut(&mut Option2::None1);
}
+250
View File
@@ -0,0 +1,250 @@
//@ [COPY] compile-flags: --cfg=copy
//@ revisions: COPY CLONE
// Test case from https://github.com/rust-lang/rust/issues/128081.
// Ensure both Copy and Clone get optimized copy.
#[unsafe(no_mangle)]
pub fn intra_clone(intra: &Av1BlockIntra) -> Av1BlockIntraInter {
// CHECK-LABEL: fn intra_clone(
// CHECK: [[C:_.*]] = copy (*_1);
// CHECK: _0 = Av1BlockIntraInter::Intra(move [[C]]);
Av1BlockIntraInter::Intra(intra.clone())
}
#[unsafe(no_mangle)]
pub fn inter_clone(inter: &Av1BlockInter) -> Av1BlockIntraInter {
// CHECK-LABEL: fn inter_clone(
// CHECK: [[C:_.*]] = copy (*_1);
// CHECK: _0 = Av1BlockIntraInter::Inter(move [[C]]);
Av1BlockIntraInter::Inter(inter.clone())
}
#[unsafe(no_mangle)]
pub fn dav1dsequenceheader_copy(v: &Dav1dSequenceHeader) -> Dav1dSequenceHeader {
// CHECK-LABEL: fn dav1dsequenceheader_copy(
// CHECK: _0 = copy (*_1);
v.clone()
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct mv {
pub y: i16,
pub x: i16,
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct MaskedInterIntraPredMode(u8);
#[derive(Clone)]
#[cfg_attr(copy, derive(Copy))]
#[repr(C)]
pub struct Av1BlockInter1d {
pub mv: [mv; 2],
pub wedge_idx: u8,
pub mask_sign: u8,
pub interintra_mode: MaskedInterIntraPredMode,
pub _padding: u8,
}
#[derive(Clone)]
#[cfg_attr(copy, derive(Copy))]
#[repr(C)]
pub struct Av1BlockInterNd {
pub one_d: Av1BlockInter1d,
}
#[derive(Clone, Copy)]
pub enum CompInterType {
WeightedAvg = 1,
Avg = 2,
Seg = 3,
Wedge = 4,
}
#[derive(Clone, Copy)]
pub enum MotionMode {
Translation = 0,
Obmc = 1,
Warp = 2,
}
#[derive(Clone, Copy)]
pub enum DrlProximity {
Nearest,
Nearer,
Near,
Nearish,
}
#[derive(Clone, Copy)]
pub enum TxfmSize {
S4x4 = 0,
S8x8 = 1,
S16x16 = 2,
S32x32 = 3,
S64x64 = 4,
R4x8 = 5,
R8x4 = 6,
R8x16 = 7,
R16x8 = 8,
R16x32 = 9,
R32x16 = 10,
R32x64 = 11,
R64x32 = 12,
R4x16 = 13,
R16x4 = 14,
R8x32 = 15,
R32x8 = 16,
R16x64 = 17,
R64x16 = 18,
}
#[derive(Clone, Copy)]
pub enum Filter2d {
Regular8Tap = 0,
RegularSmooth8Tap = 1,
RegularSharp8Tap = 2,
SharpRegular8Tap = 3,
SharpSmooth8Tap = 4,
Sharp8Tap = 5,
SmoothRegular8Tap = 6,
Smooth8Tap = 7,
SmoothSharp8Tap = 8,
Bilinear = 9,
}
#[derive(Clone, Copy)]
pub enum InterIntraType {
Blend,
Wedge,
}
#[cfg_attr(copy, derive(Copy))]
#[derive(Clone)]
#[repr(C)]
pub struct Av1BlockInter {
pub nd: Av1BlockInterNd,
pub comp_type: Option<CompInterType>,
pub inter_mode: u8,
pub motion_mode: MotionMode,
pub drl_idx: DrlProximity,
pub r#ref: [i8; 2],
pub max_ytx: TxfmSize,
pub filter2d: Filter2d,
pub interintra_type: Option<InterIntraType>,
pub tx_split0: u8,
pub tx_split1: u16,
}
#[cfg_attr(copy, derive(Copy))]
#[derive(Clone)]
#[repr(C)]
pub struct Av1BlockIntra {
pub y_mode: u8,
pub uv_mode: u8,
pub tx: TxfmSize,
pub pal_sz: [u8; 2],
pub y_angle: i8,
pub uv_angle: i8,
pub cfl_alpha: [i8; 2],
}
#[repr(C)]
pub enum Av1BlockIntraInter {
Intra(Av1BlockIntra),
Inter(Av1BlockInter),
}
use std::ffi::{c_int, c_uint};
pub type Dav1dPixelLayout = c_uint;
pub type Dav1dColorPrimaries = c_uint;
pub type Dav1dTransferCharacteristics = c_uint;
pub type Dav1dMatrixCoefficients = c_uint;
pub type Dav1dChromaSamplePosition = c_uint;
pub type Dav1dAdaptiveBoolean = c_uint;
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Dav1dSequenceHeaderOperatingPoint {
pub major_level: u8,
pub minor_level: u8,
pub initial_display_delay: u8,
pub idc: u16,
pub tier: u8,
pub decoder_model_param_present: u8,
pub display_model_param_present: u8,
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Dav1dSequenceHeaderOperatingParameterInfo {
pub decoder_buffer_delay: u32,
pub encoder_buffer_delay: u32,
pub low_delay_mode: u8,
}
pub const DAV1D_MAX_OPERATING_POINTS: usize = 32;
#[cfg_attr(copy, derive(Copy))]
#[derive(Clone)]
#[repr(C)]
pub struct Dav1dSequenceHeader {
pub profile: u8,
pub max_width: c_int,
pub max_height: c_int,
pub layout: Dav1dPixelLayout,
pub pri: Dav1dColorPrimaries,
pub trc: Dav1dTransferCharacteristics,
pub mtrx: Dav1dMatrixCoefficients,
pub chr: Dav1dChromaSamplePosition,
pub hbd: u8,
pub color_range: u8,
pub num_operating_points: u8,
pub operating_points: [Dav1dSequenceHeaderOperatingPoint; DAV1D_MAX_OPERATING_POINTS],
pub still_picture: u8,
pub reduced_still_picture_header: u8,
pub timing_info_present: u8,
pub num_units_in_tick: u32,
pub time_scale: u32,
pub equal_picture_interval: u8,
pub num_ticks_per_picture: u32,
pub decoder_model_info_present: u8,
pub encoder_decoder_buffer_delay_length: u8,
pub num_units_in_decoding_tick: u32,
pub buffer_removal_delay_length: u8,
pub frame_presentation_delay_length: u8,
pub display_model_info_present: u8,
pub width_n_bits: u8,
pub height_n_bits: u8,
pub frame_id_numbers_present: u8,
pub delta_frame_id_n_bits: u8,
pub frame_id_n_bits: u8,
pub sb128: u8,
pub filter_intra: u8,
pub intra_edge_filter: u8,
pub inter_intra: u8,
pub masked_compound: u8,
pub warped_motion: u8,
pub dual_filter: u8,
pub order_hint: u8,
pub jnt_comp: u8,
pub ref_frame_mvs: u8,
pub screen_content_tools: Dav1dAdaptiveBoolean,
pub force_integer_mv: Dav1dAdaptiveBoolean,
pub order_hint_n_bits: u8,
pub super_res: u8,
pub cdef: u8,
pub restoration: u8,
pub ss_hor: u8,
pub ss_ver: u8,
pub monochrome: u8,
pub color_description_present: u8,
pub separate_uv_delta_q: u8,
pub film_grain_present: u8,
pub operating_parameter_info:
[Dav1dSequenceHeaderOperatingParameterInfo; DAV1D_MAX_OPERATING_POINTS],
}
@@ -3,7 +3,6 @@
fn ub_if_b(_1: Thing) -> Thing {
debug t => _1;
let mut _0: Thing;
let mut _2: isize;
scope 1 (inlined #[track_caller] unreachable_unchecked) {
scope 2 (inlined core::ub_checks::check_language_ub) {
scope 3 (inlined core::ub_checks::check_language_ub::runtime) {
@@ -12,16 +11,7 @@ fn ub_if_b(_1: Thing) -> Thing {
}
bb0: {
_2 = discriminant(_1);
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb2];
}
bb1: {
_0 = move _1;
return;
}
bb2: {
unreachable;
}
}
@@ -4,11 +4,11 @@ fn two_unwrap_unchecked(_1: &Option<i32>) -> i32 {
debug v => _1;
let mut _0: i32;
let mut _2: std::option::Option<i32>;
let _4: i32;
let _3: i32;
scope 1 {
debug v1 => _4;
debug v1 => _3;
scope 2 {
debug v2 => _4;
debug v2 => _3;
}
scope 8 (inlined #[track_caller] Option::<i32>::unwrap_unchecked) {
scope 9 {
@@ -22,7 +22,6 @@ fn two_unwrap_unchecked(_1: &Option<i32>) -> i32 {
}
}
scope 3 (inlined #[track_caller] Option::<i32>::unwrap_unchecked) {
let mut _3: isize;
scope 4 {
}
scope 5 (inlined #[track_caller] unreachable_unchecked) {
@@ -35,17 +34,8 @@ fn two_unwrap_unchecked(_1: &Option<i32>) -> i32 {
bb0: {
_2 = copy (*_1);
_3 = discriminant(_2);
switchInt(copy _3) -> [0: bb2, 1: bb1, otherwise: bb2];
}
bb1: {
_4 = copy ((_2 as Some).0: i32);
_0 = Add(copy _4, copy _4);
_3 = copy ((_2 as Some).0: i32);
_0 = Add(copy _3, copy _3);
return;
}
bb2: {
unreachable;
}
}