Skip to content

Add sbt incremental compilation support #1244

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

Merged
merged 6 commits into from
May 29, 2016

Conversation

smarter
Copy link
Member

@smarter smarter commented May 2, 2016

Review by @DarkDimius


To test this with sbt, see
https://github.com/lampepfl/dotty/wiki/Using-Dotty-with-sbt

The following flags are added:

  • -Yforce-sbt-phases: Run the phases used by sbt for incremental compilation
    (ExtractDependencies and ExtractAPI) even if the compiler is ran outside of
    sbt, for debugging.
  • -Ydump-sbt-inc: For every compiled foo.scala, output the sbt API
    representation and dependencies in foo.inc, implies -Yforce-sbt-phases.

This commit introduces two new phases which do not transform trees:

  • ExtractDependencies which extracts the dependency information of the current
    compilation unit and sends it to sbt via callbacks
  • ExtractAPI which creates a representation of the API of the current compilation
    unit and sends it to sbt via callbacks

Briefly, when a file changes sbt will recompile it, if its API has
changed (determined by what ExtractAPI sent) then sbt will determine
which reverse-dependencies (determined by what ExtractDependencies
sent) of the API have to be recompiled depending on what changed.
See http://www.scala-sbt.org/0.13/docs/Understanding-Recompilation.html for
more information on how sbt incremental compilation works.

