Skip to content

Avoid O(n^2) performance by checking reduced set of obligations #18386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 28, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 75 additions & 21 deletions src/librustc/middle/traits/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ pub struct FulfillmentContext {
// A list of all obligations that have been registered with this
// fulfillment context.
trait_obligations: Vec<Obligation>,

// Remembers the count of trait obligations that we have already
// attempted to select. This is used to avoid repeating work
// when `select_new_obligations` is called.
attempted_mark: uint,
}

impl FulfillmentContext {
pub fn new() -> FulfillmentContext {
FulfillmentContext {
trait_obligations: Vec::new(),
attempted_mark: 0,
}
}

Expand Down Expand Up @@ -74,18 +80,49 @@ impl FulfillmentContext {
}
}

pub fn select_new_obligations<'a,'tcx>(&mut self,
infcx: &InferCtxt<'a,'tcx>,
param_env: &ty::ParameterEnvironment,
typer: &Typer<'tcx>)
-> Result<(),Vec<FulfillmentError>>
{
/*!
* Attempts to select obligations that were registered since
* the call to a selection routine. This is used by the type checker
* to eagerly attempt to resolve obligations in hopes of gaining
* type information. It'd be equally valid to use `select_where_possible`
* but it results in `O(n^2)` performance (#18208).
*/

let mut selcx = SelectionContext::new(infcx, param_env, typer);
self.select(&mut selcx, true)
}

pub fn select_where_possible<'a,'tcx>(&mut self,
infcx: &InferCtxt<'a,'tcx>,
param_env: &ty::ParameterEnvironment,
typer: &Typer<'tcx>)
-> Result<(),Vec<FulfillmentError>>
{
let tcx = infcx.tcx;
let mut selcx = SelectionContext::new(infcx, param_env, typer);
self.select(&mut selcx, false)
}

debug!("select_where_possible({} obligations) start",
self.trait_obligations.len());
fn select(&mut self,
selcx: &mut SelectionContext,
only_new_obligations: bool)
-> Result<(),Vec<FulfillmentError>>
{
/*!
* Attempts to select obligations using `selcx`. If
* `only_new_obligations` is true, then it only attempts to
* select obligations that haven't been seen before.
*/
debug!("select({} obligations, only_new_obligations={}) start",
self.trait_obligations.len(),
only_new_obligations);

let tcx = selcx.tcx();
let mut errors = Vec::new();

loop {
Expand All @@ -96,30 +133,47 @@ impl FulfillmentContext {

let mut selections = Vec::new();

// If we are only attempting obligations we haven't seen yet,
// then set `skip` to the number of obligations we've already
// seen.
let mut skip = if only_new_obligations {
self.attempted_mark
} else {
0
};

// First pass: walk each obligation, retaining
// only those that we cannot yet process.
self.trait_obligations.retain(|obligation| {
match selcx.select(obligation) {
Ok(None) => {
true
}
Ok(Some(s)) => {
selections.push(s);
false
}
Err(selection_err) => {
debug!("obligation: {} error: {}",
obligation.repr(tcx),
selection_err.repr(tcx));

errors.push(FulfillmentError::new(
(*obligation).clone(),
CodeSelectionError(selection_err)));
false
// Hack: Retain does not pass in the index, but we want
// to avoid processing the first `start_count` entries.
if skip > 0 {
skip -= 1;
true
} else {
match selcx.select(obligation) {
Ok(None) => {
true
}
Ok(Some(s)) => {
selections.push(s);
false
}
Err(selection_err) => {
debug!("obligation: {} error: {}",
obligation.repr(tcx),
selection_err.repr(tcx));
errors.push(FulfillmentError::new(
(*obligation).clone(),
CodeSelectionError(selection_err)));
false
}
}
}
});

self.attempted_mark = self.trait_obligations.len();

if self.trait_obligations.len() == count {
// Nothing changed.
break;
Expand All @@ -133,7 +187,7 @@ impl FulfillmentContext {
}
}

debug!("select_where_possible({} obligations, {} errors) done",
debug!("select({} obligations, {} errors) done",
self.trait_obligations.len(),
errors.len());

Expand Down
4 changes: 2 additions & 2 deletions src/librustc/middle/typeck/check/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ use middle::ty;
use middle::typeck::astconv::AstConv;
use middle::typeck::check::{FnCtxt, NoPreference, PreferMutLvalue};
use middle::typeck::check::{impl_self_ty};
use middle::typeck::check::vtable::select_fcx_obligations_where_possible;
use middle::typeck::check::vtable::select_new_fcx_obligations;
use middle::typeck::check;
use middle::typeck::infer;
use middle::typeck::{MethodCall, MethodCallee};
Expand Down Expand Up @@ -1302,7 +1302,7 @@ impl<'a, 'tcx> LookupContext<'a, 'tcx> {
// the `Self` trait).
let callee = self.confirm_candidate(rcvr_ty, &candidate);

select_fcx_obligations_where_possible(self.fcx);
select_new_fcx_obligations(self.fcx);

Some(Ok(callee))
}
Expand Down
51 changes: 33 additions & 18 deletions src/librustc/middle/typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ use middle::typeck::check::method::{AutoderefReceiver};
use middle::typeck::check::method::{CheckTraitsAndInherentMethods};
use middle::typeck::check::regionmanip::replace_late_bound_regions;
use middle::typeck::CrateCtxt;
use middle::typeck::infer::{resolve_type, force_tvar};
use middle::typeck::infer;
use middle::typeck::rscope::RegionScope;
use middle::typeck::{lookup_def_ccx};
Expand Down Expand Up @@ -1412,7 +1411,7 @@ fn check_cast(fcx: &FnCtxt,
}
// casts from C-like enums are allowed
} else if t_1_is_char {
let t_e = fcx.infcx().resolve_type_vars_if_possible(t_e);
let t_e = fcx.infcx().shallow_resolve(t_e);
if ty::get(t_e).sty != ty::ty_uint(ast::TyU8) {
fcx.type_error_message(span, |actual| {
format!("only `u8` can be cast as \
Expand Down Expand Up @@ -2564,7 +2563,7 @@ fn check_argument_types<'a>(fcx: &FnCtxt,
// an "opportunistic" vtable resolution of any trait
// bounds on the call.
if check_blocks {
vtable::select_fcx_obligations_where_possible(fcx);
vtable::select_new_fcx_obligations(fcx);
}

// For variadic functions, we don't have a declared type for all of
Expand Down Expand Up @@ -2988,9 +2987,11 @@ fn check_expr_with_unifier(fcx: &FnCtxt,
// 'else' branch.
let expected = match expected.only_has_type() {
ExpectHasType(ety) => {
match infer::resolve_type(fcx.infcx(), Some(sp), ety, force_tvar) {
Ok(rty) if !ty::type_is_ty_var(rty) => ExpectHasType(rty),
_ => NoExpectation
let ety = fcx.infcx().shallow_resolve(ety);
if !ty::type_is_ty_var(ety) {
ExpectHasType(ety)
} else {
NoExpectation
}
}
_ => NoExpectation
Expand Down Expand Up @@ -4037,7 +4038,7 @@ fn check_expr_with_unifier(fcx: &FnCtxt,
ast::ExprForLoop(ref pat, ref head, ref block, _) => {
check_expr(fcx, &**head);
let typ = lookup_method_for_for_loop(fcx, &**head, expr.id);
vtable::select_fcx_obligations_where_possible(fcx);
vtable::select_new_fcx_obligations(fcx);

let pcx = pat_ctxt {
fcx: fcx,
Expand Down Expand Up @@ -5393,18 +5394,32 @@ pub fn instantiate_path(fcx: &FnCtxt,

// Resolves `typ` by a single level if `typ` is a type variable. If no
// resolution is possible, then an error is reported.
pub fn structurally_resolved_type(fcx: &FnCtxt, sp: Span, tp: ty::t) -> ty::t {
match infer::resolve_type(fcx.infcx(), Some(sp), tp, force_tvar) {
Ok(t_s) if !ty::type_is_ty_var(t_s) => t_s,
_ => {
fcx.type_error_message(sp, |_actual| {
"the type of this value must be known in this \
context".to_string()
}, tp, None);
demand::suptype(fcx, sp, ty::mk_err(), tp);
tp
}
pub fn structurally_resolved_type(fcx: &FnCtxt, sp: Span, mut ty: ty::t) -> ty::t {
// If `ty` is a type variable, see whether we already know what it is.
ty = fcx.infcx().shallow_resolve(ty);

// If not, try resolve pending fcx obligations. Those can shed light.
//
// FIXME(#18391) -- This current strategy can lead to bad performance in
// extreme cases. We probably ought to smarter in general about
// only resolving when we need help and only resolving obligations
// will actually help.
if ty::type_is_ty_var(ty) {
vtable::select_fcx_obligations_where_possible(fcx);
ty = fcx.infcx().shallow_resolve(ty);
}

// If not, error.
if ty::type_is_ty_var(ty) {
fcx.type_error_message(sp, |_actual| {
"the type of this value must be known in this \
context".to_string()
}, ty, None);
demand::suptype(fcx, sp, ty::mk_err(), ty);
ty = ty::mk_err();
}

ty
}

// Returns the one-level-deep structure of the given type.
Expand Down
18 changes: 18 additions & 0 deletions src/librustc/middle/typeck/check/vtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,24 @@ pub fn select_fcx_obligations_where_possible(fcx: &FnCtxt) {
}
}

pub fn select_new_fcx_obligations(fcx: &FnCtxt) {
/*!
* Try to select any fcx obligation that we haven't tried yet,
* in an effort to improve inference. You could just call
* `select_fcx_obligations_where_possible` except that it leads
* to repeated work.
*/

match
fcx.inh.fulfillment_cx
.borrow_mut()
.select_new_obligations(fcx.infcx(), &fcx.inh.param_env, fcx)
{
Ok(()) => { }
Err(errors) => { report_fulfillment_errors(fcx, &errors); }
}
}

fn note_obligation_cause(fcx: &FnCtxt,
obligation: &Obligation) {
let tcx = fcx.tcx();
Expand Down