Skip to content

[clang][modules][deps] Optimize in-process timestamping of PCMs #137363

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion clang/include/clang/Serialization/ModuleCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"

#include <ctime>

namespace llvm {
class AdvisoryLock;
} // namespace llvm
Expand All @@ -31,11 +33,23 @@ class ModuleCache : public RefCountedBase<ModuleCache> {
virtual std::unique_ptr<llvm::AdvisoryLock>
getLock(StringRef ModuleFilename) = 0;

// TODO: Abstract away timestamps with isUpToDate() and markUpToDate().
// TODO: Consider exposing a "validation lock" API to prevent multiple clients
// concurrently noticing an out-of-date module file and validating its inputs.

/// Returns the timestamp denoting the last time inputs of the module file
/// were validated.
virtual std::time_t getModuleTimestamp(StringRef ModuleFilename) = 0;

/// Updates the timestamp denoting the last time inputs of the module file
/// were validated.
virtual void updateModuleTimestamp(StringRef ModuleFilename) = 0;

/// Returns this process's view of the module cache.
virtual InMemoryModuleCache &getInMemoryModuleCache() = 0;
virtual const InMemoryModuleCache &getInMemoryModuleCache() const = 0;

// TODO: Virtualize writing/reading PCM files, timestamping, pruning, etc.
// TODO: Virtualize writing/reading PCM files, pruning, etc.

virtual ~ModuleCache() = default;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h"
#include "clang/Tooling/DependencyScanning/InProcessModuleCache.h"
#include "llvm/ADT/BitmaskEnum.h"
#include "llvm/Support/Chrono.h"

namespace clang {
namespace tooling {
Expand Down Expand Up @@ -84,7 +85,9 @@ class DependencyScanningService {
DependencyScanningService(
ScanningMode Mode, ScanningOutputFormat Format,
ScanningOptimizations OptimizeArgs = ScanningOptimizations::Default,
bool EagerLoadModules = false, bool TraceVFS = false);
bool EagerLoadModules = false, bool TraceVFS = false,
std::time_t BuildSessionTimestamp =
llvm::sys::toTimeT(std::chrono::system_clock::now()));

ScanningMode getMode() const { return Mode; }

Expand All @@ -100,7 +103,9 @@ class DependencyScanningService {
return SharedCache;
}

ModuleCacheMutexes &getModuleCacheMutexes() { return ModCacheMutexes; }
ModuleCacheEntries &getModuleCacheEntries() { return ModCacheEntries; }

std::time_t getBuildSessionTimestamp() const { return BuildSessionTimestamp; }

private:
const ScanningMode Mode;
Expand All @@ -113,8 +118,10 @@ class DependencyScanningService {
const bool TraceVFS;
/// The global file system cache.
DependencyScanningFilesystemSharedCache SharedCache;
/// The global module cache mutexes.
ModuleCacheMutexes ModCacheMutexes;
/// The global module cache entries.
ModuleCacheEntries ModCacheEntries;
/// The build session timestamp.
std::time_t BuildSessionTimestamp;
};

} // end namespace dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@
namespace clang {
namespace tooling {
namespace dependencies {
struct ModuleCacheMutexes {
struct ModuleCacheEntry {
std::shared_mutex CompilationMutex;
std::atomic<std::time_t> Timestamp = 0;
};

struct ModuleCacheEntries {
std::mutex Mutex;
llvm::StringMap<std::unique_ptr<std::shared_mutex>> Map;
llvm::StringMap<std::unique_ptr<ModuleCacheEntry>> Map;
};

IntrusiveRefCntPtr<ModuleCache>
makeInProcessModuleCache(ModuleCacheMutexes &Mutexes);
makeInProcessModuleCache(ModuleCacheEntries &Entries);
} // namespace dependencies
} // namespace tooling
} // namespace clang
Expand Down
12 changes: 0 additions & 12 deletions clang/lib/Serialization/ASTCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -510,15 +510,3 @@ bool serialization::needsAnonymousDeclarationNumber(const NamedDecl *D) {
return false;
return isa<TagDecl, FieldDecl>(D);
}

void serialization::updateModuleTimestamp(StringRef ModuleFilename) {
// Overwrite the timestamp file contents so that file's mtime changes.
std::error_code EC;
llvm::raw_fd_ostream OS(ModuleFile::getTimestampFilename(ModuleFilename), EC,
llvm::sys::fs::OF_TextWithCRLF);
if (EC)
return;
OS << "Timestamp file\n";
OS.close();
OS.clear_error(); // Avoid triggering a fatal error.
}
2 changes: 0 additions & 2 deletions clang/lib/Serialization/ASTCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,6 @@ inline bool isPartOfPerModuleInitializer(const Decl *D) {
return false;
}

void updateModuleTimestamp(StringRef ModuleFilename);

} // namespace serialization

} // namespace clang
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4952,7 +4952,8 @@ ASTReader::ASTReadResult ASTReader::ReadAST(StringRef FileName, ModuleKind Type,
ImportedModule &M = Loaded[I];
if (M.Mod->Kind == MK_ImplicitModule &&
M.Mod->InputFilesValidationTimestamp < HSOpts.BuildSessionTimestamp)
updateModuleTimestamp(M.Mod->FileName);
getModuleManager().getModuleCache().updateModuleTimestamp(
M.Mod->FileName);
}
}

Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Serialization/ASTWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5394,7 +5394,7 @@ ASTWriter::WriteAST(llvm::PointerUnion<Sema *, Preprocessor *> Subject,
if (WritingModule && PPRef.getHeaderSearchInfo()
.getHeaderSearchOpts()
.ModulesValidateOncePerBuildSession)
updateModuleTimestamp(OutputFile);
ModCache.updateModuleTimestamp(OutputFile);

if (ShouldCacheASTInMemory) {
// Construct MemoryBuffer and update buffer manager.
Expand Down
23 changes: 23 additions & 0 deletions clang/lib/Serialization/ModuleCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "clang/Serialization/ModuleCache.h"

#include "clang/Serialization/InMemoryModuleCache.h"
#include "clang/Serialization/ModuleFile.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LockFileManager.h"
#include "llvm/Support/Path.h"
Expand All @@ -32,6 +33,28 @@ class CrossProcessModuleCache : public ModuleCache {
return std::make_unique<llvm::LockFileManager>(ModuleFilename);
}

std::time_t getModuleTimestamp(StringRef ModuleFilename) override {
std::string TimestampFilename =
serialization::ModuleFile::getTimestampFilename(ModuleFilename);
llvm::sys::fs::file_status Status;
if (llvm::sys::fs::status(ModuleFilename, Status) != std::error_code{})
return 0;
return llvm::sys::toTimeT(Status.getLastModificationTime());
}

void updateModuleTimestamp(StringRef ModuleFilename) override {
// Overwrite the timestamp file contents so that file's mtime changes.
std::error_code EC;
llvm::raw_fd_ostream OS(
serialization::ModuleFile::getTimestampFilename(ModuleFilename), EC,
llvm::sys::fs::OF_TextWithCRLF);
if (EC)
return;
OS << "Timestamp file\n";
OS.close();
OS.clear_error(); // Avoid triggering a fatal error.
}

InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
const InMemoryModuleCache &getInMemoryModuleCache() const override {
return InMemory;
Expand Down
12 changes: 3 additions & 9 deletions clang/lib/Serialization/ModuleManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,9 @@ ModuleManager::addModule(StringRef FileName, ModuleKind Type,
NewModule->ImportLoc = ImportLoc;
NewModule->InputFilesValidationTimestamp = 0;

if (NewModule->Kind == MK_ImplicitModule) {
std::string TimestampFilename =
ModuleFile::getTimestampFilename(NewModule->FileName);
llvm::vfs::Status Status;
// A cached stat value would be fine as well.
if (!FileMgr.getNoncachedStatValue(TimestampFilename, Status))
NewModule->InputFilesValidationTimestamp =
llvm::sys::toTimeT(Status.getLastModificationTime());
}
if (NewModule->Kind == MK_ImplicitModule)
NewModule->InputFilesValidationTimestamp =
ModCache->getModuleTimestamp(NewModule->FileName);

// Load the contents of the module
if (std::unique_ptr<llvm::MemoryBuffer> Buffer = lookupBuffer(FileName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ using namespace dependencies;

DependencyScanningService::DependencyScanningService(
ScanningMode Mode, ScanningOutputFormat Format,
ScanningOptimizations OptimizeArgs, bool EagerLoadModules, bool TraceVFS)
ScanningOptimizations OptimizeArgs, bool EagerLoadModules, bool TraceVFS,
std::time_t BuildSessionTimestamp)
: Mode(Mode), Format(Format), OptimizeArgs(OptimizeArgs),
EagerLoadModules(EagerLoadModules), TraceVFS(TraceVFS) {}
EagerLoadModules(EagerLoadModules), TraceVFS(TraceVFS),
BuildSessionTimestamp(BuildSessionTimestamp) {}
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ class DependencyScanningAction : public tooling::ToolAction {
Scanned = true;

// Create a compiler instance to handle the actual work.
auto ModCache = makeInProcessModuleCache(Service.getModuleCacheMutexes());
auto ModCache = makeInProcessModuleCache(Service.getModuleCacheEntries());
ScanInstanceStorage.emplace(std::move(Invocation),
std::move(PCHContainerOps), ModCache.get());
CompilerInstance &ScanInstance = *ScanInstanceStorage;
Expand All @@ -428,6 +428,10 @@ class DependencyScanningAction : public tooling::ToolAction {
ScanInstance.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath =
true;

if (ScanInstance.getHeaderSearchOpts().ModulesValidateOncePerBuildSession)
ScanInstance.getHeaderSearchOpts().BuildSessionTimestamp =
Service.getBuildSessionTimestamp();

ScanInstance.getFrontendOpts().GenerateGlobalModuleIndex = false;
ScanInstance.getFrontendOpts().UseGlobalModuleIndex = false;
// This will prevent us compiling individual modules asynchronously since
Expand Down
48 changes: 37 additions & 11 deletions clang/lib/Tooling/DependencyScanning/InProcessModuleCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "clang/Serialization/InMemoryModuleCache.h"
#include "llvm/Support/AdvisoryLock.h"
#include "llvm/Support/Chrono.h"

#include <mutex>

Expand Down Expand Up @@ -50,7 +51,7 @@ class ReaderWriterLock : public llvm::AdvisoryLock {
};

class InProcessModuleCache : public ModuleCache {
ModuleCacheMutexes &Mutexes;
ModuleCacheEntries &Entries;

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

public:
InProcessModuleCache(ModuleCacheMutexes &Mutexes) : Mutexes(Mutexes) {}
InProcessModuleCache(ModuleCacheEntries &Entries) : Entries(Entries) {}

void prepareForGetLock(StringRef Filename) override {}

std::unique_ptr<llvm::AdvisoryLock> getLock(StringRef Filename) override {
auto &Mtx = [&]() -> std::shared_mutex & {
std::lock_guard<std::mutex> Lock(Mutexes.Mutex);
auto &Mutex = Mutexes.Map[Filename];
if (!Mutex)
Mutex = std::make_unique<std::shared_mutex>();
return *Mutex;
auto &CompilationMutex = [&]() -> std::shared_mutex & {
std::lock_guard Lock(Entries.Mutex);
auto &Entry = Entries.Map[Filename];
if (!Entry)
Entry = std::make_unique<ModuleCacheEntry>();
return Entry->CompilationMutex;
}();
return std::make_unique<ReaderWriterLock>(Mtx);
return std::make_unique<ReaderWriterLock>(CompilationMutex);
}

std::time_t getModuleTimestamp(StringRef Filename) override {
auto &Timestamp = [&]() -> std::atomic<std::time_t> & {
std::lock_guard Lock(Entries.Mutex);
auto &Entry = Entries.Map[Filename];
if (!Entry)
Entry = std::make_unique<ModuleCacheEntry>();
return Entry->Timestamp;
}();

return Timestamp.load();
}

void updateModuleTimestamp(StringRef Filename) override {
// Note: This essentially replaces FS contention with mutex contention.
auto &Timestamp = [&]() -> std::atomic<std::time_t> & {
std::lock_guard Lock(Entries.Mutex);
auto &Entry = Entries.Map[Filename];
if (!Entry)
Entry = std::make_unique<ModuleCacheEntry>();
return Entry->Timestamp;
}();

Timestamp.store(llvm::sys::toTimeT(std::chrono::system_clock::now()));
}

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

IntrusiveRefCntPtr<ModuleCache>
dependencies::makeInProcessModuleCache(ModuleCacheMutexes &Mutexes) {
return llvm::makeIntrusiveRefCnt<InProcessModuleCache>(Mutexes);
dependencies::makeInProcessModuleCache(ModuleCacheEntries &Entries) {
return llvm::makeIntrusiveRefCnt<InProcessModuleCache>(Entries);
}