This phase was originally based on
https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt
which attempts to integrate the sbt phases into scalac (and is itself based
on https://github.com/sbt/sbt/tree/0.13/compile/interface/src/main/scala/xsbt),
but it has been heavily refactored and adapted to Dotty. The main
functional differences are:

  • ExtractDependencies runs right after Frontend (so that we don't lose
    dependency informations because of the simplifications done by PostTyper),
    but ExtractAPI runs right after PostTyper (so that SuperAccessors are
    part of the API).
  • ExtractAPI only extract types as they are defined and never "as seen
    from" some some specific prefix, see its documentation for more details.
  • ExtractDependenciesTraverser and ExtractUsedNames have been fused into
    one tree traversal in ExtractDependenciesCollector.

TODO: Try to run these phases in parallel with the rest of the compiler
pipeline since they're independent (except for the sbt callbacks in GenBCode) ?

@smarter
Copy link
Member Author

smarter commented May 2, 2016

To run the scripted tests in dotty-bridge:

$ sbt
> scripted source-dependencies/*

@@ -25,4 +27,12 @@
* Example: ./src/library/scala/collection/Seq.scala
*/
default void onSourceCompiled(SourceFile source) {};

default void sourceDependency(File dependsOn, File source, xsbti.DependencyContext context) {};
Copy link
Contributor

Choose a reason for hiding this comment

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

I dont see what a method does given its name. Should it be addSourceDependency?

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'll remove these extra callbacks, they're not needed now that I added Context#sbtCallback instead.

@smarter smarter force-pushed the add/sbt-phase branch 2 times, most recently from 94c6809 to 5c5bdec Compare May 7, 2016 20:38
@smarter smarter changed the title [DO NOT MERGE] Add sbt incremental compilation support via a special phase Add sbt incremental compilation support via a special phase May 7, 2016
@@ -160,6 +160,8 @@ class ScalaSettings extends Settings.SettingGroup {
val YtestPickler = BooleanSetting("-Ytest-pickler", "self-test for pickling functionality; should be used with -Ystop-after:pickler")
val YcheckReentrant = BooleanSetting("-Ycheck-reentrant", "check that compiled program does not contain vars that can be accessed from a global root.")
val YkeepComments = BooleanSetting("-Ykeep-comments", "Keep comments when scanning source files.")
val YforceSbtPhase = BooleanSetting("-Yforce-sbt-phase", "Run the sbt incremental compiler phase even if the compiler is ran outside of sbt, for debugging.")
val YdumpSbtApi = BooleanSetting("-Ydump-sbt-api", "For every compiled foo.scala, output the sbt API representation in foo.api, implies -Yforce-sbt-phase.")
Copy link
Contributor

Choose a reason for hiding this comment

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

dependsOn(YforceSbtPhase)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like this would force the user to write -Yforce-sbt-phase -Ydump-sbt-api, I can do the change if you want but I don't think it's very useful

@smarter smarter force-pushed the add/sbt-phase branch 4 times, most recently from fd79ba0 to 341e76c Compare May 9, 2016 23:10
@smarter
Copy link
Member Author

smarter commented May 10, 2016

I didn't realize this before but there's a single test that does not pass now: trait-super because it relies on the presence or absence of superaccesors in the API to know what should be recompiled, but in dotty superaccessors are created by PostTyper but the sbt phase is run before PostTyper so that it can see constant types and trees of types (and so that the constants test pass), I don't know if there's a way to fix this without doing some phase-travelling. Anyway, this is something that can be fixed later.

@smarter smarter force-pushed the add/sbt-phase branch 2 times, most recently from 401ca9e to fffa04e Compare May 16, 2016 21:38
@smarter smarter changed the title Add sbt incremental compilation support via a special phase Add sbt incremental compilation support May 16, 2016
@smarter smarter force-pushed the add/sbt-phase branch 2 times, most recently from 55340fe to 5dae0a7 Compare May 16, 2016 21:41
smarter added a commit to smarter/dotty-bridge that referenced this pull request May 16, 2016
This involved some mad ClassLoader hacks, see `CompilerClassLoader`.

Starting from this commit, dotty-bridge will only work with a dotty
where scala/scala3#1244 has been merged.
@smarter
Copy link
Member Author

smarter commented May 16, 2016

OK I just fixed trait-super by splitting the sbt phase into two phases, ExtractDependencies before PostTyper and ExtractAPI after PostTyper (they were already completely independent before, but ran in the same phase one after the other), this works out because:

  • ExtractDependencies needs to be before PostTyper to avoid loosing dependency informations (see constants for an example where this matters).
  • ExtractAPI needs to be after PostTyper because SuperAccessors need to be part of the API (see again trait-super for an example where this matter). Unlike ExtractDependencies, the simplication to trees done by PostTyper do not affect this phase because it only cares about definitions, and PostTyper does not change definitions.

I've also cleaned up the changes needed to dotty-bridge, they now sit in smarter/dotty-bridge#1 and will be merged once this PR has been merged.

@smarter
Copy link
Member Author

smarter commented May 16, 2016

/cc @gkossakowski and @adriaanm who might be interested to know that this exist (also thanks to both of you for the discussions we had on incremental compilation!)

smarter referenced this pull request in adriaanm/scala May 16, 2016
quick hack to experiment with stability/perf of api extraction

I tried to stay as close to the way it's done in sbt,
EXCEPT for fusing all traversals into one tree walk
(there's still some local redundant tree.foreach'ing, but should be ok)

i do believe integrating sbt's compiler interface into the compiler is the way forward
smarter added a commit to smarter/dotty-bridge that referenced this pull request May 16, 2016
This involved some mad ClassLoader hacks, see `CompilerClassLoader`.

Starting from this commit, dotty-bridge will only work with a dotty
where scala/scala3#1244 has been merged.
@smarter smarter force-pushed the add/sbt-phase branch 2 times, most recently from d318f9f to 64d2b47 Compare May 16, 2016 22:32
smarter added a commit to smarter/dotty-bridge that referenced this pull request May 20, 2016
This involved some mad ClassLoader hacks, see `CompilerClassLoader`.

Starting from this commit, dotty-bridge will only work with a dotty
where scala/scala3#1244 has been merged.
smarter added a commit to smarter/dotty-bridge that referenced this pull request May 20, 2016
This involved some mad ClassLoader hacks, see `CompilerClassLoader`.

Starting from this commit, dotty-bridge will only work with a dotty
where scala/scala3#1244 has been merged.
@DarkDimius
Copy link
Contributor

Needs rebasing.

@@ -41,56 +41,58 @@ class Compiler {
*/
def phases: List[List[Phase]] =
List(
List(new FrontEnd), // Compiler frontend: scanner, parser, namer, typer
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer not to loose git history here.
Indentation is not that important.

Additionally, it will introduce a lot of merge conflicts with linker.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, will revert formatting change (though note that git blame -w and git diff -w make this less important)

@DarkDimius
Copy link
Contributor

I've done a quick review, as I'm not into details of xsbti.api.

I'd be curious to see how much does introduction of this phase affect compilation time of dotty&stdlib.

Otherwise LGTM

@smarter
Copy link
Member Author

smarter commented May 28, 2016

I'd be curious to see how much does introduction of this phase affect compilation time of dotty&stdlib.

I spent a bunch of time optimizing it and currently it adds a few seconds to compilation, most of that time is just because of the full AST traversal done by ExtractDependenciesCollector which I don't think we can avoid (I don't think ExtractDependencies can be fused with any other phase). It would be interesting to see if we could just run it in a separate thread.

To test this with sbt, see
https://github.com/lampepfl/dotty/wiki/Using-Dotty-with-sbt

The following flags are added:
 - -Yforce-sbt-phases: Run the phases used by sbt for incremental compilation
   (ExtractDependencies and ExtractAPI) even if the compiler is ran outside of
   sbt, for debugging.
 - -Ydump-sbt-inc: For every compiled foo.scala, output the API
   representation and dependencies used for sbt incremental compilation
   in foo.inc, implies -Yforce-sbt-phases.

This commit introduces two new phases which do not transform trees:
- `ExtractDependencies` which extracts the dependency information of the current
  compilation unit and sends it to sbt via callbacks
- `ExtractAPI` which creates a representation of the API of the current compilation
  unit and sends it to sbt via callbacks

Briefly, when a file changes sbt will recompile it, if its API has
changed (determined by what `ExtractAPI` sent) then sbt will determine
which reverse-dependencies (determined by what `ExtractDependencies`
sent) of the API have to be recompiled depending on what changed.
See http://www.scala-sbt.org/0.13/docs/Understanding-Recompilation.html for
more information on how sbt incremental compilation works.

This phase was originally based on
https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt
which attempts to integrate the sbt phases into scalac (and is itself based
on https://github.com/sbt/sbt/tree/0.13/compile/interface/src/main/scala/xsbt),
but it has been heavily refactored and adapted to Dotty. The main
functional differences are:
- ExtractDependencies runs right after Frontend (so that we don't lose
  dependency informations because of the simplifications done by PostTyper),
  but ExtractAPI runs right after PostTyper (so that SuperAccessors are
  part of the API).
- `ExtractAPI` only extract types as they are defined and never "as seen
  from" some some specific prefix, see its documentation for more details.
- `ExtractDependenciesTraverser` and `ExtractUsedNames` have been fused into
  one tree traversal in `ExtractDependenciesCollector`.

TODO: Try to run these phases in parallel with the rest of the compiler
pipeline since they're independent (except for the sbt callbacks in `GenBCode`) ?
@smarter smarter merged commit fabe697 into scala:master May 29, 2016
@liufengyun
Copy link
Contributor

We have a big improvement in performance, I guess it's due to incremental compilation:

https://d-d.me/tnc/dotty/web/

@smarter
Copy link
Member Author

smarter commented May 30, 2016

That's very weird, the incremental compilation cannot possibily be used by these tests, they're not set up to be run through sbt and the dotty-bridge

smarter added a commit to smarter/dotty-bridge that referenced this pull request Jun 3, 2016
This involved some mad ClassLoader hacks, see `CompilerClassLoader`.

Starting from this commit, dotty-bridge will only work with a dotty
where scala/scala3#1244 has been merged.
@Blaisorblade
Copy link
Contributor

ExtractAPI runs right after PostTyper (so that SuperAccessors are part of the API).

@smarter Can you confirm this prevents issues like sbt/sbt#466, as I'm thinking?

To summarize: nowadays adding super calls to a trait requires recompiling implementing classes/objects, but SBT does not manage, because superaccessors is ignored by SBT (in sbt/sbt#466 (comment) I wondered whether that could be fixed). It seems your design prevents such problems?

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.

4 participants