Skip to content

Commit db9a848

Browse files
committed
Simplify specialize_constructor
Also removes the ugly caching that was introduced in #76918. It was bolted on without deeper knowledge of the workings of the algorithm. This commit manages to be more performant without any of the complexity. It should be better on representative workloads too.
1 parent 54fa702 commit db9a848

File tree

1 file changed

+13
-175
lines changed
  • compiler/rustc_mir_build/src/thir/pattern

1 file changed

+13
-175
lines changed

compiler/rustc_mir_build/src/thir/pattern/_match.rs

+13-175
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ use self::Usefulness::*;
293293
use self::WitnessPreference::*;
294294

295295
use rustc_data_structures::captures::Captures;
296-
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
296+
use rustc_data_structures::fx::FxHashSet;
297297
use rustc_data_structures::sync::OnceCell;
298298
use rustc_index::vec::Idx;
299299

@@ -401,48 +401,17 @@ impl<'p, 'tcx> PatStack<'p, 'tcx> {
401401
}
402402
}
403403

404-
/// This computes `S(constructor, self)`. See top of the file for explanations.
405-
///
406-
/// This is the main specialization step. It expands the pattern
407-
/// into `arity` patterns based on the constructor. For most patterns, the step is trivial,
408-
/// for instance tuple patterns are flattened and box patterns expand into their inner pattern.
409-
/// Returns `None` if the pattern does not have the given constructor.
404+
/// This computes `S(self.head_ctor(), self)`. See top of the file for explanations.
410405
///
411-
/// OTOH, slice patterns with a subslice pattern (tail @ ..) can be expanded into multiple
412-
/// different patterns.
413406
/// Structure patterns with a partial wild pattern (Foo { a: 42, .. }) have their missing
414407
/// fields filled with wild patterns.
415408
///
416409
/// This is roughly the inverse of `Constructor::apply`.
417-
fn specialize_constructor(
418-
&self,
419-
pcx: PatCtxt<'_, 'p, 'tcx>,
420-
ctor: &Constructor<'tcx>,
421-
ctor_wild_subpatterns: &Fields<'p, 'tcx>,
422-
is_my_head_ctor: bool,
423-
) -> Option<PatStack<'p, 'tcx>> {
424-
// We return `None` if `ctor` is not covered by `self.head()`. If `ctor` is known to be
425-
// derived from `self.head()`, then we don't need to check; otherwise, we check for
426-
// constructor inclusion.
427-
// Note that this shortcut is also necessary for correctness: a pattern should always be
428-
// specializable with its own constructor, even in cases where we refuse to inspect values like
429-
// opaque constants.
430-
if !is_my_head_ctor && !ctor.is_covered_by(pcx, self.head_ctor(pcx.cx)) {
431-
return None;
432-
}
433-
let new_fields = ctor_wild_subpatterns.replace_with_pattern_arguments(self.head());
434-
435-
debug!(
436-
"specialize_constructor({:#?}, {:#?}, {:#?}) = {:#?}",
437-
self.head(),
438-
ctor,
439-
ctor_wild_subpatterns,
440-
new_fields
441-
);
442-
410+
fn pop_head_constructor(&self, ctor_wild_subpatterns: &Fields<'p, 'tcx>) -> PatStack<'p, 'tcx> {
443411
// We pop the head pattern and push the new fields extracted from the arguments of
444412
// `self.head()`.
445-
Some(new_fields.push_on_patstack(&self.pats[1..]))
413+
let new_fields = ctor_wild_subpatterns.replace_with_pattern_arguments(self.head());
414+
new_fields.push_on_patstack(&self.pats[1..])
446415
}
447416
}
448417

@@ -467,36 +436,15 @@ impl<'p, 'tcx> FromIterator<&'p Pat<'tcx>> for PatStack<'p, 'tcx> {
467436
}
468437
}
469438

