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