Skip to content

Commit

Permalink
Auto merge of #119031 - Nadrieril:two-phase-match-lowering, r=<try>
Browse files Browse the repository at this point in the history
[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`
  • Loading branch information
bors committed Jan 21, 2024
2 parents d9d89fd + b005fea commit 568ca85
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 149 deletions.
53 changes: 43 additions & 10 deletions compiler/rustc_mir_build/src/build/matches/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty};
use rustc_span::symbol::Symbol;
use rustc_span::{BytePos, Pos, Span};
use rustc_target::abi::VariantIdx;
use smallvec::{smallvec, SmallVec};

// helper functions, broken out by category:
mod simplify;
mod test;
Expand Down Expand Up @@ -951,12 +949,15 @@ struct Candidate<'pat, 'tcx> {
has_guard: bool,

/// All of these must be satisfied...
match_pairs: SmallVec<[MatchPair<'pat, 'tcx>; 1]>,
// Invariant: all the `MatchPair`s are recursively simplified.
match_pairs: Vec<MatchPair<'pat, 'tcx>>,

/// ...these bindings established...
// Invariant: not mutated during match tree construction.
bindings: Vec<Binding<'tcx>>,

/// ...and these types asserted...
// Invariant: not mutated during match tree construction.
ascriptions: Vec<Ascription<'tcx>>,

/// ...and if this is non-empty, one of these subcandidates also has to match...
Expand All @@ -976,19 +977,27 @@ impl<'tcx, 'pat> Candidate<'pat, 'tcx> {
place: PlaceBuilder<'tcx>,
pattern: &'pat Pat<'tcx>,
has_guard: bool,
cx: &Builder<'_, 'tcx>,
cx: &mut Builder<'_, 'tcx>,
) -> Self {
Candidate {
let mut candidate = Candidate {
span: pattern.span,
has_guard,
match_pairs: smallvec![MatchPair::new(place, pattern, cx)],
match_pairs: vec![MatchPair::new(place, pattern, cx)],
bindings: Vec::new(),
ascriptions: Vec::new(),
subcandidates: Vec::new(),
otherwise_block: None,
pre_binding_block: None,
next_candidate_pre_binding_block: None,
}
};

cx.simplify_candidate(
&mut candidate.bindings,
&mut candidate.ascriptions,
&mut candidate.match_pairs,
);

candidate
}

/// Visit the leaf candidates (those with no subcandidates) contained in
Expand Down Expand Up @@ -1044,13 +1053,17 @@ struct Ascription<'tcx> {
variance: ty::Variance,
}

#[derive(Clone, Debug)]
#[derive(Debug)]
pub(crate) struct MatchPair<'pat, 'tcx> {
// this place...
// This place...
place: PlaceBuilder<'tcx>,

// ... must match this pattern.
// Invariant: this pattern must be simplified, i.e. requires a test.
pattern: &'pat Pat<'tcx>,

/// Precomputed sub-match pairs.
subpairs: Vec<Self>,
}

/// See [`Test`] for more.
Expand Down Expand Up @@ -1172,7 +1185,27 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// be a switch or pattern comparison.
let mut split_or_candidate = false;
for candidate in &mut *candidates {
split_or_candidate |= self.simplify_candidate(candidate);
split_or_candidate |= {
if let [MatchPair { pattern: Pat { kind: PatKind::Or { pats }, .. }, place, .. }] =
&*candidate.match_pairs
{
// 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.
candidate.subcandidates =
self.create_or_subcandidates(place, pats, candidate.has_guard);
candidate.match_pairs.pop();
true
} else {
false
}
};
}

ensure_sufficient_stack(|| {
Expand Down
130 changes: 70 additions & 60 deletions compiler/rustc_mir_build/src/build/matches/simplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,20 @@
use crate::build::expr::as_place::PlaceBuilder;
use crate::build::matches::{Ascription, Binding, Candidate, MatchPair};
use crate::build::Builder;
use rustc_middle::mir::PlaceElem;
use rustc_middle::thir::{self, *};

use std::mem;

impl<'a, 'tcx> Builder<'a, 'tcx> {
/// Simplify a candidate so that all match pairs require a test.
///
/// This method will also 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. If this happens this method returns
/// `true`.
#[instrument(skip(self, candidate), level = "debug")]
#[instrument(skip(self), level = "debug")]
pub(super) fn simplify_candidate<'pat>(
&mut self,
candidate: &mut Candidate<'pat, 'tcx>,
) -> bool {
candidate_bindings: &mut Vec<Binding<'tcx>>,
candidate_ascriptions: &mut Vec<Ascription<'tcx>>,
candidate_match_pairs: &mut Vec<MatchPair<'pat, 'tcx>>,
) {
// repeatedly simplify match pairs until fixed point is reached
debug!("{candidate:#?}");

// existing_bindings and new_bindings exists to keep the semantics in order.
// Reversing the binding order for bindings after `@` changes the binding order in places
Expand All @@ -59,28 +48,32 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// binding in iter 2: [6, 7]
//
// final binding: [1, 2, 3, 6, 7, 4, 5]
let mut existing_bindings = mem::take(&mut candidate.bindings);
let mut existing_bindings = mem::take(candidate_bindings);
let mut new_bindings = Vec::new();
loop {
let match_pairs = mem::take(&mut candidate.match_pairs);
let mut match_pairs = mem::take(candidate_match_pairs);

if let [MatchPair { pattern: Pat { kind: PatKind::Or { pats }, .. }, place }] =
&*match_pairs
if let [MatchPair { pattern: Pat { kind: PatKind::Or { .. }, .. }, .. }] = &*match_pairs
{
existing_bindings.extend_from_slice(&new_bindings);
mem::swap(&mut candidate.bindings, &mut existing_bindings);
candidate.subcandidates = self.create_or_subcandidates(candidate, place, pats);
return true;
mem::swap(candidate_bindings, &mut existing_bindings);
mem::swap(candidate_match_pairs, &mut match_pairs);
return;
}

let mut changed = false;
for match_pair in match_pairs {
match self.simplify_match_pair(match_pair, candidate) {
match self.simplify_match_pair(
match_pair,
candidate_bindings,
candidate_ascriptions,
candidate_match_pairs,
) {
Ok(()) => {
changed = true;
}
Err(match_pair) => {
candidate.match_pairs.push(match_pair);
candidate_match_pairs.push(match_pair);
}
}
}
Expand All @@ -97,38 +90,43 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// let z = x.copy_field;
// let y = x;
// }
candidate.bindings.extend_from_slice(&new_bindings);
mem::swap(&mut candidate.bindings, &mut new_bindings);
candidate.bindings.clear();
candidate_bindings.extend_from_slice(&new_bindings);
mem::swap(candidate_bindings, &mut new_bindings);
candidate_bindings.clear();

if !changed {
existing_bindings.extend_from_slice(&new_bindings);
mem::swap(&mut candidate.bindings, &mut existing_bindings);
mem::swap(candidate_bindings, &mut existing_bindings);
// Move or-patterns to the end, because they can result in us
// creating additional candidates, so we want to test them as
// late as possible.
candidate
.match_pairs
candidate_match_pairs
.sort_by_key(|pair| matches!(pair.pattern.kind, PatKind::Or { .. }));
debug!(simplified = ?candidate, "simplify_candidate");
return false; // if we were not able to simplify any, done.
debug!(simplified = ?candidate_match_pairs, "simplify_candidate");
return; // if we were not able to simplify any, done.
}
}
}

/// Given `candidate` that has a single or-pattern for its match-pairs,
/// creates a fresh candidate for each of its input subpatterns passed via
/// `pats`.
fn create_or_subcandidates<'pat>(
pub(super) fn create_or_subcandidates<'pat>(
&mut self,
candidate: &Candidate<'pat, 'tcx>,
place: &PlaceBuilder<'tcx>,
pats: &'pat [Box<Pat<'tcx>>],
has_guard: bool,
) -> Vec<Candidate<'pat, 'tcx>> {
pats.iter()
.map(|box pat| {
let mut candidate = Candidate::new(place.clone(), pat, candidate.has_guard, self);
self.simplify_candidate(&mut candidate);
let mut candidate = Candidate::new(place.clone(), pat, has_guard, self);
if let [MatchPair { pattern: Pat { kind: PatKind::Or { pats }, .. }, place, .. }] =
&*candidate.match_pairs
{
candidate.subcandidates =
self.create_or_subcandidates(place, pats, candidate.has_guard);
candidate.match_pairs.pop();
}
candidate
})
.collect()
Expand All @@ -141,8 +139,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
/// candidate.
fn simplify_match_pair<'pat>(
&mut self,
match_pair: MatchPair<'pat, 'tcx>,
candidate: &mut Candidate<'pat, 'tcx>,
mut match_pair: MatchPair<'pat, 'tcx>,
bindings: &mut Vec<Binding<'tcx>>,
ascriptions: &mut Vec<Ascription<'tcx>>,
match_pairs: &mut Vec<MatchPair<'pat, 'tcx>>,
) -> Result<(), MatchPair<'pat, 'tcx>> {
match match_pair.pattern.kind {
PatKind::AscribeUserType {
Expand All @@ -151,14 +151,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
} => {
// Apply the type ascription to the value at `match_pair.place`, which is the
if let Some(source) = match_pair.place.try_to_place(self) {
candidate.ascriptions.push(Ascription {
ascriptions.push(Ascription {
annotation: annotation.clone(),
source,
variance,
});
}

candidate.match_pairs.push(MatchPair::new(match_pair.place, subpattern, self));
match_pairs.push(MatchPair::new(match_pair.place, subpattern, self));

Ok(())
}
Expand All @@ -178,7 +178,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
is_primary: _,
} => {
if let Some(source) = match_pair.place.try_to_place(self) {
candidate.bindings.push(Binding {
bindings.push(Binding {
span: match_pair.pattern.span,
source,
var_id: var,
Expand All @@ -188,7 +188,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {

if let Some(subpattern) = subpattern.as_ref() {
// this is the `x @ P` case; have to keep matching against `P` now
candidate.match_pairs.push(MatchPair::new(match_pair.place, subpattern, self));
match_pairs.push(MatchPair::new(match_pair.place, subpattern, self));
}

Ok(())
Expand All @@ -206,7 +206,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}

PatKind::InlineConstant { subpattern: ref pattern, def: _ } => {
candidate.match_pairs.push(MatchPair::new(match_pair.place, pattern, self));
match_pairs.push(MatchPair::new(match_pair.place, pattern, self));

Ok(())
}
Expand All @@ -222,15 +222,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
PatKind::Slice { ref prefix, ref slice, ref suffix } => {
if prefix.is_empty() && slice.is_some() && suffix.is_empty() {
// irrefutable
self.prefix_slice_suffix(match_pairs, &match_pair.place, prefix, slice, suffix);
Ok(())
} else {
self.prefix_slice_suffix(
&mut candidate.match_pairs,
&mut match_pair.subpairs,
&match_pair.place,
prefix,
slice,
suffix,
);
Ok(())
} else {
self.simplify_candidate(bindings, ascriptions, &mut match_pair.subpairs);
Err(match_pair)
}
}
Expand All @@ -248,35 +250,43 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|| !adt_def.is_variant_list_non_exhaustive());
if irrefutable {
let place_builder = match_pair.place.downcast(adt_def, variant_index);
candidate
.match_pairs
.extend(self.field_match_pairs(place_builder, subpatterns));
match_pairs.extend(self.field_match_pairs(place_builder, subpatterns));
Ok(())
} else {
// If we have a match-pattern like `x @ Enum::Variant(P1, P2)`,
// we want to create a set of derived match-patterns like
// `(x as Variant).0 @ P1` and `(x as Variant).1 @ P1`.
let downcast_place = match_pair.place.clone().downcast(adt_def, variant_index); // `(x as Variant)`
let consequent_match_pairs = subpatterns.iter().map(|subpattern| {
// e.g., `(x as Variant).0`
let place = downcast_place.clone_project(PlaceElem::Field(
subpattern.field,
subpattern.pattern.ty,
));
// e.g., `(x as Variant).0 @ P1`
MatchPair::new(place, &subpattern.pattern, self)
});

match_pair.subpairs.extend(consequent_match_pairs);
self.simplify_candidate(bindings, ascriptions, &mut match_pair.subpairs);
Err(match_pair)
}
}

PatKind::Array { ref prefix, ref slice, ref suffix } => {
self.prefix_slice_suffix(
&mut candidate.match_pairs,
&match_pair.place,
prefix,
slice,
suffix,
);
self.prefix_slice_suffix(match_pairs, &match_pair.place, prefix, slice, suffix);
Ok(())
}

PatKind::Leaf { ref subpatterns } => {
// tuple struct, match subpats (if any)
candidate.match_pairs.extend(self.field_match_pairs(match_pair.place, subpatterns));
match_pairs.extend(self.field_match_pairs(match_pair.place, subpatterns));
Ok(())
}

PatKind::Deref { ref subpattern } => {
let place_builder = match_pair.place.deref();
candidate.match_pairs.push(MatchPair::new(place_builder, subpattern, self));
match_pairs.push(MatchPair::new(place_builder, subpattern, self));
Ok(())
}

Expand Down
Loading

0 comments on commit 568ca85

Please sign in to comment.