Skip to content

Commit de06faf

Browse files
committed
Use local cache when there are unbound type variables and where clauses in scope.
Fixes rust-lang#18209.
1 parent d44ea72 commit de06faf

File tree

4 files changed

+212
-59
lines changed

4 files changed

+212
-59
lines changed

src/librustc/middle/traits/doc.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,4 +279,132 @@ selection. This is because it must account for the transformed self
279279
type of the receiver and various other complications. The procedure is
280280
described in `select.rs` in the "METHOD MATCHING" section.
281281
282+
# Caching and subtle considerations therewith
283+
284+
In general we attempt to cache the results of trait selection. This
285+
is a somewhat complex process. Part of the reason for this is that we
286+
want to be able to cache results even when all the types in the trait
287+
reference are not fully known. In that case, it may happen that the
288+
trait selection process is also influencing type variables, so we have
289+
to be able to not only cache the *result* of the selection process,
290+
but *reply* its effects on the type variables.
291+
292+
## An example
293+
294+
The high-level idea of how the cache works is that we first replace
295+
all unbound inference variables with skolemized versions. Therefore,
296+
if we had a trait reference `uint : Foo<$1>`, where `$n` is an unbound
297+
inference variable, we might replace it with `uint : Foo<%0>`, where
298+
`%n` is a skolemized type. We would then look this up in the cache.
299+
If we found a hit, the hit would tell us the immediate next step to
300+
take in the selection process: i.e., apply impl #22, or apply where
301+
clause `X : Foo<Y>`. Let's say in this case there is no hit.
302+
Therefore, we search through impls and where clauses and so forth, and
303+
we come to the conclusion that the only possible impl is this one,
304+
with def-id 22:
305+
306+
impl Foo<int> for uint { ... } // Impl #22
307+
308+
We would then record in the cache `uint : Foo<%0> ==>
309+
ImplCandidate(22)`. Next we would confirm `ImplCandidate(22)`, which
310+
would (as a side-effect) unify `$1` with `int`.
311+
312+
Now, at some later time, we might come along and see a `uint :
313+
Foo<$3>`. When skolemized, this would yield `uint : Foo<%0>`, just as
314+
before, and hence the cache lookup would succeed, yielding
315+
`ImplCandidate(22)`. We would confirm `ImplCandidate(22)` which would
316+
(as a side-effect) unify `$3` with `int`.
317+
318+
## Where clauses and the local vs global cache
319+
320+
One subtle interaction is that the results of trait lookup will vary
321+
depending on what where clauses are in scope. Therefore, we actually
322+
have *two* caches, a local and a global cache. The local cache is
323+
attached to the `ParameterEnvironment` and the global cache attached
324+
to the `tcx`. We use the local cache whenever the result might depend
325+
on the where clauses that are in scope. The determination of which
326+
cache to use is done by the method `pick_candidate_cache` in
327+
`select.rs`.
328+
329+
There are two cases where we currently use the local cache. The
330+
current rules are probably more conservative than necessary.
331+
332+
### Trait references that involve parameter types
333+
334+
The most obvious case where you need the local environment is
335+
when the trait reference includes parameter types. For example,
336+
consider the following function:
337+
338+
impl<T> Vec<T> {
339+
fn foo(x: T)
340+
where T : Foo
341+
{ ... }
342+
343+
fn bar(x: T)
344+
{ ... }
345+
}
346+
347+
If there is an obligation `T : Foo`, or `int : Bar<T>`, or whatever,
348+
clearly the results from `foo` and `bar` are potentially different,
349+
since the set of where clauses in scope are different.
350+
351+
### Trait references with unbound variables when where clauses are in scope
352+
353+
There is another less obvious interaction which involves unbound variables
354+
where *only* where clauses are in scope (no impls). This manifested as
355+
issue #18209 (`run-pass/trait-cache-issue-18209.rs`). Consider
356+
this snippet:
357+
358+
```
359+
pub trait Foo {
360+
fn load_from() -> Box<Self>;
361+
fn load() -> Box<Self> {
362+
Foo::load_from()
363+
}
364+
}
365+
```
366+
367+
The default method will incur an obligation `$0 : Foo` from the call
368+
to `load_from`. If there are no impls, this can be eagerly resolved to
369+
`VtableParam(Self : Foo)` and cached. Because the trait reference
370+
doesn't involve any parameters types (only the resolution does), this
371+
result was stored in the global cache, causing later calls to
372+
`Foo::load_from()` to get nonsense.
373+
374+
To fix this, we always use the local cache if there are unbound
375+
variables and where clauses in scope. This is more conservative than
376+
necessary as far as I can tell. However, it still seems to be a simple
377+
rule and I observe ~99% hit rate on rustc, so it doesn't seem to hurt
378+
us in particular.
379+
380+
Here is an example of the kind of subtle case that I would be worried
381+
about with a more complex rule (although this particular case works
382+
out ok). Imagine the trait reference doesn't directly reference a
383+
where clause, but the where clause plays a role in the winnowing
384+
phase. Something like this:
385+
386+
```
387+
pub trait Foo<T> { ... }
388+
pub trait Bar { ... }
389+
impl<U,T:Bar> Foo<U> for T { ... } // Impl A
390+
impl Foo<char> for uint { ... } // Impl B
391+
```
392+
393+
Now, in some function, we have no where clauses in scope, and we have
394+
an obligation `$1 : Foo<$0>`. We might then conclude that `$0=char`
395+
and `$1=uint`: this is because for impl A to apply, `uint:Bar` would
396+
have to hold, and we know it does not or else the coherence check
397+
would have failed. So we might enter into our global cache: `$1 :
398+
Foo<$0> => Impl B`. Then we come along in a different scope, where a
399+
generic type `A` is around with the bound `A:Bar`. Now suddenly the
400+
impl is viable.
401+
402+
The flaw in this imaginary DOOMSDAY SCENARIO is that we would not
403+
currently conclude that `$1 : Foo<$0>` implies that `$0 == uint` and
404+
`$1 == char`, even though it is true that (absent type parameters)
405+
there is no other type the user could enter. However, it is not
406+
*completely* implausible that we *could* draw this conclusion in the
407+
future; we wouldn't have to guess types, in particular, we could be
408+
led by the impls.
409+
282410
*/

