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/Serialization/InMemoryModuleCache.h"
12#include "llvm/Support/AdvisoryLock.h"
13#include "llvm/Support/Chrono.h"
14#include "llvm/Support/Error.h"
15#include "llvm/Support/IOSandbox.h"
16
17using namespace clang;
18using namespace dependencies;
19
20namespace {
21class ReaderWriterLock : public llvm::AdvisoryLock {
22 ModuleCacheEntry &Entry;
23 std::optional<unsigned> OwnedGeneration;
24
25public:
26 ReaderWriterLock(ModuleCacheEntry &Entry) : Entry(Entry) {}
27
28 Expected<bool> tryLock() override {
29 std::lock_guard<std::mutex> Lock(Entry.Mutex);
30 if (Entry.Locked)
31 return false;
32 Entry.Locked = true;
33 OwnedGeneration = Entry.Generation;
34 return true;
35 }
36
37 llvm::WaitForUnlockResult
38 waitForUnlockFor(std::chrono::seconds MaxSeconds) override {
39 assert(!OwnedGeneration);
40 std::unique_lock<std::mutex> Lock(Entry.Mutex);
41 unsigned CurrentGeneration = Entry.Generation;
42 bool Success = Entry.CondVar.wait_for(lock&: Lock, rtime: MaxSeconds, p: [&] {
43 // We check not only Locked, but also Generation to break the wait in case
44 // of unsafeUnlock() and successful tryLock().
45 return !Entry.Locked || Entry.Generation != CurrentGeneration;
46 });
47 return Success ? llvm::WaitForUnlockResult::Success
48 : llvm::WaitForUnlockResult::Timeout;
49 }
50
51 std::error_code unsafeUnlock() override {
52 {
53 std::lock_guard<std::mutex> Lock(Entry.Mutex);
54 Entry.Generation += 1;
55 Entry.Locked = false;
56 }
57 Entry.CondVar.notify_all();
58 return {};
59 }
60
61 ~ReaderWriterLock() override {
62 if (OwnedGeneration) {
63 {
64 std::lock_guard<std::mutex> Lock(Entry.Mutex);
65 // Avoid stomping over the state managed by someone else after
66 // unsafeUnlock() and successful tryLock().
67 if (*OwnedGeneration == Entry.Generation)
68 Entry.Locked = false;
69 }
70 Entry.CondVar.notify_all();
71 }
72 }
73};
74
75class InProcessModuleCache : public ModuleCache {
76 ModuleCacheEntries &Entries;
77
78 // TODO: If we changed the InMemoryModuleCache API and relied on strict
79 // context hash, we could probably create more efficient thread-safe
80 // implementation of the InMemoryModuleCache such that it doesn't need to be
81 // recreated for each translation unit.
82 InMemoryModuleCache InMemory;
83
84public:
85 InProcessModuleCache(ModuleCacheEntries &Entries) : Entries(Entries) {}
86
87 void prepareForGetLock(StringRef Filename) override {}
88
89 std::unique_ptr<llvm::AdvisoryLock> getLock(StringRef Filename) override {
90 auto &Entry = [&]() -> ModuleCacheEntry & {
91 std::lock_guard<std::mutex> Lock(Entries.Mutex);
92 auto &Entry = Entries.Map[Filename];
93 if (!Entry)
94 Entry = std::make_unique<ModuleCacheEntry>();
95 return *Entry;
96 }();
97 return std::make_unique<ReaderWriterLock>(args&: Entry);
98 }
99
100 std::time_t getModuleTimestamp(StringRef Filename) override {
101 auto &Timestamp = [&]() -> std::atomic<std::time_t> & {
102 std::lock_guard<std::mutex> Lock(Entries.Mutex);
103 auto &Entry = Entries.Map[Filename];
104 if (!Entry)
105 Entry = std::make_unique<ModuleCacheEntry>();
106 return Entry->Timestamp;
107 }();
108
109 return Timestamp.load();
110 }
111
112 void updateModuleTimestamp(StringRef Filename) override {
113 // Note: This essentially replaces FS contention with mutex contention.
114 auto &Timestamp = [&]() -> std::atomic<std::time_t> & {
115 std::lock_guard<std::mutex> Lock(Entries.Mutex);
116 auto &Entry = Entries.Map[Filename];
117 if (!Entry)
118 Entry = std::make_unique<ModuleCacheEntry>();
119 return Entry->Timestamp;
120 }();
121
122 Timestamp.store(i: llvm::sys::toTimeT(TP: std::chrono::system_clock::now()));
123 }
124
125 void maybePrune(StringRef Path, time_t PruneInterval,
126 time_t PruneAfter) override {
127 // FIXME: This only needs to be ran once per build, not in every
128 // compilation. Call it once per service.
129 maybePruneImpl(Path, PruneInterval, PruneAfter);
130 }
131
132 InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
133 const InMemoryModuleCache &getInMemoryModuleCache() const override {
134 return InMemory;
135 }
136
137 std::error_code write(StringRef Path, llvm::MemoryBufferRef Buffer) override {
138 // This is a compiler-internal input/output, let's bypass the sandbox.
139 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
140
141 // FIXME: This could use an in-memory cache to avoid IO, and only write to
142 // disk at the end of the scan.
143 return writeImpl(Path, Buffer);
144 }
145
146 Expected<std::unique_ptr<llvm::MemoryBuffer>>
147 read(StringRef FileName, off_t &Size, time_t &ModTime) override {
148 // This is a compiler-internal input/output, let's bypass the sandbox.
149 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
150
151 // FIXME: This only needs to go to disk once per build, not in every
152 // compilation. Introduce in-memory cache.
153 return readImpl(FileName, Size, ModTime);
154 }
155};
156} // namespace
157
158std::shared_ptr<ModuleCache>
159dependencies::makeInProcessModuleCache(ModuleCacheEntries &Entries) {
160 return std::make_shared<InProcessModuleCache>(args&: Entries);
161}
162