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

Handle negative cycles correctly in the recursive solver #526

Closed

Conversation

nikomatsakis
Copy link
Contributor

This also includes a chapter in the chalk book explaining how it works.

This includes that first commit from #399 (and some further cleanups).

cc @flodiebold @zaharidichev

It's not really needed. The idea is to make an assumption about the
recursive case and then compute answers based on that assumption -- we
can then update the solution with the new one, we don't have to *merge*
them.
This implements the scheme described in the book edits
from the previous commit. In particular:

* refuting a goal on the stack is always ambiguous
* we do not cache results that were dependent on a negative cycle
@nikomatsakis
Copy link
Contributor Author

To review, I would recommend reading the "document how negative cycles work" commit first.

@jackh726
Copy link
Contributor

Wait so we're actually going to handle negative cycles in the recursive solver? Is it worth it? And, if so, should we add it back to the SLG solver?

@nikomatsakis
Copy link
Contributor Author

nikomatsakis commented Jun 15, 2020

@jackh726 Good question. It's fairly easy to handle in the recursive solver, in any case, and the old behavior was just wrong. I'm not sure if we really need to be able to handle negative cycles or not, I feel like I had an example of a case where we might need to, probably involving specialization, but I don't remember the details. I left a comment about it somewhere.

UPDATE: This comment and the one after it are the cases I was thinking of, I believe. However, I realize now that, at least in the current rustc code base, I think that a test like this would result in a cycle error, because we do not allow you to construct potential specializations unless there is a strict DAG between the traits (and here, the Iterator trait references itself).

return *minimums;
}

let old_answer = &self.context.search_graph[dfn].solution;
Copy link
Member

Choose a reason for hiding this comment

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

so was all the priority handling here just not necessary, or is it somehow subsumed by the negative cycle handling? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this comment is referring to the changes in the first commit, which remove the logic that merges the "previous answer" (from prior iteration rounds) with the "new answer" (from the current iteration round). I don't believe that logic was ever necessary, precisely because of the monotonic nature of solving -- i.e., each round should be producing strictly more answers than the round before, so it should include whatever answers we were getting before. The only exception would be negative cycles, but we are always producing the same result for those ("ambiguous").

Copy link
Member

Choose a reason for hiding this comment

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

Hmm I do remember adding it for a reason -- though I wonder whether I didn't manage to write a test for it, or whether that just works now. I think it was related to the fact that the priority logic does break this monotonic nature -- you can get a solution involving Trait::AssocType<> in the first round, and then get the properly normalized type in the second round. (The priority logic could maybe be interpreted as having a "not (the normalize rule applies)" in the fallback rule?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, good point! I remember us discussing this a bit. Sigh, things are complex. =) I guess we should see if we can craft an example where this actually causes a problem. I can imagine some cases where it might.

One situation would be around the infamous logic like

trait SomeTrait {
  type Item: Eq;
}

fn foo<T, X: SomeTrait<Item=T>>() {
}

where we (in rustc) fail to figure out that T: Eq.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Still, I think it was important to remove that "merge with the old result" logic -- but maybe more for the coinductive case than for this negative cycle handling, actually.

G2 :- not { G1 }.
```

Here we have a negative cycle for `G1`, but only through one of the two
Copy link
Contributor

Choose a reason for hiding this comment

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

The other clause will give back a positive result.

I might be missing something by how would that be. Isn't this other clause G3. If so how does it give a positive result if there is no rule for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, that's confusing. G3 is the "other subgoal". The other clause is the clause G1., which is trivially true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll try to rework the test to make it clearer at some point.

Copy link
Member

Choose a reason for hiding this comment

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

I think the 'other clause' here is G1.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah okey, I got it

@nikomatsakis
Copy link
Contributor Author

Hmm so @jackh726's comment about "Do we really want to support neg cycles" is on the money -- since, if we don't, it's going to simplify greatly the salsa integration. It's easy enough to extend the recursive solver to support them, but I think it's harder to "generalize" the concept, at least it's less obvious to me how to do it. i guess it's really worth trying to look carefully at those places we use not goals and see whether we think it is possible to create a negative cycle. I had sort of convinced myself that, through specialization, it was, but now I think I may have been mistaken because of the limits we place on the specialization rules.

@nikomatsakis
Copy link
Contributor Author

I'm going to close this for the time being.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants