Skip to content

Commit 3f8f3a8

Browse files
committed
Teach the Evaluator about Incremental Dependencies
Formalize DependencyScope, DependencySource, and the incremental dependency stack. Also specialize SimpleRequest to formalize dependency sources and dependency sinks. This allows the evaluator's internal entrypoints to specalize away the incremental dependency tracking infrastructure if a request is not actually dependency-relevant.
1 parent 58e63f7 commit 3f8f3a8

File tree

4 files changed

+262
-8
lines changed

4 files changed

+262
-8
lines changed

include/swift/AST/Evaluator.h

Lines changed: 97 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,53 @@ class Evaluator {
416463
return result;
417464
}
418465

466+
private:
467+
template <typename Request,
468+
typename std::enable_if<!Request::isDependencySink>::type * = nullptr>
469+
void reportEvaluatedResult(const Request &r,
470+
const typename Request::OutputType &o) const {}
471+
472+
template <typename Request,
473+
typename std::enable_if<Request::isDependencySink>::type * = nullptr>
474+
void reportEvaluatedResult(const Request &r,
475+
const typename Request::OutputType &o) {
476+
r.writeDependencySink(*this, o);
477+
}
478+
479+
public:
480+
/// Returns \c true if the scope of the current active source cascades.
481+
///
482+
/// If there is no active scope, the result always cascades.
483+
bool isActiveSourceCascading() const {
484+
return getActiveSourceScope() == evaluator::DependencyScope::Cascading;
485+
}
486+
487+
/// Returns the scope of the current active scope.
488+
///
489+
/// If there is no active scope, the result always cascades.
490+
evaluator::DependencyScope getActiveSourceScope() const {
491+
if (dependencySources.empty()) {
492+
return evaluator::DependencyScope::Cascading;
493+
}
494+
return dependencySources.back().getInt();
495+
}
496+
497+
/// Returns the active dependency's source file, or \c nullptr if no
498+
/// dependency source is active.
499+
SourceFile *getActiveDependencySource() const {
500+
if (dependencySources.empty())
501+
return nullptr;
502+
return dependencySources.back().getPointer();
503+
}
504+
505+
/// If there is an active dependency source, returns its
506+
/// \c ReferencedNameTracker. Else, returns \c nullptr.
507+
ReferencedNameTracker *getActiveDependencyTracker() const {
508+
if (auto *source = getActiveDependencySource())
509+
return source->getRequestBasedReferencedNameTracker();
510+
return nullptr;
511+
}
512+
419513
public:
420514
/// Print the dependencies of the given request as a tree.
421515
///
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
inline DependencyScope getScopeForAccessLevel(AccessLevel l) {
65+
switch (l) {
66+
case AccessLevel::Private:
67+
case AccessLevel::FilePrivate:
68+
return DependencyScope::Private;
69+
case AccessLevel::Internal:
70+
case AccessLevel::Public:
71+
case AccessLevel::Open:
72+
return DependencyScope::Cascading;
73+
}
74+
}
75+
76+
// A \c DependencySource is a currently defined to be a parent source file and
77+
// an associated dependency scope.
78+
//
79+
// The \c SourceFile instance is an artifact of the current dependency system,
80+
// and should be scrapped if possible. It currently encodes the idea that
81+
// edges in the incremental dependency graph invalidate entire files instead
82+
// of individual contexts.
83+
using DependencySource = llvm::PointerIntPair<SourceFile *, 1, DependencyScope>;
84+
} // end namespace evaluator
85+
86+
} // end namespace swift
87+
88+
#endif // SWIFT_AST_EVALUATOR_DEPENDENCIES_H

include/swift/AST/SimpleRequest.h

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,33 @@ class Evaluator;
3434
/// Describes how the result for a particular request will be cached.
3535
enum class CacheKind {
3636
/// The result for a particular request should never be cached.
37-
Uncached,
37+
Uncached = 1 << 0,
3838
/// The result for a particular request should be cached within the
3939
/// evaluator itself.
40-
Cached,
40+
Cached = 1 << 1,
4141
/// The result of a particular request will be cached via some separate
4242
/// mechanism, such as a mutable data structure.
43-
SeparatelyCached,
43+
SeparatelyCached = 1 << 2,
44+
/// This request introduces the source component of a source-sink
45+
/// incremental dependency pair and defines a new dependency scope.
46+
///
47+
/// This bit is optional. High-level requests
48+
/// (e.g. \c TypeCheckSourceFileRequest) will require it.
49+
DependencySource = 1 << 3,
50+
/// This request introduces the sink component of a source-sink
51+
/// incremental dependency pair and is a consumer of the current
52+
/// dependency scope.
53+
///
54+
/// This bit is optional. Name lookup requests
55+
/// (e.g. \c DirectLookupRequest) will require it.
56+
DependencySink = 1 << 4,
4457
};
4558

59+
static constexpr inline CacheKind operator|(CacheKind lhs, CacheKind rhs) {
60+
return CacheKind(static_cast<std::underlying_type<CacheKind>::type>(lhs) |
61+
static_cast<std::underlying_type<CacheKind>::type>(rhs));
62+
}
63+
4664
/// -------------------------------------------------------------------------
4765
/// Extracting the source location "nearest" a request.
4866
/// -------------------------------------------------------------------------
@@ -123,6 +141,29 @@ namespace detail {
123141
}
124142
}
125143

144+
namespace detail {
145+
constexpr bool cacheContains(CacheKind kind, CacheKind needle) {
146+
using cache_t = std::underlying_type<CacheKind>::type;
147+
return (static_cast<cache_t>(kind) & static_cast<cache_t>(needle))
148+
== static_cast<cache_t>(needle);
149+
}
150+
constexpr bool isEverCached(CacheKind kind) {
151+
return !cacheContains(kind, CacheKind::Uncached);
152+
}
153+
154+
constexpr bool hasExternalCache(CacheKind kind) {
155+
return cacheContains(kind, CacheKind::SeparatelyCached);
156+
}
157+
158+
constexpr bool isDependencySource(CacheKind kind) {
159+
return cacheContains(kind, CacheKind::DependencySource);
160+
}
161+
162+
constexpr bool isDependencySink(CacheKind kind) {
163+
return cacheContains(kind, CacheKind::DependencySink);
164+
}
165+
} // end namespace detail
166+
126167
/// Extract the first, nearest source location from a tuple.
127168
template<typename First, typename ...Rest,
128169
typename = typename std::enable_if<
@@ -177,6 +218,24 @@ SourceLoc extractNearestSourceLoc(const std::tuple<First, Rest...> &value) {
177218
/// Optional<Output> getCachedResult() const;
178219
/// void cacheResult(Output value) const;
179220
/// \endcode
221+
///
222+
/// Incremental dependency tracking occurs automatically during
223+
/// request evaluation. To support that system, high-level requests that define
224+
/// dependency sources should override \c readDependencySource()
225+
/// and specify \c CacheKind::DependencySource in addition to one of
226+
/// the 3 caching kinds defined above.
227+
/// \code
228+
/// evaluator::DependencySource readDependencySource(Evaluator &) const;
229+
/// \endcode
230+
///
231+
/// Requests that define dependency sinks should instead override
232+
/// \c writeDependencySink() and use the given evaluator and request
233+
/// result to write an edge into the dependency tracker. In addition,
234+
/// \c CacheKind::DependencySource should be specified along with
235+
/// one of the 3 caching kinds defined above.
236+
/// \code
237+
/// void writeDependencySink(Evaluator &, Output value) const;
238+
/// \endcode
180239
template<typename Derived, typename Signature, CacheKind Caching>
181240
class SimpleRequest;
182241

@@ -205,8 +264,13 @@ class SimpleRequest<Derived, Output(Inputs...), Caching> {
205264
const std::tuple<Inputs...> &getStorage() const { return storage; }
206265

207266
public:
208-
static const bool isEverCached = (Caching != CacheKind::Uncached);
209-
static const bool hasExternalCache = (Caching == CacheKind::SeparatelyCached);
267+
constexpr static bool isEverCached = detail::isEverCached(Caching);
268+
constexpr static bool hasExternalCache = detail::hasExternalCache(Caching);
269+
270+
public:
271+
constexpr static bool isDependencySource = detail::isDependencySource(Caching);
272+
constexpr static bool isDependencySink = detail::isDependencySink(Caching);
273+
constexpr static bool reportsDependencyReads = isDependencySource || isDependencySink;
210274

211275
using OutputType = Output;
212276

include/swift/AST/SourceFile.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class SourceFile final : public FileUnit {
142142

143143
/// If non-null, used to track name lookups that happen within this file.
144144
Optional<ReferencedNameTracker> ReferencedNames;
145+
Optional<ReferencedNameTracker> RequestReferencedNames;
145146

146147
/// The class in this file marked \@NS/UIApplicationMain.
147148
ClassDecl *MainClass = nullptr;
@@ -472,6 +473,13 @@ class SourceFile final : public FileUnit {
472473
return ReferencedNames ? ReferencedNames.getPointer() : nullptr;
473474
}
474475

476+
ReferencedNameTracker *getRequestBasedReferencedNameTracker() {
477+
return RequestReferencedNames ? RequestReferencedNames.getPointer() : nullptr;
478+
}
479+
const ReferencedNameTracker *getRequestBasedReferencedNameTracker() const {
480+
return RequestReferencedNames ? RequestReferencedNames.getPointer() : nullptr;
481+
}
482+
475483
void createReferencedNameTracker();
476484

477485
/// The buffer ID for the file that was imported, or None if there

0 commit comments

Comments
 (0)