src/librustc/middle/traits/select.rs

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -844,19 +844,36 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
844844
cache_skol_trait_ref: &Rc<ty::TraitRef>)
845845
-> &SelectionCache
846846
{
847+
// High-level idea: we have to decide whether to consult the
848+
// cache that is specific to this scope, or to consult the
849+
// global cache. We want the cache that is specific to this
850+
// scope whenever where clauses might affect the result.
851+
847852
// If the trait refers to any parameters in scope, then use
848-
// the cache of the param-environment. This is because the
849-
// result will depend on the where clauses that are in
850-
// scope. Otherwise, use the generic tcx cache, since the
851-
// result holds across all environments.
853+
// the cache of the param-environment.
852854
if
853855
cache_skol_trait_ref.input_types().iter().any(
854856
|&t| ty::type_has_self(t) || ty::type_has_params(t))
855857
{
856-
&self.param_env.selection_cache
857-
} else {
858-
&self.tcx().selection_cache
858+
return &self.param_env.selection_cache;
859859
}
860+
861+
// If the trait refers to unbound type variables, and there
862+
// are where clauses in scope, then use the local environment.
863+
// If there are no where clauses in scope, which is a very
864+
// common case, then we can use the global environment.
865+
// See the discussion in doc.rs for more details.
866+
if
867+
!self.param_env.caller_obligations.is_empty()
868+
&&
869+
cache_skol_trait_ref.input_types().iter().any(
870+
|&t| ty::type_has_ty_infer(t))
871+
{
872+
return &self.param_env.selection_cache;
873+
}
874+
875+
// Otherwise, we can use the global cache.
876+
&self.tcx().selection_cache
860877
}
861878

