Skip to content

Commit 115bd28

Browse files
committed
Naive Dependency Replay
Finish off private intransitive dependencies with an implementation of dependency replay. For the sake of illustration, imagine a chain of requests A -> B -> C -> ... Supposing each request is never cached, then every invocation of the compiler with the same inputs will always kick off the exact same set of requests. For the purposes of dependency tracking, that also means every single lookup request will run without issue, and all dependencies will be accurately reported. But we live in a world with cached requests. Suppose request B* is cached. The first time we encounter that request, its evaluation order looks identical: A -> B* -> C -> ... If we are in a mode that compiles single primaries, this is not a problem because every request graph will look like this. But if we are in a mode where we are compiling multiple primaries, then subsequent request graphs will *actually* hit the cache and never execute request C or any of its dependent computations! A -> B* Supposing C was a lookup request, that means the name(s) looked up downstream of B* will *never* be recorded in the referenced name tracker which can lead to miscompilation. Note that this is not a problem inherent to the design of the request evaluator - caches in the compiler have *always* hidden dependent lookups. In fact, the request evaluator provides us our first opportunity to resolve this correctness bug!
1 parent 9ee5911 commit 115bd28

File tree

4 files changed

+196
-16
lines changed

4 files changed

+196
-16
lines changed

include/swift/AST/AnyRequest.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ struct AnyRequestVTable {
6666
static SourceLoc getNearestLoc(const void *ptr) {
6767
return static_cast<const Request *>(ptr)->getNearestLoc();
6868
}
69+
static bool isCached(const void *ptr) {
70+
return static_cast<const Request *>(ptr)->isCached();
71+
}
6972
};
7073

