Skip to content

Commit 662232c

Browse files
committed
Rewrite match algorithm to avoid massive blowup in generated code for
large matches that fallback to Eq. When we encounter a case where the test being performed does not inform the candidate at all, we just stop testing the candidates at that point, rather than adding the candidate to both outcomes. The former behavior was not WRONG, but it generated a lot of code, whereas this new behavior degenerates to an if-else-if tree. Fixes #29740.
1 parent f1f5c04 commit 662232c

File tree

3 files changed

+533
-48
lines changed

3 files changed

+533
-48
lines changed

src/librustc_mir/build/matches/mod.rs

+184-23
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,15 @@ impl<'a,'tcx> Builder<'a,'tcx> {
8585

8686
// this will generate code to test discriminant_lvalue and
8787
// branch to the appropriate arm block
88-
self.match_candidates(span, &mut arm_blocks, candidates, block);
88+
let otherwise = self.match_candidates(span, &mut arm_blocks, candidates, block);
89+
90+
// because all matches are exhaustive, in principle we expect
91+
// an empty vector to be returned here, but the algorithm is
92+
// not entirely precise
93+
if !otherwise.is_empty() {
94+
let join_block = self.join_otherwise_blocks(otherwise);
95+
self.panic(join_block);
96+
}
8997

9098
// all the arm blocks will rejoin here
9199
let end_block = self.cfg.start_new_block();
@@ -279,11 +287,32 @@ struct Test<'tcx> {
279287
// Main matching algorithm
280288

281289
impl<'a,'tcx> Builder<'a,'tcx> {
290+
/// The main match algorithm. It begins with a set of candidates
291+
/// `candidates` and has the job of generating code to determine
292+
/// which of these candidates, if any, is the correct one. The
293+
/// candidates are sorted in inverse priority -- so the last item
294+
/// in the list has highest priority. When a candidate is found to
295+
/// match the value, we will generate a branch to the appropriate
296+
/// block found in `arm_blocks`.
297+
///
298+
/// The return value is a list of "otherwise" blocks. These are
299+
/// points in execution where we found that *NONE* of the
300+
/// candidates apply. In principle, this means that the input
301+
/// list was not exhaustive, though at present we sometimes are
302+
/// not smart enough to recognize all exhaustive inputs.
303+
///
304+
/// It might be surprising that the input can be inexhaustive.
305+
/// Indeed, initially, it is not, because all matches are
306+
/// exhaustive in Rust. But during processing we sometimes divide
307+
/// up the list of candidates and recurse with a non-exhaustive
308+
/// list. This is important to keep the size of the generated code
309+
/// under control. See `test_candidates` for more details.
282310
fn match_candidates<'pat>(&mut self,
283311
span: Span,
284312
arm_blocks: &mut ArmBlocks,
285313
mut candidates: Vec<Candidate<'pat, 'tcx>>,
286314
mut block: BasicBlock)
315+
-> Vec<BasicBlock>
287316
{
288317
debug!("matched_candidate(span={:?}, block={:?}, candidates={:?})",
289318
span, block, candidates);
@@ -311,17 +340,127 @@ impl<'a,'tcx> Builder<'a,'tcx> {
311340
} else {
312341
// if None is returned, then any remaining candidates
313342
// are unreachable (at least not through this path).
314-
return;
343+
return vec![];
315344
}
316345
}
317346

318347
// If there are no candidates that still need testing, we're done.
319348
// Since all matches are exhaustive, execution should never reach this point.
320349
if candidates.is_empty() {
321-
return self.panic(block);
350+
return vec![block];
351+
}
352+
353+
// Test candidates where possible.
354+
let (otherwise, tested_candidates) =
355+
self.test_candidates(span, arm_blocks, &candidates, block);
356+
357+
// If the target candidates were exhaustive, then we are done.
358+
if otherwise.is_empty() {
359+
return vec![];
360+
}
361+
362+
// If all candidates were sorted into `target_candidates` somewhere, then
363+
// the initial set was inexhaustive.
364+
let untested_candidates = candidates.len() - tested_candidates;
365+
if untested_candidates == 0 {
366+
return otherwise;
322367
}
323368

324-
// otherwise, extract the next match pair and construct tests
369+
// Otherwise, let's process those remaining candidates.
370+
let join_block = self.join_otherwise_blocks(otherwise);
371+
candidates.truncate(untested_candidates);
372+
self.match_candidates(span, arm_blocks, candidates, join_block)
373+
}
374+
375+
fn join_otherwise_blocks(&mut self,
376+
otherwise: Vec<BasicBlock>)
377+
-> BasicBlock
378+
{
379+
if otherwise.len() == 1 {
380+
otherwise[0]
381+
} else {
382+
let join_block = self.cfg.start_new_block();
383+
for block in otherwise {
384+
self.cfg.terminate(block, Terminator::Goto { target: join_block });
385+
}
386+
join_block
387+
}
388+
}
389+
390+
/// This is the most subtle part of the matching algorithm. At
391+
/// this point, the input candidates have been fully simplified,
392+
/// and so we know that all remaining match-pairs require some
393+
/// sort of test. To decide what test to do, we take the highest
394+
/// priority candidate (last one in the list) and extract the
395+
/// first match-pair from the list. From this we decide what kind
396+
/// of test is needed using `test`, defined in the `test` module.
397+
///
398+
/// *Note:* taking the first match pair is somewhat arbitrary, and
399+
/// we might do better here by choosing more carefully what to
400+
/// test.
401+
///
402+
/// For example, consider the following possible match-pairs:
403+
///
404+
/// 1. `x @ Some(P)` -- we will do a `Switch` to decide what variant `x` has
405+
/// 2. `x @ 22` -- we will do a `SwitchInt`
406+
/// 3. `x @ 3..5` -- we will do a range test
407+
/// 4. etc.
408+
///
409+
/// Once we know what sort of test we are going to perform, this
410+
/// test may also help us with other candidates. So we walk over
411+
/// the candidates (from high to low priority) and check. This
412+
/// gives us, for each outcome of the test, a transformed list of
413+
/// candidates. For example, if we are testing the current
414+
/// variant of `x.0`, and we have a candidate `{x.0 @ Some(v), x.1
415+
/// @ 22}`, then we would have a resulting candidate of `{(x.0 as
416+
/// Some).0 @ v, x.1 @ 22}`. Note that the first match-pair is now
417+
/// simpler (and, in fact, irrefutable).
418+
///
419+
/// But there may also be candidates that the test just doesn't
420+
/// apply to. For example, consider the case of #29740:
421+
///
422+
/// ```rust
423+
/// match x {
424+
/// "foo" => ...,
425+
/// "bar" => ...,
426+
/// "baz" => ...,
427+
/// _ => ...,
428+
/// }
429+
/// ```
430+
///
431+
/// Here the match-pair we are testing will be `x @ "foo"`, and we
432+
/// will generate an `Eq` test. Because `"bar"` and `"baz"` are different
433+
/// constants, we will decide that these later candidates are just not
434+
/// informed by the eq test. So we'll wind up with three candidate sets:
435+
///
436+
/// - If outcome is that `x == "foo"` (one candidate, derived from `x @ "foo"`)
437+
/// - If outcome is that `x != "foo"` (empty list of candidates)
438+
/// - Otherwise (three candidates, `x @ "bar"`, `x @ "baz"`, `x @
439+
/// _`). Here we have the invariant that everything in the
440+
/// otherwise list is of **lower priority** than the stuff in the
441+
/// other lists.
442+
///
443+
/// So we'll compile the test. For each outcome of the test, we
444+
/// recursively call `match_candidates` with the corresponding set
445+
/// of candidates. But note that this set is now inexhaustive: for
446+
/// example, in the case where the test returns false, there are
447+
/// NO candidates, even though there is stll a value to be
448+
/// matched. So we'll collect the return values from
449+
/// `match_candidates`, which are the blocks where control-flow
450+
/// goes if none of the candidates matched. At this point, we can
451+
/// continue with the "otherwise" list.
452+
///
453+
/// If you apply this to the above test, you basically wind up
454+
/// with an if-else-if chain, testing each candidate in turn,
455+
/// which is precisely what we want.
456+
fn test_candidates<'pat>(&mut self,
457+
span: Span,
458+
arm_blocks: &mut ArmBlocks,
459+
candidates: &[Candidate<'pat, 'tcx>],
460+
block: BasicBlock)
461+
-> (Vec<BasicBlock>, usize)
462+
{
463+
// extract the match-pair from the highest priority candidate
325464
let match_pair = &candidates.last().unwrap().match_pairs[0];
326465
let mut test = self.test(match_pair);
327466

@@ -331,35 +470,57 @@ impl<'a,'tcx> Builder<'a,'tcx> {
331470
// available
332471
match test.kind {
333472
TestKind::SwitchInt { switch_ty, ref mut options, ref mut indices } => {
334-
for candidate in &candidates {
335-
self.add_cases_to_switch(&match_pair.lvalue,
336-
candidate,
337-
switch_ty,
338-
options,
339-
indices);
473+
for candidate in candidates.iter().rev() {
474+
if !self.add_cases_to_switch(&match_pair.lvalue,
475+
candidate,
476+
switch_ty,
477+
options,
478+
indices) {
479+
break;
480+
}
340481
}
341482
}
342483
_ => { }
343484
}
344485

486+
// perform the test, branching to one of N blocks. For each of
487+
// those N possible outcomes, create a (initially empty)
488+
// vector of candidates. Those are the candidates that still
489+
// apply if the test has that particular outcome.
345490
debug!("match_candidates: test={:?} match_pair={:?}", test, match_pair);
346491
let target_blocks = self.perform_test(block, &match_pair.lvalue, &test);
347-
348492
let mut target_candidates: Vec<_> = (0..target_blocks.len()).map(|_| vec![]).collect();
349493

350-
for candidate in &candidates {
351-
self.sort_candidate(&match_pair.lvalue,
352-
&test,
353-
candidate,
354-
&mut target_candidates);
355-
}
356-
357-
for (target_block, target_candidates) in
494+
// Sort the candidates into the appropriate vector in
495+
// `target_candidates`. Note that at some point we may
496+
// encounter a candidate where the test is not relevant; at
497+
// that point, we stop sorting.
498+
let tested_candidates =
499+
candidates.iter()
500+
.rev()
501+
.take_while(|c| self.sort_candidate(&match_pair.lvalue,
502+
&test,
503+
c,
504+
&mut target_candidates))
505+
.count();
506+
assert!(tested_candidates > 0); // at least the last candidate ought to be tested
507+
508+
// For each outcome of test, process the candidates that still
509+
// apply. Collect a list of blocks where control flow will
510+
// branch if one of the `target_candidate` sets is not
511+
// exhaustive.
512+
let otherwise: Vec<_> =
358513
target_blocks.into_iter()
359-
.zip(target_candidates.into_iter())
360-
{
361-
self.match_candidates(span, arm_blocks, target_candidates, target_block);
362-
}
514+
.zip(target_candidates)
515+
.flat_map(|(target_block, target_candidates)| {
516+
self.match_candidates(span,
517+
arm_blocks,
518+
target_candidates,
519+
target_block)
520+
})
521+
.collect();
522+
523+
(otherwise, tested_candidates)
363524
}
364525

365526
/// Initializes each of the bindings from the candidate by

src/librustc_mir/build/matches/test.rs

+33-25
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,11 @@ impl<'a,'tcx> Builder<'a,'tcx> {
105105
switch_ty: Ty<'tcx>,
106106
options: &mut Vec<ConstVal>,
107107
indices: &mut FnvHashMap<ConstVal, usize>)
108+
-> bool
108109
{
109110
let match_pair = match candidate.match_pairs.iter().find(|mp| mp.lvalue == *test_lvalue) {
110111
Some(match_pair) => match_pair,
111-
_ => { return; }
112+
_ => { return false; }
112113
};
113114

114115
match *match_pair.pattern.kind {
@@ -121,11 +122,10 @@ impl<'a,'tcx> Builder<'a,'tcx> {
121122
options.push(value.clone());
122123
options.len() - 1
123124
});
125+
true
124126
}
125127

126-
PatternKind::Range { .. } => {
127-
}
128-
128+
PatternKind::Range { .. } |
129129
PatternKind::Constant { .. } |
130130
PatternKind::Variant { .. } |
131131
PatternKind::Slice { .. } |
@@ -134,6 +134,8 @@ impl<'a,'tcx> Builder<'a,'tcx> {
134134
PatternKind::Binding { .. } |
135135
PatternKind::Leaf { .. } |
136136
PatternKind::Deref { .. } => {
137+
// don't know how to add these patterns to a switch
138+
false
137139
}
138140
}
139141
}
@@ -284,18 +286,29 @@ impl<'a,'tcx> Builder<'a,'tcx> {
284286
/// P0` to the `resulting_candidates` entry corresponding to the
285287
/// variant `Some`.
286288
///
287-
/// In many cases we will add the `candidate` to more than one
288-
/// outcome. For example, say that the test is `x == 22`, but the
289-
/// candidate is `x @ 13..55`. In that case, if the test is true,
290-
/// then we know that the candidate applies (without this match
291-
/// pair, potentially, though we don't optimize this due to
292-
/// #29623). If the test is false, the candidate may also apply
293-
/// (with the match pair, still).
289+
/// However, in some cases, the test may just not be relevant to
290+
/// candidate. For example, suppose we are testing whether `foo.x == 22`,
291+
/// but in one match arm we have `Foo { x: _, ... }`... in that case,
292+
/// the test for what value `x` has has no particular relevance
293+
/// to this candidate. In such cases, this function just returns false
294+
/// without doing anything. This is used by the overall `match_candidates`
295+
/// algorithm to structure the match as a whole. See `match_candidates` for
296+
/// more details.
297+
///
298+
/// FIXME(#29623). In some cases, we have some tricky choices to
299+
/// make. for example, if we are testing that `x == 22`, but the
300+
/// candidate is `x @ 13..55`, what should we do? In the event
301+
/// that the test is true, we know that the candidate applies, but
302+
/// in the event of false, we don't know that it *doesn't*
303+
/// apply. For now, we return false, indicate that the test does
304+
/// not apply to this candidate, but it might be we can get
305+
/// tighter match code if we do something a bit different.
294306
pub fn sort_candidate<'pat>(&mut self,
295307
test_lvalue: &Lvalue<'tcx>,
296308
test: &Test<'tcx>,
297309
candidate: &Candidate<'pat, 'tcx>,
298-
resulting_candidates: &mut [Vec<Candidate<'pat, 'tcx>>]) {
310+
resulting_candidates: &mut [Vec<Candidate<'pat, 'tcx>>])
311+
-> bool {
299312
// Find the match_pair for this lvalue (if any). At present,
300313
// afaik, there can be at most one. (In the future, if we
301314
// adopted a more general `@` operator, there might be more
@@ -311,7 +324,7 @@ impl<'a,'tcx> Builder<'a,'tcx> {
311324
None => {
312325
// We are not testing this lvalue. Therefore, this
313326
// candidate applies to ALL outcomes.
314-
return self.add_to_all_candidate_sets(candidate, resulting_candidates);
327+
return false;
315328
}
316329
};
317330

@@ -329,9 +342,10 @@ impl<'a,'tcx> Builder<'a,'tcx> {
329342
subpatterns,
330343
candidate);
331344
resulting_candidates[variant_index].push(new_candidate);
345+
true
332346
}
333347
_ => {
334-
self.add_to_all_candidate_sets(candidate, resulting_candidates);
348+
false
335349
}
336350
}
337351
}
@@ -349,9 +363,10 @@ impl<'a,'tcx> Builder<'a,'tcx> {
349363
let new_candidate = self.candidate_without_match_pair(match_pair_index,
350364
candidate);
351365
resulting_candidates[index].push(new_candidate);
366+
true
352367
}
353368
_ => {
354-
self.add_to_all_candidate_sets(candidate, resulting_candidates);
369+
false
355370
}
356371
}
357372
}
@@ -367,8 +382,9 @@ impl<'a,'tcx> Builder<'a,'tcx> {
367382
let new_candidate = self.candidate_without_match_pair(match_pair_index,
368383
candidate);
369384
resulting_candidates[0].push(new_candidate);
385+
true
370386
} else {
371-
self.add_to_all_candidate_sets(candidate, resulting_candidates);
387+
false
372388
}
373389
}
374390
}
@@ -392,14 +408,6 @@ impl<'a,'tcx> Builder<'a,'tcx> {
392408
}
393409
}
394410

395-
fn add_to_all_candidate_sets<'pat>(&mut self,
396-
candidate: &Candidate<'pat, 'tcx>,
397-
resulting_candidates: &mut [Vec<Candidate<'pat, 'tcx>>]) {
398-
for resulting_candidate in resulting_candidates {
399-
resulting_candidate.push(candidate.clone());
400-
}
401-
}
402-
403411
fn candidate_after_variant_switch<'pat>(&mut self,
404412
match_pair_index: usize,
405413
adt_def: ty::AdtDef<'tcx>,
@@ -447,5 +455,5 @@ impl<'a,'tcx> Builder<'a,'tcx> {
447455
}
448456

449457
fn is_switch_ty<'tcx>(ty: Ty<'tcx>) -> bool {
450-
ty.is_integral() || ty.is_char()
458+
ty.is_integral() || ty.is_char() || ty.is_bool()
451459
}

0 commit comments

Comments
 (0)