Skip to content

Commit d846e92

Browse files
authored
Rollup merge of #127917 - Zalathar:after-or, r=Nadrieril
match lowering: Split `finalize_or_candidate` into more coherent methods I noticed that `finalize_or_candidate` was responsible for several different postprocessing tasks, making it difficult to understand. This PR aims to clean up some of the confusion by: - Extracting `remove_never_subcandidates` from `merge_trivial_subcandidates` - Extracting `test_remaining_match_pairs_after_or` from `finalize_or_candidate` - Taking what remains of `finalize_or_candidate`, and inlining it into its caller --- Reviewing individual commits and ignoring whitespace is recommended. Most of the large-looking changes are just moving existing code around, mostly unaltered. r? ``@Nadrieril``
2 parents 6b9982d + 239037e commit d846e92

File tree

1 file changed

+129
-91
lines changed
  • compiler/rustc_mir_build/src/build/matches

1 file changed

+129
-91
lines changed

compiler/rustc_mir_build/src/build/matches/mod.rs

+129-91
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
15981598
for subcandidate in candidate.subcandidates.iter_mut() {
15991599
expanded_candidates.push(subcandidate);
16001600
}
1601+
// Note that the subcandidates have been added to `expanded_candidates`,
1602+
// but `candidate` itself has not. If the last candidate has more match pairs,
1603+
// they are handled separately by `test_remaining_match_pairs_after_or`.
16011604
} else {
16021605
// A candidate that doesn't start with an or-pattern has nothing to
16031606
// expand, so it is included in the post-expansion list as-is.
@@ -1613,19 +1616,28 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16131616
expanded_candidates.as_mut_slice(),
16141617
);
16151618

1616-
// Simplify subcandidates and process any leftover match pairs.
1617-
for candidate in candidates_to_expand {
1619+
// Postprocess subcandidates, and process any leftover match pairs.
1620+
// (Only the last candidate can possibly have more match pairs.)
1621+
debug_assert!({
1622+
let mut all_except_last = candidates_to_expand.iter().rev().skip(1);
1623+
all_except_last.all(|candidate| candidate.match_pairs.is_empty())
1624+
});
1625+
for candidate in candidates_to_expand.iter_mut() {
16181626
if !candidate.subcandidates.is_empty() {
1619-
self.finalize_or_candidate(span, scrutinee_span, candidate);
1627+
self.merge_trivial_subcandidates(candidate);
1628+
self.remove_never_subcandidates(candidate);
16201629
}
16211630
}
1631+
if let Some(last_candidate) = candidates_to_expand.last_mut() {
1632+
self.test_remaining_match_pairs_after_or(span, scrutinee_span, last_candidate);
1633+
}
16221634

16231635
remainder_start.and(remaining_candidates)
16241636
}
16251637

16261638
/// Given a match-pair that corresponds to an or-pattern, expand each subpattern into a new
1627-
/// subcandidate. Any candidate that has been expanded that way should be passed to
1628-
/// `finalize_or_candidate` after its subcandidates have been processed.
1639+
/// subcandidate. Any candidate that has been expanded this way should also be postprocessed
1640+
/// at the end of [`Self::expand_and_match_or_candidates`].
16291641
fn create_or_subcandidates<'pat>(
16301642
&mut self,
16311643
candidate: &mut Candidate<'pat, 'tcx>,
@@ -1642,7 +1654,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16421654
candidate.subcandidates[0].false_edge_start_block = candidate.false_edge_start_block;
16431655
}
16441656