7174
const uint64_t typeID;
@@ -78,8 +81,29 @@ struct AnyRequestVTable {
7881
const std::function<void(const void *, DiagnosticEngine &)> diagnoseCycle;
7982
const std::function<void(const void *, DiagnosticEngine &)> noteCycleStep;
8083
const std::function<SourceLoc(const void *)> getNearestLoc;
84+
const std::function<bool(const void *)> isCached;
8185

82-
template <typename Request>
86+
template <typename Request,
87+
typename std::enable_if<Request::isEverCached>::type * = nullptr>
88+
static const AnyRequestVTable *get() {
89+
static const AnyRequestVTable vtable = {
90+
TypeID<Request>::value,
91+
sizeof(Request),
92+
&Impl<Request>::copy,
93+
&Impl<Request>::getHash,
94+
&Impl<Request>::deleter,
95+
&Impl<Request>::isEqual,
96+
&Impl<Request>::simpleDisplay,
97+
&Impl<Request>::diagnoseCycle,
98+
&Impl<Request>::noteCycleStep,
99+
&Impl<Request>::getNearestLoc,
100+
&Impl<Request>::isCached,
101+
};
102+
return &vtable;
103+
}
104+
105+
template <typename Request,
106+
typename std::enable_if<!Request::isEverCached>::type * = nullptr>
83107
static const AnyRequestVTable *get() {
84108
static const AnyRequestVTable vtable = {
85109
TypeID<Request>::value,
@@ -92,6 +116,7 @@ struct AnyRequestVTable {
92116
&Impl<Request>::diagnoseCycle,
93117
&Impl<Request>::noteCycleStep,
94118
&Impl<Request>::getNearestLoc,
119+
[](auto){ return false; },
95120
};
96121
return &vtable;
97122
}
@@ -194,6 +219,10 @@ class AnyRequestBase {
194219
return getVTable()->getNearestLoc(getRawStorage());
195220
}
196221

222+
bool isCached() const {
223+
return getVTable()->isCached(getRawStorage());
224+
}
225+
197226
/// Compare two instances for equality.
198227
friend bool operator==(const AnyRequestBase<Derived> &lhs,
199228
const AnyRequestBase<Derived> &rhs) {

include/swift/AST/Evaluator.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -431,19 +431,22 @@ class Evaluator {
431431
}
432432

433433
private:
434-
// Report the result of evaluating a request that is not a dependency sink -
435-
// which is to say do nothing.
436-
template <typename Request,
437-
typename std::enable_if<!Request::isDependencySink>::type * = nullptr>
434+
// Report the result of evaluating a request that is not a dependency sink.
435+
template <typename Request, typename std::enable_if<
436+
!Request::isDependencySink>::type * = nullptr>
438437
void reportEvaluatedResult(const Request &r,
439-
const typename Request::OutputType &o) {}
438+
const typename Request::OutputType &o) {
439+
collector.replay(ActiveRequest(r));
440+
}
440441

441442
// Report the result of evaluating a request that is a dependency sink.
442443
template <typename Request,
443444
typename std::enable_if<Request::isDependencySink>::type * = nullptr>
444445
void reportEvaluatedResult(const Request &r,
445446
const typename Request::OutputType &o) {
446-
r.writeDependencySink(collector, o);
447+
return collector.record(activeRequests, [&r, &o](auto &c) {
448+
return r.writeDependencySink(c, o);
449+
});
447450
}
448451

449452
public:

include/swift/AST/EvaluatorDependencies.h

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#ifndef SWIFT_AST_EVALUATOR_DEPENDENCIES_H
1919
#define SWIFT_AST_EVALUATOR_DEPENDENCIES_H
2020

21+
#include "swift/AST/AnyRequest.h"
2122
#include "swift/AST/AttrKind.h"
2223
#include "swift/AST/SourceFile.h"
2324
#include "llvm/ADT/PointerIntPair.h"
@@ -113,6 +114,70 @@ struct DependencyCollector {
113114
/// A stack of dependency sources in the order they were evaluated.
114115
llvm::SmallVector<evaluator::DependencySource, 8> dependencySources;
115116

117+
struct Reference {
118+
public:
119+
enum class Kind {
120+
Empty,
121+
Tombstone,
122+
UsedMember,
123+
PotentialMember,
124+
TopLevel,
125+
Dynamic,
126+
} kind;
127+
128+
NominalTypeDecl *subject;
129+
DeclBaseName name;
130+
131+
private:
132+
Reference(Kind kind, NominalTypeDecl *subject, DeclBaseName name)
133+
: kind(kind), subject(subject), name(name) {}
134+
135+
public:
136+
static Reference empty() {
137+
return {Kind::Empty, llvm::DenseMapInfo<NominalTypeDecl *>::getEmptyKey(),
138+
llvm::DenseMapInfo<DeclBaseName>::getEmptyKey()};
139+
}
140+
141+
static Reference tombstone() {
142+
return {Kind::Empty,
143+
llvm::DenseMapInfo<NominalTypeDecl *>::getTombstoneKey(),
144+
llvm::DenseMapInfo<DeclBaseName>::getTombstoneKey()};
145+
}
146+
147+
public:
148+
static Reference usedMember(NominalTypeDecl *subject, DeclBaseName name) {
149+
return {Kind::UsedMember, subject, name};
150+
}
151+
152+
static Reference potentialMember(NominalTypeDecl *subject) {
153+
return {Kind::PotentialMember, subject, DeclBaseName()};
154+
}
155+
156+
static Reference topLevel(DeclBaseName name) {
157+
return {Kind::TopLevel, nullptr, name};
158+
}
159+
160+
static Reference dynamic(DeclBaseName name) {
161+
return {Kind::Dynamic, nullptr, name};
162+
}
163+
164+
public:
165+
struct Info {
166+
static inline Reference getEmptyKey() { return Reference::empty(); }
167+
static inline Reference getTombstoneKey() {
168+
return Reference::tombstone();
169+
}
170+
static inline unsigned getHashValue(const Reference &Val) {
171+
return llvm::hash_combine(Val.kind, Val.subject,
172+
Val.name.getAsOpaquePointer());
173+
}
174+
static bool isEqual(const Reference &LHS, const Reference &RHS) {
175+
return LHS.kind == RHS.kind && LHS.subject == RHS.subject &&
176+
LHS.name == RHS.name;
177+
}
178+
};
179+
};
180+
116181
public:
117182
enum class Mode {
118183
// Enables the current "status quo" behavior of the dependency collector.
@@ -128,8 +193,21 @@ struct DependencyCollector {
128193
ExperimentalPrivateDependencies,
129194
};
130195
Mode mode;
196+
llvm::DenseMap<AnyRequest, llvm::DenseSet<Reference, Reference::Info>>
197+
requestReferences;
198+
llvm::DenseSet<Reference, Reference::Info> scratch;
199+
bool isRecording;
200+
201+
explicit DependencyCollector(Mode mode)
202+
: mode{mode}, requestReferences{}, scratch{}, isRecording{false} {};
131203

132-
explicit DependencyCollector(Mode mode) : mode{mode} {};
204+
private:
205+
void realizeOrRecord(const Reference &ref);
206+
207+
public:
208+
void replay(const swift::ActiveRequest &req);
209+
void record(const llvm::SetVector<swift::ActiveRequest> &stack,
210+
llvm::function_ref<void(DependencyCollector &)> rec);
133211

134212
public:
135213
/// Registers a named reference from the current dependency scope to a member

lib/AST/Evaluator.cpp

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "llvm/ADT/StringExtras.h"
2222
#include "llvm/Support/Debug.h"
2323
#include "llvm/Support/FileSystem.h"
24+
#include "llvm/Support/SaveAndRestore.h"
2425

2526
using namespace swift;
2627

@@ -378,24 +379,93 @@ void Evaluator::dumpDependenciesGraphviz() const {
378379
printDependenciesGraphviz(llvm::dbgs());
379380
}
380381

382+
void evaluator::DependencyCollector::realizeOrRecord(const Reference &ref) {
383+
if (isRecording) {
384+
scratch.insert(std::move(ref));
385+
return;
386+
}
387+
388+
auto *tracker = getActiveDependencyTracker();
389+
if (!tracker) {
390+
return;
391+
}
392+
393+
switch (ref.kind) {
394+
case Reference::Kind::Empty:
395+
case Reference::Kind::Tombstone:
396+
llvm_unreachable("cannot record empty dependency");
397+
case Reference::Kind::UsedMember:
398+
tracker->addUsedMember({ref.subject, ref.name}, isActiveSourceCascading());
399+
break;
400+
case Reference::Kind::PotentialMember:
401+
tracker->addUsedMember({ref.subject, Identifier()},
402+
isActiveSourceCascading());
403+
break;
404+
case Reference::Kind::TopLevel:
405+
tracker->addTopLevelName(ref.name, isActiveSourceCascading());
406+
break;
407+
case Reference::Kind::Dynamic:
408+
tracker->addDynamicLookupName(ref.name, isActiveSourceCascading());
409+
break;
410+
}
411+
}
412+
381413
void evaluator::DependencyCollector::addUsedMember(NominalTypeDecl *subject,
382414
DeclBaseName name) {
383-
if (auto *tracker = getActiveDependencyTracker())
384-
tracker->addUsedMember({subject, name}, isActiveSourceCascading());
415+
return realizeOrRecord(Reference::usedMember(subject, name));
385416
}
386417

387418
void evaluator::DependencyCollector::addPotentialMember(
388419
NominalTypeDecl *subject) {
389-
if (auto *tracker = getActiveDependencyTracker())
390-
tracker->addUsedMember({subject, Identifier()}, isActiveSourceCascading());
420+
return realizeOrRecord(Reference::potentialMember(subject));
391421
}
392422

393423
void evaluator::DependencyCollector::addTopLevelName(DeclBaseName name) {
394-
if (auto *tracker = getActiveDependencyTracker())
395-
tracker->addTopLevelName(name, isActiveSourceCascading());
424+
return realizeOrRecord(Reference::topLevel(name));
396425
}
397426

398427
void evaluator::DependencyCollector::addDynamicLookupName(DeclBaseName name) {
399-
if (auto *tracker = getActiveDependencyTracker())
400-
tracker->addDynamicLookupName(name, isActiveSourceCascading());
428+
return realizeOrRecord(Reference::dynamic(name));
429+
}
430+
431+
void evaluator::DependencyCollector::record(
432+
const llvm::SetVector<swift::ActiveRequest> &stack,
433+
llvm::function_ref<void(DependencyCollector &)> rec) {
434+
assert(!isRecording && "Probably not a good idea to allow nested recording");
435+
if (mode == Mode::StatusQuo) {
436+
return rec(*this);
437+
}
438+
439+
llvm::SaveAndRestore<bool> restore(isRecording, true);
440+
scratch = {};
441+
rec(*this);
442+
for (const auto &request : stack) {
443+
if (!request.isCached()) {
444+
continue;
445+
}
446+
447+
auto entry = requestReferences.find_as(request);
448+
if (entry == requestReferences.end()) {
449+
requestReferences.insert({AnyRequest(request), scratch});
450+
continue;
451+
}
452+
453+
entry->second.insert(scratch.begin(), scratch.end());
454+
}
455+
}
456+
457+
void evaluator::DependencyCollector::replay(const swift::ActiveRequest &req) {
458+
if (mode == Mode::StatusQuo) {
459+
return;
460+
}
461+
462+
assert(!isRecording && "Probably not a good idea to allow nested recording");
463+
auto entry = requestReferences.find_as(req);
464+
if (entry == requestReferences.end()) {
465+
return;
466+
}
467+
468+
for (const auto &ref : entry->second) {
469+
realizeOrRecord(ref);
470+
}
401471
}

0 commit comments

Comments
 (0)