Auto merge of #72412 - VFLashM:issue-72408-nested-closures-exponential, r=tmandry

Issue 72408 nested closures exponential

This fixes #72408.

Nested closures were resulting in exponential compilation time.

This PR is enhancing asymptotic complexity, but also increasing the constant, so I would love to see perf run results.
This commit is contained in:
bors 2020-09-18 14:08:39 +00:00
commit fdc3405c20
22 changed files with 344 additions and 82 deletions

View file

@ -3,6 +3,7 @@
// RFC for reference.
use crate::ty::subst::{GenericArg, GenericArgKind};
use crate::ty::walk::MiniSet;
use crate::ty::{self, Ty, TyCtxt, TypeFoldable};
use smallvec::SmallVec;
@ -50,12 +51,18 @@ impl<'tcx> TyCtxt<'tcx> {
/// Push onto `out` all the things that must outlive `'a` for the condition
/// `ty0: 'a` to hold. Note that `ty0` must be a **fully resolved type**.
pub fn push_outlives_components(self, ty0: Ty<'tcx>, out: &mut SmallVec<[Component<'tcx>; 4]>) {
compute_components(self, ty0, out);
let mut visited = MiniSet::new();
compute_components(self, ty0, out, &mut visited);
debug!("components({:?}) = {:?}", ty0, out);
}
}
fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Component<'tcx>; 4]>) {
fn compute_components(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
out: &mut SmallVec<[Component<'tcx>; 4]>,
visited: &mut MiniSet<GenericArg<'tcx>>,
) {
// Descend through the types, looking for the various "base"
// components and collecting them into `out`. This is not written
// with `collect()` because of the need to sometimes skip subtrees
@ -73,11 +80,11 @@ fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Compo
for child in substs {
match child.unpack() {
GenericArgKind::Type(ty) => {
compute_components(tcx, ty, out);
compute_components(tcx, ty, out, visited);
}
GenericArgKind::Lifetime(_) => {}
GenericArgKind::Const(_) => {
compute_components_recursive(tcx, child, out);
compute_components_recursive(tcx, child, out, visited);
}
}
}
@ -85,19 +92,19 @@ fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Compo
ty::Array(element, _) => {
// Don't look into the len const as it doesn't affect regions
compute_components(tcx, element, out);
compute_components(tcx, element, out, visited);
}
ty::Closure(_, ref substs) => {
for upvar_ty in substs.as_closure().upvar_tys() {
compute_components(tcx, upvar_ty, out);
compute_components(tcx, upvar_ty, out, visited);
}
}
ty::Generator(_, ref substs, _) => {
// Same as the closure case
for upvar_ty in substs.as_generator().upvar_tys() {
compute_components(tcx, upvar_ty, out);
compute_components(tcx, upvar_ty, out, visited);
}
// We ignore regions in the generator interior as we don't
@ -135,7 +142,8 @@ fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Compo
// OutlivesProjectionComponents. Continue walking
// through and constrain Pi.
let mut subcomponents = smallvec![];
compute_components_recursive(tcx, ty.into(), &mut subcomponents);
let mut subvisited = MiniSet::new();
compute_components_recursive(tcx, ty.into(), &mut subcomponents, &mut subvisited);
out.push(Component::EscapingProjection(subcomponents.into_iter().collect()));
}
}
@ -177,7 +185,7 @@ fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Compo
// the "bound regions list". In our representation, no such
// list is maintained explicitly, because bound regions
// themselves can be readily identified.
compute_components_recursive(tcx, ty.into(), out);
compute_components_recursive(tcx, ty.into(), out, visited);
}
}
}
@ -186,11 +194,12 @@ fn compute_components_recursive(
tcx: TyCtxt<'tcx>,
parent: GenericArg<'tcx>,
out: &mut SmallVec<[Component<'tcx>; 4]>,
visited: &mut MiniSet<GenericArg<'tcx>>,
) {
for child in parent.walk_shallow() {
for child in parent.walk_shallow(visited) {
match child.unpack() {
GenericArgKind::Type(ty) => {
compute_components(tcx, ty, out);
compute_components(tcx, ty, out, visited);
}
GenericArgKind::Lifetime(lt) => {
// Ignore late-bound regions.
@ -199,7 +208,7 @@ fn compute_components_recursive(
}
}
GenericArgKind::Const(_) => {
compute_components_recursive(tcx, child, out);
compute_components_recursive(tcx, child, out, visited);
}
}
}

View file

@ -4,6 +4,7 @@ use crate::ty::{self, DefIdTree, Ty, TyCtxt};
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def_id::{CrateNum, DefId};
use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
use rustc_middle::ty::walk::MiniSet;
// `pretty` is a separate module only for organization.
mod pretty;
@ -263,22 +264,34 @@ pub trait Printer<'tcx>: Sized {
/// function tries to find a "characteristic `DefId`" for a
/// type. It's just a heuristic so it makes some questionable
/// decisions and we may want to adjust it later.
pub fn characteristic_def_id_of_type(ty: Ty<'_>) -> Option<DefId> {
///
/// Visited set is needed to avoid full iteration over
/// deeply nested tuples that have no DefId.
fn characteristic_def_id_of_type_cached<'a>(
ty: Ty<'a>,
visited: &mut MiniSet<Ty<'a>>,
) -> Option<DefId> {
match *ty.kind() {
ty::Adt(adt_def, _) => Some(adt_def.did),
ty::Dynamic(data, ..) => data.principal_def_id(),
ty::Array(subty, _) | ty::Slice(subty) => characteristic_def_id_of_type(subty),
ty::RawPtr(mt) => characteristic_def_id_of_type(mt.ty),
ty::Ref(_, ty, _) => characteristic_def_id_of_type(ty),
ty::Tuple(ref tys) => {
tys.iter().find_map(|ty| characteristic_def_id_of_type(ty.expect_ty()))
ty::Array(subty, _) | ty::Slice(subty) => {
characteristic_def_id_of_type_cached(subty, visited)
}
ty::RawPtr(mt) => characteristic_def_id_of_type_cached(mt.ty, visited),
ty::Ref(_, ty, _) => characteristic_def_id_of_type_cached(ty, visited),
ty::Tuple(ref tys) => tys.iter().find_map(|ty| {
let ty = ty.expect_ty();
if visited.insert(ty) {
return characteristic_def_id_of_type_cached(ty, visited);
}
return None;
}),
ty::FnDef(def_id, _)
| ty::Closure(def_id, _)
| ty::Generator(def_id, _, _)
@ -302,6 +315,9 @@ pub fn characteristic_def_id_of_type(ty: Ty<'_>) -> Option<DefId> {
| ty::Float(_) => None,
}
}
pub fn characteristic_def_id_of_type(ty: Ty<'_>) -> Option<DefId> {
characteristic_def_id_of_type_cached(ty, &mut MiniSet::new())
}
impl<'tcx, P: Printer<'tcx>> Print<'tcx, P> for ty::RegionKind {
type Output = P::Region;

View file

@ -1264,6 +1264,7 @@ pub struct FmtPrinterData<'a, 'tcx, F> {
used_region_names: FxHashSet<Symbol>,
region_index: usize,
binder_depth: usize,
printed_type_count: usize,
pub region_highlight_mode: RegionHighlightMode,
@ -1294,6 +1295,7 @@ impl<F> FmtPrinter<'a, 'tcx, F> {
used_region_names: Default::default(),
region_index: 0,
binder_depth: 0,
printed_type_count: 0,
region_highlight_mode: RegionHighlightMode::default(),
name_resolver: None,
}))
@ -1411,8 +1413,14 @@ impl<F: fmt::Write> Printer<'tcx> for FmtPrinter<'_, 'tcx, F> {
self.pretty_print_region(region)
}
fn print_type(self, ty: Ty<'tcx>) -> Result<Self::Type, Self::Error> {
self.pretty_print_type(ty)
fn print_type(mut self, ty: Ty<'tcx>) -> Result<Self::Type, Self::Error> {
if self.tcx.sess.type_length_limit().value_within_limit(self.printed_type_count) {
self.printed_type_count += 1;
self.pretty_print_type(ty)
} else {
write!(self, "...")?;
Ok(self)
}
}
fn print_dyn_existential(

View file

@ -3,7 +3,50 @@
use crate::ty;
use crate::ty::subst::{GenericArg, GenericArgKind};
use arrayvec::ArrayVec;
use rustc_data_structures::fx::FxHashSet;
use smallvec::{self, SmallVec};
use std::hash::Hash;
/// Small-storage-optimized implementation of a set
/// made specifically for walking type tree.
///
/// Stores elements in a small array up to a certain length
/// and switches to `HashSet` when that length is exceeded.
pub enum MiniSet<T> {
Array(ArrayVec<[T; 8]>),
Set(FxHashSet<T>),
}
impl<T: Eq + Hash + Copy> MiniSet<T> {
/// Creates an empty `MiniSet`.
pub fn new() -> Self {
MiniSet::Array(ArrayVec::new())
}
/// Adds a value to the set.
///
/// If the set did not have this value present, true is returned.
///
/// If the set did have this value present, false is returned.
pub fn insert(&mut self, elem: T) -> bool {
match self {
MiniSet::Array(array) => {
if array.iter().any(|e| *e == elem) {
false
} else {
if array.try_push(elem).is_err() {
let mut set: FxHashSet<T> = array.iter().copied().collect();
set.insert(elem);
*self = MiniSet::Set(set);
}
true
}
}
MiniSet::Set(set) => set.insert(elem),
}
}
}
// The TypeWalker's stack is hot enough that it's worth going to some effort to
// avoid heap allocations.
@ -12,11 +55,20 @@ type TypeWalkerStack<'tcx> = SmallVec<[GenericArg<'tcx>; 8]>;
pub struct TypeWalker<'tcx> {
stack: TypeWalkerStack<'tcx>,
last_subtree: usize,
visited: MiniSet<GenericArg<'tcx>>,
}
/// An iterator for walking the type tree.
///
/// It's very easy to produce a deeply
/// nested type tree with a lot of
/// identical subtrees. In order to work efficiently
/// in this situation walker only visits each type once.
/// It maintains a set of visited types and
/// skips any types that are already there.
impl<'tcx> TypeWalker<'tcx> {
pub fn new(root: GenericArg<'tcx>) -> TypeWalker<'tcx> {
TypeWalker { stack: smallvec![root], last_subtree: 1 }
pub fn new(root: GenericArg<'tcx>) -> Self {
Self { stack: smallvec![root], last_subtree: 1, visited: MiniSet::new() }
}
/// Skips the subtree corresponding to the last type
@ -41,11 +93,15 @@ impl<'tcx> Iterator for TypeWalker<'tcx> {
fn next(&mut self) -> Option<GenericArg<'tcx>> {
debug!("next(): stack={:?}", self.stack);
let next = self.stack.pop()?;
self.last_subtree = self.stack.len();
push_inner(&mut self.stack, next);
debug!("next: stack={:?}", self.stack);
Some(next)
loop {
let next = self.stack.pop()?;
self.last_subtree = self.stack.len();
if self.visited.insert(next) {
push_inner(&mut self.stack, next);
debug!("next: stack={:?}", self.stack);
return Some(next);
}
}
}
}
@ -67,9 +123,17 @@ impl GenericArg<'tcx> {
/// Iterator that walks the immediate children of `self`. Hence
/// `Foo<Bar<i32>, u32>` yields the sequence `[Bar<i32>, u32]`
/// (but not `i32`, like `walk`).
pub fn walk_shallow(self) -> impl Iterator<Item = GenericArg<'tcx>> {
///
/// Iterator only walks items once.
/// It accepts visited set, updates it with all visited types
/// and skips any types that are already there.
pub fn walk_shallow(
self,
visited: &mut MiniSet<GenericArg<'tcx>>,
) -> impl Iterator<Item = GenericArg<'tcx>> {
let mut stack = SmallVec::new();
push_inner(&mut stack, self);
stack.retain(|a| visited.insert(*a));
stack.into_iter()
}
}