Skip to content

Commit b2a4661

Browse files
committedAug 27, 2017
Track closure signatures & kinds in freshened types
This allows caching closure signatures and kinds in the normal selection and evaluation caches, and fixes the exponential worst-case in @remram44's example, which is a part of rust-lang#43787. This improvement is complenentary to rust-lang#43999 - they fix different cases.
1 parent 78e95bb commit b2a4661

File tree

2 files changed

+113
-59
lines changed

2 files changed

+113
-59
lines changed
 

‎src/librustc/infer/freshen.rs

+104-3
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,21 @@
1919
//! fact an unbound type variable, we want the match to be regarded as ambiguous, because depending
2020
//! on what type that type variable is ultimately assigned, the match may or may not succeed.
2121
//!
22+
//! To handle closures, freshened types also have to contain the signature and kind of any
23+
//! closure in the local inference context, as otherwise the cache key might be invalidated.
24+
//! The way this is done is somewhat hacky - the closure signature is appended to the substs,
25+
//! as well as the closure kind "encoded" as a type. Also, special handling is needed when
26+
//! the closure signature contains a reference to the original closure.
27+
//!
2228
//! Note that you should be careful not to allow the output of freshening to leak to the user in
2329
//! error messages or in any other form. Freshening is only really useful as an internal detail.
2430
//!
25-
//! __An important detail concerning regions.__ The freshener also replaces *all* regions with
31+
//! Because of the manipulation required to handle closures, doing arbitrary operations on
32+
//! freshened types is not recommended. However, in addition to doing equality/hash
33+
//! comparisons (for caching), it is possible to do a `ty::_match` operation between
34+
//! 2 freshened types - this works even with the closure encoding.
35+
//!
36+
//! __An important detail concerning regions.__ The freshener also replaces *all* free regions with
2637
//! 'erased. The reason behind this is that, in general, we do not take region relationships into
2738
//! account when making type-overloaded decisions. This is important because of the design of the
2839
//! region inferencer, which is not based on unification but rather on accumulating and then
@@ -33,6 +44,8 @@
3344
use ty::{self, Ty, TyCtxt, TypeFoldable};
3445
use ty::fold::TypeFolder;
3546
use util::nodemap::FxHashMap;
47+
use hir::def_id::DefId;
48+
3649
use std::collections::hash_map::Entry;
3750

3851
use super::InferCtxt;
@@ -42,6 +55,7 @@ pub struct TypeFreshener<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
4255
infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
4356
freshen_count: u32,
4457
freshen_map: FxHashMap<ty::InferTy, Ty<'tcx>>,
58+
closure_set: Vec<DefId>,
4559
}
4660

4761
impl<'a, 'gcx, 'tcx> TypeFreshener<'a, 'gcx, 'tcx> {
@@ -51,6 +65,7 @@ impl<'a, 'gcx, 'tcx> TypeFreshener<'a, 'gcx, 'tcx> {
5165
infcx,
5266
freshen_count: 0,
5367
freshen_map: FxHashMap(),
68+
closure_set: vec![],
5469
}
5570
}
5671

@@ -76,6 +91,16 @@ impl<'a, 'gcx, 'tcx> TypeFreshener<'a, 'gcx, 'tcx> {
7691
}
7792
}
7893
}
94+
95+
fn next_fresh<F>(&mut self,
96+
freshener: F)
97+
-> Ty<'tcx>
98+
where F: FnOnce(u32) -> ty::InferTy,
99+
{
100+
let index = self.freshen_count;
101+
self.freshen_count += 1;
102+
self.infcx.tcx.mk_infer(freshener(index))
103+
}
79104
}
80105

81106
impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for TypeFreshener<'a, 'gcx, 'tcx> {
@@ -105,7 +130,8 @@ impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for TypeFreshener<'a, 'gcx, 'tcx> {
105130
}
106131

107132
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
108-
if !t.needs_infer() && !t.has_erasable_regions() {
133+
if !t.needs_infer() && !t.has_erasable_regions() &&
134+
!(t.has_closure_types() && self.infcx.in_progress_tables.is_some()) {
109135
return t;
110136
}
111137

@@ -150,6 +176,82 @@ impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for TypeFreshener<'a, 'gcx, 'tcx> {
150176
t
151177
}
152178

