1 //===- TableGenServer.cpp - TableGen Language Server ----------------------===//
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
9 #include "TableGenServer.h"
10
11 #include "../lsp-server-support/CompilationDatabase.h"
12 #include "../lsp-server-support/Logging.h"
13 #include "../lsp-server-support/Protocol.h"
14 #include "../lsp-server-support/SourceMgrUtils.h"
15 #include "mlir/Support/LogicalResult.h"
16 #include "llvm/ADT/IntervalMap.h"
17 #include "llvm/ADT/PointerUnion.h"
18 #include "llvm/ADT/StringMap.h"
19 #include "llvm/ADT/StringSet.h"
20 #include "llvm/ADT/TypeSwitch.h"
21 #include "llvm/Support/FileSystem.h"
22 #include "llvm/Support/Path.h"
23 #include "llvm/TableGen/Parser.h"
24 #include "llvm/TableGen/Record.h"
25
26 using namespace mlir;
27
28 /// Returns a language server uri for the given source location. `mainFileURI`
29 /// corresponds to the uri for the main file of the source manager.
getURIFromLoc(const llvm::SourceMgr & mgr,SMLoc loc,const lsp::URIForFile & mainFileURI)30 static lsp::URIForFile getURIFromLoc(const llvm::SourceMgr &mgr, SMLoc loc,
31 const lsp::URIForFile &mainFileURI) {
32 int bufferId = mgr.FindBufferContainingLoc(loc);
33 if (bufferId == 0 || bufferId == static_cast<int>(mgr.getMainFileID()))
34 return mainFileURI;
35 llvm::Expected<lsp::URIForFile> fileForLoc = lsp::URIForFile::fromFile(
36 mgr.getBufferInfo(bufferId).Buffer->getBufferIdentifier());
37 if (fileForLoc)
38 return *fileForLoc;
39 lsp::Logger::error("Failed to create URI for include file: {0}",
40 llvm::toString(fileForLoc.takeError()));
41 return mainFileURI;
42 }
43
44 /// Returns a language server location from the given source range.
getLocationFromLoc(llvm::SourceMgr & mgr,SMRange loc,const lsp::URIForFile & uri)45 static lsp::Location getLocationFromLoc(llvm::SourceMgr &mgr, SMRange loc,
46 const lsp::URIForFile &uri) {
47 return lsp::Location(getURIFromLoc(mgr, loc.Start, uri),
48 lsp::Range(mgr, loc));
49 }
getLocationFromLoc(llvm::SourceMgr & mgr,SMLoc loc,const lsp::URIForFile & uri)50 static lsp::Location getLocationFromLoc(llvm::SourceMgr &mgr, SMLoc loc,
51 const lsp::URIForFile &uri) {
52 return getLocationFromLoc(mgr, lsp::convertTokenLocToRange(loc), uri);
53 }
54
55 /// Convert the given TableGen diagnostic to the LSP form.
56 static Optional<lsp::Diagnostic>
getLspDiagnoticFromDiag(const llvm::SMDiagnostic & diag,const lsp::URIForFile & uri)57 getLspDiagnoticFromDiag(const llvm::SMDiagnostic &diag,
58 const lsp::URIForFile &uri) {
59 auto *sourceMgr = const_cast<llvm::SourceMgr *>(diag.getSourceMgr());
60 if (!sourceMgr || !diag.getLoc().isValid())
61 return llvm::None;
62
63 lsp::Diagnostic lspDiag;
64 lspDiag.source = "tablegen";
65 lspDiag.category = "Parse Error";
66
67 // Try to grab a file location for this diagnostic.
68 lsp::Location loc = getLocationFromLoc(*sourceMgr, diag.getLoc(), uri);
69 lspDiag.range = loc.range;
70
71 // Skip diagnostics that weren't emitted within the main file.
72 if (loc.uri != uri)
73 return llvm::None;
74
75 // Convert the severity for the diagnostic.
76 switch (diag.getKind()) {
77 case llvm::SourceMgr::DK_Warning:
78 lspDiag.severity = lsp::DiagnosticSeverity::Warning;
79 break;
80 case llvm::SourceMgr::DK_Error:
81 lspDiag.severity = lsp::DiagnosticSeverity::Error;
82 break;
83 case llvm::SourceMgr::DK_Note:
84 // Notes are emitted separately from the main diagnostic, so we just treat
85 // them as remarks given that we can't determine the diagnostic to relate
86 // them to.
87 case llvm::SourceMgr::DK_Remark:
88 lspDiag.severity = lsp::DiagnosticSeverity::Information;
89 break;
90 }
91 lspDiag.message = diag.getMessage().str();
92
93 return lspDiag;
94 }
95
96 //===----------------------------------------------------------------------===//
97 // TableGenIndex
98 //===----------------------------------------------------------------------===//
99
100 namespace {
101 /// This class represents a single symbol definition within a TableGen index. It
102 /// contains the definition of the symbol, the location of the symbol, and any
103 /// recorded references.
104 struct TableGenIndexSymbol {
TableGenIndexSymbol__anond136bf7d0111::TableGenIndexSymbol105 TableGenIndexSymbol(const llvm::Record *record)
106 : definition(record),
107 defLoc(lsp::convertTokenLocToRange(record->getLoc().front())) {}
TableGenIndexSymbol__anond136bf7d0111::TableGenIndexSymbol108 TableGenIndexSymbol(const llvm::RecordVal *value)
109 : definition(value),
110 defLoc(lsp::convertTokenLocToRange(value->getLoc())) {}
111
112 /// The main definition of the symbol.
113 PointerUnion<const llvm::Record *, const llvm::RecordVal *> definition;
114
115 /// The source location of the definition.
116 SMRange defLoc;
117
118 /// The source location of the references of the definition.
119 SmallVector<SMRange> references;
120 };
121
122 /// This class provides an index for definitions/uses within a TableGen
123 /// document. It provides efficient lookup of a definition given an input source
124 /// range.
125 class TableGenIndex {
126 public:
TableGenIndex()127 TableGenIndex() : intervalMap(allocator) {}
128
129 /// Initialize the index with the given RecordKeeper.
130 void initialize(const llvm::RecordKeeper &records);
131
132 /// Lookup a symbol for the given location. Returns nullptr if no symbol could
133 /// be found. If provided, `overlappedRange` is set to the range that the
134 /// provided `loc` overlapped with.
135 const TableGenIndexSymbol *lookup(SMLoc loc,
136 SMRange *overlappedRange = nullptr) const;
137
138 private:
139 /// The type of interval map used to store source references. SMRange is
140 /// half-open, so we also need to use a half-open interval map.
141 using MapT = llvm::IntervalMap<
142 const char *, const TableGenIndexSymbol *,
143 llvm::IntervalMapImpl::NodeSizer<const char *,
144 const TableGenIndexSymbol *>::LeafSize,
145 llvm::IntervalMapHalfOpenInfo<const char *>>;
146
147 /// An allocator for the interval map.
148 MapT::Allocator allocator;
149
150 /// An interval map containing a corresponding definition mapped to a source
151 /// interval.
152 MapT intervalMap;
153
154 /// A mapping between definitions and their corresponding symbol.
155 DenseMap<const void *, std::unique_ptr<TableGenIndexSymbol>> defToSymbol;
156 };
157 } // namespace
158
initialize(const llvm::RecordKeeper & records)159 void TableGenIndex::initialize(const llvm::RecordKeeper &records) {
160 auto getOrInsertDef = [&](const auto *def) -> TableGenIndexSymbol * {
161 auto it = defToSymbol.try_emplace(def, nullptr);
162 if (it.second)
163 it.first->second = std::make_unique<TableGenIndexSymbol>(def);
164 return &*it.first->second;
165 };
166 auto insertRef = [&](TableGenIndexSymbol *sym, SMRange refLoc,
167 bool isDef = false) {
168 const char *startLoc = refLoc.Start.getPointer();
169 const char *endLoc = refLoc.End.getPointer();
170
171 // If the location we got was empty, try to lex a token from the start
172 // location.
173 if (startLoc == endLoc) {
174 refLoc = lsp::convertTokenLocToRange(SMLoc::getFromPointer(startLoc));
175 startLoc = refLoc.Start.getPointer();
176 endLoc = refLoc.End.getPointer();
177
178 // If the location is still empty, bail on trying to use this reference
179 // location.
180 if (startLoc == endLoc)
181 return;
182 }
183
184 // Check to see if a symbol is already attached to this location.
185 // IntervalMap doesn't allow overlapping inserts, and we don't really
186 // want multiple symbols attached to a source location anyways. This
187 // shouldn't really happen in practice, but we should handle it gracefully.
188 if (!intervalMap.overlaps(startLoc, endLoc))
189 intervalMap.insert(startLoc, endLoc, sym);
190
191 if (!isDef)
192 sym->references.push_back(refLoc);
193 };
194 auto classes =
195 llvm::make_pointee_range(llvm::make_second_range(records.getClasses()));
196 auto defs =
197 llvm::make_pointee_range(llvm::make_second_range(records.getDefs()));
198 for (const llvm::Record &def : llvm::concat<llvm::Record>(classes, defs)) {
199 auto *sym = getOrInsertDef(&def);
200 insertRef(sym, sym->defLoc, /*isDef=*/true);
201
202 // Add references to the definition.
203 for (SMLoc loc : def.getLoc().drop_front())
204 insertRef(sym, lsp::convertTokenLocToRange(loc));
205
206 // Add references to any super classes.
207 for (auto &it : def.getSuperClasses())
208 insertRef(getOrInsertDef(it.first),
209 lsp::convertTokenLocToRange(it.second.Start));
210
211 // Add definitions for any values.
212 for (const llvm::RecordVal &value : def.getValues()) {
213 auto *sym = getOrInsertDef(&value);
214 insertRef(sym, sym->defLoc, /*isDef=*/true);
215 }
216 }
217 }
218
219 const TableGenIndexSymbol *
lookup(SMLoc loc,SMRange * overlappedRange) const220 TableGenIndex::lookup(SMLoc loc, SMRange *overlappedRange) const {
221 auto it = intervalMap.find(loc.getPointer());
222 if (!it.valid() || loc.getPointer() < it.start())
223 return nullptr;
224
225 if (overlappedRange) {
226 *overlappedRange = SMRange(SMLoc::getFromPointer(it.start()),
227 SMLoc::getFromPointer(it.stop()));
228 }
229 return it.value();
230 }
231
232 //===----------------------------------------------------------------------===//
233 // TableGenTextFile
234 //===----------------------------------------------------------------------===//
235
236 namespace {
237 /// This class represents a text file containing one or more TableGen documents.
238 class TableGenTextFile {
239 public:
240 TableGenTextFile(const lsp::URIForFile &uri, StringRef fileContents,
241 int64_t version,
242 const std::vector<std::string> &extraIncludeDirs,
243 std::vector<lsp::Diagnostic> &diagnostics);
244
245 /// Return the current version of this text file.
getVersion() const246 int64_t getVersion() const { return version; }
247
248 /// Update the file to the new version using the provided set of content
249 /// changes. Returns failure if the update was unsuccessful.
250 LogicalResult update(const lsp::URIForFile &uri, int64_t newVersion,
251 ArrayRef<lsp::TextDocumentContentChangeEvent> changes,
252 std::vector<lsp::Diagnostic> &diagnostics);
253
254 //===--------------------------------------------------------------------===//
255 // Definitions and References
256 //===--------------------------------------------------------------------===//
257
258 void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos,
259 std::vector<lsp::Location> &locations);
260 void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos,
261 std::vector<lsp::Location> &references);
262
263 //===--------------------------------------------------------------------===//
264 // Document Links
265 //===--------------------------------------------------------------------===//
266
267 void getDocumentLinks(const lsp::URIForFile &uri,
268 std::vector<lsp::DocumentLink> &links);
269
270 //===--------------------------------------------------------------------===//
271 // Hover
272 //===--------------------------------------------------------------------===//
273
274 Optional<lsp::Hover> findHover(const lsp::URIForFile &uri,
275 const lsp::Position &hoverPos);
276
277 private:
278 /// Initialize the text file from the given file contents.
279 void initialize(const lsp::URIForFile &uri, int64_t newVersion,
280 std::vector<lsp::Diagnostic> &diagnostics);
281
282 /// The full string contents of the file.
283 std::string contents;
284
285 /// The version of this file.
286 int64_t version;
287
288 /// The include directories for this file.
289 std::vector<std::string> includeDirs;
290
291 /// The source manager containing the contents of the input file.
292 llvm::SourceMgr sourceMgr;
293
294 /// The record keeper containing the parsed tablegen constructs.
295 std::unique_ptr<llvm::RecordKeeper> recordKeeper;
296
297 /// The index of the parsed file.
298 TableGenIndex index;
299
300 /// The set of includes of the parsed file.
301 SmallVector<lsp::SourceMgrInclude> parsedIncludes;
302 };
303 } // namespace
304
TableGenTextFile(const lsp::URIForFile & uri,StringRef fileContents,int64_t version,const std::vector<std::string> & extraIncludeDirs,std::vector<lsp::Diagnostic> & diagnostics)305 TableGenTextFile::TableGenTextFile(
306 const lsp::URIForFile &uri, StringRef fileContents, int64_t version,
307 const std::vector<std::string> &extraIncludeDirs,
308 std::vector<lsp::Diagnostic> &diagnostics)
309 : contents(fileContents.str()), version(version) {
310 // Build the set of include directories for this file.
311 llvm::SmallString<32> uriDirectory(uri.file());
312 llvm::sys::path::remove_filename(uriDirectory);
313 includeDirs.push_back(uriDirectory.str().str());
314 includeDirs.insert(includeDirs.end(), extraIncludeDirs.begin(),
315 extraIncludeDirs.end());
316
317 // Initialize the file.
318 initialize(uri, version, diagnostics);
319 }
320
321 LogicalResult
update(const lsp::URIForFile & uri,int64_t newVersion,ArrayRef<lsp::TextDocumentContentChangeEvent> changes,std::vector<lsp::Diagnostic> & diagnostics)322 TableGenTextFile::update(const lsp::URIForFile &uri, int64_t newVersion,
323 ArrayRef<lsp::TextDocumentContentChangeEvent> changes,
324 std::vector<lsp::Diagnostic> &diagnostics) {
325 if (failed(lsp::TextDocumentContentChangeEvent::applyTo(changes, contents))) {
326 lsp::Logger::error("Failed to update contents of {0}", uri.file());
327 return failure();
328 }
329
330 // If the file contents were properly changed, reinitialize the text file.
331 initialize(uri, newVersion, diagnostics);
332 return success();
333 }
334
initialize(const lsp::URIForFile & uri,int64_t newVersion,std::vector<lsp::Diagnostic> & diagnostics)335 void TableGenTextFile::initialize(const lsp::URIForFile &uri,
336 int64_t newVersion,
337 std::vector<lsp::Diagnostic> &diagnostics) {
338 version = newVersion;
339 sourceMgr = llvm::SourceMgr();
340 recordKeeper = std::make_unique<llvm::RecordKeeper>();
341
342 // Build a buffer for this file.
343 auto memBuffer = llvm::MemoryBuffer::getMemBuffer(contents, uri.file());
344 if (!memBuffer) {
345 lsp::Logger::error("Failed to create memory buffer for file", uri.file());
346 return;
347 }
348 sourceMgr.setIncludeDirs(includeDirs);
349 sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc());
350
351 // This class provides a context argument for the llvm::SourceMgr diagnostic
352 // handler.
353 struct DiagHandlerContext {
354 std::vector<lsp::Diagnostic> &diagnostics;
355 const lsp::URIForFile &uri;
356 } handlerContext{diagnostics, uri};
357
358 // Set the diagnostic handler for the tablegen source manager.
359 sourceMgr.setDiagHandler(
360 [](const llvm::SMDiagnostic &diag, void *rawHandlerContext) {
361 auto *ctx = reinterpret_cast<DiagHandlerContext *>(rawHandlerContext);
362 if (auto lspDiag = getLspDiagnoticFromDiag(diag, ctx->uri))
363 ctx->diagnostics.push_back(*lspDiag);
364 },
365 &handlerContext);
366 bool failedToParse = llvm::TableGenParseFile(sourceMgr, *recordKeeper);
367
368 // Process all of the include files.
369 lsp::gatherIncludeFiles(sourceMgr, parsedIncludes);
370 if (failedToParse)
371 return;
372
373 // If we successfully parsed the file, we can now build the index.
374 index.initialize(*recordKeeper);
375 }
376
377 //===----------------------------------------------------------------------===//
378 // TableGenTextFile: Definitions and References
379 //===----------------------------------------------------------------------===//
380
getLocationsOf(const lsp::URIForFile & uri,const lsp::Position & defPos,std::vector<lsp::Location> & locations)381 void TableGenTextFile::getLocationsOf(const lsp::URIForFile &uri,
382 const lsp::Position &defPos,
383 std::vector<lsp::Location> &locations) {
384 SMLoc posLoc = defPos.getAsSMLoc(sourceMgr);
385 const TableGenIndexSymbol *symbol = index.lookup(posLoc);
386 if (!symbol)
387 return;
388
389 locations.push_back(getLocationFromLoc(sourceMgr, symbol->defLoc, uri));
390 }
391
findReferencesOf(const lsp::URIForFile & uri,const lsp::Position & pos,std::vector<lsp::Location> & references)392 void TableGenTextFile::findReferencesOf(
393 const lsp::URIForFile &uri, const lsp::Position &pos,
394 std::vector<lsp::Location> &references) {
395 SMLoc posLoc = pos.getAsSMLoc(sourceMgr);
396 const TableGenIndexSymbol *symbol = index.lookup(posLoc);
397 if (!symbol)
398 return;
399
400 references.push_back(getLocationFromLoc(sourceMgr, symbol->defLoc, uri));
401 for (SMRange refLoc : symbol->references)
402 references.push_back(getLocationFromLoc(sourceMgr, refLoc, uri));
403 }
404
405 //===--------------------------------------------------------------------===//
406 // TableGenTextFile: Document Links
407 //===--------------------------------------------------------------------===//
408
getDocumentLinks(const lsp::URIForFile & uri,std::vector<lsp::DocumentLink> & links)409 void TableGenTextFile::getDocumentLinks(const lsp::URIForFile &uri,
410 std::vector<lsp::DocumentLink> &links) {
411 for (const lsp::SourceMgrInclude &include : parsedIncludes)
412 links.emplace_back(include.range, include.uri);
413 }
414
415 //===----------------------------------------------------------------------===//
416 // TableGenTextFile: Hover
417 //===----------------------------------------------------------------------===//
418
419 Optional<lsp::Hover>
findHover(const lsp::URIForFile & uri,const lsp::Position & hoverPos)420 TableGenTextFile::findHover(const lsp::URIForFile &uri,
421 const lsp::Position &hoverPos) {
422 // Check for a reference to an include.
423 for (const lsp::SourceMgrInclude &include : parsedIncludes)
424 if (include.range.contains(hoverPos))
425 return include.buildHover();
426 return llvm::None;
427 }
428
429 //===----------------------------------------------------------------------===//
430 // TableGenServer::Impl
431 //===----------------------------------------------------------------------===//
432
433 struct lsp::TableGenServer::Impl {
Impllsp::TableGenServer::Impl434 explicit Impl(const Options &options)
435 : options(options), compilationDatabase(options.compilationDatabases) {}
436
437 /// TableGen LSP options.
438 const Options &options;
439
440 /// The compilation database containing additional information for files
441 /// passed to the server.
442 lsp::CompilationDatabase compilationDatabase;
443
444 /// The files held by the server, mapped by their URI file name.
445 llvm::StringMap<std::unique_ptr<TableGenTextFile>> files;
446 };
447
448 //===----------------------------------------------------------------------===//
449 // TableGenServer
450 //===----------------------------------------------------------------------===//
451
TableGenServer(const Options & options)452 lsp::TableGenServer::TableGenServer(const Options &options)
453 : impl(std::make_unique<Impl>(options)) {}
454 lsp::TableGenServer::~TableGenServer() = default;
455
addDocument(const URIForFile & uri,StringRef contents,int64_t version,std::vector<Diagnostic> & diagnostics)456 void lsp::TableGenServer::addDocument(const URIForFile &uri, StringRef contents,
457 int64_t version,
458 std::vector<Diagnostic> &diagnostics) {
459 // Build the set of additional include directories.
460 std::vector<std::string> additionalIncludeDirs = impl->options.extraDirs;
461 const auto &fileInfo = impl->compilationDatabase.getFileInfo(uri.file());
462 llvm::append_range(additionalIncludeDirs, fileInfo.includeDirs);
463
464 impl->files[uri.file()] = std::make_unique<TableGenTextFile>(
465 uri, contents, version, additionalIncludeDirs, diagnostics);
466 }
467
updateDocument(const URIForFile & uri,ArrayRef<TextDocumentContentChangeEvent> changes,int64_t version,std::vector<Diagnostic> & diagnostics)468 void lsp::TableGenServer::updateDocument(
469 const URIForFile &uri, ArrayRef<TextDocumentContentChangeEvent> changes,
470 int64_t version, std::vector<Diagnostic> &diagnostics) {
471 // Check that we actually have a document for this uri.
472 auto it = impl->files.find(uri.file());
473 if (it == impl->files.end())
474 return;
475
476 // Try to update the document. If we fail, erase the file from the server. A
477 // failed updated generally means we've fallen out of sync somewhere.
478 if (failed(it->second->update(uri, version, changes, diagnostics)))
479 impl->files.erase(it);
480 }
481
removeDocument(const URIForFile & uri)482 Optional<int64_t> lsp::TableGenServer::removeDocument(const URIForFile &uri) {
483 auto it = impl->files.find(uri.file());
484 if (it == impl->files.end())
485 return llvm::None;
486
487 int64_t version = it->second->getVersion();
488 impl->files.erase(it);
489 return version;
490 }
491
getLocationsOf(const URIForFile & uri,const Position & defPos,std::vector<Location> & locations)492 void lsp::TableGenServer::getLocationsOf(const URIForFile &uri,
493 const Position &defPos,
494 std::vector<Location> &locations) {
495 auto fileIt = impl->files.find(uri.file());
496 if (fileIt != impl->files.end())
497 fileIt->second->getLocationsOf(uri, defPos, locations);
498 }
499
findReferencesOf(const URIForFile & uri,const Position & pos,std::vector<Location> & references)500 void lsp::TableGenServer::findReferencesOf(const URIForFile &uri,
501 const Position &pos,
502 std::vector<Location> &references) {
503 auto fileIt = impl->files.find(uri.file());
504 if (fileIt != impl->files.end())
505 fileIt->second->findReferencesOf(uri, pos, references);
506 }
507
getDocumentLinks(const URIForFile & uri,std::vector<DocumentLink> & documentLinks)508 void lsp::TableGenServer::getDocumentLinks(
509 const URIForFile &uri, std::vector<DocumentLink> &documentLinks) {
510 auto fileIt = impl->files.find(uri.file());
511 if (fileIt != impl->files.end())
512 return fileIt->second->getDocumentLinks(uri, documentLinks);
513 }
514
findHover(const URIForFile & uri,const Position & hoverPos)515 Optional<lsp::Hover> lsp::TableGenServer::findHover(const URIForFile &uri,
516 const Position &hoverPos) {
517 auto fileIt = impl->files.find(uri.file());
518 if (fileIt != impl->files.end())
519 return fileIt->second->findHover(uri, hoverPos);
520 return llvm::None;
521 }
522