Skip to content

[clang][modules] Timestamp-less validation API #138983

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

Merged
merged 1 commit into from
May 14, 2025
Merged
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
3 changes: 2 additions & 1 deletion clang-tools-extra/clangd/ModulesBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ bool IsModuleFileUpToDate(PathRef ModuleFilePath,
Preprocessor PP(PPOpts, *Diags, LangOpts, SourceMgr, HeaderInfo,
ModuleLoader);

IntrusiveRefCntPtr<ModuleCache> ModCache = createCrossProcessModuleCache();
IntrusiveRefCntPtr<ModuleCache> ModCache =
createCrossProcessModuleCache(HSOpts.BuildSessionTimestamp);
PCHContainerOperations PCHOperations;
ASTReader Reader(PP, *ModCache, /*ASTContext=*/nullptr,
PCHOperations.getRawReader(), {});
Expand Down
14 changes: 6 additions & 8 deletions clang/include/clang/Serialization/ModuleCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,14 @@ 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;
/// Checks whether the inputs of the module file were marked as validated.
virtual bool isMarkedUpToDate(StringRef ModuleFilename) = 0;

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

/// Returns this process's view of the module cache.
virtual InMemoryModuleCache &getInMemoryModuleCache() = 0;
Expand All @@ -58,7 +55,8 @@ class ModuleCache : public RefCountedBase<ModuleCache> {
/// operated on by multiple processes. This instance must be used across all
/// \c CompilerInstance instances participating in building modules for single
/// translation unit in order to share the same \c InMemoryModuleCache.
IntrusiveRefCntPtr<ModuleCache> createCrossProcessModuleCache();
IntrusiveRefCntPtr<ModuleCache>
createCrossProcessModuleCache(std::time_t BuildSessionTimestamp);
} // namespace clang

#endif
8 changes: 3 additions & 5 deletions clang/include/clang/Serialization/ModuleFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,9 @@ class ModuleFile {
// system input files reside at [NumUserInputFiles, InputFilesLoaded.size()).
unsigned NumUserInputFiles = 0;

/// If non-zero, specifies the time when we last validated input
/// files. Zero means we never validated them.
///
/// The time is specified in seconds since the start of the Epoch.
uint64_t InputFilesValidationTimestamp = 0;
/// Specifies whether the input files have been validated (i.e. checked
/// whether they are up-to-date).
bool InputFilesValidated = false;

// === Source Locations ===

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#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 @@ -85,9 +84,7 @@ class DependencyScanningService {
DependencyScanningService(
ScanningMode Mode, ScanningOutputFormat Format,
ScanningOptimizations OptimizeArgs = ScanningOptimizations::Default,
bool EagerLoadModules = false, bool TraceVFS = false,
std::time_t BuildSessionTimestamp =
llvm::sys::toTimeT(std::chrono::system_clock::now()));
bool EagerLoadModules = false, bool TraceVFS = false);

ScanningMode getMode() const { return Mode; }

Expand All @@ -105,8 +102,6 @@ class DependencyScanningService {

ModuleCacheEntries &getModuleCacheEntries() { return ModCacheEntries; }

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

private:
const ScanningMode Mode;
const ScanningOutputFormat Format;
Expand All @@ -120,8 +115,6 @@ class DependencyScanningService {
DependencyScanningFilesystemSharedCache SharedCache;
/// 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 @@ -20,7 +20,7 @@ namespace tooling {
namespace dependencies {
struct ModuleCacheEntry {
std::shared_mutex CompilationMutex;
std::atomic<std::time_t> Timestamp = 0;
std::atomic<bool> UpToDate = false;
};

struct ModuleCacheEntries {
Expand Down
10 changes: 6 additions & 4 deletions clang/lib/Frontend/ASTUnit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -829,9 +829,10 @@ std::unique_ptr<ASTUnit> ASTUnit::LoadFromASTFile(
AST->SourceMgr = new SourceManager(AST->getDiagnostics(),
AST->getFileManager(),
UserFilesAreVolatile);
AST->ModCache = createCrossProcessModuleCache();
AST->HSOpts = std::make_unique<HeaderSearchOptions>(HSOpts);
AST->HSOpts->ModuleFormat = std::string(PCHContainerRdr.getFormats().front());
AST->ModCache =
createCrossProcessModuleCache(AST->HSOpts->BuildSessionTimestamp);
AST->HeaderInfo.reset(new HeaderSearch(AST->getHeaderSearchOpts(),
AST->getSourceManager(),
AST->getDiagnostics(),
Expand Down Expand Up @@ -1548,7 +1549,8 @@ ASTUnit::create(std::shared_ptr<CompilerInvocation> CI,
AST->UserFilesAreVolatile = UserFilesAreVolatile;
AST->SourceMgr = new SourceManager(AST->getDiagnostics(), *AST->FileMgr,
UserFilesAreVolatile);
AST->ModCache = createCrossProcessModuleCache();
AST->ModCache = createCrossProcessModuleCache(
AST->Invocation->getHeaderSearchOpts().BuildSessionTimestamp);

return AST;
}
Expand Down Expand Up @@ -1834,7 +1836,6 @@ std::unique_ptr<ASTUnit> ASTUnit::LoadFromCommandLine(
AST->FileMgr = new FileManager(AST->FileSystemOpts, VFS);
AST->StorePreamblesInMemory = StorePreamblesInMemory;
AST->PreambleStoragePath = PreambleStoragePath;
AST->ModCache = createCrossProcessModuleCache();
AST->OnlyLocalDecls = OnlyLocalDecls;
AST->CaptureDiagnostics = CaptureDiagnostics;
AST->TUKind = TUKind;
Expand All @@ -1843,6 +1844,8 @@ std::unique_ptr<ASTUnit> ASTUnit::LoadFromCommandLine(
= IncludeBriefCommentsInCodeCompletion;
AST->UserFilesAreVolatile = UserFilesAreVolatile;
AST->Invocation = CI;
AST->ModCache = createCrossProcessModuleCache(
AST->Invocation->getHeaderSearchOpts().BuildSessionTimestamp);
AST->SkipFunctionBodies = SkipFunctionBodies;
if (ForSerialization)
AST->WriterData.reset(new ASTWriterData(*AST->ModCache));
Expand Down Expand Up @@ -2378,7 +2381,6 @@ bool ASTUnit::serialize(raw_ostream &OS) {

SmallString<128> Buffer;
llvm::BitstreamWriter Stream(Buffer);
IntrusiveRefCntPtr<ModuleCache> ModCache = createCrossProcessModuleCache();
ASTWriter Writer(Stream, Buffer, *ModCache, {});
return serializeUnit(Writer, Buffer, getSema(), OS);
}
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/Frontend/CompilerInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ CompilerInstance::CompilerInstance(
ModuleCache *ModCache)
: ModuleLoader(/*BuildingModule=*/ModCache),
Invocation(std::move(Invocation)),
ModCache(ModCache ? ModCache : createCrossProcessModuleCache()),
ModCache(ModCache ? ModCache
: createCrossProcessModuleCache(
getHeaderSearchOpts().BuildSessionTimestamp)),
ThePCHContainerOperations(std::move(PCHContainerOps)) {
assert(this->Invocation && "Invocation must not be null");
}
Expand Down
9 changes: 3 additions & 6 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3103,8 +3103,7 @@ ASTReader::ReadControlBlock(ModuleFile &F,

unsigned N = ValidateSystemInputs ? NumInputs : NumUserInputs;
if (HSOpts.ModulesValidateOncePerBuildSession &&
F.InputFilesValidationTimestamp > HSOpts.BuildSessionTimestamp &&
F.Kind == MK_ImplicitModule)
F.InputFilesValidated && F.Kind == MK_ImplicitModule)
N = ForceValidateUserInputs ? NumUserInputs : 0;

for (unsigned I = 0; I < N; ++I) {
Expand Down Expand Up @@ -4950,10 +4949,8 @@ ASTReader::ASTReadResult ASTReader::ReadAST(StringRef FileName, ModuleKind Type,
// timestamp files are up-to-date in this build session.
for (unsigned I = 0, N = Loaded.size(); I != N; ++I) {
ImportedModule &M = Loaded[I];
if (M.Mod->Kind == MK_ImplicitModule &&
M.Mod->InputFilesValidationTimestamp < HSOpts.BuildSessionTimestamp)
getModuleManager().getModuleCache().updateModuleTimestamp(
M.Mod->FileName);
if (M.Mod->Kind == MK_ImplicitModule && !M.Mod->InputFilesValidated)
getModuleManager().getModuleCache().markUpToDate(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)
ModCache.updateModuleTimestamp(OutputFile);
ModCache.markUpToDate(OutputFile);

if (ShouldCacheASTInMemory) {
// Construct MemoryBuffer and update buffer manager.
Expand Down
20 changes: 14 additions & 6 deletions clang/lib/Serialization/ModuleCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ namespace {
class CrossProcessModuleCache : public ModuleCache {
InMemoryModuleCache InMemory;

std::time_t BuildSessionTimestamp;

public:
explicit CrossProcessModuleCache(std::time_t BuildSessionTimestamp)
: BuildSessionTimestamp(BuildSessionTimestamp) {}

void prepareForGetLock(StringRef ModuleFilename) override {
// FIXME: Do this in LockFileManager and only if the directory doesn't
// exist.
Expand All @@ -33,16 +38,17 @@ class CrossProcessModuleCache : public ModuleCache {
return std::make_unique<llvm::LockFileManager>(ModuleFilename);
}

std::time_t getModuleTimestamp(StringRef ModuleFilename) override {
bool isMarkedUpToDate(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());
return false;
return llvm::sys::toTimeT(Status.getLastModificationTime()) >
BuildSessionTimestamp;
}

void updateModuleTimestamp(StringRef ModuleFilename) override {
void markUpToDate(StringRef ModuleFilename) override {
// Overwrite the timestamp file contents so that file's mtime changes.
std::error_code EC;
llvm::raw_fd_ostream OS(
Expand All @@ -62,6 +68,8 @@ class CrossProcessModuleCache : public ModuleCache {
};
} // namespace

IntrusiveRefCntPtr<ModuleCache> clang::createCrossProcessModuleCache() {
return llvm::makeIntrusiveRefCnt<CrossProcessModuleCache>();
IntrusiveRefCntPtr<ModuleCache>
clang::createCrossProcessModuleCache(std::time_t BuildSessionTimestamp) {
return llvm::makeIntrusiveRefCnt<CrossProcessModuleCache>(
BuildSessionTimestamp);
}
6 changes: 3 additions & 3 deletions clang/lib/Serialization/ModuleManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,11 @@ ModuleManager::addModule(StringRef FileName, ModuleKind Type,
NewModule->Index = Chain.size();
NewModule->FileName = FileName.str();
NewModule->ImportLoc = ImportLoc;
NewModule->InputFilesValidationTimestamp = 0;
NewModule->InputFilesValidated = false;

if (NewModule->Kind == MK_ImplicitModule)
NewModule->InputFilesValidationTimestamp =
ModCache->getModuleTimestamp(NewModule->FileName);
NewModule->InputFilesValidated =
ModCache->isMarkedUpToDate(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,8 +14,6 @@ using namespace dependencies;

DependencyScanningService::DependencyScanningService(
ScanningMode Mode, ScanningOutputFormat Format,
ScanningOptimizations OptimizeArgs, bool EagerLoadModules, bool TraceVFS,
std::time_t BuildSessionTimestamp)
ScanningOptimizations OptimizeArgs, bool EagerLoadModules, bool TraceVFS)
: Mode(Mode), Format(Format), OptimizeArgs(OptimizeArgs),
EagerLoadModules(EagerLoadModules), TraceVFS(TraceVFS),
BuildSessionTimestamp(BuildSessionTimestamp) {}
EagerLoadModules(EagerLoadModules), TraceVFS(TraceVFS) {}
Original file line number Diff line number Diff line change
Expand Up @@ -428,10 +428,6 @@ 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
16 changes: 8 additions & 8 deletions clang/lib/Tooling/DependencyScanning/InProcessModuleCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,29 +75,29 @@ class InProcessModuleCache : public ModuleCache {
return std::make_unique<ReaderWriterLock>(CompilationMutex);
}

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

return Timestamp.load();
return IsUpToDate;
}

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

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

InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
Expand Down
Loading