Skip to content

Commit 607eb32

Browse files
committed
trait_sel: skip elaboration of sizedness supertrait
As a performance optimization, skip elaborating the supertraits of `Sized`, and if a `MetaSized` obligation is being checked, then look for a `Sized` predicate in the parameter environment. This makes the `ParamEnv` smaller which should improve compiler performance as it avoids all the iteration over the larger `ParamEnv`.
1 parent 47abf2e commit 607eb32

File tree

16 files changed

+268
-40
lines changed

16 files changed

+268
-40
lines changed

compiler/rustc_next_trait_solver/src/solve/trait_goals.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_type_ir::inherent::*;
66
use rustc_type_ir::lang_items::TraitSolverLangItem;
77
use rustc_type_ir::solve::{CanonicalResponse, SizedTraitKind};
88
use rustc_type_ir::{
9-
self as ty, Interner, Movability, TraitPredicate, TypeVisitableExt as _, TypingMode,
9+
self as ty, Interner, Movability, TraitPredicate, TraitRef, TypeVisitableExt as _, TypingMode,
1010
Upcast as _, elaborate,
1111
};
1212
use tracing::{debug, instrument, trace};
@@ -131,16 +131,29 @@ where
131131
assumption: I::Clause,
132132
) -> Result<(), NoSolution> {
133133
if let Some(trait_clause) = assumption.as_trait_clause() {
134-
if trait_clause.def_id() == goal.predicate.def_id()
135-
&& trait_clause.polarity() == goal.predicate.polarity
136-
{
134+
if trait_clause.polarity() != goal.predicate.polarity {
135+
return Err(NoSolution);
136+
}
137+
138+
if trait_clause.def_id() == goal.predicate.def_id() {
137139
if DeepRejectCtxt::relate_rigid_rigid(ecx.cx()).args_may_unify(
138140
goal.predicate.trait_ref.args,
139141
trait_clause.skip_binder().trait_ref.args,
140142
) {
141143
return Ok(());
142144
}
143145
}
146+
147+
// PERF(sized-hierarchy): Sizedness supertraits aren't elaborated to improve perf, so
148+
// check for a `Sized` subtrait when looking for `MetaSized`. `PointeeSized` bounds
149+
// are syntactic sugar for a lack of bounds so don't need this.
150+
if ecx.cx().is_lang_item(goal.predicate.def_id(), TraitSolverLangItem::MetaSized)
151+
&& ecx.cx().is_lang_item(trait_clause.def_id(), TraitSolverLangItem::Sized)
152+
{
153+
let meta_sized_clause =
154+
trait_predicate_with_def_id(ecx.cx(), trait_clause, goal.predicate.def_id());
155+
return Self::fast_reject_assumption(ecx, goal, meta_sized_clause);
156+
}
144157
}
145158

146159
Err(NoSolution)
@@ -154,6 +167,17 @@ where
154167
) -> QueryResult<I> {
155168
let trait_clause = assumption.as_trait_clause().unwrap();
156169

170+
// PERF(sized-hierarchy): Sizedness supertraits aren't elaborated to improve perf, so
171+
// check for a `Sized` subtrait when looking for `MetaSized`. `PointeeSized` bounds
172+
// are syntactic sugar for a lack of bounds so don't need this.
173+
if ecx.cx().is_lang_item(goal.predicate.def_id(), TraitSolverLangItem::MetaSized)
174+
&& ecx.cx().is_lang_item(trait_clause.def_id(), TraitSolverLangItem::Sized)
175+
{
176+
let meta_sized_clause =
177+
trait_predicate_with_def_id(ecx.cx(), trait_clause, goal.predicate.def_id());
178+
return Self::match_assumption(ecx, goal, meta_sized_clause, then);
179+
}
180+
157181
let assumption_trait_pred = ecx.instantiate_binder_with_infer(trait_clause);
158182
ecx.eq(goal.param_env, goal.predicate.trait_ref, assumption_trait_pred.trait_ref)?;
159183

@@ -817,6 +841,25 @@ where
817841
}
818842
}
819843

844+
/// Small helper function to change the `def_id` of a trait predicate - this is not normally
845+
/// something that you want to do, as different traits will require different args and so making
846+
/// it easy to change the trait is something of a footgun, but it is useful in the narrow
847+
/// circumstance of changing from `MetaSized` to `Sized`, which happens as part of the lazy
848+
/// elaboration of sizedness candidates.
849+
#[inline(always)]
850+
fn trait_predicate_with_def_id<I: Interner>(
851+
cx: I,
852+
clause: ty::Binder<I, ty::TraitPredicate<I>>,
853+
did: I::DefId,
854+
) -> I::Clause {
855+
clause
856+
.map_bound(|c| TraitPredicate {
857+
trait_ref: TraitRef::new_from_args(cx, did, c.trait_ref.args),
858+
polarity: c.polarity,
859+
})
860+
.upcast(cx)
861+
}
862+
820863
impl<D, I> EvalCtxt<'_, D>
821864
where
822865
D: SolverDelegate<Interner = I>,

