16
16
#include " llvm/ADT/None.h"
17
17
#include " llvm/ADT/Optional.h"
18
18
#include " llvm/ADT/STLExtras.h"
19
+ #include " llvm/ADT/ScopeExit.h"
19
20
#include " llvm/ADT/SmallString.h"
20
21
#include " llvm/Support/FileSystem.h"
21
22
#include " llvm/Support/FileUtilities.h"
22
23
#include " llvm/Support/Path.h"
23
24
#include " llvm/Support/Program.h"
25
+ #include < chrono>
24
26
#include < string>
25
27
#include < tuple>
26
28
#include < vector>
@@ -58,10 +60,117 @@ GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
58
60
return Cmd;
59
61
}
60
62
63
+ // Loads and caches the CDB from a single directory.
64
+ //
65
+ // This class is threadsafe, which is to say we have independent locks for each
66
+ // directory we're searching for a CDB.
67
+ // Loading is deferred until first access.
68
+ //
69
+ // The DirectoryBasedCDB keeps a map from path => DirectoryCache.
70
+ // Typical usage is to:
71
+ // - 1) determine all the paths that might be searched
72
+ // - 2) acquire the map lock and get-or-create all the DirectoryCache entries
73
+ // - 3) release the map lock and query the caches as desired
74
+ //
75
+ // FIXME: this should revalidate the cache sometimes
76
+ // FIXME: IO should go through a VFS
77
+ class DirectoryBasedGlobalCompilationDatabase ::DirectoryCache {
78
+ // Absolute canonical path that we're the cache for. (Not case-folded).
79
+ const std::string Path;
80
+
81
+ // True if we've looked for a CDB here and found none.
82
+ // (This makes it possible for get() to return without taking a lock)
83
+ // FIXME: this should have an expiry time instead of lasting forever.
84
+ std::atomic<bool > FinalizedNoCDB = {false };
85
+
86
+ // Guards following cache state.
87
+ std::mutex Mu;
88
+ // Has cache been filled from disk? FIXME: this should be an expiry time.
89
+ bool CachePopulated = false ;
90
+ // Whether a new CDB has been loaded but not broadcast yet.
91
+ bool NeedsBroadcast = false ;
92
+ // Last loaded CDB, meaningful if CachePopulated is set.
93
+ // shared_ptr so we can overwrite this when callers are still using the CDB.
94
+ std::shared_ptr<tooling::CompilationDatabase> CDB;
95
+
96
+ public:
97
+ DirectoryCache (llvm::StringRef Path) : Path(Path) {
98
+ assert (llvm::sys::path::is_absolute (Path));
99
+ }
100
+
101
+ // Get the CDB associated with this directory.
102
+ // ShouldBroadcast:
103
+ // - as input, signals whether the caller is willing to broadcast a
104
+ // newly-discovered CDB. (e.g. to trigger background indexing)
105
+ // - as output, signals whether the caller should do so.
106
+ // (If a new CDB is discovered and ShouldBroadcast is false, we mark the
107
+ // CDB as needing broadcast, and broadcast it next time we can).
108
+ std::shared_ptr<const tooling::CompilationDatabase>
109
+ get (bool &ShouldBroadcast) {
110
+ // Fast path for common case without taking lock.
111
+ if (FinalizedNoCDB.load ()) {
112
+ ShouldBroadcast = false ;
113
+ return nullptr ;
114
+ }
115
+ std::lock_guard<std::mutex> Lock (Mu);
116
+ auto RequestBroadcast = llvm::make_scope_exit ([&, OldCDB (CDB.get ())] {
117
+ // If we loaded a new CDB, it should be broadcast at some point.
118
+ if (CDB != nullptr && CDB.get () != OldCDB)
119
+ NeedsBroadcast = true ;
120
+ else if (CDB == nullptr ) // nothing to broadcast anymore!
121
+ NeedsBroadcast = false ;
122
+ // If we have something to broadcast, then do so iff allowed.
123
+ if (!ShouldBroadcast)
124
+ return ;
125
+ ShouldBroadcast = NeedsBroadcast;
126
+ NeedsBroadcast = false ;
127
+ });
128
+
129
+ // For now, we never actually attempt to revalidate a populated cache.
130
+ if (CachePopulated)
131
+ return CDB;
132
+ assert (CDB == nullptr );
133
+
134
+ load ();
135
+ CachePopulated = true ;
136
+
137
+ if (!CDB)
138
+ FinalizedNoCDB.store (true );
139
+ return CDB;
140
+ }
141
+
142
+ llvm::StringRef path () const { return Path; }
143
+
144
+ private:
145
+ // Updates `CDB` from disk state.
146
+ void load () {
147
+ std::string Error; // ignored, because it's often "didn't find anything".
148
+ CDB = tooling::CompilationDatabase::loadFromDirectory (Path, Error);
149
+ if (!CDB) {
150
+ // Fallback: check for $src/build, the conventional CMake build root.
151
+ // Probe existence first to avoid each plugin doing IO if it doesn't
152
+ // exist.
153
+ llvm::SmallString<256 > BuildDir (Path);
154
+ llvm::sys::path::append (BuildDir, " build" );
155
+ if (llvm::sys::fs::is_directory (BuildDir)) {
156
+ vlog (" Found candidate build directory {0}" , BuildDir);
157
+ CDB = tooling::CompilationDatabase::loadFromDirectory (BuildDir, Error);
158
+ }
159
+ }
160
+ if (CDB) {
161
+ log (" Loaded compilation database from {0}" , Path);
162
+ } else {
163
+ vlog (" No compilation database at {0}" , Path);
164
+ }
165
+ }
166
+ };
167
+
61
168
DirectoryBasedGlobalCompilationDatabase::
62
169
DirectoryBasedGlobalCompilationDatabase (
63
- llvm::Optional<Path> CompileCommandsDir)
64
- : CompileCommandsDir(std::move(CompileCommandsDir)) {}
170
+ llvm::Optional<Path> CompileCommandsDir) {
171
+ if (CompileCommandsDir)
172
+ OnlyDirCache = std::make_unique<DirectoryCache>(*CompileCommandsDir);
173
+ }
65
174
66
175
DirectoryBasedGlobalCompilationDatabase::
67
176
~DirectoryBasedGlobalCompilationDatabase () = default ;
@@ -107,31 +216,26 @@ static bool pathEqual(PathRef A, PathRef B) {
107
216
#endif
108
217
}
109
218
110
- DirectoryBasedGlobalCompilationDatabase::CachedCDB &
111
- DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked (PathRef Dir) const {
112
- // FIXME(ibiryukov): Invalidate cached compilation databases on changes
113
- auto Key = maybeCaseFoldPath (Dir);
114
- auto R = CompilationDatabases.try_emplace (Key);
115
- if (R.second ) { // Cache miss, try to load CDB.
116
- CachedCDB &Entry = R.first ->second ;
117
- std::string Error;
118
- Entry.Path = std::string (Dir);
119
- Entry.CDB = tooling::CompilationDatabase::loadFromDirectory (Dir, Error);
120
- // Check for $src/build, the conventional CMake build root.
121
- // Probe existence first to avoid each plugin doing IO if it doesn't exist.
122
- if (!CompileCommandsDir && !Entry.CDB ) {
123
- llvm::SmallString<256 > BuildDir = Dir;
124
- llvm::sys::path::append (BuildDir, " build" );
125
- if (llvm::sys::fs::is_directory (BuildDir)) {
126
- vlog (" Found candidate build directory {0}" , BuildDir);
127
- Entry.CDB =
128
- tooling::CompilationDatabase::loadFromDirectory (BuildDir, Error);
129
- }
130
- }
131
- if (Entry.CDB )
132
- log (" Loaded compilation database from {0}" , Dir);
219
+ std::vector<DirectoryBasedGlobalCompilationDatabase::DirectoryCache *>
220
+ DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches (
221
+ llvm::ArrayRef<llvm::StringRef> Dirs) const {
222
+ std::vector<std::string> FoldedDirs;
223
+ FoldedDirs.reserve (Dirs.size ());
224
+ for (const auto &Dir : Dirs) {
225
+ #ifndef NDEBUG
226
+ if (!llvm::sys::path::is_absolute (Dir))
227
+ elog (" Trying to cache CDB for relative {0}" );
228
+ #endif
229
+ FoldedDirs.push_back (maybeCaseFoldPath (Dir));
133
230
}
134
- return R.first ->second ;
231
+
232
+ std::vector<DirectoryCache *> Ret;
233
+ Ret.reserve (Dirs.size ());
234
+
235
+ std::lock_guard<std::mutex> Lock (DirCachesMutex);
236
+ for (unsigned I = 0 ; I < Dirs.size (); ++I)
237
+ Ret.push_back (&DirCaches.try_emplace (FoldedDirs[I], Dirs[I]).first ->second );
238
+ return Ret;
135
239
}
136
240
137
241
llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
@@ -141,39 +245,40 @@ DirectoryBasedGlobalCompilationDatabase::lookupCDB(
141
245
" path must be absolute" );
142
246
143
247
bool ShouldBroadcast = false ;
144
- CDBLookupResult Result;
145
-
146
- {
147
- std::lock_guard<std::mutex> Lock (Mutex);
148
- CachedCDB *Entry = nullptr ;
149
- if (CompileCommandsDir) {
150
- Entry = &getCDBInDirLocked (*CompileCommandsDir);
151
- } else {
152
- // Traverse the canonical version to prevent false positives. i.e.:
153
- // src/build/../a.cc can detect a CDB in /src/build if not canonicalized.
154
- // FIXME(sammccall): this loop is hot, use a union-find-like structure.
155
- actOnAllParentDirectories (removeDots (Request.FileName ),
156
- [&](PathRef Path) {
157
- Entry = &getCDBInDirLocked (Path);
158
- return Entry->CDB != nullptr ;
159
- });
248
+ DirectoryCache *DirCache = nullptr ;
249
+ std::shared_ptr<const tooling::CompilationDatabase> CDB = nullptr ;
250
+ if (OnlyDirCache) {
251
+ DirCache = OnlyDirCache.get ();
252
+ ShouldBroadcast = Request.ShouldBroadcast ;
253
+ CDB = DirCache->get (ShouldBroadcast);
254
+ } else {
255
+ // Traverse the canonical version to prevent false positives. i.e.:
256
+ // src/build/../a.cc can detect a CDB in /src/build if not canonicalized.
257
+ std::string CanonicalPath = removeDots (Request.FileName );
258
+ std::vector<llvm::StringRef> SearchDirs;
259
+ actOnAllParentDirectories (CanonicalPath, [&](PathRef Path) {
260
+ SearchDirs.push_back (Path);
261
+ return false ;
262
+ });
263
+ for (DirectoryCache *Candidate : getDirectoryCaches (SearchDirs)) {
264
+ bool CandidateShouldBroadcast = Request.ShouldBroadcast ;
265
+ if ((CDB = Candidate->get (CandidateShouldBroadcast))) {
266
+ DirCache = Candidate;
267
+ ShouldBroadcast = CandidateShouldBroadcast;
268
+ break ;
269
+ }
160
270
}
271
+ }
161
272
162
- if (!Entry || !Entry->CDB )
163
- return llvm::None;
164
-
165
- // Mark CDB as broadcasted to make sure discovery is performed once.
166
- if (Request.ShouldBroadcast && !Entry->SentBroadcast ) {
167
- Entry->SentBroadcast = true ;
168
- ShouldBroadcast = true ;
169
- }
273
+ if (!CDB)
274
+ return llvm::None;
170
275
171
- Result. CDB = Entry-> CDB . get () ;
172
- Result.PI . SourceRoot = Entry-> Path ;
173
- }
276
+ CDBLookupResult Result;
277
+ Result.CDB = std::move (CDB) ;
278
+ Result. PI . SourceRoot = DirCache-> path (). str ();
174
279
175
- // FIXME: Maybe make the following part async, since this can block retrieval
176
- // of compile commands.
280
+ // FIXME: Maybe make the following part async, since this can block
281
+ // retrieval of compile commands.
177
282
if (ShouldBroadcast)
178
283
broadcastCDB (Result);
179
284
return Result;
@@ -186,29 +291,32 @@ void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
186
291
std::vector<std::string> AllFiles = Result.CDB ->getAllFiles ();
187
292
// We assume CDB in CompileCommandsDir owns all of its entries, since we don't
188
293
// perform any search in parent paths whenever it is set.
189
- if (CompileCommandsDir ) {
190
- assert (*CompileCommandsDir == Result.PI .SourceRoot &&
294
+ if (OnlyDirCache ) {
295
+ assert (OnlyDirCache-> path () == Result.PI .SourceRoot &&
191
296
" Trying to broadcast a CDB outside of CompileCommandsDir!" );
192
297
OnCommandChanged.broadcast (std::move (AllFiles));
193
298
return ;
194
299
}
195
300
301
+ // Uniquify all parent directories of all files.
196
302
llvm::StringMap<bool > DirectoryHasCDB;
197
- {
198
- std::lock_guard<std::mutex> Lock (Mutex);
199
- // Uniquify all parent directories of all files.
200
- for (llvm::StringRef File : AllFiles) {
201
- actOnAllParentDirectories (File, [&](PathRef Path) {
202
- auto It = DirectoryHasCDB.try_emplace (Path);
203
- // Already seen this path, and all of its parents.
204
- if (!It.second )
205
- return true ;
206
-
207
- CachedCDB &Entry = getCDBInDirLocked (Path);
208
- It.first ->second = Entry.CDB != nullptr ;
209
- return pathEqual (Path, Result.PI .SourceRoot );
210
- });
211
- }
303
+ std::vector<llvm::StringRef> FileAncestors;
304
+ for (llvm::StringRef File : AllFiles) {
305
+ actOnAllParentDirectories (File, [&](PathRef Path) {
306
+ auto It = DirectoryHasCDB.try_emplace (Path);
307
+ // Already seen this path, and all of its parents.
308
+ if (!It.second )
309
+ return true ;
310
+
311
+ FileAncestors.push_back (It.first ->getKey ());
312
+ return pathEqual (Path, Result.PI .SourceRoot );
313
+ });
314
+ }
315
+ // Work out which ones have CDBs in them.
316
+ for (DirectoryCache *Dir : getDirectoryCaches (FileAncestors)) {
317
+ bool ShouldBroadcast = false ;
318
+ if (Dir->get (ShouldBroadcast))
319
+ DirectoryHasCDB.find (Dir->path ())->setValue (true );
212
320
}
213
321
214
322
std::vector<std::string> GovernedFiles;
0 commit comments