MLIR 24.0.0git
MLIRServer.cpp
Go to the documentation of this file.
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 "Protocol.h"
15#include "mlir/IR/Operation.h"
17#include "mlir/Parser/Parser.h"
20#include "llvm/ADT/StringExtras.h"
21#include "llvm/Support/Base64.h"
22#include "llvm/Support/FileSystem.h"
23#include "llvm/Support/LSP/Logging.h"
24#include "llvm/Support/Path.h"
25#include "llvm/Support/SourceMgr.h"
26#include <limits>
27#include <optional>
28
29using namespace mlir;
30
31/// Returns the range of a lexical token given a SMLoc corresponding to the
32/// start of an token location. The range is computed heuristically, and
33/// supports identifier-like tokens, strings, etc.
34static SMRange convertTokenLocToRange(SMLoc loc) {
35 return lsp::convertTokenLocToRange(loc, "$-.");
36}
37
38/// Convert an MLIR one-based file position to a zero-based LSP position. A
39/// zero position represents an unknown coordinate and maps to zero.
40static std::optional<int> convertFileLocPosition(unsigned value) {
41 if (value == 0)
42 return 0;
43 --value;
44 if (value > static_cast<unsigned>(std::numeric_limits<int>::max()))
45 return std::nullopt;
46 return static_cast<int>(value);
47}
48
49/// Returns a language server location from the given MLIR file location.
50/// `uriScheme` is the scheme to use when building new uris.
51static std::optional<lsp::Location>
52getLocationFromLoc(StringRef uriScheme, FileLineColLoc loc,
53 StringRef workspaceRoot) {
54 StringRef filename = loc.getFilename();
55 SmallString<128> absPath;
56 // Always make the path absolute. Skip paths that start with a separator:
57 // prevents incorrect resolution of virtual paths used in tests on Windows.
58 if (!llvm::sys::path::is_absolute(filename) && !filename.starts_with("/") &&
59 !filename.starts_with("\\")) {
60 if (!workspaceRoot.empty())
61 llvm::sys::path::append(absPath, workspaceRoot, filename);
62 else
63 absPath = filename;
64 llvm::sys::fs::make_absolute(absPath);
65 filename = absPath;
66 }
67
69 lsp::URIForFile::fromFile(filename, uriScheme);
70 if (!sourceURI) {
71 llvm::lsp::Logger::error("Failed to create URI for file `{0}`: {1}",
72 filename, llvm::toString(sourceURI.takeError()));
73 return std::nullopt;
74 }
75
76 std::optional<int> line = convertFileLocPosition(loc.getLine());
77 std::optional<int> character = convertFileLocPosition(loc.getColumn());
78 if (!line || !character)
79 return std::nullopt;
80
81 lsp::Position position;
82 position.line = *line;
83 position.character = *character;
84 return lsp::Location{*sourceURI, lsp::Range(position)};
85}
86
87/// Returns a language server location from the given MLIR location, or
88/// std::nullopt if one couldn't be created. `uriScheme` is the scheme to use
89/// when building new uris. `uri` is an optional additional filter that, when
90/// present, is used to filter sub locations that do not share the same uri.
91static std::optional<lsp::Location>
92getLocationFromLoc(llvm::SourceMgr &sourceMgr, Location loc,
93 StringRef uriScheme, StringRef workspaceRoot,
94 const lsp::URIForFile *uri = nullptr) {
95 std::optional<lsp::Location> location;
96 loc->walk([&](Location nestedLoc) {
97 auto fileLoc = dyn_cast<FileLineColLoc>(nestedLoc);
98 if (!fileLoc)
99 return WalkResult::advance();
100
101 std::optional<lsp::Location> sourceLoc =
102 getLocationFromLoc(uriScheme, fileLoc, workspaceRoot);
103 if (sourceLoc && (!uri || sourceLoc->uri == *uri)) {
104 location = *sourceLoc;
105 SMLoc loc = sourceMgr.FindLocForLineAndColumn(
106 sourceMgr.getMainFileID(), fileLoc.getLine(), fileLoc.getColumn());
107
108 // Use range of potential identifier starting at location, else length 1
109 // range.
110 if (location->range.end.character < std::numeric_limits<int>::max())
111 ++location->range.end.character;
112 if (loc.isValid()) {
113 SMRange range = convertTokenLocToRange(loc);
114 auto lineCol = sourceMgr.getLineAndColumn(range.End);
115 uint64_t endCharacter = std::max<uint64_t>(
116 static_cast<uint64_t>(fileLoc.getColumn()) + 1, lineCol.second - 1);
117 location->range.end.character = static_cast<int>(
118 std::min<uint64_t>(endCharacter, std::numeric_limits<int>::max()));
119 }
120 return WalkResult::interrupt();
121 }
122 return WalkResult::advance();
123 });
124 return location;
125}
126
127/// Collect all of the locations from the given MLIR location that are not
128/// contained within the given URI.
130 std::vector<lsp::Location> &locations,
131 const lsp::URIForFile &uri,
132 StringRef workspaceRoot) {
133 SetVector<Location> visitedLocs;
134 loc->walk([&](Location nestedLoc) {
135 FileLineColLoc fileLoc = dyn_cast<FileLineColLoc>(nestedLoc);
136 if (!fileLoc || !visitedLocs.insert(nestedLoc))
137 return WalkResult::advance();
138
139 std::optional<lsp::Location> sourceLoc =
140 getLocationFromLoc(uri.scheme(), fileLoc, workspaceRoot);
141 if (sourceLoc && sourceLoc->uri != uri)
142 locations.push_back(*sourceLoc);
143 return WalkResult::advance();
144 });
145}
146
147/// Returns true if the given range contains the given source location. Note
148/// that this has slightly different behavior than SMRange because it is
149/// inclusive of the end location.
150static bool contains(SMRange range, SMLoc loc) {
151 return range.Start.getPointer() <= loc.getPointer() &&
152 loc.getPointer() <= range.End.getPointer();
153}
154
155/// Returns true if the given location is contained by the definition or one of
156/// the uses of the given SMDefinition. If provided, `overlappedRange` is set to
157/// the range within `def` that the provided `loc` overlapped with.
158static bool isDefOrUse(const AsmParserState::SMDefinition &def, SMLoc loc,
159 SMRange *overlappedRange = nullptr) {
160 // Check the main definition.
161 if (contains(def.loc, loc)) {
162 if (overlappedRange)
163 *overlappedRange = def.loc;
164 return true;
165 }
166
167 // Check the uses.
168 const auto *useIt = llvm::find_if(
169 def.uses, [&](const SMRange &range) { return contains(range, loc); });
170 if (useIt != def.uses.end()) {
171 if (overlappedRange)
172 *overlappedRange = *useIt;
173 return true;
174 }
175 return false;
176}
177
178/// Given a location pointing to a result, return the result number it refers
179/// to or std::nullopt if it refers to all of the results.
180static std::optional<unsigned> getResultNumberFromLoc(SMLoc loc) {
181 // Skip all of the identifier characters.
182 auto isIdentifierChar = [](char c) {
183 return isalnum(c) || c == '%' || c == '$' || c == '.' || c == '_' ||
184 c == '-';
185 };
186 const char *curPtr = loc.getPointer();
187 while (isIdentifierChar(*curPtr))
188 ++curPtr;
189
190 // Check to see if this location indexes into the result group, via `#`. If it
191 // doesn't, we can't extract a sub result number.
192 if (*curPtr != '#')
193 return std::nullopt;
194
195 // Compute the sub result number from the remaining portion of the string.
196 const char *numberStart = ++curPtr;
197 while (llvm::isDigit(*curPtr))
198 ++curPtr;
199 StringRef numberStr(numberStart, curPtr - numberStart);
200 unsigned resultNumber = 0;
201 return numberStr.consumeInteger(10, resultNumber) ? std::optional<unsigned>()
202 : resultNumber;
203}
204
205/// Given a source location range, return the text covered by the given range.
206/// If the range is invalid, returns std::nullopt.
207static std::optional<StringRef> getTextFromRange(SMRange range) {
208 if (!range.isValid())
209 return std::nullopt;
210 const char *startPtr = range.Start.getPointer();
211 return StringRef(startPtr, range.End.getPointer() - startPtr);
212}
213
214/// Given a block and source location, print the source name of the block to the
215/// given output stream.
216static void printDefBlockName(raw_ostream &os, Block *block, SMRange loc = {}) {
217 // Try to extract a name from the source location.
218 std::optional<StringRef> text = getTextFromRange(loc);
219 if (text && text->starts_with("^")) {
220 os << *text;
221 return;
222 }
223
224 // Otherwise, we don't have a name so print the block number.
225 os << "<Block #" << block->computeBlockNumber() << ">";
226}
230}
231
232/// Convert the given MLIR diagnostic to the LSP form.
233static lsp::Diagnostic getLspDiagnoticFromDiag(llvm::SourceMgr &sourceMgr,
235 const lsp::URIForFile &uri,
236 StringRef workspaceRoot) {
237 lsp::Diagnostic lspDiag;
238 lspDiag.source = "mlir";
239
240 // Note: Right now all of the diagnostics are treated as parser issues, but
241 // some are parser and some are verifier.
242 lspDiag.category = "Parse Error";
243
244 // Try to grab a file location for this diagnostic.
245 // TODO: For simplicity, we just grab the first one. It may be likely that we
246 // will need a more interesting heuristic here.'
247 StringRef uriScheme = uri.scheme();
248 std::optional<lsp::Location> lspLocation = getLocationFromLoc(
249 sourceMgr, diag.getLocation(), uriScheme, workspaceRoot, &uri);
250 if (lspLocation)
251 lspDiag.range = lspLocation->range;
252
253 // Convert the severity for the diagnostic.
254 switch (diag.getSeverity()) {
256 llvm_unreachable("expected notes to be handled separately");
258 lspDiag.severity = llvm::lsp::DiagnosticSeverity::Warning;
259 break;
261 lspDiag.severity = llvm::lsp::DiagnosticSeverity::Error;
262 break;
264 lspDiag.severity = llvm::lsp::DiagnosticSeverity::Information;
265 break;
266 }
267 lspDiag.message = diag.str();
268
269 // Attach any notes to the main diagnostic as related information.
270 std::vector<llvm::lsp::DiagnosticRelatedInformation> relatedDiags;
271 for (Diagnostic &note : diag.getNotes()) {
272 lsp::Location noteLoc;
273 if (std::optional<lsp::Location> loc = getLocationFromLoc(
274 sourceMgr, note.getLocation(), uriScheme, workspaceRoot))
275 noteLoc = *loc;
276 else
277 noteLoc.uri = uri;
278 relatedDiags.emplace_back(noteLoc, note.str());
279 }
280 if (!relatedDiags.empty())
281 lspDiag.relatedInformation = std::move(relatedDiags);
282
283 return lspDiag;
284}
285
286//===----------------------------------------------------------------------===//
287// MLIRDocument
288//===----------------------------------------------------------------------===//
289
290namespace {
291/// This class represents all of the information pertaining to a specific MLIR
292/// document.
293struct MLIRDocument {
294 MLIRDocument(MLIRContext &context, const lsp::URIForFile &uri,
295 StringRef contents, StringRef workspaceRoot,
296 std::vector<lsp::Diagnostic> &diagnostics);
297 MLIRDocument(const MLIRDocument &) = delete;
298 MLIRDocument &operator=(const MLIRDocument &) = delete;
299
300 //===--------------------------------------------------------------------===//
301 // Definitions and References
302 //===--------------------------------------------------------------------===//
303
304 void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos,
305 std::vector<lsp::Location> &locations);
306 void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos,
307 std::vector<lsp::Location> &references);
308
309 //===--------------------------------------------------------------------===//
310 // Hover
311 //===--------------------------------------------------------------------===//
312
313 std::optional<lsp::Hover> findHover(const lsp::URIForFile &uri,
314 const lsp::Position &hoverPos);
315 std::optional<lsp::Hover>
316 buildHoverForOperation(SMRange hoverRange,
317 const AsmParserState::OperationDefinition &op);
318 lsp::Hover buildHoverForOperationResult(SMRange hoverRange, Operation *op,
319 unsigned resultStart,
320 unsigned resultEnd, SMLoc posLoc);
321 lsp::Hover buildHoverForBlock(SMRange hoverRange,
322 const AsmParserState::BlockDefinition &block);
323 lsp::Hover
324 buildHoverForBlockArgument(SMRange hoverRange, BlockArgument arg,
325 const AsmParserState::BlockDefinition &block);
326
327 lsp::Hover buildHoverForAttributeAlias(
328 SMRange hoverRange, const AsmParserState::AttributeAliasDefinition &attr);
329 lsp::Hover
330 buildHoverForTypeAlias(SMRange hoverRange,
331 const AsmParserState::TypeAliasDefinition &type);
332
333 //===--------------------------------------------------------------------===//
334 // Document Symbols
335 //===--------------------------------------------------------------------===//
336
337 void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols);
338 void findDocumentSymbols(Operation *op,
339 std::vector<lsp::DocumentSymbol> &symbols);
340
341 //===--------------------------------------------------------------------===//
342 // Code Completion
343 //===--------------------------------------------------------------------===//
344
345 lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri,
346 const lsp::Position &completePos,
347 const DialectRegistry &registry);
348
349 //===--------------------------------------------------------------------===//
350 // Code Action
351 //===--------------------------------------------------------------------===//
352
353 void getCodeActionForDiagnostic(const lsp::URIForFile &uri,
354 lsp::Position &pos, StringRef severity,
355 StringRef message,
356 std::vector<llvm::lsp::TextEdit> &edits);
357
358 //===--------------------------------------------------------------------===//
359 // Bytecode
360 //===--------------------------------------------------------------------===//
361
362 llvm::Expected<lsp::MLIRConvertBytecodeResult> convertToBytecode();
363
364 //===--------------------------------------------------------------------===//
365 // Fields
366 //===--------------------------------------------------------------------===//
367
368 /// The high level parser state used to find definitions and references within
369 /// the source file.
370 AsmParserState asmState;
371
372 /// The container for the IR parsed from the input file.
373 Block parsedIR;
374
375 /// A collection of external resources, which we want to propagate up to the
376 /// user.
377 FallbackAsmResourceMap fallbackResourceMap;
378
379 /// The source manager containing the contents of the input file.
380 llvm::SourceMgr sourceMgr;
381
382 /// The workspace root of the server.
383 std::string workspaceRoot;
384};
385} // namespace
386
387MLIRDocument::MLIRDocument(MLIRContext &context, const lsp::URIForFile &uri,
388 StringRef contents, StringRef workspaceRoot,
389 std::vector<lsp::Diagnostic> &diagnostics)
390 : workspaceRoot(workspaceRoot.str()) {
391 ScopedDiagnosticHandler handler(&context, [&](Diagnostic &diag) {
392 diagnostics.push_back(
393 getLspDiagnoticFromDiag(sourceMgr, diag, uri, workspaceRoot));
394 });
395
396 // Try to parsed the given IR string.
397 auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file());
398 if (!memBuffer) {
399 llvm::lsp::Logger::error("Failed to create memory buffer for file",
400 uri.file());
401 return;
402 }
403
404 ParserConfig config(&context, /*verifyAfterParse=*/true,
405 &fallbackResourceMap);
406 sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc());
407 if (failed(parseAsmSourceFile(sourceMgr, &parsedIR, config, &asmState))) {
408 // If parsing failed, clear out any of the current state.
409 parsedIR.clear();
410 asmState = AsmParserState();
411 fallbackResourceMap = FallbackAsmResourceMap();
412 return;
413 }
414}
415
416//===----------------------------------------------------------------------===//
417// MLIRDocument: Definitions and References
418//===----------------------------------------------------------------------===//
419
420void MLIRDocument::getLocationsOf(const lsp::URIForFile &uri,
421 const lsp::Position &defPos,
422 std::vector<lsp::Location> &locations) {
423 SMLoc posLoc = defPos.getAsSMLoc(sourceMgr);
424
425 // Functor used to check if an SM definition contains the position.
426 auto containsPosition = [&](const AsmParserState::SMDefinition &def) {
427 if (!isDefOrUse(def, posLoc))
428 return false;
429 locations.emplace_back(uri, sourceMgr, def.loc);
430 return true;
431 };
432
433 // Check all definitions related to operations.
434 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) {
435 if (contains(op.loc, posLoc))
436 return collectLocationsFromLoc(op.op->getLoc(), locations, uri,
437 workspaceRoot);
438 for (const auto &result : op.resultGroups)
439 if (containsPosition(result.definition))
440 return collectLocationsFromLoc(op.op->getLoc(), locations, uri,
441 workspaceRoot);
442 for (const auto &symUse : op.symbolUses) {
443 if (contains(symUse, posLoc)) {
444 locations.emplace_back(uri, sourceMgr, op.loc);
445 return collectLocationsFromLoc(op.op->getLoc(), locations, uri,
446 workspaceRoot);
447 }
448 }
449 }
450
451 // Check all definitions related to blocks.
452 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) {
453 if (containsPosition(block.definition))
454 return;
455 for (const AsmParserState::SMDefinition &arg : block.arguments)
456 if (containsPosition(arg))
457 return;
458 }
459
460 // Check all alias definitions.
461 for (const AsmParserState::AttributeAliasDefinition &attr :
462 asmState.getAttributeAliasDefs()) {
463 if (containsPosition(attr.definition))
464 return;
465 }
466 for (const AsmParserState::TypeAliasDefinition &type :
467 asmState.getTypeAliasDefs()) {
468 if (containsPosition(type.definition))
469 return;
470 }
471}
472
473void MLIRDocument::findReferencesOf(const lsp::URIForFile &uri,
474 const lsp::Position &pos,
475 std::vector<lsp::Location> &references) {
476 // Functor used to append all of the definitions/uses of the given SM
477 // definition to the reference list.
478 auto appendSMDef = [&](const AsmParserState::SMDefinition &def) {
479 references.emplace_back(uri, sourceMgr, def.loc);
480 for (const SMRange &use : def.uses)
481 references.emplace_back(uri, sourceMgr, use);
482 };
483
484 SMLoc posLoc = pos.getAsSMLoc(sourceMgr);
485
486 // Check all definitions related to operations.
487 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) {
488 if (contains(op.loc, posLoc)) {
489 for (const auto &result : op.resultGroups)
490 appendSMDef(result.definition);
491 for (const auto &symUse : op.symbolUses)
492 if (contains(symUse, posLoc))
493 references.emplace_back(uri, sourceMgr, symUse);
494 return;
495 }
496 for (const auto &result : op.resultGroups)
497 if (isDefOrUse(result.definition, posLoc))
498 return appendSMDef(result.definition);
499 for (const auto &symUse : op.symbolUses) {
500 if (!contains(symUse, posLoc))
501 continue;
502 for (const auto &symUse : op.symbolUses)
503 references.emplace_back(uri, sourceMgr, symUse);
504 return;
505 }
506 }
507
508 // Check all definitions related to blocks.
509 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) {
510 if (isDefOrUse(block.definition, posLoc))
511 return appendSMDef(block.definition);
512
513 for (const AsmParserState::SMDefinition &arg : block.arguments)
514 if (isDefOrUse(arg, posLoc))
515 return appendSMDef(arg);
516 }
517
518 // Check all alias definitions.
519 for (const AsmParserState::AttributeAliasDefinition &attr :
520 asmState.getAttributeAliasDefs()) {
521 if (isDefOrUse(attr.definition, posLoc))
522 return appendSMDef(attr.definition);
523 }
524 for (const AsmParserState::TypeAliasDefinition &type :
525 asmState.getTypeAliasDefs()) {
526 if (isDefOrUse(type.definition, posLoc))
527 return appendSMDef(type.definition);
528 }
529}
530
531//===----------------------------------------------------------------------===//
532// MLIRDocument: Hover
533//===----------------------------------------------------------------------===//
534
535std::optional<lsp::Hover>
536MLIRDocument::findHover(const lsp::URIForFile &uri,
537 const lsp::Position &hoverPos) {
538 SMLoc posLoc = hoverPos.getAsSMLoc(sourceMgr);
539 SMRange hoverRange;
540
541 // Check for Hovers on operations and results.
542 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) {
543 // Check if the position points at this operation.
544 if (contains(op.loc, posLoc))
545 return buildHoverForOperation(op.loc, op);
546
547 // Check if the position points at the symbol name.
548 for (auto &use : op.symbolUses)
549 if (contains(use, posLoc))
550 return buildHoverForOperation(use, op);
551
552 // Check if the position points at a result group.
553 for (unsigned i = 0, e = op.resultGroups.size(); i < e; ++i) {
554 const auto &result = op.resultGroups[i];
555 if (!isDefOrUse(result.definition, posLoc, &hoverRange))
556 continue;
557
558 // Get the range of results covered by the over position.
559 unsigned resultStart = result.startIndex;
560 unsigned resultEnd = (i == e - 1) ? op.op->getNumResults()
561 : op.resultGroups[i + 1].startIndex;
562 return buildHoverForOperationResult(hoverRange, op.op, resultStart,
563 resultEnd, posLoc);
564 }
565 }
566
567 // Check to see if the hover is over a block argument.
568 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) {
569 if (isDefOrUse(block.definition, posLoc, &hoverRange))
570 return buildHoverForBlock(hoverRange, block);
571
572 for (const auto &arg : llvm::enumerate(block.arguments)) {
573 if (!isDefOrUse(arg.value(), posLoc, &hoverRange))
574 continue;
575
576 return buildHoverForBlockArgument(
577 hoverRange, block.block->getArgument(arg.index()), block);
578 }
579 }
580
581 // Check to see if the hover is over an alias.
582 for (const AsmParserState::AttributeAliasDefinition &attr :
583 asmState.getAttributeAliasDefs()) {
584 if (isDefOrUse(attr.definition, posLoc, &hoverRange))
585 return buildHoverForAttributeAlias(hoverRange, attr);
586 }
587 for (const AsmParserState::TypeAliasDefinition &type :
588 asmState.getTypeAliasDefs()) {
589 if (isDefOrUse(type.definition, posLoc, &hoverRange))
590 return buildHoverForTypeAlias(hoverRange, type);
591 }
592
593 return std::nullopt;
594}
595
596std::optional<lsp::Hover> MLIRDocument::buildHoverForOperation(
597 SMRange hoverRange, const AsmParserState::OperationDefinition &op) {
598 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
599 llvm::raw_string_ostream os(hover.contents.value);
600
601 // Add the operation name to the hover.
602 os << "\"" << op.op->getName() << "\"";
603 if (SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(op.op))
604 os << " : " << symbol.getVisibility() << " @" << symbol.getName() << "";
605 os << "\n\n";
606
607 os << "Generic Form:\n\n```mlir\n";
608
609 op.op->print(os, OpPrintingFlags()
610 .printGenericOpForm()
611 .elideLargeElementsAttrs()
612 .skipRegions());
613 os << "\n```\n";
614
615 return hover;
616}
617
618lsp::Hover MLIRDocument::buildHoverForOperationResult(SMRange hoverRange,
619 Operation *op,
620 unsigned resultStart,
621 unsigned resultEnd,
622 SMLoc posLoc) {
623 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
624 llvm::raw_string_ostream os(hover.contents.value);
625
626 // Add the parent operation name to the hover.
627 os << "Operation: \"" << op->getName() << "\"\n\n";
628
629 // Check to see if the location points to a specific result within the
630 // group.
631 if (std::optional<unsigned> resultNumber = getResultNumberFromLoc(posLoc)) {
632 if ((resultStart + *resultNumber) < resultEnd) {
633 resultStart += *resultNumber;
634 resultEnd = resultStart + 1;
635 }
636 }
637
638 // Add the range of results and their types to the hover info.
639 if ((resultStart + 1) == resultEnd) {
640 os << "Result #" << resultStart << "\n\n"
641 << "Type: `" << op->getResult(resultStart).getType() << "`\n\n";
642 } else {
643 os << "Result #[" << resultStart << ", " << (resultEnd - 1) << "]\n\n"
644 << "Types: ";
645 llvm::interleaveComma(
646 op->getResults().slice(resultStart, resultEnd), os,
647 [&](Value result) { os << "`" << result.getType() << "`"; });
648 }
649
650 return hover;
651}
652
653lsp::Hover
654MLIRDocument::buildHoverForBlock(SMRange hoverRange,
655 const AsmParserState::BlockDefinition &block) {
656 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
657 llvm::raw_string_ostream os(hover.contents.value);
658
659 // Print the given block to the hover output stream.
660 auto printBlockToHover = [&](Block *newBlock) {
661 if (const auto *def = asmState.getBlockDef(newBlock))
662 printDefBlockName(os, *def);
663 else
664 printDefBlockName(os, newBlock);
665 };
666
667 // Display the parent operation, block number, predecessors, and successors.
668 os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n"
669 << "Block #" << block.block->computeBlockNumber() << "\n\n";
670 if (!block.block->hasNoPredecessors()) {
671 os << "Predecessors: ";
672 llvm::interleaveComma(block.block->getPredecessors(), os,
673 printBlockToHover);
674 os << "\n\n";
675 }
676 if (!block.block->hasNoSuccessors()) {
677 os << "Successors: ";
678 llvm::interleaveComma(block.block->getSuccessors(), os, printBlockToHover);
679 os << "\n\n";
680 }
681
682 return hover;
683}
684
685lsp::Hover MLIRDocument::buildHoverForBlockArgument(
686 SMRange hoverRange, BlockArgument arg,
687 const AsmParserState::BlockDefinition &block) {
688 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
689 llvm::raw_string_ostream os(hover.contents.value);
690
691 // Display the parent operation, block, the argument number, and the type.
692 os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n"
693 << "Block: ";
694 printDefBlockName(os, block);
695 os << "\n\nArgument #" << arg.getArgNumber() << "\n\n"
696 << "Type: `" << arg.getType() << "`\n\n";
697
698 return hover;
699}
700
701lsp::Hover MLIRDocument::buildHoverForAttributeAlias(
702 SMRange hoverRange, const AsmParserState::AttributeAliasDefinition &attr) {
703 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
704 llvm::raw_string_ostream os(hover.contents.value);
705
706 os << "Attribute Alias: \"" << attr.name << "\n\n";
707 os << "Value: ```mlir\n" << attr.value << "\n```\n\n";
708
709 return hover;
710}
711
712lsp::Hover MLIRDocument::buildHoverForTypeAlias(
713 SMRange hoverRange, const AsmParserState::TypeAliasDefinition &type) {
714 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
715 llvm::raw_string_ostream os(hover.contents.value);
716
717 os << "Type Alias: \"" << type.name << "\n\n";
718 os << "Value: ```mlir\n" << type.value << "\n```\n\n";
719
720 return hover;
721}
722
723//===----------------------------------------------------------------------===//
724// MLIRDocument: Document Symbols
725//===----------------------------------------------------------------------===//
726
727void MLIRDocument::findDocumentSymbols(
728 std::vector<lsp::DocumentSymbol> &symbols) {
729 for (Operation &op : parsedIR)
730 findDocumentSymbols(&op, symbols);
731}
732
733void MLIRDocument::findDocumentSymbols(
734 Operation *op, std::vector<lsp::DocumentSymbol> &symbols) {
735 std::vector<lsp::DocumentSymbol> *childSymbols = &symbols;
736
737 // Check for the source information of this operation.
738 if (const AsmParserState::OperationDefinition *def = asmState.getOpDef(op)) {
739 // If this operation defines a symbol, record it.
740 if (SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(op)) {
741 symbols.emplace_back(symbol.getName(),
742 isa<FunctionOpInterface>(op)
743 ? llvm::lsp::SymbolKind::Function
744 : llvm::lsp::SymbolKind::Class,
745 lsp::Range(sourceMgr, def->scopeLoc),
746 lsp::Range(sourceMgr, def->loc));
747 childSymbols = &symbols.back().children;
748
749 } else if (op->hasTrait<OpTrait::SymbolTable>()) {
750 // Otherwise, if this is a symbol table push an anonymous document symbol.
751 symbols.emplace_back("<" + op->getName().getStringRef() + ">",
752 llvm::lsp::SymbolKind::Namespace,
753 llvm::lsp::Range(sourceMgr, def->scopeLoc),
754 llvm::lsp::Range(sourceMgr, def->loc));
755 childSymbols = &symbols.back().children;
756 }
757 }
758
759 // Recurse into the regions of this operation.
760 if (!op->getNumRegions())
761 return;
762 for (Region &region : op->getRegions())
763 for (Operation &childOp : region.getOps())
764 findDocumentSymbols(&childOp, *childSymbols);
765}
766
767//===----------------------------------------------------------------------===//
768// MLIRDocument: Code Completion
769//===----------------------------------------------------------------------===//
770
771namespace {
772class LSPCodeCompleteContext : public AsmParserCodeCompleteContext {
773public:
774 LSPCodeCompleteContext(SMLoc completeLoc, lsp::CompletionList &completionList,
775 MLIRContext *ctx)
776 : AsmParserCodeCompleteContext(completeLoc),
777 completionList(completionList), ctx(ctx) {}
778
779 /// Signal code completion for a dialect name, with an optional prefix.
780 void completeDialectName(StringRef prefix) final {
781 for (StringRef dialect : ctx->getAvailableDialects()) {
782 llvm::lsp::CompletionItem item(prefix + dialect,
783 llvm::lsp::CompletionItemKind::Module,
784 /*sortText=*/"3");
785 item.detail = "dialect";
786 completionList.items.emplace_back(item);
787 }
788 }
790
791 /// Signal code completion for an operation name within the given dialect.
792 void completeOperationName(StringRef dialectName) final {
793 Dialect *dialect = ctx->getOrLoadDialect(dialectName);
794 if (!dialect)
795 return;
796
797 for (const auto &op : ctx->getRegisteredOperations()) {
798 if (&op.getDialect() != dialect)
799 continue;
800
801 llvm::lsp::CompletionItem item(
802 op.getStringRef().drop_front(dialectName.size() + 1),
803 llvm::lsp::CompletionItemKind::Field,
804 /*sortText=*/"1");
805 item.detail = "operation";
806 completionList.items.emplace_back(item);
807 }
808 }
809
810 /// Append the given SSA value as a code completion result for SSA value
811 /// completions.
812 void appendSSAValueCompletion(StringRef name, std::string typeData) final {
813 // Check if we need to insert the `%` or not.
814 bool stripPrefix = getCodeCompleteLoc().getPointer()[-1] == '%';
815
816 llvm::lsp::CompletionItem item(name,
817 llvm::lsp::CompletionItemKind::Variable);
818 if (stripPrefix)
819 item.insertText = name.drop_front(1).str();
820 item.detail = std::move(typeData);
821 completionList.items.emplace_back(item);
822 }
823
824 /// Append the given block as a code completion result for block name
825 /// completions.
826 void appendBlockCompletion(StringRef name) final {
827 // Check if we need to insert the `^` or not.
828 bool stripPrefix = getCodeCompleteLoc().getPointer()[-1] == '^';
829
830 llvm::lsp::CompletionItem item(name, llvm::lsp::CompletionItemKind::Field);
831 if (stripPrefix)
832 item.insertText = name.drop_front(1).str();
833 completionList.items.emplace_back(item);
834 }
835
836 /// Signal a completion for the given expected token.
837 void completeExpectedTokens(ArrayRef<StringRef> tokens, bool optional) final {
838 for (StringRef token : tokens) {
839 llvm::lsp::CompletionItem item(token,
840 llvm::lsp::CompletionItemKind::Keyword,
841 /*sortText=*/"0");
842 item.detail = optional ? "optional" : "";
843 completionList.items.emplace_back(item);
844 }
845 }
846
847 /// Signal a completion for an attribute.
848 void completeAttribute(const llvm::StringMap<Attribute> &aliases) override {
849 appendSimpleCompletions({"affine_set", "affine_map", "dense",
850 "dense_resource", "false", "loc", "sparse", "true",
851 "unit"},
852 llvm::lsp::CompletionItemKind::Field,
853 /*sortText=*/"1");
854
855 completeDialectName("#");
856 completeAliases(aliases, "#");
857 }
858 void completeDialectAttributeOrAlias(
859 const llvm::StringMap<Attribute> &aliases) override {
860 completeDialectName();
861 completeAliases(aliases);
862 }
863
864 /// Signal a completion for a type.
865 void completeType(const llvm::StringMap<Type> &aliases) override {
866 // Handle the various builtin types.
867 appendSimpleCompletions({"memref", "tensor", "complex", "tuple", "vector",
868 "bf16", "f16", "f32", "f64", "f80", "f128",
869 "index", "none"},
870 llvm::lsp::CompletionItemKind::Field,
871 /*sortText=*/"1");
872
873 // Handle the builtin integer types.
874 for (StringRef type : {"i", "si", "ui"}) {
875 llvm::lsp::CompletionItem item(type + "<N>",
876 llvm::lsp::CompletionItemKind::Field,
877 /*sortText=*/"1");
878 item.insertText = type.str();
879 completionList.items.emplace_back(item);
880 }
881
882 // Insert completions for dialect types and aliases.
883 completeDialectName("!");
884 completeAliases(aliases, "!");
885 }
886 void
887 completeDialectTypeOrAlias(const llvm::StringMap<Type> &aliases) override {
888 completeDialectName();
889 completeAliases(aliases);
890 }
891
892 /// Add completion results for the given set of aliases.
893 template <typename T>
894 void completeAliases(const llvm::StringMap<T> &aliases,
895 StringRef prefix = "") {
896 for (const auto &alias : aliases) {
897 llvm::lsp::CompletionItem item(prefix + alias.getKey(),
898 llvm::lsp::CompletionItemKind::Field,
899 /*sortText=*/"2");
900 llvm::raw_string_ostream(item.detail) << "alias: " << alias.getValue();
901 completionList.items.emplace_back(item);
902 }
903 }
904
905 /// Add a set of simple completions that all have the same kind.
906 void appendSimpleCompletions(ArrayRef<StringRef> completions,
907 llvm::lsp::CompletionItemKind kind,
908 StringRef sortText = "") {
909 for (StringRef completion : completions)
910 completionList.items.emplace_back(completion, kind, sortText);
911 }
912
913private:
914 lsp::CompletionList &completionList;
915 MLIRContext *ctx;
916};
917} // namespace
918
919lsp::CompletionList
920MLIRDocument::getCodeCompletion(const lsp::URIForFile &uri,
921 const lsp::Position &completePos,
922 const DialectRegistry &registry) {
923 SMLoc posLoc = completePos.getAsSMLoc(sourceMgr);
924 if (!posLoc.isValid())
925 return lsp::CompletionList();
926
927 // To perform code completion, we run another parse of the module with the
928 // code completion context provided.
929 MLIRContext tmpContext(registry, MLIRContext::Threading::DISABLED);
930 tmpContext.allowUnregisteredDialects();
931 lsp::CompletionList completionList;
932 LSPCodeCompleteContext lspCompleteContext(posLoc, completionList,
933 &tmpContext);
934
935 Block tmpIR;
936 AsmParserState tmpState;
937 (void)parseAsmSourceFile(sourceMgr, &tmpIR, &tmpContext, &tmpState,
938 &lspCompleteContext);
939 return completionList;
940}
941
942//===----------------------------------------------------------------------===//
943// MLIRDocument: Code Action
944//===----------------------------------------------------------------------===//
945
946void MLIRDocument::getCodeActionForDiagnostic(
947 const lsp::URIForFile &uri, lsp::Position &pos, StringRef severity,
948 StringRef message, std::vector<llvm::lsp::TextEdit> &edits) {
949 // Ignore diagnostics that print the current operation. These are always
950 // enabled for the language server, but not generally during normal
951 // parsing/verification.
952 if (message.starts_with("see current operation: "))
953 return;
954
955 // Get the start of the line containing the diagnostic.
956 const auto &buffer = sourceMgr.getBufferInfo(sourceMgr.getMainFileID());
957 const char *lineStart = buffer.getPointerForLineNumber(pos.line + 1);
958 if (!lineStart)
959 return;
960 StringRef line(lineStart, pos.character);
961
962 // Add a text edit for adding an expected-* diagnostic check for this
963 // diagnostic.
964 llvm::lsp::TextEdit edit;
965 edit.range = lsp::Range(lsp::Position(pos.line, 0));
966
967 // Use the indent of the current line for the expected-* diagnostic.
968 size_t indent = line.find_first_not_of(' ');
969 if (indent == StringRef::npos)
970 indent = line.size();
971
972 edit.newText.append(indent, ' ');
973 llvm::raw_string_ostream(edit.newText)
974 << "// expected-" << severity << " @below {{" << message << "}}\n";
975 edits.emplace_back(std::move(edit));
976}
977
978//===----------------------------------------------------------------------===//
979// MLIRDocument: Bytecode
980//===----------------------------------------------------------------------===//
981
982llvm::Expected<lsp::MLIRConvertBytecodeResult>
983MLIRDocument::convertToBytecode() {
984 // TODO: We currently require a single top-level operation, but this could
985 // conceptually be relaxed.
986 if (!llvm::hasSingleElement(parsedIR)) {
987 if (parsedIR.empty()) {
988 return llvm::make_error<llvm::lsp::LSPError>(
989 "expected a single and valid top-level operation, please ensure "
990 "there are no errors",
991 llvm::lsp::ErrorCode::RequestFailed);
992 }
993 return llvm::make_error<llvm::lsp::LSPError>(
994 "expected a single top-level operation",
995 llvm::lsp::ErrorCode::RequestFailed);
996 }
997
998 lsp::MLIRConvertBytecodeResult result;
999 {
1000 BytecodeWriterConfig writerConfig(fallbackResourceMap);
1001
1002 std::string rawBytecodeBuffer;
1003 llvm::raw_string_ostream os(rawBytecodeBuffer);
1004 // No desired bytecode version set, so no need to check for error.
1005 (void)writeBytecodeToFile(&parsedIR.front(), os, writerConfig);
1006 result.output = llvm::encodeBase64(rawBytecodeBuffer);
1007 }
1008 return result;
1009}
1010
1011//===----------------------------------------------------------------------===//
1012// MLIRTextFileChunk
1013//===----------------------------------------------------------------------===//
1014
1015namespace {
1016/// This class represents a single chunk of an MLIR text file.
1017struct MLIRTextFileChunk {
1018 MLIRTextFileChunk(MLIRContext &context, uint64_t lineOffset,
1019 const lsp::URIForFile &uri, StringRef contents,
1020 StringRef workspaceRoot,
1021 std::vector<lsp::Diagnostic> &diagnostics)
1022 : lineOffset(lineOffset),
1023 document(context, uri, contents, workspaceRoot, diagnostics) {}
1024
1025 /// Adjust the line number of the given range to anchor at the beginning of
1026 /// the file, instead of the beginning of this chunk.
1027 void adjustLocForChunkOffset(lsp::Range &range) {
1028 adjustLocForChunkOffset(range.start);
1029 adjustLocForChunkOffset(range.end);
1030 }
1031 /// Adjust the line number of the given position to anchor at the beginning of
1032 /// the file, instead of the beginning of this chunk.
1033 void adjustLocForChunkOffset(lsp::Position &pos) { pos.line += lineOffset; }
1034
1035 /// The line offset of this chunk from the beginning of the file.
1036 uint64_t lineOffset;
1037 /// The document referred to by this chunk.
1038 MLIRDocument document;
1039};
1040} // namespace
1041
1042//===----------------------------------------------------------------------===//
1043// MLIRTextFile
1044//===----------------------------------------------------------------------===//
1045
1046namespace {
1047/// This class represents a text file containing one or more MLIR documents.
1048class MLIRTextFile {
1049public:
1050 MLIRTextFile(const lsp::URIForFile &uri, StringRef fileContents,
1051 int64_t version, lsp::DialectRegistryFn registryFn,
1052 StringRef workspaceRoot,
1053 std::vector<lsp::Diagnostic> &diagnostics);
1054
1055 /// Return the current version of this text file.
1056 int64_t getVersion() const { return version; }
1057
1058 //===--------------------------------------------------------------------===//
1059 // LSP Queries
1060 //===--------------------------------------------------------------------===//
1061
1062 void getLocationsOf(const lsp::URIForFile &uri, lsp::Position defPos,
1063 std::vector<lsp::Location> &locations);
1064 void findReferencesOf(const lsp::URIForFile &uri, lsp::Position pos,
1065 std::vector<lsp::Location> &references);
1066 std::optional<lsp::Hover> findHover(const lsp::URIForFile &uri,
1067 lsp::Position hoverPos);
1068 void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols);
1069 lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri,
1070 lsp::Position completePos);
1071 void getCodeActions(const lsp::URIForFile &uri, const lsp::Range &pos,
1072 const lsp::CodeActionContext &context,
1073 std::vector<lsp::CodeAction> &actions);
1074 llvm::Expected<lsp::MLIRConvertBytecodeResult> convertToBytecode();
1075
1076private:
1077 /// Find the MLIR document that contains the given position, and update the
1078 /// position to be anchored at the start of the found chunk instead of the
1079 /// beginning of the file.
1080 MLIRTextFileChunk &getChunkFor(lsp::Position &pos);
1081
1082 /// The context used to hold the state contained by the parsed document.
1083 MLIRContext context;
1084
1085 /// The full string contents of the file.
1086 std::string contents;
1087
1088 /// The version of this file.
1089 int64_t version;
1090
1091 /// The number of lines in the file.
1092 int64_t totalNumLines = 0;
1093
1094 /// The chunks of this file. The order of these chunks is the order in which
1095 /// they appear in the text file.
1096 std::vector<std::unique_ptr<MLIRTextFileChunk>> chunks;
1097};
1098} // namespace
1099
1100MLIRTextFile::MLIRTextFile(const lsp::URIForFile &uri, StringRef fileContents,
1101 int64_t version, lsp::DialectRegistryFn registryFn,
1102 StringRef workspaceRoot,
1103 std::vector<lsp::Diagnostic> &diagnostics)
1104 : context(registryFn(uri), MLIRContext::Threading::DISABLED),
1105 contents(fileContents.str()), version(version) {
1106 context.allowUnregisteredDialects();
1107
1108 // Split the file into separate MLIR documents.
1109 SmallVector<StringRef, 8> subContents;
1110 StringRef(contents).split(subContents, kDefaultSplitMarker);
1111 chunks.emplace_back(std::make_unique<MLIRTextFileChunk>(
1112 context, /*lineOffset=*/0, uri, subContents.front(), workspaceRoot,
1113 diagnostics));
1114
1115 uint64_t lineOffset = subContents.front().count('\n');
1116 for (StringRef docContents : llvm::drop_begin(subContents)) {
1117 unsigned currentNumDiags = diagnostics.size();
1118 auto chunk = std::make_unique<MLIRTextFileChunk>(
1119 context, lineOffset, uri, docContents, workspaceRoot, diagnostics);
1120 lineOffset += docContents.count('\n');
1121
1122 // Adjust locations used in diagnostics to account for the offset from the
1123 // beginning of the file.
1124 for (lsp::Diagnostic &diag :
1125 llvm::drop_begin(diagnostics, currentNumDiags)) {
1126 chunk->adjustLocForChunkOffset(diag.range);
1127
1128 if (!diag.relatedInformation)
1129 continue;
1130 for (auto &it : *diag.relatedInformation)
1131 if (it.location.uri == uri)
1132 chunk->adjustLocForChunkOffset(it.location.range);
1133 }
1134 chunks.emplace_back(std::move(chunk));
1135 }
1136 totalNumLines = lineOffset;
1137}
1138
1139void MLIRTextFile::getLocationsOf(const lsp::URIForFile &uri,
1140 lsp::Position defPos,
1141 std::vector<lsp::Location> &locations) {
1142 MLIRTextFileChunk &chunk = getChunkFor(defPos);
1143 chunk.document.getLocationsOf(uri, defPos, locations);
1144
1145 // Adjust any locations within this file for the offset of this chunk.
1146 if (chunk.lineOffset == 0)
1147 return;
1148 for (lsp::Location &loc : locations)
1149 if (loc.uri == uri)
1150 chunk.adjustLocForChunkOffset(loc.range);
1151}
1152
1153void MLIRTextFile::findReferencesOf(const lsp::URIForFile &uri,
1154 lsp::Position pos,
1155 std::vector<lsp::Location> &references) {
1156 MLIRTextFileChunk &chunk = getChunkFor(pos);
1157 chunk.document.findReferencesOf(uri, pos, references);
1158
1159 // Adjust any locations within this file for the offset of this chunk.
1160 if (chunk.lineOffset == 0)
1161 return;
1162 for (lsp::Location &loc : references)
1163 if (loc.uri == uri)
1164 chunk.adjustLocForChunkOffset(loc.range);
1165}
1166
1167std::optional<lsp::Hover> MLIRTextFile::findHover(const lsp::URIForFile &uri,
1168 lsp::Position hoverPos) {
1169 MLIRTextFileChunk &chunk = getChunkFor(hoverPos);
1170 std::optional<lsp::Hover> hoverInfo = chunk.document.findHover(uri, hoverPos);
1171
1172 // Adjust any locations within this file for the offset of this chunk.
1173 if (chunk.lineOffset != 0 && hoverInfo && hoverInfo->range)
1174 chunk.adjustLocForChunkOffset(*hoverInfo->range);
1175 return hoverInfo;
1176}
1177
1178void MLIRTextFile::findDocumentSymbols(
1179 std::vector<lsp::DocumentSymbol> &symbols) {
1180 if (chunks.size() == 1)
1181 return chunks.front()->document.findDocumentSymbols(symbols);
1182
1183 // If there are multiple chunks in this file, we create top-level symbols for
1184 // each chunk.
1185 for (unsigned i = 0, e = chunks.size(); i < e; ++i) {
1186 MLIRTextFileChunk &chunk = *chunks[i];
1187 lsp::Position startPos(chunk.lineOffset);
1188 lsp::Position endPos((i == e - 1) ? totalNumLines - 1
1189 : chunks[i + 1]->lineOffset);
1190 lsp::DocumentSymbol symbol("<file-split-" + Twine(i) + ">",
1191 llvm::lsp::SymbolKind::Namespace,
1192 /*range=*/lsp::Range(startPos, endPos),
1193 /*selectionRange=*/lsp::Range(startPos));
1194 chunk.document.findDocumentSymbols(symbol.children);
1195
1196 // Fixup the locations of document symbols within this chunk.
1197 if (i != 0) {
1198 SmallVector<lsp::DocumentSymbol *> symbolsToFix;
1199 for (lsp::DocumentSymbol &childSymbol : symbol.children)
1200 symbolsToFix.push_back(&childSymbol);
1201
1202 while (!symbolsToFix.empty()) {
1203 lsp::DocumentSymbol *symbol = symbolsToFix.pop_back_val();
1204 chunk.adjustLocForChunkOffset(symbol->range);
1205 chunk.adjustLocForChunkOffset(symbol->selectionRange);
1206
1207 for (lsp::DocumentSymbol &childSymbol : symbol->children)
1208 symbolsToFix.push_back(&childSymbol);
1209 }
1210 }
1211
1212 // Push the symbol for this chunk.
1213 symbols.emplace_back(std::move(symbol));
1214 }
1215}
1216
1217lsp::CompletionList MLIRTextFile::getCodeCompletion(const lsp::URIForFile &uri,
1218 lsp::Position completePos) {
1219 MLIRTextFileChunk &chunk = getChunkFor(completePos);
1220 lsp::CompletionList completionList = chunk.document.getCodeCompletion(
1221 uri, completePos, context.getDialectRegistry());
1222
1223 // Adjust any completion locations.
1224 for (llvm::lsp::CompletionItem &item : completionList.items) {
1225 if (item.textEdit)
1226 chunk.adjustLocForChunkOffset(item.textEdit->range);
1227 for (llvm::lsp::TextEdit &edit : item.additionalTextEdits)
1228 chunk.adjustLocForChunkOffset(edit.range);
1229 }
1230 return completionList;
1231}
1232
1233void MLIRTextFile::getCodeActions(const lsp::URIForFile &uri,
1234 const lsp::Range &pos,
1235 const lsp::CodeActionContext &context,
1236 std::vector<lsp::CodeAction> &actions) {
1237 // Create actions for any diagnostics in this file.
1238 for (auto &diag : context.diagnostics) {
1239 if (diag.source != "mlir")
1240 continue;
1241 lsp::Position diagPos = diag.range.start;
1242 MLIRTextFileChunk &chunk = getChunkFor(diagPos);
1243
1244 // Add a new code action that inserts a "expected" diagnostic check.
1245 lsp::CodeAction action;
1246 action.title = "Add expected-* diagnostic checks";
1247 action.kind = lsp::CodeAction::kQuickFix.str();
1248
1249 StringRef severity;
1250 switch (diag.severity) {
1251 case llvm::lsp::DiagnosticSeverity::Error:
1252 severity = "error";
1253 break;
1254 case llvm::lsp::DiagnosticSeverity::Warning:
1255 severity = "warning";
1256 break;
1257 default:
1258 continue;
1259 }
1260
1261 // Get edits for the diagnostic.
1262 std::vector<llvm::lsp::TextEdit> edits;
1263 chunk.document.getCodeActionForDiagnostic(uri, diagPos, severity,
1264 diag.message, edits);
1265
1266 // Walk the related diagnostics, this is how we encode notes.
1267 if (diag.relatedInformation) {
1268 for (auto &noteDiag : *diag.relatedInformation) {
1269 if (noteDiag.location.uri != uri)
1270 continue;
1271 diagPos = noteDiag.location.range.start;
1272 diagPos.line -= chunk.lineOffset;
1273 chunk.document.getCodeActionForDiagnostic(uri, diagPos, "note",
1274 noteDiag.message, edits);
1275 }
1276 }
1277 // Fixup the locations for any edits.
1278 for (llvm::lsp::TextEdit &edit : edits)
1279 chunk.adjustLocForChunkOffset(edit.range);
1280
1281 action.edit.emplace();
1282 action.edit->changes[uri.uri().str()] = std::move(edits);
1283 action.diagnostics = {diag};
1284
1285 actions.emplace_back(std::move(action));
1286 }
1287}
1288
1289llvm::Expected<lsp::MLIRConvertBytecodeResult>
1290MLIRTextFile::convertToBytecode() {
1291 // Bail out if there is more than one chunk, bytecode wants a single module.
1292 if (chunks.size() != 1) {
1293 return llvm::make_error<llvm::lsp::LSPError>(
1294 "unexpected split file, please remove all `// -----`",
1295 llvm::lsp::ErrorCode::RequestFailed);
1296 }
1297 return chunks.front()->document.convertToBytecode();
1298}
1299
1300MLIRTextFileChunk &MLIRTextFile::getChunkFor(lsp::Position &pos) {
1301 if (chunks.size() == 1)
1302 return *chunks.front();
1303
1304 // Search for the first chunk with a greater line offset, the previous chunk
1305 // is the one that contains `pos`.
1306 auto it = llvm::upper_bound(
1307 chunks, pos, [](const lsp::Position &pos, const auto &chunk) {
1308 return static_cast<uint64_t>(pos.line) < chunk->lineOffset;
1309 });
1310 MLIRTextFileChunk &chunk = it == chunks.end() ? *chunks.back() : **(--it);
1311 pos.line -= chunk.lineOffset;
1312 return chunk;
1313}
1314
1315//===----------------------------------------------------------------------===//
1316// MLIRServer::Impl
1317//===----------------------------------------------------------------------===//
1318
1321
1322 /// The registry factory for containing dialects that can be recognized in
1323 /// parsed .mlir files.
1325
1326 /// The files held by the server, mapped by their URI file name.
1327 llvm::StringMap<std::unique_ptr<MLIRTextFile>> files;
1328
1329 /// The workspace root of the server.
1330 std::string workspaceRoot;
1331};
1332
1333//===----------------------------------------------------------------------===//
1334// MLIRServer
1335//===----------------------------------------------------------------------===//
1336
1338 : impl(std::make_unique<Impl>(registryFn)) {}
1340
1342 const URIForFile &uri, StringRef contents, int64_t version,
1343 std::vector<llvm::lsp::Diagnostic> &diagnostics) {
1344 impl->files[uri.file()] =
1345 std::make_unique<MLIRTextFile>(uri, contents, version, impl->registryFn,
1346 impl->workspaceRoot, diagnostics);
1347}
1348
1349std::optional<int64_t> lsp::MLIRServer::removeDocument(const URIForFile &uri) {
1350 auto it = impl->files.find(uri.file());
1351 if (it == impl->files.end())
1352 return std::nullopt;
1353
1354 int64_t version = it->second->getVersion();
1355 impl->files.erase(it);
1356 return version;
1357}
1358
1360 const URIForFile &uri, const Position &defPos,
1361 std::vector<llvm::lsp::Location> &locations) {
1362 auto fileIt = impl->files.find(uri.file());
1363 if (fileIt != impl->files.end())
1364 fileIt->second->getLocationsOf(uri, defPos, locations);
1365}
1366
1368 const URIForFile &uri, const Position &pos,
1369 std::vector<llvm::lsp::Location> &references) {
1370 auto fileIt = impl->files.find(uri.file());
1371 if (fileIt != impl->files.end())
1372 fileIt->second->findReferencesOf(uri, pos, references);
1373}
1374
1375std::optional<lsp::Hover> lsp::MLIRServer::findHover(const URIForFile &uri,
1376 const Position &hoverPos) {
1377 auto fileIt = impl->files.find(uri.file());
1378 if (fileIt != impl->files.end())
1379 return fileIt->second->findHover(uri, hoverPos);
1380 return std::nullopt;
1381}
1382
1384 const URIForFile &uri, std::vector<DocumentSymbol> &symbols) {
1385 auto fileIt = impl->files.find(uri.file());
1386 if (fileIt != impl->files.end())
1387 fileIt->second->findDocumentSymbols(symbols);
1388}
1389
1390lsp::CompletionList
1392 const Position &completePos) {
1393 auto fileIt = impl->files.find(uri.file());
1394 if (fileIt != impl->files.end())
1395 return fileIt->second->getCodeCompletion(uri, completePos);
1396 return CompletionList();
1397}
1398
1399void lsp::MLIRServer::getCodeActions(const URIForFile &uri, const Range &pos,
1400 const CodeActionContext &context,
1401 std::vector<CodeAction> &actions) {
1402 auto fileIt = impl->files.find(uri.file());
1403 if (fileIt != impl->files.end())
1404 fileIt->second->getCodeActions(uri, pos, context, actions);
1405}
1406
1409 MLIRContext tempContext(impl->registryFn(uri));
1410 tempContext.allowUnregisteredDialects();
1411
1412 // Collect any errors during parsing.
1413 std::string errorMsg;
1414 ScopedDiagnosticHandler diagHandler(
1415 &tempContext,
1416 [&](mlir::Diagnostic &diag) { errorMsg += diag.str() + "\n"; });
1417
1418 // Handling for external resources, which we want to propagate up to the user.
1419 FallbackAsmResourceMap fallbackResourceMap;
1420
1421 // Setup the parser config.
1422 ParserConfig parserConfig(&tempContext, /*verifyAfterParse=*/true,
1423 &fallbackResourceMap);
1424
1425 // Try to parse the given source file.
1426 Block parsedBlock;
1427 if (failed(parseSourceFile(uri.file(), &parsedBlock, parserConfig))) {
1428 return llvm::make_error<llvm::lsp::LSPError>(
1429 "failed to parse bytecode source file: " + errorMsg,
1430 llvm::lsp::ErrorCode::RequestFailed);
1431 }
1432
1433 // TODO: We currently expect a single top-level operation, but this could
1434 // conceptually be relaxed.
1435 if (!llvm::hasSingleElement(parsedBlock)) {
1436 return llvm::make_error<llvm::lsp::LSPError>(
1437 "expected bytecode to contain a single top-level operation",
1438 llvm::lsp::ErrorCode::RequestFailed);
1439 }
1440
1441 // Print the module to a buffer.
1443 {
1444 // Extract the top-level op so that aliases get printed.
1445 // FIXME: We should be able to enable aliases without having to do this!
1446 OwningOpRef<Operation *> topOp = &parsedBlock.front();
1447 topOp->remove();
1448
1449 AsmState state(*topOp, OpPrintingFlags().enableDebugInfo().assumeVerified(),
1450 /*locationMap=*/nullptr, &fallbackResourceMap);
1451
1452 llvm::raw_string_ostream os(result.output);
1453 topOp->print(os, state);
1454 }
1455 return std::move(result);
1456}
1457
1460 auto fileIt = impl->files.find(uri.file());
1461 if (fileIt == impl->files.end()) {
1462 return llvm::make_error<llvm::lsp::LSPError>(
1463 "language server does not contain an entry for this source file",
1464 llvm::lsp::ErrorCode::RequestFailed);
1465 }
1466 return fileIt->second->convertToBytecode();
1467}
1468
1470 impl->workspaceRoot = root.str();
1471}
static std::optional< unsigned > getResultNumberFromLoc(SMLoc loc)
Given a location pointing to a result, return the result number it refers to or std::nullopt if it re...
static std::optional< StringRef > getTextFromRange(SMRange range)
Given a source location range, return the text covered by the given range.
static std::optional< lsp::Location > getLocationFromLoc(StringRef uriScheme, FileLineColLoc loc, StringRef workspaceRoot)
Returns a language server location from the given MLIR file location.
static bool isDefOrUse(const AsmParserState::SMDefinition &def, SMLoc loc, SMRange *overlappedRange=nullptr)
Returns true if the given location is contained by the definition or one of the uses of the given SMD...
static bool contains(SMRange range, SMLoc loc)
Returns true if the given range contains the given source location.
static void collectLocationsFromLoc(Location loc, std::vector< lsp::Location > &locations, const lsp::URIForFile &uri, StringRef workspaceRoot)
Collect all of the locations from the given MLIR location that are not contained within the given URI...
static lsp::Diagnostic getLspDiagnoticFromDiag(llvm::SourceMgr &sourceMgr, Diagnostic &diag, const lsp::URIForFile &uri, StringRef workspaceRoot)
Convert the given MLIR diagnostic to the LSP form.
static void printDefBlockName(raw_ostream &os, Block *block, SMRange loc={})
Given a block and source location, print the source name of the block to the given output stream.
static SMRange convertTokenLocToRange(SMLoc loc)
Returns the range of a lexical token given a SMLoc corresponding to the start of an token location.
static std::optional< int > convertFileLocPosition(unsigned value)
Convert an MLIR one-based file position to a zero-based LSP position.
static std::string diag(const llvm::Value &value)
This class represents state from a parsed MLIR textual format string.
iterator_range< AttributeDefIterator > getAttributeAliasDefs() const
Return a range of the AttributeAliasDefinitions held by the current parser state.
iterator_range< BlockDefIterator > getBlockDefs() const
Return a range of the BlockDefinitions held by the current parser state.
const OperationDefinition * getOpDef(Operation *op) const
Return the definition for the given operation, or nullptr if the given operation does not have a defi...
const BlockDefinition * getBlockDef(Block *block) const
Return the definition for the given block, or nullptr if the given block does not have a definition.
iterator_range< OperationDefIterator > getOpDefs() const
Return a range of the OperationDefinitions held by the current parser state.
iterator_range< TypeDefIterator > getTypeAliasDefs() const
Return a range of the TypeAliasDefinitions held by the current parser state.
This class provides management for the lifetime of the state used when printing the IR.
Definition AsmState.h:542
unsigned getArgNumber() const
Returns the number of this argument.
Definition Value.h:318
Block represents an ordered list of Operations.
Definition Block.h:33
bool empty()
Definition Block.h:172
BlockArgument getArgument(unsigned i)
Definition Block.h:153
bool hasNoSuccessors()
Returns true if this blocks has no successors.
Definition Block.h:272
iterator_range< pred_iterator > getPredecessors()
Definition Block.h:264
Operation & front()
Definition Block.h:177
SuccessorRange getSuccessors()
Definition Block.h:294
bool hasNoPredecessors()
Return true if this block has no predecessors.
Definition Block.h:269
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
unsigned computeBlockNumber()
Compute the position of this block within its parent region using an O(N) linear scan.
Definition Block.cpp:144
This class contains all of the information necessary to report a diagnostic to the DiagnosticEngine.
A fallback map containing external resources not explicitly handled by another parser/printer.
Definition AsmState.h:421
An instance of this location represents a tuple of file, line number, and column number.
Definition Location.h:174
unsigned getLine() const
Definition Location.cpp:173
StringAttr getFilename() const
Definition Location.cpp:169
unsigned getColumn() const
Definition Location.cpp:175
WalkResult walk(function_ref< WalkResult(Location)> walkFn)
Walk all of the locations nested directly under, and including, the current.
Definition Location.cpp:124
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
T * getOrLoadDialect()
Get (or create) a dialect for the given derived dialect type.
ArrayRef< RegisteredOperationName > getRegisteredOperations()
Return a sorted array containing the information about all registered operations.
const DialectRegistry & getDialectRegistry()
Return the dialect registry associated with this context.
std::vector< StringRef > getAvailableDialects()
Return information about all available dialects in the registry in this context.
void allowUnregisteredDialects(bool allow=true)
Enables creating operations in unregistered dialects.
Set of flags used to control the behavior of the various IR print methods (e.g.
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
Definition Operation.h:237
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:801
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:726
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
void print(raw_ostream &os, const OpPrintingFlags &flags={})
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:729
result_range getResults()
Definition Operation.h:440
This class acts as an owning reference to an op, and will automatically destroy the held op on destru...
Definition OwningOpRef.h:29
This class represents a configuration for the MLIR assembly parser.
Definition AsmState.h:469
This diagnostic handler is a simple RAII class that registers and erases a diagnostic handler on a gi...
Type getType() const
Return the type of this value.
Definition Value.h:105
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
void addOrUpdateDocument(const URIForFile &uri, StringRef contents, int64_t version, std::vector< Diagnostic > &diagnostics)
Add or update the document, with the provided version, at the given URI.
std::optional< int64_t > removeDocument(const URIForFile &uri)
Remove the document with the given uri.
void findReferencesOf(const URIForFile &uri, const Position &pos, std::vector< Location > &references)
Find all references of the object pointed at by the given position.
void getLocationsOf(const URIForFile &uri, const Position &defPos, std::vector< Location > &locations)
Return the locations of the object pointed at by the given position.
std::optional< Hover > findHover(const URIForFile &uri, const Position &hoverPos)
Find a hover description for the given hover position, or std::nullopt if one couldn't be found.
llvm::Expected< MLIRConvertBytecodeResult > convertFromBytecode(const URIForFile &uri)
Convert the given bytecode file to the textual format.
llvm::Expected< MLIRConvertBytecodeResult > convertToBytecode(const URIForFile &uri)
Convert the given textual file to the bytecode format.
void setWorkspaceRoot(StringRef root)
Set the workspace root for the server.
CompletionList getCodeCompletion(const URIForFile &uri, const Position &completePos)
Get the code completion list for the position within the given file.
void findDocumentSymbols(const URIForFile &uri, std::vector< DocumentSymbol > &symbols)
Find all of the document symbols within the given file.
MLIRServer(DialectRegistryFn registry_fn)
Construct a new server with the given dialect registry function.
void getCodeActions(const URIForFile &uri, const Range &pos, const CodeActionContext &context, std::vector< CodeAction > &actions)
Get the set of code actions within the file.
llvm::function_ref< DialectRegistry &(const llvm::lsp::URIForFile &uri)> DialectRegistryFn
SMRange convertTokenLocToRange(SMLoc loc, StringRef identifierChars="")
Returns the range of a lexical token given a SMLoc corresponding to the start of an token location.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
Include the generated interface declarations.
const char *const kDefaultSplitMarker
LogicalResult parseAsmSourceFile(const llvm::SourceMgr &sourceMgr, Block *block, const ParserConfig &config, AsmParserState *asmState=nullptr, AsmParserCodeCompleteContext *codeCompleteContext=nullptr)
This parses the file specified by the indicated SourceMgr and appends parsed operations to the given ...
Definition Parser.cpp:2978
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
LogicalResult parseSourceFile(const llvm::SourceMgr &sourceMgr, Block *block, const ParserConfig &config, LocationAttr *sourceFileLoc=nullptr)
This parses the file specified by the indicated SourceMgr and appends parsed operations to the given ...
Definition Parser.cpp:38
LogicalResult writeBytecodeToFile(Operation *op, raw_ostream &os, const BytecodeWriterConfig &config={})
Write the bytecode for the given operation to the provided output stream.
This class represents the result of converting between MLIR's bytecode and textual format.
Definition Protocol.h:48
std::string workspaceRoot
The workspace root of the server.
lsp::DialectRegistryFn registryFn
The registry factory for containing dialects that can be recognized in parsed .mlir files.
llvm::StringMap< std::unique_ptr< MLIRTextFile > > files
The files held by the server, mapped by their URI file name.
Impl(lsp::DialectRegistryFn registryFn)
StringRef name
The name of the attribute alias.
Attribute value
The value of the alias.
This class represents the information for a block definition within the input file.
Block * block
The block representing this definition.
SMDefinition definition
The source location for the block, i.e.
Operation * op
The operation representing this definition.
This class represents a definition within the source manager, containing it's defining location and l...
SmallVector< SMRange > uses
The source location of all uses of the definition.
SMRange loc
The source location of the definition.
StringRef name
The name of the attribute alias.
Represents a range (offset, size, and stride) where each element of the triple may be dynamic or stat...