1//===- InProcessModuleCache.cpp - Implicit Module Cache ---------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang/DependencyScanning/InProcessModuleCache.h"
10
11#include "clang/Basic/AtomicLineLogger.h"
12#include "clang/Serialization/InMemoryModuleCache.h"
13#include "clang/Serialization/ModuleCache.h"
14#include "llvm/Support/AdvisoryLock.h"
15#include "llvm/Support/Chrono.h"
16#include "llvm/Support/Error.h"
17#include "llvm/Support/FileSystem.h"
18#include "llvm/Support/IOSandbox.h"
19#include "llvm/Support/MemoryBuffer.h"
20#include "llvm/Support/Path.h"
21
22using namespace clang;
23using namespace dependencies;
24
25void ModuleCacheEntries::addInvalidatedPath(StringRef Path) {
26#ifndef NDEBUG
27 SmallString<256> Canonical(Path);
28 assert(llvm::sys::path::is_absolute(Path) &&
29 !llvm::sys::path::remove_dots(Canonical, /*remove_dot_dot=*/true) &&
30 "invalidated paths must be spelled like ModuleDeps::DirectoryDeps");
31#endif
32 {
33 std::lock_guard<std::mutex> Lock(InvalidatedPathsMutex);
34 InvalidatedPaths.insert(key: Path);
35 }
36 AnyInvalidatedPaths.store(i: true, m: std::memory_order_release);
37}
38
39std::optional<bool>
40ModuleCacheEntries::isDirectoryInvalidated(StringRef Directory) const {
41 if (!ValidateAgainstInvalidatedPaths)
42 return std::nullopt;
43 if (!AnyInvalidatedPaths.load(m: std::memory_order_acquire))
44 return false;
45 std::lock_guard<std::mutex> Lock(InvalidatedPathsMutex);
46 return InvalidatedPaths.contains(key: Directory);
47}
48
49void ModuleCacheEntries::flush() {
50 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
51 for (auto &[Path, Entry] : Map) {
52 if (Entry->State == ModuleCacheEntry::S_Written) {
53 assert(Entry->WrittenBuffer && "Wrote PCM with no contents");
54 // Note: We could propagate Entry->ModTime to the on-disk file, but
55 // implicitly-built modules (unlike explicitly-built modules) don't use
56 // that metadata to refer to imports, rendering this unnecessary.
57 off_t Size;
58 time_t ModTime;
59 // Best-effort: ignore errors (e.g. read-only cache directory).
60 (void)writeImpl(Path, Buffer: *Entry->WrittenBuffer, Size, ModTime);
61 }
62 }
63}
64
65namespace {
66class ReaderWriterLock : public llvm::AdvisoryLock {
67 ModuleCacheEntry &Entry;
68 std::optional<unsigned> OwnedGeneration;
69
70public:
71 ReaderWriterLock(ModuleCacheEntry &Entry) : Entry(Entry) {}
72
73 Expected<bool> tryLock() override {
74 std::lock_guard<std::mutex> Lock(Entry.Mutex);
75 if (Entry.Locked)
76 return false;
77 Entry.Locked = true;
78 OwnedGeneration = Entry.Generation;
79 return true;
80 }
81
82 llvm::WaitForUnlockResult
83 waitForUnlockFor(std::chrono::seconds MaxSeconds) override {
84 assert(!OwnedGeneration);
85 std::unique_lock<std::mutex> Lock(Entry.Mutex);
86 unsigned CurrentGeneration = Entry.Generation;
87 bool Success = Entry.CondVar.wait_for(lock&: Lock, rtime: MaxSeconds, p: [&] {
88 // We check not only Locked, but also Generation to break the wait in case
89 // of unsafeUnlock() and successful tryLock().
90 return !Entry.Locked || Entry.Generation != CurrentGeneration;
91 });
92 return Success ? llvm::WaitForUnlockResult::Success
93 : llvm::WaitForUnlockResult::Timeout;
94 }
95
96 std::error_code unsafeUnlock() override {
97 {
98 std::lock_guard<std::mutex> Lock(Entry.Mutex);
99 Entry.Generation += 1;
100 Entry.Locked = false;
101 }
102 Entry.CondVar.notify_all();
103 return {};
104 }
105
106 ~ReaderWriterLock() override {
107 if (OwnedGeneration) {
108 {
109 std::lock_guard<std::mutex> Lock(Entry.Mutex);
110 // Avoid stomping over the state managed by someone else after
111 // unsafeUnlock() and successful tryLock().
112 if (*OwnedGeneration == Entry.Generation)
113 Entry.Locked = false;
114 }
115 Entry.CondVar.notify_all();
116 }
117 }
118};
119
120class InProcessModuleCache : public ModuleCache {
121 ModuleCacheEntries &Entries;
122
123 // TODO: If we changed the InMemoryModuleCache API and relied on strict
124 // context hash, we could probably create more efficient thread-safe
125 // implementation of the InMemoryModuleCache such that it doesn't need to be
126 // recreated for each translation unit.
127 InMemoryModuleCache InMemory;
128
129 ModuleCacheEntry &getOrCreateEntry(StringRef Filename) {
130 std::lock_guard<std::mutex> Lock(Entries.Mutex);
131 auto &Entry = Entries.Map[Filename];
132 if (!Entry)
133 Entry = std::make_unique<ModuleCacheEntry>();
134 return *Entry;
135 }
136
137public:
138 InProcessModuleCache(ModuleCacheEntries &Entries, AtomicLineLogger &Logger)
139 : ModuleCache(Logger), Entries(Entries), InMemory(Logger) {}
140
141 std::unique_ptr<llvm::AdvisoryLock> getLock(StringRef Filename) override {
142 auto &Entry = getOrCreateEntry(Filename);
143 return std::make_unique<ReaderWriterLock>(args&: Entry);
144 }
145
146 std::time_t getModuleTimestamp(StringRef Filename) override {
147 auto &Timestamp = getOrCreateEntry(Filename).Timestamp;
148
149 Logger.log() << "timestamp_read: " << Filename;
150 return Timestamp.load();
151 }
152
153 void updateModuleTimestamp(StringRef Filename) override {
154 // Note: This essentially replaces FS contention with mutex contention.
155 auto &Timestamp = getOrCreateEntry(Filename).Timestamp;
156
157 Logger.log() << "timestamp_write: " << Filename;
158 Timestamp.store(i: llvm::sys::toTimeT(TP: std::chrono::system_clock::now()));
159 }
160
161 std::optional<bool> isDirectoryInvalidated(StringRef Directory) override {
162 return Entries.isDirectoryInvalidated(Directory);
163 }
164
165 void maybePrune(StringRef Path, time_t PruneInterval,
166 time_t PruneAfter) override {
167 // FIXME: This only needs to be ran once per build, not in every
168 // compilation. Call it once per service.
169 maybePruneImpl(Path, PruneInterval, PruneAfter);
170 }
171
172 InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
173 const InMemoryModuleCache &getInMemoryModuleCache() const override {
174 return InMemory;
175 }
176
177 std::error_code write(StringRef Path, llvm::MemoryBufferRef Buffer,
178 off_t &Size, time_t &ModTime) override {
179 ModuleCacheEntry &Entry = getOrCreateEntry(Filename: Path);
180 std::lock_guard<std::mutex> Lock(Entry.Mutex);
181 Logger.log() << "pcm_write: " << Path;
182 if (Entry.State == ModuleCacheEntry::S_Written) {
183 assert(Entry.WrittenBuffer && "Wrote PCM with no contents");
184 assert(Entry.WrittenBuffer->getBuffer() == Buffer.getBuffer() &&
185 "Wrote the same PCM with different contents");
186 Size = Entry.WrittenBuffer->getBufferSize();
187 ModTime = Entry.ModTime;
188 return {};
189 }
190 Entry.WrittenBuffer =
191 llvm::MemoryBuffer::getMemBufferCopy(InputData: Buffer.getBuffer(), BufferName: Path);
192 Entry.ModTime = llvm::sys::toTimeT(TP: std::chrono::system_clock::now());
193 Entry.State = ModuleCacheEntry::S_Written;
194 Size = Entry.WrittenBuffer->getBufferSize();
195 ModTime = Entry.ModTime;
196 return {};
197 }
198
199 Expected<std::unique_ptr<llvm::MemoryBuffer>>
200 read(StringRef FileName, off_t &Size, time_t &ModTime) override {
201 Logger.log() << "pcm_read_disk: " << FileName;
202 ModuleCacheEntry &Entry = getOrCreateEntry(Filename: FileName);
203 std::lock_guard<std::mutex> Lock(Entry.Mutex);
204 if (Entry.State == ModuleCacheEntry::S_Unknown) {
205 // This is a compiler-internal input/output, let's bypass the sandbox.
206 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
207 off_t ReadSize;
208 time_t ReadModTime;
209 auto ReadBuffer = readImpl(FileName, Size&: ReadSize, ModTime&: ReadModTime);
210 if (!ReadBuffer)
211 return ReadBuffer.takeError();
212 Entry.ReadBuffer = std::move(*ReadBuffer);
213 Entry.ModTime = ReadModTime;
214 Entry.State = ModuleCacheEntry::S_Read;
215 }
216 // The written buffer takes precedence over any read buffer.
217 llvm::MemoryBuffer *Buffer = Entry.WrittenBuffer ? Entry.WrittenBuffer.get()
218 : Entry.ReadBuffer.get();
219 Size = Buffer->getBufferSize();
220 ModTime = Entry.ModTime;
221 // Note: Creates a reference to ReadBuffer or WrittenBuffer.
222 return llvm::MemoryBuffer::getMemBuffer(Ref: *Buffer,
223 /*RequiresNullTerminator=*/false);
224 }
225};
226} // namespace
227
228std::shared_ptr<ModuleCache>
229dependencies::makeInProcessModuleCache(ModuleCacheEntries &Entries,
230 AtomicLineLogger &Logger) {
231 return std::make_shared<InProcessModuleCache>(args&: Entries, args&: Logger);
232}
233