Skip to content

Commit 88d8ba5

Browse files
committed
Auto merge of #22028 - nikomatsakis:issue-22019-caching, r=aturon
Simplify cache selection by just using the local cache whenever there are any where-clauses at all. This seems to be the simplest possible rule and will (hopefully!) put an end to these annoying "cache leak" bugs. Fixes #22019. r? @aturon
2 parents 94c06a1 + acaad3a commit 88d8ba5

File tree

3 files changed

+60
-114
lines changed

3 files changed

+60
-114
lines changed

src/librustc/middle/traits/doc.rs

+6-82
Original file line numberDiff line numberDiff line change
@@ -434,87 +434,11 @@ attached to the `ParameterEnvironment` and the global cache attached
434434
to the `tcx`. We use the local cache whenever the result might depend
435435
on the where clauses that are in scope. The determination of which
436436
cache to use is done by the method `pick_candidate_cache` in
437-
`select.rs`.
438-
439-
There are two cases where we currently use the local cache. The
440-
current rules are probably more conservative than necessary.
441-
442-
### Trait references that involve parameter types
443-
444-
The most obvious case where you need the local environment is
445-
when the trait reference includes parameter types. For example,
446-
consider the following function:
447-
448-
impl<T> Vec<T> {
449-
fn foo(x: T)
450-
where T : Foo
451-
{ ... }
452-
453-
fn bar(x: T)
454-
{ ... }
455-
}
456-
457-
If there is an obligation `T : Foo`, or `int : Bar<T>`, or whatever,
458-
clearly the results from `foo` and `bar` are potentially different,
459-
since the set of where clauses in scope are different.
460-
461-
### Trait references with unbound variables when where clauses are in scope
462-
463-
There is another less obvious interaction which involves unbound variables
464-
where *only* where clauses are in scope (no impls). This manifested as
465-
issue #18209 (`run-pass/trait-cache-issue-18209.rs`). Consider
466-
this snippet:
467-
468-
```
469-
pub trait Foo {
470-
fn load_from() -> Box<Self>;
471-
fn load() -> Box<Self> {
472-
Foo::load_from()
473-
}
474-
}
475-
```
476-
477-
The default method will incur an obligation `$0 : Foo` from the call
478-
to `load_from`. If there are no impls, this can be eagerly resolved to
479-
`VtableParam(Self : Foo)` and cached. Because the trait reference
480-
doesn't involve any parameters types (only the resolution does), this
481-
result was stored in the global cache, causing later calls to
482-
`Foo::load_from()` to get nonsense.
483-
484-
To fix this, we always use the local cache if there are unbound
485-
variables and where clauses in scope. This is more conservative than
486-
necessary as far as I can tell. However, it still seems to be a simple
487-
rule and I observe ~99% hit rate on rustc, so it doesn't seem to hurt
488-
us in particular.
489-
490-
Here is an example of the kind of subtle case that I would be worried
491-
about with a more complex rule (although this particular case works
492-
out ok). Imagine the trait reference doesn't directly reference a
493-
where clause, but the where clause plays a role in the winnowing
494-
phase. Something like this:
495-
496-
```
497-
pub trait Foo<T> { ... }
498-
pub trait Bar { ... }
499-
impl<U,T:Bar> Foo<U> for T { ... } // Impl A
500-
impl Foo<char> for uint { ... } // Impl B
501-
```
502-
503-
Now, in some function, we have no where clauses in scope, and we have
504-
an obligation `$1 : Foo<$0>`. We might then conclude that `$0=char`
505-
and `$1=uint`: this is because for impl A to apply, `uint:Bar` would
506-
have to hold, and we know it does not or else the coherence check
507-
would have failed. So we might enter into our global cache: `$1 :
508-
Foo<$0> => Impl B`. Then we come along in a different scope, where a
509-
generic type `A` is around with the bound `A:Bar`. Now suddenly the
510-
impl is viable.
511-
512-
The flaw in this imaginary DOOMSDAY SCENARIO is that we would not
513-
currently conclude that `$1 : Foo<$0>` implies that `$0 == uint` and
514-
`$1 == char`, even though it is true that (absent type parameters)
515-
there is no other type the user could enter. However, it is not
516-
*completely* implausible that we *could* draw this conclusion in the
517-
future; we wouldn't have to guess types, in particular, we could be
518-
led by the impls.
437+
`select.rs`. At the moment, we use a very simple, conservative rule:
438+
if there are any where-clauses in scope, then we use the local cache.
439+
We used to try and draw finer-grained distinctions, but that led to a
440+
serious of annoying and weird bugs like #22019 and #18290. This simple
441+
rule seems to be pretty clearly safe and also still retains a very
442+
high hit rate (~95% when compiling rustc).
519443
520444
*/