862879
fn check_candidate_cache(&mut self,
@@ -1935,26 +1952,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
19351952
util::obligations_for_generics(self.tcx(), cause, recursion_depth,
19361953
&impl_generics, impl_substs)
19371954
}
1938-
1939-
fn contains_skolemized_types(&self,
1940-
ty: ty::t)
1941-
-> bool
1942-
{
1943-
/*!
1944-
* True if the type contains skolemized variables.
1945-
*/
1946-
1947-
let mut found_skol = false;
1948-
1949-
ty::walk_ty(ty, |t| {
1950-
match ty::get(t).sty {
1951-
ty::ty_infer(ty::SkolemizedTy(_)) => { found_skol = true; }
1952-
_ => { }
1953-
}
1954-
});
1955-
1956-
found_skol
1957-
}
19581955
}
19591956

19601957
impl Repr for Candidate {

src/librustc/middle/ty.rs

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -585,18 +585,18 @@ pub struct ctxt<'tcx> {
585585
pub repr_hint_cache: RefCell<DefIdMap<Rc<Vec<attr::ReprAttr>>>>,
586586
}
587587

588-
pub enum tbox_flag {
589-
has_params = 1,
590-
has_self = 2,
591-
needs_infer = 4,
592-
has_regions = 8,
593-
has_ty_err = 16,
594-
has_ty_bot = 32,
595-
596-
// a meta-pub flag: subst may be required if the type has parameters, a self
597-
// type, or references bound regions
598-
needs_subst = 1 | 2 | 8
599-
}
588+
// Flags that we track on types. These flags are propagated upwards
589+
// through the type during type construction, so that we can quickly
590+
// check whether the type has various kinds of types in it without
591+
// recursing over the type itself.
592+
const HAS_PARAMS: uint = 1;
593+
const HAS_SELF: uint = 2;
594+
const HAS_TY_INFER: uint = 4;
595+
const HAS_RE_INFER: uint = 8;
596+
const HAS_REGIONS: uint = 16;
597+
const HAS_TY_ERR: uint = 32;
598+
const HAS_TY_BOT: uint = 64;
599+
const NEEDS_SUBST: uint = HAS_PARAMS | HAS_SELF | HAS_REGIONS;
600600

601601
pub type t_box = &'static t_box_;
602602

@@ -631,15 +631,16 @@ pub fn get(t: t) -> t_box {
631631
}
632632
}
633633

