From 9af4498582ec3ebdb36c9759b71ba5199d4b2572 Mon Sep 17 00:00:00 2001 From: dianqk Date: Sat, 11 Oct 2025 22:02:40 +0800 Subject: [PATCH 1/3] mir-opt: Simplify trivial constants in SimplifyConstCondition --- compiler/rustc_mir_transform/src/lib.rs | 2 + .../src/simplify_branches.rs | 28 +++++- tests/mir-opt/const_prop/trivial_const.rs | 15 +++ ...ifyConstCondition-after-inst-simplify.diff | 58 +++++++++++ .../pre-codegen/two_unwrap_unchecked.rs | 12 +++ ...ap_unchecked.two_unwrap_unchecked.GVN.diff | 96 +++++++++++++++++++ ....two_unwrap_unchecked.PreCodegen.after.mir | 69 +++++++++++++ 7 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 tests/mir-opt/const_prop/trivial_const.rs create mode 100644 tests/mir-opt/const_prop/trivial_const.unwrap_unchecked.SimplifyConstCondition-after-inst-simplify.diff create mode 100644 tests/mir-opt/pre-codegen/two_unwrap_unchecked.rs create mode 100644 tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.GVN.diff create mode 100644 tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.PreCodegen.after.mir diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 3ffd263fab027..b096e88eb87bb 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -189,6 +189,7 @@ declare_passes! { Final }; mod simplify_branches : SimplifyConstCondition { + AfterInstSimplify, AfterConstProp, Final }; @@ -708,6 +709,7 @@ pub(crate) fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<' // optimizations. This invalidates CFG caches, so avoid putting between // `ReferencePropagation` and `GVN` which both use the dominator tree. &instsimplify::InstSimplify::AfterSimplifyCfg, + &o1(simplify_branches::SimplifyConstCondition::AfterInstSimplify), &ref_prop::ReferencePropagation, &sroa::ScalarReplacementOfAggregates, &simplify::SimplifyLocals::BeforeConstProp, diff --git a/compiler/rustc_mir_transform/src/simplify_branches.rs b/compiler/rustc_mir_transform/src/simplify_branches.rs index ed94a058ec6d9..e464829c481fa 100644 --- a/compiler/rustc_mir_transform/src/simplify_branches.rs +++ b/compiler/rustc_mir_transform/src/simplify_branches.rs @@ -5,6 +5,7 @@ use tracing::trace; use crate::patch::MirPatch; pub(super) enum SimplifyConstCondition { + AfterInstSimplify, AfterConstProp, Final, } @@ -13,6 +14,9 @@ pub(super) enum SimplifyConstCondition { impl<'tcx> crate::MirPass<'tcx> for SimplifyConstCondition { fn name(&self) -> &'static str { match self { + SimplifyConstCondition::AfterInstSimplify => { + "SimplifyConstCondition-after-inst-simplify" + } SimplifyConstCondition::AfterConstProp => "SimplifyConstCondition-after-const-prop", SimplifyConstCondition::Final => "SimplifyConstCondition-final", } @@ -24,19 +28,39 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyConstCondition { let mut patch = MirPatch::new(body); 'blocks: for (bb, block) in body.basic_blocks.iter_enumerated() { + let mut pre_local_const: Option<(Local, &'_ ConstOperand<'_>)> = None; + for (statement_index, stmt) in block.statements.iter().enumerate() { + let has_local_const = pre_local_const.take(); // Simplify `assume` of a known value: either a NOP or unreachable. if let StatementKind::Intrinsic(box ref intrinsic) = stmt.kind && let NonDivergingIntrinsic::Assume(discr) = intrinsic - && let Operand::Constant(c) = discr - && let Some(constant) = c.const_.try_eval_bool(tcx, typing_env) { + let c = if let Operand::Constant(c) = discr { + c + } else if let Some((local, c)) = has_local_const + && let Some(assume_local) = discr.place().and_then(|p| p.as_local()) + && local == assume_local + { + c + } else { + continue; + }; + let Some(constant) = c.const_.try_eval_bool(tcx, typing_env) else { + continue; + }; if constant { patch.nop_statement(Location { block: bb, statement_index }); } else { patch.patch_terminator(bb, TerminatorKind::Unreachable); continue 'blocks; } + } else if let StatementKind::Assign(box (ref lhs, ref rvalue)) = stmt.kind + && let Some(local) = lhs.as_local() + && let Rvalue::Use(Operand::Constant(c)) = rvalue + && c.const_.ty().is_bool() + { + pre_local_const = Some((local, c)); } } diff --git a/tests/mir-opt/const_prop/trivial_const.rs b/tests/mir-opt/const_prop/trivial_const.rs new file mode 100644 index 0000000000000..e9134c1ebdeff --- /dev/null +++ b/tests/mir-opt/const_prop/trivial_const.rs @@ -0,0 +1,15 @@ +//@ test-mir-pass: SimplifyConstCondition-after-inst-simplify +//@ compile-flags: -Zmir-enable-passes=+InstSimplify-after-simplifycfg -Zub_checks=false -Zinline-mir + +#![crate_type = "lib"] + +// EMIT_MIR trivial_const.unwrap_unchecked.SimplifyConstCondition-after-inst-simplify.diff +pub fn unwrap_unchecked(v: &Option) -> i32 { + // CHECK-LABEL: fn unwrap_unchecked( + // CHECK: bb0: { + // CHECK: switchInt({{.*}}) -> [0: [[AssumeFalseBB:bb.*]], 1: + // CHECK: [[AssumeFalseBB]]: { + // CHECK-NEXT: unreachable; + let v = unsafe { v.unwrap_unchecked() }; + v +} diff --git a/tests/mir-opt/const_prop/trivial_const.unwrap_unchecked.SimplifyConstCondition-after-inst-simplify.diff b/tests/mir-opt/const_prop/trivial_const.unwrap_unchecked.SimplifyConstCondition-after-inst-simplify.diff new file mode 100644 index 0000000000000..d14c42a330eb7 --- /dev/null +++ b/tests/mir-opt/const_prop/trivial_const.unwrap_unchecked.SimplifyConstCondition-after-inst-simplify.diff @@ -0,0 +1,58 @@ +- // MIR for `unwrap_unchecked` before SimplifyConstCondition-after-inst-simplify ++ // MIR for `unwrap_unchecked` after SimplifyConstCondition-after-inst-simplify + + fn unwrap_unchecked(_1: &Option) -> i32 { + debug v => _1; + let mut _0: i32; + let _2: i32; + let mut _3: std::option::Option; + scope 1 { + debug v => _2; + } + scope 2 (inlined #[track_caller] Option::::unwrap_unchecked) { + let mut _4: isize; + scope 3 { + } + scope 4 (inlined #[track_caller] unreachable_unchecked) { + let _5: (); + scope 5 (inlined core::ub_checks::check_language_ub) { + let mut _6: bool; + scope 6 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + } + } + + bb0: { + StorageLive(_2); + StorageLive(_3); + _3 = copy (*_1); + StorageLive(_4); + StorageLive(_5); + _4 = discriminant(_3); + switchInt(move _4) -> [0: bb2, 1: bb3, otherwise: bb1]; + } + + bb1: { + unreachable; + } + + bb2: { +- StorageLive(_6); +- _6 = const false; +- assume(copy _6); +- _5 = unreachable_unchecked::precondition_check() -> [return: bb1, unwind unreachable]; ++ unreachable; + } + + bb3: { + _2 = move ((_3 as Some).0: i32); + StorageDead(_5); + StorageDead(_4); + StorageDead(_3); + _0 = copy _2; + StorageDead(_2); + return; + } + } + diff --git a/tests/mir-opt/pre-codegen/two_unwrap_unchecked.rs b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.rs new file mode 100644 index 0000000000000..e82e7f2236f18 --- /dev/null +++ b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.rs @@ -0,0 +1,12 @@ +// skip-filecheck +//@ compile-flags: -O + +#![crate_type = "lib"] + +// EMIT_MIR two_unwrap_unchecked.two_unwrap_unchecked.GVN.diff +// EMIT_MIR two_unwrap_unchecked.two_unwrap_unchecked.PreCodegen.after.mir +pub fn two_unwrap_unchecked(v: &Option) -> i32 { + let v1 = unsafe { v.unwrap_unchecked() }; + let v2 = unsafe { v.unwrap_unchecked() }; + v1 + v2 +} diff --git a/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.GVN.diff b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.GVN.diff new file mode 100644 index 0000000000000..3b96b4ebc7913 --- /dev/null +++ b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.GVN.diff @@ -0,0 +1,96 @@ +- // MIR for `two_unwrap_unchecked` before GVN ++ // MIR for `two_unwrap_unchecked` after GVN + + fn two_unwrap_unchecked(_1: &Option) -> i32 { + debug v => _1; + let mut _0: i32; + let _2: i32; + let mut _3: std::option::Option; + let mut _5: std::option::Option; + let mut _6: i32; + let mut _7: i32; + scope 1 { + debug v1 => _2; + let _4: i32; + scope 2 { + debug v2 => _4; + } + scope 8 (inlined #[track_caller] Option::::unwrap_unchecked) { + let mut _9: isize; + scope 9 { + } + scope 10 (inlined #[track_caller] unreachable_unchecked) { + scope 11 (inlined core::ub_checks::check_language_ub) { + scope 12 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + } + } + } + scope 3 (inlined #[track_caller] Option::::unwrap_unchecked) { + let mut _8: isize; + scope 4 { + } + scope 5 (inlined #[track_caller] unreachable_unchecked) { + scope 6 (inlined core::ub_checks::check_language_ub) { + scope 7 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + } + } + + bb0: { +- StorageLive(_2); ++ nop; + StorageLive(_3); + _3 = copy (*_1); + StorageLive(_8); + _8 = discriminant(_3); + switchInt(move _8) -> [0: bb2, 1: bb3, otherwise: bb1]; + } + + bb1: { + unreachable; + } + + bb2: { + unreachable; + } + + bb3: { + _2 = move ((_3 as Some).0: i32); + StorageDead(_8); + StorageDead(_3); +- StorageLive(_4); ++ nop; + StorageLive(_5); + _5 = copy (*_1); + StorageLive(_9); + _9 = discriminant(_5); + switchInt(move _9) -> [0: bb4, 1: bb5, otherwise: bb1]; + } + + bb4: { + unreachable; + } + + bb5: { + _4 = move ((_5 as Some).0: i32); + StorageDead(_9); + StorageDead(_5); + StorageLive(_6); + _6 = copy _2; + StorageLive(_7); + _7 = copy _4; +- _0 = Add(move _6, move _7); ++ _0 = Add(copy _2, copy _4); + StorageDead(_7); + StorageDead(_6); +- StorageDead(_4); +- StorageDead(_2); ++ nop; ++ nop; + return; + } + } + diff --git a/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.PreCodegen.after.mir b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.PreCodegen.after.mir new file mode 100644 index 0000000000000..67910b4f17841 --- /dev/null +++ b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.PreCodegen.after.mir @@ -0,0 +1,69 @@ +// MIR for `two_unwrap_unchecked` after PreCodegen + +fn two_unwrap_unchecked(_1: &Option) -> i32 { + debug v => _1; + let mut _0: i32; + let mut _2: std::option::Option; + let _4: i32; + let mut _5: std::option::Option; + scope 1 { + debug v1 => _4; + let _7: i32; + scope 2 { + debug v2 => _7; + } + scope 8 (inlined #[track_caller] Option::::unwrap_unchecked) { + let mut _6: isize; + scope 9 { + } + scope 10 (inlined #[track_caller] unreachable_unchecked) { + scope 11 (inlined core::ub_checks::check_language_ub) { + scope 12 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + } + } + } + scope 3 (inlined #[track_caller] Option::::unwrap_unchecked) { + let mut _3: isize; + scope 4 { + } + scope 5 (inlined #[track_caller] unreachable_unchecked) { + scope 6 (inlined core::ub_checks::check_language_ub) { + scope 7 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + } + } + + bb0: { + StorageLive(_2); + _2 = copy (*_1); + StorageLive(_3); + _3 = discriminant(_2); + switchInt(move _3) -> [0: bb3, 1: bb1, otherwise: bb3]; + } + + bb1: { + _4 = move ((_2 as Some).0: i32); + StorageDead(_3); + StorageDead(_2); + StorageLive(_5); + _5 = copy (*_1); + StorageLive(_6); + _6 = discriminant(_5); + switchInt(move _6) -> [0: bb3, 1: bb2, otherwise: bb3]; + } + + bb2: { + _7 = move ((_5 as Some).0: i32); + StorageDead(_6); + StorageDead(_5); + _0 = Add(copy _4, copy _7); + return; + } + + bb3: { + unreachable; + } +} From 28c79602eec8a90ef5af42b5859d3c680cc48d80 Mon Sep 17 00:00:00 2001 From: dianqk Date: Tue, 14 Oct 2025 21:30:53 +0800 Subject: [PATCH 2/3] GVN: Preserve derefs at unreachable --- compiler/rustc_mir_transform/src/gvn.rs | 4 +- .../pre-codegen/two_unwrap_unchecked.rs | 5 ++- ...ap_unchecked.two_unwrap_unchecked.GVN.diff | 39 ++++++++++++------- ....two_unwrap_unchecked.PreCodegen.after.mir | 28 +++---------- 4 files changed, 36 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 0d97571fa818a..579bb939ca536 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1930,7 +1930,9 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> { // Currently, only preserving derefs for trivial terminators like SwitchInt and Goto. let safe_to_preserve_derefs = matches!( terminator.kind, - TerminatorKind::SwitchInt { .. } | TerminatorKind::Goto { .. } + TerminatorKind::SwitchInt { .. } + | TerminatorKind::Goto { .. } + | TerminatorKind::Unreachable ); if !safe_to_preserve_derefs { self.invalidate_derefs(); diff --git a/tests/mir-opt/pre-codegen/two_unwrap_unchecked.rs b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.rs index e82e7f2236f18..7b742b956ae5c 100644 --- a/tests/mir-opt/pre-codegen/two_unwrap_unchecked.rs +++ b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.rs @@ -1,4 +1,3 @@ -// skip-filecheck //@ compile-flags: -O #![crate_type = "lib"] @@ -6,6 +5,10 @@ // EMIT_MIR two_unwrap_unchecked.two_unwrap_unchecked.GVN.diff // EMIT_MIR two_unwrap_unchecked.two_unwrap_unchecked.PreCodegen.after.mir pub fn two_unwrap_unchecked(v: &Option) -> i32 { + // CHECK-LABEL: fn two_unwrap_unchecked( + // CHECK: [[DEREF_V:_.*]] = copy (*_1); + // CHECK: [[V1V2:_.*]] = copy (([[DEREF_V]] as Some).0: i32); + // CHECK: _0 = Add(copy [[V1V2]], copy [[V1V2]]); let v1 = unsafe { v.unwrap_unchecked() }; let v2 = unsafe { v.unwrap_unchecked() }; v1 + v2 diff --git a/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.GVN.diff b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.GVN.diff index 3b96b4ebc7913..5b063e6762e07 100644 --- a/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.GVN.diff +++ b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.GVN.diff @@ -41,12 +41,15 @@ bb0: { - StorageLive(_2); +- StorageLive(_3); ++ nop; + nop; - StorageLive(_3); _3 = copy (*_1); - StorageLive(_8); +- StorageLive(_8); ++ nop; _8 = discriminant(_3); - switchInt(move _8) -> [0: bb2, 1: bb3, otherwise: bb1]; +- switchInt(move _8) -> [0: bb2, 1: bb3, otherwise: bb1]; ++ switchInt(copy _8) -> [0: bb2, 1: bb3, otherwise: bb1]; } bb1: { @@ -58,16 +61,21 @@ } bb3: { - _2 = move ((_3 as Some).0: i32); - StorageDead(_8); - StorageDead(_3); -- StorageLive(_4); +- _2 = move ((_3 as Some).0: i32); +- StorageDead(_8); +- StorageDead(_3); ++ _2 = copy ((_3 as Some).0: i32); + nop; ++ nop; + StorageLive(_4); StorageLive(_5); - _5 = copy (*_1); +- _5 = copy (*_1); ++ _5 = copy _3; StorageLive(_9); - _9 = discriminant(_5); - switchInt(move _9) -> [0: bb4, 1: bb5, otherwise: bb1]; +- _9 = discriminant(_5); +- switchInt(move _9) -> [0: bb4, 1: bb5, otherwise: bb1]; ++ _9 = copy _8; ++ switchInt(copy _8) -> [0: bb4, 1: bb5, otherwise: bb1]; } bb4: { @@ -75,20 +83,21 @@ } bb5: { - _4 = move ((_5 as Some).0: i32); +- _4 = move ((_5 as Some).0: i32); ++ _4 = copy _2; StorageDead(_9); StorageDead(_5); StorageLive(_6); _6 = copy _2; StorageLive(_7); - _7 = copy _4; +- _7 = copy _4; - _0 = Add(move _6, move _7); -+ _0 = Add(copy _2, copy _4); ++ _7 = copy _2; ++ _0 = Add(copy _2, copy _2); StorageDead(_7); StorageDead(_6); -- StorageDead(_4); + StorageDead(_4); - StorageDead(_2); -+ nop; + nop; return; } diff --git a/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.PreCodegen.after.mir b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.PreCodegen.after.mir index 67910b4f17841..b2b7f88d8534b 100644 --- a/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.PreCodegen.after.mir +++ b/tests/mir-opt/pre-codegen/two_unwrap_unchecked.two_unwrap_unchecked.PreCodegen.after.mir @@ -5,15 +5,12 @@ fn two_unwrap_unchecked(_1: &Option) -> i32 { let mut _0: i32; let mut _2: std::option::Option; let _4: i32; - let mut _5: std::option::Option; scope 1 { debug v1 => _4; - let _7: i32; scope 2 { - debug v2 => _7; + debug v2 => _4; } scope 8 (inlined #[track_caller] Option::::unwrap_unchecked) { - let mut _6: isize; scope 9 { } scope 10 (inlined #[track_caller] unreachable_unchecked) { @@ -37,33 +34,18 @@ fn two_unwrap_unchecked(_1: &Option) -> i32 { } bb0: { - StorageLive(_2); _2 = copy (*_1); - StorageLive(_3); _3 = discriminant(_2); - switchInt(move _3) -> [0: bb3, 1: bb1, otherwise: bb3]; + switchInt(copy _3) -> [0: bb2, 1: bb1, otherwise: bb2]; } bb1: { - _4 = move ((_2 as Some).0: i32); - StorageDead(_3); - StorageDead(_2); - StorageLive(_5); - _5 = copy (*_1); - StorageLive(_6); - _6 = discriminant(_5); - switchInt(move _6) -> [0: bb3, 1: bb2, otherwise: bb3]; - } - - bb2: { - _7 = move ((_5 as Some).0: i32); - StorageDead(_6); - StorageDead(_5); - _0 = Add(copy _4, copy _7); + _4 = copy ((_2 as Some).0: i32); + _0 = Add(copy _4, copy _4); return; } - bb3: { + bb2: { unreachable; } } From da8a271fc25bb329c1a04dd92f40da86c4540f1e Mon Sep 17 00:00:00 2001 From: dianqk Date: Sun, 12 Oct 2025 21:26:05 +0800 Subject: [PATCH 3/3] GVN: Merge instances of `*_x` even if `_x` is a mutable borrow --- compiler/rustc_mir_transform/src/gvn.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 579bb939ca536..9f66309e54fd7 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -814,21 +814,15 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { let projection_ty = place_ty.projection_ty(self.tcx, proj); let proj = match proj { ProjectionElem::Deref => { - if let Some(Mutability::Not) = place_ty.ty.ref_mutability() - && projection_ty.ty.is_freeze(self.tcx, self.typing_env()) + if let Value::Address { base, projection, .. } = self.get(value) + && let Some(value) = self.dereference_address(base, projection) { - if let Value::Address { base, projection, .. } = self.get(value) - && let Some(value) = self.dereference_address(base, projection) - { - return Some((projection_ty, value)); - } - - // An immutable borrow `_x` always points to the same value for the - // lifetime of the borrow, so we can merge all instances of `*_x`. - return Some((projection_ty, self.insert_deref(projection_ty.ty, value))); - } else { - return None; + return Some((projection_ty, value)); } + + // An borrow `_x` can point to the same value for the + // lifetime of the borrow, so we can merge instances of `*_x`. + return Some((projection_ty, self.insert_deref(projection_ty.ty, value))); } ProjectionElem::Downcast(name, index) => ProjectionElem::Downcast(name, index), ProjectionElem::Field(f, _) => match self.get(value) { @@ -1933,6 +1927,7 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> { TerminatorKind::SwitchInt { .. } | TerminatorKind::Goto { .. } | TerminatorKind::Unreachable + | TerminatorKind::Return ); if !safe_to_preserve_derefs { self.invalidate_derefs();