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