[mlir:vscode] Add support for viewing and editing a bytecode file as .mlir

This commit adds support for interacting with a (valid) bytecode file in the same
way as .mlir. This allows editing, using all of the traditional LSP features, etc. but
still using bytecode as the on-disk serialization format. Loading a bytecode file this
way will fail if the bytecode is invalid, and saving will fail if the edited .mlir is invalid.

Differential Revision: https://reviews.llvm.org/D132970
This commit is contained in:
River Riddle 2022-09-02 16:24:17 -07:00
parent 603d5a8d63
commit f7b8a70e7a
17 changed files with 573 additions and 75 deletions

View File

@ -15,6 +15,7 @@
#include "mlir/Support/LogicalResult.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Format.h"
@ -116,7 +117,16 @@ static std::string percentDecode(StringRef content) {
return result;
}
static bool isValidScheme(StringRef scheme) {
/// Return the set containing the supported URI schemes.
static StringSet<> &getSupportedSchemes() {
static StringSet<> schemes({"file", "test"});
return schemes;
}
/// Returns true if the given scheme is structurally valid, i.e. it does not
/// contain any invalid scheme characters. This does not check that the scheme
/// is actually supported.
static bool isStructurallyValidScheme(StringRef scheme) {
if (scheme.empty())
return false;
if (!llvm::isAlpha(scheme[0]))
@ -126,7 +136,8 @@ static bool isValidScheme(StringRef scheme) {
});
}
static llvm::Expected<std::string> uriFromAbsolutePath(StringRef absolutePath) {
static llvm::Expected<std::string> uriFromAbsolutePath(StringRef absolutePath,
StringRef scheme) {
std::string body;
StringRef authority;
StringRef root = llvm::sys::path::root_name(absolutePath);
@ -140,7 +151,7 @@ static llvm::Expected<std::string> uriFromAbsolutePath(StringRef absolutePath) {
}
body += llvm::sys::path::convert_to_slash(absolutePath);
std::string uri = "file:";
std::string uri = scheme.str() + ":";
if (authority.empty() && body.empty())
return uri;
@ -186,7 +197,7 @@ static llvm::Expected<std::string> parseFilePathFromURI(StringRef origUri) {
origUri);
StringRef schemeStr = uri.substr(0, pos);
std::string uriScheme = percentDecode(schemeStr);
if (!isValidScheme(uriScheme))
if (!isStructurallyValidScheme(uriScheme))
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Invalid scheme: " + schemeStr +
" (decoded: " + uriScheme + ")");
@ -204,10 +215,10 @@ static llvm::Expected<std::string> parseFilePathFromURI(StringRef origUri) {
std::string uriBody = percentDecode(uri);
// Compute the absolute path for this uri.
if (uriScheme != "file" && uriScheme != "test") {
return llvm::createStringError(
llvm::inconvertibleErrorCode(),
"mlir-lsp-server only supports 'file' URI scheme for workspace files");
if (!getSupportedSchemes().contains(uriScheme)) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"unsupported URI scheme `" + uriScheme +
"' for workspace files");
}
return getAbsolutePath(uriAuthority, uriBody);
}
@ -219,13 +230,21 @@ llvm::Expected<URIForFile> URIForFile::fromURI(StringRef uri) {
return URIForFile(std::move(*filePath), uri.str());
}
llvm::Expected<URIForFile> URIForFile::fromFile(StringRef absoluteFilepath) {
llvm::Expected<std::string> uri = uriFromAbsolutePath(absoluteFilepath);
llvm::Expected<URIForFile> URIForFile::fromFile(StringRef absoluteFilepath,
StringRef scheme) {
llvm::Expected<std::string> uri =
uriFromAbsolutePath(absoluteFilepath, scheme);
if (!uri)
return uri.takeError();
return fromURI(*uri);
}
StringRef URIForFile::scheme() const { return uri().split(':').first; }
void URIForFile::registerSupportedScheme(StringRef scheme) {
getSupportedSchemes().insert(scheme);
}
bool mlir::lsp::fromJSON(const llvm::json::Value &value, URIForFile &result,
llvm::json::Path path) {
if (Optional<StringRef> str = value.getAsString()) {

View File

@ -53,6 +53,7 @@ enum class ErrorCode {
// Defined by the protocol.
RequestCancelled = -32800,
ContentModified = -32801,
RequestFailed = -32803,
};
/// Defines how the host (editor) should sync document changes to the language
@ -103,8 +104,10 @@ public:
/// Try to build a URIForFile from the given URI string.
static llvm::Expected<URIForFile> fromURI(StringRef uri);
/// Try to build a URIForFile from the given absolute file path.
static llvm::Expected<URIForFile> fromFile(StringRef absoluteFilepath);
/// Try to build a URIForFile from the given absolute file path and optional
/// scheme.
static llvm::Expected<URIForFile> fromFile(StringRef absoluteFilepath,
StringRef scheme = "file");
/// Returns the absolute path to the file.
StringRef file() const { return filePath; }
@ -112,6 +115,9 @@ public:
/// Returns the original uri of the file.
StringRef uri() const { return uriStr; }
/// Return the scheme of the uri.
StringRef scheme() const;
explicit operator bool() const { return !filePath.empty(); }
friend bool operator==(const URIForFile &lhs, const URIForFile &rhs) {
@ -124,6 +130,11 @@ public:
return lhs.filePath < rhs.filePath;
}
/// Register a supported URI scheme. The protocol supports `file` by default,
/// so this is only necessary for any additional schemes that a server wants
/// to support.
static void registerSupportedScheme(StringRef scheme);
private:
explicit URIForFile(std::string &&filePath, std::string &&uriStr)
: filePath(std::move(filePath)), uriStr(uriStr) {}

View File

@ -2,11 +2,13 @@ add_mlir_library(MLIRLspServerLib
LSPServer.cpp
MLIRServer.cpp
MlirLspServerMain.cpp
Protocol.cpp
ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/Tools/mlir-lsp-server
LINK_LIBS PUBLIC
MLIRBytecodeWriter
MLIRIR
MLIRLspServerSupportLib
MLIRParser

View File

@ -8,9 +8,9 @@
#include "LSPServer.h"
#include "../lsp-server-support/Logging.h"
#include "../lsp-server-support/Protocol.h"
#include "../lsp-server-support/Transport.h"
#include "MLIRServer.h"
#include "Protocol.h"
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/StringMap.h"
@ -74,6 +74,14 @@ struct LSPServer {
void onCodeAction(const CodeActionParams &params,
Callback<llvm::json::Value> reply);
//===--------------------------------------------------------------------===//
// Bytecode
void onConvertFromBytecode(const MLIRConvertBytecodeParams &params,
Callback<MLIRConvertBytecodeResult> reply);
void onConvertToBytecode(const MLIRConvertBytecodeParams &params,
Callback<MLIRConvertBytecodeResult> reply);
//===--------------------------------------------------------------------===//
// Fields
//===--------------------------------------------------------------------===//
@ -254,6 +262,20 @@ void LSPServer::onCodeAction(const CodeActionParams &params,
reply(std::move(actions));
}
//===----------------------------------------------------------------------===//
// Bytecode
void LSPServer::onConvertFromBytecode(
const MLIRConvertBytecodeParams &params,
Callback<MLIRConvertBytecodeResult> reply) {
reply(server.convertFromBytecode(params.uri));
}
void LSPServer::onConvertToBytecode(const MLIRConvertBytecodeParams &params,
Callback<MLIRConvertBytecodeResult> reply) {
reply(server.convertToBytecode(params.uri));
}
//===----------------------------------------------------------------------===//
// Entry point
//===----------------------------------------------------------------------===//
@ -298,6 +320,12 @@ LogicalResult lsp::runMlirLSPServer(MLIRServer &server,
messageHandler.method("textDocument/codeAction", &lspServer,
&LSPServer::onCodeAction);
// Bytecode
messageHandler.method("mlir/convertFromBytecode", &lspServer,
&LSPServer::onConvertFromBytecode);
messageHandler.method("mlir/convertToBytecode", &lspServer,
&LSPServer::onConvertToBytecode);
// Diagnostics
lspServer.publishDiagnostics =
messageHandler.outgoingNotification<PublishDiagnosticsParams>(

View File

@ -8,21 +8,26 @@
#include "MLIRServer.h"
#include "../lsp-server-support/Logging.h"
#include "../lsp-server-support/Protocol.h"
#include "../lsp-server-support/SourceMgrUtils.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/FunctionInterfaces.h"
#include "mlir/IR/Operation.h"
#include "mlir/Parser/Parser.h"
#include "llvm/Support/Base64.h"
#include "llvm/Support/SourceMgr.h"
using namespace mlir;
/// Returns a language server location from the given MLIR file location.
static Optional<lsp::Location> getLocationFromLoc(FileLineColLoc loc) {
/// `uriScheme` is the scheme to use when building new uris.
static Optional<lsp::Location> getLocationFromLoc(StringRef uriScheme,
FileLineColLoc loc) {
llvm::Expected<lsp::URIForFile> sourceURI =
lsp::URIForFile::fromFile(loc.getFilename());
lsp::URIForFile::fromFile(loc.getFilename(), uriScheme);
if (!sourceURI) {
lsp::Logger::error("Failed to create URI for file `{0}`: {1}",
loc.getFilename(),
@ -37,18 +42,19 @@ static Optional<lsp::Location> getLocationFromLoc(FileLineColLoc loc) {
}
/// Returns a language server location from the given MLIR location, or None if
/// one couldn't be created. `uri` is an optional additional filter that, when
/// present, is used to filter sub locations that do not share the same uri.
/// 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 Optional<lsp::Location>
getLocationFromLoc(llvm::SourceMgr &sourceMgr, Location loc,
const lsp::URIForFile *uri = nullptr) {
StringRef uriScheme, const lsp::URIForFile *uri = nullptr) {
Optional<lsp::Location> location;
loc->walk([&](Location nestedLoc) {
FileLineColLoc fileLoc = nestedLoc.dyn_cast<FileLineColLoc>();
if (!fileLoc)
return WalkResult::advance();
Optional<lsp::Location> sourceLoc = getLocationFromLoc(fileLoc);
Optional<lsp::Location> sourceLoc = getLocationFromLoc(uriScheme, fileLoc);
if (sourceLoc && (!uri || sourceLoc->uri == *uri)) {
location = *sourceLoc;
SMLoc loc = sourceMgr.FindLocForLineAndColumn(
@ -80,7 +86,8 @@ static void collectLocationsFromLoc(Location loc,
if (!fileLoc || !visitedLocs.insert(nestedLoc))
return WalkResult::advance();
Optional<lsp::Location> sourceLoc = getLocationFromLoc(fileLoc);
Optional<lsp::Location> sourceLoc =
getLocationFromLoc(uri.scheme(), fileLoc);
if (sourceLoc && sourceLoc->uri != uri)
locations.push_back(*sourceLoc);
return WalkResult::advance();
@ -191,8 +198,9 @@ static lsp::Diagnostic getLspDiagnoticFromDiag(llvm::SourceMgr &sourceMgr,
// 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();
Optional<lsp::Location> lspLocation =
getLocationFromLoc(sourceMgr, diag.getLocation(), &uri);
getLocationFromLoc(sourceMgr, diag.getLocation(), uriScheme, &uri);
if (lspLocation)
lspDiag.range = lspLocation->range;
@ -217,7 +225,7 @@ static lsp::Diagnostic getLspDiagnoticFromDiag(llvm::SourceMgr &sourceMgr,
for (Diagnostic &note : diag.getNotes()) {
lsp::Location noteLoc;
if (Optional<lsp::Location> loc =
getLocationFromLoc(sourceMgr, note.getLocation()))
getLocationFromLoc(sourceMgr, note.getLocation(), uriScheme))
noteLoc = *loc;
else
noteLoc.uri = uri;
@ -294,6 +302,12 @@ struct MLIRDocument {
StringRef message,
std::vector<lsp::TextEdit> &edits);
//===--------------------------------------------------------------------===//
// Bytecode
//===--------------------------------------------------------------------===//
llvm::Expected<lsp::MLIRConvertBytecodeResult> convertToBytecode();
//===--------------------------------------------------------------------===//
// Fields
//===--------------------------------------------------------------------===//
@ -840,6 +854,35 @@ void MLIRDocument::getCodeActionForDiagnostic(
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<lsp::LSPError>(
"expected a single and valid top-level operation, please ensure "
"there are no errors",
lsp::ErrorCode::RequestFailed);
}
return llvm::make_error<lsp::LSPError>(
"expected a single top-level operation", lsp::ErrorCode::RequestFailed);
}
lsp::MLIRConvertBytecodeResult result;
{
std::string rawBytecodeBuffer;
llvm::raw_string_ostream os(rawBytecodeBuffer);
writeBytecodeToFile(&parsedIR.front(), os);
result.output = llvm::encodeBase64(rawBytecodeBuffer);
}
return result;
}
//===----------------------------------------------------------------------===//
// MLIRTextFileChunk
//===----------------------------------------------------------------------===//
@ -900,6 +943,7 @@ public:
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
@ -1115,6 +1159,17 @@ void MLIRTextFile::getCodeActions(const lsp::URIForFile &uri,
}
}
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<lsp::LSPError>(
"unexpected split file, please remove all `// -----`",
lsp::ErrorCode::RequestFailed);
}
return chunks.front()->document.convertToBytecode();
}
MLIRTextFileChunk &MLIRTextFile::getChunkFor(lsp::Position &pos) {
if (chunks.size() == 1)
return *chunks.front();
@ -1217,3 +1272,57 @@ void lsp::MLIRServer::getCodeActions(const URIForFile &uri, const Range &pos,
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->registry);
tempContext.allowUnregisteredDialects();
// Collect any errors during parsing.
std::string errorMsg;
ScopedDiagnosticHandler diagHandler(
&tempContext,
[&](mlir::Diagnostic &diag) { errorMsg += diag.str() + "\n"; });
// Try to parse the given source file.
// TODO: This won't preserve external resources or the producer, we should try
// to fix this.
Block parsedBlock;
if (failed(parseSourceFile(uri.file(), &parsedBlock, &tempContext))) {
return llvm::make_error<lsp::LSPError>(
"failed to parse bytecode source file: " + errorMsg,
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<lsp::LSPError>(
"expected bytecode to contain a single top-level operation",
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();
llvm::raw_string_ostream os(result.output);
(*topOp)->print(os, OpPrintingFlags().enableDebugInfo().assumeVerified());
}
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<lsp::LSPError>(
"language server does not contain an entry for this source file",
lsp::ErrorCode::RequestFailed);
}
return fileIt->second->convertToBytecode();
}

View File

@ -10,6 +10,7 @@
#define LIB_MLIR_TOOLS_MLIRLSPSERVER_SERVER_H_
#include "mlir/Support/LLVM.h"
#include "llvm/Support/Error.h"
#include <memory>
namespace mlir {
@ -23,6 +24,7 @@ struct Diagnostic;
struct DocumentSymbol;
struct Hover;
struct Location;
struct MLIRConvertBytecodeResult;
struct Position;
struct Range;
class URIForFile;
@ -73,6 +75,14 @@ public:
const CodeActionContext &context,
std::vector<CodeAction> &actions);
/// Convert the given bytecode file to the textual format.
llvm::Expected<MLIRConvertBytecodeResult>
convertFromBytecode(const URIForFile &uri);
/// Convert the given textual file to the bytecode format.
llvm::Expected<MLIRConvertBytecodeResult>
convertToBytecode(const URIForFile &uri);
private:
struct Impl;

View File

@ -68,6 +68,9 @@ LogicalResult mlir::MlirLspServerMain(int argc, char **argv,
llvm::sys::ChangeStdinToBinary();
JSONTransport transport(stdin, llvm::outs(), inputStyle, prettyPrint);
// Register the additionally supported URI schemes for the MLIR server.
URIForFile::registerSupportedScheme("mlir.bytecode-mlir");
// Configure the servers and start the main language server.
MLIRServer server(registry);
return runMlirLSPServer(server, transport);

View File

@ -0,0 +1,44 @@
//===--- Protocol.cpp - Language Server Protocol Implementation -----------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains the serialization code for the MLIR specific LSP structs.
//
//===----------------------------------------------------------------------===//
#include "Protocol.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
using namespace mlir;
using namespace mlir::lsp;
//===----------------------------------------------------------------------===//
// MLIRConvertBytecodeParams
//===----------------------------------------------------------------------===//
bool mlir::lsp::fromJSON(const llvm::json::Value &value,
MLIRConvertBytecodeParams &result,
llvm::json::Path path) {
llvm::json::ObjectMapper o(value, path);
return o && o.map("uri", result.uri);
}
//===----------------------------------------------------------------------===//
// MLIRConvertBytecodeResult
//===----------------------------------------------------------------------===//
llvm::json::Value mlir::lsp::toJSON(const MLIRConvertBytecodeResult &value) {
return llvm::json::Object{{"output", value.output}};
}

View File

@ -0,0 +1,59 @@
//===--- Protocol.h - Language Server Protocol Implementation ---*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains structs for LSP commands that are specific to the MLIR
// server.
//
// Each struct has a toJSON and fromJSON function, that converts between
// the struct and a JSON representation. (See JSON.h)
//
// Some structs also have operator<< serialization. This is for debugging and
// tests, and is not generally machine-readable.
//
//===----------------------------------------------------------------------===//
#ifndef LIB_MLIR_TOOLS_MLIRLSPSERVER_PROTOCOL_H_
#define LIB_MLIR_TOOLS_MLIRLSPSERVER_PROTOCOL_H_
#include "../lsp-server-support/Protocol.h"
namespace mlir {
namespace lsp {
//===----------------------------------------------------------------------===//
// MLIRConvertBytecodeParams
//===----------------------------------------------------------------------===//
/// This class represents the parameters used when converting between MLIR's
/// bytecode and textual format.
struct MLIRConvertBytecodeParams {
/// The input file containing the bytecode or textual format.
URIForFile uri;
};
/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, MLIRConvertBytecodeParams &result,
llvm::json::Path path);
//===----------------------------------------------------------------------===//
// MLIRConvertBytecodeResult
//===----------------------------------------------------------------------===//
/// This class represents the result of converting between MLIR's bytecode and
/// textual format.
struct MLIRConvertBytecodeResult {
/// The resultant output of the conversion.
std::string output;
};
/// Add support for JSON serialization.
llvm::json::Value toJSON(const MLIRConvertBytecodeResult &value);
} // namespace lsp
} // namespace mlir
#endif

View File

@ -1,13 +1,14 @@
{
"name": "vscode-mlir",
"version": "0.0.9",
"version": "0.0.10",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "vscode-mlir",
"version": "0.0.9",
"version": "0.0.10",
"dependencies": {
"base64-js": "^1.5.1",
"chokidar": "3.5.2",
"vscode-languageclient": "^8.0.2-next.5"
},
@ -137,7 +138,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [
{
"type": "github",
@ -2037,8 +2037,7 @@
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"big-integer": {
"version": "1.6.48",

View File

@ -21,6 +21,8 @@
"tablegen"
],
"activationEvents": [
"onFileSystem:mlir.bytecode-mlir",
"onCustomEditor:mlir.bytecode",
"onLanguage:mlir",
"onLanguage:pdll",
"onLanguage:tablegen"
@ -35,6 +37,7 @@
"git-clang-format": "git-clang-format"
},
"dependencies": {
"base64-js": "^1.5.1",
"chokidar": "3.5.2",
"vscode-languageclient": "^8.0.2-next.5"
},
@ -52,6 +55,18 @@
"url": "https://github.com/llvm/vscode-mlir.git"
},
"contributes": {
"customEditors": [
{
"viewType": "mlir.bytecode",
"displayName": "MLIR Bytecode",
"priority": "default",
"selector": [
{
"filenamePattern": "*.mlirbc"
}
]
}
],
"languages": [
{
"id": "mlir",
@ -60,7 +75,8 @@
"mlir"
],
"extensions": [
".mlir"
".mlir",
".mlirbc"
],
"configuration": "./language-configuration.json"
},

View File

@ -0,0 +1,170 @@
import * as base64 from 'base64-js'
import * as vscode from 'vscode'
import {MLIRContext} from '../mlirContext';
/**
* The parameters to the mlir/convert(To|From)Bytecode commands. These
* parameters are:
* - `uri`: The URI of the file to convert.
*/
type ConvertBytecodeParams = Partial<{uri : string}>;
/**
* The output of the mlir/convert(To|From)Bytecode commands:
* - `output`: The output buffer of the command, e.g. a .mlir or bytecode
* buffer.
*/
type ConvertBytecodeResult = Partial<{output : string}>;
/**
* A custom filesystem that is used to convert MLIR bytecode files to text for
* use in the editor, but still use bytecode on disk.
*/
class BytecodeFS implements vscode.FileSystemProvider {
mlirContext: MLIRContext;
constructor(mlirContext: MLIRContext) { this.mlirContext = mlirContext; }
/*
* Forward to the default filesystem for the various methods that don't need
* to understand the bytecode <-> text translation.
*/
readDirectory(uri: vscode.Uri): Thenable<[ string, vscode.FileType ][]> {
return vscode.workspace.fs.readDirectory(uri);
}
delete(uri: vscode.Uri): void {
vscode.workspace.fs.delete(uri.with({scheme : "file"}));
}
stat(uri: vscode.Uri): Thenable<vscode.FileStat> {
return vscode.workspace.fs.stat(uri.with({scheme : "file"}));
}
rename(oldUri: vscode.Uri, newUri: vscode.Uri,
options: {overwrite: boolean}): void {
vscode.workspace.fs.rename(oldUri.with({scheme : "file"}),
newUri.with({scheme : "file"}), options);
}
createDirectory(uri: vscode.Uri): void {
vscode.workspace.fs.createDirectory(uri.with({scheme : "file"}));
}
watch(_uri: vscode.Uri, _options: {
readonly recursive: boolean; readonly excludes : readonly string[]
}): vscode.Disposable {
return new vscode.Disposable(() => {});
}
private _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> =
this._emitter.event;
/*
* Read in a bytecode file, converting it to text before returning it to the
* caller.
*/
async readFile(uri: vscode.Uri): Promise<Uint8Array> {
// Try to start a language client for this file so that we can parse
// it.
const client =
await this.mlirContext.getOrActivateLanguageClient(uri, 'mlir');
if (!client) {
throw new Error(
'Failed to activate mlir language server to read bytecode');
}
// Ask the client to do the conversion.
let convertParams: ConvertBytecodeParams = {uri : uri.toString()};
try {
const result: ConvertBytecodeResult =
await client.sendRequest('mlir/convertFromBytecode', convertParams);
return new TextEncoder().encode(result.output);
} catch (e) {
vscode.window.showErrorMessage(e.message);
throw new Error(`Failed to read bytecode file: ${e}`);
}
}
/*
* Save the provided content, which contains MLIR text, as bytecode.
*/
async writeFile(uri: vscode.Uri, content: Uint8Array,
_options: {create: boolean, overwrite: boolean}) {
// Get the language client managing this file.
let client = this.mlirContext.getLanguageClient(uri, 'mlir');
if (!client) {
throw new Error(
'Failed to activate mlir language server to write bytecode');
}
// Ask the client to do the conversion.
let convertParams: ConvertBytecodeParams = {
uri : uri.toString(),
};
const result: ConvertBytecodeResult =
await client.sendRequest('mlir/convertToBytecode', convertParams);
await vscode.workspace.fs.writeFile(uri.with({scheme : "file"}),
base64.toByteArray(result.output));
}
}
/**
* A custom bytecode document for use by the custom editor provider below.
*/
class BytecodeDocument implements vscode.CustomDocument {
readonly uri: vscode.Uri;
constructor(uri: vscode.Uri) { this.uri = uri; }
dispose(): void {}
}
/**
* A custom editor provider for MLIR bytecode that allows for non-binary
* interpretation.
*/
class BytecodeEditorProvider implements
vscode.CustomReadonlyEditorProvider<BytecodeDocument> {
public async openCustomDocument(uri: vscode.Uri, _openContext: any,
_token: vscode.CancellationToken):
Promise<BytecodeDocument> {
return new BytecodeDocument(uri);
}
public async resolveCustomEditor(document: BytecodeDocument,
_webviewPanel: vscode.WebviewPanel,
_token: vscode.CancellationToken):
Promise<void> {
// Ask the user for the desired view type.
const editType = await vscode.window.showQuickPick(
[ {label : '.mlir', description : "Edit as a .mlir text file"} ],
{title : 'Select an editor for the bytecode.'},
);
// If we don't have a valid view type, just bail.
if (!editType) {
await vscode.commands.executeCommand(
'workbench.action.closeActiveEditor');
return;
}
// TODO: We should also provide a non-`.mlir` way of viewing the
// bytecode, which should also ideally have some support for invalid
// bytecode files.
// Close the active editor given that we aren't using it.
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
// Display the file using a .mlir format.
await vscode.window.showTextDocument(
document.uri.with({scheme : "mlir.bytecode-mlir"}),
{preview : true, preserveFocus : false});
}
}
/**
* Register the necessary providers for supporting MLIR bytecode.
*/
export function registerMLIRBytecodeExtensions(context: vscode.ExtensionContext,
mlirContext: MLIRContext) {
vscode.workspace.registerFileSystemProvider("mlir.bytecode-mlir",
new BytecodeFS(mlirContext));
vscode.window.registerCustomEditorProvider('mlir.bytecode',
new BytecodeEditorProvider());
}

View File

@ -0,0 +1,12 @@
import * as vscode from 'vscode';
import {MLIRContext} from '../mlirContext';
import {registerMLIRBytecodeExtensions} from './bytecodeProvider';
/**
* Register the necessary extensions for supporting MLIR.
*/
export function registerMLIRExtensions(context: vscode.ExtensionContext,
mlirContext: MLIRContext) {
registerMLIRBytecodeExtensions(context, mlirContext);
}

View File

@ -28,9 +28,8 @@ export class ViewPDLLCommand extends Command {
return;
// Check to see if a language client is active for this document.
const workspaceFolder =
vscode.workspace.getWorkspaceFolder(editor.document.uri);
const pdllClient = this.context.getLanguageClient(workspaceFolder, "pdll");
const pdllClient =
this.context.getLanguageClient(editor.document.uri, "pdll");
if (!pdllClient) {
return;
}

View File

@ -4,9 +4,9 @@ import {MLIRContext} from '../mlirContext';
import {ViewPDLLCommand} from './commands/viewOutput';
/**
* Register the necessary context and commands for PDLL.
* Register the necessary extensions for supporting PDLL.
*/
export function registerPDLLCommands(context: vscode.ExtensionContext,
mlirContext: MLIRContext) {
export function registerPDLLExtensions(context: vscode.ExtensionContext,
mlirContext: MLIRContext) {
context.subscriptions.push(new ViewPDLLCommand(mlirContext));
}

View File

@ -1,7 +1,8 @@
import * as vscode from 'vscode';
import {registerMLIRExtensions} from './MLIR/mlir';
import {MLIRContext} from './mlirContext';
import {registerPDLLCommands} from './PDLL/pdll';
import {registerPDLLExtensions} from './PDLL/pdll';
/**
* This method is called when the extension is activated. The extension is
@ -21,7 +22,8 @@ export function activate(context: vscode.ExtensionContext) {
mlirContext.dispose();
await mlirContext.activate(outputChannel);
}));
registerPDLLCommands(context, mlirContext);
registerMLIRExtensions(context, mlirContext);
registerPDLLExtensions(context, mlirContext);
mlirContext.activate(outputChannel);
}

View File

@ -25,49 +25,19 @@ class WorkspaceFolderContext implements vscode.Disposable {
export class MLIRContext implements vscode.Disposable {
subscriptions: vscode.Disposable[] = [];
workspaceFolders: Map<string, WorkspaceFolderContext> = new Map();
outputChannel: vscode.OutputChannel;
/**
* Activate the MLIR context, and start the language clients.
*/
async activate(outputChannel: vscode.OutputChannel) {
this.outputChannel = outputChannel;
// This lambda is used to lazily start language clients for the given
// document. It removes the need to pro-actively start language clients for
// every folder within the workspace and every language type we provide.
const startClientOnOpenDocument = async (document: vscode.TextDocument) => {
if (document.uri.scheme !== 'file') {
return;
}
let serverSettingName: string;
if (document.languageId === 'mlir') {
serverSettingName = 'server_path';
} else if (document.languageId === 'pdll') {
serverSettingName = 'pdll_server_path';
} else if (document.languageId === 'tablegen') {
serverSettingName = 'tablegen_server_path';
} else {
return;
}
// Resolve the workspace folder if this document is in one. We use the
// workspace folder when determining if a server needs to be started.
const uri = document.uri;
let workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
let workspaceFolderStr =
workspaceFolder ? workspaceFolder.uri.toString() : "";
// Get or create a client context for this folder.
let folderContext = this.workspaceFolders.get(workspaceFolderStr);
if (!folderContext) {
folderContext = new WorkspaceFolderContext();
this.workspaceFolders.set(workspaceFolderStr, folderContext);
}
// Start the client for this language if necessary.
if (!folderContext.clients.has(document.languageId)) {
let client = await this.activateWorkspaceFolder(
workspaceFolder, serverSettingName, document.languageId,
outputChannel);
folderContext.clients.set(document.languageId, client);
}
await this.getOrActivateLanguageClient(document.uri, document.languageId);
};
// Process any existing documents.
for (const textDoc of vscode.workspace.textDocuments) {
@ -89,6 +59,50 @@ export class MLIRContext implements vscode.Disposable {
}));
}
/**
* Open or return a language server for the given uri and language.
*/
async getOrActivateLanguageClient(uri: vscode.Uri, languageId: string):
Promise<vscodelc.LanguageClient> {
let serverSettingName: string;
if (languageId === 'mlir') {
serverSettingName = 'server_path';
} else if (languageId === 'pdll') {
serverSettingName = 'pdll_server_path';
} else if (languageId === 'tablegen') {
serverSettingName = 'tablegen_server_path';
} else {
return null;
}
// Check the scheme of the uri.
let validSchemes = [ 'file', 'mlir.bytecode-mlir' ];
if (!validSchemes.includes(uri.scheme)) {
return null;
}
// Resolve the workspace folder if this document is in one. We use the
// workspace folder when determining if a server needs to be started.
let workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
let workspaceFolderStr =
workspaceFolder ? workspaceFolder.uri.toString() : "";
// Get or create a client context for this folder.
let folderContext = this.workspaceFolders.get(workspaceFolderStr);
if (!folderContext) {
folderContext = new WorkspaceFolderContext();
this.workspaceFolders.set(workspaceFolderStr, folderContext);
}
// Start the client for this language if necessary.
let client = folderContext.clients.get(languageId);
if (!client) {
client = await this.activateWorkspaceFolder(
workspaceFolder, serverSettingName, languageId, this.outputChannel);
folderContext.clients.set(languageId, client);
}
return client;
}
/**
* Prepare a compilation database option for a server.
*/
@ -263,7 +277,7 @@ export class MLIRContext implements vscode.Disposable {
// Configure the client options.
const clientOptions: vscodelc.LanguageClientOptions = {
documentSelector : [
{scheme : 'file', language : languageName, pattern : selectorPattern}
{language : languageName, pattern : selectorPattern},
],
synchronize : {
// Notify the server about file changes to language files contained in
@ -353,11 +367,12 @@ export class MLIRContext implements vscode.Disposable {
}
/**
* Return the language client for the given language and workspace folder, or
* null if no client is active.
* Return the language client for the given language and uri, or null if no
* client is active.
*/
getLanguageClient(workspaceFolder: vscode.WorkspaceFolder,
getLanguageClient(uri: vscode.Uri,
languageName: string): vscodelc.LanguageClient {
let workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
let workspaceFolderStr =
workspaceFolder ? workspaceFolder.uri.toString() : "";
let folderContext = this.workspaceFolders.get(workspaceFolderStr);