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