Skip to content

Commit d0155dc

Browse files
committed
Auto merge of rust-lang#119031 - Nadrieril:two-phase-match-lowering, r=<try>
[Experiment] Play with match lowering Match lowering to MIR has the reputation of being the most intricate piece of the compiler, and after banging my head on it for a bit I sure hope there isn't anything more intricate somewhere else. It's good quality code but I hope to unentangle it. This PR is me wrestling with it and asking `rustc-timer` for its opinion. r? `@ghost`
2 parents e004adb + 8ae0de2 commit d0155dc

File tree

4 files changed

+128
-116
lines changed

4 files changed

+128
-116
lines changed

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

+36-73
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,27 @@ pub(crate) struct Test<'tcx> {
10631063
#[derive(Copy, Clone, Debug)]
10641064
pub(crate) struct ArmHasGuard(pub(crate) bool);
10651065

1066+
/// A single step in the match algorithm.
1067+
pub(crate) struct MatchAutomatonStep<'a, 'c, 'pat, 'tcx> {
1068+
/// Perform this test...
1069+
test: Test<'tcx>,
1070+
/// ... on this place...
1071+
place: PlaceBuilder<'tcx>,
1072+
/// ... depending on the result, branch to one of these candidate lists...
1073+
target_candidates: Vec<Vec<&'a mut Candidate<'pat, 'tcx>>>,
1074+
/// ... if it doesn't match, continue with these.
1075+
remaining_candidates: &'a mut [&'c mut Candidate<'pat, 'tcx>],
1076+
}
1077+
1078+
impl<'b, 'c, 'pat, 'tcx> std::fmt::Debug for MatchAutomatonStep<'b, 'c, 'pat, 'tcx> {
1079+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1080+
f.debug_struct("MatchAutomatonStep")
1081+
.field("test", &self.test)
1082+
.field("place", &self.place)
1083+
.finish()
1084+
}
1085+
}
1086+
10661087
///////////////////////////////////////////////////////////////////////////
10671088
// Main matching algorithm
10681089

@@ -1085,7 +1106,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
10851106
/// exhaustive in Rust. But during processing we sometimes divide
10861107
/// up the list of candidates and recurse with a non-exhaustive
10871108
/// list. This is important to keep the size of the generated code
1088-
/// under control. See [`Builder::test_candidates`] for more details.
1109+
/// under control. See [`Builder::build_test_step`] for more details.
10891110
///
10901111
/// If `fake_borrows` is `Some`, then places which need fake borrows
10911112
/// will be added to it.
@@ -1321,7 +1342,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
13211342
}
13221343

13231344
/// Tests a candidate where there are only or-patterns left to test, or
1324-
/// forwards to [Builder::test_candidates].
1345+
/// forwards to [Builder::build_test_step].
13251346
///
13261347
/// Given a pattern `(P | Q, R | S)` we (in principle) generate a CFG like
13271348
/// so:
@@ -1389,14 +1410,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
13891410
match first_candidate.match_pairs[0].pattern.kind {
13901411
PatKind::Or { .. } => (),
13911412
_ => {
1392-
self.test_candidates(
1393-
span,
1394-
scrutinee_span,
1395-
candidates,
1396-
block,
1397-
otherwise_block,
1398-
fake_borrows,
1399-
);
1413+
let step = self.build_test_step(candidates, fake_borrows);
1414+
self.perform_test(span, scrutinee_span, block, otherwise_block, fake_borrows, step);
14001415
return;
14011416
}
14021417
}
@@ -1449,11 +1464,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
14491464
fake_borrows: &mut Option<FxIndexSet<Place<'tcx>>>,
14501465
) {
14511466
debug!("candidate={:#?}\npats={:#?}", candidate, pats);
1452-
let mut or_candidates: Vec<_> = pats
1467+
candidate.subcandidates = pats
14531468
.iter()
14541469
.map(|pat| Candidate::new(place.clone(), pat, candidate.has_guard, self))
14551470
.collect();
1456-
let mut or_candidate_refs: Vec<_> = or_candidates.iter_mut().collect();
1471+
let mut or_candidate_refs: Vec<_> = candidate.subcandidates.iter_mut().collect();
14571472
let otherwise = if candidate.otherwise_block.is_some() {
14581473
&mut candidate.otherwise_block
14591474
} else {
@@ -1467,7 +1482,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
14671482
&mut or_candidate_refs,
14681483
fake_borrows,
14691484
);
1470-
candidate.subcandidates = or_candidates;
14711485
self.merge_trivial_subcandidates(candidate, self.source_info(or_span));
14721486
}
14731487