634-
pub fn tbox_has_flag(tb: t_box, flag: tbox_flag) -> bool {
635-
(tb.flags & (flag as uint)) != 0u
634+
fn tbox_has_flag(tb: t_box, flag: uint) -> bool {
635+
(tb.flags & flag) != 0u
636636
}
637637
pub fn type_has_params(t: t) -> bool {
638-
tbox_has_flag(get(t), has_params)
638+
tbox_has_flag(get(t), HAS_PARAMS)
639639
}
640-
pub fn type_has_self(t: t) -> bool { tbox_has_flag(get(t), has_self) }
640+
pub fn type_has_self(t: t) -> bool { tbox_has_flag(get(t), HAS_SELF) }
641+
pub fn type_has_ty_infer(t: t) -> bool { tbox_has_flag(get(t), HAS_TY_INFER) }
641642
pub fn type_needs_infer(t: t) -> bool {
642-
tbox_has_flag(get(t), needs_infer)
643+
tbox_has_flag(get(t), HAS_TY_INFER | HAS_RE_INFER)
643644
}
644645
pub fn type_id(t: t) -> uint { get(t).id }
645646

@@ -910,13 +911,13 @@ mod primitives {
910911
pub static TY_BOT: t_box_ = t_box_ {
911912
sty: super::ty_bot,
912913
id: 16,
913-
flags: super::has_ty_bot as uint,
914+
flags: super::HAS_TY_BOT,
914915
};
915916

916917
pub static TY_ERR: t_box_ = t_box_ {
917918
sty: super::ty_err,
918919
id: 17,
919-
flags: super::has_ty_err as uint,
920+
flags: super::HAS_TY_ERR,
920921
};
921922

922923
pub const LAST_PRIMITIVE_ID: uint = 18;
@@ -1579,9 +1580,9 @@ pub fn mk_t(cx: &ctxt, st: sty) -> t {
15791580

15801581
let mut flags = 0u;
15811582
fn rflags(r: Region) -> uint {
1582-
(has_regions as uint) | {
1583+
HAS_REGIONS | {
15831584
match r {
1584-
ty::ReInfer(_) => needs_infer as uint,
1585+
ty::ReInfer(_) => HAS_RE_INFER,
15851586
_ => 0u
15861587
}
15871588
}
@@ -1610,22 +1611,22 @@ pub fn mk_t(cx: &ctxt, st: sty) -> t {
16101611
&ty_str => {}
16111612
// You might think that we could just return ty_err for
16121613
// any type containing ty_err as a component, and get
1613-
// rid of the has_ty_err flag -- likewise for ty_bot (with
1614+
// rid of the HAS_TY_ERR flag -- likewise for ty_bot (with
16141615
// the exception of function types that return bot).
16151616
// But doing so caused sporadic memory corruption, and
16161617
// neither I (tjc) nor nmatsakis could figure out why,
16171618
// so we're doing it this way.
1618-
&ty_bot => flags |= has_ty_bot as uint,
1619-
&ty_err => flags |= has_ty_err as uint,
1619+
&ty_bot => flags |= HAS_TY_BOT,
1620+
&ty_err => flags |= HAS_TY_ERR,
16201621
&ty_param(ref p) => {
16211622
if p.space == subst::SelfSpace {
1622-
flags |= has_self as uint;
1623+
flags |= HAS_SELF;
16231624
} else {
1624-
flags |= has_params as uint;
1625+
flags |= HAS_PARAMS;
16251626
}
16261627
}
16271628
&ty_unboxed_closure(_, ref region) => flags |= rflags(*region),
1628-
&ty_infer(_) => flags |= needs_infer as uint,
1629+
&ty_infer(_) => flags |= HAS_TY_INFER,
16291630
&ty_enum(_, ref substs) | &ty_struct(_, ref substs) => {
16301631
flags |= sflags(substs);
16311632
}
@@ -1648,7 +1649,7 @@ pub fn mk_t(cx: &ctxt, st: sty) -> t {
16481649
for a in f.sig.inputs.iter() { flags |= get(*a).flags; }
16491650
flags |= get(f.sig.output).flags;
16501651
// T -> _|_ is *not* _|_ !
1651-
flags &= !(has_ty_bot as uint);
1652+
flags &= !HAS_TY_BOT;
16521653
}
16531654
&ty_closure(ref f) => {
16541655
match f.store {
@@ -1660,7 +1661,7 @@ pub fn mk_t(cx: &ctxt, st: sty) -> t {
16601661
for a in f.sig.inputs.iter() { flags |= get(*a).flags; }
16611662
flags |= get(f.sig.output).flags;
16621663
// T -> _|_ is *not* _|_ !
1663-
flags &= !(has_ty_bot as uint);
1664+
flags &= !HAS_TY_BOT;
16641665
flags |= flags_for_bounds(&f.bounds);
16651666
}
16661667
}
@@ -1979,15 +1980,15 @@ impl ItemSubsts {
19791980
pub fn type_is_nil(ty: t) -> bool { get(ty).sty == ty_nil }
19801981

19811982
pub fn type_is_bot(ty: t) -> bool {
1982-
(get(ty).flags & (has_ty_bot as uint)) != 0
1983+
(get(ty).flags & HAS_TY_BOT) != 0
19831984
}
19841985

19851986
pub fn type_is_error(ty: t) -> bool {
1986-
(get(ty).flags & (has_ty_err as uint)) != 0
1987+
(get(ty).flags & HAS_TY_ERR) != 0
19871988
}
19881989

19891990
pub fn type_needs_subst(ty: t) -> bool {
1990-
tbox_has_flag(get(ty), needs_subst)
1991+
tbox_has_flag(get(ty), NEEDS_SUBST)
19911992
}
19921993

19931994
pub fn trait_ref_contains_error(tref: &ty::TraitRef) -> bool {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2014 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 that the cache results from the default method do not pollute
12+
// the cache for the later call in `load()`.
13+
//
14+
// See issue #18209.
15+
16+
pub trait Foo {
17+
fn load_from() -> Box<Self>;
18+
fn load() -> Box<Self> {
19+
Foo::load_from()
20+
}
21+
}
22+
23+
pub fn load<M: Foo>() -> Box<M> {
24+
Foo::load()
25+
}
26+
27+
fn main() { }

0 commit comments

Comments
 (0)