1645-
/// Simplify subcandidates and process any leftover match pairs. The candidate should have been
1657+
/// Try to merge all of the subcandidates of the given candidate into one. This avoids
1658+
/// exponentially large CFGs in cases like `(1 | 2, 3 | 4, ...)`. The candidate should have been
16461659
/// expanded with `create_or_subcandidates`.
16471660
///
16481661
/// Given a pattern `(P | Q, R | S)` we (in principle) generate a CFG like
@@ -1695,103 +1708,128 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16951708
/// |
16961709
/// ...
16971710
/// ```
1698-
fn finalize_or_candidate(
1699-
&mut self,
1700-
span: Span,
1701-
scrutinee_span: Span,
1702-
candidate: &mut Candidate<'_, 'tcx>,
1703-
) {
1704-
if candidate.subcandidates.is_empty() {
1711+
///
1712+
/// Note that this takes place _after_ the subcandidates have participated
1713+
/// in match tree lowering.
1714+
fn merge_trivial_subcandidates(&mut self, candidate: &mut Candidate<'_, 'tcx>) {
1715+
assert!(!candidate.subcandidates.is_empty());
1716+
if candidate.has_guard {
1717+
// FIXME(or_patterns; matthewjasper) Don't give up if we have a guard.
17051718
return;
17061719
}
17071720

1708-
self.merge_trivial_subcandidates(candidate);
1721+
// FIXME(or_patterns; matthewjasper) Try to be more aggressive here.
1722+
let can_merge = candidate.subcandidates.iter().all(|subcandidate| {
1723+
subcandidate.subcandidates.is_empty() && subcandidate.extra_data.is_empty()
1724+
});
1725+
if !can_merge {
1726+
return;
1727+
}
17091728

1710-
if !candidate.match_pairs.is_empty() {
1711-
let or_span = candidate.or_span.unwrap_or(candidate.extra_data.span);
1712-
let source_info = self.source_info(or_span);
1713-
// If more match pairs remain, test them after each subcandidate.
1714-
// We could add them to the or-candidates before the call to `test_or_pattern` but this
1715-
// would make it impossible to detect simplifiable or-patterns. That would guarantee
1716-
// exponentially large CFGs for cases like `(1 | 2, 3 | 4, ...)`.
1717-
let mut last_otherwise = None;
1718-
candidate.visit_leaves(|leaf_candidate| {
1719-
last_otherwise = leaf_candidate.otherwise_block;
1720-
});
1721-
let remaining_match_pairs = mem::take(&mut candidate.match_pairs);
1722-
candidate.visit_leaves(|leaf_candidate| {
1723-
assert!(leaf_candidate.match_pairs.is_empty());
1724-
leaf_candidate.match_pairs.extend(remaining_match_pairs.iter().cloned());
1725-
let or_start = leaf_candidate.pre_binding_block.unwrap();
1726-
let otherwise =
1727-
self.match_candidates(span, scrutinee_span, or_start, &mut [leaf_candidate]);
1728-
// In a case like `(P | Q, R | S)`, if `P` succeeds and `R | S` fails, we know `(Q,
1729-
// R | S)` will fail too. If there is no guard, we skip testing of `Q` by branching
1730-
// directly to `last_otherwise`. If there is a guard,
1731-
// `leaf_candidate.otherwise_block` can be reached by guard failure as well, so we
1732-
// can't skip `Q`.
1733-
let or_otherwise = if leaf_candidate.has_guard {
1734-
leaf_candidate.otherwise_block.unwrap()
1735-
} else {
1736-
last_otherwise.unwrap()
1737-
};
1738-
self.cfg.goto(otherwise, source_info, or_otherwise);
1739-
});
1729+
let mut last_otherwise = None;
1730+
let shared_pre_binding_block = self.cfg.start_new_block();
1731+
// This candidate is about to become a leaf, so unset `or_span`.
1732+
let or_span = candidate.or_span.take().unwrap();
1733+
let source_info = self.source_info(or_span);
1734+
1735+
if candidate.false_edge_start_block.is_none() {
1736+
candidate.false_edge_start_block = candidate.subcandidates[0].false_edge_start_block;
1737+
}
1738+
1739+
// Remove the (known-trivial) subcandidates from the candidate tree,
1740+
// so that they aren't visible after match tree lowering, and wire them
1741+
// all to join up at a single shared pre-binding block.
1742+
// (Note that the subcandidates have already had their part of the match
1743+
// tree lowered by this point, which is why we can add a goto to them.)
1744+
for subcandidate in mem::take(&mut candidate.subcandidates) {
1745+
let subcandidate_block = subcandidate.pre_binding_block.unwrap();
1746+
self.cfg.goto(subcandidate_block, source_info, shared_pre_binding_block);
1747+
last_otherwise = subcandidate.otherwise_block;
17401748
}
1749+
candidate.pre_binding_block = Some(shared_pre_binding_block);
1750+
assert!(last_otherwise.is_some());
1751+
candidate.otherwise_block = last_otherwise;
17411752
}
17421753

1743-
/// Try to merge all of the subcandidates of the given candidate into one. This avoids
1744-
/// exponentially large CFGs in cases like `(1 | 2, 3 | 4, ...)`. The candidate should have been
1745-
/// expanded with `create_or_subcandidates`.
1746-
fn merge_trivial_subcandidates(&mut self, candidate: &mut Candidate<'_, 'tcx>) {
1747-
if candidate.subcandidates.is_empty() || candidate.has_guard {
1748-
// FIXME(or_patterns; matthewjasper) Don't give up if we have a guard.
1754+
/// Never subcandidates may have a set of bindings inconsistent with their siblings,
1755+
/// which would break later code. So we filter them out. Note that we can't filter out
1756+
/// top-level candidates this way.
1757+
fn remove_never_subcandidates(&mut self, candidate: &mut Candidate<'_, 'tcx>) {
1758+
if candidate.subcandidates.is_empty() {
17491759
return;
17501760
}
17511761

1752-
// FIXME(or_patterns; matthewjasper) Try to be more aggressive here.
1753-
let can_merge = candidate.subcandidates.iter().all(|subcandidate| {
1754-
subcandidate.subcandidates.is_empty() && subcandidate.extra_data.is_empty()
1755-
});
1756-
if can_merge {
1757-
let mut last_otherwise = None;
1758-
let any_matches = self.cfg.start_new_block();
1759-
let or_span = candidate.or_span.take().unwrap();
1760-
let source_info = self.source_info(or_span);
1761-
if candidate.false_edge_start_block.is_none() {
1762-
candidate.false_edge_start_block =
1763-
candidate.subcandidates[0].false_edge_start_block;
1764-
}
1765-
for subcandidate in mem::take(&mut candidate.subcandidates) {
1766-
let or_block = subcandidate.pre_binding_block.unwrap();
1767-
self.cfg.goto(or_block, source_info, any_matches);
1768-
last_otherwise = subcandidate.otherwise_block;
1769-
}
1770-
candidate.pre_binding_block = Some(any_matches);
1771-
assert!(last_otherwise.is_some());
1772-
candidate.otherwise_block = last_otherwise;
1773-
} else {
1774-
// Never subcandidates may have a set of bindings inconsistent with their siblings,
1775-
// which would break later code. So we filter them out. Note that we can't filter out
1776-
// top-level candidates this way.
1777-
candidate.subcandidates.retain_mut(|candidate| {
1778-
if candidate.extra_data.is_never {
1779-
candidate.visit_leaves(|subcandidate| {
1780-
let block = subcandidate.pre_binding_block.unwrap();
1781-
// That block is already unreachable but needs a terminator to make the MIR well-formed.
1782-
let source_info = self.source_info(subcandidate.extra_data.span);
1783-
self.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
1784-
});
1785-
false
1786-
} else {
1787-
true
1788-
}
1789-
});
1790-
if candidate.subcandidates.is_empty() {
1791-
// If `candidate` has become a leaf candidate, ensure it has a `pre_binding_block`.
1792-
candidate.pre_binding_block = Some(self.cfg.start_new_block());
1762+
candidate.subcandidates.retain_mut(|candidate| {
1763+
if candidate.extra_data.is_never {
1764+
candidate.visit_leaves(|subcandidate| {
1765+
let block = subcandidate.pre_binding_block.unwrap();
1766+
// That block is already unreachable but needs a terminator to make the MIR well-formed.
1767+
let source_info = self.source_info(subcandidate.extra_data.span);
1768+
self.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
1769+
});
1770+
false
1771+
} else {
1772+
true
17931773
}
1774+
});
1775+
if candidate.subcandidates.is_empty() {
1776+
// If `candidate` has become a leaf candidate, ensure it has a `pre_binding_block`.
1777+
candidate.pre_binding_block = Some(self.cfg.start_new_block());
1778+
}
1779+
}
1780+
1781+
/// If more match pairs remain, test them after each subcandidate.
1782+
/// We could have added them to the or-candidates during or-pattern expansion, but that
1783+
/// would make it impossible to detect simplifiable or-patterns. That would guarantee
1784+
/// exponentially large CFGs for cases like `(1 | 2, 3 | 4, ...)`.
1785+
fn test_remaining_match_pairs_after_or(
1786+
&mut self,
1787+
span: Span,
1788+
scrutinee_span: Span,
1789+
candidate: &mut Candidate<'_, 'tcx>,
1790+
) {
1791+
if candidate.match_pairs.is_empty() {
1792+
return;
17941793
}
1794+
1795+
let or_span = candidate.or_span.unwrap_or(candidate.extra_data.span);
1796+
let source_info = self.source_info(or_span);
1797+
let mut last_otherwise = None;
1798+
candidate.visit_leaves(|leaf_candidate| {
1799+
last_otherwise = leaf_candidate.otherwise_block;
1800+
});
1801+
1802+
let remaining_match_pairs = mem::take(&mut candidate.match_pairs);
1803+
// We're testing match pairs that remained after an `Or`, so the remaining
1804+
// pairs should all be `Or` too, due to the sorting invariant.
1805+
debug_assert!(
1806+
remaining_match_pairs
1807+
.iter()
1808+
.all(|match_pair| matches!(match_pair.test_case, TestCase::Or { .. }))
1809+
);
1810+
1811+
candidate.visit_leaves(|leaf_candidate| {
1812+
// At this point the leaf's own match pairs have all been lowered
1813+
// and removed, so `extend` and assignment are equivalent,
1814+
// but extending can also recycle any existing vector capacity.
1815+
assert!(leaf_candidate.match_pairs.is_empty());
1816+
leaf_candidate.match_pairs.extend(remaining_match_pairs.iter().cloned());
1817+
1818+
let or_start = leaf_candidate.pre_binding_block.unwrap();
1819+
let otherwise =
1820+
self.match_candidates(span, scrutinee_span, or_start, &mut [leaf_candidate]);
1821+
// In a case like `(P | Q, R | S)`, if `P` succeeds and `R | S` fails, we know `(Q,
1822+
// R | S)` will fail too. If there is no guard, we skip testing of `Q` by branching
1823+
// directly to `last_otherwise`. If there is a guard,
1824+
// `leaf_candidate.otherwise_block` can be reached by guard failure as well, so we
1825+
// can't skip `Q`.
1826+
let or_otherwise = if leaf_candidate.has_guard {
1827+
leaf_candidate.otherwise_block.unwrap()
1828+
} else {
1829+
last_otherwise.unwrap()
1830+
};
1831+
self.cfg.goto(otherwise, source_info, or_otherwise);
1832+
});
17951833
}
17961834

17971835
/// Pick a test to run. Which test doesn't matter as long as it is guaranteed to fully match at

0 commit comments

Comments
 (0)