Skip to content

Commit

Permalink
Auto merge of #30652 - aturon:specialization, r=nikomatsakis
Browse files Browse the repository at this point in the history
Implement RFC 1210: impl specialization

This PR implements [impl specialization](rust-lang/rfcs#1210),
carefully following the proposal laid out in the RFC.

The implementation covers the bulk of the RFC. The remaining gaps I know of are:

- no checking for lifetime-dependent specialization (a soundness hole);
- no `default impl` yet;
- no support for `default` with associated consts;

I plan to cover these gaps in follow-up PRs, as per @nikomatsakis's preference.

The basic strategy is to build up a *specialization graph* during
coherence checking. Insertion into the graph locates the right place
to put an impl in the specialization hierarchy; if there is no right
place (due to partial overlap but no containment), you get an overlap
error. Specialization is consulted when selecting an impl (of course),
and the graph is consulted when propagating defaults down the
specialization hierarchy.

You might expect that the specialization graph would be used during
selection -- i.e., when actually performing specialization. This is
not done for two reasons:

- It's merely an optimization: given a set of candidates that apply,
  we can determine the most specialized one by comparing them directly
  for specialization, rather than consulting the graph. Given that we
  also cache the results of selection, the benefit of this
  optimization is questionable.

- To build the specialization graph in the first place, we need to use
  selection (because we need to determine whether one impl specializes
  another). Dealing with this reentrancy would require some additional
  mode switch for selection. Given that there seems to be no strong
  reason to use the graph anyway, we stick with a simpler approach in
  selection, and use the graph only for propagating default
  implementations.

Trait impl selection can succeed even when multiple impls can apply,
as long as they are part of the same specialization family. In that
case, it returns a *single* impl on success -- this is the most
specialized impl *known* to apply. However, if there are any inference
variables in play, the returned impl may not be the actual impl we
will use at trans time. Thus, we take special care to avoid projecting
associated types unless either (1) the associated type does not use
`default` and thus cannot be overridden or (2) all input types are
known concretely.

r? @nikomatsakis
  • Loading branch information
bors committed Mar 15, 2016
2 parents 6d215fe + 6562eeb commit 9ca7561
Show file tree
Hide file tree
Showing 115 changed files with 3,029 additions and 620 deletions.
9 changes: 4 additions & 5 deletions src/librustc/dep_graph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ could invalidate work done for other items. So, for example:
not shared state, because if it changes it does not itself
invalidate other functions (though it may be that it causes new
monomorphizations to occur, but that's handled independently).

Put another way: if the HIR for an item changes, we are going to
recompile that item for sure. But we need the dep tracking map to tell
us what *else* we have to recompile. Shared state is anything that is
Expand Down Expand Up @@ -177,7 +177,7 @@ reads from `item`, there would be missing edges in the graph:
| ^
| |
+---------------------------------+ // added by `visit_all_items_in_krate`

In particular, the edge from `Hir(X)` to `ItemSignature(X)` is only
present because we called `read` ourselves when entering the `ItemSignature(X)`
task.
Expand Down Expand Up @@ -273,8 +273,8 @@ should not exist. In contrast, using the memoized helper, you get:
... -> MapVariant(key) -> A
|
+----------> B
which is much cleaner.

which is much cleaner.

**Be aware though that the closure is executed with `MapVariant(key)`
pushed onto the stack as the current task!** That means that you must
Expand Down Expand Up @@ -387,4 +387,3 @@ RUST_DEP_GRAPH_FILTER='Hir&foo -> TypeckItemBody & bar'
This will dump out all the nodes that lead from `Hir(foo)` to
`TypeckItemBody(bar)`, from which you can (hopefully) see the source
of the erroneous edge.

7 changes: 5 additions & 2 deletions src/librustc/middle/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use middle::expr_use_visitor as euv;
use middle::infer;
use middle::mem_categorization::{cmt};
use middle::pat_util::*;
use middle::traits::ProjectionMode;
use middle::ty::*;
use middle::ty;
use std::cmp::Ordering;
Expand Down Expand Up @@ -1101,7 +1102,8 @@ fn check_legality_of_move_bindings(cx: &MatchCheckCtxt,
//FIXME: (@jroesch) this code should be floated up as well
let infcx = infer::new_infer_ctxt(cx.tcx,
&cx.tcx.tables,
Some(cx.param_env.clone()));
Some(cx.param_env.clone()),
ProjectionMode::AnyFinal);
if infcx.type_moves_by_default(pat_ty, pat.span) {
check_move(p, sub.as_ref().map(|p| &**p));
}
Expand Down Expand Up @@ -1133,7 +1135,8 @@ fn check_for_mutation_in_guard<'a, 'tcx>(cx: &'a MatchCheckCtxt<'a, 'tcx>,

let infcx = infer::new_infer_ctxt(cx.tcx,
&cx.tcx.tables,
Some(checker.cx.param_env.clone()));
Some(checker.cx.param_env.clone()),
ProjectionMode::AnyFinal);

let mut visitor = ExprUseVisitor::new(&mut checker, &infcx);
visitor.walk_expr(guard);
Expand Down
8 changes: 7 additions & 1 deletion src/librustc/middle/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use middle::def_id::DefId;
use middle::pat_util::def_to_path;
use middle::ty::{self, Ty, TyCtxt};
use middle::ty::util::IntTypeExt;
use middle::traits::ProjectionMode;
use middle::astconv_util::ast_ty_to_prim_ty;
use util::nodemap::NodeMap;

Expand Down Expand Up @@ -1049,7 +1050,7 @@ fn resolve_trait_associated_const<'a, 'tcx: 'a>(tcx: &'a TyCtxt<'tcx>,
trait_ref);

tcx.populate_implementations_for_trait_if_necessary(trait_ref.def_id());
let infcx = infer::new_infer_ctxt(tcx, &tcx.tables, None);
let infcx = infer::new_infer_ctxt(tcx, &tcx.tables, None, ProjectionMode::AnyFinal);

let mut selcx = traits::SelectionContext::new(&infcx);
let obligation = traits::Obligation::new(traits::ObligationCause::dummy(),
Expand All @@ -1067,6 +1068,11 @@ fn resolve_trait_associated_const<'a, 'tcx: 'a>(tcx: &'a TyCtxt<'tcx>,
}
};

