-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Trait evaluation cache interacts badly with incremental compilation #83538
Comments
This option would make our choice of |
Can we revert whatever caused this (#83007, i think?) until a solution is available? |
@m-ou-se: These ICEs occur when we would otherwise produce an incorrect result. Given that there's already once case of that leading to unsoundness, I'd be concerned that we'd be simply covering up miscompilations. |
I opened #83719 to measure the performance impact of only ever returning |
Many people are running into this, and this is blocking us from getting full |
I've opened #83913 with a potential fix |
@Aaron1011 it sounds like #83913 isn't going to land soon because of the perf impact. If we added |
I think that might work - we'd effectively be explicitly declaring our reliance on global state (the evaluation cache). cc @rust-lang/wg-incr-comp |
However, I am worried that we're papering over an issue with soundness implications. Both the trait system and lifetimes are very subtle, and this issue involves both of them. I don't know what issues might arise as a result of treating the same obligation as |
Hmm, but eval_always would avoid mistaking those two, wouldn't it? eval_always is definitely papering over the issues, but I don't think it will leave any unsoundness - unless you think the trait cache is wrong even when it doesn't interact with the query system? |
I tried marking the query as |
Hmm, could we try the opposite approach? Use the trait evaluation cache less? I don't know how practical that is, I don't know anything about the trait system. |
I've managed to create a single-file reproduction: pub struct TokenTree {
pub field: Vec<TokenTree>
}
pub trait Synom {
fn parse();
}
pub struct Tokens {
tts: Vec<TokenTree>,
}
pub struct Delimited<T> {
inner: Vec<(T, Option<()>)>
}
impl<T> Delimited<T> {
pub fn push_first(&mut self, token: T) {
self.inner.push((token, None));
}
}
fn make_ty() -> Ty {
panic!()
}
pub enum Ty {
/// A tuple (`(A, B, C, D, ...)`)
Tup(TyTup),
/// A trait object type `Bound1 + Bound2 + Bound3`
/// where `Bound` is a trait or a lifetime.
TraitObject(TyTraitObject),
/// No-op: kept solely so that we can pretty-print faithfully
Group(TyGroup),
}
/// A tuple (`(A, B, C, D, ...)`)
pub struct TyTup {
pub tys: Vec<Ty>,
}
/// A trait object type `Bound1 + Bound2 + Bound3`
/// where `Bound` is a trait or a lifetime.
pub struct TyTraitObject {
pub bounds: TokenTree,
}
/// No-op: kept solely so that we can pretty-print faithfully
pub struct TyGroup {
pub ty: Box<Ty>,
}
impl Synom for Ty {
fn parse() {
<TyGroup as Synom>::parse();
}
}
pub fn parse_test() {
let mut res: Delimited<Ty> = Delimited {
inner: Vec::new(),
};
Ty::parse();
res.push_first(make_ty());
}
impl Synom for TyGroup {
fn parse() {
<Ty as Synom>::parse();
panic!()
}
}
pub fn to_tokens(tokens: &mut Tokens) {} Reproduction steps:
You can also clone the repository https://github.com/Aaron1011/syn-crash and use |
Made a bit more progress; insert the newline before struct Ty - this script should work. use std::marker::PhantomData;
struct a {
b : PhantomData< a >
}
pub struct c {
d : PhantomData< a >
}
struct e< f > {
g : Vec< f >
}
struct Ty(h, i, j);
struct h {
k : Vec< Ty >
}
struct i {
bounds : a
}
struct j {
l : Box< Ty >
}
pub fn m() {
e::< Ty >{g : Vec::new ()};
}
pub fn n(t : &mut c) {} |
Note that for some reason, @Mark-Simulacrum's reproduction only works with |
Version with nicer names: struct First {
b : Vec< First >
} pub struct Second {
d : Vec< First >
} struct Third< f > {
g : Vec< f >
} enum Ty { j(Fourth, Fifth, Sixth) } struct Fourth {
o : Vec< Ty >
} struct Fifth {
bounds : First
} struct Sixth {
p : Box< Ty >
} pub fn q() {
Third::< Ty >{g : Vec::new ()};
}
pub fn s(t : &mut Second) {} |
From looking at the debug log output, I believe that #83913 is still the correct fix. During the first compilation session, we end up evaluating During the second compilation session, we don't end up evaluating I believe that the root cause of this issue is that handling of co-inductive cycles causes us to 'infect' other evaluations with However, we could also end up evaluating rust/compiler/rustc_trait_selection/src/traits/select/mod.rs Lines 664 to 670 in ff34b91
In particular, if the This means that we have two possible results for |
I think PR #83913 remains the best solution to this issue |
PR #83220 solved one issue with the trait evaluation cache, but I've found another one.
On the
perf
server,syn
is currently causing the following ICE:I've created a standalone repository that reproduces this crash: https://github.com/Aaron1011/syn-crash
The following shell script can be used to reproduce the ICE:
EDIT: This appears to be due to the way we handle caching for a coinductive cycle.
The root cause appears to be here:rust/compiler/rustc_trait_selection/src/traits/select/mod.rs
Lines 390 to 407 in 5e65467
The
evaluation_probe
method is used (among other things) to wrap the call toevaluate_predicate_recursively
when we are trying to evaluate a root obligation. This means that any region constraints that get created during evaluation (including the recursive evaluation of other predicates) will cause us to returnEvaluatedToOkModuloRegions
.However, whether or not we end up creating region constraints depends on the state of the evaluation cache - if we get a cache hit, we may skip creating a region constraint that would have otherwise been created. This means that re-running predicate evaluation during incremental compilation may produce a different result.
I see a few different ways of fixing this:
EvaluatedToOkModuloRegions
even if we got a cache hit.EvaluatedToOk
when there are no erased regions in our original predicate (in this case,quote::Tokens: Unpin
. I don't actually know if this is sound - there are no 'input' region variables to worry about, but I'm not sure if safe to ignore any 'internal' region constraints that get added.EvaluatedToOk
entirely, and always returnEvaluatedToOkModuloRegions
. I'm not sure what the performance impact of doing this would be, or how much refactoring it would require. However, this would eliminate a large source of potential ICEs.I'm not familiar enough with the trait evaluation code to know which, if any, of those options is the best choice.
cc @nikomatsakis @matthewjasper
The text was updated successfully, but these errors were encountered: