blob: f6de39549f4e7151ce29bfb0324825ec0dc1763b [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"
14#include "Trace.h"
15#include "index/IndexAction.h"
16#include "index/MemIndex.h"
17#include "index/Serialization.h"
18#include "llvm/Support/SHA1.h"
19#include <random>
20
21using namespace llvm;
22namespace clang {
23namespace clangd {
24
25BackgroundIndex::BackgroundIndex(Context BackgroundContext,
26 StringRef ResourceDir,
27 const FileSystemProvider &FSProvider)
28 : SwapIndex(llvm::make_unique<MemIndex>()), ResourceDir(ResourceDir),
29 FSProvider(FSProvider), BackgroundContext(std::move(BackgroundContext)),
30 Thread([this] { run(); }) {}
31
32BackgroundIndex::~BackgroundIndex() {
33 stop();
34 Thread.join();
35}
36
37void BackgroundIndex::stop() {
38 {
39 std::lock_guard<std::mutex> Lock(QueueMu);
40 ShouldStop = true;
41 }
42 QueueCV.notify_all();
43}
44
45void BackgroundIndex::run() {
46 WithContext Background(std::move(BackgroundContext));
47 while (true) {
48 llvm::Optional<Task> Task;
49 {
50 std::unique_lock<std::mutex> Lock(QueueMu);
51 QueueCV.wait(Lock, [&] { return ShouldStop || !Queue.empty(); });
52 if (ShouldStop) {
53 Queue.clear();
54 QueueCV.notify_all();
55 return;
56 }
57 ++NumActiveTasks;
58 Task = std::move(Queue.front());
59 Queue.pop_front();
60 }
61 (*Task)();
62 {
63 std::unique_lock<std::mutex> Lock(QueueMu);
64 assert(NumActiveTasks > 0 && "before decrementing");
65 --NumActiveTasks;
66 }
67 QueueCV.notify_all();
68 }
69}
70
71void BackgroundIndex::blockUntilIdleForTest() {
72 std::unique_lock<std::mutex> Lock(QueueMu);
73 QueueCV.wait(Lock, [&] { return Queue.empty() && NumActiveTasks == 0; });
74}
75
76void BackgroundIndex::enqueue(StringRef Directory,
77 tooling::CompileCommand Cmd) {
Sam McCallbca624a2018-10-16 09:05:13 +000078 {
79 std::lock_guard<std::mutex> Lock(QueueMu);
80 enqueueLocked(std::move(Cmd));
81 }
82 QueueCV.notify_all();
Sam McCall8dc9dbb2018-10-15 13:34:10 +000083}
84
85void BackgroundIndex::enqueueAll(StringRef Directory,
86 const tooling::CompilationDatabase &CDB) {
87 trace::Span Tracer("BackgroundIndexEnqueueCDB");
88 // FIXME: this function may be slow. Perhaps enqueue a task to re-read the CDB
89 // from disk and enqueue the commands asynchronously?
90 auto Cmds = CDB.getAllCompileCommands();
91 SPAN_ATTACH(Tracer, "commands", int64_t(Cmds.size()));
92 std::mt19937 Generator(std::random_device{}());
93 std::shuffle(Cmds.begin(), Cmds.end(), Generator);
94 log("Enqueueing {0} commands for indexing from {1}", Cmds.size(), Directory);
95 {
96 std::lock_guard<std::mutex> Lock(QueueMu);
97 for (auto &Cmd : Cmds)
98 enqueueLocked(std::move(Cmd));
99 }
100 QueueCV.notify_all();
101}
102
103void BackgroundIndex::enqueueLocked(tooling::CompileCommand Cmd) {
104 Queue.push_back(Bind(
105 [this](tooling::CompileCommand Cmd) {
106 std::string Filename = Cmd.Filename;
107 Cmd.CommandLine.push_back("-resource-dir=" + ResourceDir);
108 if (auto Error = index(std::move(Cmd)))
109 log("Indexing {0} failed: {1}", Filename, std::move(Error));
110 },
111 std::move(Cmd)));
112}
113
114llvm::Error BackgroundIndex::index(tooling::CompileCommand Cmd) {
115 trace::Span Tracer("BackgroundIndex");
116 SPAN_ATTACH(Tracer, "file", Cmd.Filename);
117 SmallString<128> AbsolutePath;
118 if (llvm::sys::path::is_absolute(Cmd.Filename)) {
119 AbsolutePath = Cmd.Filename;
120 } else {
121 AbsolutePath = Cmd.Directory;
122 llvm::sys::path::append(AbsolutePath, Cmd.Filename);
123 }
124
125 auto FS = FSProvider.getFileSystem();
126 auto Buf = FS->getBufferForFile(AbsolutePath);
127 if (!Buf)
128 return errorCodeToError(Buf.getError());
129 StringRef Contents = Buf->get()->getBuffer();
130 auto Hash = SHA1::hash({(const uint8_t *)Contents.data(), Contents.size()});
131
132 if (FileHash.lookup(AbsolutePath) == Hash) {
133 vlog("No need to index {0}, already up to date", AbsolutePath);
134 return Error::success();
135 }
136
137 log("Indexing {0}", Cmd.Filename, toHex(Hash));
138 ParseInputs Inputs;
139 Inputs.FS = std::move(FS);
140 Inputs.FS->setCurrentWorkingDirectory(Cmd.Directory);
141 Inputs.CompileCommand = std::move(Cmd);
142 auto CI = buildCompilerInvocation(Inputs);
143 if (!CI)
144 return createStringError(llvm::inconvertibleErrorCode(),
145 "Couldn't build compiler invocation");
146 IgnoreDiagnostics IgnoreDiags;
147 auto Clang = prepareCompilerInstance(
148 std::move(CI), /*Preamble=*/nullptr, std::move(*Buf),
149 std::make_shared<PCHContainerOperations>(), Inputs.FS, IgnoreDiags);
150 if (!Clang)
151 return createStringError(llvm::inconvertibleErrorCode(),
152 "Couldn't build compiler instance");
153
154 SymbolCollector::Options IndexOpts;
155 SymbolSlab Symbols;
156 RefSlab Refs;
157 IndexFileIn IndexData;
158 auto Action = createStaticIndexingAction(
159 IndexOpts, [&](SymbolSlab S) { Symbols = std::move(S); },
160 [&](RefSlab R) { Refs = std::move(R); });
161
162 // We're going to run clang here, and it could potentially crash.
163 // We could use CrashRecoveryContext to try to make indexing crashes nonfatal,
164 // but the leaky "recovery" is pretty scary too in a long-running process.
165 // If crashes are a real problem, maybe we should fork a child process.
166
167 const FrontendInputFile &Input = Clang->getFrontendOpts().Inputs.front();
168 if (!Action->BeginSourceFile(*Clang, Input))
169 return createStringError(llvm::inconvertibleErrorCode(),
170 "BeginSourceFile() failed");
171 if (!Action->Execute())
172 return createStringError(llvm::inconvertibleErrorCode(),
173 "Execute() failed");
174 Action->EndSourceFile();
175
176 log("Indexed {0} ({1} symbols, {2} refs)", Inputs.CompileCommand.Filename,
177 Symbols.size(), Refs.size());
178 SPAN_ATTACH(Tracer, "symbols", int(Symbols.size()));
179 SPAN_ATTACH(Tracer, "refs", int(Refs.size()));
180 // FIXME: partition the symbols by file rather than TU, to avoid duplication.
181 IndexedSymbols.update(AbsolutePath,
182 llvm::make_unique<SymbolSlab>(std::move(Symbols)),
183 llvm::make_unique<RefSlab>(std::move(Refs)));
184 FileHash[AbsolutePath] = Hash;
185
186 // FIXME: this should rebuild once-in-a-while, not after every file.
187 // At that point we should use Dex, too.
188 vlog("Rebuilding automatic index");
Sam McCall96f24892018-10-16 08:53:52 +0000189 reset(IndexedSymbols.buildIndex(IndexType::Light));
Sam McCall8dc9dbb2018-10-15 13:34:10 +0000190 return Error::success();
191}
192
193} // namespace clangd
194} // namespace clang