Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 8a8e13d

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 deab445 commit 8a8e13d

File tree

17 files changed

+275
-62
lines changed

17 files changed

+275
-62
lines changed

compiler/rustc_next_trait_solver/src/solve/trait_goals.rs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,31 +133,52 @@ where
133133
then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
134134
) -> Result<Candidate<I>, NoSolution> {
135135
if let Some(trait_clause) = assumption.as_trait_clause() {
136-
if trait_clause.def_id() == goal.predicate.def_id()
137-
&& trait_clause.polarity() == goal.predicate.polarity
138-
{
136+
let goal_did = goal.predicate.def_id();
137+
let trait_clause_did = trait_clause.def_id();
138+
139+
if trait_clause.polarity() != goal.predicate.polarity {
140+
return Err(NoSolution);
141+
}
142+
143+
if trait_clause_did == goal_did {
139144
if !DeepRejectCtxt::relate_rigid_rigid(ecx.cx()).args_may_unify(
140145
goal.predicate.trait_ref.args,
141146
trait_clause.skip_binder().trait_ref.args,
142147
) {
143148
return Err(NoSolution);
144149
}
145150

146-
ecx.probe_trait_candidate(source).enter(|ecx| {
151+
return ecx.probe_trait_candidate(source).enter(|ecx| {
147152
let assumption_trait_pred = ecx.instantiate_binder_with_infer(trait_clause);
148153
ecx.eq(
149154
goal.param_env,
150155
goal.predicate.trait_ref,
151156
assumption_trait_pred.trait_ref,
152157
)?;
153158
then(ecx)
154-
})
155-
} else {
156-
Err(NoSolution)
159+
});
160+
}
161+
162+
// PERF(sized-hierarchy): Sizedness supertraits aren't elaborated to improve perf, so
163+
// check for a `Sized` subtrait when looking for `MetaSized`. `PointeeSized` bounds
164+
// are syntactic sugar for a lack of bounds so don't need this.
165+
if ecx.cx().is_lang_item(goal_did, TraitSolverLangItem::MetaSized)
166+
&& ecx.cx().is_lang_item(trait_clause_did, TraitSolverLangItem::Sized)
167+
{
168+
let trait_ref = trait_clause
169+
.self_ty()
170+
.map_bound(|ty| ty::TraitRef::new(ecx.cx(), goal_did, [ty]));
171+
return Self::probe_and_match_goal_against_assumption(
172+
ecx,
173+
source,
174+
goal,
175+
trait_ref.upcast(ecx.cx()),
176+
then,
177+
);
157178
}
158-
} else {
159-
Err(NoSolution)
160179
}
180+
181+
Err(NoSolution)
161182
}
162183

163184
fn consider_auto_trait_candidate(

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,26 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
187187
let mut distinct_normalized_bounds = FxHashSet::default();
188188
let _ = self.for_each_item_bound::<!>(
189189
placeholder_trait_predicate.self_ty(),
190-
|selcx, bound, idx| {
191-
let Some(bound) = bound.as_trait_clause() else {
190+
|selcx, clause, idx| {
191+
let Some(bound) = clause.as_trait_clause() else {
192192
return ControlFlow::Continue(());
193193
};
194194
if bound.polarity() != placeholder_trait_predicate.polarity {
195195
return ControlFlow::Continue(());
196196
}
197197

198198
selcx.infcx.probe(|_| {
199+
if util::unelaborated_sizedness_candidate(selcx.infcx, obligation, [bound])
200+
.is_some()
201+
{
202+
// As `ProjectionCandidate` takes an index and not a predicate, a
203+
// "corrected" `<Projection>: MetaSized` candidate cannot be created,
204+
// so instead keep the index of the `<Projection>: Sized` predicate and
205+
// correct for this in confirmation.
206+
candidates.vec.push(ProjectionCandidate(idx));
207+
return;
208+
}
209+
199210
// We checked the polarity already
200211
match selcx.match_normalize_trait_ref(
201212
obligation,
@@ -234,6 +245,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
234245
) -> Result<(), SelectionError<'tcx>> {
235246
debug!(?stack.obligation);
236247

248+
if let Some(bound) =
249+
util::unelaborated_sizedness_candidate_from_obligation(self.infcx, stack.obligation)
250+
{
251+
candidates.vec.push(ParamCandidate(bound));
252+
return Ok(());
253+
}
254+
237255
let bounds = stack
238256
.obligation
239257
.param_env

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
170170
.expect("projection candidate is not a trait predicate")
171171
.map_bound(|t| t.trait_ref);
172172

173+
// During candidate assembly, if a unelaborated sizedness candidate would have been added,
174+
// then the index of the `<Projection>: Sized` predicate that satisfied this
175+
// `<Projection>: MetaSized` obligation was used - this means the candidate won't match
176+
// the obligation below, so exit early.
177+
if util::unelaborated_sizedness_candidate(
178+
self.infcx,
179+
obligation,
180+
[candidate.upcast(self.infcx.tcx)],
181+
)
182+
.is_some()
183+
{
184+
return Ok(PredicateObligations::new());
185+
}
186+
173187
let candidate = self.infcx.instantiate_binder_with_fresh_vars(
174188
obligation.cause.span,
175189
HigherRankedType,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2706,6 +2706,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
27062706
HigherRankedType,
27072707
poly_trait_ref,
27082708
);
2709+
27092710
self.infcx
27102711
.at(&obligation.cause, obligation.param_env)
27112712
.eq(DefineOpaqueTypes::No, predicate.trait_ref, trait_ref)

compiler/rustc_trait_selection/src/traits/util.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ 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;
910
use rustc_middle::ty::{
10-
self, SizedTraitKind, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt,
11+
self, PolyTraitPredicate, SizedTraitKind, Ty, TyCtxt, TypeFoldable, TypeFolder,
12+
TypeSuperFoldable, TypeVisitableExt,
1113
};
1214
use rustc_span::Span;
1315
use smallvec::{SmallVec, smallvec};
@@ -531,3 +533,48 @@ pub fn sizedness_fast_path<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tc
531533

532534
false
533535
}
536+
537+
/// To improve performance, sizedness traits are not elaborated and so special-casing is required
538+
/// in the trait solver to find a `Sized` candidate for a `MetaSized` obligation. Returns the
539+
/// predicate to used in the candidate for such a `obligation`, given `candidates`.
540+
pub(crate) fn unelaborated_sizedness_candidate<'tcx>(
541+
infcx: &InferCtxt<'tcx>,
542+
obligation: &PolyTraitObligation<'tcx>,
543+
candidates: impl IntoIterator<Item = PolyTraitPredicate<'tcx>>,
544+
) -> Option<PolyTraitPredicate<'tcx>> {
545+
use crate::infer::InferCtxtExt;
546+
if !infcx.tcx.is_lang_item(obligation.predicate.def_id(), LangItem::MetaSized) {
547+
return None;
548+
}
549+
550+
let has = candidates.into_iter().any(|c| {
551+
if !infcx.tcx.is_lang_item(c.def_id(), LangItem::Sized) {
552+
return false;
553+
}
554+
555+
let expected_self_ty = obligation.self_ty();
556+
let found_self_ty = c.self_ty();
557+
obligation.predicate.polarity() == c.polarity()
558+
&& expected_self_ty.bound_vars() == found_self_ty.bound_vars()
559+
&& {
560+
let expected_self_ty =
561+
infcx.tcx.instantiate_bound_regions_with_erased(expected_self_ty);
562+
let found_self_ty = infcx.tcx.instantiate_bound_regions_with_erased(found_self_ty);
563+
infcx.can_eq(obligation.param_env, expected_self_ty, found_self_ty)
564+
}
565+
});
566+
567+
if has { Some(obligation.predicate) } else { None }
568+
}
569+
570+
/// See `unelaborated_sizedness_candidate`, but uses `Obligation::param_env` as `candidates`.
571+
pub(crate) fn unelaborated_sizedness_candidate_from_obligation<'tcx>(
572+
infcx: &InferCtxt<'tcx>,
573+
obligation: &PolyTraitObligation<'tcx>,
574+
) -> Option<PolyTraitPredicate<'tcx>> {
575+
unelaborated_sizedness_candidate(
576+
infcx,
577+
obligation,
578+
obligation.param_env.caller_bounds().iter().filter_map(|c| c.as_trait_clause()),
579+
)
580+
}

compiler/rustc_type_ir/src/elaborate.rs

Lines changed: 45 additions & 5 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

@@ -79,20 +80,55 @@ pub fn elaborate<I: Interner, O: Elaboratable<I>>(
7980
) -> Elaborator<I, O> {
8081
let mut elaborator =
8182
Elaborator { cx, stack: Vec::new(), visited: HashSet::default(), mode: Filter::All };
82-
elaborator.extend_deduped(obligations);
83+
elaborator.extend_deduped(None, obligations);
8384
elaborator
8485
}
8586

8687
impl<I: Interner, O: Elaboratable<I>> Elaborator<I, O> {
87-
fn extend_deduped(&mut self, obligations: impl IntoIterator<Item = O>) {
88+
/// Adds `obligations` to the stack. `current_clause` is the clause which was elaborated to
89+
/// produce these obligations.
90+
fn extend_deduped(
91+
&mut self,
92+
current_clause: Option<I::Clause>,
93+
obligations: impl IntoIterator<Item = O>,
94+
) {
8895
// Only keep those bounds that we haven't already seen.
8996
// This is necessary to prevent infinite recursion in some
9097
// cases. One common case is when people define
9198
// `trait Sized: Sized { }` rather than `trait Sized { }`.
9299
self.stack.extend(
93-
obligations.into_iter().filter(|o| {
94-
self.visited.insert(self.cx.anonymize_bound_vars(o.predicate().kind()))
95-
}),
100+
obligations
101+
.into_iter()
102+
.filter(|o| self.visited.insert(self.cx.anonymize_bound_vars(o.predicate().kind())))
103+
.filter(|o| {
104+
let Some(current_clause) = current_clause else {
105+
return true;
106+
};
107+
let Some(next_clause) = o.predicate().as_clause() else {
108+
return true;
109+
};
110+
111+
let current_did = match current_clause.kind().skip_binder() {
112+
ty::ClauseKind::Trait(data) => data.def_id(),
113+
_ => return true,
114+
};
115+
let next_did = match next_clause.kind().skip_binder() {
116+
ty::ClauseKind::Trait(data) => data.def_id(),
117+
_ => return true,
118+
};
119+
120+
// PERF(sized-hierarchy): To avoid iterating over sizedness supertraits in
121+
// parameter environments, as an optimisation, sizedness supertraits aren't
122+
// elaborated, so check if a `Sized` obligation is being elaborated to a
123+
// `MetaSized` obligation and emit it. Candidate assembly and confirmation
124+
// are modified to check for the `Sized` subtrait when a `MetaSized` obligation
125+
// is present.
126+
if self.cx.is_lang_item(current_did, TraitSolverLangItem::Sized) {
127+
!self.cx.is_lang_item(next_did, TraitSolverLangItem::MetaSized)
128+
} else {
129+
true
130+
}
131+
}),
96132
);
97133
}
98134

@@ -132,12 +168,14 @@ impl<I: Interner, O: Elaboratable<I>> Elaborator<I, O> {
132168
// Get predicates implied by the trait, or only super predicates if we only care about self predicates.
133169
match self.mode {
134170
Filter::All => self.extend_deduped(
171+
Some(clause),
135172
cx.explicit_implied_predicates_of(data.def_id())
136173
.iter_identity()
137174
.enumerate()
138175
.map(map_to_child_clause),
139176
),
140177
Filter::OnlySelf => self.extend_deduped(
178+
Some(clause),
141179
cx.explicit_super_predicates_of(data.def_id())
142180
.iter_identity()
143181
.enumerate()
@@ -147,6 +185,7 @@ impl<I: Interner, O: Elaboratable<I>> Elaborator<I, O> {
147185
}
148186
// `T: ~const Trait` implies `T: ~const Supertrait`.
149187
ty::ClauseKind::HostEffect(data) => self.extend_deduped(
188+
Some(clause),
150189
cx.explicit_implied_const_bounds(data.def_id()).iter_identity().map(|trait_ref| {
151190
elaboratable.child(
152191
trait_ref
@@ -177,6 +216,7 @@ impl<I: Interner, O: Elaboratable<I>> Elaborator<I, O> {
177216
let mut components = smallvec![];
178217
push_outlives_components(cx, ty_max, &mut components);
179218
self.extend_deduped(
219+
Some(clause),
180220
components
181221
.into_iter()
182222
.filter_map(|component| elaborate_component_to_clause(cx, component, r_min))

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>)
99
where
1010
for<'b> &'b T: A,
1111
<&'static T as A>::X: Sized

0 commit comments

Comments
 (0)