179+
ty::TyClosure(def_id, substs) => {
180+
let closure_in_progress = self.infcx.in_progress_tables.map_or(false, |tables| {
181+
tcx.hir.as_local_node_id(def_id).map_or(false, |closure_id| {
182+
tables.borrow().local_id_root ==
183+
Some(DefId::local(tcx.hir.node_to_hir_id(closure_id).owner))
184+
})
185+
});
186+
187+
if !closure_in_progress {
188+
// If this closure belongs to another infcx, its kind etc. were
189+
// fully inferred and its signature/kind are exactly what's listed
190+
// in its infcx. So we don't need to add the markers for them.
191+
return t.super_fold_with(self);
192+
}
193+
194+
// We are encoding a closure in progress. Because we want our freshening
195+
// key to contain all inference information needed to make sense of our
196+
// value, we need to encode the closure signature and kind. The way
197+
// we do that is to add them as 2 variables to the closure substs,
198+
// basically because it's there (and nobody cares about adding extra stuff
199+
// to substs).
200+
//
201+
// This means the "freshened" closure substs ends up looking like
202+
// fresh_substs = [PARENT_SUBSTS* ; UPVARS* ; SIG_MARKER ; KIND_MARKER]
203+
204+
let closure_sig_marker = if self.closure_set.contains(&def_id) {
205+
// We found the closure def-id within its own signature. Just
206+
// leave a new freshened type - any matching operations would
207+
// have found and compared the exterior closure already to
208+
// get here.
209+
//
210+
// In that case, we already know what the signature would
211+
// be - the parent closure on the stack already contains a
212+
// "copy" of the signature, so there is no reason to encode
213+
// it again for injectivity. Just use a fresh type variable
214+
// to make everything comparable.
215+
//
216+
// For example (closure kinds omitted for clarity)
217+
// t=[closure FOO sig=[closure BAR sig=[closure FOO ..]]]
218+
// Would get encoded to
219+
// t=[closure FOO sig=[closure BAR sig=[closure FOO sig=$0]]]
220+
//
221+
// and we can decode by having
222+
// $0=[closure BAR {sig doesn't exist in decode}]
223+
// and get
224+
// t=[closure FOO]
225+
// sig[FOO] = [closure BAR]
226+
// sig[BAR] = [closure FOO]
227+
self.next_fresh(ty::FreshTy)
228+
} else {
229+
self.closure_set.push(def_id);
230+
let closure_sig = self.infcx.fn_sig(def_id);
231+
let closure_sig_marker = tcx.mk_fn_ptr(closure_sig.fold_with(self));
232+
self.closure_set.pop();
233+
closure_sig_marker
234+
};
235+
236+
// HACK: use a "random" integer type to mark the kind. Because different
237+
// closure kinds shouldn't get unified during selection, the "subtyping"
238+
// relationship (where any kind is better than no kind) shouldn't
239+
// matter here, just that the types are different.
240+
let closure_kind = self.infcx.closure_kind(def_id);
241+
let closure_kind_marker = match closure_kind {
242+
None => tcx.types.i8,
243+
Some(ty::ClosureKind::Fn) => tcx.types.i16,
244+
Some(ty::ClosureKind::FnMut) => tcx.types.i32,
245+
Some(ty::ClosureKind::FnOnce) => tcx.types.i64,
246+
};
247+
248+
let params = tcx.mk_substs(
249+
substs.substs.iter().map(|k| k.fold_with(self)).chain(
250+
[closure_sig_marker, closure_kind_marker].iter().cloned().map(From::from)
251+
));
252+
tcx.mk_closure(def_id, params)
253+
}
254+
153255
ty::TyBool |
154256
ty::TyChar |
155257
ty::TyInt(..) |
@@ -165,7 +267,6 @@ impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for TypeFreshener<'a, 'gcx, 'tcx> {
165267
ty::TyFnDef(..) |
166268
ty::TyFnPtr(_) |
167269
ty::TyDynamic(..) |
168-
ty::TyClosure(..) |
169270
ty::TyNever |
170271
ty::TyTuple(..) |
171272
ty::TyProjection(..) |

