-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Remove interior mutability from TraitDef by turning fields into queries #41911
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the overall approach but have some doubts on the details.
}; | ||
} | ||
|
||
pub fn provide_extern(providers: &mut ty::maps::Providers) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since they're identical I'd use the same function twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You think so? It's just a coincidence that they are identical at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe have provide()
call provide_extern()
? I imagine that anything we provide for extern crates, we would also provide for locals, but maybe not the other way around
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense.
src/librustc/traits/project.rs
Outdated
@@ -1320,23 +1320,10 @@ fn assoc_ty_def<'cx, 'gcx, 'tcx>( | |||
let trait_def_id = selcx.tcx().impl_trait_ref(impl_def_id).unwrap().def_id; | |||
let trait_def = selcx.tcx().trait_def(trait_def_id); | |||
|
|||
if !trait_def.is_complete(selcx.tcx()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this work? Do we produce cycle errors in more cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you mean exactly. I don't see a potential cycle here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The !is_complete
case is a fallback for cycles, that is, when normalization of e.g. <T as Foo>::Assoc
is needed to compute Foo
's specialization graph.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @aturon We may be lacking tests here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, so assoc_ty_def()
might be called while still building the specialization graph?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It needs to be used in the specialization graph of the same trait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It needs to be used in the specialization graph of the same trait.
Yes, that makes sense. And I thought the following example might trigger the condition, but it doesn't :(
#![feature(specialization)]
trait Assoc {
type Output;
}
impl<T> Assoc for T {
default type Output = u8;
}
impl Assoc for u32 {
type Output = u32;
}
impl Assoc for u64 {
// Shouldn't this cause a reentrant call?
type Output = <u32 as Assoc>::Output;
}
fn main() {
let _x: <u32 as Assoc>::Output = 0;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the impl header - only Self/params are checked by coherence/specialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the following gives me the error overflow evaluating the requirement <u32 as Assoc>::Output
regardless of whether cycle detection is enabled:
#![feature(specialization)]
trait Assoc {
type Output;
}
impl Assoc for u32 {
type Output = u64;
}
impl Assoc for <u32 as Assoc>::Output {
type Output = u64;
}
fn main() {
let _x: <u64 as Assoc>::Output = 0;
}
So that's not really what we are looking for, I guess?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I don't really expect you to be able to project <u32 as Assoc>::Output
in an impl for Assoc
. What I expect could work is if there is a DAG between the traits, sort of like this:
#![feature(specialization)]
use std::vec;
trait Assoc {
type Output;
}
impl Assoc for u32 {
type Output = u64;
}
impl Assoc for <vec::IntoIter<u64> as Iterator>::Item {
type Output = u64;
}
fn main() {
}
Interestingly, coherence rejects this example, presumably because of RFC 1214 concerns. I'm not sure that's right, but this example does work today:
#![feature(specialization)]
use std::vec;
trait Assoc {
type Output;
}
impl Assoc for u32 {
type Output = u64;
}
impl Assoc for <u32 as Assoc2>::Output {
type Output = u64;
}
trait Assoc2 {
type Output;
}
impl Assoc2 for u32 {
type Output = u64;
}
fn main() {
}
That said, I think that would also work in @michaelwoerister's branch, no?
src/librustc/ty/maps.rs
Outdated
[] trait_impls_of: TraitImpls(DefId) -> Rc<Vec<DefId>>, | ||
// Note that TraitDef::for_each_relevant_impl() will do type simplication for you. | ||
[] relevant_trait_impls_for: relevant_trait_impls_for((DefId, SimplifiedType)) | ||
-> Rc<Vec<DefId>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, where are all the blanket impls?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are part of the list.
src/librustc/ty/trait_def.rs
Outdated
impl_simple_self_ty == self_ty | ||
} else { | ||
// blanket impl (?) | ||
true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is potentially very bad, as N blanket impls and M non-blanket now use N*M space.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, OK, let me think about that. Maybe we could share the list of blanket impls?
☔ The latest upstream changes (presumably #41847) made this pull request unmergeable. Please resolve the merge conflicts. |
3abfdbe
to
742ebc1
Compare
@eddyb I added the cycle check back in (40a6734) and blanket impl lists are now shared (742ebc1). But I didn't manage to come up with a test case that would cause a cycle (see #41911 (comment)), unfortunately. |
@michaelwoerister Yeah, I'm waiting on @aturon and/or @nikomatsakis for that. |
@@ -497,15 +497,15 @@ impl<'hir> Map<'hir> { | |||
} | |||
|
|||
pub fn trait_impls(&self, trait_did: DefId) -> &'hir [NodeId] { | |||
self.dep_graph.read(DepNode::TraitImpls(trait_did)); | |||
self.dep_graph.read(DepNode::AllLocalTraitImpls); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious why you converted this into a single dep-node, rather than keeping it per-trait. It seems like this is losing quite a bit of precision, no?
(That is, if any trait adds an impl, we will consider them all to have changed.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly for ease of implementation and symmetry with the situation in metadata. DepNode::TraitImpls
has been re-purposed to represent local and remote impls and, with red-green, will take care of stopping invalidation short.
Without red-green though you are right, it's losing precision. Want me to make it per-item?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not a big deal.
src/librustc/traits/project.rs
Outdated
@@ -1320,23 +1320,10 @@ fn assoc_ty_def<'cx, 'gcx, 'tcx>( | |||
let trait_def_id = selcx.tcx().impl_trait_ref(impl_def_id).unwrap().def_id; | |||
let trait_def = selcx.tcx().trait_def(trait_def_id); | |||
|
|||
if !trait_def.is_complete(selcx.tcx()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I don't really expect you to be able to project <u32 as Assoc>::Output
in an impl for Assoc
. What I expect could work is if there is a DAG between the traits, sort of like this:
#![feature(specialization)]
use std::vec;
trait Assoc {
type Output;
}
impl Assoc for u32 {
type Output = u64;
}
impl Assoc for <vec::IntoIter<u64> as Iterator>::Item {
type Output = u64;
}
fn main() {
}
Interestingly, coherence rejects this example, presumably because of RFC 1214 concerns. I'm not sure that's right, but this example does work today:
#![feature(specialization)]
use std::vec;
trait Assoc {
type Output;
}
impl Assoc for u32 {
type Output = u64;
}
impl Assoc for <u32 as Assoc2>::Output {
type Output = u64;
}
trait Assoc2 {
type Output;
}
impl Assoc2 for u32 {
type Output = u64;
}
fn main() {
}
That said, I think that would also work in @michaelwoerister's branch, no?
src/librustc/ty/trait_def.rs
Outdated
let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); | ||
let impl_simple_self_ty = fast_reject::simplify_type(tcx, | ||
impl_trait_ref.self_ty(), | ||
false).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized, this is stricter than it has to be. it should use tcx.type_of(impl_def_id)
instead.
src/librustc/ty/trait_def.rs
Outdated
.map(|&node_id| tcx.hir.local_def_id(node_id)); | ||
|
||
for impl_def_id in local_impls.chain(remote_impls.into_iter()) { | ||
let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, I suppose.
|
||
// Formerly this ICEd with the following message: | ||
// Tried to project an inherited associated type during coherence checking, | ||
// which is currently not supported. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is that message in the source? Could that codepath be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It here:
rust/src/librustc/traits/project.rs
Line 989 in 4d09a0e
span_bug!(obligation.cause.span, |
But it has this nice, long comment which made me reluctant to remove it. I could move the comment to assoc_ty_def()
, make its return value non-optional, and then remove the branch though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, moving the comment should be fine.
span_bug!(obligation.cause.span, | ||
"Tried to project an inherited associated type during \ | ||
coherence checking, which is currently not supported."); | ||
None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you remove this comment etc? Do we think this code-path is dead, or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we think it's unreachable, I'd rather see bug!(...)
, probably with an updated comment explaining that we expect this scenario to be prevented by cycle-errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The diff is a bit confusion here because indentation has changed. The None
belongs to let new_candidate = if !is_default {
, for which nothing should have changed, I believe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I see now
@bors r+ |
📌 Commit 08660af has been approved by |
…akis Remove interior mutability from TraitDef by turning fields into queries This PR gets rid of anything `std::cell` in `TraitDef` by - moving the global list of trait impls from `TraitDef` into a query, - moving the list of trait impls relevent for some self-type from `TraitDef` into a query - moving the specialization graph of trait impls into a query, and - moving `TraitDef::object_safety` into a query. I really like how querifying things not only helps with incremental compilation and on-demand, but also just plain makes the code cleaner `:)` There are also some smaller fixes in the PR. Commits can be reviewed separately. r? @eddyb or @nikomatsakis
☀️ Test successful - status-appveyor, status-travis |
A change in rust-lang#41911 had made `for_all_relevant_impls` do a linear scan over all impls, instead of using an HashMap. Use an HashMap again to avoid quadratic blowup when there is a large number of structs with impls. I think this fixes rust-lang#43141 completely, but I want better measurements in order to be sure. As a perf patch, please don't roll this up.
make `for_all_relevant_impls` O(1) again A change in #41911 had made `for_all_relevant_impls` do a linear scan over all impls, instead of using an HashMap. Use an HashMap again to avoid quadratic blowup when there is a large number of structs with impls. I think this fixes #43141 completely, but I want better measurements in order to be sure. As a perf patch, please don't roll this up. r? @eddyb beta-nominating because regression
A change in rust-lang#41911 had made `for_all_relevant_impls` do a linear scan over all impls, instead of using an HashMap. Use an HashMap again to avoid quadratic blowup when there is a large number of structs with impls. I think this fixes rust-lang#43141 completely, but I want better measurements in order to be sure. As a perf patch, please don't roll this up.
This PR gets rid of anything
std::cell
inTraitDef
byTraitDef
into a query,TraitDef
into a queryTraitDef::object_safety
into a query.I really like how querifying things not only helps with incremental compilation and on-demand, but also just plain makes the code cleaner
:)
There are also some smaller fixes in the PR. Commits can be reviewed separately.
r? @eddyb or @nikomatsakis