//===- 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/Serialization/InMemoryModuleCache.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 OwnedGeneration; public: ReaderWriterLock(ModuleCacheEntry &Entry) : Entry(Entry) {} Expected tryLock() override { std::lock_guard 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 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 Lock(Entry.Mutex); Entry.Generation += 1; Entry.Locked = false; } Entry.CondVar.notify_all(); return {}; } ~ReaderWriterLock() override { if (OwnedGeneration) { { std::lock_guard 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 Lock(Entries.Mutex); auto &Entry = Entries.Map[Filename]; if (!Entry) Entry = std::make_unique(); return *Entry; } public: InProcessModuleCache(ModuleCacheEntries &Entries) : Entries(Entries) {} std::unique_ptr getLock(StringRef Filename) override { auto &Entry = getOrCreateEntry(Filename); return std::make_unique(Entry); } std::time_t getModuleTimestamp(StringRef Filename) override { auto &Timestamp = getOrCreateEntry(Filename).Timestamp; return Timestamp.load(); } void updateModuleTimestamp(StringRef Filename) override { // Note: This essentially replaces FS contention with mutex contention. auto &Timestamp = getOrCreateEntry(Filename).Timestamp; 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 Lock(Entry.Mutex); 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> read(StringRef FileName, off_t &Size, time_t &ModTime) override { ModuleCacheEntry &Entry = getOrCreateEntry(FileName); std::lock_guard 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 dependencies::makeInProcessModuleCache(ModuleCacheEntries &Entries) { return std::make_shared(Entries); }