@@ -1626,15 +1640,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16261640
/// In addition to avoiding exponential-time blowups, this algorithm
16271641
/// also has the nice property that each guard and arm is only generated
16281642
/// once.
1629-
fn test_candidates<'pat, 'b, 'c>(
1643+
fn build_test_step<'pat, 'b, 'c>(
16301644
&mut self,
1631-
span: Span,
1632-
scrutinee_span: Span,
16331645
mut candidates: &'b mut [&'c mut Candidate<'pat, 'tcx>],
1634-
block: BasicBlock,
1635-
otherwise_block: &mut Option<BasicBlock>,
16361646
fake_borrows: &mut Option<FxIndexSet<Place<'tcx>>>,
1637-
) {
1647+
) -> MatchAutomatonStep<'b, 'c, 'pat, 'tcx> {
16381648
// extract the match-pair from the highest priority candidate
16391649
let match_pair = &candidates.first().unwrap().match_pairs[0];
16401650
let mut test = self.test(match_pair);
@@ -1673,7 +1683,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16731683
// those N possible outcomes, create a (initially empty)
16741684
// vector of candidates. Those are the candidates that still
16751685
// apply if the test has that particular outcome.
1676-
debug!("test_candidates: test={:?} match_pair={:?}", test, match_pair);
1686+
debug!("build_test_step: test={:?} match_pair={:?}", test, match_pair);
16771687
let mut target_candidates: Vec<Vec<&mut Candidate<'pat, 'tcx>>> = vec![];
16781688
target_candidates.resize_with(test.targets(), Default::default);
16791689

@@ -1699,59 +1709,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16991709
debug!("tested_candidates: {}", total_candidate_count - candidates.len());
17001710
debug!("untested_candidates: {}", candidates.len());
17011711

1702-
// HACK(matthewjasper) This is a closure so that we can let the test
1703-
// create its blocks before the rest of the match. This currently
1704-
// improves the speed of llvm when optimizing long string literal
1705-
// matches
1706-
let make_target_blocks = move |this: &mut Self| -> Vec<BasicBlock> {
1707-
// The block that we should branch to if none of the
1708-
// `target_candidates` match. This is either the block where we
1709-
// start matching the untested candidates if there are any,
1710-
// otherwise it's the `otherwise_block`.
1711-
let remainder_start = &mut None;
1712-
let remainder_start =
1713-
if candidates.is_empty() { &mut *otherwise_block } else { remainder_start };
1714-
1715-
// For each outcome of test, process the candidates that still
1716-
// apply. Collect a list of blocks where control flow will
1717-
// branch if one of the `target_candidate` sets is not
1718-
// exhaustive.
1719-
let target_blocks: Vec<_> = target_candidates
1720-
.into_iter()
1721-
.map(|mut candidates| {
1722-
if !candidates.is_empty() {
1723-
let candidate_start = this.cfg.start_new_block();
1724-
this.match_candidates(
1725-
span,
1726-
scrutinee_span,
1727-
candidate_start,
1728-
remainder_start,
1729-
&mut *candidates,
1730-
fake_borrows,
1731-
);
1732-
candidate_start
1733-
} else {
1734-
*remainder_start.get_or_insert_with(|| this.cfg.start_new_block())
1735-
}
1736-
})
1737-
.collect();
1738-
1739-
if !candidates.is_empty() {
1740-
let remainder_start = remainder_start.unwrap_or_else(|| this.cfg.start_new_block());
1741-
this.match_candidates(
1742-
span,
1743-
scrutinee_span,
1744-
remainder_start,
1745-
otherwise_block,
1746-
candidates,
1747-
fake_borrows,
1748-
);
1749-
};
1750-
1751-
target_blocks
1752-
};
1753-
1754-
self.perform_test(span, scrutinee_span, block, &match_place, &test, make_target_blocks);
1712+
MatchAutomatonStep {
1713+
test,
1714+
place: match_place,
1715+
remaining_candidates: candidates,
1716+
target_candidates,
1717+
}
17551718
}
17561719

