Skip to content

Commit aa0bf50

Browse files
authored
Merge pull request #30723 from CodaFi/dependendable-dependencies
[Evaluator] Online Request-Based Incremental Dependency Tracking
2 parents 8e447a4 + ee723cd commit aa0bf50

38 files changed

+1106
-215
lines changed

docs/Lexicon.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ source code, tests, and commit messages. See also the `LLVM lexicon`_.
7777
same; the exception is when generics get involved. In this case you'll need
7878
a `generic environment`. Contrast with `sugared type`.
7979

80+
cascading dependency
81+
A kind of dependency edge relevant to the incremental name tracking
82+
subsystem. A cascading dependency (as opposed to a
83+
`private dependency <private dependency>`) requires the Swift driver to
84+
transitively consider dependency edges in the file that defines the used
85+
name when incremental compilation is enabled. A cascading dependency is much
86+
safer to produce than its private counterpart, but it comes at the cost of
87+
increased usage of compilation resources - even if those resources are being
88+
wasted on rebuilding a file that didn't actually require rebuilding.
89+
See :doc:`DependencyAnalysis.rst <DependencyAnalysis>`.
90+
8091
Clang importer
8192
The part of the compiler that reads C and Objective-C declarations and
8293
exposes them as Swift. Essentially contains a small instance of Clang
@@ -100,6 +111,24 @@ source code, tests, and commit messages. See also the `LLVM lexicon`_.
100111
i.e. one that conforming types don't *have* to implement but have the option
101112
to "customize".
102113

114+
dependency sink
115+
Any request that uses a matching dependency source to write dependency
116+
edges into the referenced name trackers. For example, a request that
117+
performs direct lookup will write the name being looked up into the
118+
name tracker associated with the file that issued the lookup request.
119+
The request evaluator automatically determines the appropriate tracker
120+
for a dependency sink to write into based on the current active
121+
`dependency source <dependency source>` request.
122+
123+
dependency source
124+
Any request that defines a scope under which reference dependencies may be
125+
registered. For example, a request to type check an entire file is a
126+
dependency source. Dependency sources are automatically managed by the
127+
request evaluator as request evaluation proceeds. Dependency sources provide
128+
one half of the necessary information to complete a full dependency edge.
129+
The other half is provided by corresponding
130+
`dependency sink <dependency sink>` requests.
131+
103132
DI (definite initialization / definitive initialization)
104133
The feature that no uninitialized variables, constants, or properties will
105134
be read by a program, or the analysis pass that operates on SIL to
@@ -327,6 +356,17 @@ source code, tests, and commit messages. See also the `LLVM lexicon`_.
327356
only needed for context. See also
328357
`Whole-Module Optimization <WMO (whole-module optimization)>`.
329358

359+
private dependency
360+
A kind of dependency edge relevant to the incremental name tracking
361+
subsystem. A private dependency (as opposed to a
362+
`cascading dependency <cascading dependency>`) declares a dependency edge
363+
from one file to a name referenced in that file that does not
364+
require further transitive evaluation of dependency edges by the Swift
365+
driver. Private dependencies are therefore cheaper than cascading
366+
dependencies, but must be used with the utmost care or dependent files will
367+
fail to rebuild and the result will most certainly be a miscompile.
368+
See :doc:`DependencyAnalysis.rst <DependencyAnalysis>`.
369+
330370
QoI
331371
"Quality of implementation." The term is meant to describe not how
332372
well-engineered a particular implementation is, but how much value it

docs/RequestEvaluator.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ The request-evaluator contains a cache of any requests that have already been ev
5252

5353
Until then, the request-evaluator lives in a compiler that has mutable ASTs, and will for the foreseeable future. To cope with this, requests can opt for "separate" caching of their results, implementing a simple protocol to query their own cache (`getCachedResult`) and set the value for the cache (`cacheResult`). For now, these functions can directly modify state in the AST, allowing the requests to be mixed with direct mutation of the state. For each request, the intent is to make all state access go through the evaluator, but getting there can be an incremental process.
5454

55+
## Incremental Dependency Tracking
56+
Request evaluation naturally captures the dependency structure of any given invocation of the compiler frontend. In fact, it captures it so well that the request graph trace generated by a select kind of lookup request can be used to completely recover the information relevant to the Swift compiler's incremental compilation subsystem. For these select *dependency-relevant* requests, we can further subdivide them into so-called *dependency sources* and *dependency sinks*. A dependency source is any (usually high-level) request that introduces a new context under which dependencies can be registered. Currently, these are the requests that operate that the level of individual source files. A dependency sink is any (usually lower-level) request that executes as a sub-computation of a dependency source. Any names that are dependency-relevant, such as the result of a lookup in a particular context, are then registered against trackers in the active dependency source (file). Using this, the evaluator pushes and pops sources and sinks automatically as request evaluation proceeds, and sink requests pair automatically to source requests to write out dependency information.
57+
58+
To define a request as a dependency source, it must implement an accessor for the new active scope (`readDependencySource`). To define a request as a dependency sink, it must implement a function that writes the result of evaluating the request into the current active source (`writeDependencySink`).
59+
5560
## Open Projects
5661