470-
/// Depending on the match patterns, the specialization process might be able to use a fast path.
471-
/// Tracks whether we can use the fast path and the lookup table needed in those cases.
472-
#[derive(Clone, Debug, PartialEq)]
473-
enum SpecializationCache {
474-
/// Patterns consist of only enum variants.
475-
/// Variant patterns does not intersect with each other (in contrast to range patterns),
476-
/// so it is possible to precompute the result of `Matrix::specialize_constructor` at a
477-
/// lower computational complexity.
478-
/// `lookup` is responsible for holding the precomputed result of
479-
/// specialization, while `wilds` is used for two purposes: the first one is
480-
/// the precomputed result of specialization with a wildcard, and the second is to be used as a
481-
/// fallback for `Matrix::specialize_constructor` when it tries to apply a constructor that
482-
/// has not been seen in the `Matrix`. See `update_cache` for further explanations.
483-
Variants { lookup: FxHashMap<DefId, SmallVec<[usize; 1]>>, wilds: SmallVec<[usize; 1]> },
484-
/// Does not belong to the cases above, use the slow path.
485-
Incompatible,
486-
}
487-
488439
/// A 2D matrix.
489440
#[derive(Clone, PartialEq)]
490441
crate struct Matrix<'p, 'tcx> {
491442
patterns: Vec<PatStack<'p, 'tcx>>,
492-
cache: SpecializationCache,
493443
}
494444

495445
impl<'p, 'tcx> Matrix<'p, 'tcx> {
496446
crate fn empty() -> Self {
497-
// Use `SpecializationCache::Incompatible` as a placeholder; we will initialize it on the
498-
// first call to `push`. See the first half of `update_cache`.
499-
Matrix { patterns: vec![], cache: SpecializationCache::Incompatible }
447+
Matrix { patterns: vec![] }
500448
}
501449

502450
/// Pushes a new row to the matrix. If the row starts with an or-pattern, this expands it.
@@ -509,70 +457,6 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> {
509457
}
510458
} else {
511459
self.patterns.push(row);
512-
self.update_cache(self.patterns.len() - 1);
513-
}
514-
}
515-
516-
fn update_cache(&mut self, idx: usize) {
517-
let row = &self.patterns[idx];
518-
// We don't know which kind of cache could be used until we see the first row; therefore an
519-
// empty `Matrix` is initialized with `SpecializationCache::Empty`, then the cache is
520-
// assigned the appropriate variant below on the first call to `push`.
521-
if self.patterns.is_empty() {
522-
self.cache = if row.is_empty() {
523-
SpecializationCache::Incompatible
524-
} else {
525-
match *row.head().kind {
526-
PatKind::Variant { .. } => SpecializationCache::Variants {
527-
lookup: FxHashMap::default(),
528-
wilds: SmallVec::new(),
529-
},
530-
// Note: If the first pattern is a wildcard, then all patterns after that is not
531-
// useful. The check is simple enough so we treat it as the same as unsupported
532-
// patterns.
533-
_ => SpecializationCache::Incompatible,
534-
}
535-
};
536-
}
537-
// Update the cache.
538-
match &mut self.cache {
539-
SpecializationCache::Variants { ref mut lookup, ref mut wilds } => {
540-
let head = row.head();
541-
match *head.kind {
542-
_ if head.is_wildcard() => {
543-
// Per rule 1.3 in the top-level comments, a wildcard pattern is included in
544-
// the result of `specialize_constructor` for *any* `Constructor`.
545-
// We push the wildcard pattern to the precomputed result for constructors
546-
// that we have seen before; results for constructors we have not yet seen
547-
// defaults to `wilds`, which is updated right below.
548-
for (_, v) in lookup.iter_mut() {
549-
v.push(idx);
550-
}
551-
// Per rule 2.1 and 2.2 in the top-level comments, only wildcard patterns
552-
// are included in the result of specialization with a wildcard.
553-
// What we do here is to track the wildcards we have seen; so in addition to
554-
// acting as the precomputed result of specialization with a wildcard, `wilds` also
555-
// serves as the default value of `specialize_constructor` for constructors
556-
// that are not in `lookup`.
557-
wilds.push(idx);
558-
}
559-
PatKind::Variant { adt_def, variant_index, .. } => {
560-
// Handle the cases of rule 1.1 and 1.2 in the top-level comments.
561-
// A variant pattern can only be included in the results of
562-
// `specialize_constructor` for a particular constructor, therefore we are
563-
// using a HashMap to track that.
564-
lookup
565-
.entry(adt_def.variants[variant_index].def_id)
566-
// Default to `wilds` for absent keys. See above for an explanation.
567-
.or_insert_with(|| wilds.clone())
568-
.push(idx);
569-
}
570-
_ => {
571-
self.cache = SpecializationCache::Incompatible;
572-
}
573-
}
574-
}
575-
SpecializationCache::Incompatible => {}
576460
}
577461
}
578462

