Short-cut Sized matching on ADTs
Put a constraint type on every ADT def, such that the ADT def is sized iff the constraint type is, and use that in selection. This ignores types that are obviously sized. This improves typeck performance by ~15%.
This commit is contained in:
parent
3157691f96
commit
73f39a026a
@ -88,6 +88,7 @@ pub enum DepNode<D: Clone + Debug> {
|
||||
ImplOrTraitItems(D),
|
||||
ItemSignature(D),
|
||||
FieldTy(D),
|
||||
SizedConstraint(D),
|
||||
TraitItemDefIds(D),
|
||||
InherentImpls(D),
|
||||
ImplItems(D),
|
||||
@ -193,6 +194,7 @@ impl<D: Clone + Debug> DepNode<D> {
|
||||
ImplOrTraitItems(ref d) => op(d).map(ImplOrTraitItems),
|
||||
ItemSignature(ref d) => op(d).map(ItemSignature),
|
||||
FieldTy(ref d) => op(d).map(FieldTy),
|
||||
SizedConstraint(ref d) => op(d).map(SizedConstraint),
|
||||
TraitItemDefIds(ref d) => op(d).map(TraitItemDefIds),
|
||||
InherentImpls(ref d) => op(d).map(InherentImpls),
|
||||
ImplItems(ref d) => op(d).map(ImplItems),
|
||||
|
@ -13,7 +13,6 @@
|
||||
pub use self::MethodMatchResult::*;
|
||||
pub use self::MethodMatchedData::*;
|
||||
use self::SelectionCandidate::*;
|
||||
use self::BuiltinBoundConditions::*;
|
||||
use self::EvaluationResult::*;
|
||||
|
||||
use super::coherence;
|
||||
@ -232,10 +231,18 @@ struct EvaluatedCandidate<'tcx> {
|
||||
evaluation: EvaluationResult,
|
||||
}
|
||||
|
||||
enum BuiltinBoundConditions<'tcx> {
|
||||
If(ty::Binder<Vec<Ty<'tcx>>>),
|
||||
ParameterBuiltin,
|
||||
AmbiguousBuiltin
|
||||
/// When does the builtin impl for `T: Trait` apply?
|
||||
enum BuiltinImplConditions<'tcx> {
|
||||
/// The impl is conditional on T1,T2,.. : Trait
|
||||
Where(ty::Binder<Vec<Ty<'tcx>>>),
|
||||
/// There is no built-in impl. There may be some other
|
||||
/// candidate (a where-clause or user-defined impl).
|
||||
None,
|
||||
/// There is *no* impl for this, builtin or not. Ignore
|
||||
/// all where-clauses.
|
||||
Never(SelectionError<'tcx>),
|
||||
/// It is unknown whether there is an impl.
|
||||
Ambiguous
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
@ -1608,6 +1615,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
// those will hopefully change to library-defined traits in the
|
||||
// future.
|
||||
|
||||
// HACK: if this returns an error, selection exits without considering
|
||||
// other impls.
|
||||
fn assemble_builtin_bound_candidates<'o>(&mut self,
|
||||
bound: ty::BuiltinBound,
|
||||
obligation: &TraitObligation<'tcx>,
|
||||
@ -1615,25 +1624,25 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
-> Result<(),SelectionError<'tcx>>
|
||||
{
|
||||
match self.builtin_bound(bound, obligation) {
|
||||
Ok(If(..)) => {
|
||||
BuiltinImplConditions::Where(..) => {
|
||||
debug!("builtin_bound: bound={:?}",
|
||||
bound);
|
||||
candidates.vec.push(BuiltinCandidate(bound));
|
||||
Ok(())
|
||||
}
|
||||
Ok(ParameterBuiltin) => { Ok(()) }
|
||||
Ok(AmbiguousBuiltin) => {
|
||||
BuiltinImplConditions::None => { Ok(()) }
|
||||
BuiltinImplConditions::Ambiguous => {
|
||||
debug!("assemble_builtin_bound_candidates: ambiguous builtin");
|
||||
Ok(candidates.ambiguous = true)
|
||||
}
|
||||
Err(e) => { Err(e) }
|
||||
BuiltinImplConditions::Never(e) => { Err(e) }
|
||||
}
|
||||
}
|
||||
|
||||
fn builtin_bound(&mut self,
|
||||
bound: ty::BuiltinBound,
|
||||
obligation: &TraitObligation<'tcx>)
|
||||
-> Result<BuiltinBoundConditions<'tcx>,SelectionError<'tcx>>
|
||||
-> BuiltinImplConditions<'tcx>
|
||||
{
|
||||
// Note: these tests operate on types that may contain bound
|
||||
// regions. To be proper, we ought to skolemize here, but we
|
||||
@ -1641,6 +1650,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
// confirmation step.
|
||||
|
||||
let self_ty = self.infcx.shallow_resolve(obligation.predicate.0.self_ty());
|
||||
|
||||
let always = BuiltinImplConditions::Where(ty::Binder(Vec::new()));
|
||||
let never = BuiltinImplConditions::Never(Unimplemented);
|
||||
|
||||
return match self_ty.sty {
|
||||
ty::TyInfer(ty::IntVar(_)) |
|
||||
ty::TyInfer(ty::FloatVar(_)) |
|
||||
@ -1652,14 +1665,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
ty::TyFnPtr(_) |
|
||||
ty::TyChar => {
|
||||
// safe for everything
|
||||
ok_if(Vec::new())
|
||||
always
|
||||
}
|
||||
|
||||
ty::TyBox(_) => { // Box<T>
|
||||
match bound {
|
||||
ty::BoundCopy => Err(Unimplemented),
|
||||
|
||||
ty::BoundSized => ok_if(Vec::new()),
|
||||
ty::BoundCopy => never,
|
||||
ty::BoundSized => always,
|
||||
|
||||
ty::BoundSync | ty::BoundSend => {
|
||||
bug!("Send/Sync shouldn't occur in builtin_bounds()");
|
||||
@ -1669,7 +1681,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
|
||||
ty::TyRawPtr(..) => { // *const T, *mut T
|
||||
match bound {
|
||||
ty::BoundCopy | ty::BoundSized => ok_if(Vec::new()),
|
||||
ty::BoundCopy | ty::BoundSized => always,
|
||||
|
||||
ty::BoundSync | ty::BoundSend => {
|
||||
bug!("Send/Sync shouldn't occur in builtin_bounds()");
|
||||
@ -1679,10 +1691,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
|
||||
ty::TyTrait(ref data) => {
|
||||
match bound {
|
||||
ty::BoundSized => Err(Unimplemented),
|
||||
ty::BoundSized => never,
|
||||
ty::BoundCopy => {
|
||||
// FIXME(#32963): bit-rot fungus infestation
|
||||
if data.bounds.builtin_bounds.contains(&bound) {
|
||||
ok_if(Vec::new())
|
||||
always
|
||||
} else {
|
||||
// Recursively check all supertraits to find out if any further
|
||||
// bounds are required and thus we must fulfill.
|
||||
@ -1692,11 +1705,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
let copy_def_id = obligation.predicate.def_id();
|
||||
for tr in util::supertraits(self.tcx(), principal) {
|
||||
if tr.def_id() == copy_def_id {
|
||||
return ok_if(Vec::new())
|
||||
return always
|
||||
}
|
||||
}
|
||||
|
||||
Err(Unimplemented)
|
||||
never
|
||||
}
|
||||
}
|
||||
ty::BoundSync | ty::BoundSend => {
|
||||
@ -1711,14 +1724,14 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
ty::BoundCopy => {
|
||||
match mutbl {
|
||||
// &mut T is affine and hence never `Copy`
|
||||
hir::MutMutable => Err(Unimplemented),
|
||||
hir::MutMutable => never,
|
||||
|
||||
// &T is always copyable
|
||||
hir::MutImmutable => ok_if(Vec::new()),
|
||||
hir::MutImmutable => always
|
||||
}
|
||||
}
|
||||
|
||||
ty::BoundSized => ok_if(Vec::new()),
|
||||
ty::BoundSized => always,
|
||||
|
||||
ty::BoundSync | ty::BoundSend => {
|
||||
bug!("Send/Sync shouldn't occur in builtin_bounds()");
|
||||
@ -1730,7 +1743,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
// [T; n]
|
||||
match bound {
|
||||
ty::BoundCopy => ok_if(vec![element_ty]),
|
||||
ty::BoundSized => ok_if(Vec::new()),
|
||||
ty::BoundSized => always,
|
||||
ty::BoundSync | ty::BoundSend => {
|
||||
bug!("Send/Sync shouldn't occur in builtin_bounds()");
|
||||
}
|
||||
@ -1743,46 +1756,42 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
bug!("Send/Sync shouldn't occur in builtin_bounds()");
|
||||
}
|
||||
|
||||
ty::BoundCopy | ty::BoundSized => Err(Unimplemented),
|
||||
ty::BoundCopy | ty::BoundSized => never
|
||||
}
|
||||
}
|
||||
|
||||
// (T1, ..., Tn) -- meets any bound that all of T1...Tn meet
|
||||
ty::TyTuple(ref tys) => ok_if(tys.clone()),
|
||||
|
||||
ty::TyClosure(_, ref substs) => {
|
||||
// FIXME -- This case is tricky. In the case of by-ref
|
||||
// closures particularly, we need the results of
|
||||
// inference to decide how to reflect the type of each
|
||||
// upvar (the upvar may have type `T`, but the runtime
|
||||
// type could be `&mut`, `&`, or just `T`). For now,
|
||||
// though, we'll do this unsoundly and assume that all
|
||||
// captures are by value. Really what we ought to do
|
||||
// is reserve judgement and then intertwine this
|
||||
// analysis with closure inference.
|
||||
ty::TyClosure(..) => {
|
||||
match bound {
|
||||
ty::BoundSync | ty::BoundSend => {
|
||||
bug!("Send/Sync shouldn't occur in builtin_bounds()");
|
||||
}
|
||||
|
||||
// Unboxed closures shouldn't be
|
||||
// implicitly copyable
|
||||
if bound == ty::BoundCopy {
|
||||
return Ok(ParameterBuiltin);
|
||||
ty::BoundCopy => never,
|
||||
ty::BoundSized => always
|
||||
}
|
||||
|
||||
// Upvars are always local variables or references to
|
||||
// local variables, and local variables cannot be
|
||||
// unsized, so the closure struct as a whole must be
|
||||
// Sized.
|
||||
if bound == ty::BoundSized {
|
||||
return ok_if(Vec::new());
|
||||
}
|
||||
|
||||
ok_if(substs.upvar_tys.clone())
|
||||
}
|
||||
|
||||
ty::TyStruct(def, substs) | ty::TyEnum(def, substs) => {
|
||||
let types: Vec<Ty> = def.all_fields().map(|f| {
|
||||
f.ty(self.tcx(), substs)
|
||||
}).collect();
|
||||
nominal(bound, types)
|
||||
match bound {
|
||||
// Fallback to whatever user-defined impls exist in this case.
|
||||
ty::BoundCopy => BuiltinImplConditions::None,
|
||||
|
||||
// Sized if all the component types are sized.
|
||||
ty::BoundSized => {
|
||||
let sized_crit = def.sized_constraint(self.tcx());
|
||||
if sized_crit == self.tcx().types.bool {
|
||||
always
|
||||
} else {
|
||||
ok_if(vec![sized_crit.subst(self.tcx(), substs)])
|
||||
}
|
||||
}
|
||||
|
||||
// Shouldn't be coming through here.
|
||||
ty::BoundSend | ty::BoundSync => bug!(),
|
||||
}
|
||||
}
|
||||
|
||||
ty::TyProjection(_) | ty::TyParam(_) => {
|
||||
@ -1790,7 +1799,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
// particular bound if there is a where clause telling
|
||||
// us that it does, and that case is handled by
|
||||
// `assemble_candidates_from_caller_bounds()`.
|
||||
Ok(ParameterBuiltin)
|
||||
BuiltinImplConditions::None
|
||||
}
|
||||
|
||||
ty::TyInfer(ty::TyVar(_)) => {
|
||||
@ -1798,10 +1807,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
// applicable impls and so forth, depending on what
|
||||
// those type variables wind up being bound to.
|
||||
debug!("assemble_builtin_bound_candidates: ambiguous builtin");
|
||||
Ok(AmbiguousBuiltin)
|
||||
BuiltinImplConditions::Ambiguous
|
||||
}
|
||||
|
||||
ty::TyError => ok_if(Vec::new()),
|
||||
ty::TyError => always,
|
||||
|
||||
ty::TyInfer(ty::FreshTy(_))
|
||||
| ty::TyInfer(ty::FreshIntTy(_))
|
||||
@ -1811,26 +1820,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
}
|
||||
};
|
||||
|
||||
fn ok_if<'tcx>(v: Vec<Ty<'tcx>>)
|
||||
-> Result<BuiltinBoundConditions<'tcx>, SelectionError<'tcx>> {
|
||||
Ok(If(ty::Binder(v)))
|
||||
}
|
||||
|
||||
fn nominal<'cx, 'tcx>(bound: ty::BuiltinBound,
|
||||
types: Vec<Ty<'tcx>>)
|
||||
-> Result<BuiltinBoundConditions<'tcx>, SelectionError<'tcx>>
|
||||
{
|
||||
// First check for markers and other nonsense.
|
||||
match bound {
|
||||
// Fallback to whatever user-defined impls exist in this case.
|
||||
ty::BoundCopy => Ok(ParameterBuiltin),
|
||||
|
||||
// Sized if all the component types are sized.
|
||||
ty::BoundSized => ok_if(types),
|
||||
|
||||
// Shouldn't be coming through here.
|
||||
ty::BoundSend | ty::BoundSync => bug!(),
|
||||
}
|
||||
fn ok_if<'tcx>(v: Vec<Ty<'tcx>>) -> BuiltinImplConditions<'tcx> {
|
||||
BuiltinImplConditions::Where(ty::Binder(v))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1999,7 +1990,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
match candidate {
|
||||
BuiltinCandidate(builtin_bound) => {
|
||||
Ok(VtableBuiltin(
|
||||
self.confirm_builtin_candidate(obligation, builtin_bound)?))
|
||||
self.confirm_builtin_candidate(obligation, builtin_bound)))
|
||||
}
|
||||
|
||||
ParamCandidate(param) => {
|
||||
@ -2100,18 +2091,18 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
fn confirm_builtin_candidate(&mut self,
|
||||
obligation: &TraitObligation<'tcx>,
|
||||
bound: ty::BuiltinBound)
|
||||
-> Result<VtableBuiltinData<PredicateObligation<'tcx>>,
|
||||
SelectionError<'tcx>>
|
||||
-> VtableBuiltinData<PredicateObligation<'tcx>>
|
||||
{
|
||||
debug!("confirm_builtin_candidate({:?})",
|
||||
obligation);
|
||||
|
||||
match self.builtin_bound(bound, obligation)? {
|
||||
If(nested) => Ok(self.vtable_builtin_data(obligation, bound, nested)),
|
||||
AmbiguousBuiltin | ParameterBuiltin => {
|
||||
match self.builtin_bound(bound, obligation) {
|
||||
BuiltinImplConditions::Where(nested) =>
|
||||
self.vtable_builtin_data(obligation, bound, nested),
|
||||
_ => {
|
||||
span_bug!(
|
||||
obligation.cause.span,
|
||||
"builtin bound for {:?} was ambig",
|
||||
"confiriming builtin impl for {:?} where none exists",
|
||||
obligation);
|
||||
}
|
||||
}
|
||||
|
@ -1498,6 +1498,7 @@ pub struct AdtDefData<'tcx, 'container: 'tcx> {
|
||||
pub variants: Vec<VariantDefData<'tcx, 'container>>,
|
||||
destructor: Cell<Option<DefId>>,
|
||||
flags: Cell<AdtFlags>,
|
||||
sized_constraint: ivar::TyIVar<'tcx, 'container>,
|
||||
}
|
||||
|
||||
impl<'tcx, 'container> PartialEq for AdtDefData<'tcx, 'container> {
|
||||
@ -1575,7 +1576,8 @@ impl<'tcx, 'container> AdtDefData<'tcx, 'container> {
|
||||
did: did,
|
||||
variants: variants,
|
||||
flags: Cell::new(flags),
|
||||
destructor: Cell::new(None)
|
||||
destructor: Cell::new(None),
|
||||
sized_constraint: ivar::TyIVar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1716,6 +1718,153 @@ impl<'tcx, 'container> AdtDefData<'tcx, 'container> {
|
||||
None => NoDtor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a simpler type such that `Self: Sized` if and only
|
||||
/// if that type is Sized, or `TyErr` if this type is recursive.
|
||||
pub fn sized_constraint(&self, tcx: &ty::TyCtxt<'tcx>) -> Ty<'tcx> {
|
||||
let dep_node = DepNode::SizedConstraint(self.did);
|
||||
match self.sized_constraint.get(dep_node) {
|
||||
None => {
|
||||
let this = tcx.lookup_adt_def_master(self.did);
|
||||
this.calculate_sized_constraint_inner(tcx, &mut Vec::new());
|
||||
self.sized_constraint.unwrap(dep_node)
|
||||
}
|
||||
Some(ty) => ty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> AdtDefData<'tcx, 'tcx> {
|
||||
fn sized_constraint_for_tys<TYS>(
|
||||
&'tcx self,
|
||||
tcx: &ty::TyCtxt<'tcx>,
|
||||
stack: &mut Vec<AdtDefMaster<'tcx>>,
|
||||
tys: TYS
|
||||
) -> Ty<'tcx>
|
||||
where TYS: IntoIterator<Item=Ty<'tcx>>
|
||||
{
|
||||
let tys : Vec<_> = tys.into_iter()
|
||||
.map(|ty| self.sized_constraint_for_ty(tcx, stack, ty))
|
||||
.filter(|ty| *ty != tcx.types.bool)
|
||||
.collect();
|
||||
|
||||
match tys.len() {
|
||||
0 => tcx.types.bool,
|
||||
1 => tys[0],
|
||||
_ => tcx.mk_tup(tys)
|
||||
}
|
||||
}
|
||||
fn sized_constraint_for_ty(
|
||||
&'tcx self,
|
||||
tcx: &ty::TyCtxt<'tcx>,
|
||||
stack: &mut Vec<AdtDefMaster<'tcx>>,
|
||||
ty: Ty<'tcx>
|
||||
) -> Ty<'tcx> {
|
||||
let result = match ty.sty {
|
||||
TyBool | TyChar | TyInt(..) | TyUint(..) | TyFloat(..) |
|
||||
TyBox(..) | TyRawPtr(..) | TyRef(..) | TyFnDef(..) | TyFnPtr(_) |
|
||||
TyArray(..) | TyClosure(..) => {
|
||||
// these are always sized - return a primitive
|
||||
tcx.types.bool
|
||||
}
|
||||
|
||||
TyStr | TyTrait(..) | TySlice(_) => {
|
||||
// these are never sized - return the target type
|
||||
ty
|
||||
}
|
||||
|
||||
TyTuple(ref tys) => {
|
||||
self.sized_constraint_for_tys(tcx, stack, tys.iter().cloned())
|
||||
}
|
||||
|
||||
TyEnum(adt, substs) | TyStruct(adt, substs) => {
|
||||
// recursive case
|
||||
let adt = tcx.lookup_adt_def_master(adt.did);
|
||||
adt.calculate_sized_constraint_inner(tcx, stack);
|
||||
let adt_ty =
|
||||
adt.sized_constraint
|
||||
.unwrap(DepNode::SizedConstraint(adt.did))
|
||||
.subst(tcx, substs);
|
||||
debug!("sized_constraint_for_ty({:?}) intermediate = {:?}",
|
||||
ty, adt_ty);
|
||||
self.sized_constraint_for_ty(tcx, stack, adt_ty)
|
||||
}
|
||||
|
||||
TyProjection(..) => {
|
||||
// must calculate explicitly.
|
||||
// FIXME: consider special-casing always-Sized projections
|
||||
ty
|
||||
}
|
||||
|
||||
TyParam(..) => {
|
||||
let sized_trait = match tcx.lang_items.sized_trait() {
|
||||
Some(x) => x,
|
||||
_ => return ty
|
||||
};
|
||||
let sized_predicate = Binder(TraitRef {
|
||||
def_id: sized_trait,
|
||||
substs: tcx.mk_substs(Substs::new_trait(
|
||||
vec![], vec![], ty
|
||||
))
|
||||
}).to_predicate();
|
||||
let predicates = tcx.lookup_predicates(self.did).predicates;
|
||||
if predicates.into_iter().any(|p| p == sized_predicate) {
|
||||
tcx.types.bool
|
||||
} else {
|
||||
ty
|
||||
}
|
||||
}
|
||||
|
||||
TyInfer(..) | TyError => {
|
||||
bug!("unexpected type `{:?}` in sized_constraint_for_ty",
|
||||
ty)
|
||||
}
|
||||
};
|
||||
debug!("sized_constraint_for_ty({:?}) = {:?}", ty, result);
|
||||
result
|
||||
}
|
||||
|
||||
/// Calculates the Sized-constraint. This replaces all always-Sized
|
||||
/// types with bool. I could have made the TyIVar an Option, but that
|
||||
/// would have been so much code.
|
||||
fn calculate_sized_constraint_inner(&'tcx self, tcx: &ty::TyCtxt<'tcx>,
|
||||
stack: &mut Vec<AdtDefMaster<'tcx>>)
|
||||
{
|
||||
|
||||
let dep_node = DepNode::SizedConstraint(self.did);
|
||||
|
||||
if self.sized_constraint.get(dep_node).is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if stack.contains(&self) {
|
||||
debug!("calculate_sized_constraint: {:?} is recursive", self);
|
||||
self.sized_constraint.fulfill(dep_node, tcx.types.err);
|
||||
return;
|
||||
}
|
||||
|
||||
stack.push(self);
|
||||
let ty = self.sized_constraint_for_tys(
|
||||
tcx, stack,
|
||||
self.variants.iter().flat_map(|v| {
|
||||
v.fields.last()
|
||||
}).map(|f| f.unsubst_ty())
|
||||
);
|
||||
|
||||
let self_ = stack.pop().unwrap();
|
||||
assert_eq!(self_, self);
|
||||
|
||||
match self.sized_constraint.get(dep_node) {
|
||||
Some(old_ty) => {
|
||||
debug!("calculate_sized_constraint: {:?} recurred", self);
|
||||
assert_eq!(old_ty, tcx.types.err)
|
||||
}
|
||||
None => {
|
||||
debug!("calculate_sized_constraint: {:?} => {:?}", self, ty);
|
||||
self.sized_constraint.fulfill(dep_node, ty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx, 'container> VariantDefData<'tcx, 'container> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user