1 //===- MLIRServer.cpp - MLIR Generic 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 "MLIRServer.h"
10 #include "../lsp-server-support/Logging.h"
11 #include "../lsp-server-support/Protocol.h"
12 #include "mlir/IR/FunctionInterfaces.h"
13 #include "mlir/IR/Operation.h"
14 #include "mlir/Parser/AsmParserState.h"
15 #include "mlir/Parser/Parser.h"
16 #include "llvm/Support/SourceMgr.h"
17 
18 using namespace mlir;
19 
20 /// Returns a language server position for the given source location.
21 static lsp::Position getPosFromLoc(llvm::SourceMgr &mgr, SMLoc loc) {
22   std::pair<unsigned, unsigned> lineAndCol = mgr.getLineAndColumn(loc);
23   lsp::Position pos;
24   pos.line = lineAndCol.first - 1;
25   pos.character = lineAndCol.second - 1;
26   return pos;
27 }
28 
29 /// Returns a source location from the given language server position.
30 static SMLoc getPosFromLoc(llvm::SourceMgr &mgr, lsp::Position pos) {
31   return mgr.FindLocForLineAndColumn(mgr.getMainFileID(), pos.line + 1,
32                                      pos.character);
33 }
34 
35 /// Returns a language server range for the given source range.
36 static lsp::Range getRangeFromLoc(llvm::SourceMgr &mgr, SMRange range) {
37   return {getPosFromLoc(mgr, range.Start), getPosFromLoc(mgr, range.End)};
38 }
39 
40 /// Returns a language server location from the given source range.
41 static lsp::Location getLocationFromLoc(llvm::SourceMgr &mgr, SMRange range,
42                                         const lsp::URIForFile &uri) {
43   return lsp::Location{uri, getRangeFromLoc(mgr, range)};
44 }
45 
46 /// Returns a language server location from the given MLIR file location.
47 static Optional<lsp::Location> getLocationFromLoc(FileLineColLoc loc) {
48   llvm::Expected<lsp::URIForFile> sourceURI =
49       lsp::URIForFile::fromFile(loc.getFilename());
50   if (!sourceURI) {
51     lsp::Logger::error("Failed to create URI for file `{0}`: {1}",
52                        loc.getFilename(),
53                        llvm::toString(sourceURI.takeError()));
54     return llvm::None;
55   }
56 
57   lsp::Position position;
58   position.line = loc.getLine() - 1;
59   position.character = loc.getColumn();
60   return lsp::Location{*sourceURI, lsp::Range(position)};
61 }
62 
63 /// Returns a language server location from the given MLIR location, or None if
64 /// one couldn't be created. `uri` is an optional additional filter that, when
65 /// present, is used to filter sub locations that do not share the same uri.
66 static Optional<lsp::Location>
67 getLocationFromLoc(llvm::SourceMgr &sourceMgr, Location loc,
68                    const lsp::URIForFile *uri = nullptr) {
69   Optional<lsp::Location> location;
70   loc->walk([&](Location nestedLoc) {
71     FileLineColLoc fileLoc = nestedLoc.dyn_cast<FileLineColLoc>();
72     if (!fileLoc)
73       return WalkResult::advance();
74 
75     Optional<lsp::Location> sourceLoc = getLocationFromLoc(fileLoc);
76     if (sourceLoc && (!uri || sourceLoc->uri == *uri)) {
77       location = *sourceLoc;
78       SMLoc loc = sourceMgr.FindLocForLineAndColumn(
79           sourceMgr.getMainFileID(), fileLoc.getLine(), fileLoc.getColumn());
80 
81       // Use range of potential identifier starting at location, else length 1
82       // range.
83       location->range.end.character += 1;
84       if (Optional<SMRange> range = AsmParserState::convertIdLocToRange(loc)) {
85         auto lineCol = sourceMgr.getLineAndColumn(range->End);
86         location->range.end.character =
87             std::max(fileLoc.getColumn() + 1, lineCol.second - 1);
88       }
89       return WalkResult::interrupt();
90     }
91     return WalkResult::advance();
92   });
93   return location;
94 }
95 
96 /// Collect all of the locations from the given MLIR location that are not
97 /// contained within the given URI.
98 static void collectLocationsFromLoc(Location loc,
99                                     std::vector<lsp::Location> &locations,
100                                     const lsp::URIForFile &uri) {
101   SetVector<Location> visitedLocs;
102   loc->walk([&](Location nestedLoc) {
103     FileLineColLoc fileLoc = nestedLoc.dyn_cast<FileLineColLoc>();
104     if (!fileLoc || !visitedLocs.insert(nestedLoc))
105       return WalkResult::advance();
106 
107     Optional<lsp::Location> sourceLoc = getLocationFromLoc(fileLoc);
108     if (sourceLoc && sourceLoc->uri != uri)
109       locations.push_back(*sourceLoc);
110     return WalkResult::advance();
111   });
112 }
113 
114 /// Returns true if the given range contains the given source location. Note
115 /// that this has slightly different behavior than SMRange because it is
116 /// inclusive of the end location.
117 static bool contains(SMRange range, SMLoc loc) {
118   return range.Start.getPointer() <= loc.getPointer() &&
119          loc.getPointer() <= range.End.getPointer();
120 }
121 
122 /// Returns true if the given location is contained by the definition or one of
123 /// the uses of the given SMDefinition. If provided, `overlappedRange` is set to
124 /// the range within `def` that the provided `loc` overlapped with.
125 static bool isDefOrUse(const AsmParserState::SMDefinition &def, SMLoc loc,
126                        SMRange *overlappedRange = nullptr) {
127   // Check the main definition.
128   if (contains(def.loc, loc)) {
129     if (overlappedRange)
130       *overlappedRange = def.loc;
131     return true;
132   }
133 
134   // Check the uses.
135   const auto *useIt = llvm::find_if(
136       def.uses, [&](const SMRange &range) { return contains(range, loc); });
137   if (useIt != def.uses.end()) {
138     if (overlappedRange)
139       *overlappedRange = *useIt;
140     return true;
141   }
142   return false;
143 }
144 
145 /// Given a location pointing to a result, return the result number it refers
146 /// to or None if it refers to all of the results.
147 static Optional<unsigned> getResultNumberFromLoc(SMLoc loc) {
148   // Skip all of the identifier characters.
149   auto isIdentifierChar = [](char c) {
150     return isalnum(c) || c == '%' || c == '$' || c == '.' || c == '_' ||
151            c == '-';
152   };
153   const char *curPtr = loc.getPointer();
154   while (isIdentifierChar(*curPtr))
155     ++curPtr;
156 
157   // Check to see if this location indexes into the result group, via `#`. If it
158   // doesn't, we can't extract a sub result number.
159   if (*curPtr != '#')
160     return llvm::None;
161 
162   // Compute the sub result number from the remaining portion of the string.
163   const char *numberStart = ++curPtr;
164   while (llvm::isDigit(*curPtr))
165     ++curPtr;
166   StringRef numberStr(numberStart, curPtr - numberStart);
167   unsigned resultNumber = 0;
168   return numberStr.consumeInteger(10, resultNumber) ? Optional<unsigned>()
169                                                     : resultNumber;
170 }
171 
172 /// Given a source location range, return the text covered by the given range.
173 /// If the range is invalid, returns None.
174 static Optional<StringRef> getTextFromRange(SMRange range) {
175   if (!range.isValid())
176     return None;
177   const char *startPtr = range.Start.getPointer();
178   return StringRef(startPtr, range.End.getPointer() - startPtr);
179 }
180 
181 /// Given a block, return its position in its parent region.
182 static unsigned getBlockNumber(Block *block) {
183   return std::distance(block->getParent()->begin(), block->getIterator());
184 }
185 
186 /// Given a block and source location, print the source name of the block to the
187 /// given output stream.
188 static void printDefBlockName(raw_ostream &os, Block *block, SMRange loc = {}) {
189   // Try to extract a name from the source location.
190   Optional<StringRef> text = getTextFromRange(loc);
191   if (text && text->startswith("^")) {
192     os << *text;
193     return;
194   }
195 
196   // Otherwise, we don't have a name so print the block number.
197   os << "<Block #" << getBlockNumber(block) << ">";
198 }
199 static void printDefBlockName(raw_ostream &os,
200                               const AsmParserState::BlockDefinition &def) {
201   printDefBlockName(os, def.block, def.definition.loc);
202 }
203 
204 /// Convert the given MLIR diagnostic to the LSP form.
205 static lsp::Diagnostic getLspDiagnoticFromDiag(llvm::SourceMgr &sourceMgr,
206                                                Diagnostic &diag,
207                                                const lsp::URIForFile &uri) {
208   lsp::Diagnostic lspDiag;
209   lspDiag.source = "mlir";
210 
211   // Note: Right now all of the diagnostics are treated as parser issues, but
212   // some are parser and some are verifier.
213   lspDiag.category = "Parse Error";
214 
215   // Try to grab a file location for this diagnostic.
216   // TODO: For simplicity, we just grab the first one. It may be likely that we
217   // will need a more interesting heuristic here.'
218   Optional<lsp::Location> lspLocation =
219       getLocationFromLoc(sourceMgr, diag.getLocation(), &uri);
220   if (lspLocation)
221     lspDiag.range = lspLocation->range;
222 
223   // Convert the severity for the diagnostic.
224   switch (diag.getSeverity()) {
225   case DiagnosticSeverity::Note:
226     llvm_unreachable("expected notes to be handled separately");
227   case DiagnosticSeverity::Warning:
228     lspDiag.severity = lsp::DiagnosticSeverity::Warning;
229     break;
230   case DiagnosticSeverity::Error:
231     lspDiag.severity = lsp::DiagnosticSeverity::Error;
232     break;
233   case DiagnosticSeverity::Remark:
234     lspDiag.severity = lsp::DiagnosticSeverity::Information;
235     break;
236   }
237   lspDiag.message = diag.str();
238 
239   // Attach any notes to the main diagnostic as related information.
240   std::vector<lsp::DiagnosticRelatedInformation> relatedDiags;
241   for (Diagnostic &note : diag.getNotes()) {
242     lsp::Location noteLoc;
243     if (Optional<lsp::Location> loc =
244             getLocationFromLoc(sourceMgr, note.getLocation()))
245       noteLoc = *loc;
246     else
247       noteLoc.uri = uri;
248     relatedDiags.emplace_back(noteLoc, note.str());
249   }
250   if (!relatedDiags.empty())
251     lspDiag.relatedInformation = std::move(relatedDiags);
252 
253   return lspDiag;
254 }
255 
256 //===----------------------------------------------------------------------===//
257 // MLIRDocument
258 //===----------------------------------------------------------------------===//
259 
260 namespace {
261 /// This class represents all of the information pertaining to a specific MLIR
262 /// document.
263 struct MLIRDocument {
264   MLIRDocument(MLIRContext &context, const lsp::URIForFile &uri,
265                StringRef contents, std::vector<lsp::Diagnostic> &diagnostics);
266   MLIRDocument(const MLIRDocument &) = delete;
267   MLIRDocument &operator=(const MLIRDocument &) = delete;
268 
269   //===--------------------------------------------------------------------===//
270   // Definitions and References
271   //===--------------------------------------------------------------------===//
272 
273   void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos,
274                       std::vector<lsp::Location> &locations);
275   void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos,
276                         std::vector<lsp::Location> &references);
277 
278   //===--------------------------------------------------------------------===//
279   // Hover
280   //===--------------------------------------------------------------------===//
281 
282   Optional<lsp::Hover> findHover(const lsp::URIForFile &uri,
283                                  const lsp::Position &hoverPos);
284   Optional<lsp::Hover>
285   buildHoverForOperation(SMRange hoverRange,
286                          const AsmParserState::OperationDefinition &op);
287   lsp::Hover buildHoverForOperationResult(SMRange hoverRange, Operation *op,
288                                           unsigned resultStart,
289                                           unsigned resultEnd, SMLoc posLoc);
290   lsp::Hover buildHoverForBlock(SMRange hoverRange,
291                                 const AsmParserState::BlockDefinition &block);
292   lsp::Hover
293   buildHoverForBlockArgument(SMRange hoverRange, BlockArgument arg,
294                              const AsmParserState::BlockDefinition &block);
295 
296   //===--------------------------------------------------------------------===//
297   // Document Symbols
298   //===--------------------------------------------------------------------===//
299 
300   void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols);
301   void findDocumentSymbols(Operation *op,
302                            std::vector<lsp::DocumentSymbol> &symbols);
303 
304   //===--------------------------------------------------------------------===//
305   // Fields
306   //===--------------------------------------------------------------------===//
307 
308   /// The high level parser state used to find definitions and references within
309   /// the source file.
310   AsmParserState asmState;
311 
312   /// The container for the IR parsed from the input file.
313   Block parsedIR;
314 
315   /// The source manager containing the contents of the input file.
316   llvm::SourceMgr sourceMgr;
317 };
318 } // namespace
319 
320 MLIRDocument::MLIRDocument(MLIRContext &context, const lsp::URIForFile &uri,
321                            StringRef contents,
322                            std::vector<lsp::Diagnostic> &diagnostics) {
323   ScopedDiagnosticHandler handler(&context, [&](Diagnostic &diag) {
324     diagnostics.push_back(getLspDiagnoticFromDiag(sourceMgr, diag, uri));
325   });
326 
327   // Try to parsed the given IR string.
328   auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file());
329   if (!memBuffer) {
330     lsp::Logger::error("Failed to create memory buffer for file", uri.file());
331     return;
332   }
333 
334   sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc());
335   if (failed(parseSourceFile(sourceMgr, &parsedIR, &context, nullptr,
336                              &asmState))) {
337     // If parsing failed, clear out any of the current state.
338     parsedIR.clear();
339     asmState = AsmParserState();
340     return;
341   }
342 }
343 
344 //===----------------------------------------------------------------------===//
345 // MLIRDocument: Definitions and References
346 //===----------------------------------------------------------------------===//
347 
348 void MLIRDocument::getLocationsOf(const lsp::URIForFile &uri,
349                                   const lsp::Position &defPos,
350                                   std::vector<lsp::Location> &locations) {
351   SMLoc posLoc = getPosFromLoc(sourceMgr, defPos);
352 
353   // Functor used to check if an SM definition contains the position.
354   auto containsPosition = [&](const AsmParserState::SMDefinition &def) {
355     if (!isDefOrUse(def, posLoc))
356       return false;
357     locations.push_back(getLocationFromLoc(sourceMgr, def.loc, uri));
358     return true;
359   };
360 
361   // Check all definitions related to operations.
362   for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) {
363     if (contains(op.loc, posLoc))
364       return collectLocationsFromLoc(op.op->getLoc(), locations, uri);
365     for (const auto &result : op.resultGroups)
366       if (containsPosition(result.definition))
367         return collectLocationsFromLoc(op.op->getLoc(), locations, uri);
368     for (const auto &symUse : op.symbolUses) {
369       if (contains(symUse, posLoc)) {
370         locations.push_back(getLocationFromLoc(sourceMgr, op.loc, uri));
371         return collectLocationsFromLoc(op.op->getLoc(), locations, uri);
372       }
373     }
374   }
375 
376   // Check all definitions related to blocks.
377   for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) {
378     if (containsPosition(block.definition))
379       return;
380     for (const AsmParserState::SMDefinition &arg : block.arguments)
381       if (containsPosition(arg))
382         return;
383   }
384 }
385 
386 void MLIRDocument::findReferencesOf(const lsp::URIForFile &uri,
387                                     const lsp::Position &pos,
388                                     std::vector<lsp::Location> &references) {
389   // Functor used to append all of the definitions/uses of the given SM
390   // definition to the reference list.
391   auto appendSMDef = [&](const AsmParserState::SMDefinition &def) {
392     references.push_back(getLocationFromLoc(sourceMgr, def.loc, uri));
393     for (const SMRange &use : def.uses)
394       references.push_back(getLocationFromLoc(sourceMgr, use, uri));
395   };
396 
397   SMLoc posLoc = getPosFromLoc(sourceMgr, pos);
398 
399   // Check all definitions related to operations.
400   for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) {
401     if (contains(op.loc, posLoc)) {
402       for (const auto &result : op.resultGroups)
403         appendSMDef(result.definition);
404       for (const auto &symUse : op.symbolUses)
405         if (contains(symUse, posLoc))
406           references.push_back(getLocationFromLoc(sourceMgr, symUse, uri));
407       return;
408     }
409     for (const auto &result : op.resultGroups)
410       if (isDefOrUse(result.definition, posLoc))
411         return appendSMDef(result.definition);
412     for (const auto &symUse : op.symbolUses) {
413       if (!contains(symUse, posLoc))
414         continue;
415       for (const auto &symUse : op.symbolUses)
416         references.push_back(getLocationFromLoc(sourceMgr, symUse, uri));
417       return;
418     }
419   }
420 
421   // Check all definitions related to blocks.
422   for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) {
423     if (isDefOrUse(block.definition, posLoc))
424       return appendSMDef(block.definition);
425 
426     for (const AsmParserState::SMDefinition &arg : block.arguments)
427       if (isDefOrUse(arg, posLoc))
428         return appendSMDef(arg);
429   }
430 }
431 
432 //===----------------------------------------------------------------------===//
433 // MLIRDocument: Hover
434 //===----------------------------------------------------------------------===//
435 
436 Optional<lsp::Hover> MLIRDocument::findHover(const lsp::URIForFile &uri,
437                                              const lsp::Position &hoverPos) {
438   SMLoc posLoc = getPosFromLoc(sourceMgr, hoverPos);
439   SMRange hoverRange;
440 
441   // Check for Hovers on operations and results.
442   for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) {
443     // Check if the position points at this operation.
444     if (contains(op.loc, posLoc))
445       return buildHoverForOperation(op.loc, op);
446 
447     // Check if the position points at the symbol name.
448     for (auto &use : op.symbolUses)
449       if (contains(use, posLoc))
450         return buildHoverForOperation(use, op);
451 
452     // Check if the position points at a result group.
453     for (unsigned i = 0, e = op.resultGroups.size(); i < e; ++i) {
454       const auto &result = op.resultGroups[i];
455       if (!isDefOrUse(result.definition, posLoc, &hoverRange))
456         continue;
457 
458       // Get the range of results covered by the over position.
459       unsigned resultStart = result.startIndex;
460       unsigned resultEnd = (i == e - 1) ? op.op->getNumResults()
461                                         : op.resultGroups[i + 1].startIndex;
462       return buildHoverForOperationResult(hoverRange, op.op, resultStart,
463                                           resultEnd, posLoc);
464     }
465   }
466 
467   // Check to see if the hover is over a block argument.
468   for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) {
469     if (isDefOrUse(block.definition, posLoc, &hoverRange))
470       return buildHoverForBlock(hoverRange, block);
471 
472     for (const auto &arg : llvm::enumerate(block.arguments)) {
473       if (!isDefOrUse(arg.value(), posLoc, &hoverRange))
474         continue;
475 
476       return buildHoverForBlockArgument(
477           hoverRange, block.block->getArgument(arg.index()), block);
478     }
479   }
480   return llvm::None;
481 }
482 
483 Optional<lsp::Hover> MLIRDocument::buildHoverForOperation(
484     SMRange hoverRange, const AsmParserState::OperationDefinition &op) {
485   lsp::Hover hover(getRangeFromLoc(sourceMgr, hoverRange));
486   llvm::raw_string_ostream os(hover.contents.value);
487 
488   // Add the operation name to the hover.
489   os << "\"" << op.op->getName() << "\"";
490   if (SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(op.op))
491     os << " : " << symbol.getVisibility() << " @" << symbol.getName() << "";
492   os << "\n\n";
493 
494   os << "Generic Form:\n\n```mlir\n";
495 
496   // Temporary drop the regions of this operation so that they don't get
497   // printed in the output. This helps keeps the size of the output hover
498   // small.
499   SmallVector<std::unique_ptr<Region>> regions;
500   for (Region &region : op.op->getRegions()) {
501     regions.emplace_back(std::make_unique<Region>());
502     regions.back()->takeBody(region);
503   }
504 
505   op.op->print(
506       os, OpPrintingFlags().printGenericOpForm().elideLargeElementsAttrs());
507   os << "\n```\n";
508 
509   // Move the regions back to the current operation.
510   for (Region &region : op.op->getRegions())
511     region.takeBody(*regions.back());
512 
513   return hover;
514 }
515 
516 lsp::Hover MLIRDocument::buildHoverForOperationResult(SMRange hoverRange,
517                                                       Operation *op,
518                                                       unsigned resultStart,
519                                                       unsigned resultEnd,
520                                                       SMLoc posLoc) {
521   lsp::Hover hover(getRangeFromLoc(sourceMgr, hoverRange));
522   llvm::raw_string_ostream os(hover.contents.value);
523 
524   // Add the parent operation name to the hover.
525   os << "Operation: \"" << op->getName() << "\"\n\n";
526 
527   // Check to see if the location points to a specific result within the
528   // group.
529   if (Optional<unsigned> resultNumber = getResultNumberFromLoc(posLoc)) {
530     if ((resultStart + *resultNumber) < resultEnd) {
531       resultStart += *resultNumber;
532       resultEnd = resultStart + 1;
533     }
534   }
535 
536   // Add the range of results and their types to the hover info.
537   if ((resultStart + 1) == resultEnd) {
538     os << "Result #" << resultStart << "\n\n"
539        << "Type: `" << op->getResult(resultStart).getType() << "`\n\n";
540   } else {
541     os << "Result #[" << resultStart << ", " << (resultEnd - 1) << "]\n\n"
542        << "Types: ";
543     llvm::interleaveComma(
544         op->getResults().slice(resultStart, resultEnd), os,
545         [&](Value result) { os << "`" << result.getType() << "`"; });
546   }
547 
548   return hover;
549 }
550 
551 lsp::Hover
552 MLIRDocument::buildHoverForBlock(SMRange hoverRange,
553                                  const AsmParserState::BlockDefinition &block) {
554   lsp::Hover hover(getRangeFromLoc(sourceMgr, hoverRange));
555   llvm::raw_string_ostream os(hover.contents.value);
556 
557   // Print the given block to the hover output stream.
558   auto printBlockToHover = [&](Block *newBlock) {
559     if (const auto *def = asmState.getBlockDef(newBlock))
560       printDefBlockName(os, *def);
561     else
562       printDefBlockName(os, newBlock);
563   };
564 
565   // Display the parent operation, block number, predecessors, and successors.
566   os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n"
567      << "Block #" << getBlockNumber(block.block) << "\n\n";
568   if (!block.block->hasNoPredecessors()) {
569     os << "Predecessors: ";
570     llvm::interleaveComma(block.block->getPredecessors(), os,
571                           printBlockToHover);
572     os << "\n\n";
573   }
574   if (!block.block->hasNoSuccessors()) {
575     os << "Successors: ";
576     llvm::interleaveComma(block.block->getSuccessors(), os, printBlockToHover);
577     os << "\n\n";
578   }
579 
580   return hover;
581 }
582 
583 lsp::Hover MLIRDocument::buildHoverForBlockArgument(
584     SMRange hoverRange, BlockArgument arg,
585     const AsmParserState::BlockDefinition &block) {
586   lsp::Hover hover(getRangeFromLoc(sourceMgr, hoverRange));
587   llvm::raw_string_ostream os(hover.contents.value);
588 
589   // Display the parent operation, block, the argument number, and the type.
590   os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n"
591      << "Block: ";
592   printDefBlockName(os, block);
593   os << "\n\nArgument #" << arg.getArgNumber() << "\n\n"
594      << "Type: `" << arg.getType() << "`\n\n";
595 
596   return hover;
597 }
598 
599 //===----------------------------------------------------------------------===//
600 // MLIRDocument: Document Symbols
601 //===----------------------------------------------------------------------===//
602 
603 void MLIRDocument::findDocumentSymbols(
604     std::vector<lsp::DocumentSymbol> &symbols) {
605   for (Operation &op : parsedIR)
606     findDocumentSymbols(&op, symbols);
607 }
608 
609 void MLIRDocument::findDocumentSymbols(
610     Operation *op, std::vector<lsp::DocumentSymbol> &symbols) {
611   std::vector<lsp::DocumentSymbol> *childSymbols = &symbols;
612 
613   // Check for the source information of this operation.
614   if (const AsmParserState::OperationDefinition *def = asmState.getOpDef(op)) {
615     // If this operation defines a symbol, record it.
616     if (SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(op)) {
617       symbols.emplace_back(symbol.getName(),
618                            isa<FunctionOpInterface>(op)
619                                ? lsp::SymbolKind::Function
620                                : lsp::SymbolKind::Class,
621                            getRangeFromLoc(sourceMgr, def->scopeLoc),
622                            getRangeFromLoc(sourceMgr, def->loc));
623       childSymbols = &symbols.back().children;
624 
625     } else if (op->hasTrait<OpTrait::SymbolTable>()) {
626       // Otherwise, if this is a symbol table push an anonymous document symbol.
627       symbols.emplace_back("<" + op->getName().getStringRef() + ">",
628                            lsp::SymbolKind::Namespace,
629                            getRangeFromLoc(sourceMgr, def->scopeLoc),
630                            getRangeFromLoc(sourceMgr, def->loc));
631       childSymbols = &symbols.back().children;
632     }
633   }
634 
635   // Recurse into the regions of this operation.
636   if (!op->getNumRegions())
637     return;
638   for (Region &region : op->getRegions())
639     for (Operation &childOp : region.getOps())
640       findDocumentSymbols(&childOp, *childSymbols);
641 }
642 
643 //===----------------------------------------------------------------------===//
644 // MLIRTextFileChunk
645 //===----------------------------------------------------------------------===//
646 
647 namespace {
648 /// This class represents a single chunk of an MLIR text file.
649 struct MLIRTextFileChunk {
650   MLIRTextFileChunk(MLIRContext &context, uint64_t lineOffset,
651                     const lsp::URIForFile &uri, StringRef contents,
652                     std::vector<lsp::Diagnostic> &diagnostics)
653       : lineOffset(lineOffset), document(context, uri, contents, diagnostics) {}
654 
655   /// Adjust the line number of the given range to anchor at the beginning of
656   /// the file, instead of the beginning of this chunk.
657   void adjustLocForChunkOffset(lsp::Range &range) {
658     adjustLocForChunkOffset(range.start);
659     adjustLocForChunkOffset(range.end);
660   }
661   /// Adjust the line number of the given position to anchor at the beginning of
662   /// the file, instead of the beginning of this chunk.
663   void adjustLocForChunkOffset(lsp::Position &pos) { pos.line += lineOffset; }
664 
665   /// The line offset of this chunk from the beginning of the file.
666   uint64_t lineOffset;
667   /// The document referred to by this chunk.
668   MLIRDocument document;
669 };
670 } // namespace
671 
672 //===----------------------------------------------------------------------===//
673 // MLIRTextFile
674 //===----------------------------------------------------------------------===//
675 
676 namespace {
677 /// This class represents a text file containing one or more MLIR documents.
678 class MLIRTextFile {
679 public:
680   MLIRTextFile(const lsp::URIForFile &uri, StringRef fileContents,
681                int64_t version, DialectRegistry &registry,
682                std::vector<lsp::Diagnostic> &diagnostics);
683 
684   /// Return the current version of this text file.
685   int64_t getVersion() const { return version; }
686 
687   //===--------------------------------------------------------------------===//
688   // LSP Queries
689   //===--------------------------------------------------------------------===//
690 
691   void getLocationsOf(const lsp::URIForFile &uri, lsp::Position defPos,
692                       std::vector<lsp::Location> &locations);
693   void findReferencesOf(const lsp::URIForFile &uri, lsp::Position pos,
694                         std::vector<lsp::Location> &references);
695   Optional<lsp::Hover> findHover(const lsp::URIForFile &uri,
696                                  lsp::Position hoverPos);
697   void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols);
698 
699 private:
700   /// Find the MLIR document that contains the given position, and update the
701   /// position to be anchored at the start of the found chunk instead of the
702   /// beginning of the file.
703   MLIRTextFileChunk &getChunkFor(lsp::Position &pos);
704 
705   /// The context used to hold the state contained by the parsed document.
706   MLIRContext context;
707 
708   /// The full string contents of the file.
709   std::string contents;
710 
711   /// The version of this file.
712   int64_t version;
713 
714   /// The number of lines in the file.
715   int64_t totalNumLines = 0;
716 
717   /// The chunks of this file. The order of these chunks is the order in which
718   /// they appear in the text file.
719   std::vector<std::unique_ptr<MLIRTextFileChunk>> chunks;
720 };
721 } // namespace
722 
723 MLIRTextFile::MLIRTextFile(const lsp::URIForFile &uri, StringRef fileContents,
724                            int64_t version, DialectRegistry &registry,
725                            std::vector<lsp::Diagnostic> &diagnostics)
726     : context(registry, MLIRContext::Threading::DISABLED),
727       contents(fileContents.str()), version(version) {
728   context.allowUnregisteredDialects();
729 
730   // Split the file into separate MLIR documents.
731   // TODO: Find a way to share the split file marker with other tools. We don't
732   // want to use `splitAndProcessBuffer` here, but we do want to make sure this
733   // marker doesn't go out of sync.
734   SmallVector<StringRef, 8> subContents;
735   StringRef(contents).split(subContents, "// -----");
736   chunks.emplace_back(std::make_unique<MLIRTextFileChunk>(
737       context, /*lineOffset=*/0, uri, subContents.front(), diagnostics));
738 
739   uint64_t lineOffset = subContents.front().count('\n');
740   for (StringRef docContents : llvm::drop_begin(subContents)) {
741     unsigned currentNumDiags = diagnostics.size();
742     auto chunk = std::make_unique<MLIRTextFileChunk>(context, lineOffset, uri,
743                                                      docContents, diagnostics);
744     lineOffset += docContents.count('\n');
745 
746     // Adjust locations used in diagnostics to account for the offset from the
747     // beginning of the file.
748     for (lsp::Diagnostic &diag :
749          llvm::drop_begin(diagnostics, currentNumDiags)) {
750       chunk->adjustLocForChunkOffset(diag.range);
751 
752       if (!diag.relatedInformation)
753         continue;
754       for (auto &it : *diag.relatedInformation)
755         if (it.location.uri == uri)
756           chunk->adjustLocForChunkOffset(it.location.range);
757     }
758     chunks.emplace_back(std::move(chunk));
759   }
760   totalNumLines = lineOffset;
761 }
762 
763 void MLIRTextFile::getLocationsOf(const lsp::URIForFile &uri,
764                                   lsp::Position defPos,
765                                   std::vector<lsp::Location> &locations) {
766   MLIRTextFileChunk &chunk = getChunkFor(defPos);
767   chunk.document.getLocationsOf(uri, defPos, locations);
768 
769   // Adjust any locations within this file for the offset of this chunk.
770   if (chunk.lineOffset == 0)
771     return;
772   for (lsp::Location &loc : locations)
773     if (loc.uri == uri)
774       chunk.adjustLocForChunkOffset(loc.range);
775 }
776 
777 void MLIRTextFile::findReferencesOf(const lsp::URIForFile &uri,
778                                     lsp::Position pos,
779                                     std::vector<lsp::Location> &references) {
780   MLIRTextFileChunk &chunk = getChunkFor(pos);
781   chunk.document.findReferencesOf(uri, pos, references);
782 
783   // Adjust any locations within this file for the offset of this chunk.
784   if (chunk.lineOffset == 0)
785     return;
786   for (lsp::Location &loc : references)
787     if (loc.uri == uri)
788       chunk.adjustLocForChunkOffset(loc.range);
789 }
790 
791 Optional<lsp::Hover> MLIRTextFile::findHover(const lsp::URIForFile &uri,
792                                              lsp::Position hoverPos) {
793   MLIRTextFileChunk &chunk = getChunkFor(hoverPos);
794   Optional<lsp::Hover> hoverInfo = chunk.document.findHover(uri, hoverPos);
795 
796   // Adjust any locations within this file for the offset of this chunk.
797   if (chunk.lineOffset != 0 && hoverInfo && hoverInfo->range)
798     chunk.adjustLocForChunkOffset(*hoverInfo->range);
799   return hoverInfo;
800 }
801 
802 void MLIRTextFile::findDocumentSymbols(
803     std::vector<lsp::DocumentSymbol> &symbols) {
804   if (chunks.size() == 1)
805     return chunks.front()->document.findDocumentSymbols(symbols);
806 
807   // If there are multiple chunks in this file, we create top-level symbols for
808   // each chunk.
809   for (unsigned i = 0, e = chunks.size(); i < e; ++i) {
810     MLIRTextFileChunk &chunk = *chunks[i];
811     lsp::Position startPos(chunk.lineOffset);
812     lsp::Position endPos((i == e - 1) ? totalNumLines - 1
813                                       : chunks[i + 1]->lineOffset);
814     lsp::DocumentSymbol symbol("<file-split-" + Twine(i) + ">",
815                                lsp::SymbolKind::Namespace,
816                                /*range=*/lsp::Range(startPos, endPos),
817                                /*selectionRange=*/lsp::Range(startPos));
818     chunk.document.findDocumentSymbols(symbol.children);
819 
820     // Fixup the locations of document symbols within this chunk.
821     if (i != 0) {
822       SmallVector<lsp::DocumentSymbol *> symbolsToFix;
823       for (lsp::DocumentSymbol &childSymbol : symbol.children)
824         symbolsToFix.push_back(&childSymbol);
825 
826       while (!symbolsToFix.empty()) {
827         lsp::DocumentSymbol *symbol = symbolsToFix.pop_back_val();
828         chunk.adjustLocForChunkOffset(symbol->range);
829         chunk.adjustLocForChunkOffset(symbol->selectionRange);
830 
831         for (lsp::DocumentSymbol &childSymbol : symbol->children)
832           symbolsToFix.push_back(&childSymbol);
833       }
834     }
835 
836     // Push the symbol for this chunk.
837     symbols.emplace_back(std::move(symbol));
838   }
839 }
840 
841 MLIRTextFileChunk &MLIRTextFile::getChunkFor(lsp::Position &pos) {
842   if (chunks.size() == 1)
843     return *chunks.front();
844 
845   // Search for the first chunk with a greater line offset, the previous chunk
846   // is the one that contains `pos`.
847   auto it = llvm::upper_bound(
848       chunks, pos, [](const lsp::Position &pos, const auto &chunk) {
849         return static_cast<uint64_t>(pos.line) < chunk->lineOffset;
850       });
851   MLIRTextFileChunk &chunk = it == chunks.end() ? *chunks.back() : **(--it);
852   pos.line -= chunk.lineOffset;
853   return chunk;
854 }
855 
856 //===----------------------------------------------------------------------===//
857 // MLIRServer::Impl
858 //===----------------------------------------------------------------------===//
859 
860 struct lsp::MLIRServer::Impl {
861   Impl(DialectRegistry &registry) : registry(registry) {}
862 
863   /// The registry containing dialects that can be recognized in parsed .mlir
864   /// files.
865   DialectRegistry &registry;
866 
867   /// The files held by the server, mapped by their URI file name.
868   llvm::StringMap<std::unique_ptr<MLIRTextFile>> files;
869 };
870 
871 //===----------------------------------------------------------------------===//
872 // MLIRServer
873 //===----------------------------------------------------------------------===//
874 
875 lsp::MLIRServer::MLIRServer(DialectRegistry &registry)
876     : impl(std::make_unique<Impl>(registry)) {}
877 lsp::MLIRServer::~MLIRServer() = default;
878 
879 void lsp::MLIRServer::addOrUpdateDocument(
880     const URIForFile &uri, StringRef contents, int64_t version,
881     std::vector<Diagnostic> &diagnostics) {
882   impl->files[uri.file()] = std::make_unique<MLIRTextFile>(
883       uri, contents, version, impl->registry, diagnostics);
884 }
885 
886 Optional<int64_t> lsp::MLIRServer::removeDocument(const URIForFile &uri) {
887   auto it = impl->files.find(uri.file());
888   if (it == impl->files.end())
889     return llvm::None;
890 
891   int64_t version = it->second->getVersion();
892   impl->files.erase(it);
893   return version;
894 }
895 
896 void lsp::MLIRServer::getLocationsOf(const URIForFile &uri,
897                                      const Position &defPos,
898                                      std::vector<Location> &locations) {
899   auto fileIt = impl->files.find(uri.file());
900   if (fileIt != impl->files.end())
901     fileIt->second->getLocationsOf(uri, defPos, locations);
902 }
903 
904 void lsp::MLIRServer::findReferencesOf(const URIForFile &uri,
905                                        const Position &pos,
906                                        std::vector<Location> &references) {
907   auto fileIt = impl->files.find(uri.file());
908   if (fileIt != impl->files.end())
909     fileIt->second->findReferencesOf(uri, pos, references);
910 }
911 
912 Optional<lsp::Hover> lsp::MLIRServer::findHover(const URIForFile &uri,
913                                                 const Position &hoverPos) {
914   auto fileIt = impl->files.find(uri.file());
915   if (fileIt != impl->files.end())
916     return fileIt->second->findHover(uri, hoverPos);
917   return llvm::None;
918 }
919 
920 void lsp::MLIRServer::findDocumentSymbols(
921     const URIForFile &uri, std::vector<DocumentSymbol> &symbols) {
922   auto fileIt = impl->files.find(uri.file());
923   if (fileIt != impl->files.end())
924     fileIt->second->findDocumentSymbols(symbols);
925 }
926