//===- 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/IOSandbox.h" using namespace clang; using namespace dependencies; 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; public: InProcessModuleCache(ModuleCacheEntries &Entries) : Entries(Entries) {} std::unique_ptr getLock(StringRef Filename) override { auto &Entry = [&]() -> ModuleCacheEntry & { std::lock_guard Lock(Entries.Mutex); auto &Entry = Entries.Map[Filename]; if (!Entry) Entry = std::make_unique(); return *Entry; }(); return std::make_unique(Entry); } std::time_t getModuleTimestamp(StringRef Filename) override { auto &Timestamp = [&]() -> std::atomic & { std::lock_guard Lock(Entries.Mutex); auto &Entry = Entries.Map[Filename]; if (!Entry) Entry = std::make_unique(); 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::lock_guard Lock(Entries.Mutex); auto &Entry = Entries.Map[Filename]; if (!Entry) Entry = std::make_unique(); return Entry->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) override { // This is a compiler-internal input/output, let's bypass the sandbox. auto BypassSandbox = llvm::sys::sandbox::scopedDisable(); // FIXME: This could use an in-memory cache to avoid IO, and only write to // disk at the end of the scan. return writeImpl(Path, Buffer); } Expected> read(StringRef FileName, off_t &Size, time_t &ModTime) override { // This is a compiler-internal input/output, let's bypass the sandbox. auto BypassSandbox = llvm::sys::sandbox::scopedDisable(); // FIXME: This only needs to go to disk once per build, not in every // compilation. Introduce in-memory cache. return readImpl(FileName, Size, ModTime); } }; } // namespace std::shared_ptr dependencies::makeInProcessModuleCache(ModuleCacheEntries &Entries) { return std::make_shared(Entries); }