@@ -11,7 +11,7 @@ simple extension to the overall query system. It relies on the fact that:
1111This chapter will explain how we can use these properties for making things
1212incremental and then goes on to discuss version implementation issues.
1313
14- # A Basic Algorithm For Incremental Query Evaluation
14+ ## A Basic Algorithm For Incremental Query Evaluation
1515
1616As explained in the [ query evaluation model primer] [ query-model ] , query
1717invocations form a directed-acyclic graph. Here's the example from the
@@ -36,17 +36,17 @@ we know which queries were invoked (the nodes of the graph) and for each
3636invocation, which other queries or input has gone into computing the query's
3737result (the edges of the graph).
3838
39- Now suppose, we change the source code of our program so that
39+ Now suppose we change the source code of our program so that
4040HIR of ` bar ` looks different than before. Our goal is to only recompute
41- those queries that are actually affected by the change while just re-using
41+ those queries that are actually affected by the change while re-using
4242the cached results of all the other queries. Given the dependency graph we can
4343do exactly that. For a given query invocation, the graph tells us exactly
4444what data has gone into computing its results, we just have to follow the
4545edges until we reach something that has changed. If we don't encounter
4646anything that has changed, we know that the query still would evaluate to
4747the same result we already have in our cache.
4848
49- Taking the ` type_of(foo) ` invocation from above as example, we can check
49+ Taking the ` type_of(foo) ` invocation from above as an example, we can check
5050whether the cached result is still valid by following the edges to its
5151inputs. The only edge leads to ` Hir(foo) ` , an input that has not been affected
5252by the change. So we know that the cached result for ` type_of(foo) ` is still
@@ -63,9 +63,9 @@ turn will re-run `type_of(bar)`, which will yield an up-to-date result
6363because it reads the up-to-date version of ` Hir(bar) ` .
6464
6565
66- # The Problem With The Basic Algorithm: False Positives
66+ ## The Problem With The Basic Algorithm: False Positives
6767
68- If you read the previous paragraph carefully, you'll notice that it says that
68+ If you read the previous paragraph carefully you'll notice that it says that
6969` type_of(bar) ` * might* have changed because one of its inputs has changed.
7070There's also the possibility that it might still yield exactly the same
7171result * even though* its input has changed. Consider an example with a
@@ -90,15 +90,15 @@ of examples like this and small changes to the input often potentially affect
9090very large parts of the output binaries. As a consequence, we had to make the
9191change detection system smarter and more accurate.
9292
93- # Improving Accuracy: The red-green Algorithm
93+ ## Improving Accuracy: The red-green Algorithm
9494
9595The "false positives" problem can be solved by interleaving change detection
9696and query re-evaluation. Instead of walking the graph all the way to the
9797inputs when trying to find out if some cached result is still valid, we can
9898check if a result has * actually* changed after we were forced to re-evaluate
9999it.
100100
101- We call this algorithm, for better or worse, the red-green algorithm because nodes
101+ We call this algorithm the red-green algorithm because nodes
102102in the dependency graph are assigned the color green if we were able to prove
103103that its cached result is still valid and the color red if the result has
104104turned out to be different after re-evaluating it.
@@ -128,7 +128,7 @@ fn try_mark_green(tcx, current_node) -> bool {
128128 return false
129129 }
130130 Unknown => {
131- // This is the first time we are look at this node. Let's try
131+ // This is the first time we look at this node. Let's try
132132 // to mark it green by calling try_mark_green() recursively.
133133 if try_mark_green(tcx, dependency) {
134134 // We successfully marked the input as green, on to the
@@ -186,15 +186,15 @@ invoke the query provider to re-compute the result.
186186
187187
188188
189- # The Real World: How Persistence Makes Everything Complicated
189+ ## The Real World: How Persistence Makes Everything Complicated
190190
191191The sections above described the underlying algorithm for incremental
192192compilation but because the compiler process exits after being finished and
193- takes the query context with its result cache with it into oblivion, we have
193+ takes the query context with its result cache with it into oblivion, we have to
194194persist data to disk, so the next compilation session can make use of it.
195195This comes with a whole new set of implementation challenges:
196196
197- - The query results cache is stored to disk, so they are not readily available
197+ - The query result cache is stored to disk, so they are not readily available
198198 for change comparison.
199199- A subsequent compilation session will start off with new version of the code
200200 that has arbitrary changes applied to it. All kinds of IDs and indices that
@@ -205,11 +205,11 @@ This comes with a whole new set of implementation challenges:
205205- Persisting things to disk comes at a cost, so not every tiny piece of
206206 information should be actually cached in between compilation sessions.
207207 Fixed-sized, plain-old-data is preferred to complex things that need to run
208- branching code during (de-)serialization.
208+ through an expensive (de-)serialization step .
209209
210210The following sections describe how the compiler currently solves these issues.
211211
212- ## A Question Of Stability: Bridging The Gap Between Compilation Sessions
212+ ### A Question Of Stability: Bridging The Gap Between Compilation Sessions
213213
214214As noted before, various IDs (like ` DefId ` ) are generated by the compiler in a
215215way that depends on the contents of the source code being compiled. ID assignment
@@ -253,7 +253,7 @@ the `LocalId`s within it are still the same.
253253
254254
255255
256- ## Checking Query Results For Changes: StableHash And Fingerprints
256+ ### Checking Query Results For Changes: HashStable And Fingerprints
257257
258258In order to do red-green-marking we often need to check if the result of a
259259query has changed compared to the result it had during the previous
@@ -273,7 +273,7 @@ value of the result. We call this hash value "the `Fingerprint` of the query
273273result". The hashing is (and has to be) done "in a stable way". This means
274274that whenever something is hashed that might change in between compilation
275275sessions (e.g. a ` DefId ` ), we instead hash its stable equivalent
276- (e.g. the corresponding ` DefPath ` ). That's what the whole ` StableHash `
276+ (e.g. the corresponding ` DefPath ` ). That's what the whole ` HashStable `
277277infrastructure is for. This way ` Fingerprint ` s computed in two
278278different compilation sessions are still comparable.
279279
@@ -300,12 +300,8 @@ This approach works rather well but it's not without flaws:
300300 use a good and thus expensive hash function, and we have to map things to
301301 their stable equivalents while doing the hashing.
302302
303- In the future we might want to explore different approaches to this problem.
304- For now it's ` StableHash ` and ` Fingerprint ` .
305303
306-
307-
308- ## A Tale Of Two DepGraphs: The Old And The New
304+ ### A Tale Of Two DepGraphs: The Old And The New
309305
310306The initial description of dependency tracking glosses over a few details
311307that quickly become a head scratcher when actually trying to implement things.
@@ -327,7 +323,7 @@ the given fingerprint, it means that the query key refers to something that
327323did not yet exist in the previous session.
328324
329325So, having found the dep-node in the previous dependency graph, we can look
330- up its dependencies (also dep-nodes in the previous graph) and continue with
326+ up its dependencies (i.e. also dep-nodes in the previous graph) and continue with
331327the rest of the try-mark-green algorithm. The next interesting thing happens
332328when we successfully marked the node as green. At that point we copy the node
333329and the edges to its dependencies from the old graph into the new graph. We
@@ -343,12 +339,86 @@ new graph is serialized out to disk, alongside the query result cache, and can
343339act as the previous dep-graph in a subsequent compilation session.
344340
345341
346- ## Didn't You Forget Something?: Cache Promotion
347- TODO
342+ ### Didn't You Forget Something?: Cache Promotion
343+
344+ The system described so far has a somewhat subtle property: If all inputs of a
345+ dep-node are green then the dep-node itself can be marked as green without
346+ computing or loading the corresponding query result. Applying this property
347+ transitively often leads to the situation that some intermediate results are
348+ never actually loaded from disk, as in the following example:
349+
350+ ``` ignore
351+ input(A) <-- intermediate_query(B) <-- leaf_query(C)
352+ ```
353+
354+ The compiler might need the value of ` leaf_query(C) ` in order to generate some
355+ output artifact. If it can mark ` leaf_query(C) ` as green, it will load the
356+ result from the on-disk cache. The result of ` intermediate_query(B) ` is never
357+ loaded though. As a consequence, when the compiler persists the * new* result
358+ cache by writing all in-memory query results to disk, ` intermediate_query(B) `
359+ will not be in memory and thus will be missing from the new result cache.
360+
361+ If there subsequently is another compilation session that actually needs the
362+ result of ` intermediate_query(B) ` it will have to be re-computed even though we
363+ had a perfectly valid result for it in the cache just before.
364+
365+ In order to prevent this from happening, the compiler does something called
366+ "cache promotion": Before emitting the new result cache it will walk all green
367+ dep-nodes and make sure that their query result is loaded into memory. That way
368+ the result cache doesn't unnecessarily shrink again.
369+
370+
371+
372+ # Incremental Compilation and the Compiler Backend
373+
374+ The compiler backend, the part involving LLVM, is using the query system but
375+ it is not implemented in terms of queries itself. As a consequence
376+ it does not automatically partake in dependency tracking. However, the manual
377+ integration with the tracking system is pretty straight-forward. The compiler
378+ simply tracks what queries get invoked when generating the initial LLVM version
379+ of each codegen unit, which results in a dep-node for each of them. In
380+ subsequent compilation sessions it then tries to mark the dep-node for a CGU as
381+ green. If it succeeds it knows that the corresponding object and bitcode files
382+ on disk are still valid. If it doesn't succeed, the entire codegen unit has to
383+ be recompiled.
384+
385+ This is the same approach that is used for regular queries. The main differences
386+ are:
387+
388+ - that we cannot easily compute a fingerprint for LLVM modules (because
389+ they are opaque C++ objects),
390+
391+ - that the logic for dealing with cached values is rather different from
392+ regular queries because here we have bitcode and object files instead of
393+ serialized Rust values in the common result cache file, and
394+
395+ - the operations around LLVM are so expensive in terms of computation time and
396+ memory consumption that we need to have tight control over what is
397+ executed when and what stays in memory for how long.
398+
399+ The query system could probably be extended with general purpose mechanisms to
400+ deal with all of the above but so far that seemed like more trouble than it
401+ would save.
402+
403+
404+ # Shortcomings of the Current System
405+
406+ There are many things that still can be improved.
407+
408+ ## Incrementality of on-disk data structures
409+
410+ The current system is not able to update on-disk caches and the dependency graph
411+ in-place. Instead it has to rewrite each file entirely in each compilation
412+ session. The overhead of doing so is a few percent of total compilation time.
413+
414+ ## Unnecessary data dependencies
348415
416+ Data structures used as query results could be factored in a way that removes
417+ edges from the dependency graph. Especially "span" information is very volatile,
418+ so including it in query result will increase the chance that that result won't
419+ be reusable. See https://github.com/rust-lang/rust/issues/47389 for more
420+ information.
349421
350- # The Future: Shortcomings Of The Current System and Possible Solutions
351- TODO
352422
353423
354424[ query-model ] : ./query-evaluation-model-in-detail.html
0 commit comments