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
Rewrite DependencyGraph [ci: last-only] #10687
base: 2.13.x
Are you sure you want to change the base?
Conversation
what prompted you to pry open the lid of this haunted ancient crypt? |
9b060a8
to
dc0240c
Compare
Will replace the algo, but thought I'd tease it apart first, also to run the benchmark for before/after. Also, forgot to review dotty phase fusion. |
336822f
to
b205cad
Compare
b205cad
to
71e0165
Compare
"parser" and "terminal" are still special names, but should rely only on |
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 looks a lot clearer - I need more time to look into compilerPhaseList
though. Do you write graph algorithm textbooks in your spare time?
Some high-level questions.
I assume it can give different results than 2.13.13? It's not unlikely to have loose dependency graphs, I remember lightbend/genjavadoc#191 from last time. Would it be possible to
- also run the old algorithm, warn if there are changes?
- warn about loose graphs? I guess that's not actionalbe though, when using multiple plugins.
Is the algorithm deterministic? I see the starting point is global.phasesSet
, which is a hash set. Would it make sense to sort them in DependencyGraph.apply
?
//size += 1 | ||
adjacency(v) ::= Edge(from = v, to = w, weight) | ||
case Some(e) if weight == FollowsNow => // use runsRightAfter edge FollowsNow | ||
adjacency(v) = Edge(from = v, to = w, weight) :: adjacency(v).filterNot(e => e.from == v && e.to == w) |
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.
when do we get 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.
There is already v -> w
(runsBefore) and we add v => w
(runsRightAfter), retaining the =>
(for FollowsNow
).
Otherwise, the new edge is normal Follows
so keep existing edge, which can't have lower weight.
Can you elaborate how the new implementation is better? Did you compare the benchmark before / after? It seems performance is relevant e83ea44 |
I assume CB will show broken constraints (since the compiler had to be tweaked); see the OP comment on async and scaladoc. For example, I had to accommodate having a Users could compare old and new by trying the old and new compilers. This is a topo sort for longest path, where Recently I used github search for I ran the retronym benchmark FWIW, which was in the context of adding many phases in order. The unit test agrees that the old code is slow with more phases. This either doesn't matter IRL, or matters to anyone running many compilations in the cloud.
after, also showing a cost for a tweak for correctness
|
71e0165
to
e588119
Compare
sample new order with acyclic.
|
Assuming the new implementation is correct, the compiler behaves as specified; but there will be valid changes within the constrants that might uncover bugs in compiler plugins, or it can break projects that were the constraints are too lax and everything was working by accident. Is release notes enough? @SethTisue
I don't follow; if the test everything is deterministic, but in the compiler the phases go through a hash set. |
In general, the phases are assigned their greatest distance from parser (longest path); in each equivalence class, the phases are sorted deterministically (by the tuple In that test, I was setting up the "actual" constraints to fix a bug (in handling BTW, that bug convinced me that simple things are easier to fix. Also, "basically correct except for this bug" is easier to fix than "code with unknown behavior which, like a broken clock, is occasionally correct, but has this erroneous test we don't know how to fix". Since there is a good chance that any given plugin "works by chance", if we can get an actual snapshot of CB, I would follow up with tweaks. Besides plugins, someone may also use the "internal API" to manage phases, like Scaladoc. |
Good, thanks! |
I don't doubt the new code is cleaner and faster, but I'm not convinced we should merge this, on cost/benefit grounds. Any behavior change here that affects actual plugins — even if the change could technically be classified as a progression rather than regression — is an ecosystem cost. See Lukas's remarks above. As for potential benefits
|
retronym's previous swing was due to a real customer. The unit test is newly commented that the old code took 20 seconds; I forgot to revert the test from 64K because it is fast now. If you run CB, I'll follow up with mitigations. |
Ah! I've contacted him and invited him to comment.
I can definitely do that (but after 2.13.14 ships). |
Note to self: needs some clean-up and code comments. For mitigating change, it should revert to the previous sorting (alpha-only, not internal-first). The API should not require
This PR makes everything follow the initial phase; a lack of constraint does not mean "don't run me". I don't remember what the old code intended with "orphan" nodes. |
e588119
to
6665398
Compare
I verified that |
Improve
PhaseAssembly
for correctness and performance.One test correctly assembles the phases now but failed before.
One nuance is that phases at the same "level" (distance from parser) used to be sorted only by name,
but now are sorted first by whether they are "internal" or supplied by a plugin. The async test relied on the old sorting but arguably usingrunsRightAfter
is more correct.Scaladoc was lax about its
phasesSet
, which is typically the sum of internal, platform, and plugin components. Now Scaladoc only adds the phases it wants.Some anomalies such as empty phase names (for
runsAfter
constraints) have been corrected, but will be ignored.The initial (parser) and terminal (terminal) phases are required. The standard terminal component is added if there is none. All other phases run after parser and before terminal.
The
phasesSet
is closed; a phase can't depend on a spurious phase name.Fixes scala/bug#8755