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 ¬e : 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 ®istry, 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 ®istry, 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 ®ion) { 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 ®istry, 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 ®istry, 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 ®istry, 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 ®istry) : registry(registry) {} 720 721 /// The registry containing dialects that can be recognized in parsed .mlir 722 /// files. 723 DialectRegistry ®istry; 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 ®istry) 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