blob: 6c104cbe2419c20a1f763d79bee1abfc22ce108f [file] [log] [blame]
Sam McCall8dc9dbb2018-10-15 13:34:10 +00001//===-- Background.cpp - Build an index in a background thread ------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "index/Background.h"
11#include "ClangdUnit.h"
12#include "Compiler.h"
13#include "Logger.h"
Kadir Cetinkayad08eab42018-11-27 16:08:53 +000014#include "SourceCode.h"
Kadir Cetinkaya6675be82018-10-30 12:13:27 +000015#include "Threading.h"
Sam McCall8dc9dbb2018-10-15 13:34:10 +000016#include "Trace.h"
Eric Liuad588af2018-11-06 10:55:21 +000017#include "URI.h"
Sam McCall8dc9dbb2018-10-15 13:34:10 +000018#include "index/IndexAction.h"
19#include "index/MemIndex.h"
20#include "index/Serialization.h"
Eric Liuad588af2018-11-06 10:55:21 +000021#include "index/SymbolCollector.h"
22#include "clang/Basic/SourceLocation.h"
23#include "clang/Basic/SourceManager.h"
24#include "llvm/ADT/STLExtras.h"
25#include "llvm/ADT/StringMap.h"
26#include "llvm/ADT/StringRef.h"
Sam McCall8dc9dbb2018-10-15 13:34:10 +000027#include "llvm/Support/SHA1.h"
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +000028
29#include <memory>
Sam McCall7d0e4842018-11-26 13:35:02 +000030#include <numeric>
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +000031#include <queue>
Sam McCall8dc9dbb2018-10-15 13:34:10 +000032#include <random>
Eric Liuad588af2018-11-06 10:55:21 +000033#include <string>
Sam McCall8dc9dbb2018-10-15 13:34:10 +000034
35using namespace llvm;
36namespace clang {
37namespace clangd {
Kadir Cetinkaya219c0fa2018-12-04 11:31:57 +000038namespace {
39// Resolves URI to file paths with cache.
40class URIToFileCache {
41public:
42 URIToFileCache(llvm::StringRef HintPath) : HintPath(HintPath) {}
43
44 llvm::StringRef resolve(llvm::StringRef FileURI) {
45 auto I = URIToPathCache.try_emplace(FileURI);
46 if (I.second) {
47 auto U = URI::parse(FileURI);
48 if (!U) {
49 elog("Failed to parse URI {0}: {1}", FileURI, U.takeError());
50 assert(false && "Failed to parse URI");
51 return "";
52 }
53 auto Path = URI::resolve(*U, HintPath);
54 if (!Path) {
55 elog("Failed to resolve URI {0}: {1}", FileURI, Path.takeError());
56 assert(false && "Failed to resolve URI");
57 return "";
58 }
59 I.first->second = *Path;
60 }
61 return I.first->second;
62 }
63
64private:
65 std::string HintPath;
66 llvm::StringMap<std::string> URIToPathCache;
67};
68
69// We keep only the node "U" and its edges. Any node other than "U" will be
70// empty in the resultant graph.
71IncludeGraph getSubGraph(const URI &U, const IncludeGraph &FullGraph) {
72 IncludeGraph IG;
73
74 std::string FileURI = U.toString();
75 auto Entry = IG.try_emplace(FileURI).first;
76 auto &Node = Entry->getValue();
77 Node = FullGraph.lookup(Entry->getKey());
78 Node.URI = Entry->getKey();
79
80 // URIs inside nodes must point into the keys of the same IncludeGraph.
81 for (auto &Include : Node.DirectIncludes) {
82 auto I = IG.try_emplace(Include).first;
83 I->getValue().URI = I->getKey();
84 Include = I->getKey();
85 }
86
87 return IG;
88}
Haojian Wu9d0d9f82018-12-13 13:07:29 +000089
90// Creates a filter to not collect index results from files with unchanged
91// digests.
92// \p FileDigests contains file digests for the current indexed files, and all
93// changed files will be added to \p FilesToUpdate.
94decltype(SymbolCollector::Options::FileFilter)
95createFileFilter(const llvm::StringMap<FileDigest> &FileDigests,
96 llvm::StringMap<FileDigest> &FilesToUpdate) {
97 return [&FileDigests, &FilesToUpdate](const SourceManager &SM, FileID FID) {
98 StringRef Path;
99 if (const auto *F = SM.getFileEntryForID(FID))
100 Path = F->getName();
101 if (Path.empty())
102 return false; // Skip invalid files.
103 SmallString<128> AbsPath(Path);
104 if (std::error_code EC =
105 SM.getFileManager().getVirtualFileSystem()->makeAbsolute(AbsPath)) {
106 elog("Warning: could not make absolute file: {0}", EC.message());
107 return false; // Skip files without absolute path.
108 }
109 sys::path::remove_dots(AbsPath, /*remove_dot_dot=*/true);
110 auto Digest = digestFile(SM, FID);
111 if (!Digest)
112 return false;
113 auto D = FileDigests.find(AbsPath);
114 if (D != FileDigests.end() && D->second == Digest)
115 return false; // Skip files that haven't changed.
116
117 FilesToUpdate[AbsPath] = *Digest;
118 return true;
119 };
120}
121
Kadir Cetinkaya219c0fa2018-12-04 11:31:57 +0000122} // namespace
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000123
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +0000124BackgroundIndex::BackgroundIndex(
125 Context BackgroundContext, StringRef ResourceDir,
Sam McCall6e2d2a32018-11-26 09:51:50 +0000126 const FileSystemProvider &FSProvider, const GlobalCompilationDatabase &CDB,
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +0000127 BackgroundIndexStorage::Factory IndexStorageFactory, size_t ThreadPoolSize)
Sam McCallc008af62018-10-20 15:30:37 +0000128 : SwapIndex(make_unique<MemIndex>()), ResourceDir(ResourceDir),
Sam McCall6e2d2a32018-11-26 09:51:50 +0000129 FSProvider(FSProvider), CDB(CDB),
130 BackgroundContext(std::move(BackgroundContext)),
131 IndexStorageFactory(std::move(IndexStorageFactory)),
132 CommandsChanged(
133 CDB.watch([&](const std::vector<std::string> &ChangedFiles) {
134 enqueue(ChangedFiles);
135 })) {
Kadir Cetinkaya6675be82018-10-30 12:13:27 +0000136 assert(ThreadPoolSize > 0 && "Thread pool size can't be zero.");
Haojian Wu1bf52c52018-11-16 09:41:14 +0000137 assert(this->IndexStorageFactory && "Storage factory can not be null!");
Kadir Cetinkaya6675be82018-10-30 12:13:27 +0000138 while (ThreadPoolSize--) {
139 ThreadPool.emplace_back([this] { run(); });
140 // Set priority to low, since background indexing is a long running task we
141 // do not want to eat up cpu when there are any other high priority threads.
142 // FIXME: In the future we might want a more general way of handling this to
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +0000143 // support tasks with various priorities.
Kadir Cetinkaya6675be82018-10-30 12:13:27 +0000144 setThreadPriority(ThreadPool.back(), ThreadPriority::Low);
145 }
146}
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000147
148BackgroundIndex::~BackgroundIndex() {
149 stop();
Kadir Cetinkaya6675be82018-10-30 12:13:27 +0000150 for (auto &Thread : ThreadPool)
151 Thread.join();
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000152}
153
154void BackgroundIndex::stop() {
155 {
156 std::lock_guard<std::mutex> Lock(QueueMu);
157 ShouldStop = true;
158 }
159 QueueCV.notify_all();
160}
161
162void BackgroundIndex::run() {
Kadir Cetinkaya6675be82018-10-30 12:13:27 +0000163 WithContext Background(BackgroundContext.clone());
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000164 while (true) {
Sam McCallc008af62018-10-20 15:30:37 +0000165 Optional<Task> Task;
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000166 {
167 std::unique_lock<std::mutex> Lock(QueueMu);
168 QueueCV.wait(Lock, [&] { return ShouldStop || !Queue.empty(); });
169 if (ShouldStop) {
170 Queue.clear();
171 QueueCV.notify_all();
172 return;
173 }
174 ++NumActiveTasks;
175 Task = std::move(Queue.front());
176 Queue.pop_front();
177 }
178 (*Task)();
179 {
180 std::unique_lock<std::mutex> Lock(QueueMu);
181 assert(NumActiveTasks > 0 && "before decrementing");
182 --NumActiveTasks;
183 }
184 QueueCV.notify_all();
185 }
186}
187
Sam McCall422c8282018-11-26 16:00:11 +0000188bool BackgroundIndex::blockUntilIdleForTest(
189 llvm::Optional<double> TimeoutSeconds) {
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000190 std::unique_lock<std::mutex> Lock(QueueMu);
Sam McCall422c8282018-11-26 16:00:11 +0000191 return wait(Lock, QueueCV, timeoutSeconds(TimeoutSeconds),
192 [&] { return Queue.empty() && NumActiveTasks == 0; });
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000193}
194
Sam McCall6e2d2a32018-11-26 09:51:50 +0000195void BackgroundIndex::enqueue(const std::vector<std::string> &ChangedFiles) {
196 enqueueTask([this, ChangedFiles] {
197 trace::Span Tracer("BackgroundIndexEnqueue");
198 // We're doing this asynchronously, because we'll read shards here too.
199 // FIXME: read shards here too.
200
201 log("Enqueueing {0} commands for indexing", ChangedFiles.size());
202 SPAN_ATTACH(Tracer, "files", int64_t(ChangedFiles.size()));
203
204 // We shuffle the files because processing them in a random order should
205 // quickly give us good coverage of headers in the project.
206 std::vector<unsigned> Permutation(ChangedFiles.size());
207 std::iota(Permutation.begin(), Permutation.end(), 0);
208 std::mt19937 Generator(std::random_device{}());
209 std::shuffle(Permutation.begin(), Permutation.end(), Generator);
210
211 for (const unsigned I : Permutation)
212 enqueue(ChangedFiles[I]);
213 });
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000214}
215
Sam McCall6e2d2a32018-11-26 09:51:50 +0000216void BackgroundIndex::enqueue(const std::string &File) {
217 ProjectInfo Project;
218 if (auto Cmd = CDB.getCompileCommand(File, &Project)) {
219 auto *Storage = IndexStorageFactory(Project.SourceRoot);
220 enqueueTask(Bind(
221 [this, File, Storage](tooling::CompileCommand Cmd) {
222 Cmd.CommandLine.push_back("-resource-dir=" + ResourceDir);
223 if (auto Error = index(std::move(Cmd), Storage))
224 log("Indexing {0} failed: {1}", File, std::move(Error));
225 },
226 std::move(*Cmd)));
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000227 }
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000228}
229
Sam McCall6e2d2a32018-11-26 09:51:50 +0000230void BackgroundIndex::enqueueTask(Task T) {
231 {
232 std::lock_guard<std::mutex> Lock(QueueMu);
233 Queue.push_back(std::move(T));
234 }
235 QueueCV.notify_all();
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000236}
237
Eric Liuad588af2018-11-06 10:55:21 +0000238/// Given index results from a TU, only update files in \p FilesToUpdate.
Kadir Cetinkayad08eab42018-11-27 16:08:53 +0000239void BackgroundIndex::update(StringRef MainFile, IndexFileIn Index,
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +0000240 const StringMap<FileDigest> &FilesToUpdate,
241 BackgroundIndexStorage *IndexStorage) {
Eric Liuad588af2018-11-06 10:55:21 +0000242 // Partition symbols/references into files.
243 struct File {
244 DenseSet<const Symbol *> Symbols;
245 DenseSet<const Ref *> Refs;
246 };
247 StringMap<File> Files;
248 URIToFileCache URICache(MainFile);
Kadir Cetinkayad08eab42018-11-27 16:08:53 +0000249 for (const auto &Sym : *Index.Symbols) {
Eric Liuad588af2018-11-06 10:55:21 +0000250 if (Sym.CanonicalDeclaration) {
251 auto DeclPath = URICache.resolve(Sym.CanonicalDeclaration.FileURI);
252 if (FilesToUpdate.count(DeclPath) != 0)
253 Files[DeclPath].Symbols.insert(&Sym);
254 }
255 // For symbols with different declaration and definition locations, we store
256 // the full symbol in both the header file and the implementation file, so
257 // that merging can tell the preferred symbols (from canonical headers) from
258 // other symbols (e.g. forward declarations).
259 if (Sym.Definition &&
260 Sym.Definition.FileURI != Sym.CanonicalDeclaration.FileURI) {
261 auto DefPath = URICache.resolve(Sym.Definition.FileURI);
262 if (FilesToUpdate.count(DefPath) != 0)
263 Files[DefPath].Symbols.insert(&Sym);
264 }
265 }
266 DenseMap<const Ref *, SymbolID> RefToIDs;
Kadir Cetinkayad08eab42018-11-27 16:08:53 +0000267 for (const auto &SymRefs : *Index.Refs) {
Eric Liuad588af2018-11-06 10:55:21 +0000268 for (const auto &R : SymRefs.second) {
269 auto Path = URICache.resolve(R.Location.FileURI);
270 if (FilesToUpdate.count(Path) != 0) {
271 auto &F = Files[Path];
272 RefToIDs[&R] = SymRefs.first;
273 F.Refs.insert(&R);
274 }
275 }
276 }
277
278 // Build and store new slabs for each updated file.
279 for (const auto &F : Files) {
280 StringRef Path = F.first();
281 vlog("Update symbols in {0}", Path);
282 SymbolSlab::Builder Syms;
283 RefSlab::Builder Refs;
284 for (const auto *S : F.second.Symbols)
285 Syms.insert(*S);
286 for (const auto *R : F.second.Refs)
287 Refs.insert(RefToIDs[R], *R);
288
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +0000289 auto SS = llvm::make_unique<SymbolSlab>(std::move(Syms).build());
290 auto RS = llvm::make_unique<RefSlab>(std::move(Refs).build());
Kadir Cetinkaya219c0fa2018-12-04 11:31:57 +0000291 auto IG = llvm::make_unique<IncludeGraph>(
292 getSubGraph(URI::create(Path), Index.Sources.getValue()));
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +0000293
294 auto Hash = FilesToUpdate.lookup(Path);
295 // We need to store shards before updating the index, since the latter
296 // consumes slabs.
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +0000297 if (IndexStorage) {
298 IndexFileOut Shard;
299 Shard.Symbols = SS.get();
300 Shard.Refs = RS.get();
Kadir Cetinkaya219c0fa2018-12-04 11:31:57 +0000301 Shard.Sources = IG.get();
Kadir Cetinkayad08eab42018-11-27 16:08:53 +0000302
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +0000303 if (auto Error = IndexStorage->storeShard(Path, Shard))
304 elog("Failed to write background-index shard for file {0}: {1}", Path,
305 std::move(Error));
306 }
307
Eric Liuad588af2018-11-06 10:55:21 +0000308 std::lock_guard<std::mutex> Lock(DigestsMu);
309 // This can override a newer version that is added in another thread,
310 // if this thread sees the older version but finishes later. This should be
311 // rare in practice.
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +0000312 IndexedFileDigests[Path] = Hash;
313 IndexedSymbols.update(Path, std::move(SS), std::move(RS));
Eric Liuad588af2018-11-06 10:55:21 +0000314 }
315}
316
Kadir Cetinkaya06553bf2018-11-16 09:03:56 +0000317Error BackgroundIndex::index(tooling::CompileCommand Cmd,
318 BackgroundIndexStorage *IndexStorage) {
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000319 trace::Span Tracer("BackgroundIndex");
320 SPAN_ATTACH(Tracer, "file", Cmd.Filename);
Kadir Cetinkayaed18e782018-11-15 10:34:39 +0000321 SmallString<128> AbsolutePath;
322 if (sys::path::is_absolute(Cmd.Filename)) {
323 AbsolutePath = Cmd.Filename;
324 } else {
325 AbsolutePath = Cmd.Directory;
326 sys::path::append(AbsolutePath, Cmd.Filename);
327 }
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000328
329 auto FS = FSProvider.getFileSystem();
330 auto Buf = FS->getBufferForFile(AbsolutePath);
331 if (!Buf)
332 return errorCodeToError(Buf.getError());
Eric Liuad588af2018-11-06 10:55:21 +0000333 auto Hash = digest(Buf->get()->getBuffer());
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000334
Eric Liuad588af2018-11-06 10:55:21 +0000335 // Take a snapshot of the digests to avoid locking for each file in the TU.
336 llvm::StringMap<FileDigest> DigestsSnapshot;
337 {
338 std::lock_guard<std::mutex> Lock(DigestsMu);
339 if (IndexedFileDigests.lookup(AbsolutePath) == Hash) {
340 vlog("No need to index {0}, already up to date", AbsolutePath);
341 return Error::success();
342 }
343
344 DigestsSnapshot = IndexedFileDigests;
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000345 }
346
347 log("Indexing {0}", Cmd.Filename, toHex(Hash));
348 ParseInputs Inputs;
349 Inputs.FS = std::move(FS);
350 Inputs.FS->setCurrentWorkingDirectory(Cmd.Directory);
351 Inputs.CompileCommand = std::move(Cmd);
352 auto CI = buildCompilerInvocation(Inputs);
353 if (!CI)
Sam McCallc008af62018-10-20 15:30:37 +0000354 return createStringError(inconvertibleErrorCode(),
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000355 "Couldn't build compiler invocation");
356 IgnoreDiagnostics IgnoreDiags;
357 auto Clang = prepareCompilerInstance(
358 std::move(CI), /*Preamble=*/nullptr, std::move(*Buf),
359 std::make_shared<PCHContainerOperations>(), Inputs.FS, IgnoreDiags);
360 if (!Clang)
Sam McCallc008af62018-10-20 15:30:37 +0000361 return createStringError(inconvertibleErrorCode(),
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000362 "Couldn't build compiler instance");
363
364 SymbolCollector::Options IndexOpts;
Eric Liuad588af2018-11-06 10:55:21 +0000365 StringMap<FileDigest> FilesToUpdate;
366 IndexOpts.FileFilter = createFileFilter(DigestsSnapshot, FilesToUpdate);
Kadir Cetinkaya219c0fa2018-12-04 11:31:57 +0000367 IndexFileIn Index;
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000368 auto Action = createStaticIndexingAction(
Kadir Cetinkaya219c0fa2018-12-04 11:31:57 +0000369 IndexOpts, [&](SymbolSlab S) { Index.Symbols = std::move(S); },
370 [&](RefSlab R) { Index.Refs = std::move(R); },
371 [&](IncludeGraph IG) { Index.Sources = std::move(IG); });
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000372
373 // We're going to run clang here, and it could potentially crash.
374 // We could use CrashRecoveryContext to try to make indexing crashes nonfatal,
375 // but the leaky "recovery" is pretty scary too in a long-running process.
376 // If crashes are a real problem, maybe we should fork a child process.
377
378 const FrontendInputFile &Input = Clang->getFrontendOpts().Inputs.front();
379 if (!Action->BeginSourceFile(*Clang, Input))
Sam McCallc008af62018-10-20 15:30:37 +0000380 return createStringError(inconvertibleErrorCode(),
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000381 "BeginSourceFile() failed");
382 if (!Action->Execute())
Sam McCallc008af62018-10-20 15:30:37 +0000383 return createStringError(inconvertibleErrorCode(), "Execute() failed");
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000384 Action->EndSourceFile();
385
Kadir Cetinkaya219c0fa2018-12-04 11:31:57 +0000386 log("Indexed {0} ({1} symbols, {2} refs, {3} files)",
387 Inputs.CompileCommand.Filename, Index.Symbols->size(),
388 Index.Refs->numRefs(), Index.Sources->size());
389 SPAN_ATTACH(Tracer, "symbols", int(Index.Symbols->size()));
390 SPAN_ATTACH(Tracer, "refs", int(Index.Refs->numRefs()));
391 SPAN_ATTACH(Tracer, "sources", int(Index.Sources->size()));
Kadir Cetinkayad08eab42018-11-27 16:08:53 +0000392
393 update(AbsolutePath, std::move(Index), FilesToUpdate, IndexStorage);
Eric Liuad588af2018-11-06 10:55:21 +0000394 {
395 // Make sure hash for the main file is always updated even if there is no
396 // index data in it.
397 std::lock_guard<std::mutex> Lock(DigestsMu);
398 IndexedFileDigests[AbsolutePath] = Hash;
399 }
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000400
401 // FIXME: this should rebuild once-in-a-while, not after every file.
402 // At that point we should use Dex, too.
403 vlog("Rebuilding automatic index");
Eric Liuc0ac4bb2018-11-22 15:02:05 +0000404 reset(IndexedSymbols.buildIndex(IndexType::Light, DuplicateHandling::Merge));
Eric Liuad588af2018-11-06 10:55:21 +0000405
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000406 return Error::success();
407}
408
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000409} // namespace clangd
410} // namespace clang