blob: 6e137950d2836bb14af53ca5fb472d603f219ad7 [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) {
78 std::lock_guard<std::mutex> Lock(QueueMu);
79 enqueueLocked(std::move(Cmd));
80}
81
82void BackgroundIndex::enqueueAll(StringRef Directory,
83 const tooling::CompilationDatabase &CDB) {
84 trace::Span Tracer("BackgroundIndexEnqueueCDB");
85 // FIXME: this function may be slow. Perhaps enqueue a task to re-read the CDB
86 // from disk and enqueue the commands asynchronously?
87 auto Cmds = CDB.getAllCompileCommands();
88 SPAN_ATTACH(Tracer, "commands", int64_t(Cmds.size()));
89 std::mt19937 Generator(std::random_device{}());
90 std::shuffle(Cmds.begin(), Cmds.end(), Generator);
91 log("Enqueueing {0} commands for indexing from {1}", Cmds.size(), Directory);
92 {
93 std::lock_guard<std::mutex> Lock(QueueMu);
94 for (auto &Cmd : Cmds)
95 enqueueLocked(std::move(Cmd));
96 }
97 QueueCV.notify_all();
98}
99
100void BackgroundIndex::enqueueLocked(tooling::CompileCommand Cmd) {
101 Queue.push_back(Bind(
102 [this](tooling::CompileCommand Cmd) {
103 std::string Filename = Cmd.Filename;
104 Cmd.CommandLine.push_back("-resource-dir=" + ResourceDir);
105 if (auto Error = index(std::move(Cmd)))
106 log("Indexing {0} failed: {1}", Filename, std::move(Error));
107 },
108 std::move(Cmd)));
109}
110
111llvm::Error BackgroundIndex::index(tooling::CompileCommand Cmd) {
112 trace::Span Tracer("BackgroundIndex");
113 SPAN_ATTACH(Tracer, "file", Cmd.Filename);
114 SmallString<128> AbsolutePath;
115 if (llvm::sys::path::is_absolute(Cmd.Filename)) {
116 AbsolutePath = Cmd.Filename;
117 } else {
118 AbsolutePath = Cmd.Directory;
119 llvm::sys::path::append(AbsolutePath, Cmd.Filename);
120 }
121
122 auto FS = FSProvider.getFileSystem();
123 auto Buf = FS->getBufferForFile(AbsolutePath);
124 if (!Buf)
125 return errorCodeToError(Buf.getError());
126 StringRef Contents = Buf->get()->getBuffer();
127 auto Hash = SHA1::hash({(const uint8_t *)Contents.data(), Contents.size()});
128
129 if (FileHash.lookup(AbsolutePath) == Hash) {
130 vlog("No need to index {0}, already up to date", AbsolutePath);
131 return Error::success();
132 }
133
134 log("Indexing {0}", Cmd.Filename, toHex(Hash));
135 ParseInputs Inputs;
136 Inputs.FS = std::move(FS);
137 Inputs.FS->setCurrentWorkingDirectory(Cmd.Directory);
138 Inputs.CompileCommand = std::move(Cmd);
139 auto CI = buildCompilerInvocation(Inputs);
140 if (!CI)
141 return createStringError(llvm::inconvertibleErrorCode(),
142 "Couldn't build compiler invocation");
143 IgnoreDiagnostics IgnoreDiags;
144 auto Clang = prepareCompilerInstance(
145 std::move(CI), /*Preamble=*/nullptr, std::move(*Buf),
146 std::make_shared<PCHContainerOperations>(), Inputs.FS, IgnoreDiags);
147 if (!Clang)
148 return createStringError(llvm::inconvertibleErrorCode(),
149 "Couldn't build compiler instance");
150
151 SymbolCollector::Options IndexOpts;
152 SymbolSlab Symbols;
153 RefSlab Refs;
154 IndexFileIn IndexData;
155 auto Action = createStaticIndexingAction(
156 IndexOpts, [&](SymbolSlab S) { Symbols = std::move(S); },
157 [&](RefSlab R) { Refs = std::move(R); });
158
159 // We're going to run clang here, and it could potentially crash.
160 // We could use CrashRecoveryContext to try to make indexing crashes nonfatal,
161 // but the leaky "recovery" is pretty scary too in a long-running process.
162 // If crashes are a real problem, maybe we should fork a child process.
163
164 const FrontendInputFile &Input = Clang->getFrontendOpts().Inputs.front();
165 if (!Action->BeginSourceFile(*Clang, Input))
166 return createStringError(llvm::inconvertibleErrorCode(),
167 "BeginSourceFile() failed");
168 if (!Action->Execute())
169 return createStringError(llvm::inconvertibleErrorCode(),
170 "Execute() failed");
171 Action->EndSourceFile();
172
173 log("Indexed {0} ({1} symbols, {2} refs)", Inputs.CompileCommand.Filename,
174 Symbols.size(), Refs.size());
175 SPAN_ATTACH(Tracer, "symbols", int(Symbols.size()));
176 SPAN_ATTACH(Tracer, "refs", int(Refs.size()));
177 // FIXME: partition the symbols by file rather than TU, to avoid duplication.
178 IndexedSymbols.update(AbsolutePath,
179 llvm::make_unique<SymbolSlab>(std::move(Symbols)),
180 llvm::make_unique<RefSlab>(std::move(Refs)));
181 FileHash[AbsolutePath] = Hash;
182
183 // FIXME: this should rebuild once-in-a-while, not after every file.
184 // At that point we should use Dex, too.
185 vlog("Rebuilding automatic index");
186 reset(IndexedSymbols.buildMemIndex());
187 return Error::success();
188}
189
190} // namespace clangd
191} // namespace clang