mirror-llvm-project/clang/lib/DependencyScanning/InProcessModuleCache.cpp
Qiongsi Wu a730dda7d5
[clang][DependencyScanning] Logging Dependency Scanning Events (#195896)
This PR sets up an `AtomicLineLogger` (added by
https://github.com/llvm/llvm-project/pull/205395) to log the sequence of
dependency scanning events. The logging is triggered by a new `LogPath`
option in `DependencyScanningServiceOptions`. This PR adds an option
`-log-path=` to `clang-scan-deps` to trigger the log.

We are logging 16 events during dependency scanning. 

| Event | Logged In | When |
|-------|-----------|------|
| `starting scanning command: <args>` | `DependencyScanningWorker.cpp` |
Start of each TU scan |
| `finished scanning command: <args>` | `DependencyScanningWorker.cpp` |
End of each TU scan |
| `init_compiler_instance_with_context: <args>` |
`DependencyScanningTool.cpp` | CompilerInstanceWithContext created for
by-name scanning |
| `start scan_by_name: <module name>` | `DependencyScanningTool.cpp` |
Start of a by-name module query |
| `finish scan_by_name: <module name>` | `DependencyScanningTool.cpp` |
End of a by-name module query |
| `module_compile_thread: parent=<TID> pcm_compile: <pcm path>` |
`CompilerInstance::compileModule` | A new thread is created to compile a
pcm |
| `timestamp_read: <pcm path>` |
`InProcessModuleCache::getModuleTimestamp` | Validation timestamp read |
| `timestamp_write: <pcm path>` |
`InProcessModuleCache::updateModuleTimestamp` | Validation timestamp
update |
| `pcm_write: <pcm path>` | `InProcessModuleCache::write` | PCM write to
disk attempted |
| `pcm_read_disk: <pcm path>` | `InProcessModuleCache::read` | PCM read
from disk attempted |
| `pcm_read_cached: <pcm path>` | `InMemoryModuleCache::lookupPCM` | PCM
looked up in in-memory cache |
| `pcm_add: <pcm path>` | `InMemoryModuleCache::addPCM` | PCM added to
cache (not validated) |
| `pcm_add_built: <pcm path>` | `InMemoryModuleCache::addBuiltPCM` | PCM
added to cache (final) |
| `pcm_finalized: <pcm path>` | `InMemoryModuleCache::finalizePCM` | PCM
marked as validated/final |
| `pcm_dropped: <pcm path>` | `InMemoryModuleCache::tryToDropPCM` | PCM
dropped (validation failed) |
| `pcm_not_dropped: <pcm path>` | `InMemoryModuleCache::tryToDropPCM` |
PCM not dropped (validation failed, but already final) |

In addition to checking the logger's functionality,
`clang/test/ClangScanDeps/logging-simple.c` checks against the change
committed by https://github.com/llvm/llvm-project/pull/177062. Without
https://github.com/llvm/llvm-project/pull/177062,
`clang/test/ClangScanDeps/logging-simple.c` fails.

This implementation adds a single instance of `AtomicLineLogger` to the
`DependencyScanningService`. Most of the plumbing goes into the
`ModuleCache` and its derived classes.

Assisted-by: claude-opus-4.6

rdar://39907408
2026-07-16 10:41:22 -07:00

204 lines
7.4 KiB
C++

//===- InProcessModuleCache.cpp - Implicit Module Cache ---------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/DependencyScanning/InProcessModuleCache.h"
#include "clang/Basic/AtomicLineLogger.h"
#include "clang/Serialization/InMemoryModuleCache.h"
#include "clang/Serialization/ModuleCache.h"
#include "llvm/Support/AdvisoryLock.h"
#include "llvm/Support/Chrono.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/IOSandbox.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
using namespace clang;
using namespace dependencies;
void ModuleCacheEntries::flush() {
auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
for (auto &[Path, Entry] : Map) {
if (Entry->State == ModuleCacheEntry::S_Written) {
assert(Entry->WrittenBuffer && "Wrote PCM with no contents");
// Note: We could propagate Entry->ModTime to the on-disk file, but
// implicitly-built modules (unlike explicitly-built modules) don't use
// that metadata to refer to imports, rendering this unnecessary.
off_t Size;
time_t ModTime;
// Best-effort: ignore errors (e.g. read-only cache directory).
(void)writeImpl(Path, *Entry->WrittenBuffer, Size, ModTime);
}
}
}
namespace {
class ReaderWriterLock : public llvm::AdvisoryLock {
ModuleCacheEntry &Entry;
std::optional<unsigned> OwnedGeneration;
public:
ReaderWriterLock(ModuleCacheEntry &Entry) : Entry(Entry) {}
Expected<bool> tryLock() override {
std::lock_guard<std::mutex> Lock(Entry.Mutex);
if (Entry.Locked)
return false;
Entry.Locked = true;
OwnedGeneration = Entry.Generation;
return true;
}
llvm::WaitForUnlockResult
waitForUnlockFor(std::chrono::seconds MaxSeconds) override {
assert(!OwnedGeneration);
std::unique_lock<std::mutex> Lock(Entry.Mutex);
unsigned CurrentGeneration = Entry.Generation;
bool Success = Entry.CondVar.wait_for(Lock, MaxSeconds, [&] {
// We check not only Locked, but also Generation to break the wait in case
// of unsafeUnlock() and successful tryLock().
return !Entry.Locked || Entry.Generation != CurrentGeneration;
});
return Success ? llvm::WaitForUnlockResult::Success
: llvm::WaitForUnlockResult::Timeout;
}
std::error_code unsafeUnlock() override {
{
std::lock_guard<std::mutex> Lock(Entry.Mutex);
Entry.Generation += 1;
Entry.Locked = false;
}
Entry.CondVar.notify_all();
return {};
}
~ReaderWriterLock() override {
if (OwnedGeneration) {
{
std::lock_guard<std::mutex> Lock(Entry.Mutex);
// Avoid stomping over the state managed by someone else after
// unsafeUnlock() and successful tryLock().
if (*OwnedGeneration == Entry.Generation)
Entry.Locked = false;
}
Entry.CondVar.notify_all();
}
}
};
class InProcessModuleCache : public ModuleCache {
ModuleCacheEntries &Entries;
// TODO: If we changed the InMemoryModuleCache API and relied on strict
// context hash, we could probably create more efficient thread-safe
// implementation of the InMemoryModuleCache such that it doesn't need to be
// recreated for each translation unit.
InMemoryModuleCache InMemory;
ModuleCacheEntry &getOrCreateEntry(StringRef Filename) {
std::lock_guard<std::mutex> Lock(Entries.Mutex);
auto &Entry = Entries.Map[Filename];
if (!Entry)
Entry = std::make_unique<ModuleCacheEntry>();
return *Entry;
}
public:
InProcessModuleCache(ModuleCacheEntries &Entries, AtomicLineLogger &Logger)
: ModuleCache(Logger), Entries(Entries), InMemory(Logger) {}
std::unique_ptr<llvm::AdvisoryLock> getLock(StringRef Filename) override {
auto &Entry = getOrCreateEntry(Filename);
return std::make_unique<ReaderWriterLock>(Entry);
}
std::time_t getModuleTimestamp(StringRef Filename) override {
auto &Timestamp = getOrCreateEntry(Filename).Timestamp;
Logger.log() << "timestamp_read: " << Filename;
return Timestamp.load();
}
void updateModuleTimestamp(StringRef Filename) override {
// Note: This essentially replaces FS contention with mutex contention.
auto &Timestamp = getOrCreateEntry(Filename).Timestamp;
Logger.log() << "timestamp_write: " << Filename;
Timestamp.store(llvm::sys::toTimeT(std::chrono::system_clock::now()));
}
void maybePrune(StringRef Path, time_t PruneInterval,
time_t PruneAfter) override {
// FIXME: This only needs to be ran once per build, not in every
// compilation. Call it once per service.
maybePruneImpl(Path, PruneInterval, PruneAfter);
}
InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
const InMemoryModuleCache &getInMemoryModuleCache() const override {
return InMemory;
}
std::error_code write(StringRef Path, llvm::MemoryBufferRef Buffer,
off_t &Size, time_t &ModTime) override {
ModuleCacheEntry &Entry = getOrCreateEntry(Path);
std::lock_guard<std::mutex> Lock(Entry.Mutex);
Logger.log() << "pcm_write: " << Path;
if (Entry.State == ModuleCacheEntry::S_Written) {
assert(Entry.WrittenBuffer && "Wrote PCM with no contents");
assert(Entry.WrittenBuffer->getBuffer() == Buffer.getBuffer() &&
"Wrote the same PCM with different contents");
Size = Entry.WrittenBuffer->getBufferSize();
ModTime = Entry.ModTime;
return {};
}
Entry.WrittenBuffer =
llvm::MemoryBuffer::getMemBufferCopy(Buffer.getBuffer(), Path);
Entry.ModTime = llvm::sys::toTimeT(std::chrono::system_clock::now());
Entry.State = ModuleCacheEntry::S_Written;
Size = Entry.WrittenBuffer->getBufferSize();
ModTime = Entry.ModTime;
return {};
}
Expected<std::unique_ptr<llvm::MemoryBuffer>>
read(StringRef FileName, off_t &Size, time_t &ModTime) override {
Logger.log() << "pcm_read_disk: " << FileName;
ModuleCacheEntry &Entry = getOrCreateEntry(FileName);
std::lock_guard<std::mutex> Lock(Entry.Mutex);
if (Entry.State == ModuleCacheEntry::S_Unknown) {
// This is a compiler-internal input/output, let's bypass the sandbox.
auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
off_t ReadSize;
time_t ReadModTime;
auto ReadBuffer = readImpl(FileName, ReadSize, ReadModTime);
if (!ReadBuffer)
return ReadBuffer.takeError();
Entry.ReadBuffer = std::move(*ReadBuffer);
Entry.ModTime = ReadModTime;
Entry.State = ModuleCacheEntry::S_Read;
}
// The written buffer takes precedence over any read buffer.
llvm::MemoryBuffer *Buffer = Entry.WrittenBuffer ? Entry.WrittenBuffer.get()
: Entry.ReadBuffer.get();
Size = Buffer->getBufferSize();
ModTime = Entry.ModTime;
// Note: Creates a reference to ReadBuffer or WrittenBuffer.
return llvm::MemoryBuffer::getMemBuffer(*Buffer,
/*RequiresNullTerminator=*/false);
}
};
} // namespace
std::shared_ptr<ModuleCache>
dependencies::makeInProcessModuleCache(ModuleCacheEntries &Entries,
AtomicLineLogger &Logger) {
return std::make_shared<InProcessModuleCache>(Entries, Logger);
}