// NOTE: this code does not currently account for specialization, but when
// it does so, it should hook into the ProjectionMode to determine when the
// constant should resolve; this will also require plumbing through to this
// function whether we are in "trans mode" to pick the right ProjectionMode
// when constructing the inference context above.
match selection {
traits::VtableImpl(ref impl_data) => {
match tcx.associated_consts(impl_data.impl_def_id)
Expand Down
2 changes: 2 additions & 0 deletions src/librustc/middle/cstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ pub trait CrateStore<'tcx> : Any {
-> Option<ty::adjustment::CustomCoerceUnsized>;
fn associated_consts(&self, tcx: &TyCtxt<'tcx>, def: DefId)
-> Vec<Rc<ty::AssociatedConst<'tcx>>>;
fn impl_parent(&self, impl_def_id: DefId) -> Option<DefId>;

// trait/impl-item info
fn trait_of_item(&self, tcx: &TyCtxt<'tcx>, def_id: DefId)
Expand Down Expand Up @@ -346,6 +347,7 @@ impl<'tcx> CrateStore<'tcx> for DummyCrateStore {
{ unimplemented!() }
fn associated_consts(&self, tcx: &TyCtxt<'tcx>, def: DefId)
-> Vec<Rc<ty::AssociatedConst<'tcx>>> { unimplemented!() }
fn impl_parent(&self, def: DefId) -> Option<DefId> { unimplemented!() }

// trait/impl-item info
fn trait_of_item(&self, tcx: &TyCtxt<'tcx>, def_id: DefId)
Expand Down
26 changes: 18 additions & 8 deletions src/librustc/middle/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use middle::region::CodeExtent;
use middle::subst;
use middle::subst::Substs;
use middle::subst::Subst;
use middle::traits;
use middle::traits::{self, ProjectionMode};
use middle::ty::adjustment;
use middle::ty::{TyVid, IntVid, FloatVid};
use middle::ty::{self, Ty, TyCtxt};
Expand Down Expand Up @@ -99,6 +99,11 @@ pub struct InferCtxt<'a, 'tcx: 'a> {
// directly.
normalize: bool,

// Sadly, the behavior of projection varies a bit depending on the
// stage of compilation. The specifics are given in the
// documentation for `ProjectionMode`.
projection_mode: ProjectionMode,

err_count_on_creation: usize,
}

Expand Down Expand Up @@ -354,7 +359,8 @@ pub fn fixup_err_to_string(f: FixupError) -> String {

pub fn new_infer_ctxt<'a, 'tcx>(tcx: &'a TyCtxt<'tcx>,
tables: &'a RefCell<ty::Tables<'tcx>>,
param_env: Option<ty::ParameterEnvironment<'a, 'tcx>>)
param_env: Option<ty::ParameterEnvironment<'a, 'tcx>>,
projection_mode: ProjectionMode)
-> InferCtxt<'a, 'tcx> {
InferCtxt {
tcx: tcx,
Expand All @@ -366,14 +372,16 @@ pub fn new_infer_ctxt<'a, 'tcx>(tcx: &'a TyCtxt<'tcx>,
parameter_environment: param_env.unwrap_or(tcx.empty_parameter_environment()),
reported_trait_errors: RefCell::new(FnvHashSet()),
normalize: false,
projection_mode: projection_mode,
err_count_on_creation: tcx.sess.err_count()
}
}

pub fn normalizing_infer_ctxt<'a, 'tcx>(tcx: &'a TyCtxt<'tcx>,
tables: &'a RefCell<ty::Tables<'tcx>>)
tables: &'a RefCell<ty::Tables<'tcx>>,
projection_mode: ProjectionMode)
-> InferCtxt<'a, 'tcx> {
let mut infcx = new_infer_ctxt(tcx, tables, None);
let mut infcx = new_infer_ctxt(tcx, tables, None, projection_mode);
infcx.normalize = true;
infcx
}
Expand Down Expand Up @@ -514,6 +522,7 @@ pub struct CombinedSnapshot {
region_vars_snapshot: RegionSnapshot,
}

// NOTE: Callable from trans only!
pub fn normalize_associated_type<'tcx,T>(tcx: &TyCtxt<'tcx>, value: &T) -> T
where T : TypeFoldable<'tcx>
{
Expand All @@ -525,7 +534,7 @@ pub fn normalize_associated_type<'tcx,T>(tcx: &TyCtxt<'tcx>, value: &T) -> T
return value;
}

let infcx = new_infer_ctxt(tcx, &tcx.tables, None);
let infcx = new_infer_ctxt(tcx, &tcx.tables, None, ProjectionMode::Any);
let mut selcx = traits::SelectionContext::new(&infcx);
let cause = traits::ObligationCause::dummy();
let traits::Normalized { value: result, obligations } =
Expand Down Expand Up @@ -593,6 +602,10 @@ pub fn drain_fulfillment_cx<'a,'tcx,T>(infcx: &InferCtxt<'a,'tcx>,
}

impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
pub fn projection_mode(&self) -> ProjectionMode {
self.projection_mode
}

pub fn freshen<T:TypeFoldable<'tcx>>(&self, t: T) -> T {
t.fold_with(&mut self.freshener())
}
Expand Down Expand Up @@ -1025,8 +1038,6 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
substs: &mut Substs<'tcx>,
defs: &[ty::TypeParameterDef<'tcx>]) {

let mut vars = Vec::with_capacity(defs.len());

for def in defs.iter() {
let default = def.default.map(|default| {
type_variable::Default {
Expand All @@ -1038,7 +1049,6 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {

let ty_var = self.next_ty_var_with_default(default);
substs.types.push(space, ty_var);
vars.push(ty_var)
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/librustc/middle/subst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,15 @@ impl<'tcx> Substs<'tcx> {
Substs { types: types, regions: regions }
}

pub fn with_method_from_subst(self, other: &Substs<'tcx>) -> Substs<'tcx> {
let Substs { types, regions } = self;
let types = types.with_slice(FnSpace, other.types.get_slice(FnSpace));
let regions = regions.map(|r| {
r.with_slice(FnSpace, other.regions().get_slice(FnSpace))
});
Substs { types: types, regions: regions }
}

/// Creates a trait-ref out of this substs, ignoring the FnSpace substs
pub fn to_trait_ref(&self, tcx: &TyCtxt<'tcx>, trait_id: DefId)
-> ty::TraitRef<'tcx> {
Expand Down
40 changes: 40 additions & 0 deletions src/librustc/middle/traits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,43 @@ We used to try and draw finer-grained distinctions, but that led to a
serious of annoying and weird bugs like #22019 and #18290. This simple
rule seems to be pretty clearly safe and also still retains a very
high hit rate (~95% when compiling rustc).

# Specialization

Defined in the `specialize` module.

The basic strategy is to build up a *specialization graph* during
coherence checking. Insertion into the graph locates the right place
to put an impl in the specialization hierarchy; if there is no right
place (due to partial overlap but no containment), you get an overlap
error. Specialization is consulted when selecting an impl (of course),
and the graph is consulted when propagating defaults down the
specialization hierarchy.

You might expect that the specialization graph would be used during
selection -- i.e., when actually performing specialization. This is
not done for two reasons:

- It's merely an optimization: given a set of candidates that apply,
we can determine the most specialized one by comparing them directly
for specialization, rather than consulting the graph. Given that we
also cache the results of selection, the benefit of this
optimization is questionable.

- To build the specialization graph in the first place, we need to use
selection (because we need to determine whether one impl specializes
another). Dealing with this reentrancy would require some additional
mode switch for selection. Given that there seems to be no strong
reason to use the graph anyway, we stick with a simpler approach in
selection, and use the graph only for propagating default
implementations.

Trait impl selection can succeed even when multiple impls can apply,
as long as they are part of the same specialization family. In that
case, it returns a *single* impl on success -- this is the most
specialized impl *known* to apply. However, if there are any inference
variables in play, the returned impl may not be the actual impl we
will use at trans time. Thus, we take special care to avoid projecting
associated types unless either (1) the associated type does not use
`default` and thus cannot be overridden or (2) all input types are
known concretely.
7 changes: 3 additions & 4 deletions src/librustc/middle/traits/coherence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@

//! See `README.md` for high-level documentation
use super::{SelectionContext};
use super::{Obligation, ObligationCause};
use super::{SelectionContext, Obligation, ObligationCause};

use middle::cstore::LOCAL_CRATE;
use middle::def_id::DefId;
Expand All @@ -23,8 +22,8 @@ use syntax::codemap::DUMMY_SP;
#[derive(Copy, Clone)]
struct InferIsLocal(bool);

/// If there are types that satisfy both impls, returns an `ImplTy`
/// with those types substituted (by updating the given `infcx`)
/// If there are types that satisfy both impls, returns a suitably-freshened
/// `ImplHeader` with those types substituted
pub fn overlapping_impls<'cx, 'tcx>(infcx: &InferCtxt<'cx, 'tcx>,
impl1_def_id: DefId,
impl2_def_id: DefId)
Expand Down
16 changes: 9 additions & 7 deletions src/librustc/middle/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,18 @@ pub use self::coherence::orphan_check;
pub use self::coherence::overlapping_impls;
pub use self::coherence::OrphanCheckErr;
pub use self::fulfill::{FulfillmentContext, GlobalFulfilledPredicates, RegionObligation};
pub use self::project::MismatchedProjectionTypes;
pub use self::project::normalize;
pub use self::project::Normalized;
pub use self::project::{MismatchedProjectionTypes, ProjectionMode};
pub use self::project::{normalize, Normalized};
pub use self::object_safety::is_object_safe;
pub use self::object_safety::astconv_object_safety_violations;
pub use self::object_safety::object_safety_violations;
pub use self::object_safety::ObjectSafetyViolation;
pub use self::object_safety::MethodViolationCode;
pub use self::object_safety::is_vtable_safe_method;
pub use self::select::EvaluationCache;
pub use self::select::SelectionContext;
pub use self::select::SelectionCache;
pub use self::select::{EvaluationCache, SelectionContext, SelectionCache};
pub use self::select::{MethodMatchResult, MethodMatched, MethodAmbiguous, MethodDidNotMatch};
pub use self::select::{MethodMatchedData}; // intentionally don't export variants
pub use self::specialize::{Overlap, specialization_graph, specializes, translate_substs};
pub use self::util::elaborate_predicates;
pub use self::util::get_vtable_index_of_object_method;
pub use self::util::trait_ref_for_builtin_bound;
Expand All @@ -67,6 +65,7 @@ mod fulfill;
mod project;
mod object_safety;
mod select;
mod specialize;
mod structural_impls;
mod util;

Expand Down Expand Up @@ -434,7 +433,10 @@ pub fn normalize_param_env_or_error<'a,'tcx>(unnormalized_env: ty::ParameterEnvi

let elaborated_env = unnormalized_env.with_caller_bounds(predicates);

let infcx = infer::new_infer_ctxt(tcx, &tcx.tables, Some(elaborated_env));
let infcx = infer::new_infer_ctxt(tcx,
&tcx.tables,
Some(elaborated_env),
ProjectionMode::AnyFinal);
let predicates = match fully_normalize(&infcx,
cause,
&infcx.parameter_environment.caller_bounds) {
Expand Down
Loading

0 comments on commit 9ca7561

Please sign in to comment.