@@ -593,59 +477,14 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> {
593477
fn specialize_constructor(
594478
&self,
595479
pcx: PatCtxt<'_, 'p, 'tcx>,
596-
constructor: &Constructor<'tcx>,
480+
ctor: &Constructor<'tcx>,
597481
ctor_wild_subpatterns: &Fields<'p, 'tcx>,
598482
) -> Matrix<'p, 'tcx> {
599-
match &self.cache {
600-
SpecializationCache::Variants { lookup, wilds } => {
601-
let cached = if let Constructor::Variant(id) = constructor {
602-
lookup
603-
.get(id)
604-
// Default to `wilds` for absent keys. See `update_cache` for an explanation.
605-
.unwrap_or(&wilds)
606-
} else if let Wildcard = constructor {
607-
&wilds
608-
} else {
609-
bug!(
610-
"unexpected constructor encountered while dealing with matrix cache: {:?}",
611-
constructor
612-
);
613-
};
614-
let result: Self = cached
615-
.iter()
616-
.filter_map(|&i| {
617-
self.patterns[i].specialize_constructor(
618-
pcx,
619-
constructor,
620-
ctor_wild_subpatterns,
621-
false,
622-
)
623-
})
624-
.collect();
625-
// When debug assertions are enabled, check the results against the "slow path"
626-
// result.
627-
debug_assert_eq!(
628-
result,
629-
Matrix {
630-
patterns: self.patterns.clone(),
631-
cache: SpecializationCache::Incompatible
632-
}
633-
.specialize_constructor(
634-
pcx,
635-
constructor,
636-
ctor_wild_subpatterns
637-
)
638-
);
639-
result
640-
}
641-
SpecializationCache::Incompatible => self
642-
.patterns
643-
.iter()
644-
.filter_map(|r| {
645-
r.specialize_constructor(pcx, constructor, ctor_wild_subpatterns, false)
646-
})
647-
.collect(),
648-
}
483+
self.patterns
484+
.iter()
485+
.filter(|r| ctor.is_covered_by(pcx, r.head_ctor(pcx.cx)))
486+
.map(|r| r.pop_head_constructor(ctor_wild_subpatterns))
487+
.collect()
649488
}
650489
}
651490

@@ -2442,8 +2281,7 @@ crate fn is_useful<'p, 'tcx>(
24422281
// We cache the result of `Fields::wildcards` because it is used a lot.
24432282
let ctor_wild_subpatterns = Fields::wildcards(pcx, &ctor);
24442283
let matrix = pcx.matrix.specialize_constructor(pcx, &ctor, &ctor_wild_subpatterns);
2445-
// Unwrap is ok: v can always be specialized with its own constructor.
2446-
let v = v.specialize_constructor(pcx, &ctor, &ctor_wild_subpatterns, true).unwrap();
2284+
let v = v.pop_head_constructor(&ctor_wild_subpatterns);
24472285
let usefulness =
24482286
is_useful(pcx.cx, &matrix, &v, witness_preference, hir_id, is_under_guard, false);
24492287
usefulness.apply_constructor(pcx, &ctor, &ctor_wild_subpatterns, is_top_level)

0 commit comments

Comments
 (0)