1//===----------------------------------------------------------------------===//
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/Tooling/DependencyScanning/InProcessModuleCache.h"
10
11#include "clang/Serialization/InMemoryModuleCache.h"
12#include "llvm/Support/AdvisoryLock.h"
13#include "llvm/Support/Chrono.h"
14
15#include <mutex>
16
17using namespace clang;
18using namespace tooling;
19using namespace dependencies;
20
21namespace {
22class ReaderWriterLock : public llvm::AdvisoryLock {
23 // TODO: Consider using std::atomic::{wait,notify_all} when we move to C++20.
24 std::unique_lock<std::shared_mutex> OwningLock;
25
26public:
27 ReaderWriterLock(std::shared_mutex &Mutex)
28 : OwningLock(Mutex, std::defer_lock) {}
29
30 Expected<bool> tryLock() override { return OwningLock.try_lock(); }
31
32 llvm::WaitForUnlockResult
33 waitForUnlockFor(std::chrono::seconds MaxSeconds) override {
34 assert(!OwningLock);
35 // We do not respect the timeout here. It's very generous for implicit
36 // modules, so we'd typically only reach it if the owner crashed (but so did
37 // we, since we run in the same process), or encountered deadlock.
38 (void)MaxSeconds;
39 std::shared_lock<std::shared_mutex> Lock(*OwningLock.mutex());
40 return llvm::WaitForUnlockResult::Success;
41 }
42
43 std::error_code unsafeMaybeUnlock() override {
44 // Unlocking the mutex here would trigger UB and we don't expect this to be
45 // actually called when compiling scanning modules due to the no-timeout
46 // guarantee above.
47 return {};
48 }
49
50 ~ReaderWriterLock() override = default;
51};
52
53class InProcessModuleCache : public ModuleCache {
54 ModuleCacheEntries &Entries;
55
56 // TODO: If we changed the InMemoryModuleCache API and relied on strict
57 // context hash, we could probably create more efficient thread-safe
58 // implementation of the InMemoryModuleCache such that it doesn't need to be
59 // recreated for each translation unit.
60 InMemoryModuleCache InMemory;
61
62public:
63 InProcessModuleCache(ModuleCacheEntries &Entries) : Entries(Entries) {}
64
65 void prepareForGetLock(StringRef Filename) override {}
66
67 std::unique_ptr<llvm::AdvisoryLock> getLock(StringRef Filename) override {
68 auto &CompilationMutex = [&]() -> std::shared_mutex & {
69 std::lock_guard<std::mutex> Lock(Entries.Mutex);
70 auto &Entry = Entries.Map[Filename];
71 if (!Entry)
72 Entry = std::make_unique<ModuleCacheEntry>();
73 return Entry->CompilationMutex;
74 }();
75 return std::make_unique<ReaderWriterLock>(args&: CompilationMutex);
76 }
77
78 std::time_t getModuleTimestamp(StringRef Filename) override {
79 auto &Timestamp = [&]() -> std::atomic<std::time_t> & {
80 std::lock_guard<std::mutex> Lock(Entries.Mutex);
81 auto &Entry = Entries.Map[Filename];
82 if (!Entry)
83 Entry = std::make_unique<ModuleCacheEntry>();
84 return Entry->Timestamp;
85 }();
86
87 return Timestamp.load();
88 }
89
90 void updateModuleTimestamp(StringRef Filename) override {
91 // Note: This essentially replaces FS contention with mutex contention.
92 auto &Timestamp = [&]() -> std::atomic<std::time_t> & {
93 std::lock_guard<std::mutex> Lock(Entries.Mutex);
94 auto &Entry = Entries.Map[Filename];
95 if (!Entry)
96 Entry = std::make_unique<ModuleCacheEntry>();
97 return Entry->Timestamp;
98 }();
99
100 Timestamp.store(i: llvm::sys::toTimeT(TP: std::chrono::system_clock::now()));
101 }
102
103 InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
104 const InMemoryModuleCache &getInMemoryModuleCache() const override {
105 return InMemory;
106 }
107};
108} // namespace
109
110IntrusiveRefCntPtr<ModuleCache>
111dependencies::makeInProcessModuleCache(ModuleCacheEntries &Entries) {
112 return llvm::makeIntrusiveRefCnt<InProcessModuleCache>(A&: Entries);
113}
114