Skip to content
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

Merged
merged 10 commits into from
May 18, 2017

Conversation

michaelwoerister
Copy link
Member

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

@alexcrichton alexcrichton added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 11, 2017
Copy link
Member

@eddyb eddyb left a 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) {
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Contributor

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense.

@@ -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()) {
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

@eddyb eddyb May 12, 2017

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.

Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member Author

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;
}

Copy link
Member

@eddyb eddyb May 12, 2017

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.

Copy link
Member Author

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?

Copy link
Contributor

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?

[] 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>>,
Copy link
Member

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?

Copy link
Member Author

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.

impl_simple_self_ty == self_ty
} else {
// blanket impl (?)
true
Copy link
Member

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.

Copy link
Member Author

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?

@bors
Copy link
Contributor

bors commented May 13, 2017

☔ The latest upstream changes (presumably #41847) made this pull request unmergeable. Please resolve the merge conflicts.

@Mark-Simulacrum Mark-Simulacrum added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels May 14, 2017
@michaelwoerister
Copy link
Member Author

@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 michaelwoerister added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 15, 2017
@eddyb
Copy link
Member

eddyb commented May 15, 2017

@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);
Copy link
Contributor

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.)

Copy link
Member Author

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?

Copy link
Contributor

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.

@@ -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()) {
Copy link
Contributor

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?

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();
Copy link
Member

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.

.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();
Copy link
Member

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.
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It here:

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.

Copy link
Member

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.

@michaelwoerister
Copy link
Member Author

@eddyb I'm not entirely sure if that last commit (08660af) is an improvement.

span_bug!(obligation.cause.span,
"Tried to project an inherited associated type during \
coherence checking, which is currently not supported.");
None
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I see now

@nikomatsakis
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented May 17, 2017

📌 Commit 08660af has been approved by nikomatsakis

@bors
Copy link
Contributor

bors commented May 17, 2017

⌛ Testing commit 08660af with merge 4640e18...

bors added a commit that referenced this pull request May 17, 2017
…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
@bors
Copy link
Contributor

bors commented May 18, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: nikomatsakis
Pushing 4640e18 to master...

@bors bors merged commit 08660af into rust-lang:master May 18, 2017
arielb1 added a commit to arielb1/rust that referenced this pull request Aug 8, 2017
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.
bors added a commit that referenced this pull request Aug 8, 2017
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
alexcrichton pushed a commit to alexcrichton/rust that referenced this pull request Aug 23, 2017
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants