Skip to content

Commit

Permalink
Add a cheap mode for compute_missing_ctors.
Browse files Browse the repository at this point in the history
`compute_missing_ctors` is called a lot. It produces a vector, which can
be reasonably large (e.g. 100+ elements), but the vector is almost
always only checked for emptiness.

This commit changes `compute_missing_ctors` so it can be called in a
cheap way that just indicates if the vector would be empty. If
necessary, the function can subsequently be called in an expensive way
to compute the full vector.

This change reduces instruction counts for several benchmarks up to 2%.
  • Loading branch information
nnethercote committed Oct 22, 2018
1 parent 8a7048b commit b5336c0
Showing 1 changed file with 66 additions and 19 deletions.
85 changes: 66 additions & 19 deletions src/librustc_mir/hair/pattern/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -931,12 +931,37 @@ impl<'tcx> IntRange<'tcx> {
}
}

// Return a set of constructors equivalent to `all_ctors \ used_ctors`.
// A request for missing constructor data in terms of either:
// - whether or not there any missing constructors; or
// - the actual set of missing constructors.
#[derive(PartialEq)]
enum MissingCtorsInfo {
Emptiness,
Ctors,
}

// Used by `compute_missing_ctors`.
#[derive(Debug, PartialEq)]
enum MissingCtors<'tcx> {
Empty,
NonEmpty,

// Note that the Vec can be empty.
Ctors(Vec<Constructor<'tcx>>),
}

// When `info` is `MissingCtorsInfo::Ctors`, compute a set of constructors
// equivalent to `all_ctors \ used_ctors`. When `info` is
// `MissingCtorsInfo::Emptiness`, just determines if that set is empty or not.
// (The split logic gives a performance win, because we always need to know if
// the set is empty, but we rarely need the full set, and it can be expensive
// to compute the full set.)
fn compute_missing_ctors<'a, 'tcx: 'a>(
info: MissingCtorsInfo,
tcx: TyCtxt<'a, 'tcx, 'tcx>,
all_ctors: &Vec<Constructor<'tcx>>,
used_ctors: &Vec<Constructor<'tcx>>,
) -> Vec<Constructor<'tcx>> {
) -> MissingCtors<'tcx> {
let mut missing_ctors = vec![];

for req_ctor in all_ctors {
Expand Down Expand Up @@ -965,10 +990,22 @@ fn compute_missing_ctors<'a, 'tcx: 'a>(
// We add `refined_ctors` instead of `req_ctor`, because then we can
// provide more detailed error information about precisely which
// ranges have been omitted.
missing_ctors.extend(refined_ctors);
if info == MissingCtorsInfo::Emptiness {
if !refined_ctors.is_empty() {
// The set is non-empty; return early.
return MissingCtors::NonEmpty;
}
} else {
missing_ctors.extend(refined_ctors);
}
}

missing_ctors
if info == MissingCtorsInfo::Emptiness {
// If we reached here, the set is empty.
MissingCtors::Empty
} else {
MissingCtors::Ctors(missing_ctors)
}
}

/// Algorithm from http://moscova.inria.fr/~maranget/papers/warn/index.html
Expand Down Expand Up @@ -1081,20 +1118,23 @@ pub fn is_useful<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
// feature flag is not present, so this is only
// needed for that case.

// Find those constructors that are not matched by any non-wildcard patterns in the
// current column.
let missing_ctors = compute_missing_ctors(cx.tcx, &all_ctors, &used_ctors);
// Missing constructors are those that are not matched by any
// non-wildcard patterns in the current column. We always determine if
// the set is empty, but we only fully construct them on-demand,
// because they're rarely used and can be big.
let cheap_missing_ctors =
compute_missing_ctors(MissingCtorsInfo::Emptiness, cx.tcx, &all_ctors, &used_ctors);

let is_privately_empty = all_ctors.is_empty() && !cx.is_uninhabited(pcx.ty);
let is_declared_nonexhaustive = cx.is_non_exhaustive_enum(pcx.ty) && !cx.is_local(pcx.ty);
debug!("missing_ctors={:#?} is_privately_empty={:#?} is_declared_nonexhaustive={:#?}",
missing_ctors, is_privately_empty, is_declared_nonexhaustive);
debug!("cheap_missing_ctors={:#?} is_privately_empty={:#?} is_declared_nonexhaustive={:#?}",
cheap_missing_ctors, is_privately_empty, is_declared_nonexhaustive);

// For privately empty and non-exhaustive enums, we work as if there were an "extra"
// `_` constructor for the type, so we can never match over all constructors.
let is_non_exhaustive = is_privately_empty || is_declared_nonexhaustive;

if missing_ctors.is_empty() && !is_non_exhaustive {
if cheap_missing_ctors == MissingCtors::Empty && !is_non_exhaustive {
split_grouped_constructors(cx.tcx, all_ctors, matrix, pcx.ty).into_iter().map(|c| {
is_useful_specialized(cx, matrix, v, c.clone(), pcx.ty, witness)
}).find(|result| result.is_useful()).unwrap_or(NotUseful)
Expand Down Expand Up @@ -1165,15 +1205,22 @@ pub fn is_useful<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
witness
}).collect()
} else {
pats.into_iter().flat_map(|witness| {
missing_ctors.iter().map(move |ctor| {
// Extends the witness with a "wild" version of this
// constructor, that matches everything that can be built with
// it. For example, if `ctor` is a `Constructor::Variant` for
// `Option::Some`, this pushes the witness for `Some(_)`.
witness.clone().push_wild_constructor(cx, ctor, pcx.ty)
})
}).collect()
let expensive_missing_ctors =
compute_missing_ctors(MissingCtorsInfo::Ctors, cx.tcx, &all_ctors,
&used_ctors);
if let MissingCtors::Ctors(missing_ctors) = expensive_missing_ctors {
pats.into_iter().flat_map(|witness| {
missing_ctors.iter().map(move |ctor| {
// Extends the witness with a "wild" version of this
// constructor, that matches everything that can be built with
// it. For example, if `ctor` is a `Constructor::Variant` for
// `Option::Some`, this pushes the witness for `Some(_)`.
witness.clone().push_wild_constructor(cx, ctor, pcx.ty)
})
}).collect()
} else {
bug!("cheap missing ctors")
}
};
UsefulWithWitness(new_witnesses)
}
Expand Down

0 comments on commit b5336c0

Please sign in to comment.