Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upIncremental compilation RFC #1298
Conversation
nikomatsakis
added
the
T-compiler
label
Sep 28, 2015
This comment has been minimized.
This comment has been minimized.
larsbergstrom
commented
Sep 28, 2015
|
Awesome! This is about incremental builds for a single-crate, right? If so, it's worth calling that out. Also, if I'm correct, these caches are not meant to be shared across build machines, right? |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis The summary talks about debug builds specifically, but IIRC we discussed how this would apply to release builds as well? (I.e., a story a bit like parallel codegen units, where you'd be trading incrementality against optimization potential due to passing LLVM smaller units of code) |
This comment has been minimized.
This comment has been minimized.
|
@larsbergstrom actually, I believe incremental builds across crates can be done relatively easily, though I didn't discuss it. I will add a TODO item to summarize how that would work. @aturon yes I updated the summary, my mistake. |
This comment has been minimized.
This comment has been minimized.
|
@larsbergstrom added a brief note about cross-crate dependencies |
This comment has been minimized.
This comment has been minimized.
That is correct. |
This comment has been minimized.
This comment has been minimized.
|
How does member function name ( |
This comment has been minimized.
This comment has been minimized.
That "something" is the IR tables that indicate what traits are in scope at a given point, as well as those that collect all the impls for a trait (I did not add an exhaustive listing to the RFC). Those will presumably be linked up something like the following:
That is roughly the idea. Make sense? |
This comment has been minimized.
This comment has been minimized.
|
Yes, that makes sense; thanks. |
bstrie
reviewed
Sep 28, 2015
| strategies can be used to enable lazy or parallel compilation at later | ||
| times. (Eventually, though, it might be nice to restructure the | ||
| compiler so that it operates in more of a demand driven style, rather | ||
| than a series of sweeping passes.) |
This comment has been minimized.
This comment has been minimized.
bstrie
Sep 28, 2015
Contributor
Mind elaborating on what "demand driven style" entails and how it differs from our current approach?
This comment has been minimized.
This comment has been minimized.
|
As an example of what I think is incremental compilation done right, see MSVC. Not only does it have an incremental compilation + linking mode that works fairly well, but it also has an incremental LTCG mode where it does full link time optimization, just incrementally. |
shepmaster
reviewed
Sep 28, 2015
| impl Type { // Path: <root>::foo::<impl1> | ||
| fn bar() {..} // Path: <root>::foo::<impl1>::bar | ||
| } | ||
| impl Type { } // Path: <root>::foo::<impl2> |
This comment has been minimized.
This comment has been minimized.
shepmaster
Sep 28, 2015
Member
Since you don't indicate that every path has a unique integer, this seems to imply that you'd have to know if there are any duplicate children before you start naming, or have some amount of mutability to go back and "fix" the first child when you see the second child.
Is there a possibility to simply leave the first one as <impl> and then mark the second one as <impl2>?
shepmaster
reviewed
Sep 28, 2015
| in which they appear. This does mean that reordering impls may cause | ||
| spurious recompilations. We can try to mitigate this somewhat by making the | ||
| path entry for an impl include some sort of hash for its header or its contents, | ||
| but that will be something we can add later. |
This comment has been minimized.
This comment has been minimized.
shepmaster
Sep 28, 2015
Member
I can't think of any concrete cases, but it seems unlikely that this would be the only spurious recompilation. If you know of others, it might be good to list those somewhere. Maybe even as a staged list (corresponding to your list above?), stating that when we cache object files, X and Y cause unneeded recompilation, but after that only X does.
shepmaster
reviewed
Sep 28, 2015
| The nodes fall into the following categories: | ||
|
|
||
| - **HIR nodes.** Represent some portion of the input HIR. For example, | ||
| the body of a fn as a HIR node (or, perhaps, HIR node). These are |
This comment has been minimized.
This comment has been minimized.
shepmaster
reviewed
Sep 28, 2015
| As you can see, this graph indicates that if the signature of either | ||
| function changes, we will need to rebuild the MIR for `foo`. But there | ||
| is no path from the body of `bar` to the MIR for foo, so changes there | ||
| need not trigger a rebuild. |
This comment has been minimized.
This comment has been minimized.
shepmaster
Sep 28, 2015
Member
I believe this is an understood limitation, but it may be worth pointing out again that this wouldn't allow bar to be inlined into foo, otherwise you'd end up with two versions of bar.
Indeed, this is mentioned in optimization below, so a link down there may be all that is needed.
Actually, this may have more nuance, as if the functions are in the same codegen unit, then there should be a link, I believe.
shepmaster
reviewed
Sep 28, 2015
| In terms of the dependency graph, we would create one IR node | ||
| representing the codegen unit. This would have the object code as an | ||
| associated artifact. We would also have edges from each component of | ||
| the codegen unit. As today. generic or inlined functions would not |
This comment has been minimized.
This comment has been minimized.
shepmaster
reviewed
Sep 28, 2015
|
|
||
| # Alternatives | ||
|
|
||
| This design is an evolution from a prior RFC. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
As I understand it, a large benefit of incremental compilation is speed, but there's no mention of tests that attempt to quantify or ensure that the new world order will be faster. Is there anything more beyond |
frewsxcv
reviewed
Sep 29, 2015
| - Object files | ||
| - This represents the final result of running LLVM. It may be that | ||
| the best strategy is to "cache" compiled code in the form of an | ||
| rlib that is progessively patched, or it may be easier to store |
This comment has been minimized.
This comment has been minimized.
frewsxcv
reviewed
Sep 29, 2015
| currently indexes into the HIR of the appropriate crate, becomes an | ||
| index into the crate's list of paths. | ||
|
|
||
| For the most part, these paths match up with user's intutions. So a |
This comment has been minimized.
This comment has been minimized.
frewsxcv
reviewed
Sep 29, 2015
| } | ||
| ``` | ||
|
|
||
| Note that the impls were arbitarily assigned indices based on the order |
This comment has been minimized.
This comment has been minimized.
frewsxcv
reviewed
Sep 29, 2015
| In general, while I described the general case of a stack of procedure | ||
| nodes, it may be desirable to try and maintain the invariant that | ||
| there is only ever one procedure node on the stack at a | ||
| time. Otherwise, failing to push/pop a procdure at the right time |
This comment has been minimized.
This comment has been minimized.
nrc
assigned
nikomatsakis
Sep 29, 2015
This comment has been minimized.
This comment has been minimized.
comex
commented
Sep 29, 2015
|
Not a very helpful comment, but: |
This comment has been minimized.
This comment has been minimized.
By demand-driven style, what I meant was that we would build a dependency graph that we use to drive compilation. So, for example, we would begin by saying "we need to trans the |
This comment has been minimized.
This comment has been minimized.
In the actual implementation, every path element also has a disambiguating integer. This begins as zero, but when we create a new def-id, we check if the parent already has a child with that name and, if so, increment the disambiguating integer as many times as we have to until we get a unique name. I can tweak the RFC to reflect the impl more precisely. |
This comment has been minimized.
This comment has been minimized.
I don't know what you mean here, actually. Do you mean that if we inlined, then the graph would be wrong? Because that is not the case: this graph refers to the front end's view of things, which is before inlining etc will take place. When we actually do codegen, if |
This comment has been minimized.
This comment has been minimized.
|
Hear ye, hear ye. This RFC is now entering final comment period. |
This comment has been minimized.
This comment has been minimized.
michaelwoerister
commented
Oct 21, 2015
|
I think this RFC is good to go. Conceptually it seems sound to me and it contains enough of a concrete outline to start implementing. |
This comment has been minimized.
This comment has been minimized.
|
I get that as a mere rust user that has never contributed to rustc, I'm basically pontificating on these design decisions that don't affect any public interface. But might somebody comment on whether the alternative of building the dependency graph explicitly and then processing it (lazily or otherwise) as I wrote earlier was considered? |
This comment has been minimized.
This comment has been minimized.
Sorry, I meant to reply to your comment earlier. I did consider that design On Wed, Oct 21, 2015 at 1:33 PM, John Ericson notifications@github.com
|
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis Thank you, that is very reassuring. I absolutely agree on the soundness issue; in fact I'd say without refactoring to make sure the graph traversals are correct by construction, there's hardly any point in taking my route. Sounds like your view is the implicit dependency route is a good way to accurately catch all dependencies without forcing the big refactor, but explicit dependencies is a decent end goal? |
This comment has been minimized.
This comment has been minimized.
|
I think there will always be some of both. Some dependencies at least On Wed, Oct 21, 2015 at 7:33 PM, John Ericson notifications@github.com
|
This comment has been minimized.
This comment has been minimized.
|
Ah. I envisioned stuff like that working by the traversal of one graph creating another. |
This comment has been minimized.
This comment has been minimized.
|
To clarify. Suppose we have something like token tree -(macros)-> collection of items -(type-checking and method resolution...)-> collection of MIR -(llvm)-> collection of bitcode. To really do laziness right with this, not only would the graphs be traversed lazily, but also created lazily. The MIR for each function would be bundled with a thunk to generate the MIR for all referenced functions. [For any Nix users out there (cough @eddyb cough) this is related to doing things like |
This comment has been minimized.
This comment has been minimized.
|
Finally, I mentioned earlier I'd love to right some generic library to persist/cache all that. To make that a bit more concrete I was thinking of something like https://github.com/dmbarbour/haskell-vcache or https://github.com/mirage/irmin along with some infrastructure to serialize thunks. |
This comment has been minimized.
This comment has been minimized.
bkoropoff
commented
Oct 23, 2015
|
This looks great to me. The greatest challenge is going to be building a dependency graph that is as precise as possible (to get maximum benefit) without introducing unsoundness. I don't see any silver bullets here; just "be really careful" and "test a lot". There may be an interesting class of source code changes affecting lifetime or variance inference where typechecking artifacts are invalidated, but it is theoretically possible to avoid invalidating trans artifacts since lifetimes are erased by then. I haven't thought of any concrete examples that would be worth exploiting, however. |
This comment has been minimized.
This comment has been minimized.
michaelwoerister
commented
Oct 23, 2015
|
One thing that is not mentioned in the RFC at all yet is monomorphization and the consequences it has.
One complication I can see here is that we can only know after type-checking which monomorphizations are still used, but the proposed algorithm already wants to garbage-collect the on-disk cache right after building the HIR. This has to be accounted for somehow. |
This comment has been minimized.
This comment has been minimized.
|
True. We don't actually know what monomorphizations we want until trans. I've also been thinking about what it would take to do an early target that
On Fri, Oct 23, 2015 at 10:20 AM, Michael Woerister <
|
This comment has been minimized.
This comment has been minimized.
|
We already save the type-checked body of monomorphizable fns. |
This comment has been minimized.
This comment has been minimized.
Yes, but what we are mostly talking about is preserving the monomorphized Well, I guess I was saying that for a first draft, it might not be worth On Fri, Oct 23, 2015 at 1:18 PM, arielb1 notifications@github.com wrote:
|
This comment has been minimized.
This comment has been minimized.
|
Maybe convert all translation to use inlining and save the serialized data (we would also need to have some way of stably comparing it for this to work). Using serialized MIR instead of serialized AST may make this easier, but I feel like the issues are orthogonal. |
This comment has been minimized.
This comment has been minimized.
|
@arielb1 I'm not clear on what problem you are proposing to solve here? (I On Fri, Oct 23, 2015 at 4:16 PM, arielb1 notifications@github.com wrote:
|
This comment has been minimized.
This comment has been minimized.
michaelwoerister
commented
Oct 24, 2015
Isn't it proposed anyway that the complete set of items hashed on every compilation? |
bstrie
reviewed
Nov 3, 2015
| ## Basic usage | ||
|
|
||
| The basic usage will be that one enables incremental compilation using | ||
| a compiler flag like `-C incremental-compilation=TMPDIR`. The `TMPDIR` |
This comment has been minimized.
This comment has been minimized.
bstrie
Nov 3, 2015
Contributor
Do you expect that Cargo will pass this flag by default for all projects?
bstrie
reviewed
Nov 3, 2015
| directory is intended to be an empty directory that the compiler can | ||
| use to store intermediate by-products; the compiler will automatically | ||
| "GC" this directory, deleting older files that are no longer relevant | ||
| and creating new ones. |
This comment has been minimized.
This comment has been minimized.
bstrie
Nov 3, 2015
Contributor
When does this GC happen? Will it be sort of like Git where GCs can potentially happen whenever you type any command?
This comment has been minimized.
This comment has been minimized.
michaelwoerister
Nov 3, 2015
I would expect it to run on every compiler run using the given directory.
bstrie
reviewed
Nov 3, 2015
|
|
||
| Regardless of whether it is invoked in incremental compilation mode or | ||
| not, the compiler will always parse and macro expand the entire crate, | ||
| resulting in a HIR tree. Once we have a complete HIR tree, and if we |
This comment has been minimized.
This comment has been minimized.
bstrie
Nov 3, 2015
Contributor
Experience suggests that an important contributor to compilation time are syntax extensions like regex! and Serde's annotations, which will seemingly be left out in the cold here. Do you have any ideas for incrementalizing/caching things in this area?
This comment has been minimized.
This comment has been minimized.
eddyb
Nov 3, 2015
Member
IME most of the time spent in those cases is on later stages, processing the large amounts of code generated by the syntax extension.
This comment has been minimized.
This comment has been minimized.
bstrie
Nov 3, 2015
Contributor
Ah, I should have profiled instead of assuming that the slowdown was in the expansion phase itself. :P
bstrie
reviewed
Nov 3, 2015
| When we come to the final LLVM stages, we must | ||
| [separate the functions into distinct "codegen units"](#optimization) | ||
| for the purpose of LLVM code generation. This will build on the | ||
| existing "codegen-units" used for parallel code generation. LLVM may |
This comment has been minimized.
This comment has been minimized.
bstrie
Nov 3, 2015
Contributor
Does it merely build on the existing codegen-units, or does it replace it entirely? Would seem a little odd to have both exposed.
This comment has been minimized.
This comment has been minimized.
michaelwoerister
Nov 3, 2015
I think, it doesn't make much sense to allow the user to specify the number of codegen-units when compiling incrementally. The compiler needs more control over what ends up where and, at least in the beginning, I would expect the -Ccodgegen-units option to be ignored (with a note to the user) for incremental builds.
This comment has been minimized.
This comment has been minimized.
|
Huzzah! The compiler team has decided to accept this RFC. The expectation is that the actual impl will discover numerous surprises (we've already found a few) that require adjustments, and that we will come back and update the RFC to be more inline with the final design when that has shaken out a bit. |
nikomatsakis
merged commit 59b01f1
into
rust-lang:master
Nov 6, 2015
This comment has been minimized.
This comment has been minimized.
matthewhammer
commented
Dec 6, 2015
|
There's lots of interesting talk about incremental computation in this thread, which is great! In case anyone was wondering about PL research literature on this topic, these researchers have also been thinking about incremental, demand-driven compilation / computation:
The first paper is more recent, and specialized to a situation similar to the one described in the discussion above (incremental compilation, using demand-driven, dynamic dependency graphs). The second paper gives a general approach for such incremental, demand-driven computations. There is follow-on work on adapton.org. |
jonas-schievink
referenced this pull request
Dec 26, 2015
Closed
Don't recheck files for 'build' if they have already passed 'check' #2248
This comment has been minimized.
This comment has been minimized.
White-Oak
commented
Dec 30, 2015
|
Any update on the state of implementation? |
This comment has been minimized.
This comment has been minimized.
|
@White-Oak Creation of a dependency graph is being done in rust-lang/rust#30532 |
nikomatsakis commentedSep 28, 2015
High-level strategy for incremental compilation.
cc @rust-lang/compiler
Rendered