compiler/rustc_trait_selection/src/traits/coherence.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ fn impl_intersection_has_negative_obligation(
462462
let param_env = infcx.resolve_vars_if_possible(param_env);
463463

464464
util::elaborate(tcx, tcx.predicates_of(impl2_def_id).instantiate(tcx, impl2_header.impl_args))
465+
.elaborate_sized()
465466
.any(|(clause, _)| try_prove_negated_where_clause(infcx, clause, param_env))
466467
}
467468

compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
215215
}
216216

217217
selcx.infcx.probe(|_| {
218+
let bound = util::lazily_elaborate_sizedness_candidate(
219+
selcx.infcx,
220+
obligation,
221+
bound,
222+
);
223+
218224
// We checked the polarity already
219225
match selcx.match_normalize_trait_ref(
220226
obligation,
@@ -259,14 +265,21 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
259265
.caller_bounds()
260266
.iter()
261267
.filter_map(|p| p.as_trait_clause())
262-
// Micro-optimization: filter out predicates relating to different traits.
263-
.filter(|p| p.def_id() == stack.obligation.predicate.def_id())
268+
// Micro-optimization: filter out predicates with different polarities.
264269
.filter(|p| p.polarity() == stack.obligation.predicate.polarity());
265270

266271
let drcx = DeepRejectCtxt::relate_rigid_rigid(self.tcx());
267272
let obligation_args = stack.obligation.predicate.skip_binder().trait_ref.args;
268273
// Keep only those bounds which may apply, and propagate overflow if it occurs.
269274
for bound in bounds {
275+
let bound =
276+
util::lazily_elaborate_sizedness_candidate(self.infcx, stack.obligation, bound);
277+
278+
// Micro-optimization: filter out predicates relating to different traits.
279+
if bound.def_id() != stack.obligation.predicate.def_id() {
280+
continue;
281+
}
282+
270283
let bound_trait_ref = bound.map_bound(|t| t.trait_ref);
271284
if !drcx.args_may_unify(obligation_args, bound_trait_ref.skip_binder().args) {
272285
continue;

compiler/rustc_trait_selection/src/traits/select/confirmation.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
166166
)
167167
.break_value()
168168
.expect("expected to index into clause that exists");
169-
let candidate = candidate_predicate
169+
let candidate_predicate = candidate_predicate
170170
.as_trait_clause()
171-
.expect("projection candidate is not a trait predicate")
172-
.map_bound(|t| t.trait_ref);
171+
.expect("projection candidate is not a trait predicate");
172+
let candidate_predicate =
173+
util::lazily_elaborate_sizedness_candidate(self.infcx, obligation, candidate_predicate);
174+
175+
let candidate = candidate_predicate.map_bound(|t| t.trait_ref);
173176

174177
let candidate = self.infcx.instantiate_binder_with_fresh_vars(
175178
obligation.cause.span,
@@ -226,6 +229,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
226229
) -> PredicateObligations<'tcx> {
227230
debug!(?obligation, ?param, "confirm_param_candidate");
228231

232+
let param = util::lazily_elaborate_sizedness_candidate(
233+
self.infcx,
234+
obligation,
235+
param.upcast(self.infcx.tcx),
236+
)
237+
.map_bound(|p| p.trait_ref);
238+
229239
// During evaluation, we already checked that this
230240
// where-clause trait-ref could be unified with the obligation
231241
// trait-ref. Repeat that unification now without any

compiler/rustc_trait_selection/src/traits/util.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
44
use rustc_hir::LangItem;
55
use rustc_hir::def_id::DefId;
66
use rustc_infer::infer::InferCtxt;
7+
use rustc_infer::traits::PolyTraitObligation;
78
pub use rustc_infer::traits::util::*;
89
use rustc_middle::bug;
10+
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
911
use rustc_middle::ty::{
10-
self, SizedTraitKind, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt,
12+
self, PolyTraitPredicate, SizedTraitKind, TraitPredicate, TraitRef, Ty, TyCtxt, TypeFoldable,
13+
TypeFolder, TypeSuperFoldable, TypeVisitableExt,
1114
};
1215
pub use rustc_next_trait_solver::placeholder::BoundVarReplacer;
1316
use rustc_span::Span;
@@ -382,3 +385,39 @@ pub fn sizedness_fast_path<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tc
382385

383386
false
384387
}
388+
389+
/// To improve performance, sizedness traits are not elaborated and so special-casing is required
390+
/// in the trait solver to find a `Sized` candidate for a `MetaSized` obligation. Returns the
391+
/// predicate to used in the candidate for such a `obligation`, given a `candidate`.
392+
pub(crate) fn lazily_elaborate_sizedness_candidate<'tcx>(
393+
infcx: &InferCtxt<'tcx>,
394+
obligation: &PolyTraitObligation<'tcx>,
395+
candidate: PolyTraitPredicate<'tcx>,
396+
) -> PolyTraitPredicate<'tcx> {
397+
if !infcx.tcx.is_lang_item(obligation.predicate.def_id(), LangItem::MetaSized)
398+
|| !infcx.tcx.is_lang_item(candidate.def_id(), LangItem::Sized)
399+
{
400+
return candidate;
401+
}
402+
403+
if obligation.predicate.polarity() != candidate.polarity() {
404+
return candidate;
405+
}
406+
407+
let drcx = DeepRejectCtxt::relate_rigid_rigid(infcx.tcx);
408+
if !drcx.args_may_unify(
409+
obligation.predicate.skip_binder().trait_ref.args,
410+
candidate.skip_binder().trait_ref.args,
411+
) {
412+
return candidate;
413+
}
414+
415+
candidate.map_bound(|c| TraitPredicate {
416+
trait_ref: TraitRef::new_from_args(
417+
infcx.tcx,
418+
obligation.predicate.def_id(),
419+
c.trait_ref.args,
420+
),
421+
polarity: c.polarity,
422+
})
423+
}

compiler/rustc_type_ir/src/elaborate.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use smallvec::smallvec;
44

55
use crate::data_structures::HashSet;
66
use crate::inherent::*;
7+
use crate::lang_items::TraitSolverLangItem;
78
use crate::outlives::{Component, push_outlives_components};
89
use crate::{self as ty, Interner, Upcast as _};
910

@@ -18,13 +19,20 @@ pub struct Elaborator<I: Interner, O> {
1819
stack: Vec<O>,
1920
visited: HashSet<ty::Binder<I, ty::PredicateKind<I>>>,
2021
mode: Filter,
22+
elaborate_sized: ElaborateSized,
2123
}
2224

2325
enum Filter {
2426
All,
2527
OnlySelf,
2628
}
2729

30+
#[derive(Eq, PartialEq)]
31+
enum ElaborateSized {
32+
Yes,
33+
No,
34+
}
35+
2836
/// Describes how to elaborate an obligation into a sub-obligation.
2937
pub trait Elaboratable<I: Interner> {
3038
fn predicate(&self) -> I::Predicate;
@@ -77,13 +85,19 @@ pub fn elaborate<I: Interner, O: Elaboratable<I>>(
7785
cx: I,
7886
obligations: impl IntoIterator<Item = O>,
7987
) -> Elaborator<I, O> {
80-
let mut elaborator =
81-
Elaborator { cx, stack: Vec::new(), visited: HashSet::default(), mode: Filter::All };
88+
let mut elaborator = Elaborator {
89+
cx,
90+
stack: Vec::new(),
91+
visited: HashSet::default(),
92+
mode: Filter::All,
93+
elaborate_sized: ElaborateSized::No,
94+
};
8295
elaborator.extend_deduped(obligations);
8396
elaborator
8497
}
8598

8699
impl<I: Interner, O: Elaboratable<I>> Elaborator<I, O> {
100+
/// Adds `obligations` to the stack.
87101
fn extend_deduped(&mut self, obligations: impl IntoIterator<Item = O>) {
88102
// Only keep those bounds that we haven't already seen.
89103
// This is necessary to prevent infinite recursion in some
@@ -103,6 +117,13 @@ impl<I: Interner, O: Elaboratable<I>> Elaborator<I, O> {
103117
self
104118
}
105119

120+
/// Start elaborating `Sized` - reqd during coherence checking, normally skipped to improve
121+
/// compiler performance.
122+
pub fn elaborate_sized(mut self) -> Self {
123+
self.elaborate_sized = ElaborateSized::Yes;
124+
self
125+
}
126+
106127
fn elaborate(&mut self, elaboratable: &O) {
107128
let cx = self.cx;
108129

@@ -111,6 +132,19 @@ impl<I: Interner, O: Elaboratable<I>> Elaborator<I, O> {
111132
return;
112133
};
113134

135+
// PERF(sized-hierarchy): To avoid iterating over sizedness supertraits in
136+
// parameter environments, as an optimisation, sizedness supertraits aren't
137+
// elaborated, so check if a `Sized` obligation is being elaborated to a
138+
// `MetaSized` obligation and emit it. Candidate assembly and confirmation
139+
// are modified to check for the `Sized` subtrait when a `MetaSized` obligation
140+
// is present.
141+
if self.elaborate_sized == ElaborateSized::No
142+
&& let Some(did) = clause.as_trait_clause().map(|c| c.def_id())
143+
&& self.cx.is_lang_item(did, TraitSolverLangItem::Sized)
144+
{
145+
return;
146+
}
147+
114148
let bound_clause = clause.kind();
115149
match bound_clause.skip_binder() {
116150
ty::ClauseKind::Trait(data) => {

tests/ui/attributes/dump-preds.stderr

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ LL | type Assoc<P: Eq>: std::ops::Deref<Target = ()>
3636
= note: Binder { value: ProjectionPredicate(AliasTerm { args: [Alias(Projection, AliasTy { args: [Self/#0, T/#1, P/#2], def_id: DefId(..), .. })], def_id: DefId(..), .. }, Term::Ty(())), bound_vars: [] }
3737
= note: Binder { value: TraitPredicate(<<Self as Trait<T>>::Assoc<P> as std::ops::Deref>, polarity:Positive), bound_vars: [] }
3838
= note: Binder { value: TraitPredicate(<<Self as Trait<T>>::Assoc<P> as std::marker::Sized>, polarity:Positive), bound_vars: [] }
39-
= note: Binder { value: TraitPredicate(<<Self as Trait<T>>::Assoc<P> as std::marker::MetaSized>, polarity:Positive), bound_vars: [] }
4039

4140
error: aborting due to 3 previous errors
4241

tests/ui/extern/extern-types-unsized.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ fn main() {
2727

2828
assert_sized::<Bar<A>>();
2929
//~^ ERROR the size for values of type
30+
//~| ERROR the size for values of type
3031

3132
assert_sized::<Bar<Bar<A>>>();
3233
//~^ ERROR the size for values of type
34+
//~| ERROR the size for values of type
3335
}

tests/ui/extern/extern-types-unsized.stderr

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,21 @@ help: consider relaxing the implicit `Sized` restriction
5959
LL | fn assert_sized<T: ?Sized>() {}
6060
| ++++++++
6161

62+
error[E0277]: the size for values of type `A` cannot be known
63+
--> $DIR/extern-types-unsized.rs:28:20
64+
|
65+
LL | assert_sized::<Bar<A>>();
66+
| ^^^^^^ doesn't have a known size
67+
|
68+
= help: the trait `MetaSized` is not implemented for `A`
69+
note: required by a bound in `Bar`
70+
--> $DIR/extern-types-unsized.rs:14:12
71+
|
72+
LL | struct Bar<T: ?Sized> {
73+
| ^ required by this bound in `Bar`
74+
6275
error[E0277]: the size for values of type `A` cannot be known at compilation time
63-
--> $DIR/extern-types-unsized.rs:31:20
76+
--> $DIR/extern-types-unsized.rs:32:20
6477
|
6578
LL | assert_sized::<Bar<Bar<A>>>();
6679
| ^^^^^^^^^^^ doesn't have a size known at compile-time
@@ -81,6 +94,19 @@ help: consider relaxing the implicit `Sized` restriction
8194
LL | fn assert_sized<T: ?Sized>() {}
8295
| ++++++++
8396

84-
error: aborting due to 4 previous errors
97+
error[E0277]: the size for values of type `A` cannot be known
98+
--> $DIR/extern-types-unsized.rs:32:20
99+
|
100+
LL | assert_sized::<Bar<Bar<A>>>();
101+
| ^^^^^^^^^^^ doesn't have a known size
102+
|
103+
= help: the trait `MetaSized` is not implemented for `A`
104+
note: required by a bound in `Bar`
105+
--> $DIR/extern-types-unsized.rs:14:12
106+
|
107+
LL | struct Bar<T: ?Sized> {
108+
| ^ required by this bound in `Bar`
109+
110+
error: aborting due to 6 previous errors
85111

86112
For more information about this error, try `rustc --explain E0277`.

tests/ui/nll/issue-50716.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ trait A {
55
type X: ?Sized;
66
}
77

8-
fn foo<'a, T: 'static>(s: Box<<&'a T as A>::X>) //~ ERROR mismatched types
8+
fn foo<'a, T: 'static>(s: Box<<&'a T as A>::X>) //~ ERROR
99
where
1010
for<'b> &'b T: A,
1111
<&'static T as A>::X: Sized

0 commit comments

Comments
 (0)