From 6b84d7566eaeefe521fdba12c65a9e0b137e34a6 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 3 Mar 2024 02:22:37 +0100 Subject: [PATCH 1/6] Add tests --- tests/mir-opt/or_pattern.rs | 24 +++++ ...ut_second_or.SimplifyCfg-initial.after.mir | 100 ++++++++++++++++++ ...le_switchint.SimplifyCfg-initial.after.mir | 79 ++++++++++++++ tests/ui/or-patterns/bindings-runpass-2.rs | 1 + tests/ui/or-patterns/inner-or-pat.or3.stderr | 2 +- tests/ui/or-patterns/inner-or-pat.or4.stderr | 2 +- tests/ui/or-patterns/inner-or-pat.rs | 4 +- ...ssue-70413-no-unreachable-pat-and-guard.rs | 21 ++-- tests/ui/or-patterns/search-via-bindings.rs | 22 ++++ .../or-patterns/simplification_subtleties.rs | 11 ++ 10 files changed, 250 insertions(+), 16 deletions(-) create mode 100644 tests/mir-opt/or_pattern.rs create mode 100644 tests/mir-opt/or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir create mode 100644 tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir create mode 100644 tests/ui/or-patterns/simplification_subtleties.rs diff --git a/tests/mir-opt/or_pattern.rs b/tests/mir-opt/or_pattern.rs new file mode 100644 index 0000000000000..0ad0ce8ead1e2 --- /dev/null +++ b/tests/mir-opt/or_pattern.rs @@ -0,0 +1,24 @@ +// skip-filecheck + +// EMIT_MIR or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir +fn shortcut_second_or() { + // Check that after matching `0`, failing to match `2 | 3` skips trying to match `(1, 2 | 3)`. + match ((0, 0), 0) { + (x @ (0, _) | x @ (_, 1), y @ 2 | y @ 3) => {} + _ => {} + } +} + +// EMIT_MIR or_pattern.single_switchint.SimplifyCfg-initial.after.mir +fn single_switchint() { + // Check how many `SwitchInt`s we do. In theory a single one is necessary. + match (1, true) { + (1, true) => 1, + (2, false) => 2, + (1 | 2, true | false) => 3, + (3 | 4, true | false) => 4, + _ => 5, + }; +} + +fn main() {} diff --git a/tests/mir-opt/or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir b/tests/mir-opt/or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir new file mode 100644 index 0000000000000..56edd38a6da4c --- /dev/null +++ b/tests/mir-opt/or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir @@ -0,0 +1,100 @@ +// MIR for `shortcut_second_or` after SimplifyCfg-initial + +fn shortcut_second_or() -> () { + let mut _0: (); + let mut _1: ((i32, i32), i32); + let mut _2: (i32, i32); + let _3: (i32, i32); + let _4: i32; + scope 1 { + debug x => _3; + debug y => _4; + } + + bb0: { + StorageLive(_1); + StorageLive(_2); + _2 = (const 0_i32, const 0_i32); + _1 = (move _2, const 0_i32); + StorageDead(_2); + PlaceMention(_1); + switchInt(((_1.0: (i32, i32)).0: i32)) -> [0: bb4, otherwise: bb2]; + } + + bb1: { + _0 = const (); + goto -> bb14; + } + + bb2: { + switchInt(((_1.0: (i32, i32)).1: i32)) -> [1: bb3, otherwise: bb1]; + } + + bb3: { + switchInt((_1.1: i32)) -> [2: bb7, 3: bb8, otherwise: bb1]; + } + + bb4: { + switchInt((_1.1: i32)) -> [2: bb5, 3: bb6, otherwise: bb1]; + } + + bb5: { + falseEdge -> [real: bb10, imaginary: bb6]; + } + + bb6: { + falseEdge -> [real: bb11, imaginary: bb2]; + } + + bb7: { + falseEdge -> [real: bb12, imaginary: bb8]; + } + + bb8: { + falseEdge -> [real: bb13, imaginary: bb1]; + } + + bb9: { + _0 = const (); + StorageDead(_4); + StorageDead(_3); + goto -> bb14; + } + + bb10: { + StorageLive(_3); + _3 = (_1.0: (i32, i32)); + StorageLive(_4); + _4 = (_1.1: i32); + goto -> bb9; + } + + bb11: { + StorageLive(_3); + _3 = (_1.0: (i32, i32)); + StorageLive(_4); + _4 = (_1.1: i32); + goto -> bb9; + } + + bb12: { + StorageLive(_3); + _3 = (_1.0: (i32, i32)); + StorageLive(_4); + _4 = (_1.1: i32); + goto -> bb9; + } + + bb13: { + StorageLive(_3); + _3 = (_1.0: (i32, i32)); + StorageLive(_4); + _4 = (_1.1: i32); + goto -> bb9; + } + + bb14: { + StorageDead(_1); + return; + } +} diff --git a/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir b/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir new file mode 100644 index 0000000000000..72b1a835cf081 --- /dev/null +++ b/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir @@ -0,0 +1,79 @@ +// MIR for `single_switchint` after SimplifyCfg-initial + +fn single_switchint() -> () { + let mut _0: (); + let _1: i32; + let mut _2: (i32, bool); + + bb0: { + StorageLive(_1); + StorageLive(_2); + _2 = (const 1_i32, const true); + PlaceMention(_2); + switchInt((_2.0: i32)) -> [1: bb6, 2: bb8, otherwise: bb1]; + } + + bb1: { + switchInt((_2.0: i32)) -> [1: bb3, 2: bb3, otherwise: bb2]; + } + + bb2: { + switchInt((_2.0: i32)) -> [3: bb5, 4: bb5, otherwise: bb4]; + } + + bb3: { + falseEdge -> [real: bb12, imaginary: bb2]; + } + + bb4: { + _1 = const 5_i32; + goto -> bb14; + } + + bb5: { + falseEdge -> [real: bb13, imaginary: bb4]; + } + + bb6: { + switchInt((_2.1: bool)) -> [0: bb1, otherwise: bb7]; + } + + bb7: { + falseEdge -> [real: bb10, imaginary: bb8]; + } + + bb8: { + switchInt((_2.1: bool)) -> [0: bb9, otherwise: bb1]; + } + + bb9: { + falseEdge -> [real: bb11, imaginary: bb1]; + } + + bb10: { + _1 = const 1_i32; + goto -> bb14; + } + + bb11: { + _1 = const 2_i32; + goto -> bb14; + } + + bb12: { + _1 = const 3_i32; + goto -> bb14; + } + + bb13: { + _1 = const 4_i32; + goto -> bb14; + } + + bb14: { + StorageDead(_2); + StorageDead(_1); + _0 = const (); + return; + } +} diff --git a/tests/ui/or-patterns/bindings-runpass-2.rs b/tests/ui/or-patterns/bindings-runpass-2.rs index 657d7f1ed189f..a9ae998108405 100644 --- a/tests/ui/or-patterns/bindings-runpass-2.rs +++ b/tests/ui/or-patterns/bindings-runpass-2.rs @@ -26,5 +26,6 @@ fn main() { assert_eq!(or_at(Err(7)), 207); assert_eq!(or_at(Err(8)), 8); assert_eq!(or_at(Err(20)), 220); + assert_eq!(or_at(Err(34)), 134); assert_eq!(or_at(Err(50)), 500); } diff --git a/tests/ui/or-patterns/inner-or-pat.or3.stderr b/tests/ui/or-patterns/inner-or-pat.or3.stderr index 10ec7c202e4d6..5c522a97ccef3 100644 --- a/tests/ui/or-patterns/inner-or-pat.or3.stderr +++ b/tests/ui/or-patterns/inner-or-pat.or3.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/inner-or-pat.rs:38:54 + --> $DIR/inner-or-pat.rs:36:54 | LL | match x { | - this expression has type `&str` diff --git a/tests/ui/or-patterns/inner-or-pat.or4.stderr b/tests/ui/or-patterns/inner-or-pat.or4.stderr index 97800161d82fe..508520c823793 100644 --- a/tests/ui/or-patterns/inner-or-pat.or4.stderr +++ b/tests/ui/or-patterns/inner-or-pat.or4.stderr @@ -1,5 +1,5 @@ error[E0408]: variable `x` is not bound in all patterns - --> $DIR/inner-or-pat.rs:53:37 + --> $DIR/inner-or-pat.rs:51:37 | LL | (x @ "red" | (x @ "blue" | "red")) => { | - ^^^^^ pattern doesn't bind `x` diff --git a/tests/ui/or-patterns/inner-or-pat.rs b/tests/ui/or-patterns/inner-or-pat.rs index ceb0a8b3f7969..4d136de005357 100644 --- a/tests/ui/or-patterns/inner-or-pat.rs +++ b/tests/ui/or-patterns/inner-or-pat.rs @@ -1,7 +1,5 @@ -//@ revisions: or1 or2 or3 or4 or5 +//@ revisions: or1 or3 or4 //@ [or1] run-pass -//@ [or2] run-pass -//@ [or5] run-pass #![allow(unreachable_patterns)] #![allow(unused_variables)] diff --git a/tests/ui/or-patterns/issue-70413-no-unreachable-pat-and-guard.rs b/tests/ui/or-patterns/issue-70413-no-unreachable-pat-and-guard.rs index 7d62364a6aeec..76dc298a5c851 100644 --- a/tests/ui/or-patterns/issue-70413-no-unreachable-pat-and-guard.rs +++ b/tests/ui/or-patterns/issue-70413-no-unreachable-pat-and-guard.rs @@ -1,21 +1,20 @@ -//@ check-pass +//@ run-pass #![deny(unreachable_patterns)] fn main() { - match (3,42) { - (a,_) | (_,a) if a > 10 => {println!("{}", a)} - _ => () + match (3, 42) { + (a, _) | (_, a) if a > 10 => {} + _ => unreachable!(), } - match Some((3,42)) { - Some((a, _)) | Some((_, a)) if a > 10 => {println!("{}", a)} - _ => () - + match Some((3, 42)) { + Some((a, _)) | Some((_, a)) if a > 10 => {} + _ => unreachable!(), } - match Some((3,42)) { - Some((a, _) | (_, a)) if a > 10 => {println!("{}", a)} - _ => () + match Some((3, 42)) { + Some((a, _) | (_, a)) if a > 10 => {} + _ => unreachable!(), } } diff --git a/tests/ui/or-patterns/search-via-bindings.rs b/tests/ui/or-patterns/search-via-bindings.rs index a760112f1d421..42174bd7cef73 100644 --- a/tests/ui/or-patterns/search-via-bindings.rs +++ b/tests/ui/or-patterns/search-via-bindings.rs @@ -42,6 +42,23 @@ fn search_old_style(target: (bool, bool, bool)) -> u32 { } } +// Check that a dummy or-pattern also leads to running the guard multiple times. +fn search_with_dummy(target: (bool, bool)) -> u32 { + let x = ((false, true), (false, true), ()); + let mut guard_count = 0; + match x { + ((a, _) | (_, a), (b, _) | (_, b), _ | _) + if { + guard_count += 1; + (a, b) == target + } => + { + guard_count + } + _ => unreachable!(), + } +} + fn main() { assert_eq!(search((false, false, false)), 1); assert_eq!(search((false, false, true)), 2); @@ -60,4 +77,9 @@ fn main() { assert_eq!(search_old_style((true, false, true)), 6); assert_eq!(search_old_style((true, true, false)), 7); assert_eq!(search_old_style((true, true, true)), 8); + + assert_eq!(search_with_dummy((false, false)), 1); + assert_eq!(search_with_dummy((false, true)), 3); + assert_eq!(search_with_dummy((true, false)), 5); + assert_eq!(search_with_dummy((true, true)), 7); } diff --git a/tests/ui/or-patterns/simplification_subtleties.rs b/tests/ui/or-patterns/simplification_subtleties.rs new file mode 100644 index 0000000000000..a932bd531e6d8 --- /dev/null +++ b/tests/ui/or-patterns/simplification_subtleties.rs @@ -0,0 +1,11 @@ +//@ run-pass + +#[allow(unreachable_patterns)] +fn main() { + // Test that we don't naively sort the two `2`s together and confuse the failure paths. + match (1, true) { + (1 | 2, false | false) => unreachable!(), + (2, _) => unreachable!(), + _ => {} + } +} From e74b30e3a9f75f8854fb04a6f9b528077c5e9ec5 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 20 Mar 2024 15:53:25 +0100 Subject: [PATCH 2/6] Tweak simple or-pattern expansion --- .../rustc_mir_build/src/build/matches/mod.rs | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 544f27b84e90d..13e208524f156 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1364,9 +1364,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { otherwise_block: BasicBlock, candidates: &mut [&mut Candidate<'pat, 'tcx>], ) { - let mut split_or_candidate = false; - for candidate in &mut *candidates { - if let [MatchPair { test_case: TestCase::Or { .. }, .. }] = &*candidate.match_pairs { + let expand_or_pats = candidates.iter().any(|candidate| { + matches!(&*candidate.match_pairs, [MatchPair { test_case: TestCase::Or { .. }, .. }]) + }); + + ensure_sufficient_stack(|| { + if expand_or_pats { // Split a candidate in which the only match-pair is an or-pattern into multiple // candidates. This is so that // @@ -1376,30 +1379,32 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // } // // only generates a single switch. - let match_pair = candidate.match_pairs.pop().unwrap(); - self.create_or_subcandidates(candidate, match_pair); - split_or_candidate = true; - } - } - - ensure_sufficient_stack(|| { - if split_or_candidate { - // At least one of the candidates has been split into subcandidates. - // We need to change the candidate list to include those. let mut new_candidates = Vec::new(); for candidate in candidates.iter_mut() { - candidate.visit_leaves(|leaf_candidate| new_candidates.push(leaf_candidate)); + if let [MatchPair { test_case: TestCase::Or { .. }, .. }] = + &*candidate.match_pairs + { + let match_pair = candidate.match_pairs.pop().unwrap(); + self.create_or_subcandidates(candidate, match_pair); + for subcandidate in candidate.subcandidates.iter_mut() { + new_candidates.push(subcandidate); + } + } else { + new_candidates.push(candidate); + } } self.match_candidates( span, scrutinee_span, start_block, otherwise_block, - &mut *new_candidates, + new_candidates.as_mut_slice(), ); for candidate in candidates { - self.merge_trivial_subcandidates(candidate); + if !candidate.subcandidates.is_empty() { + self.merge_trivial_subcandidates(candidate); + } } } else { self.match_simplified_candidates( From 5fe2ca65cf5b2696d4d48c261cb7f6cecb2f7099 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 3 Mar 2024 01:42:18 +0100 Subject: [PATCH 3/6] Always set `otherwise_block`s --- .../rustc_mir_build/src/build/matches/mod.rs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 13e208524f156..a67c59138cd29 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1074,12 +1074,9 @@ struct Candidate<'pat, 'tcx> { // because that would break binding consistency. subcandidates: Vec>, - /// ...and the guard must be evaluated if there is one. + /// ...and if there is a guard it must be evaluated; if it's `false` then branch to `otherwise_block`. has_guard: bool, - /// If the guard is `false` then branch to `otherwise_block`. - otherwise_block: Option, - /// If the candidate matches, bindings and ascriptions must be established. extra_data: PatternExtraData<'tcx>, @@ -1090,6 +1087,9 @@ struct Candidate<'pat, 'tcx> { /// The block before the `bindings` have been established. pre_binding_block: Option, + /// The block to branch to if the guard or a nested candidate fails to match. + otherwise_block: Option, + /// The earliest block that has only candidates >= this one as descendents. Used for false /// edges, see the doc for [`Builder::match_expr`]. false_edge_start_block: Option, @@ -1500,11 +1500,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { candidate.pre_binding_block = Some(start_block); let otherwise_block = self.cfg.start_new_block(); - if candidate.has_guard { - // Create the otherwise block for this candidate, which is the - // pre-binding block for the next candidate. - candidate.otherwise_block = Some(otherwise_block); - } + // Create the otherwise block for this candidate, which is the + // pre-binding block for the next candidate. + candidate.otherwise_block = Some(otherwise_block); otherwise_block } @@ -1591,10 +1589,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { assert!(leaf_candidate.match_pairs.is_empty()); leaf_candidate.match_pairs.extend(remaining_match_pairs.iter().cloned()); let or_start = leaf_candidate.pre_binding_block.unwrap(); - // In a case like `(a | b, c | d)`, if `a` succeeds and `c | d` fails, we know `(b, - // c | d)` will fail too. If there is no guard, we skip testing of `b` by branching - // directly to `remainder_start`. If there is a guard, we have to try `(b, c | d)`. - let or_otherwise = leaf_candidate.otherwise_block.unwrap_or(remainder_start); + // In a case like `(P | Q, R | S)`, if `P` succeeds and `R | S` fails, we know `(Q, + // R | S)` will fail too. If there is no guard, we skip testing of `Q` by branching + // directly to `remainder_start`. If there is a guard, `or_otherwise` can be reached + // by guard failure as well, so we can't skip `Q`. + let or_otherwise = if leaf_candidate.has_guard { + leaf_candidate.otherwise_block.unwrap() + } else { + remainder_start + }; self.test_candidates_with_or( span, scrutinee_span, @@ -1669,6 +1672,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { subcandidate.subcandidates.is_empty() && subcandidate.extra_data.is_empty() }); if can_merge { + let mut last_otherwise = None; let any_matches = self.cfg.start_new_block(); let or_span = candidate.or_span.take().unwrap(); let source_info = self.source_info(or_span); @@ -1679,8 +1683,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { for subcandidate in mem::take(&mut candidate.subcandidates) { let or_block = subcandidate.pre_binding_block.unwrap(); self.cfg.goto(or_block, source_info, any_matches); + last_otherwise = subcandidate.otherwise_block; } candidate.pre_binding_block = Some(any_matches); + assert!(last_otherwise.is_some()); + candidate.otherwise_block = last_otherwise; } else { // Never subcandidates may have a set of bindings inconsistent with their siblings, // which would break later code. So we filter them out. Note that we can't filter out From 764f086f2cd7cc4e2ca9c5fd9f53ba4c0b3a1740 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 4 Mar 2024 01:41:37 +0100 Subject: [PATCH 4/6] Use `otherwise_block` for or-pattern shortcutting --- compiler/rustc_mir_build/src/build/matches/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index a67c59138cd29..a5f24ba3e5b20 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1585,18 +1585,23 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // We could add them to the or-candidates before the call to `test_or_pattern` but this // would make it impossible to detect simplifiable or-patterns. That would guarantee // exponentially large CFGs for cases like `(1 | 2, 3 | 4, ...)`. + let mut last_otherwise = None; + first_candidate.visit_leaves(|leaf_candidate| { + last_otherwise = leaf_candidate.otherwise_block; + }); first_candidate.visit_leaves(|leaf_candidate| { assert!(leaf_candidate.match_pairs.is_empty()); leaf_candidate.match_pairs.extend(remaining_match_pairs.iter().cloned()); let or_start = leaf_candidate.pre_binding_block.unwrap(); // In a case like `(P | Q, R | S)`, if `P` succeeds and `R | S` fails, we know `(Q, // R | S)` will fail too. If there is no guard, we skip testing of `Q` by branching - // directly to `remainder_start`. If there is a guard, `or_otherwise` can be reached - // by guard failure as well, so we can't skip `Q`. + // directly to `last_otherwise`. If there is a guard, + // `leaf_candidate.otherwise_block` can be reached by guard failure as well, so we + // can't skip `Q`. let or_otherwise = if leaf_candidate.has_guard { leaf_candidate.otherwise_block.unwrap() } else { - remainder_start + last_otherwise.unwrap() }; self.test_candidates_with_or( span, From ce374fcbc16f6bd9fcbb03ce1894ae13e5e3d67b Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 20 Mar 2024 01:56:24 +0100 Subject: [PATCH 5/6] Factor out `finalize_or_candidate` --- .../rustc_mir_build/src/build/matches/mod.rs | 60 +++++++++++++------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index a5f24ba3e5b20..70ba83c55d7c5 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1575,21 +1575,52 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } let first_match_pair = first_candidate.match_pairs.remove(0); - let remaining_match_pairs = mem::take(&mut first_candidate.match_pairs); let remainder_start = self.cfg.start_new_block(); // Test the alternatives of this or-pattern. - self.test_or_pattern(first_candidate, start_block, remainder_start, first_match_pair); + self.test_or_pattern( + span, + scrutinee_span, + first_candidate, + start_block, + remainder_start, + first_match_pair, + ); + + // Test the remaining candidates. + self.match_candidates( + span, + scrutinee_span, + remainder_start, + otherwise_block, + remaining_candidates, + ); + } + + /// Simplify subcandidates and process any leftover match pairs. The candidate should have been + /// expanded with `create_or_subcandidates`. + fn finalize_or_candidate( + &mut self, + span: Span, + scrutinee_span: Span, + candidate: &mut Candidate<'_, 'tcx>, + ) { + if candidate.subcandidates.is_empty() { + return; + } + + self.merge_trivial_subcandidates(candidate); - if !remaining_match_pairs.is_empty() { + if !candidate.match_pairs.is_empty() { // If more match pairs remain, test them after each subcandidate. // We could add them to the or-candidates before the call to `test_or_pattern` but this // would make it impossible to detect simplifiable or-patterns. That would guarantee // exponentially large CFGs for cases like `(1 | 2, 3 | 4, ...)`. let mut last_otherwise = None; - first_candidate.visit_leaves(|leaf_candidate| { + candidate.visit_leaves(|leaf_candidate| { last_otherwise = leaf_candidate.otherwise_block; }); - first_candidate.visit_leaves(|leaf_candidate| { + let remaining_match_pairs = mem::take(&mut candidate.match_pairs); + candidate.visit_leaves(|leaf_candidate| { assert!(leaf_candidate.match_pairs.is_empty()); leaf_candidate.match_pairs.extend(remaining_match_pairs.iter().cloned()); let or_start = leaf_candidate.pre_binding_block.unwrap(); @@ -1612,20 +1643,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); }); } - - // Test the remaining candidates. - self.match_candidates( - span, - scrutinee_span, - remainder_start, - otherwise_block, - remaining_candidates, - ); } #[instrument(skip(self, start_block, otherwise_block, candidate, match_pair), level = "debug")] fn test_or_pattern<'pat>( &mut self, + span: Span, + scrutinee_span: Span, candidate: &mut Candidate<'pat, 'tcx>, start_block: BasicBlock, otherwise_block: BasicBlock, @@ -1641,12 +1665,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { otherwise_block, &mut or_candidate_refs, ); - self.merge_trivial_subcandidates(candidate); + self.finalize_or_candidate(span, scrutinee_span, candidate); } /// Given a match-pair that corresponds to an or-pattern, expand each subpattern into a new /// subcandidate. Any candidate that has been expanded that way should be passed to - /// `merge_trivial_subcandidates` after its subcandidates have been processed. + /// `finalize_or_candidate` after its subcandidates have been processed. fn create_or_subcandidates<'pat>( &mut self, candidate: &mut Candidate<'pat, 'tcx>, @@ -1664,8 +1688,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } /// Try to merge all of the subcandidates of the given candidate into one. This avoids - /// exponentially large CFGs in cases like `(1 | 2, 3 | 4, ...)`. The or-pattern should have - /// been expanded with `create_or_subcandidates`. + /// exponentially large CFGs in cases like `(1 | 2, 3 | 4, ...)`. The candidate should have been + /// expanded with `create_or_subcandidates`. fn merge_trivial_subcandidates(&mut self, candidate: &mut Candidate<'_, 'tcx>) { if candidate.subcandidates.is_empty() || candidate.has_guard { // FIXME(or_patterns; matthewjasper) Don't give up if we have a guard. From 7b764be9f1dd4cde35c393ceaad891b7c22b2ebb Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 20 Mar 2024 15:57:51 +0100 Subject: [PATCH 6/6] Expand or-candidates mixed with candidates above We can't mix them with candidates below them, but we can mix them with candidates above. --- .../rustc_mir_build/src/build/matches/mod.rs | 176 ++++++++---------- ...le_switchint.SimplifyCfg-initial.after.mir | 40 ++-- 2 files changed, 97 insertions(+), 119 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 70ba83c55d7c5..68244136d1adf 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1364,61 +1364,105 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { otherwise_block: BasicBlock, candidates: &mut [&mut Candidate<'pat, 'tcx>], ) { - let expand_or_pats = candidates.iter().any(|candidate| { - matches!(&*candidate.match_pairs, [MatchPair { test_case: TestCase::Or { .. }, .. }]) - }); + // We process or-patterns here. If any candidate starts with an or-pattern, we have to + // expand the or-pattern before we can proceed further. + // + // We can't expand them freely however. The rule is: if the candidate has an or-pattern as + // its only remaining match pair, we can expand it freely. If it has other match pairs, we + // can expand it but we can't process more candidates after it. + // + // If we didn't stop, the `otherwise` cases could get mixed up. E.g. in the following, + // or-pattern simplification (in `merge_trivial_subcandidates`) makes it so the `1` and `2` + // cases branch to a same block (which then tests `false`). If we took `(2, _)` in the same + // set of candidates, when we reach the block that tests `false` we don't know whether we + // came from `1` or `2`, hence we can't know where to branch on failure. + // ```ignore(illustrative) + // match (1, true) { + // (1 | 2, false) => {}, + // (2, _) => {}, + // _ => {} + // } + // ``` + // + // We therefore split the `candidates` slice in two, expand or-patterns in the first half, + // and process both halves separately. + let mut expand_until = 0; + for (i, candidate) in candidates.iter().enumerate() { + if matches!( + &*candidate.match_pairs, + [MatchPair { test_case: TestCase::Or { .. }, .. }, ..] + ) { + expand_until = i + 1; + if candidate.match_pairs.len() > 1 { + break; + } + } + } + let (candidates_to_expand, remaining_candidates) = candidates.split_at_mut(expand_until); ensure_sufficient_stack(|| { - if expand_or_pats { - // Split a candidate in which the only match-pair is an or-pattern into multiple - // candidates. This is so that - // - // match x { - // 0 | 1 => { ... }, - // 2 | 3 => { ... }, - // } - // - // only generates a single switch. - let mut new_candidates = Vec::new(); - for candidate in candidates.iter_mut() { - if let [MatchPair { test_case: TestCase::Or { .. }, .. }] = + if candidates_to_expand.is_empty() { + // No candidates start with an or-pattern, we can continue. + self.match_expanded_candidates( + span, + scrutinee_span, + start_block, + otherwise_block, + remaining_candidates, + ); + } else { + // Expand one level of or-patterns for each candidate in `candidates_to_expand`. + let mut expanded_candidates = Vec::new(); + for candidate in candidates_to_expand.iter_mut() { + if let [MatchPair { test_case: TestCase::Or { .. }, .. }, ..] = &*candidate.match_pairs { - let match_pair = candidate.match_pairs.pop().unwrap(); - self.create_or_subcandidates(candidate, match_pair); + let or_match_pair = candidate.match_pairs.remove(0); + // Expand the or-pattern into subcandidates. + self.create_or_subcandidates(candidate, or_match_pair); + // Collect the newly created subcandidates. for subcandidate in candidate.subcandidates.iter_mut() { - new_candidates.push(subcandidate); + expanded_candidates.push(subcandidate); } } else { - new_candidates.push(candidate); + expanded_candidates.push(candidate); } } + + // Process the expanded candidates. + let remainder_start = self.cfg.start_new_block(); + // There might be new or-patterns obtained from expanding the old ones, so we call + // `match_candidates` again. self.match_candidates( span, scrutinee_span, start_block, - otherwise_block, - new_candidates.as_mut_slice(), + remainder_start, + expanded_candidates.as_mut_slice(), ); - for candidate in candidates { + // Simplify subcandidates and process any leftover match pairs. + for candidate in candidates_to_expand { if !candidate.subcandidates.is_empty() { - self.merge_trivial_subcandidates(candidate); + self.finalize_or_candidate(span, scrutinee_span, candidate); } } - } else { - self.match_simplified_candidates( + + // Process the remaining candidates. + self.match_candidates( span, scrutinee_span, - start_block, + remainder_start, otherwise_block, - candidates, + remaining_candidates, ); } }); } - fn match_simplified_candidates( + /// Construct the decision tree for `candidates`. Caller must ensure that no candidate in + /// `candidates` starts with an or-pattern. + fn match_expanded_candidates( &mut self, span: Span, scrutinee_span: Span, @@ -1443,7 +1487,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // The first candidate has satisfied all its match pairs; we link it up and continue // with the remaining candidates. start_block = self.select_matched_candidate(first, start_block); - self.match_simplified_candidates( + self.match_expanded_candidates( span, scrutinee_span, start_block, @@ -1453,7 +1497,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } candidates => { // The first candidate has some unsatisfied match pairs; we proceed to do more tests. - self.test_candidates_with_or( + self.test_candidates( span, scrutinee_span, candidates, @@ -1506,8 +1550,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { otherwise_block } - /// Tests a candidate where there are only or-patterns left to test, or - /// forwards to [Builder::test_candidates]. + /// Simplify subcandidates and process any leftover match pairs. The candidate should have been + /// expanded with `create_or_subcandidates`. /// /// Given a pattern `(P | Q, R | S)` we (in principle) generate a CFG like /// so: @@ -1559,45 +1603,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// | /// ... /// ``` - fn test_candidates_with_or( - &mut self, - span: Span, - scrutinee_span: Span, - candidates: &mut [&mut Candidate<'_, 'tcx>], - start_block: BasicBlock, - otherwise_block: BasicBlock, - ) { - let (first_candidate, remaining_candidates) = candidates.split_first_mut().unwrap(); - assert!(first_candidate.subcandidates.is_empty()); - if !matches!(first_candidate.match_pairs[0].test_case, TestCase::Or { .. }) { - self.test_candidates(span, scrutinee_span, candidates, start_block, otherwise_block); - return; - } - - let first_match_pair = first_candidate.match_pairs.remove(0); - let remainder_start = self.cfg.start_new_block(); - // Test the alternatives of this or-pattern. - self.test_or_pattern( - span, - scrutinee_span, - first_candidate, - start_block, - remainder_start, - first_match_pair, - ); - - // Test the remaining candidates. - self.match_candidates( - span, - scrutinee_span, - remainder_start, - otherwise_block, - remaining_candidates, - ); - } - - /// Simplify subcandidates and process any leftover match pairs. The candidate should have been - /// expanded with `create_or_subcandidates`. fn finalize_or_candidate( &mut self, span: Span, @@ -1634,40 +1639,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } else { last_otherwise.unwrap() }; - self.test_candidates_with_or( + self.match_candidates( span, scrutinee_span, - &mut [leaf_candidate], or_start, or_otherwise, + &mut [leaf_candidate], ); }); } } - #[instrument(skip(self, start_block, otherwise_block, candidate, match_pair), level = "debug")] - fn test_or_pattern<'pat>( - &mut self, - span: Span, - scrutinee_span: Span, - candidate: &mut Candidate<'pat, 'tcx>, - start_block: BasicBlock, - otherwise_block: BasicBlock, - match_pair: MatchPair<'pat, 'tcx>, - ) { - let or_span = match_pair.pattern.span; - self.create_or_subcandidates(candidate, match_pair); - let mut or_candidate_refs: Vec<_> = candidate.subcandidates.iter_mut().collect(); - self.match_candidates( - or_span, - or_span, - start_block, - otherwise_block, - &mut or_candidate_refs, - ); - self.finalize_or_candidate(span, scrutinee_span, candidate); - } - /// Given a match-pair that corresponds to an or-pattern, expand each subpattern into a new /// subcandidate. Any candidate that has been expanded that way should be passed to /// `finalize_or_candidate` after its subcandidates have been processed. diff --git a/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir b/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir index 72b1a835cf081..eafe95b4a11ea 100644 --- a/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir +++ b/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir @@ -10,67 +10,63 @@ fn single_switchint() -> () { StorageLive(_2); _2 = (const 1_i32, const true); PlaceMention(_2); - switchInt((_2.0: i32)) -> [1: bb6, 2: bb8, otherwise: bb1]; + switchInt((_2.0: i32)) -> [1: bb2, 2: bb4, otherwise: bb1]; } bb1: { - switchInt((_2.0: i32)) -> [1: bb3, 2: bb3, otherwise: bb2]; + switchInt((_2.0: i32)) -> [3: bb8, 4: bb8, otherwise: bb7]; } bb2: { - switchInt((_2.0: i32)) -> [3: bb5, 4: bb5, otherwise: bb4]; + switchInt((_2.1: bool)) -> [0: bb6, otherwise: bb3]; } bb3: { - falseEdge -> [real: bb12, imaginary: bb2]; + falseEdge -> [real: bb9, imaginary: bb4]; } bb4: { - _1 = const 5_i32; - goto -> bb14; + switchInt((_2.1: bool)) -> [0: bb5, otherwise: bb6]; } bb5: { - falseEdge -> [real: bb13, imaginary: bb4]; + falseEdge -> [real: bb10, imaginary: bb6]; } bb6: { - switchInt((_2.1: bool)) -> [0: bb1, otherwise: bb7]; + falseEdge -> [real: bb11, imaginary: bb1]; } bb7: { - falseEdge -> [real: bb10, imaginary: bb8]; + _1 = const 5_i32; + goto -> bb13; } bb8: { - switchInt((_2.1: bool)) -> [0: bb9, otherwise: bb1]; + falseEdge -> [real: bb12, imaginary: bb7]; } bb9: { - falseEdge -> [real: bb11, imaginary: bb1]; - } - - bb10: { _1 = const 1_i32; - goto -> bb14; + goto -> bb13; } - bb11: { + bb10: { _1 = const 2_i32; - goto -> bb14; + goto -> bb13; } - bb12: { + bb11: { _1 = const 3_i32; - goto -> bb14; + goto -> bb13; } - bb13: { + bb12: { _1 = const 4_i32; - goto -> bb14; + goto -> bb13; } - bb14: { + bb13: { StorageDead(_2); StorageDead(_1); _0 = const ();