5762
The request-evaluator is relatively new to the Swift compiler, having been introduced in mid-2018. There are a number of improvements that can be made to the evaluator itself and how it is used in the compiler:

include/swift/AST/AccessRequests.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class ValueDecl;
3333
class AccessLevelRequest :
3434
public SimpleRequest<AccessLevelRequest,
3535
AccessLevel(ValueDecl *),
36-
CacheKind::SeparatelyCached> {
36+
RequestFlags::SeparatelyCached> {
3737
public:
3838
using SimpleRequest::SimpleRequest;
3939

@@ -56,7 +56,7 @@ class AccessLevelRequest :
5656
class SetterAccessLevelRequest :
5757
public SimpleRequest<SetterAccessLevelRequest,
5858
AccessLevel(AbstractStorageDecl *),
59-
CacheKind::SeparatelyCached> {
59+
RequestFlags::SeparatelyCached> {
6060
public:
6161
using SimpleRequest::SimpleRequest;
6262

@@ -79,7 +79,7 @@ using DefaultAndMax = std::pair<AccessLevel, AccessLevel>;
7979
class DefaultAndMaxAccessLevelRequest :
8080
public SimpleRequest<DefaultAndMaxAccessLevelRequest,
8181
DefaultAndMax(ExtensionDecl *),
82-
CacheKind::SeparatelyCached> {
82+
RequestFlags::SeparatelyCached> {
8383
public:
8484
using SimpleRequest::SimpleRequest;
8585
private:

include/swift/AST/Evaluator.h

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#define SWIFT_AST_EVALUATOR_H
2020

2121
#include "swift/AST/AnyRequest.h"
22+
#include "swift/AST/EvaluatorDependencies.h"
2223
#include "swift/Basic/AnyValue.h"
2324
#include "swift/Basic/Debug.h"
2425
#include "swift/Basic/Defer.h"
@@ -47,6 +48,12 @@ class DiagnosticEngine;
4748
class Evaluator;
4849
class UnifiedStatsReporter;
4950

51+
namespace detail {
52+
// Remove this when the compiler bumps to C++17.
53+
template <typename...>
54+
using void_t = void;
55+
}
56+
5057
/// An "abstract" request function pointer, which is the storage type
5158
/// used for each of the
5259
using AbstractRequestFunction = void(void);
@@ -224,6 +231,38 @@ class Evaluator {
224231
/// so all clients must cope with cycles.
225232
llvm::DenseMap<AnyRequest, std::vector<AnyRequest>> dependencies;
226233

234+
/// A stack of dependency sources in the order they were evaluated.
235+
llvm::SmallVector<evaluator::DependencySource, 8> dependencySources;
236+
237+
/// An RAII type that manages manipulating the evaluator's
238+
/// dependency source stack. It is specialized to be zero-cost for
239+
/// requests that are not dependency sources.
240+
template <typename Request, typename = detail::void_t<>>
241+
struct IncrementalDependencyStackRAII {
242+
IncrementalDependencyStackRAII(Evaluator &E, const Request &Req) {}
243+
};
244+
245+
template <typename Request>
246+
struct IncrementalDependencyStackRAII<
247+
Request, typename std::enable_if<Request::isDependencySource>::type> {
248+
NullablePtr<Evaluator> Eval;
249+
IncrementalDependencyStackRAII(Evaluator &E, const Request &Req) {
250+
auto Source = Req.readDependencySource(E);
251+
// If there is no source to introduce, bail. This can occur if
252+
// a request originates in the context of a module.
253+
if (!Source.getPointer()) {
254+
return;
255+
}
256+
E.dependencySources.emplace_back(Source);
257+
Eval = &E;
258+
}
259+
260+
~IncrementalDependencyStackRAII() {
261+
if (Eval.isNonNull())
262+
Eval.get()->dependencySources.pop_back();
263+
}
264+
};
265+
227266
/// Retrieve the request function for the given zone and request IDs.
228267
AbstractRequestFunction *getAbstractRequestFunction(uint8_t zoneID,
229268
uint8_t requestID) const;
@@ -264,6 +303,7 @@ class Evaluator {
264303
typename std::enable_if<Request::isEverCached>::type * = nullptr>
265304
llvm::Expected<typename Request::OutputType>
266305
operator()(const Request &request) {
306+
IncrementalDependencyStackRAII<Request> incDeps{*this, request};
267307
// The request can be cached, but check a predicate to determine
268308
// whether this particular instance is cached. This allows more
269309
// fine-grained control over which instances get cache.
@@ -279,6 +319,7 @@ class Evaluator {
279319
typename std::enable_if<!Request::isEverCached>::type * = nullptr>
280320
llvm::Expected<typename Request::OutputType>
281321
operator()(const Request &request) {
322+
IncrementalDependencyStackRAII<Request> incDeps{*this, request};
282323
return getResultUncached(request);
283324
}
284325

@@ -366,7 +407,9 @@ class Evaluator {
366407
FrontendStatsTracer statsTracer = make_tracer(stats, request);
367408
if (stats) reportEvaluatedRequest(*stats, request);
368409

369-
return getRequestFunction<Request>()(request, *this);
410+
auto &&r = getRequestFunction<Request>()(request, *this);
411+
reportEvaluatedResult<Request>(request, r);
412+
return std::move(r);
370413
}
371414

372415
/// Get the result of a request, consulting an external cache
@@ -377,8 +420,10 @@ class Evaluator {
377420
llvm::Expected<typename Request::OutputType>
378421
getResultCached(const Request &request) {
379422
// If there is a cached result, return it.
380-
if (auto cached = request.getCachedResult())
423+
if (auto cached = request.getCachedResult()) {
424+
reportEvaluatedResult<Request>(request, *cached);
381425
return *cached;
426+
}
382427

383428
// Compute the result.
384429
auto result = getResultUncached(request);
@@ -403,7 +448,9 @@ class Evaluator {
403448
// If we already have an entry for this request in the cache, return it.
404449
auto known = cache.find_as(request);
405450
if (known != cache.end()) {
406-
return known->second.template castTo<typename Request::OutputType>();
451+
auto r = known->second.template castTo<typename Request::OutputType>();
452+
reportEvaluatedResult<Request>(request, r);
453+
return r;
407454
}
408455

409456
// Compute the result.
@@ -416,6 +463,62 @@ class Evaluator {
416463
return result;
417464
}
418465

466+
private:
467+
// Report the result of evaluating a request that is not a dependency sink -
468+
// which is to say do nothing.
469+
template <typename Request,
470+
typename std::enable_if<!Request::isDependencySink>::type * = nullptr>
471+
void reportEvaluatedResult(const Request &r,
472+
const typename Request::OutputType &o) {}
473+
474+
// Report the result of evaluating a request that is a dependency sink.
475+
template <typename Request,
476+
typename std::enable_if<Request::isDependencySink>::type * = nullptr>
477+
void reportEvaluatedResult(const Request &r,
478+
const typename Request::OutputType &o) {
479+
if (auto *tracker = getActiveDependencyTracker())
480+
r.writeDependencySink(*this, *tracker, o);
481+
}
482+
483+
/// If there is an active dependency source, returns its
484+
/// \c ReferencedNameTracker. Else, returns \c nullptr.
485+
ReferencedNameTracker *getActiveDependencyTracker() const {
486+
if (auto *source = getActiveDependencySourceOrNull())
487+
return source->getRequestBasedReferencedNameTracker();
488+
return nullptr;
489+
}
490+
491+
public:
492+
/// Returns \c true if the scope of the current active source cascades.
493+
///
494+
/// If there is no active scope, the result always cascades.
495+
bool isActiveSourceCascading() const {
496+
return getActiveSourceScope() == evaluator::DependencyScope::Cascading;
497+
}
498+
499+
/// Returns the scope of the current active scope.
500+
///
501+
/// If there is no active scope, the result always cascades.
502+
evaluator::DependencyScope getActiveSourceScope() const {
503+
if (dependencySources.empty()) {
504+
return evaluator::DependencyScope::Cascading;
505+
}
506+
return dependencySources.back().getInt();
507+
}
508+
509+
/// Returns the active dependency's source file, or \c nullptr if no
510+
/// dependency source is active.
511+
///
512+
/// The use of this accessor is strongly discouraged, as it implies that a
513+
/// dependency sink is seeking to filter out names based on the files they
514+
/// come from. Existing callers are being migrated to more reasonable ways
515+
/// of judging the relevancy of a dependency.
516+
SourceFile *getActiveDependencySourceOrNull() const {
517+
if (dependencySources.empty())
518+
return nullptr;
519+
return dependencySources.back().getPointer();
520+
}
521+
419522
public:
420523
/// Print the dependencies of the given request as a tree.
421524
///
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//===--- EvaluatorDependencies.h - Auto-Incremental Dependencies -*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file defines data structures to support the request evaluator's
14+
// automatic incremental dependency tracking functionality.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#ifndef SWIFT_AST_EVALUATOR_DEPENDENCIES_H
19+
#define SWIFT_AST_EVALUATOR_DEPENDENCIES_H
20+
21+
#include "swift/AST/AttrKind.h"
22+
#include "swift/AST/SourceFile.h"
23+
#include "llvm/ADT/PointerIntPair.h"
24+
25+
namespace swift {
26+
27+
namespace evaluator {
28+
29+
/// The "scope" of a dependency edge tracked by the evaluator.
30+
///
31+
/// Dependency scopes come in two flavors: cascading and private. A private
32+
/// edge captures dependencies discovered in contexts that are not visible to
33+
/// to other files. For example, a conformance to a private protocol, or the use
34+
/// of any names inside of a function body. A cascading edge, by contrast,
35+
/// captures dependencies discovered in the remaining visible contexts. These
36+
/// are types with at least \c internal visibility, names defined or used
37+
/// outside of function bodies with at least \c internal visibility, etc. A
38+
/// dependency that has cascading scope is so-named because upon traversing the
39+
/// edge, a reader such as the driver should continue transitively evaluating
40+
/// further dependency edges.
41+
///
42+
/// A cascading edge is always conservatively correct to produce, but it comes
43+
/// at the cost of increased resources spent (and possibly even wasted!) during
44+
/// incremental compilation. A private edge, by contrast, is more efficient for
45+
/// incremental compilation but it is harder to safely use.
46+
///
47+
/// To ensure that these edges are registered consistently with the correct
48+
/// scopes, requests that act as the source of dependency edges are required
49+
/// to specify a \c DependencyScope under which all evaluated sub-requests will
50+
/// register their dependency edges. In this way, \c DependencyScope values
51+
/// form a stack-like structure and are pushed and popped by the evaluator
52+
/// during the course of request evaluation.
53+
///
54+
/// When determining the kind of scope a request should use, always err on the
55+
/// side of a cascading scope unless there is absolute proof any discovered
56+
/// dependencies will be private. Inner requests may also defensively choose to
57+
/// flip the dependency scope from private to cascading in the name of safety.
58+
enum class DependencyScope : bool {
59+
Private = false,
60+
Cascading = true,
61+
};
62+
63+
/// Returns a \c DependencyScope appropriate for the given (formal) access level.
64+
///
65+
/// :warning: This function exists to bridge the old manual reference
66+
/// dependencies code to the new evaluator-based reference dependencies code.
67+
/// The manual code often made private/cascading scope judgements based on the
68+
/// access level of a declaration. While this makes some sense intuitively, it
69+
/// does not necessarily capture an accurate picture of where real incremental
70+
/// dependencies lie. For example, references to formally private types can
71+
/// "escape" to contexts that have no reference to the private name if, say,
72+
/// the layout of that private type is taken into consideration by
73+
/// SILGen or IRGen in a separate file that references the declaration
74+
/// transitively. However, due to the density of the current dependency
75+
/// graph, redundancy in registered dependency edges, and the liberal use of
76+
/// cascading edges, we may be saved from the worst consequences of this
77+
/// modelling choice.
78+
///
79+
/// The use of access-levels for dependency decisions is an anti-pattern that
80+
/// should be revisited once finer-grained dependencies are explored more
81+
/// thoroughly.
82+
inline DependencyScope getScopeForAccessLevel(AccessLevel l) {
83+
switch (l) {
84+
case AccessLevel::Private:
85+
case AccessLevel::FilePrivate:
86+
return DependencyScope::Private;
87+
case AccessLevel::Internal:
88+
case AccessLevel::Public:
89+
case AccessLevel::Open:
90+
return DependencyScope::Cascading;
91+
}
92+
}
93+
94+
// A \c DependencySource is currently defined to be a parent source file and
95+
// an associated dependency scope.
96+
//
97+
// The \c SourceFile instance is an artifact of the current dependency system,
98+
// and should be scrapped if possible. It currently encodes the idea that
99+
// edges in the incremental dependency graph invalidate entire files instead
100+
// of individual contexts.
101+
using DependencySource = llvm::PointerIntPair<SourceFile *, 1, DependencyScope>;
102+
} // end namespace evaluator
103+
104+
} // end namespace swift
105+
106+
#endif // SWIFT_AST_EVALUATOR_DEPENDENCIES_H

0 commit comments

Comments
 (0)