Skip to content

Commit 1da73ff

Browse files
committed
Avoid O(n^2) performance by reconsidering the full set of obligations only when we are about to report an error (#18208). I found it is still important to consider the full set in order to make tests like let x: Vec<_> = obligations.iter().collect() work.
1 parent faed648 commit 1da73ff

File tree

4 files changed

+128
-41
lines changed

4 files changed

+128
-41
lines changed

src/librustc/middle/traits/fulfill.rs

+75-21
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,18 @@ pub struct FulfillmentContext {
3535
// A list of all obligations that have been registered with this
3636
// fulfillment context.
3737
trait_obligations: Vec<Obligation>,
38+
39+
// Remembers the count of trait obligations that we have already
40+
// attempted to select. This is used to avoid repeating work
41+
// when `select_new_obligations` is called.
42+
attempted_mark: uint,
3843
}
3944

4045
impl FulfillmentContext {
4146
pub fn new() -> FulfillmentContext {
4247
FulfillmentContext {
4348
trait_obligations: Vec::new(),
49+
attempted_mark: 0,
4450
}
4551
}
4652

@@ -74,18 +80,49 @@ impl FulfillmentContext {
7480
}
7581
}
7682

83+
pub fn select_new_obligations<'a,'tcx>(&mut self,
84+
infcx: &InferCtxt<'a,'tcx>,
85+
param_env: &ty::ParameterEnvironment,
86+
typer: &Typer<'tcx>)
87+
-> Result<(),Vec<FulfillmentError>>
88+
{
89+
/*!
90+
* Attempts to select obligations that were registered since
91+
* the call to a selection routine. This is used by the type checker
92+
* to eagerly attempt to resolve obligations in hopes of gaining
93+
* type information. It'd be equally valid to use `select_where_possible`
94+
* but it results in `O(n^2)` performance (#18208).
95+
*/
96+
97+
let mut selcx = SelectionContext::new(infcx, param_env, typer);
98+
self.select(&mut selcx, true)
99+
}
100+
77101
pub fn select_where_possible<'a,'tcx>(&mut self,
78102
infcx: &InferCtxt<'a,'tcx>,
79103
param_env: &ty::ParameterEnvironment,
80104
typer: &Typer<'tcx>)
81105
-> Result<(),Vec<FulfillmentError>>
82106
{
83-
let tcx = infcx.tcx;
84107
let mut selcx = SelectionContext::new(infcx, param_env, typer);
108+
self.select(&mut selcx, false)
109+
}
85110

86-
debug!("select_where_possible({} obligations) start",
87-
self.trait_obligations.len());
111+
fn select(&mut self,
112+
selcx: &mut SelectionContext,
113+
only_new_obligations: bool)
114+
-> Result<(),Vec<FulfillmentError>>
115+
{
116+
/*!
117+
* Attempts to select obligations using `selcx`. If
118+
* `only_new_obligations` is true, then it only attempts to
119+
* select obligations that haven't been seen before.
120+
*/
121+
debug!("select({} obligations, only_new_obligations={}) start",
122+
self.trait_obligations.len(),
123+
only_new_obligations);
88124

125+
let tcx = selcx.tcx();
89126
let mut errors = Vec::new();
90127

91128
loop {
@@ -96,30 +133,47 @@ impl FulfillmentContext {
96133

97134
let mut selections = Vec::new();
98135

136+
// If we are only attempting obligations we haven't seen yet,
137+
// then set `skip` to the number of obligations we've already
138+
// seen.
139+
let mut skip = if only_new_obligations {
140+
self.attempted_mark
141+
} else {
142+
0
143+
};
144+
99145
// First pass: walk each obligation, retaining
100146
// only those that we cannot yet process.
101147
self.trait_obligations.retain(|obligation| {
102-
match selcx.select(obligation) {
103-
Ok(None) => {
104-
true
105-
}
106-
Ok(Some(s)) => {
107-
selections.push(s);
108-
false
109-
}
110-
Err(selection_err) => {
111-
debug!("obligation: {} error: {}",
112-
obligation.repr(tcx),
113-
selection_err.repr(tcx));
114-
115-
errors.push(FulfillmentError::new(
116-
(*obligation).clone(),
117-
CodeSelectionError(selection_err)));
118-
false
148+
// Hack: Retain does not pass in the index, but we want
149+
// to avoid processing the first `start_count` entries.
150+
if skip > 0 {
151+
skip -= 1;
152+
true
153+
} else {
154+
match selcx.select(obligation) {
155+
Ok(None) => {
156+
true
157+
}
158+
Ok(Some(s)) => {
159+
selections.push(s);
160+
false
161+
}
162+
Err(selection_err) => {
163+
debug!("obligation: {} error: {}",
164+
obligation.repr(tcx),
165+
selection_err.repr(tcx));
166+
errors.push(FulfillmentError::new(
167+
(*obligation).clone(),
168+
CodeSelectionError(selection_err)));
169+
false
170+
}
119171
}
120172
}
121173
});
122174

175+
self.attempted_mark = self.trait_obligations.len();
176+
123177
if self.trait_obligations.len() == count {
124178
// Nothing changed.
125179
break;
@@ -133,7 +187,7 @@ impl FulfillmentContext {
133187
}
134188
}
135189

136-
debug!("select_where_possible({} obligations, {} errors) done",
190+
debug!("select({} obligations, {} errors) done",
137191
self.trait_obligations.len(),
138192
errors.len());
139193

src/librustc/middle/typeck/check/method.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ use middle::ty;
8888
use middle::typeck::astconv::AstConv;
8989
use middle::typeck::check::{FnCtxt, NoPreference, PreferMutLvalue};
9090
use middle::typeck::check::{impl_self_ty};
91-
use middle::typeck::check::vtable::select_fcx_obligations_where_possible;
91+
use middle::typeck::check::vtable::select_new_fcx_obligations;
9292
use middle::typeck::check;
9393
use middle::typeck::infer;
9494
use middle::typeck::{MethodCall, MethodCallee};
@@ -1302,7 +1302,7 @@ impl<'a, 'tcx> LookupContext<'a, 'tcx> {
13021302
// the `Self` trait).
13031303
let callee = self.confirm_candidate(rcvr_ty, &candidate);
13041304

1305-
select_fcx_obligations_where_possible(self.fcx);
1305+
select_new_fcx_obligations(self.fcx);
13061306

13071307
Some(Ok(callee))
13081308
}

src/librustc/middle/typeck/check/mod.rs

+33-18
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ use middle::typeck::check::method::{AutoderefReceiver};
101101
use middle::typeck::check::method::{CheckTraitsAndInherentMethods};
102102
use middle::typeck::check::regionmanip::replace_late_bound_regions;
103103
use middle::typeck::CrateCtxt;
104-
use middle::typeck::infer::{resolve_type, force_tvar};
105104
use middle::typeck::infer;
106105
use middle::typeck::rscope::RegionScope;
107106
use middle::typeck::{lookup_def_ccx};
@@ -1412,7 +1411,7 @@ fn check_cast(fcx: &FnCtxt,
14121411
}
14131412
// casts from C-like enums are allowed
14141413
} else if t_1_is_char {
1415-
let t_e = fcx.infcx().resolve_type_vars_if_possible(t_e);
1414+
let t_e = fcx.infcx().shallow_resolve(t_e);
14161415
if ty::get(t_e).sty != ty::ty_uint(ast::TyU8) {
14171416
fcx.type_error_message(span, |actual| {
14181417
format!("only `u8` can be cast as \
@@ -2564,7 +2563,7 @@ fn check_argument_types<'a>(fcx: &FnCtxt,
25642563
// an "opportunistic" vtable resolution of any trait
25652564
// bounds on the call.
25662565
if check_blocks {
2567-
vtable::select_fcx_obligations_where_possible(fcx);
2566+
vtable::select_new_fcx_obligations(fcx);
25682567
}
25692568

25702569
// For variadic functions, we don't have a declared type for all of
@@ -2988,9 +2987,11 @@ fn check_expr_with_unifier(fcx: &FnCtxt,
29882987
// 'else' branch.
29892988
let expected = match expected.only_has_type() {
29902989
ExpectHasType(ety) => {
2991-
match infer::resolve_type(fcx.infcx(), Some(sp), ety, force_tvar) {
2992-
Ok(rty) if !ty::type_is_ty_var(rty) => ExpectHasType(rty),
2993-
_ => NoExpectation
2990+
let ety = fcx.infcx().shallow_resolve(ety);
2991+
if !ty::type_is_ty_var(ety) {
2992+
ExpectHasType(ety)
2993+
} else {
2994+
NoExpectation
29942995
}
29952996
}
29962997
_ => NoExpectation
@@ -4037,7 +4038,7 @@ fn check_expr_with_unifier(fcx: &FnCtxt,
40374038
ast::ExprForLoop(ref pat, ref head, ref block, _) => {
40384039
check_expr(fcx, &**head);
40394040
let typ = lookup_method_for_for_loop(fcx, &**head, expr.id);
4040-
vtable::select_fcx_obligations_where_possible(fcx);
4041+
vtable::select_new_fcx_obligations(fcx);
40414042

40424043
let pcx = pat_ctxt {
40434044
fcx: fcx,
@@ -5393,18 +5394,32 @@ pub fn instantiate_path(fcx: &FnCtxt,
53935394

53945395
// Resolves `typ` by a single level if `typ` is a type variable. If no
53955396
// resolution is possible, then an error is reported.
5396-
pub fn structurally_resolved_type(fcx: &FnCtxt, sp: Span, tp: ty::t) -> ty::t {
5397-
match infer::resolve_type(fcx.infcx(), Some(sp), tp, force_tvar) {
5398-
Ok(t_s) if !ty::type_is_ty_var(t_s) => t_s,
5399-
_ => {
5400-
fcx.type_error_message(sp, |_actual| {
5401-
"the type of this value must be known in this \
5402-
context".to_string()
5403-
}, tp, None);
5404-
demand::suptype(fcx, sp, ty::mk_err(), tp);
5405-
tp
5406-
}
5397+
pub fn structurally_resolved_type(fcx: &FnCtxt, sp: Span, mut ty: ty::t) -> ty::t {
5398+
// If `ty` is a type variable, see whether we already know what it is.
5399+
ty = fcx.infcx().shallow_resolve(ty);
5400+
5401+
// If not, try resolve pending fcx obligations. Those can shed light.
5402+
//
5403+
// FIXME(#18391) -- This current strategy can lead to bad performance in
5404+
// extreme cases. We probably ought to smarter in general about
5405+
// only resolving when we need help and only resolving obligations
5406+
// will actually help.
5407+
if ty::type_is_ty_var(ty) {
5408+
vtable::select_fcx_obligations_where_possible(fcx);
5409+
ty = fcx.infcx().shallow_resolve(ty);
54075410
}
5411+
5412+
// If not, error.
5413+
if ty::type_is_ty_var(ty) {
5414+
fcx.type_error_message(sp, |_actual| {
5415+
"the type of this value must be known in this \
5416+
context".to_string()
5417+
}, ty, None);
5418+
demand::suptype(fcx, sp, ty::mk_err(), ty);
5419+
ty = ty::mk_err();
5420+
}
5421+
5422+
ty
54085423
}
54095424

54105425
// Returns the one-level-deep structure of the given type.

src/librustc/middle/typeck/check/vtable.rs

+18
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,24 @@ pub fn select_fcx_obligations_where_possible(fcx: &FnCtxt) {
339339
}
340340
}
341341

342+
pub fn select_new_fcx_obligations(fcx: &FnCtxt) {
343+
/*!
344+
* Try to select any fcx obligation that we haven't tried yet,
345+
* in an effort to improve inference. You could just call
346+
* `select_fcx_obligations_where_possible` except that it leads
347+
* to repeated work.
348+
*/
349+
350+
match
351+
fcx.inh.fulfillment_cx
352+
.borrow_mut()
353+
.select_new_obligations(fcx.infcx(), &fcx.inh.param_env, fcx)
354+
{
355+
Ok(()) => { }
356+
Err(errors) => { report_fulfillment_errors(fcx, &errors); }
357+
}
358+
}
359+
342360
fn note_obligation_cause(fcx: &FnCtxt,
343361
obligation: &Obligation) {
344362
let tcx = fcx.tcx();

0 commit comments

Comments
 (0)