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