‎src/librustc/traits/select.rs

+9-56
Original file line numberDiff line numberDiff line change
@@ -888,14 +888,9 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
888888
dep_node: DepNodeIndex,
889889
result: EvaluationResult)
890890
{
891-
// Avoid caching results that depend on more than just the trait-ref:
892-
// The stack can create recursion, and closure signatures
893-
// being yet uninferred can create "spurious" EvaluatedToAmbig
894-
// and EvaluatedToOk.
895-
if result.is_stack_dependent() ||
896-
((result == EvaluatedToAmbig || result == EvaluatedToOk)
897-
&& trait_ref.has_closure_types())
898-
{
891+
// Avoid caching results that depend on more than just the trait-ref
892+
// - the stack can create recursion.
893+
if result.is_stack_dependent() {
899894
return;
900895
}
901896

@@ -955,15 +950,12 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
955950
this.candidate_from_obligation_no_cache(stack)
956951
});
957952

958-
if self.should_update_candidate_cache(&cache_fresh_trait_pred, &candidate) {
959-
debug!("CACHE MISS: SELECT({:?})={:?}",
960-
cache_fresh_trait_pred, candidate);
961-
self.insert_candidate_cache(stack.obligation.param_env,
962-
cache_fresh_trait_pred,
963-
dep_node,
964-
candidate.clone());
965-
}
966-
953+
debug!("CACHE MISS: SELECT({:?})={:?}",
954+
cache_fresh_trait_pred, candidate);
955+
self.insert_candidate_cache(stack.obligation.param_env,
956+
cache_fresh_trait_pred,
957+
dep_node,
958+
candidate.clone());
967959
candidate
968960
}
969961

@@ -1203,45 +1195,6 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
12031195
.insert(trait_ref, WithDepNode::new(dep_node, candidate));
12041196
}
12051197

1206-
fn should_update_candidate_cache(&mut self,
1207-
cache_fresh_trait_pred: &ty::PolyTraitPredicate<'tcx>,
1208-
candidate: &SelectionResult<'tcx, SelectionCandidate<'tcx>>)
1209-
-> bool
1210-
{
1211-
// In general, it's a good idea to cache results, even
1212-
// ambiguous ones, to save us some trouble later. But we have
1213-
// to be careful not to cache results that could be
1214-
// invalidated later by advances in inference. Normally, this
1215-
// is not an issue, because any inference variables whose
1216-
// types are not yet bound are "freshened" in the cache key,
1217-
// which means that if we later get the same request once that
1218-
// type variable IS bound, we'll have a different cache key.
1219-
// For example, if we have `Vec<_#0t> : Foo`, and `_#0t` is
1220-
// not yet known, we may cache the result as `None`. But if
1221-
// later `_#0t` is bound to `Bar`, then when we freshen we'll
1222-
// have `Vec<Bar> : Foo` as the cache key.
1223-
//
1224-
// HOWEVER, it CAN happen that we get an ambiguity result in
1225-
// one particular case around closures where the cache key
1226-
// would not change. That is when the precise types of the
1227-
// upvars that a closure references have not yet been figured
1228-
// out (i.e., because it is not yet known if they are captured
1229-
// by ref, and if by ref, what kind of ref). In these cases,
1230-
// when matching a builtin bound, we will yield back an
1231-
// ambiguous result. But the *cache key* is just the closure type,
1232-
// it doesn't capture the state of the upvar computation.
1233-
//
1234-
// To avoid this trap, just don't cache ambiguous results if
1235-
// the self-type contains no inference byproducts (that really
1236-
// shouldn't happen in other circumstances anyway, given
1237-
// coherence).
1238-
1239-
match *candidate {
1240-
Ok(Some(_)) | Err(_) => true,
1241-
Ok(None) => cache_fresh_trait_pred.has_infer_types()
1242-
}
1243-
}
1244-
12451198
fn assemble_candidates<'o>(&mut self,
12461199
stack: &TraitObligationStack<'o, 'tcx>)
12471200
-> Result<SelectionCandidateSet<'tcx>, SelectionError<'tcx>>

0 commit comments

Comments
 (0)
Please sign in to comment.