Skip to content

Commit 00a43b2

Browse files
authored
Merge pull request swiftlang#35014 from CodaFi/whorlpool
Extend the Concept of "Interface Hash" To Entire Modules
2 parents bb0c6b3 + 3330903 commit 00a43b2

23 files changed

+246
-53
lines changed

include/swift/AST/AbstractSourceFileDepGraphFactory.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ class AbstractSourceFileDepGraphFactory {
8787

8888
void addAUsedDecl(const DependencyKey &def, const DependencyKey &use);
8989

90+
/// Add an external dependency node to the graph. If the provided fingerprint
91+
/// is not \c None, it is added to the def key.
92+
void addAnExternalDependency(const DependencyKey &def,
93+
const DependencyKey &use,
94+
Optional<Fingerprint> dependencyFingerprint);
95+
9096
static Optional<Fingerprint>
9197
getFingerprintIfAny(std::pair<const NominalTypeDecl *, const ValueDecl *>) {
9298
return None;

include/swift/AST/Module.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,20 @@ class ModuleDecl : public DeclContext, public TypeDecl {
734734

735735
/// Calls \p callback for each source file of the module.
736736
void collectBasicSourceFileInfo(
737-
llvm::function_ref<void(const BasicSourceFileInfo &)> callback);
737+
llvm::function_ref<void(const BasicSourceFileInfo &)> callback) const;
738+
739+
public:
740+
/// Retrieve a fingerprint value that summarizes the contents of this module.
741+
///
742+
/// This interface hash a of a module is guaranteed to change if the interface
743+
/// hash of any of its (primary) source files changes. For example, when
744+
/// building incrementally, the interface hash of this module will change when
745+
/// the primaries contributing to its content changes. In contrast, when
746+
/// a module is deserialized, the hash of every source file contributes to
747+
/// the module's interface hash. It therefore serves as an effective, if
748+
/// coarse-grained, way of determining when top-level changes to a module's
749+
/// contents have been made.
750+
Fingerprint getFingerprint() const;
738751

739752
SourceRange getSourceRange() const { return SourceRange(); }
740753

include/swift/AST/ModuleLoader.h

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919

2020
#include "swift/AST/Identifier.h"
2121
#include "swift/AST/Import.h"
22+
#include "swift/AST/ModuleDependencies.h"
23+
#include "swift/Basic/ArrayRefView.h"
24+
#include "swift/Basic/Fingerprint.h"
2225
#include "swift/Basic/LLVM.h"
2326
#include "swift/Basic/Located.h"
2427
#include "swift/Basic/SourceLoc.h"
2528
#include "llvm/ADT/SetVector.h"
2629
#include "llvm/ADT/TinyPtrVector.h"
27-
#include "swift/AST/ModuleDependencies.h"
2830
#include <system_error>
2931

3032
namespace llvm {
@@ -74,8 +76,25 @@ enum class IntermoduleDepTrackingMode {
7476
/// Records dependencies on files outside of the current module;
7577
/// implemented in terms of a wrapped clang::DependencyCollector.
7678
class DependencyTracker {
79+
public:
80+
/// A representation of a first-class incremental dependency known to the
81+
/// Swift compiler.
82+
struct IncrementalDependency {
83+
std::string path;
84+
Fingerprint fingerprint;
85+
86+
IncrementalDependency(std::string Path, Fingerprint FP)
87+
: path(std::move(Path)), fingerprint(std::move(FP)) {}
88+
};
89+
90+
inline static std::string getPath(const IncrementalDependency &id) {
91+
return id.path;
92+
}
93+
typedef ArrayRefView<IncrementalDependency, std::string, getPath>
94+
DependencyPathArrayRef;
95+
7796
std::shared_ptr<clang::DependencyCollector> clangCollector;
78-
SmallVector<std::string, 8> incrementalDeps;
97+
SmallVector<IncrementalDependency, 8> incrementalDeps;
7998
llvm::StringSet<> incrementalDepsUniquer;
8099

81100
public:
@@ -94,14 +113,20 @@ class DependencyTracker {
94113
///
95114
/// No additional canonicalization or adulteration of the file path in
96115
/// \p File is performed.
97-
void addIncrementalDependency(StringRef File);
116+
void addIncrementalDependency(StringRef File, Fingerprint FP);
98117

99118
/// Fetches the list of dependencies.
100119
ArrayRef<std::string> getDependencies() const;
101120

102121
/// Fetches the list of dependencies that are known to have incremental swift
103122
/// dependency information embedded inside of them.
104-
ArrayRef<std::string> getIncrementalDependencies() const;
123+
ArrayRef<IncrementalDependency> getIncrementalDependencies() const;
124+
125+
/// A view of the paths of the dependencies known to have incremental swift
126+
/// dependency information embedded inside of them.
127+
DependencyPathArrayRef getIncrementalDependencyPaths() const {
128+
return DependencyPathArrayRef(getIncrementalDependencies());
129+
}
105130

106131
/// Return the underlying clang::DependencyCollector that this
107132
/// class wraps.

include/swift/AST/RawComment.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ struct BasicSourceFileInfo {
104104

105105
BasicSourceFileInfo() {}
106106

107-
bool populate(SourceFile *SF);
107+
bool populate(const SourceFile *SF);
108108
};
109109

110110
} // namespace swift

include/swift/AST/SourceFile.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,12 +520,24 @@ class SourceFile final : public FileUnit {
520520
/// Set the root refinement context for the file.
521521
void setTypeRefinementContext(TypeRefinementContext *TRC);
522522

523-
/// Whether this file has an interface hash available.
523+
/// Whether this file can compute an interface hash.
524524
bool hasInterfaceHash() const {
525525
return ParsingOpts.contains(ParsingFlags::EnableInterfaceHash);
526526
}
527527

528-
/// Output this file's interface hash into the provided string buffer.
528+
/// Retrieve a fingerprint value that summarizes the declarations in this
529+
/// source file.
530+
///
531+
/// Note that the interface hash merely summarizes the top-level declarations
532+
/// in this file. Type body fingerprints are currently implemented such that
533+
/// they divert tokens away from the hasher used for fingerprints. That is,
534+
/// changes to the bodies of types and extensions will not result in a change
535+
/// to the interface hash.
536+
///
537+
/// In order for the interface hash to be enabled, this source file must be a
538+
/// primary and the compiler must be set in incremental mode. If this is not
539+
/// the case, this function will try to signal with an assert. It is useful
540+
/// to guard requests for the interface hash with \c hasInterfaceHash().
529541
Fingerprint getInterfaceHash() const;
530542

531543
void dumpInterfaceHash(llvm::raw_ostream &out) {

include/swift/Basic/Fingerprint.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ class Fingerprint final {
9494
return llvm::hash_value(fp.core);
9595
}
9696

97+
public:
98+
bool operator<(const Fingerprint &other) const {
99+
return core < other.core;
100+
}
101+
97102
public:
98103
/// The fingerprint value consisting of 32 bytes of zeroes.
99104
///

lib/AST/AbstractSourceFileDepGraphFactory.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,16 @@ void AbstractSourceFileDepGraphFactory::addAUsedDecl(
108108
assert(useNode->getIsProvides() && "Use (using node) must be a provides");
109109
g.addArc(defNode, useNode);
110110
}
111+
112+
void AbstractSourceFileDepGraphFactory::addAnExternalDependency(
113+
const DependencyKey &defKey, const DependencyKey &useKey,
114+
Optional<Fingerprint> maybeFP) {
115+
auto *defNode = g.findExistingNodeOrCreateIfNew(defKey, maybeFP,
116+
false /* = !isProvides */);
117+
118+
auto nullableUse = g.findExistingNode(useKey);
119+
assert(nullableUse.isNonNull() && "Use must be an already-added provides");
120+
auto *useNode = nullableUse.get();
121+
assert(useNode->getIsProvides() && "Use (using node) must be a provides");
122+
g.addArc(defNode, useNode);
123+
}

lib/AST/FrontendSourceFileDepGraphFactory.cpp

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -402,16 +402,14 @@ namespace {
402402
/// Extracts uses out of a SourceFile
403403
class UsedDeclEnumerator {
404404
const SourceFile *SF;
405-
const DependencyTracker &depTracker;
406405
StringRef swiftDeps;
407406

408407
/// Cache these for efficiency
409408
const DependencyKey sourceFileImplementation;
410409

411410
public:
412-
UsedDeclEnumerator(const SourceFile *SF, const DependencyTracker &depTracker,
413-
StringRef swiftDeps)
414-
: SF(SF), depTracker(depTracker), swiftDeps(swiftDeps),
411+
UsedDeclEnumerator(const SourceFile *SF, StringRef swiftDeps)
412+
: SF(SF), swiftDeps(swiftDeps),
415413
sourceFileImplementation(DependencyKey::createKeyForWholeSourceFile(
416414
DeclAspect::implementation, swiftDeps)) {}
417415

@@ -446,7 +444,6 @@ class UsedDeclEnumerator {
446444
}
447445
}
448446
});
449-
enumerateExternalUses(enumerator);
450447
enumerateNominalUses(enumerator);
451448
}
452449

@@ -474,23 +471,59 @@ class UsedDeclEnumerator {
474471
enumerateUse<NodeKind::nominal>(context, "", enumerator);
475472
});
476473
}
474+
};
475+
} // end namespace
476+
477+
namespace {
478+
class ExternalDependencyEnumerator {
479+
const DependencyTracker &depTracker;
480+
const DependencyKey sourceFileImplementation;
481+
482+
public:
483+
using UseEnumerator = llvm::function_ref<void(
484+
const DependencyKey &, const DependencyKey &, Optional<Fingerprint>)>;
485+
486+
ExternalDependencyEnumerator(const DependencyTracker &depTracker,
487+
StringRef swiftDeps)
488+
: depTracker(depTracker),
489+
sourceFileImplementation(DependencyKey::createKeyForWholeSourceFile(
490+
DeclAspect::implementation, swiftDeps)) {}
477491

478492
void enumerateExternalUses(UseEnumerator enumerator) {
479-
for (StringRef s : depTracker.getIncrementalDependencies())
480-
enumerateUse<NodeKind::incrementalExternalDepend>("", s, enumerator);
493+
for (const auto &id : depTracker.getIncrementalDependencies()) {
494+
enumerateUse<NodeKind::incrementalExternalDepend>(enumerator, id.path,
495+
id.fingerprint);
496+
}
497+
for (StringRef s : depTracker.getDependencies()) {
498+
enumerateUse<NodeKind::externalDepend>(enumerator, s, None);
499+
}
500+
}
481501

482-
for (StringRef s : depTracker.getDependencies())
483-
enumerateUse<NodeKind::externalDepend>("", s, enumerator);
502+
private:
503+
template <NodeKind kind>
504+
void enumerateUse(UseEnumerator createDefUse, StringRef name,
505+
Optional<Fingerprint> maybeFP) {
506+
static_assert(kind == NodeKind::incrementalExternalDepend ||
507+
kind == NodeKind::externalDepend,
508+
"Not a kind of external dependency!");
509+
createDefUse(DependencyKey(kind, DeclAspect::interface, "", name.str()),
510+
sourceFileImplementation, maybeFP);
484511
}
485512
};
486513
} // end namespace
487514

488515
void FrontendSourceFileDepGraphFactory::addAllUsedDecls() {
489-
UsedDeclEnumerator(SF, depTracker, swiftDeps)
516+
UsedDeclEnumerator(SF, swiftDeps)
490517
.enumerateAllUses(
491518
[&](const DependencyKey &def, const DependencyKey &use) {
492519
addAUsedDecl(def, use);
493520
});
521+
ExternalDependencyEnumerator(depTracker, swiftDeps)
522+
.enumerateExternalUses([&](const DependencyKey &def,
523+
const DependencyKey &use,
524+
Optional<Fingerprint> maybeFP) {
525+
addAnExternalDependency(def, use, maybeFP);
526+
});
494527
}
495528

496529
//==============================================================================
@@ -499,19 +532,10 @@ void FrontendSourceFileDepGraphFactory::addAllUsedDecls() {
499532

500533
ModuleDepGraphFactory::ModuleDepGraphFactory(const ModuleDecl *Mod,
501534
bool emitDot)
502-
: AbstractSourceFileDepGraphFactory(Mod->getASTContext().hadError(),
503-
Mod->getNameStr(), Fingerprint::ZERO(),
504-
emitDot, Mod->getASTContext().Diags),
505-
506-
Mod(Mod) {
507-
// Since a fingerprint only summarizes the state of the module but not
508-
// the state of its fingerprinted sub-declarations, and since a module
509-
// contains no state other than sub-declarations, its fingerprint does not
510-
// matter and can just be some arbitrary value. Should it be the case that a
511-
// change in a declaration that does not have a fingerprint must cause
512-
// a rebuild of a file outside of the module, this assumption will need
513-
// to be revisited.
514-
}
535+
: AbstractSourceFileDepGraphFactory(
536+
Mod->getASTContext().hadError(), Mod->getNameStr(),
537+
Mod->getFingerprint(), emitDot, Mod->getASTContext().Diags),
538+
Mod(Mod) {}
515539

516540
void ModuleDepGraphFactory::addAllDefinedDecls() {
517541
// TODO: express the multiple provides and depends streams with variadic

lib/AST/Module.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,9 +1536,9 @@ const clang::Module *ModuleDecl::findUnderlyingClangModule() const {
15361536
}
15371537

15381538
void ModuleDecl::collectBasicSourceFileInfo(
1539-
llvm::function_ref<void(const BasicSourceFileInfo &)> callback) {
1540-
for (FileUnit *fileUnit : getFiles()) {
1541-
if (SourceFile *SF = dyn_cast<SourceFile>(fileUnit)) {
1539+
llvm::function_ref<void(const BasicSourceFileInfo &)> callback) const {
1540+
for (const FileUnit *fileUnit : getFiles()) {
1541+
if (const auto *SF = dyn_cast<SourceFile>(fileUnit)) {
15421542
BasicSourceFileInfo info;
15431543
if (info.populate(SF))
15441544
continue;
@@ -1549,6 +1549,25 @@ void ModuleDecl::collectBasicSourceFileInfo(
15491549
}
15501550
}
15511551

1552+
Fingerprint ModuleDecl::getFingerprint() const {
1553+
StableHasher hasher = StableHasher::defaultHasher();
1554+
SmallVector<Fingerprint, 16> FPs;
1555+
collectBasicSourceFileInfo([&](const BasicSourceFileInfo &bsfi) {
1556+
FPs.emplace_back(bsfi.InterfaceHash);
1557+
});
1558+
1559+
// Sort the fingerprints lexicographically so we have a stable hash despite
1560+
// an unstable ordering of files across rebuilds.
1561+
// FIXME: If we used a commutative hash combine (say, if we could take an
1562+
// XOR here) we could avoid this sort.
1563+
std::sort(FPs.begin(), FPs.end(), std::less<Fingerprint>());
1564+
for (const auto &FP : FPs) {
1565+
hasher.combine(FP);
1566+
}
1567+
1568+
return Fingerprint{std::move(hasher)};
1569+
}
1570+
15521571
//===----------------------------------------------------------------------===//
15531572
// Cross-Import Overlays
15541573
//===----------------------------------------------------------------------===//

lib/AST/ModuleLoader.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ DependencyTracker::addDependency(StringRef File, bool IsSystem) {
5050
/*IsMissing=*/false);
5151
}
5252

53-
void DependencyTracker::addIncrementalDependency(StringRef File) {
53+
void DependencyTracker::addIncrementalDependency(StringRef File,
54+
Fingerprint FP) {
5455
if (incrementalDepsUniquer.insert(File).second) {
55-
incrementalDeps.emplace_back(File.str());
56+
incrementalDeps.emplace_back(File.str(), FP);
5657
}
5758
}
5859

@@ -61,7 +62,7 @@ DependencyTracker::getDependencies() const {
6162
return clangCollector->getDependencies();
6263
}
6364

64-
ArrayRef<std::string>
65+
ArrayRef<DependencyTracker::IncrementalDependency>
6566
DependencyTracker::getIncrementalDependencies() const {
6667
return incrementalDeps;
6768
}

0 commit comments

Comments
 (0)