instantiate higher ranked goals outside of candidate selection#119820
instantiate higher ranked goals outside of candidate selection#119820bors merged 2 commits intorust-lang:masterfrom
Conversation
|
Type relation code was changed |
|
@bors try @rust-timer queue (perf and need crater for rust-lang/trait-system-refactor-initiative#34) |
This comment has been minimized.
This comment has been minimized.
remove the `coherence_leak_check` future compat lint and adapt the old solver to not rely on universe errors for higher ranked goals to impact candidate selection. This matches the behavior of the new solver: rust-lang/trait-system-refactor-initiative#34 r? `@nikomatsakis`
|
☀️ Try build successful - checks-actions |
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (b69bfee): comparison URL. Overall result: ❌ regressions - ACTION NEEDEDBenchmarking this pull request likely means that it is perf-sensitive, so we're automatically marking it as not fit for rolling up. While you can manually mark this PR as fit for rollup, we strongly recommend not doing so since this PR may lead to changes in compiler perf. Next Steps: If you can justify the regressions found in this try perf run, please indicate this with @bors rollup=never Instruction countThis is a highly reliable metric that was used to determine the overall result at the top of this comment.
Max RSS (memory usage)ResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
CyclesResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
Binary sizeThis benchmark run did not return any relevant results for this metric. Bootstrap: 666.26s -> 666.778s (0.08%) |
|
@craterbot check |
|
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
🎉 Experiment
|
|
looking at https://crates.io/crates/rene, I think the error pattern is the following: trait Trait {}
impl<T: Trait> Trait for &T {}
impl Trait for u32 {}
fn hr_bound<T>()
where
for<'a> &'a T: Trait,
{}
fn foo<T>()
where
T: Trait,
for<'a> &'a &'a T: Trait,
{
hr_bound::<&T>();
// We get a universe error when using the `param_env` candidate
// but are able to successfully use the impl candidate. Without
// the leak check both candidates may apply and we prefer the
// `param_env` candidate in winnowing.
}
fn main() {} |
|
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
@bors rollup=never |
|
☀️ Test successful - checks-actions |
|
Finished benchmarking commit (43f4f2a): comparison URL. Overall result: ❌ regressions - ACTION NEEDEDNext Steps: If you can justify the regressions found in this perf run, please indicate this with @rustbot label: +perf-regression Instruction countThis is a highly reliable metric that was used to determine the overall result at the top of this comment.
Max RSS (memory usage)ResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
CyclesResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
Binary sizeThis benchmark run did not return any relevant results for this metric. Bootstrap: 669.809s -> 667.94s (-0.28%) |
|
Expected small perf. regression. @rustbot label: +perf-regression-triaged |
edit: this PR has been reverted in #127568, keeping the current inconsistent behavior. Supporting this behavior in the new trait solver is tracked in rust-lang/trait-system-refactor-initiative#120
This PR modifies
evaluateto more eagerly instantiate higher-ranked goals, preventing theleak_checkduring candidate selection from detecting placeholder errors involving that binder.For a general background regarding higher-ranked region solving and the leak check, see https://hackmd.io/qd9Wp03cQVy06yOLnro2Kg.
The ideal future
We would like to end up with the following idealized design to handle universal binders:
That is, when universally instantiating a binder, anything using the placeholders has to happen inside of a limited scope (the closure
f). After this closure has completed, all constraints involving placeholders are known.We then handle any external constraints which name these placeholders. We destructure
TypeOutlivesconstraints involving placeholders and eagerly handle any region constraints involving these placeholders. We do not return anything mentioning the placeholders created inside of this function to the caller.Being able to eagerly handle all region constraints involving placeholders will be difficult due to complex
TypeOutlivesconstraints, involving inference variables or alias types, and higher ranked implied bounds. The exact issues and possible solutions are out of scope of this FCP.How does the leak check fit into this
The
leak_checkis an underapproximation ofeagerly_handle_higher_ranked_region_constraints_in. It detects some kinds of errors involving placeholders fromnew_universe, but not all of them.It only looks at region outlives constraints, ignoring
TypeOutlives, and checks whether one of the following two conditions are met for placeholders in or abovenew_universe, in which case it results in an error:'!p1: '!p2a placeholder'!p2outlives a different placeholder'!p1'!p1: '?2an inference variable'?2outlives a placeholder'!p1which it cannot nameIt does not handle all higher ranked region constraints, so we still return constraints involving placeholders from
new_universewhich are then (re)checked bylexical_region_resolveor MIR borrowck.As we check higher ranked constraints in the full regionck anyways, the
leak_checkis not soundness critical. It's current only purpose is to move some higher ranked region errors earlier, enabling it to guide type inference and trait solving. Adding additional uses of theleak_checkin the future would only strengthen inference and is therefore not breaking.Where do we use currently use the leak check
The
leak_checkis currently used in two places:Coherence does not use a proper regionck, only relying on the
leak_checkcalled at the end of the implicit negative overlap check. During coherence all parameters are instantiated with inference variables, so the only possible region errors are higher-ranked. We currently also sometimes make guesses when destructuringTypeOutlivesconstraints which can theoretically result in incorrect errors. This could result in overlapping impls.We also use the
leak_checkat the end offn evaluation_probe. This function is used during candidate assembly forTraitgoals. Most notably we use inside ofevaluate_candidateduring winnowing. Conceptionally, it is as if we compute each candidate in a separateenter_forall.The current use in
fn evaluation_probeis undesirableBecause we only instantiate a higher-ranked goal once inside of
fn evaluation_probe, errors involving placeholders from that binder can impact selection. This results in inconsistent behavior (playground):We generally prefer
where-bounds over implementations during candidate selection, both for trait goals and during normalization. However, we currently do not use theleak_checkduring candidate assembly in normalizing. This can result in inconsistent behavior:This is also likely to be more performant. It enables more caching in the new trait solver by simply recursively calling the canonical query after instantiating the higher-ranked goal.
It is also unclear how to add the leak check to normalization in the new solver. To handle rust-lang/trait-system-refactor-initiative#1
Projectiongoals are implemented viaAliasRelate. This again means that we instantiate the binder before ever normalizing any alias. Even if we were to avoid this, we lose the ability to cache normalization by itself, ignoring the expectedterm. We cannot replace thetermwith an inference variable before instantiating the binder, as otherwisefor<'a> T: Trait<Assoc<'a> = &'a ()>breaks. If we only replace the term after instantiating the binder, we cannot easily evaluate the goal in a separate context, as we'd then lose the information necessary for the leak check. Adding this information to the canonical input also seems non-trivial.Proposed solution
I propose to instantiate the binder outside of candidate assembly, causing placeholders from higher-ranked goals to get ignored while selecting their candidate. This mostly1 matches the current behavior of the new solver. The impact of this change is therefore as follows:
This does not change the behavior if candidates have higher ranked nested goals, as in this case the
leak_checkcauses the nested goal to result in an error (playground):Impact on existing crates
This is a breaking change. A crater run found 17 regressed crates with 7 root causes.
For a full analysis of all affected crates, see https://gist.github.com/lcnr/7c1c652f30567048ea240554a36ed95c.
I believe this breakage to be acceptable and would merge this change. I am confident that the new position of the leak check matches our idealized future and cannot envision any other consistent alternative. Where possible, I intend to open PRs fixing/avoiding the regressions before landing this PR.
I originally intended to remove the
coherence_leak_checklint in the same PR. However, while I am confident in the position of the leak check, deciding on its exact behavior is left as future work, cc #112999. This PR therefore only moves the leak check while keeping the lint when relying on it in coherence.r? @nikomatsakis
Footnotes
the new solver has a separate cause of inconsistent behavior rn https://github.com/rust-lang/trait-system-refactor-initiative/issues/53#issuecomment-1914310171 ↩