Skip to content

Commit ff1c26e

Browse files
jansvoboda11cyndyishida
authored andcommitted
[clang][modules][deps] Optimize in-process timestamping of PCMs (llvm#137363)
In the past, timestamps used for `-fmodules-validate-once-per-build-session` were found to be a source of contention in the dependency scanner ([D149802](https://reviews.llvm.org/D149802), llvm#112452). This PR is yet another attempt to optimize these. We now make use of the new `ModuleCache` interface to implement the in-process version in terms of atomic `std::time_t` variables rather the mtime attribute on `.timestamp` files.
1 parent 66131b3 commit ff1c26e

File tree

12 files changed

+109
-47
lines changed

12 files changed

+109
-47
lines changed

clang/include/clang/Serialization/ModuleCache.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include "clang/Basic/LLVM.h"
1313
#include "llvm/ADT/IntrusiveRefCntPtr.h"
1414

15+
#include <ctime>
16+
1517
namespace llvm {
1618
class AdvisoryLock;
1719
} // namespace llvm
@@ -31,11 +33,23 @@ class ModuleCache : public RefCountedBase<ModuleCache> {
3133
virtual std::unique_ptr<llvm::AdvisoryLock>
3234
getLock(StringRef ModuleFilename) = 0;
3335

36+
// TODO: Abstract away timestamps with isUpToDate() and markUpToDate().
37+
// TODO: Consider exposing a "validation lock" API to prevent multiple clients
38+
// concurrently noticing an out-of-date module file and validating its inputs.
39+
40+
/// Returns the timestamp denoting the last time inputs of the module file
41+
/// were validated.
42+
virtual std::time_t getModuleTimestamp(StringRef ModuleFilename) = 0;
43+
44+
/// Updates the timestamp denoting the last time inputs of the module file
45+
/// were validated.
46+
virtual void updateModuleTimestamp(StringRef ModuleFilename) = 0;
47+
3448
/// Returns this process's view of the module cache.
3549
virtual InMemoryModuleCache &getInMemoryModuleCache() = 0;
3650
virtual const InMemoryModuleCache &getInMemoryModuleCache() const = 0;
3751

38-
// TODO: Virtualize writing/reading PCM files, timestamping, pruning, etc.
52+
// TODO: Virtualize writing/reading PCM files, pruning, etc.
3953

4054
virtual ~ModuleCache() = default;
4155
};

clang/include/clang/Tooling/DependencyScanning/DependencyScanningService.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "clang/Tooling/DependencyScanning/InProcessModuleCache.h"
1616
#include "llvm/ADT/BitmaskEnum.h"
1717
#include "llvm/CAS/ActionCache.h"
18+
#include "llvm/Support/Chrono.h"
1819

1920
namespace clang {
2021
namespace tooling {
@@ -104,7 +105,9 @@ class DependencyScanningService {
104105
std::shared_ptr<llvm::cas::ActionCache> Cache,
105106
IntrusiveRefCntPtr<llvm::cas::CachingOnDiskFileSystem> SharedFS,
106107
ScanningOptimizations OptimizeArgs = ScanningOptimizations::Default,
107-
bool EagerLoadModules = false, bool TraceVFS = false);
108+
bool EagerLoadModules = false, bool TraceVFS = false,
109+
std::time_t BuildSessionTimestamp =
110+
llvm::sys::toTimeT(std::chrono::system_clock::now()));
108111

109112
ScanningMode getMode() const { return Mode; }
110113

@@ -131,7 +134,9 @@ class DependencyScanningService {
131134

132135
bool useCASFS() const { return (bool)SharedFS; }
133136

134-
ModuleCacheMutexes &getModuleCacheMutexes() { return ModCacheMutexes; }
137+
ModuleCacheEntries &getModuleCacheEntries() { return ModCacheEntries; }
138+
139+
std::time_t getBuildSessionTimestamp() const { return BuildSessionTimestamp; }
135140

136141
private:
137142
const ScanningMode Mode;
@@ -150,8 +155,10 @@ class DependencyScanningService {
150155
IntrusiveRefCntPtr<llvm::cas::CachingOnDiskFileSystem> SharedFS;
151156
/// The global file system cache.
152157
std::optional<DependencyScanningFilesystemSharedCache> SharedCache;
153-
/// The global module cache mutexes.
154-
ModuleCacheMutexes ModCacheMutexes;
158+
/// The global module cache entries.
159+
ModuleCacheEntries ModCacheEntries;
160+
/// The build session timestamp.
161+
std::time_t BuildSessionTimestamp;
155162
};
156163

157164
} // end namespace dependencies

clang/include/clang/Tooling/DependencyScanning/InProcessModuleCache.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@
1818
namespace clang {
1919
namespace tooling {
2020
namespace dependencies {
21-
struct ModuleCacheMutexes {
21+
struct ModuleCacheEntry {
22+
std::shared_mutex CompilationMutex;
23+
std::atomic<std::time_t> Timestamp = 0;
24+
};
25+
26+
struct ModuleCacheEntries {
2227
std::mutex Mutex;
23-
llvm::StringMap<std::unique_ptr<std::shared_mutex>> Map;
28+
llvm::StringMap<std::unique_ptr<ModuleCacheEntry>> Map;
2429
};
2530

2631
IntrusiveRefCntPtr<ModuleCache>
27-
makeInProcessModuleCache(ModuleCacheMutexes &Mutexes);
32+
makeInProcessModuleCache(ModuleCacheEntries &Entries);
2833
} // namespace dependencies
2934
} // namespace tooling
3035
} // namespace clang

clang/lib/Serialization/ASTCommon.cpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -501,15 +501,3 @@ bool serialization::needsAnonymousDeclarationNumber(const NamedDecl *D) {
501501
return false;
502502
return isa<TagDecl, FieldDecl>(D);
503503
}
504-
505-
void serialization::updateModuleTimestamp(StringRef ModuleFilename) {
506-
// Overwrite the timestamp file contents so that file's mtime changes.
507-
std::error_code EC;
508-
llvm::raw_fd_ostream OS(ModuleFile::getTimestampFilename(ModuleFilename), EC,
509-
llvm::sys::fs::OF_TextWithCRLF);
510-
if (EC)
511-
return;
512-
OS << "Timestamp file\n";
513-
OS.close();
514-
OS.clear_error(); // Avoid triggering a fatal error.
515-
}

clang/lib/Serialization/ASTCommon.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,6 @@ inline bool isPartOfPerModuleInitializer(const Decl *D) {
101101
return false;
102102
}
103103

104-
void updateModuleTimestamp(StringRef ModuleFilename);
105-
106104
} // namespace serialization
107105

108106
} // namespace clang

clang/lib/Serialization/ASTReader.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4752,7 +4752,8 @@ ASTReader::ASTReadResult ASTReader::ReadAST(StringRef FileName, ModuleKind Type,
47524752
ImportedModule &M = Loaded[I];
47534753
if (M.Mod->Kind == MK_ImplicitModule &&
47544754
M.Mod->InputFilesValidationTimestamp < HSOpts.BuildSessionTimestamp)
4755-
updateModuleTimestamp(M.Mod->FileName);
4755+
getModuleManager().getModuleCache().updateModuleTimestamp(
4756+
M.Mod->FileName);
47564757
}
47574758
}
47584759

clang/lib/Serialization/ASTWriter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4995,7 +4995,7 @@ ASTWriter::WriteAST(llvm::PointerUnion<Sema *, Preprocessor *> Subject,
49954995
if (WritingModule && PPRef.getHeaderSearchInfo()
49964996
.getHeaderSearchOpts()
49974997
.ModulesValidateOncePerBuildSession)
4998-
updateModuleTimestamp(OutputFile);
4998+
ModCache.updateModuleTimestamp(OutputFile);
49994999

50005000
if (ShouldCacheASTInMemory) {
50015001
// Construct MemoryBuffer and update buffer manager.

clang/lib/Serialization/ModuleCache.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "clang/Serialization/ModuleCache.h"
1010

1111
#include "clang/Serialization/InMemoryModuleCache.h"
12+
#include "clang/Serialization/ModuleFile.h"
1213
#include "llvm/Support/FileSystem.h"
1314
#include "llvm/Support/LockFileManager.h"
1415
#include "llvm/Support/Path.h"
@@ -32,6 +33,28 @@ class CrossProcessModuleCache : public ModuleCache {
3233
return std::make_unique<llvm::LockFileManager>(ModuleFilename);
3334
}
3435

36+
std::time_t getModuleTimestamp(StringRef ModuleFilename) override {
37+
std::string TimestampFilename =
38+
serialization::ModuleFile::getTimestampFilename(ModuleFilename);
39+
llvm::sys::fs::file_status Status;
40+
if (llvm::sys::fs::status(ModuleFilename, Status) != std::error_code{})
41+
return 0;
42+
return llvm::sys::toTimeT(Status.getLastModificationTime());
43+
}
44+
45+
void updateModuleTimestamp(StringRef ModuleFilename) override {
46+
// Overwrite the timestamp file contents so that file's mtime changes.
47+
std::error_code EC;
48+
llvm::raw_fd_ostream OS(
49+
serialization::ModuleFile::getTimestampFilename(ModuleFilename), EC,
50+
llvm::sys::fs::OF_TextWithCRLF);
51+
if (EC)
52+
return;
53+
OS << "Timestamp file\n";
54+
OS.close();
55+
OS.clear_error(); // Avoid triggering a fatal error.
56+
}
57+
3558
InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
3659
const InMemoryModuleCache &getInMemoryModuleCache() const override {
3760
return InMemory;

clang/lib/Serialization/ModuleManager.cpp

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,9 @@ ModuleManager::addModule(StringRef FileName, ModuleKind Type,
184184
NewModule->ImportLoc = ImportLoc;
185185
NewModule->InputFilesValidationTimestamp = 0;
186186

187-
if (NewModule->Kind == MK_ImplicitModule) {
188-
std::string TimestampFilename =
189-
ModuleFile::getTimestampFilename(NewModule->FileName);
190-
llvm::vfs::Status Status;
191-
// A cached stat value would be fine as well.
192-
if (!FileMgr.getNoncachedStatValue(TimestampFilename, Status))
193-
NewModule->InputFilesValidationTimestamp =
194-
llvm::sys::toTimeT(Status.getLastModificationTime());
195-
}
187+
if (NewModule->Kind == MK_ImplicitModule)
188+
NewModule->InputFilesValidationTimestamp =
189+
ModCache->getModuleTimestamp(NewModule->FileName);
196190

197191
// Load the contents of the module
198192
if (std::unique_ptr<llvm::MemoryBuffer> Buffer = lookupBuffer(FileName)) {

clang/lib/Tooling/DependencyScanning/DependencyScanningService.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ DependencyScanningService::DependencyScanningService(
2121
std::shared_ptr<llvm::cas::ObjectStore> CAS,
2222
std::shared_ptr<llvm::cas::ActionCache> Cache,
2323
IntrusiveRefCntPtr<llvm::cas::CachingOnDiskFileSystem> SharedFS,
24-
ScanningOptimizations OptimizeArgs, bool EagerLoadModules, bool TraceVFS)
24+
ScanningOptimizations OptimizeArgs, bool EagerLoadModules, bool TraceVFS,
25+
std::time_t BuildSessionTimestamp)
2526
: Mode(Mode), Format(Format), CASOpts(std::move(CASOpts)), CAS(std::move(CAS)), Cache(std::move(Cache)),
2627
OptimizeArgs(OptimizeArgs), EagerLoadModules(EagerLoadModules), TraceVFS(TraceVFS),
27-
SharedFS(std::move(SharedFS)) {
28+
SharedFS(std::move(SharedFS)),
29+
BuildSessionTimestamp(BuildSessionTimestamp) {
2830
if (!this->SharedFS)
2931
SharedCache.emplace();
3032

clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ class DependencyScanningAction : public tooling::ToolAction {
441441
Scanned = true;
442442

443443
// Create a compiler instance to handle the actual work.
444-
auto ModCache = makeInProcessModuleCache(Service.getModuleCacheMutexes());
444+
auto ModCache = makeInProcessModuleCache(Service.getModuleCacheEntries());
445445
ScanInstanceStorage.emplace(std::move(PCHContainerOps), ModCache.get());
446446
CompilerInstance &ScanInstance = *ScanInstanceStorage;
447447
ScanInstance.setInvocation(std::move(Invocation));
@@ -461,6 +461,10 @@ class DependencyScanningAction : public tooling::ToolAction {
461461
ScanInstance.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath =
462462
true;
463463

464+
if (ScanInstance.getHeaderSearchOpts().ModulesValidateOncePerBuildSession)
465+
ScanInstance.getHeaderSearchOpts().BuildSessionTimestamp =
466+
Service.getBuildSessionTimestamp();
467+
464468
ScanInstance.getFrontendOpts().GenerateGlobalModuleIndex = false;
465469
ScanInstance.getFrontendOpts().UseGlobalModuleIndex = false;
466470
// This will prevent us compiling individual modules asynchronously since

clang/lib/Tooling/DependencyScanning/InProcessModuleCache.cpp

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "clang/Serialization/InMemoryModuleCache.h"
1212
#include "llvm/Support/AdvisoryLock.h"
13+
#include "llvm/Support/Chrono.h"
1314

1415
#include <mutex>
1516

@@ -50,7 +51,7 @@ class ReaderWriterLock : public llvm::AdvisoryLock {
5051
};
5152

5253
class InProcessModuleCache : public ModuleCache {
53-
ModuleCacheMutexes &Mutexes;
54+
ModuleCacheEntries &Entries;
5455

5556
// TODO: If we changed the InMemoryModuleCache API and relied on strict
5657
// context hash, we could probably create more efficient thread-safe
@@ -59,19 +60,44 @@ class InProcessModuleCache : public ModuleCache {
5960
InMemoryModuleCache InMemory;
6061

6162
public:
62-
InProcessModuleCache(ModuleCacheMutexes &Mutexes) : Mutexes(Mutexes) {}
63+
InProcessModuleCache(ModuleCacheEntries &Entries) : Entries(Entries) {}
6364

6465
void prepareForGetLock(StringRef Filename) override {}
6566

6667
std::unique_ptr<llvm::AdvisoryLock> getLock(StringRef Filename) override {
67-
auto &Mtx = [&]() -> std::shared_mutex & {
68-
std::lock_guard<std::mutex> Lock(Mutexes.Mutex);
69-
auto &Mutex = Mutexes.Map[Filename];
70-
if (!Mutex)
71-
Mutex = std::make_unique<std::shared_mutex>();
72-
return *Mutex;
68+
auto &CompilationMutex = [&]() -> std::shared_mutex & {
69+
std::lock_guard Lock(Entries.Mutex);
70+
auto &Entry = Entries.Map[Filename];
71+
if (!Entry)
72+
Entry = std::make_unique<ModuleCacheEntry>();
73+
return Entry->CompilationMutex;
7374
}();
74-
return std::make_unique<ReaderWriterLock>(Mtx);
75+
return std::make_unique<ReaderWriterLock>(CompilationMutex);
76+
}
77+
78+
std::time_t getModuleTimestamp(StringRef Filename) override {
79+
auto &Timestamp = [&]() -> std::atomic<std::time_t> & {
80+
std::lock_guard Lock(Entries.Mutex);
81+
auto &Entry = Entries.Map[Filename];
82+
if (!Entry)
83+
Entry = std::make_unique<ModuleCacheEntry>();
84+
return Entry->Timestamp;
85+
}();
86+
87+
return Timestamp.load();
88+
}
89+
90+
void updateModuleTimestamp(StringRef Filename) override {
91+
// Note: This essentially replaces FS contention with mutex contention.
92+
auto &Timestamp = [&]() -> std::atomic<std::time_t> & {
93+
std::lock_guard Lock(Entries.Mutex);
94+
auto &Entry = Entries.Map[Filename];
95+
if (!Entry)
96+
Entry = std::make_unique<ModuleCacheEntry>();
97+
return Entry->Timestamp;
98+
}();
99+
100+
Timestamp.store(llvm::sys::toTimeT(std::chrono::system_clock::now()));
75101
}
76102

77103
InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
@@ -82,6 +108,6 @@ class InProcessModuleCache : public ModuleCache {
82108
} // namespace
83109

84110
IntrusiveRefCntPtr<ModuleCache>
85-
dependencies::makeInProcessModuleCache(ModuleCacheMutexes &Mutexes) {
86-
return llvm::makeIntrusiveRefCnt<InProcessModuleCache>(Mutexes);
111+
dependencies::makeInProcessModuleCache(ModuleCacheEntries &Entries) {
112+
return llvm::makeIntrusiveRefCnt<InProcessModuleCache>(Entries);
87113
}

0 commit comments

Comments
 (0)