Skip to content

Commit 975ff75

Browse files
committed
Perform The Dependency Unioning Step During Replay
In order for private dependencies to be completely correct, it must perform the name lookup unioning step when a cached request is replayed - not just when lookups are first performed. In order to reduce the overhead of this union operation, it is not necessary to walk the entire active request stack, just walk to the nearest cached request in the stack and union into that. When it is popped, its replay step will itself union into the next cached request. To see why, consider a request graph: A* -> B -> C* | -> D* where A, C, and D are cached. If a caller were to force C and D, then force A independenty, today we would *only* replay the names looked up by C and D the first time A was evaluated. That is, subsequent evaluations of A do not replay the correct set of names. If we were to perform the union step during replay as well, requests that force A would also see C and D’s lookups. Without this, callers that force requests like the DeclChecker have to be wary of the way they force the interface type request so other files see the right name sets. rdar://64008262
1 parent 0215e37 commit 975ff75

File tree

4 files changed

+58
-18
lines changed

4 files changed

+58
-18
lines changed

include/swift/AST/Evaluator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ class Evaluator {
439439
!Request::isDependencySink>::type * = nullptr>
440440
void reportEvaluatedResult(const Request &r,
441441
const typename Request::OutputType &o) {
442-
recorder.replay(ActiveRequest(r));
442+
recorder.replay(activeRequests, ActiveRequest(r));
443443
}
444444

445445
// Report the result of evaluating a request that is a dependency sink.

include/swift/AST/EvaluatorDependencies.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,16 @@ struct DependencyRecorder {
267267
void realize(const DependencyCollector::Reference &ref);
268268

269269
public:
270-
void replay(const swift::ActiveRequest &req);
270+
void replay(const llvm::SetVector<swift::ActiveRequest> &stack,
271+
const swift::ActiveRequest &req);
271272
void record(const llvm::SetVector<swift::ActiveRequest> &stack,
272273
llvm::function_ref<void(DependencyCollector &)> rec);
273274

275+
private:
276+
void
277+
unionNearestCachedRequest(ArrayRef<swift::ActiveRequest> stack,
278+
const DependencyCollector::ReferenceSet &scratch);
279+
274280
public:
275281
using ReferenceEnumerator =
276282
llvm::function_ref<void(const DependencyCollector::Reference &)>;

lib/AST/Evaluator.cpp

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -446,23 +446,12 @@ void evaluator::DependencyRecorder::record(
446446
return;
447447
}
448448

449-
assert(mode != Mode::StatusQuo);
450-
for (const auto &request : stack) {
451-
if (!request.isCached()) {
452-
continue;
453-
}
454-
455-
auto entry = requestReferences.find_as(request);
456-
if (entry == requestReferences.end()) {
457-
requestReferences.insert({AnyRequest(request), collector.scratch});
458-
continue;
459-
}
460-
461-
entry->second.insert(collector.scratch.begin(), collector.scratch.end());
462-
}
449+
return unionNearestCachedRequest(stack.getArrayRef(), collector.scratch);
463450
}
464451

465-
void evaluator::DependencyRecorder::replay(const swift::ActiveRequest &req) {
452+
void evaluator::DependencyRecorder::replay(
453+
const llvm::SetVector<swift::ActiveRequest> &stack,
454+
const swift::ActiveRequest &req) {
466455
assert(!isRecording && "Probably not a good idea to allow nested recording");
467456

468457
auto *source = getActiveDependencySourceOrNull();
@@ -482,6 +471,51 @@ void evaluator::DependencyRecorder::replay(const swift::ActiveRequest &req) {
482471
for (const auto &ref : entry->second) {
483472
realize(ref);
484473
}
474+
475+
// N.B. This is a particularly subtle detail of the replay unioning step. The
476+
// evaluator does not push cached requests onto the active request stack,
477+
// so it is possible (and, in fact, quite likely) we'll wind up with an
478+
// empty request stack. The remaining troublesome case is when we have a
479+
// cached request being run through the uncached path - take the
480+
// InterfaceTypeRequest, which involves many component requests, most of which
481+
// are themselves cached. In such a case, the active stack will look like
482+
//
483+
// -> TypeCheckSourceFileRequest
484+
// -> ...
485+
// -> InterfaceTypeRequest
486+
// -> ...
487+
// -> UnderlyingTypeRequest
488+
//
489+
// We want the UnderlyingTypeRequest to union its names into the
490+
// InterfaceTypeRequest, and if we were to just start searching the active
491+
// stack backwards for a cached request we would find...
492+
// the UnderlyingTypeRequest! So, we'll just drop it from consideration.
493+
//
494+
// We do *not* have to consider this during the recording step because none
495+
// of the name lookup requests (or any dependency sinks in general) are
496+
// cached. Should this change in the future, we will need to sink this logic
497+
// into the union step itself.
498+
const size_t d = (!stack.empty() && req == stack.back()) ? 1 : 0;
499+
return unionNearestCachedRequest(stack.getArrayRef().drop_back(d),
500+
entry->second);
501+
}
502+
503+
void evaluator::DependencyRecorder::unionNearestCachedRequest(
504+
ArrayRef<swift::ActiveRequest> stack,
505+
const DependencyCollector::ReferenceSet &scratch) {
506+
assert(mode != Mode::StatusQuo);
507+
auto nearest = std::find_if(stack.rbegin(), stack.rend(),
508+
[](const auto &req){ return req.isCached(); });
509+
if (nearest == stack.rend()) {
510+
return;
511+
}
512+
513+
auto entry = requestReferences.find_as(*nearest);
514+
if (entry == requestReferences.end()) {
515+
requestReferences.insert({AnyRequest(*nearest), scratch});
516+
} else {
517+
entry->second.insert(scratch.begin(), scratch.end());
518+
}
485519
}
486520

487521
using namespace swift;

test/Incremental/Verifier/multi-file/Inputs/UsesInner.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// rely on the interface hash changing to cause this file to be rebuilt, which
33
// will *not* work with type fingerprints enabled.
44
// See rdar://63984581
5-
// fixme-expected-provides{{Inner}}
5+
// fixme-provides{{Inner}}
66

77
// expected-provides{{defaultFoo}}
88
// expected-provides{{blah}}

0 commit comments

Comments
 (0)