src/librustc/middle/traits/select.rs

+13-32
Original file line numberDiff line numberDiff line change
@@ -705,14 +705,17 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
705705
Ok(Some(candidate))
706706
}
707707

708-
fn pick_candidate_cache(&self,
709-
cache_fresh_trait_pred: &ty::PolyTraitPredicate<'tcx>)
710-
-> &SelectionCache<'tcx>
711-
{
712-
// High-level idea: we have to decide whether to consult the
713-
// cache that is specific to this scope, or to consult the
714-
// global cache. We want the cache that is specific to this
715-
// scope whenever where clauses might affect the result.
708+
fn pick_candidate_cache(&self) -> &SelectionCache<'tcx> {
709+
// If there are any where-clauses in scope, then we always use
710+
// a cache local to this particular scope. Otherwise, we
711+
// switch to a global cache. We used to try and draw
712+
// finer-grained distinctions, but that led to a serious of
713+
// annoying and weird bugs like #22019 and #18290. This simple
714+
// rule seems to be pretty clearly safe and also still retains
715+
// a very high hit rate (~95% when compiling rustc).
716+
if !self.param_env().caller_bounds.is_empty() {
717+
return &self.param_env().selection_cache;
718+
}
716719

717720
// Avoid using the master cache during coherence and just rely
718721
// on the local cache. This effectively disables caching
@@ -725,28 +728,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
725728
return &self.param_env().selection_cache;
726729
}
727730

728-
// If the trait refers to any parameters in scope, then use
729-
// the cache of the param-environment.
730-
if
731-
cache_fresh_trait_pred.0.input_types().iter().any(
732-
|&t| ty::type_has_self(t) || ty::type_has_params(t))
733-
{
734-
return &self.param_env().selection_cache;
735-
}
736-
737-
// If the trait refers to unbound type variables, and there
738-
// are where clauses in scope, then use the local environment.
739-
// If there are no where clauses in scope, which is a very
740-
// common case, then we can use the global environment.
741-
// See the discussion in doc.rs for more details.
742-
if
743-
!self.param_env().caller_bounds.is_empty() &&
744-
cache_fresh_trait_pred.0.input_types().iter().any(
745-
|&t| ty::type_has_ty_infer(t))
746-
{
747-
return &self.param_env().selection_cache;
748-
}
749-
750731
// Otherwise, we can use the global cache.
751732
&self.tcx().selection_cache
752733
}
@@ -755,7 +736,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
755736
cache_fresh_trait_pred: &ty::PolyTraitPredicate<'tcx>)
756737
-> Option<SelectionResult<'tcx, SelectionCandidate<'tcx>>>
757738
{
758-
let cache = self.pick_candidate_cache(cache_fresh_trait_pred);
739+
let cache = self.pick_candidate_cache();
759740
let hashmap = cache.hashmap.borrow();
760741
hashmap.get(&cache_fresh_trait_pred.0.trait_ref).map(|c| (*c).clone())
761742
}
@@ -764,7 +745,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
764745
cache_fresh_trait_pred: ty::PolyTraitPredicate<'tcx>,
765746
candidate: SelectionResult<'tcx, SelectionCandidate<'tcx>>)
766747
{
767-
let cache = self.pick_candidate_cache(&cache_fresh_trait_pred);
748+
let cache = self.pick_candidate_cache();
768749
let mut hashmap = cache.hashmap.borrow_mut();
769750
hashmap.insert(cache_fresh_trait_pred.0.trait_ref.clone(), candidate);
770751
}
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Test an issue where global caching was causing free regions from
12+
// distinct scopes to be compared (`'g` and `'h`). The only important
13+
// thing is that compilation succeeds here.
14+
15+
#![allow(missing_copy_implementations)]
16+
#![allow(unused_variables)]
17+
18+
use std::borrow::ToOwned;
19+
20+
pub struct CFGNode;
21+
22+
pub type Node<'a> = &'a CFGNode;
23+
24+
pub trait GraphWalk<'c, N> {
25+
/// Returns all the nodes in this graph.
26+
fn nodes(&'c self) where [N]:ToOwned<Vec<N>>;
27+
}
28+
29+
impl<'g> GraphWalk<'g, Node<'g>> for u32
30+
{
31+
fn nodes(&'g self) where [Node<'g>]:ToOwned<Vec<Node<'g>>>
32+
{ loop { } }
33+
}
34+
35+
impl<'h> GraphWalk<'h, Node<'h>> for u64
36+
{
37+
fn nodes(&'h self) where [Node<'h>]:ToOwned<Vec<Node<'h>>>
38+
{ loop { } }
39+
}
40+
41+
fn main() { }

0 commit comments

Comments
 (0)