17571720
/// Determine the fake borrows that are needed from a set of places that

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

+71-22
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use crate::build::expr::as_place::PlaceBuilder;
99
use crate::build::matches::{Candidate, MatchPair, Test, TestKind};
1010
use crate::build::Builder;
11-
use rustc_data_structures::fx::FxIndexMap;
11+
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
1212
use rustc_hir::{LangItem, RangeEnd};
1313
use rustc_index::bit_set::BitSet;
1414
use rustc_middle::mir::*;
@@ -23,6 +23,8 @@ use rustc_target::abi::VariantIdx;
2323

2424
use std::cmp::Ordering;
2525

26+
use super::MatchAutomatonStep;
27+
2628
impl<'a, 'tcx> Builder<'a, 'tcx> {
2729
/// Identifies what test is needed to decide if `match_pair` is applicable.
2830
///
@@ -147,24 +149,82 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
147149
}
148150
}
149151

150-
#[instrument(skip(self, make_target_blocks, place_builder), level = "debug")]
152+
#[instrument(skip(self), level = "debug")]
151153
pub(super) fn perform_test(
152154
&mut self,
153155
match_start_span: Span,
154156
scrutinee_span: Span,
155157
block: BasicBlock,
156-
place_builder: &PlaceBuilder<'tcx>,
157-
test: &Test<'tcx>,
158-
make_target_blocks: impl FnOnce(&mut Self) -> Vec<BasicBlock>,
158+
otherwise_block: &mut Option<BasicBlock>,
159+
fake_borrows: &mut Option<FxIndexSet<Place<'tcx>>>,
160+
step: MatchAutomatonStep<'_, '_, '_, 'tcx>,
159161
) {
162+
let test = &step.test;
163+
let place_builder = &step.place;
164+
// HACK(matthewjasper) This is a closure so that we can let the test
165+
// create its blocks before the rest of the match. This currently
166+
// improves the speed of llvm when optimizing long string literal
167+
// matches
168+
let make_target_blocks = move |this: &mut Self| -> Vec<BasicBlock> {
169+
// The block that we should branch to if none of the
170+
// `target_candidates` match. This is either the block where we
171+
// start matching the untested candidates if there are any,
172+
// otherwise it's the `otherwise_block`.
173+
let remainder_start = &mut None;
174+
let remainder_start = if step.remaining_candidates.is_empty() {
175+
&mut *otherwise_block
176+
} else {
177+
remainder_start
178+
};
179+
180+
// For each outcome of test, process the candidates that still
181+
// apply. Collect a list of blocks where control flow will
182+
// branch if one of the `target_candidate` sets is not
183+
// exhaustive.
184+
let target_blocks: Vec<_> = step
185+
.target_candidates
186+
.into_iter()
187+
.map(|mut candidates| {
188+
if !candidates.is_empty() {
189+
let candidate_start = this.cfg.start_new_block();
190+
this.match_candidates(
191+
match_start_span,
192+
scrutinee_span,
193+
candidate_start,
194+
remainder_start,
195+
&mut *candidates,
196+
fake_borrows,
197+
);
198+
candidate_start
199+
} else {
200+
*remainder_start.get_or_insert_with(|| this.cfg.start_new_block())
201+
}
202+
})
203+
.collect();
204+
205+
if !step.remaining_candidates.is_empty() {
206+
let remainder_start = remainder_start.unwrap_or_else(|| this.cfg.start_new_block());
207+
this.match_candidates(
208+
match_start_span,
209+
scrutinee_span,
210+
remainder_start,
211+
otherwise_block,
212+
step.remaining_candidates,
213+
fake_borrows,
214+
);
215+
};
216+
217+
target_blocks
218+
};
219+
let target_blocks = make_target_blocks(self);
220+
160221
let place = place_builder.to_place(self);
161222
let place_ty = place.ty(&self.local_decls, self.tcx);
162223
debug!(?place, ?place_ty,);
163224

164225
let source_info = self.source_info(test.span);
165226
match test.kind {
166227
TestKind::Switch { adt_def, ref variants } => {
167-
let target_blocks = make_target_blocks(self);
168228
// Variants is a BitVec of indexes into adt_def.variants.
169229
let num_enum_variants = adt_def.variants().len();
170230
debug_assert_eq!(target_blocks.len(), num_enum_variants + 1);
@@ -210,7 +270,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
210270
}
211271

212272
TestKind::SwitchInt { switch_ty, ref options } => {
213-
let target_blocks = make_target_blocks(self);
214273
let terminator = if *switch_ty.kind() == ty::Bool {
215274
assert!(!options.is_empty() && options.len() <= 2);
216275
let [first_bb, second_bb] = *target_blocks else {
@@ -280,7 +339,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
280339
);
281340
self.non_scalar_compare(
282341
eq_block,
283-
make_target_blocks,
342+
target_blocks,
284343
source_info,
285344
value,
286345
ref_str,
@@ -291,15 +350,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
291350
if !ty.is_scalar() {
292351
// Use `PartialEq::eq` instead of `BinOp::Eq`
293352
// (the binop can only handle primitives)
294-
self.non_scalar_compare(
295-
block,
296-
make_target_blocks,
297-
source_info,
298-
value,
299-
place,
300-
ty,
301-
);
302-
} else if let [success, fail] = *make_target_blocks(self) {
353+
self.non_scalar_compare(block, target_blocks, source_info, value, place, ty);
354+
} else if let [success, fail] = *target_blocks {
303355
assert_eq!(value.ty(), ty);
304356
let expect = self.literal_operand(test.span, value);
305357
let val = Operand::Copy(place);
@@ -311,7 +363,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
311363

312364
TestKind::Range(ref range) => {
313365
let lower_bound_success = self.cfg.start_new_block();
314-
let target_blocks = make_target_blocks(self);
315366

316367
// Test `val` by computing `lo <= val && val <= hi`, using primitive comparisons.
317368
// FIXME: skip useless comparison when the range is half-open.
@@ -341,8 +392,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
341392
}
342393

343394
TestKind::Len { len, op } => {
344-
let target_blocks = make_target_blocks(self);
345-
346395
let usize_ty = self.tcx.types.usize;
347396
let actual = self.temp(usize_ty, test.span);
348397

@@ -406,7 +455,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
406455
fn non_scalar_compare(
407456
&mut self,
408457
block: BasicBlock,
409-
make_target_blocks: impl FnOnce(&mut Self) -> Vec<BasicBlock>,
458+
target_blocks: Vec<BasicBlock>,
410459
source_info: SourceInfo,
411460
value: Const<'tcx>,
412461
mut val: Place<'tcx>,
@@ -531,7 +580,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
531580
);
532581
self.diverge_from(block);
533582

534-
let [success_block, fail_block] = *make_target_blocks(self) else {
583+
let [success_block, fail_block] = *target_blocks else {
535584
bug!("`TestKind::Eq` should have two target blocks")
536585
};
537586
// check the result

tests/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir

+7-7
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,22 @@ fn match_tuple(_1: (u32, bool, Option<i32>, u32)) -> u32 {
3838

3939
bb4: {
4040
_5 = Le(const 6_u32, (_1.3: u32));
41-
switchInt(move _5) -> [0: bb6, otherwise: bb5];
41+
switchInt(move _5) -> [0: bb5, otherwise: bb7];
4242
}
4343

4444
bb5: {
45-
_6 = Le((_1.3: u32), const 9_u32);
46-
switchInt(move _6) -> [0: bb6, otherwise: bb8];
45+
_3 = Le(const 13_u32, (_1.3: u32));
46+
switchInt(move _3) -> [0: bb1, otherwise: bb6];
4747
}
4848

4949
bb6: {
50-
_3 = Le(const 13_u32, (_1.3: u32));
51-
switchInt(move _3) -> [0: bb1, otherwise: bb7];
50+
_4 = Le((_1.3: u32), const 16_u32);
51+
switchInt(move _4) -> [0: bb1, otherwise: bb8];
5252
}
5353

5454
bb7: {
55-
_4 = Le((_1.3: u32), const 16_u32);
56-
switchInt(move _4) -> [0: bb1, otherwise: bb8];
55+
_6 = Le((_1.3: u32), const 9_u32);
56+
switchInt(move _6) -> [0: bb5, otherwise: bb8];
5757
}
5858

5959
bb8: {

0 commit comments

Comments
 (0)