CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
1451 lines
55 KiB
C++
1451 lines
55 KiB
C++
//===- MLIRServer.cpp - MLIR Generic Language Server ----------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "MLIRServer.h"
|
|
#include "Protocol.h"
|
|
#include "mlir/AsmParser/AsmParser.h"
|
|
#include "mlir/AsmParser/AsmParserState.h"
|
|
#include "mlir/AsmParser/CodeComplete.h"
|
|
#include "mlir/Bytecode/BytecodeWriter.h"
|
|
#include "mlir/IR/Operation.h"
|
|
#include "mlir/Interfaces/FunctionInterfaces.h"
|
|
#include "mlir/Parser/Parser.h"
|
|
#include "mlir/Support/ToolUtilities.h"
|
|
#include "mlir/Tools/lsp-server-support/SourceMgrUtils.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/Base64.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/LSP/Logging.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/SourceMgr.h"
|
|
#include <optional>
|
|
|
|
using namespace mlir;
|
|
|
|
/// Returns the range of a lexical token given a SMLoc corresponding to the
|
|
/// start of an token location. The range is computed heuristically, and
|
|
/// supports identifier-like tokens, strings, etc.
|
|
static SMRange convertTokenLocToRange(SMLoc loc) {
|
|
return lsp::convertTokenLocToRange(loc, "$-.");
|
|
}
|
|
|
|
/// Returns a language server location from the given MLIR file location.
|
|
/// `uriScheme` is the scheme to use when building new uris.
|
|
static std::optional<lsp::Location>
|
|
getLocationFromLoc(StringRef uriScheme, FileLineColLoc loc,
|
|
StringRef workspaceRoot) {
|
|
StringRef filename = loc.getFilename();
|
|
SmallString<128> absPath;
|
|
// Always make the path absolute. Skip paths that start with a separator:
|
|
// prevents incorrect resolution of virtual paths used in tests on Windows.
|
|
if (!llvm::sys::path::is_absolute(filename) && !filename.starts_with("/") &&
|
|
!filename.starts_with("\\")) {
|
|
if (!workspaceRoot.empty())
|
|
llvm::sys::path::append(absPath, workspaceRoot, filename);
|
|
else
|
|
absPath = filename;
|
|
llvm::sys::fs::make_absolute(absPath);
|
|
filename = absPath;
|
|
}
|
|
|
|
llvm::Expected<lsp::URIForFile> sourceURI =
|
|
lsp::URIForFile::fromFile(filename, uriScheme);
|
|
if (!sourceURI) {
|
|
llvm::lsp::Logger::error("Failed to create URI for file `{0}`: {1}",
|
|
filename, llvm::toString(sourceURI.takeError()));
|
|
return std::nullopt;
|
|
}
|
|
|
|
lsp::Position position;
|
|
position.line = loc.getLine() - 1;
|
|
position.character = loc.getColumn() ? loc.getColumn() - 1 : 0;
|
|
return lsp::Location{*sourceURI, lsp::Range(position)};
|
|
}
|
|
|
|
/// Returns a language server location from the given MLIR location, or
|
|
/// std::nullopt if one couldn't be created. `uriScheme` is the scheme to use
|
|
/// when building new uris. `uri` is an optional additional filter that, when
|
|
/// present, is used to filter sub locations that do not share the same uri.
|
|
static std::optional<lsp::Location>
|
|
getLocationFromLoc(llvm::SourceMgr &sourceMgr, Location loc,
|
|
StringRef uriScheme, StringRef workspaceRoot,
|
|
const lsp::URIForFile *uri = nullptr) {
|
|
std::optional<lsp::Location> location;
|
|
loc->walk([&](Location nestedLoc) {
|
|
auto fileLoc = dyn_cast<FileLineColLoc>(nestedLoc);
|
|
if (!fileLoc)
|
|
return WalkResult::advance();
|
|
|
|
std::optional<lsp::Location> sourceLoc =
|
|
getLocationFromLoc(uriScheme, fileLoc, workspaceRoot);
|
|
if (sourceLoc && (!uri || sourceLoc->uri == *uri)) {
|
|
location = *sourceLoc;
|
|
SMLoc loc = sourceMgr.FindLocForLineAndColumn(
|
|
sourceMgr.getMainFileID(), fileLoc.getLine(), fileLoc.getColumn());
|
|
|
|
// Use range of potential identifier starting at location, else length 1
|
|
// range.
|
|
location->range.end.character += 1;
|
|
if (std::optional<SMRange> range = convertTokenLocToRange(loc)) {
|
|
auto lineCol = sourceMgr.getLineAndColumn(range->End);
|
|
location->range.end.character =
|
|
std::max(fileLoc.getColumn() + 1, lineCol.second - 1);
|
|
}
|
|
return WalkResult::interrupt();
|
|
}
|
|
return WalkResult::advance();
|
|
});
|
|
return location;
|
|
}
|
|
|
|
/// Collect all of the locations from the given MLIR location that are not
|
|
/// contained within the given URI.
|
|
static void collectLocationsFromLoc(Location loc,
|
|
std::vector<lsp::Location> &locations,
|
|
const lsp::URIForFile &uri,
|
|
StringRef workspaceRoot) {
|
|
SetVector<Location> visitedLocs;
|
|
loc->walk([&](Location nestedLoc) {
|
|
FileLineColLoc fileLoc = dyn_cast<FileLineColLoc>(nestedLoc);
|
|
if (!fileLoc || !visitedLocs.insert(nestedLoc))
|
|
return WalkResult::advance();
|
|
|
|
std::optional<lsp::Location> sourceLoc =
|
|
getLocationFromLoc(uri.scheme(), fileLoc, workspaceRoot);
|
|
if (sourceLoc && sourceLoc->uri != uri)
|
|
locations.push_back(*sourceLoc);
|
|
return WalkResult::advance();
|
|
});
|
|
}
|
|
|
|
/// Returns true if the given range contains the given source location. Note
|
|
/// that this has slightly different behavior than SMRange because it is
|
|
/// inclusive of the end location.
|
|
static bool contains(SMRange range, SMLoc loc) {
|
|
return range.Start.getPointer() <= loc.getPointer() &&
|
|
loc.getPointer() <= range.End.getPointer();
|
|
}
|
|
|
|
/// Returns true if the given location is contained by the definition or one of
|
|
/// the uses of the given SMDefinition. If provided, `overlappedRange` is set to
|
|
/// the range within `def` that the provided `loc` overlapped with.
|
|
static bool isDefOrUse(const AsmParserState::SMDefinition &def, SMLoc loc,
|
|
SMRange *overlappedRange = nullptr) {
|
|
// Check the main definition.
|
|
if (contains(def.loc, loc)) {
|
|
if (overlappedRange)
|
|
*overlappedRange = def.loc;
|
|
return true;
|
|
}
|
|
|
|
// Check the uses.
|
|
const auto *useIt = llvm::find_if(
|
|
def.uses, [&](const SMRange &range) { return contains(range, loc); });
|
|
if (useIt != def.uses.end()) {
|
|
if (overlappedRange)
|
|
*overlappedRange = *useIt;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Given a location pointing to a result, return the result number it refers
|
|
/// to or std::nullopt if it refers to all of the results.
|
|
static std::optional<unsigned> getResultNumberFromLoc(SMLoc loc) {
|
|
// Skip all of the identifier characters.
|
|
auto isIdentifierChar = [](char c) {
|
|
return isalnum(c) || c == '%' || c == '$' || c == '.' || c == '_' ||
|
|
c == '-';
|
|
};
|
|
const char *curPtr = loc.getPointer();
|
|
while (isIdentifierChar(*curPtr))
|
|
++curPtr;
|
|
|
|
// Check to see if this location indexes into the result group, via `#`. If it
|
|
// doesn't, we can't extract a sub result number.
|
|
if (*curPtr != '#')
|
|
return std::nullopt;
|
|
|
|
// Compute the sub result number from the remaining portion of the string.
|
|
const char *numberStart = ++curPtr;
|
|
while (llvm::isDigit(*curPtr))
|
|
++curPtr;
|
|
StringRef numberStr(numberStart, curPtr - numberStart);
|
|
unsigned resultNumber = 0;
|
|
return numberStr.consumeInteger(10, resultNumber) ? std::optional<unsigned>()
|
|
: resultNumber;
|
|
}
|
|
|
|
/// Given a source location range, return the text covered by the given range.
|
|
/// If the range is invalid, returns std::nullopt.
|
|
static std::optional<StringRef> getTextFromRange(SMRange range) {
|
|
if (!range.isValid())
|
|
return std::nullopt;
|
|
const char *startPtr = range.Start.getPointer();
|
|
return StringRef(startPtr, range.End.getPointer() - startPtr);
|
|
}
|
|
|
|
/// Given a block and source location, print the source name of the block to the
|
|
/// given output stream.
|
|
static void printDefBlockName(raw_ostream &os, Block *block, SMRange loc = {}) {
|
|
// Try to extract a name from the source location.
|
|
std::optional<StringRef> text = getTextFromRange(loc);
|
|
if (text && text->starts_with("^")) {
|
|
os << *text;
|
|
return;
|
|
}
|
|
|
|
// Otherwise, we don't have a name so print the block number.
|
|
os << "<Block #" << block->computeBlockNumber() << ">";
|
|
}
|
|
static void printDefBlockName(raw_ostream &os,
|
|
const AsmParserState::BlockDefinition &def) {
|
|
printDefBlockName(os, def.block, def.definition.loc);
|
|
}
|
|
|
|
/// Convert the given MLIR diagnostic to the LSP form.
|
|
static lsp::Diagnostic getLspDiagnoticFromDiag(llvm::SourceMgr &sourceMgr,
|
|
Diagnostic &diag,
|
|
const lsp::URIForFile &uri,
|
|
StringRef workspaceRoot) {
|
|
lsp::Diagnostic lspDiag;
|
|
lspDiag.source = "mlir";
|
|
|
|
// Note: Right now all of the diagnostics are treated as parser issues, but
|
|
// some are parser and some are verifier.
|
|
lspDiag.category = "Parse Error";
|
|
|
|
// Try to grab a file location for this diagnostic.
|
|
// TODO: For simplicity, we just grab the first one. It may be likely that we
|
|
// will need a more interesting heuristic here.'
|
|
StringRef uriScheme = uri.scheme();
|
|
std::optional<lsp::Location> lspLocation = getLocationFromLoc(
|
|
sourceMgr, diag.getLocation(), uriScheme, workspaceRoot, &uri);
|
|
if (lspLocation)
|
|
lspDiag.range = lspLocation->range;
|
|
|
|
// Convert the severity for the diagnostic.
|
|
switch (diag.getSeverity()) {
|
|
case mlir::DiagnosticSeverity::Note:
|
|
llvm_unreachable("expected notes to be handled separately");
|
|
case mlir::DiagnosticSeverity::Warning:
|
|
lspDiag.severity = llvm::lsp::DiagnosticSeverity::Warning;
|
|
break;
|
|
case mlir::DiagnosticSeverity::Error:
|
|
lspDiag.severity = llvm::lsp::DiagnosticSeverity::Error;
|
|
break;
|
|
case mlir::DiagnosticSeverity::Remark:
|
|
lspDiag.severity = llvm::lsp::DiagnosticSeverity::Information;
|
|
break;
|
|
}
|
|
lspDiag.message = diag.str();
|
|
|
|
// Attach any notes to the main diagnostic as related information.
|
|
std::vector<llvm::lsp::DiagnosticRelatedInformation> relatedDiags;
|
|
for (Diagnostic ¬e : diag.getNotes()) {
|
|
lsp::Location noteLoc;
|
|
if (std::optional<lsp::Location> loc = getLocationFromLoc(
|
|
sourceMgr, note.getLocation(), uriScheme, workspaceRoot))
|
|
noteLoc = *loc;
|
|
else
|
|
noteLoc.uri = uri;
|
|
relatedDiags.emplace_back(noteLoc, note.str());
|
|
}
|
|
if (!relatedDiags.empty())
|
|
lspDiag.relatedInformation = std::move(relatedDiags);
|
|
|
|
return lspDiag;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRDocument
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
/// This class represents all of the information pertaining to a specific MLIR
|
|
/// document.
|
|
struct MLIRDocument {
|
|
MLIRDocument(MLIRContext &context, const lsp::URIForFile &uri,
|
|
StringRef contents, StringRef workspaceRoot,
|
|
std::vector<lsp::Diagnostic> &diagnostics);
|
|
MLIRDocument(const MLIRDocument &) = delete;
|
|
MLIRDocument &operator=(const MLIRDocument &) = delete;
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
// Definitions and References
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos,
|
|
std::vector<lsp::Location> &locations);
|
|
void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos,
|
|
std::vector<lsp::Location> &references);
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
// Hover
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
std::optional<lsp::Hover> findHover(const lsp::URIForFile &uri,
|
|
const lsp::Position &hoverPos);
|
|
std::optional<lsp::Hover>
|
|
buildHoverForOperation(SMRange hoverRange,
|
|
const AsmParserState::OperationDefinition &op);
|
|
lsp::Hover buildHoverForOperationResult(SMRange hoverRange, Operation *op,
|
|
unsigned resultStart,
|
|
unsigned resultEnd, SMLoc posLoc);
|
|
lsp::Hover buildHoverForBlock(SMRange hoverRange,
|
|
const AsmParserState::BlockDefinition &block);
|
|
lsp::Hover
|
|
buildHoverForBlockArgument(SMRange hoverRange, BlockArgument arg,
|
|
const AsmParserState::BlockDefinition &block);
|
|
|
|
lsp::Hover buildHoverForAttributeAlias(
|
|
SMRange hoverRange, const AsmParserState::AttributeAliasDefinition &attr);
|
|
lsp::Hover
|
|
buildHoverForTypeAlias(SMRange hoverRange,
|
|
const AsmParserState::TypeAliasDefinition &type);
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
// Document Symbols
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols);
|
|
void findDocumentSymbols(Operation *op,
|
|
std::vector<lsp::DocumentSymbol> &symbols);
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
// Code Completion
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri,
|
|
const lsp::Position &completePos,
|
|
const DialectRegistry ®istry);
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
// Code Action
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
void getCodeActionForDiagnostic(const lsp::URIForFile &uri,
|
|
lsp::Position &pos, StringRef severity,
|
|
StringRef message,
|
|
std::vector<llvm::lsp::TextEdit> &edits);
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
// Bytecode
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
llvm::Expected<lsp::MLIRConvertBytecodeResult> convertToBytecode();
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
// Fields
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
/// The high level parser state used to find definitions and references within
|
|
/// the source file.
|
|
AsmParserState asmState;
|
|
|
|
/// The container for the IR parsed from the input file.
|
|
Block parsedIR;
|
|
|
|
/// A collection of external resources, which we want to propagate up to the
|
|
/// user.
|
|
FallbackAsmResourceMap fallbackResourceMap;
|
|
|
|
/// The source manager containing the contents of the input file.
|
|
llvm::SourceMgr sourceMgr;
|
|
|
|
/// The workspace root of the server.
|
|
std::string workspaceRoot;
|
|
};
|
|
} // namespace
|
|
|
|
MLIRDocument::MLIRDocument(MLIRContext &context, const lsp::URIForFile &uri,
|
|
StringRef contents, StringRef workspaceRoot,
|
|
std::vector<lsp::Diagnostic> &diagnostics)
|
|
: workspaceRoot(workspaceRoot.str()) {
|
|
ScopedDiagnosticHandler handler(&context, [&](Diagnostic &diag) {
|
|
diagnostics.push_back(
|
|
getLspDiagnoticFromDiag(sourceMgr, diag, uri, workspaceRoot));
|
|
});
|
|
|
|
// Try to parsed the given IR string.
|
|
auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file());
|
|
if (!memBuffer) {
|
|
llvm::lsp::Logger::error("Failed to create memory buffer for file",
|
|
uri.file());
|
|
return;
|
|
}
|
|
|
|
ParserConfig config(&context, /*verifyAfterParse=*/true,
|
|
&fallbackResourceMap);
|
|
sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc());
|
|
if (failed(parseAsmSourceFile(sourceMgr, &parsedIR, config, &asmState))) {
|
|
// If parsing failed, clear out any of the current state.
|
|
parsedIR.clear();
|
|
asmState = AsmParserState();
|
|
fallbackResourceMap = FallbackAsmResourceMap();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRDocument: Definitions and References
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void MLIRDocument::getLocationsOf(const lsp::URIForFile &uri,
|
|
const lsp::Position &defPos,
|
|
std::vector<lsp::Location> &locations) {
|
|
SMLoc posLoc = defPos.getAsSMLoc(sourceMgr);
|
|
|
|
// Functor used to check if an SM definition contains the position.
|
|
auto containsPosition = [&](const AsmParserState::SMDefinition &def) {
|
|
if (!isDefOrUse(def, posLoc))
|
|
return false;
|
|
locations.emplace_back(uri, sourceMgr, def.loc);
|
|
return true;
|
|
};
|
|
|
|
// Check all definitions related to operations.
|
|
for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) {
|
|
if (contains(op.loc, posLoc))
|
|
return collectLocationsFromLoc(op.op->getLoc(), locations, uri,
|
|
workspaceRoot);
|
|
for (const auto &result : op.resultGroups)
|
|
if (containsPosition(result.definition))
|
|
return collectLocationsFromLoc(op.op->getLoc(), locations, uri,
|
|
workspaceRoot);
|
|
for (const auto &symUse : op.symbolUses) {
|
|
if (contains(symUse, posLoc)) {
|
|
locations.emplace_back(uri, sourceMgr, op.loc);
|
|
return collectLocationsFromLoc(op.op->getLoc(), locations, uri,
|
|
workspaceRoot);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check all definitions related to blocks.
|
|
for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) {
|
|
if (containsPosition(block.definition))
|
|
return;
|
|
for (const AsmParserState::SMDefinition &arg : block.arguments)
|
|
if (containsPosition(arg))
|
|
return;
|
|
}
|
|
|
|
// Check all alias definitions.
|
|
for (const AsmParserState::AttributeAliasDefinition &attr :
|
|
asmState.getAttributeAliasDefs()) {
|
|
if (containsPosition(attr.definition))
|
|
return;
|
|
}
|
|
for (const AsmParserState::TypeAliasDefinition &type :
|
|
asmState.getTypeAliasDefs()) {
|
|
if (containsPosition(type.definition))
|
|
return;
|
|
}
|
|
}
|
|
|
|
void MLIRDocument::findReferencesOf(const lsp::URIForFile &uri,
|
|
const lsp::Position &pos,
|
|
std::vector<lsp::Location> &references) {
|
|
// Functor used to append all of the definitions/uses of the given SM
|
|
// definition to the reference list.
|
|
auto appendSMDef = [&](const AsmParserState::SMDefinition &def) {
|
|
references.emplace_back(uri, sourceMgr, def.loc);
|
|
for (const SMRange &use : def.uses)
|
|
references.emplace_back(uri, sourceMgr, use);
|
|
};
|
|
|
|
SMLoc posLoc = pos.getAsSMLoc(sourceMgr);
|
|
|
|
// Check all definitions related to operations.
|
|
for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) {
|
|
if (contains(op.loc, posLoc)) {
|
|
for (const auto &result : op.resultGroups)
|
|
appendSMDef(result.definition);
|
|
for (const auto &symUse : op.symbolUses)
|
|
if (contains(symUse, posLoc))
|
|
references.emplace_back(uri, sourceMgr, symUse);
|
|
return;
|
|
}
|
|
for (const auto &result : op.resultGroups)
|
|
if (isDefOrUse(result.definition, posLoc))
|
|
return appendSMDef(result.definition);
|
|
for (const auto &symUse : op.symbolUses) {
|
|
if (!contains(symUse, posLoc))
|
|
continue;
|
|
for (const auto &symUse : op.symbolUses)
|
|
references.emplace_back(uri, sourceMgr, symUse);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check all definitions related to blocks.
|
|
for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) {
|
|
if (isDefOrUse(block.definition, posLoc))
|
|
return appendSMDef(block.definition);
|
|
|
|
for (const AsmParserState::SMDefinition &arg : block.arguments)
|
|
if (isDefOrUse(arg, posLoc))
|
|
return appendSMDef(arg);
|
|
}
|
|
|
|
// Check all alias definitions.
|
|
for (const AsmParserState::AttributeAliasDefinition &attr :
|
|
asmState.getAttributeAliasDefs()) {
|
|
if (isDefOrUse(attr.definition, posLoc))
|
|
return appendSMDef(attr.definition);
|
|
}
|
|
for (const AsmParserState::TypeAliasDefinition &type :
|
|
asmState.getTypeAliasDefs()) {
|
|
if (isDefOrUse(type.definition, posLoc))
|
|
return appendSMDef(type.definition);
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRDocument: Hover
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
std::optional<lsp::Hover>
|
|
MLIRDocument::findHover(const lsp::URIForFile &uri,
|
|
const lsp::Position &hoverPos) {
|
|
SMLoc posLoc = hoverPos.getAsSMLoc(sourceMgr);
|
|
SMRange hoverRange;
|
|
|
|
// Check for Hovers on operations and results.
|
|
for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) {
|
|
// Check if the position points at this operation.
|
|
if (contains(op.loc, posLoc))
|
|
return buildHoverForOperation(op.loc, op);
|
|
|
|
// Check if the position points at the symbol name.
|
|
for (auto &use : op.symbolUses)
|
|
if (contains(use, posLoc))
|
|
return buildHoverForOperation(use, op);
|
|
|
|
// Check if the position points at a result group.
|
|
for (unsigned i = 0, e = op.resultGroups.size(); i < e; ++i) {
|
|
const auto &result = op.resultGroups[i];
|
|
if (!isDefOrUse(result.definition, posLoc, &hoverRange))
|
|
continue;
|
|
|
|
// Get the range of results covered by the over position.
|
|
unsigned resultStart = result.startIndex;
|
|
unsigned resultEnd = (i == e - 1) ? op.op->getNumResults()
|
|
: op.resultGroups[i + 1].startIndex;
|
|
return buildHoverForOperationResult(hoverRange, op.op, resultStart,
|
|
resultEnd, posLoc);
|
|
}
|
|
}
|
|
|
|
// Check to see if the hover is over a block argument.
|
|
for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) {
|
|
if (isDefOrUse(block.definition, posLoc, &hoverRange))
|
|
return buildHoverForBlock(hoverRange, block);
|
|
|
|
for (const auto &arg : llvm::enumerate(block.arguments)) {
|
|
if (!isDefOrUse(arg.value(), posLoc, &hoverRange))
|
|
continue;
|
|
|
|
return buildHoverForBlockArgument(
|
|
hoverRange, block.block->getArgument(arg.index()), block);
|
|
}
|
|
}
|
|
|
|
// Check to see if the hover is over an alias.
|
|
for (const AsmParserState::AttributeAliasDefinition &attr :
|
|
asmState.getAttributeAliasDefs()) {
|
|
if (isDefOrUse(attr.definition, posLoc, &hoverRange))
|
|
return buildHoverForAttributeAlias(hoverRange, attr);
|
|
}
|
|
for (const AsmParserState::TypeAliasDefinition &type :
|
|
asmState.getTypeAliasDefs()) {
|
|
if (isDefOrUse(type.definition, posLoc, &hoverRange))
|
|
return buildHoverForTypeAlias(hoverRange, type);
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<lsp::Hover> MLIRDocument::buildHoverForOperation(
|
|
SMRange hoverRange, const AsmParserState::OperationDefinition &op) {
|
|
lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
|
|
llvm::raw_string_ostream os(hover.contents.value);
|
|
|
|
// Add the operation name to the hover.
|
|
os << "\"" << op.op->getName() << "\"";
|
|
if (SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(op.op))
|
|
os << " : " << symbol.getVisibility() << " @" << symbol.getName() << "";
|
|
os << "\n\n";
|
|
|
|
os << "Generic Form:\n\n```mlir\n";
|
|
|
|
op.op->print(os, OpPrintingFlags()
|
|
.printGenericOpForm()
|
|
.elideLargeElementsAttrs()
|
|
.skipRegions());
|
|
os << "\n```\n";
|
|
|
|
return hover;
|
|
}
|
|
|
|
lsp::Hover MLIRDocument::buildHoverForOperationResult(SMRange hoverRange,
|
|
Operation *op,
|
|
unsigned resultStart,
|
|
unsigned resultEnd,
|
|
SMLoc posLoc) {
|
|
lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
|
|
llvm::raw_string_ostream os(hover.contents.value);
|
|
|
|
// Add the parent operation name to the hover.
|
|
os << "Operation: \"" << op->getName() << "\"\n\n";
|
|
|
|
// Check to see if the location points to a specific result within the
|
|
// group.
|
|
if (std::optional<unsigned> resultNumber = getResultNumberFromLoc(posLoc)) {
|
|
if ((resultStart + *resultNumber) < resultEnd) {
|
|
resultStart += *resultNumber;
|
|
resultEnd = resultStart + 1;
|
|
}
|
|
}
|
|
|
|
// Add the range of results and their types to the hover info.
|
|
if ((resultStart + 1) == resultEnd) {
|
|
os << "Result #" << resultStart << "\n\n"
|
|
<< "Type: `" << op->getResult(resultStart).getType() << "`\n\n";
|
|
} else {
|
|
os << "Result #[" << resultStart << ", " << (resultEnd - 1) << "]\n\n"
|
|
<< "Types: ";
|
|
llvm::interleaveComma(
|
|
op->getResults().slice(resultStart, resultEnd), os,
|
|
[&](Value result) { os << "`" << result.getType() << "`"; });
|
|
}
|
|
|
|
return hover;
|
|
}
|
|
|
|
lsp::Hover
|
|
MLIRDocument::buildHoverForBlock(SMRange hoverRange,
|
|
const AsmParserState::BlockDefinition &block) {
|
|
lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
|
|
llvm::raw_string_ostream os(hover.contents.value);
|
|
|
|
// Print the given block to the hover output stream.
|
|
auto printBlockToHover = [&](Block *newBlock) {
|
|
if (const auto *def = asmState.getBlockDef(newBlock))
|
|
printDefBlockName(os, *def);
|
|
else
|
|
printDefBlockName(os, newBlock);
|
|
};
|
|
|
|
// Display the parent operation, block number, predecessors, and successors.
|
|
os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n"
|
|
<< "Block #" << block.block->computeBlockNumber() << "\n\n";
|
|
if (!block.block->hasNoPredecessors()) {
|
|
os << "Predecessors: ";
|
|
llvm::interleaveComma(block.block->getPredecessors(), os,
|
|
printBlockToHover);
|
|
os << "\n\n";
|
|
}
|
|
if (!block.block->hasNoSuccessors()) {
|
|
os << "Successors: ";
|
|
llvm::interleaveComma(block.block->getSuccessors(), os, printBlockToHover);
|
|
os << "\n\n";
|
|
}
|
|
|
|
return hover;
|
|
}
|
|
|
|
lsp::Hover MLIRDocument::buildHoverForBlockArgument(
|
|
SMRange hoverRange, BlockArgument arg,
|
|
const AsmParserState::BlockDefinition &block) {
|
|
lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
|
|
llvm::raw_string_ostream os(hover.contents.value);
|
|
|
|
// Display the parent operation, block, the argument number, and the type.
|
|
os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n"
|
|
<< "Block: ";
|
|
printDefBlockName(os, block);
|
|
os << "\n\nArgument #" << arg.getArgNumber() << "\n\n"
|
|
<< "Type: `" << arg.getType() << "`\n\n";
|
|
|
|
return hover;
|
|
}
|
|
|
|
lsp::Hover MLIRDocument::buildHoverForAttributeAlias(
|
|
SMRange hoverRange, const AsmParserState::AttributeAliasDefinition &attr) {
|
|
lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
|
|
llvm::raw_string_ostream os(hover.contents.value);
|
|
|
|
os << "Attribute Alias: \"" << attr.name << "\n\n";
|
|
os << "Value: ```mlir\n" << attr.value << "\n```\n\n";
|
|
|
|
return hover;
|
|
}
|
|
|
|
lsp::Hover MLIRDocument::buildHoverForTypeAlias(
|
|
SMRange hoverRange, const AsmParserState::TypeAliasDefinition &type) {
|
|
lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
|
|
llvm::raw_string_ostream os(hover.contents.value);
|
|
|
|
os << "Type Alias: \"" << type.name << "\n\n";
|
|
os << "Value: ```mlir\n" << type.value << "\n```\n\n";
|
|
|
|
return hover;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRDocument: Document Symbols
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void MLIRDocument::findDocumentSymbols(
|
|
std::vector<lsp::DocumentSymbol> &symbols) {
|
|
for (Operation &op : parsedIR)
|
|
findDocumentSymbols(&op, symbols);
|
|
}
|
|
|
|
void MLIRDocument::findDocumentSymbols(
|
|
Operation *op, std::vector<lsp::DocumentSymbol> &symbols) {
|
|
std::vector<lsp::DocumentSymbol> *childSymbols = &symbols;
|
|
|
|
// Check for the source information of this operation.
|
|
if (const AsmParserState::OperationDefinition *def = asmState.getOpDef(op)) {
|
|
// If this operation defines a symbol, record it.
|
|
if (SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(op)) {
|
|
symbols.emplace_back(symbol.getName(),
|
|
isa<FunctionOpInterface>(op)
|
|
? llvm::lsp::SymbolKind::Function
|
|
: llvm::lsp::SymbolKind::Class,
|
|
lsp::Range(sourceMgr, def->scopeLoc),
|
|
lsp::Range(sourceMgr, def->loc));
|
|
childSymbols = &symbols.back().children;
|
|
|
|
} else if (op->hasTrait<OpTrait::SymbolTable>()) {
|
|
// Otherwise, if this is a symbol table push an anonymous document symbol.
|
|
symbols.emplace_back("<" + op->getName().getStringRef() + ">",
|
|
llvm::lsp::SymbolKind::Namespace,
|
|
llvm::lsp::Range(sourceMgr, def->scopeLoc),
|
|
llvm::lsp::Range(sourceMgr, def->loc));
|
|
childSymbols = &symbols.back().children;
|
|
}
|
|
}
|
|
|
|
// Recurse into the regions of this operation.
|
|
if (!op->getNumRegions())
|
|
return;
|
|
for (Region ®ion : op->getRegions())
|
|
for (Operation &childOp : region.getOps())
|
|
findDocumentSymbols(&childOp, *childSymbols);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRDocument: Code Completion
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
class LSPCodeCompleteContext : public AsmParserCodeCompleteContext {
|
|
public:
|
|
LSPCodeCompleteContext(SMLoc completeLoc, lsp::CompletionList &completionList,
|
|
MLIRContext *ctx)
|
|
: AsmParserCodeCompleteContext(completeLoc),
|
|
completionList(completionList), ctx(ctx) {}
|
|
|
|
/// Signal code completion for a dialect name, with an optional prefix.
|
|
void completeDialectName(StringRef prefix) final {
|
|
for (StringRef dialect : ctx->getAvailableDialects()) {
|
|
llvm::lsp::CompletionItem item(prefix + dialect,
|
|
llvm::lsp::CompletionItemKind::Module,
|
|
/*sortText=*/"3");
|
|
item.detail = "dialect";
|
|
completionList.items.emplace_back(item);
|
|
}
|
|
}
|
|
using AsmParserCodeCompleteContext::completeDialectName;
|
|
|
|
/// Signal code completion for an operation name within the given dialect.
|
|
void completeOperationName(StringRef dialectName) final {
|
|
Dialect *dialect = ctx->getOrLoadDialect(dialectName);
|
|
if (!dialect)
|
|
return;
|
|
|
|
for (const auto &op : ctx->getRegisteredOperations()) {
|
|
if (&op.getDialect() != dialect)
|
|
continue;
|
|
|
|
llvm::lsp::CompletionItem item(
|
|
op.getStringRef().drop_front(dialectName.size() + 1),
|
|
llvm::lsp::CompletionItemKind::Field,
|
|
/*sortText=*/"1");
|
|
item.detail = "operation";
|
|
completionList.items.emplace_back(item);
|
|
}
|
|
}
|
|
|
|
/// Append the given SSA value as a code completion result for SSA value
|
|
/// completions.
|
|
void appendSSAValueCompletion(StringRef name, std::string typeData) final {
|
|
// Check if we need to insert the `%` or not.
|
|
bool stripPrefix = getCodeCompleteLoc().getPointer()[-1] == '%';
|
|
|
|
llvm::lsp::CompletionItem item(name,
|
|
llvm::lsp::CompletionItemKind::Variable);
|
|
if (stripPrefix)
|
|
item.insertText = name.drop_front(1).str();
|
|
item.detail = std::move(typeData);
|
|
completionList.items.emplace_back(item);
|
|
}
|
|
|
|
/// Append the given block as a code completion result for block name
|
|
/// completions.
|
|
void appendBlockCompletion(StringRef name) final {
|
|
// Check if we need to insert the `^` or not.
|
|
bool stripPrefix = getCodeCompleteLoc().getPointer()[-1] == '^';
|
|
|
|
llvm::lsp::CompletionItem item(name, llvm::lsp::CompletionItemKind::Field);
|
|
if (stripPrefix)
|
|
item.insertText = name.drop_front(1).str();
|
|
completionList.items.emplace_back(item);
|
|
}
|
|
|
|
/// Signal a completion for the given expected token.
|
|
void completeExpectedTokens(ArrayRef<StringRef> tokens, bool optional) final {
|
|
for (StringRef token : tokens) {
|
|
llvm::lsp::CompletionItem item(token,
|
|
llvm::lsp::CompletionItemKind::Keyword,
|
|
/*sortText=*/"0");
|
|
item.detail = optional ? "optional" : "";
|
|
completionList.items.emplace_back(item);
|
|
}
|
|
}
|
|
|
|
/// Signal a completion for an attribute.
|
|
void completeAttribute(const llvm::StringMap<Attribute> &aliases) override {
|
|
appendSimpleCompletions({"affine_set", "affine_map", "dense",
|
|
"dense_resource", "false", "loc", "sparse", "true",
|
|
"unit"},
|
|
llvm::lsp::CompletionItemKind::Field,
|
|
/*sortText=*/"1");
|
|
|
|
completeDialectName("#");
|
|
completeAliases(aliases, "#");
|
|
}
|
|
void completeDialectAttributeOrAlias(
|
|
const llvm::StringMap<Attribute> &aliases) override {
|
|
completeDialectName();
|
|
completeAliases(aliases);
|
|
}
|
|
|
|
/// Signal a completion for a type.
|
|
void completeType(const llvm::StringMap<Type> &aliases) override {
|
|
// Handle the various builtin types.
|
|
appendSimpleCompletions({"memref", "tensor", "complex", "tuple", "vector",
|
|
"bf16", "f16", "f32", "f64", "f80", "f128",
|
|
"index", "none"},
|
|
llvm::lsp::CompletionItemKind::Field,
|
|
/*sortText=*/"1");
|
|
|
|
// Handle the builtin integer types.
|
|
for (StringRef type : {"i", "si", "ui"}) {
|
|
llvm::lsp::CompletionItem item(type + "<N>",
|
|
llvm::lsp::CompletionItemKind::Field,
|
|
/*sortText=*/"1");
|
|
item.insertText = type.str();
|
|
completionList.items.emplace_back(item);
|
|
}
|
|
|
|
// Insert completions for dialect types and aliases.
|
|
completeDialectName("!");
|
|
completeAliases(aliases, "!");
|
|
}
|
|
void
|
|
completeDialectTypeOrAlias(const llvm::StringMap<Type> &aliases) override {
|
|
completeDialectName();
|
|
completeAliases(aliases);
|
|
}
|
|
|
|
/// Add completion results for the given set of aliases.
|
|
template <typename T>
|
|
void completeAliases(const llvm::StringMap<T> &aliases,
|
|
StringRef prefix = "") {
|
|
for (const auto &alias : aliases) {
|
|
llvm::lsp::CompletionItem item(prefix + alias.getKey(),
|
|
llvm::lsp::CompletionItemKind::Field,
|
|
/*sortText=*/"2");
|
|
llvm::raw_string_ostream(item.detail) << "alias: " << alias.getValue();
|
|
completionList.items.emplace_back(item);
|
|
}
|
|
}
|
|
|
|
/// Add a set of simple completions that all have the same kind.
|
|
void appendSimpleCompletions(ArrayRef<StringRef> completions,
|
|
llvm::lsp::CompletionItemKind kind,
|
|
StringRef sortText = "") {
|
|
for (StringRef completion : completions)
|
|
completionList.items.emplace_back(completion, kind, sortText);
|
|
}
|
|
|
|
private:
|
|
lsp::CompletionList &completionList;
|
|
MLIRContext *ctx;
|
|
};
|
|
} // namespace
|
|
|
|
lsp::CompletionList
|
|
MLIRDocument::getCodeCompletion(const lsp::URIForFile &uri,
|
|
const lsp::Position &completePos,
|
|
const DialectRegistry ®istry) {
|
|
SMLoc posLoc = completePos.getAsSMLoc(sourceMgr);
|
|
if (!posLoc.isValid())
|
|
return lsp::CompletionList();
|
|
|
|
// To perform code completion, we run another parse of the module with the
|
|
// code completion context provided.
|
|
MLIRContext tmpContext(registry, MLIRContext::Threading::DISABLED);
|
|
tmpContext.allowUnregisteredDialects();
|
|
lsp::CompletionList completionList;
|
|
LSPCodeCompleteContext lspCompleteContext(posLoc, completionList,
|
|
&tmpContext);
|
|
|
|
Block tmpIR;
|
|
AsmParserState tmpState;
|
|
(void)parseAsmSourceFile(sourceMgr, &tmpIR, &tmpContext, &tmpState,
|
|
&lspCompleteContext);
|
|
return completionList;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRDocument: Code Action
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void MLIRDocument::getCodeActionForDiagnostic(
|
|
const lsp::URIForFile &uri, lsp::Position &pos, StringRef severity,
|
|
StringRef message, std::vector<llvm::lsp::TextEdit> &edits) {
|
|
// Ignore diagnostics that print the current operation. These are always
|
|
// enabled for the language server, but not generally during normal
|
|
// parsing/verification.
|
|
if (message.starts_with("see current operation: "))
|
|
return;
|
|
|
|
// Get the start of the line containing the diagnostic.
|
|
const auto &buffer = sourceMgr.getBufferInfo(sourceMgr.getMainFileID());
|
|
const char *lineStart = buffer.getPointerForLineNumber(pos.line + 1);
|
|
if (!lineStart)
|
|
return;
|
|
StringRef line(lineStart, pos.character);
|
|
|
|
// Add a text edit for adding an expected-* diagnostic check for this
|
|
// diagnostic.
|
|
llvm::lsp::TextEdit edit;
|
|
edit.range = lsp::Range(lsp::Position(pos.line, 0));
|
|
|
|
// Use the indent of the current line for the expected-* diagnostic.
|
|
size_t indent = line.find_first_not_of(' ');
|
|
if (indent == StringRef::npos)
|
|
indent = line.size();
|
|
|
|
edit.newText.append(indent, ' ');
|
|
llvm::raw_string_ostream(edit.newText)
|
|
<< "// expected-" << severity << " @below {{" << message << "}}\n";
|
|
edits.emplace_back(std::move(edit));
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRDocument: Bytecode
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
llvm::Expected<lsp::MLIRConvertBytecodeResult>
|
|
MLIRDocument::convertToBytecode() {
|
|
// TODO: We currently require a single top-level operation, but this could
|
|
// conceptually be relaxed.
|
|
if (!llvm::hasSingleElement(parsedIR)) {
|
|
if (parsedIR.empty()) {
|
|
return llvm::make_error<llvm::lsp::LSPError>(
|
|
"expected a single and valid top-level operation, please ensure "
|
|
"there are no errors",
|
|
llvm::lsp::ErrorCode::RequestFailed);
|
|
}
|
|
return llvm::make_error<llvm::lsp::LSPError>(
|
|
"expected a single top-level operation",
|
|
llvm::lsp::ErrorCode::RequestFailed);
|
|
}
|
|
|
|
lsp::MLIRConvertBytecodeResult result;
|
|
{
|
|
BytecodeWriterConfig writerConfig(fallbackResourceMap);
|
|
|
|
std::string rawBytecodeBuffer;
|
|
llvm::raw_string_ostream os(rawBytecodeBuffer);
|
|
// No desired bytecode version set, so no need to check for error.
|
|
(void)writeBytecodeToFile(&parsedIR.front(), os, writerConfig);
|
|
result.output = llvm::encodeBase64(rawBytecodeBuffer);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRTextFileChunk
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
/// This class represents a single chunk of an MLIR text file.
|
|
struct MLIRTextFileChunk {
|
|
MLIRTextFileChunk(MLIRContext &context, uint64_t lineOffset,
|
|
const lsp::URIForFile &uri, StringRef contents,
|
|
StringRef workspaceRoot,
|
|
std::vector<lsp::Diagnostic> &diagnostics)
|
|
: lineOffset(lineOffset),
|
|
document(context, uri, contents, workspaceRoot, diagnostics) {}
|
|
|
|
/// Adjust the line number of the given range to anchor at the beginning of
|
|
/// the file, instead of the beginning of this chunk.
|
|
void adjustLocForChunkOffset(lsp::Range &range) {
|
|
adjustLocForChunkOffset(range.start);
|
|
adjustLocForChunkOffset(range.end);
|
|
}
|
|
/// Adjust the line number of the given position to anchor at the beginning of
|
|
/// the file, instead of the beginning of this chunk.
|
|
void adjustLocForChunkOffset(lsp::Position &pos) { pos.line += lineOffset; }
|
|
|
|
/// The line offset of this chunk from the beginning of the file.
|
|
uint64_t lineOffset;
|
|
/// The document referred to by this chunk.
|
|
MLIRDocument document;
|
|
};
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRTextFile
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
/// This class represents a text file containing one or more MLIR documents.
|
|
class MLIRTextFile {
|
|
public:
|
|
MLIRTextFile(const lsp::URIForFile &uri, StringRef fileContents,
|
|
int64_t version, lsp::DialectRegistryFn registryFn,
|
|
StringRef workspaceRoot,
|
|
std::vector<lsp::Diagnostic> &diagnostics);
|
|
|
|
/// Return the current version of this text file.
|
|
int64_t getVersion() const { return version; }
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
// LSP Queries
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
void getLocationsOf(const lsp::URIForFile &uri, lsp::Position defPos,
|
|
std::vector<lsp::Location> &locations);
|
|
void findReferencesOf(const lsp::URIForFile &uri, lsp::Position pos,
|
|
std::vector<lsp::Location> &references);
|
|
std::optional<lsp::Hover> findHover(const lsp::URIForFile &uri,
|
|
lsp::Position hoverPos);
|
|
void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols);
|
|
lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri,
|
|
lsp::Position completePos);
|
|
void getCodeActions(const lsp::URIForFile &uri, const lsp::Range &pos,
|
|
const lsp::CodeActionContext &context,
|
|
std::vector<lsp::CodeAction> &actions);
|
|
llvm::Expected<lsp::MLIRConvertBytecodeResult> convertToBytecode();
|
|
|
|
private:
|
|
/// Find the MLIR document that contains the given position, and update the
|
|
/// position to be anchored at the start of the found chunk instead of the
|
|
/// beginning of the file.
|
|
MLIRTextFileChunk &getChunkFor(lsp::Position &pos);
|
|
|
|
/// The context used to hold the state contained by the parsed document.
|
|
MLIRContext context;
|
|
|
|
/// The full string contents of the file.
|
|
std::string contents;
|
|
|
|
/// The version of this file.
|
|
int64_t version;
|
|
|
|
/// The number of lines in the file.
|
|
int64_t totalNumLines = 0;
|
|
|
|
/// The chunks of this file. The order of these chunks is the order in which
|
|
/// they appear in the text file.
|
|
std::vector<std::unique_ptr<MLIRTextFileChunk>> chunks;
|
|
};
|
|
} // namespace
|
|
|
|
MLIRTextFile::MLIRTextFile(const lsp::URIForFile &uri, StringRef fileContents,
|
|
int64_t version, lsp::DialectRegistryFn registryFn,
|
|
StringRef workspaceRoot,
|
|
std::vector<lsp::Diagnostic> &diagnostics)
|
|
: context(registryFn(uri), MLIRContext::Threading::DISABLED),
|
|
contents(fileContents.str()), version(version) {
|
|
context.allowUnregisteredDialects();
|
|
|
|
// Split the file into separate MLIR documents.
|
|
SmallVector<StringRef, 8> subContents;
|
|
StringRef(contents).split(subContents, kDefaultSplitMarker);
|
|
chunks.emplace_back(std::make_unique<MLIRTextFileChunk>(
|
|
context, /*lineOffset=*/0, uri, subContents.front(), workspaceRoot,
|
|
diagnostics));
|
|
|
|
uint64_t lineOffset = subContents.front().count('\n');
|
|
for (StringRef docContents : llvm::drop_begin(subContents)) {
|
|
unsigned currentNumDiags = diagnostics.size();
|
|
auto chunk = std::make_unique<MLIRTextFileChunk>(
|
|
context, lineOffset, uri, docContents, workspaceRoot, diagnostics);
|
|
lineOffset += docContents.count('\n');
|
|
|
|
// Adjust locations used in diagnostics to account for the offset from the
|
|
// beginning of the file.
|
|
for (lsp::Diagnostic &diag :
|
|
llvm::drop_begin(diagnostics, currentNumDiags)) {
|
|
chunk->adjustLocForChunkOffset(diag.range);
|
|
|
|
if (!diag.relatedInformation)
|
|
continue;
|
|
for (auto &it : *diag.relatedInformation)
|
|
if (it.location.uri == uri)
|
|
chunk->adjustLocForChunkOffset(it.location.range);
|
|
}
|
|
chunks.emplace_back(std::move(chunk));
|
|
}
|
|
totalNumLines = lineOffset;
|
|
}
|
|
|
|
void MLIRTextFile::getLocationsOf(const lsp::URIForFile &uri,
|
|
lsp::Position defPos,
|
|
std::vector<lsp::Location> &locations) {
|
|
MLIRTextFileChunk &chunk = getChunkFor(defPos);
|
|
chunk.document.getLocationsOf(uri, defPos, locations);
|
|
|
|
// Adjust any locations within this file for the offset of this chunk.
|
|
if (chunk.lineOffset == 0)
|
|
return;
|
|
for (lsp::Location &loc : locations)
|
|
if (loc.uri == uri)
|
|
chunk.adjustLocForChunkOffset(loc.range);
|
|
}
|
|
|
|
void MLIRTextFile::findReferencesOf(const lsp::URIForFile &uri,
|
|
lsp::Position pos,
|
|
std::vector<lsp::Location> &references) {
|
|
MLIRTextFileChunk &chunk = getChunkFor(pos);
|
|
chunk.document.findReferencesOf(uri, pos, references);
|
|
|
|
// Adjust any locations within this file for the offset of this chunk.
|
|
if (chunk.lineOffset == 0)
|
|
return;
|
|
for (lsp::Location &loc : references)
|
|
if (loc.uri == uri)
|
|
chunk.adjustLocForChunkOffset(loc.range);
|
|
}
|
|
|
|
std::optional<lsp::Hover> MLIRTextFile::findHover(const lsp::URIForFile &uri,
|
|
lsp::Position hoverPos) {
|
|
MLIRTextFileChunk &chunk = getChunkFor(hoverPos);
|
|
std::optional<lsp::Hover> hoverInfo = chunk.document.findHover(uri, hoverPos);
|
|
|
|
// Adjust any locations within this file for the offset of this chunk.
|
|
if (chunk.lineOffset != 0 && hoverInfo && hoverInfo->range)
|
|
chunk.adjustLocForChunkOffset(*hoverInfo->range);
|
|
return hoverInfo;
|
|
}
|
|
|
|
void MLIRTextFile::findDocumentSymbols(
|
|
std::vector<lsp::DocumentSymbol> &symbols) {
|
|
if (chunks.size() == 1)
|
|
return chunks.front()->document.findDocumentSymbols(symbols);
|
|
|
|
// If there are multiple chunks in this file, we create top-level symbols for
|
|
// each chunk.
|
|
for (unsigned i = 0, e = chunks.size(); i < e; ++i) {
|
|
MLIRTextFileChunk &chunk = *chunks[i];
|
|
lsp::Position startPos(chunk.lineOffset);
|
|
lsp::Position endPos((i == e - 1) ? totalNumLines - 1
|
|
: chunks[i + 1]->lineOffset);
|
|
lsp::DocumentSymbol symbol("<file-split-" + Twine(i) + ">",
|
|
llvm::lsp::SymbolKind::Namespace,
|
|
/*range=*/lsp::Range(startPos, endPos),
|
|
/*selectionRange=*/lsp::Range(startPos));
|
|
chunk.document.findDocumentSymbols(symbol.children);
|
|
|
|
// Fixup the locations of document symbols within this chunk.
|
|
if (i != 0) {
|
|
SmallVector<lsp::DocumentSymbol *> symbolsToFix;
|
|
for (lsp::DocumentSymbol &childSymbol : symbol.children)
|
|
symbolsToFix.push_back(&childSymbol);
|
|
|
|
while (!symbolsToFix.empty()) {
|
|
lsp::DocumentSymbol *symbol = symbolsToFix.pop_back_val();
|
|
chunk.adjustLocForChunkOffset(symbol->range);
|
|
chunk.adjustLocForChunkOffset(symbol->selectionRange);
|
|
|
|
for (lsp::DocumentSymbol &childSymbol : symbol->children)
|
|
symbolsToFix.push_back(&childSymbol);
|
|
}
|
|
}
|
|
|
|
// Push the symbol for this chunk.
|
|
symbols.emplace_back(std::move(symbol));
|
|
}
|
|
}
|
|
|
|
lsp::CompletionList MLIRTextFile::getCodeCompletion(const lsp::URIForFile &uri,
|
|
lsp::Position completePos) {
|
|
MLIRTextFileChunk &chunk = getChunkFor(completePos);
|
|
lsp::CompletionList completionList = chunk.document.getCodeCompletion(
|
|
uri, completePos, context.getDialectRegistry());
|
|
|
|
// Adjust any completion locations.
|
|
for (llvm::lsp::CompletionItem &item : completionList.items) {
|
|
if (item.textEdit)
|
|
chunk.adjustLocForChunkOffset(item.textEdit->range);
|
|
for (llvm::lsp::TextEdit &edit : item.additionalTextEdits)
|
|
chunk.adjustLocForChunkOffset(edit.range);
|
|
}
|
|
return completionList;
|
|
}
|
|
|
|
void MLIRTextFile::getCodeActions(const lsp::URIForFile &uri,
|
|
const lsp::Range &pos,
|
|
const lsp::CodeActionContext &context,
|
|
std::vector<lsp::CodeAction> &actions) {
|
|
// Create actions for any diagnostics in this file.
|
|
for (auto &diag : context.diagnostics) {
|
|
if (diag.source != "mlir")
|
|
continue;
|
|
lsp::Position diagPos = diag.range.start;
|
|
MLIRTextFileChunk &chunk = getChunkFor(diagPos);
|
|
|
|
// Add a new code action that inserts a "expected" diagnostic check.
|
|
lsp::CodeAction action;
|
|
action.title = "Add expected-* diagnostic checks";
|
|
action.kind = lsp::CodeAction::kQuickFix.str();
|
|
|
|
StringRef severity;
|
|
switch (diag.severity) {
|
|
case llvm::lsp::DiagnosticSeverity::Error:
|
|
severity = "error";
|
|
break;
|
|
case llvm::lsp::DiagnosticSeverity::Warning:
|
|
severity = "warning";
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
// Get edits for the diagnostic.
|
|
std::vector<llvm::lsp::TextEdit> edits;
|
|
chunk.document.getCodeActionForDiagnostic(uri, diagPos, severity,
|
|
diag.message, edits);
|
|
|
|
// Walk the related diagnostics, this is how we encode notes.
|
|
if (diag.relatedInformation) {
|
|
for (auto ¬eDiag : *diag.relatedInformation) {
|
|
if (noteDiag.location.uri != uri)
|
|
continue;
|
|
diagPos = noteDiag.location.range.start;
|
|
diagPos.line -= chunk.lineOffset;
|
|
chunk.document.getCodeActionForDiagnostic(uri, diagPos, "note",
|
|
noteDiag.message, edits);
|
|
}
|
|
}
|
|
// Fixup the locations for any edits.
|
|
for (llvm::lsp::TextEdit &edit : edits)
|
|
chunk.adjustLocForChunkOffset(edit.range);
|
|
|
|
action.edit.emplace();
|
|
action.edit->changes[uri.uri().str()] = std::move(edits);
|
|
action.diagnostics = {diag};
|
|
|
|
actions.emplace_back(std::move(action));
|
|
}
|
|
}
|
|
|
|
llvm::Expected<lsp::MLIRConvertBytecodeResult>
|
|
MLIRTextFile::convertToBytecode() {
|
|
// Bail out if there is more than one chunk, bytecode wants a single module.
|
|
if (chunks.size() != 1) {
|
|
return llvm::make_error<llvm::lsp::LSPError>(
|
|
"unexpected split file, please remove all `// -----`",
|
|
llvm::lsp::ErrorCode::RequestFailed);
|
|
}
|
|
return chunks.front()->document.convertToBytecode();
|
|
}
|
|
|
|
MLIRTextFileChunk &MLIRTextFile::getChunkFor(lsp::Position &pos) {
|
|
if (chunks.size() == 1)
|
|
return *chunks.front();
|
|
|
|
// Search for the first chunk with a greater line offset, the previous chunk
|
|
// is the one that contains `pos`.
|
|
auto it = llvm::upper_bound(
|
|
chunks, pos, [](const lsp::Position &pos, const auto &chunk) {
|
|
return static_cast<uint64_t>(pos.line) < chunk->lineOffset;
|
|
});
|
|
MLIRTextFileChunk &chunk = it == chunks.end() ? *chunks.back() : **(--it);
|
|
pos.line -= chunk.lineOffset;
|
|
return chunk;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRServer::Impl
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
struct lsp::MLIRServer::Impl {
|
|
Impl(lsp::DialectRegistryFn registryFn) : registryFn(registryFn) {}
|
|
|
|
/// The registry factory for containing dialects that can be recognized in
|
|
/// parsed .mlir files.
|
|
lsp::DialectRegistryFn registryFn;
|
|
|
|
/// The files held by the server, mapped by their URI file name.
|
|
llvm::StringMap<std::unique_ptr<MLIRTextFile>> files;
|
|
|
|
/// The workspace root of the server.
|
|
std::string workspaceRoot;
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MLIRServer
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
lsp::MLIRServer::MLIRServer(lsp::DialectRegistryFn registryFn)
|
|
: impl(std::make_unique<Impl>(registryFn)) {}
|
|
lsp::MLIRServer::~MLIRServer() = default;
|
|
|
|
void lsp::MLIRServer::addOrUpdateDocument(
|
|
const URIForFile &uri, StringRef contents, int64_t version,
|
|
std::vector<llvm::lsp::Diagnostic> &diagnostics) {
|
|
impl->files[uri.file()] =
|
|
std::make_unique<MLIRTextFile>(uri, contents, version, impl->registryFn,
|
|
impl->workspaceRoot, diagnostics);
|
|
}
|
|
|
|
std::optional<int64_t> lsp::MLIRServer::removeDocument(const URIForFile &uri) {
|
|
auto it = impl->files.find(uri.file());
|
|
if (it == impl->files.end())
|
|
return std::nullopt;
|
|
|
|
int64_t version = it->second->getVersion();
|
|
impl->files.erase(it);
|
|
return version;
|
|
}
|
|
|
|
void lsp::MLIRServer::getLocationsOf(
|
|
const URIForFile &uri, const Position &defPos,
|
|
std::vector<llvm::lsp::Location> &locations) {
|
|
auto fileIt = impl->files.find(uri.file());
|
|
if (fileIt != impl->files.end())
|
|
fileIt->second->getLocationsOf(uri, defPos, locations);
|
|
}
|
|
|
|
void lsp::MLIRServer::findReferencesOf(
|
|
const URIForFile &uri, const Position &pos,
|
|
std::vector<llvm::lsp::Location> &references) {
|
|
auto fileIt = impl->files.find(uri.file());
|
|
if (fileIt != impl->files.end())
|
|
fileIt->second->findReferencesOf(uri, pos, references);
|
|
}
|
|
|
|
std::optional<lsp::Hover> lsp::MLIRServer::findHover(const URIForFile &uri,
|
|
const Position &hoverPos) {
|
|
auto fileIt = impl->files.find(uri.file());
|
|
if (fileIt != impl->files.end())
|
|
return fileIt->second->findHover(uri, hoverPos);
|
|
return std::nullopt;
|
|
}
|
|
|
|
void lsp::MLIRServer::findDocumentSymbols(
|
|
const URIForFile &uri, std::vector<DocumentSymbol> &symbols) {
|
|
auto fileIt = impl->files.find(uri.file());
|
|
if (fileIt != impl->files.end())
|
|
fileIt->second->findDocumentSymbols(symbols);
|
|
}
|
|
|
|
lsp::CompletionList
|
|
lsp::MLIRServer::getCodeCompletion(const URIForFile &uri,
|
|
const Position &completePos) {
|
|
auto fileIt = impl->files.find(uri.file());
|
|
if (fileIt != impl->files.end())
|
|
return fileIt->second->getCodeCompletion(uri, completePos);
|
|
return CompletionList();
|
|
}
|
|
|
|
void lsp::MLIRServer::getCodeActions(const URIForFile &uri, const Range &pos,
|
|
const CodeActionContext &context,
|
|
std::vector<CodeAction> &actions) {
|
|
auto fileIt = impl->files.find(uri.file());
|
|
if (fileIt != impl->files.end())
|
|
fileIt->second->getCodeActions(uri, pos, context, actions);
|
|
}
|
|
|
|
llvm::Expected<lsp::MLIRConvertBytecodeResult>
|
|
lsp::MLIRServer::convertFromBytecode(const URIForFile &uri) {
|
|
MLIRContext tempContext(impl->registryFn(uri));
|
|
tempContext.allowUnregisteredDialects();
|
|
|
|
// Collect any errors during parsing.
|
|
std::string errorMsg;
|
|
ScopedDiagnosticHandler diagHandler(
|
|
&tempContext,
|
|
[&](mlir::Diagnostic &diag) { errorMsg += diag.str() + "\n"; });
|
|
|
|
// Handling for external resources, which we want to propagate up to the user.
|
|
FallbackAsmResourceMap fallbackResourceMap;
|
|
|
|
// Setup the parser config.
|
|
ParserConfig parserConfig(&tempContext, /*verifyAfterParse=*/true,
|
|
&fallbackResourceMap);
|
|
|
|
// Try to parse the given source file.
|
|
Block parsedBlock;
|
|
if (failed(parseSourceFile(uri.file(), &parsedBlock, parserConfig))) {
|
|
return llvm::make_error<llvm::lsp::LSPError>(
|
|
"failed to parse bytecode source file: " + errorMsg,
|
|
llvm::lsp::ErrorCode::RequestFailed);
|
|
}
|
|
|
|
// TODO: We currently expect a single top-level operation, but this could
|
|
// conceptually be relaxed.
|
|
if (!llvm::hasSingleElement(parsedBlock)) {
|
|
return llvm::make_error<llvm::lsp::LSPError>(
|
|
"expected bytecode to contain a single top-level operation",
|
|
llvm::lsp::ErrorCode::RequestFailed);
|
|
}
|
|
|
|
// Print the module to a buffer.
|
|
lsp::MLIRConvertBytecodeResult result;
|
|
{
|
|
// Extract the top-level op so that aliases get printed.
|
|
// FIXME: We should be able to enable aliases without having to do this!
|
|
OwningOpRef<Operation *> topOp = &parsedBlock.front();
|
|
topOp->remove();
|
|
|
|
AsmState state(*topOp, OpPrintingFlags().enableDebugInfo().assumeVerified(),
|
|
/*locationMap=*/nullptr, &fallbackResourceMap);
|
|
|
|
llvm::raw_string_ostream os(result.output);
|
|
topOp->print(os, state);
|
|
}
|
|
return std::move(result);
|
|
}
|
|
|
|
llvm::Expected<lsp::MLIRConvertBytecodeResult>
|
|
lsp::MLIRServer::convertToBytecode(const URIForFile &uri) {
|
|
auto fileIt = impl->files.find(uri.file());
|
|
if (fileIt == impl->files.end()) {
|
|
return llvm::make_error<llvm::lsp::LSPError>(
|
|
"language server does not contain an entry for this source file",
|
|
llvm::lsp::ErrorCode::RequestFailed);
|
|
}
|
|
return fileIt->second->convertToBytecode();
|
|
}
|
|
|
|
void lsp::MLIRServer::setWorkspaceRoot(StringRef root) {
|
|
impl->workspaceRoot = root.str();
|
|
}
|