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 259 //===--------------------------------------------------------------------===// 260 // Definitions and References 261 //===--------------------------------------------------------------------===// 262 263 void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos, 264 std::vector<lsp::Location> &locations); 265 void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos, 266 std::vector<lsp::Location> &references); 267 268 //===--------------------------------------------------------------------===// 269 // Hover 270 //===--------------------------------------------------------------------===// 271 272 Optional<lsp::Hover> findHover(const lsp::URIForFile &uri, 273 const lsp::Position &hoverPos); 274 Optional<lsp::Hover> 275 buildHoverForOperation(const AsmParserState::OperationDefinition &op); 276 lsp::Hover buildHoverForOperationResult(llvm::SMRange hoverRange, 277 Operation *op, unsigned resultStart, 278 unsigned resultEnd, 279 llvm::SMLoc posLoc); 280 lsp::Hover buildHoverForBlock(llvm::SMRange hoverRange, 281 const AsmParserState::BlockDefinition &block); 282 lsp::Hover 283 buildHoverForBlockArgument(llvm::SMRange hoverRange, BlockArgument arg, 284 const AsmParserState::BlockDefinition &block); 285 286 /// The context used to hold the state contained by the parsed document. 287 MLIRContext context; 288 289 /// The high level parser state used to find definitions and references within 290 /// the source file. 291 AsmParserState asmState; 292 293 /// The container for the IR parsed from the input file. 294 Block parsedIR; 295 296 /// The source manager containing the contents of the input file. 297 llvm::SourceMgr sourceMgr; 298 }; 299 } // namespace 300 301 MLIRDocument::MLIRDocument(const lsp::URIForFile &uri, StringRef contents, 302 DialectRegistry ®istry, 303 std::vector<lsp::Diagnostic> &diagnostics) 304 : context(registry) { 305 context.allowUnregisteredDialects(); 306 ScopedDiagnosticHandler handler(&context, [&](Diagnostic &diag) { 307 diagnostics.push_back(getLspDiagnoticFromDiag(diag, uri)); 308 }); 309 310 // Try to parsed the given IR string. 311 auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file()); 312 if (!memBuffer) { 313 lsp::Logger::error("Failed to create memory buffer for file", uri.file()); 314 return; 315 } 316 317 sourceMgr.AddNewSourceBuffer(std::move(memBuffer), llvm::SMLoc()); 318 if (failed(parseSourceFile(sourceMgr, &parsedIR, &context, nullptr, 319 &asmState))) { 320 // If parsing failed, clear out any of the current state. 321 parsedIR.clear(); 322 asmState = AsmParserState(); 323 return; 324 } 325 } 326 327 //===----------------------------------------------------------------------===// 328 // MLIRDocument: Definitions and References 329 //===----------------------------------------------------------------------===// 330 331 void MLIRDocument::getLocationsOf(const lsp::URIForFile &uri, 332 const lsp::Position &defPos, 333 std::vector<lsp::Location> &locations) { 334 llvm::SMLoc posLoc = getPosFromLoc(sourceMgr, defPos); 335 336 // Functor used to check if an SM definition contains the position. 337 auto containsPosition = [&](const AsmParserState::SMDefinition &def) { 338 if (!isDefOrUse(def, posLoc)) 339 return false; 340 locations.push_back(getLocationFromLoc(sourceMgr, def.loc, uri)); 341 return true; 342 }; 343 344 // Check all definitions related to operations. 345 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) { 346 if (contains(op.loc, posLoc)) 347 return collectLocationsFromLoc(op.op->getLoc(), locations, uri); 348 for (const auto &result : op.resultGroups) 349 if (containsPosition(result.second)) 350 return collectLocationsFromLoc(op.op->getLoc(), locations, uri); 351 } 352 353 // Check all definitions related to blocks. 354 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) { 355 if (containsPosition(block.definition)) 356 return; 357 for (const AsmParserState::SMDefinition &arg : block.arguments) 358 if (containsPosition(arg)) 359 return; 360 } 361 } 362 363 void MLIRDocument::findReferencesOf(const lsp::URIForFile &uri, 364 const lsp::Position &pos, 365 std::vector<lsp::Location> &references) { 366 // Functor used to append all of the definitions/uses of the given SM 367 // definition to the reference list. 368 auto appendSMDef = [&](const AsmParserState::SMDefinition &def) { 369 references.push_back(getLocationFromLoc(sourceMgr, def.loc, uri)); 370 for (const llvm::SMRange &use : def.uses) 371 references.push_back(getLocationFromLoc(sourceMgr, use, uri)); 372 }; 373 374 llvm::SMLoc posLoc = getPosFromLoc(sourceMgr, pos); 375 376 // Check all definitions related to operations. 377 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) { 378 if (contains(op.loc, posLoc)) { 379 for (const auto &result : op.resultGroups) 380 appendSMDef(result.second); 381 return; 382 } 383 for (const auto &result : op.resultGroups) 384 if (isDefOrUse(result.second, posLoc)) 385 return appendSMDef(result.second); 386 } 387 388 // Check all definitions related to blocks. 389 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) { 390 if (isDefOrUse(block.definition, posLoc)) 391 return appendSMDef(block.definition); 392 393 for (const AsmParserState::SMDefinition &arg : block.arguments) 394 if (isDefOrUse(arg, posLoc)) 395 return appendSMDef(arg); 396 } 397 } 398 399 //===----------------------------------------------------------------------===// 400 // MLIRDocument: Hover 401 //===----------------------------------------------------------------------===// 402 403 Optional<lsp::Hover> MLIRDocument::findHover(const lsp::URIForFile &uri, 404 const lsp::Position &hoverPos) { 405 llvm::SMLoc posLoc = getPosFromLoc(sourceMgr, hoverPos); 406 llvm::SMRange hoverRange; 407 408 // Check for Hovers on operations and results. 409 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) { 410 // Check if the position points at this operation. 411 if (contains(op.loc, posLoc)) 412 return buildHoverForOperation(op); 413 414 // Check if the position points at a result group. 415 for (unsigned i = 0, e = op.resultGroups.size(); i < e; ++i) { 416 const auto &result = op.resultGroups[i]; 417 if (!isDefOrUse(result.second, posLoc, &hoverRange)) 418 continue; 419 420 // Get the range of results covered by the over position. 421 unsigned resultStart = result.first; 422 unsigned resultEnd = 423 (i == e - 1) ? op.op->getNumResults() : op.resultGroups[i + 1].first; 424 return buildHoverForOperationResult(hoverRange, op.op, resultStart, 425 resultEnd, posLoc); 426 } 427 } 428 429 // Check to see if the hover is over a block argument. 430 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) { 431 if (isDefOrUse(block.definition, posLoc, &hoverRange)) 432 return buildHoverForBlock(hoverRange, block); 433 434 for (const auto &arg : llvm::enumerate(block.arguments)) { 435 if (!isDefOrUse(arg.value(), posLoc, &hoverRange)) 436 continue; 437 438 return buildHoverForBlockArgument( 439 hoverRange, block.block->getArgument(arg.index()), block); 440 } 441 } 442 return llvm::None; 443 } 444 445 Optional<lsp::Hover> MLIRDocument::buildHoverForOperation( 446 const AsmParserState::OperationDefinition &op) { 447 // Don't show hovers for operations with regions to avoid huge hover blocks. 448 // TODO: Should we add support for printing an op without its regions? 449 if (llvm::any_of(op.op->getRegions(), 450 [](Region ®ion) { return !region.empty(); })) 451 return llvm::None; 452 453 lsp::Hover hover(getRangeFromLoc(sourceMgr, op.loc)); 454 llvm::raw_string_ostream os(hover.contents.value); 455 456 // For hovers on an operation, show the generic form. 457 os << "```mlir\n"; 458 op.op->print( 459 os, OpPrintingFlags().printGenericOpForm().elideLargeElementsAttrs()); 460 os << "\n```\n"; 461 462 return hover; 463 } 464 465 lsp::Hover MLIRDocument::buildHoverForOperationResult(llvm::SMRange hoverRange, 466 Operation *op, 467 unsigned resultStart, 468 unsigned resultEnd, 469 llvm::SMLoc posLoc) { 470 lsp::Hover hover(getRangeFromLoc(sourceMgr, hoverRange)); 471 llvm::raw_string_ostream os(hover.contents.value); 472 473 // Add the parent operation name to the hover. 474 os << "Operation: \"" << op->getName() << "\"\n\n"; 475 476 // Check to see if the location points to a specific result within the 477 // group. 478 if (Optional<unsigned> resultNumber = getResultNumberFromLoc(posLoc)) { 479 if ((resultStart + *resultNumber) < resultEnd) { 480 resultStart += *resultNumber; 481 resultEnd = resultStart + 1; 482 } 483 } 484 485 // Add the range of results and their types to the hover info. 486 if ((resultStart + 1) == resultEnd) { 487 os << "Result #" << resultStart << "\n\n" 488 << "Type: `" << op->getResult(resultStart).getType() << "`\n\n"; 489 } else { 490 os << "Result #[" << resultStart << ", " << (resultEnd - 1) << "]\n\n" 491 << "Types: "; 492 llvm::interleaveComma( 493 op->getResults().slice(resultStart, resultEnd), os, 494 [&](Value result) { os << "`" << result.getType() << "`"; }); 495 } 496 497 return hover; 498 } 499 500 lsp::Hover 501 MLIRDocument::buildHoverForBlock(llvm::SMRange hoverRange, 502 const AsmParserState::BlockDefinition &block) { 503 lsp::Hover hover(getRangeFromLoc(sourceMgr, hoverRange)); 504 llvm::raw_string_ostream os(hover.contents.value); 505 506 // Print the given block to the hover output stream. 507 auto printBlockToHover = [&](Block *newBlock) { 508 if (const auto *def = asmState.getBlockDef(newBlock)) 509 printDefBlockName(os, *def); 510 else 511 printDefBlockName(os, newBlock); 512 }; 513 514 // Display the parent operation, block number, predecessors, and successors. 515 os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n" 516 << "Block #" << getBlockNumber(block.block) << "\n\n"; 517 if (!block.block->hasNoPredecessors()) { 518 os << "Predecessors: "; 519 llvm::interleaveComma(block.block->getPredecessors(), os, 520 printBlockToHover); 521 os << "\n\n"; 522 } 523 if (!block.block->hasNoSuccessors()) { 524 os << "Successors: "; 525 llvm::interleaveComma(block.block->getSuccessors(), os, printBlockToHover); 526 os << "\n\n"; 527 } 528 529 return hover; 530 } 531 532 lsp::Hover MLIRDocument::buildHoverForBlockArgument( 533 llvm::SMRange hoverRange, BlockArgument arg, 534 const AsmParserState::BlockDefinition &block) { 535 lsp::Hover hover(getRangeFromLoc(sourceMgr, hoverRange)); 536 llvm::raw_string_ostream os(hover.contents.value); 537 538 // Display the parent operation, block, the argument number, and the type. 539 os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n" 540 << "Block: "; 541 printDefBlockName(os, block); 542 os << "\n\nArgument #" << arg.getArgNumber() << "\n\n" 543 << "Type: `" << arg.getType() << "`\n\n"; 544 545 return hover; 546 } 547 548 //===----------------------------------------------------------------------===// 549 // MLIRServer::Impl 550 //===----------------------------------------------------------------------===// 551 552 struct lsp::MLIRServer::Impl { 553 Impl(DialectRegistry ®istry) : registry(registry) {} 554 555 /// The registry containing dialects that can be recognized in parsed .mlir 556 /// files. 557 DialectRegistry ®istry; 558 559 /// The documents held by the server, mapped by their URI file name. 560 llvm::StringMap<std::unique_ptr<MLIRDocument>> documents; 561 }; 562 563 //===----------------------------------------------------------------------===// 564 // MLIRServer 565 //===----------------------------------------------------------------------===// 566 567 lsp::MLIRServer::MLIRServer(DialectRegistry ®istry) 568 : impl(std::make_unique<Impl>(registry)) {} 569 lsp::MLIRServer::~MLIRServer() {} 570 571 void lsp::MLIRServer::addOrUpdateDocument( 572 const URIForFile &uri, StringRef contents, 573 std::vector<Diagnostic> &diagnostics) { 574 impl->documents[uri.file()] = std::make_unique<MLIRDocument>( 575 uri, contents, impl->registry, diagnostics); 576 } 577 578 void lsp::MLIRServer::removeDocument(const URIForFile &uri) { 579 impl->documents.erase(uri.file()); 580 } 581 582 void lsp::MLIRServer::getLocationsOf(const URIForFile &uri, 583 const Position &defPos, 584 std::vector<Location> &locations) { 585 auto fileIt = impl->documents.find(uri.file()); 586 if (fileIt != impl->documents.end()) 587 fileIt->second->getLocationsOf(uri, defPos, locations); 588 } 589 590 void lsp::MLIRServer::findReferencesOf(const URIForFile &uri, 591 const Position &pos, 592 std::vector<Location> &references) { 593 auto fileIt = impl->documents.find(uri.file()); 594 if (fileIt != impl->documents.end()) 595 fileIt->second->findReferencesOf(uri, pos, references); 596 } 597 598 Optional<lsp::Hover> lsp::MLIRServer::findHover(const URIForFile &uri, 599 const Position &hoverPos) { 600 auto fileIt = impl->documents.find(uri.file()); 601 if (fileIt != impl->documents.end()) 602 return fileIt->second->findHover(uri, hoverPos); 603 return llvm::None; 604 } 605