clang 20.0.0git
DependencyScanningFilesystem.cpp
Go to the documentation of this file.
1//===- DependencyScanningFilesystem.cpp - clang-scan-deps fs --------------===//
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
10#include "llvm/Support/MemoryBuffer.h"
11#include "llvm/Support/SmallVectorMemoryBuffer.h"
12#include "llvm/Support/Threading.h"
13#include <optional>
14
15using namespace clang;
16using namespace tooling;
17using namespace dependencies;
18
19llvm::ErrorOr<DependencyScanningWorkerFilesystem::TentativeEntry>
20DependencyScanningWorkerFilesystem::readFile(StringRef Filename) {
21 // Load the file and its content from the file system.
22 auto MaybeFile = getUnderlyingFS().openFileForRead(Filename);
23 if (!MaybeFile)
24 return MaybeFile.getError();
25 auto File = std::move(*MaybeFile);
26
27 auto MaybeStat = File->status();
28 if (!MaybeStat)
29 return MaybeStat.getError();
30 auto Stat = std::move(*MaybeStat);
31
32 auto MaybeBuffer = File->getBuffer(Stat.getName());
33 if (!MaybeBuffer)
34 return MaybeBuffer.getError();
35 auto Buffer = std::move(*MaybeBuffer);
36
37 // If the file size changed between read and stat, pretend it didn't.
38 if (Stat.getSize() != Buffer->getBufferSize())
39 Stat = llvm::vfs::Status::copyWithNewSize(Stat, Buffer->getBufferSize());
40
41 return TentativeEntry(Stat, std::move(Buffer));
42}
43
45 EntryRef Ref) {
46 auto &Entry = Ref.Entry;
47
48 if (Entry.isError() || Entry.isDirectory())
49 return false;
50
51 CachedFileContents *Contents = Entry.getCachedContents();
52 assert(Contents && "contents not initialized");
53
54 // Double-checked locking.
55 if (Contents->DepDirectives.load())
56 return true;
57
58 std::lock_guard<std::mutex> GuardLock(Contents->ValueLock);
59
60 // Double-checked locking.
61 if (Contents->DepDirectives.load())
62 return true;
63
65 // Scan the file for preprocessor directives that might affect the
66 // dependencies.
67 if (scanSourceForDependencyDirectives(Contents->Original->getBuffer(),
68 Contents->DepDirectiveTokens,
69 Directives)) {
70 Contents->DepDirectiveTokens.clear();
71 // FIXME: Propagate the diagnostic if desired by the client.
72 Contents->DepDirectives.store(new std::optional<DependencyDirectivesTy>());
73 return false;
74 }
75
76 // This function performed double-checked locking using `DepDirectives`.
77 // Assigning it must be the last thing this function does, otherwise other
78 // threads may skip the critical section (`DepDirectives != nullptr`), leading
79 // to a data race.
80 Contents->DepDirectives.store(
81 new std::optional<DependencyDirectivesTy>(std::move(Directives)));
82 return true;
83}
84
87 // This heuristic was chosen using a empirical testing on a
88 // reasonably high core machine (iMacPro 18 cores / 36 threads). The cache
89 // sharding gives a performance edge by reducing the lock contention.
90 // FIXME: A better heuristic might also consider the OS to account for
91 // the different cost of lock contention on different OSes.
92 NumShards =
93 std::max(2u, llvm::hardware_concurrency().compute_thread_count() / 4);
94 CacheShards = std::make_unique<CacheShard[]>(NumShards);
95}
96
99 StringRef Filename) const {
100 assert(llvm::sys::path::is_absolute_gnu(Filename));
101 return CacheShards[llvm::hash_value(Filename) % NumShards];
102}
103
106 llvm::sys::fs::UniqueID UID) const {
107 auto Hash = llvm::hash_combine(UID.getDevice(), UID.getFile());
108 return CacheShards[Hash % NumShards];
109}
110
113 StringRef Filename) const {
114 assert(llvm::sys::path::is_absolute_gnu(Filename));
115 std::lock_guard<std::mutex> LockGuard(CacheLock);
116 auto It = CacheByFilename.find(Filename);
117 return It == CacheByFilename.end() ? nullptr : It->getValue().first;
118}
119
122 llvm::sys::fs::UniqueID UID) const {
123 std::lock_guard<std::mutex> LockGuard(CacheLock);
124 auto It = EntriesByUID.find(UID);
125 return It == EntriesByUID.end() ? nullptr : It->getSecond();
126}
127
131 llvm::ErrorOr<llvm::vfs::Status> Stat) {
132 std::lock_guard<std::mutex> LockGuard(CacheLock);
133 auto [It, Inserted] = CacheByFilename.insert({Filename, {nullptr, nullptr}});
134 auto &[CachedEntry, CachedRealPath] = It->getValue();
135 if (!CachedEntry) {
136 // The entry is not present in the shared cache. Either the cache doesn't
137 // know about the file at all, or it only knows about its real path.
138 assert((Inserted || CachedRealPath) && "existing file with empty pair");
139 CachedEntry =
140 new (EntryStorage.Allocate()) CachedFileSystemEntry(std::move(Stat));
141 }
142 return *CachedEntry;
143}
144
147 llvm::sys::fs::UniqueID UID, llvm::vfs::Status Stat,
148 std::unique_ptr<llvm::MemoryBuffer> Contents) {
149 std::lock_guard<std::mutex> LockGuard(CacheLock);
150 auto [It, Inserted] = EntriesByUID.insert({UID, nullptr});
151 auto &CachedEntry = It->getSecond();
152 if (Inserted) {
153 CachedFileContents *StoredContents = nullptr;
154 if (Contents)
155 StoredContents = new (ContentsStorage.Allocate())
156 CachedFileContents(std::move(Contents));
157 CachedEntry = new (EntryStorage.Allocate())
158 CachedFileSystemEntry(std::move(Stat), StoredContents);
159 }
160 return *CachedEntry;
161}
162
166 const CachedFileSystemEntry &Entry) {
167 std::lock_guard<std::mutex> LockGuard(CacheLock);
168 auto [It, Inserted] = CacheByFilename.insert({Filename, {&Entry, nullptr}});
169 auto &[CachedEntry, CachedRealPath] = It->getValue();
170 if (!Inserted || !CachedEntry)
171 CachedEntry = &Entry;
172 return *CachedEntry;
173}
174
175const CachedRealPath *
177 StringRef Filename) const {
178 assert(llvm::sys::path::is_absolute_gnu(Filename));
179 std::lock_guard<std::mutex> LockGuard(CacheLock);
180 auto It = CacheByFilename.find(Filename);
181 return It == CacheByFilename.end() ? nullptr : It->getValue().second;
182}
183
186 llvm::ErrorOr<llvm::StringRef> RealPath) {
187 std::lock_guard<std::mutex> LockGuard(CacheLock);