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

River Riddle llvmlistbot at llvm.org
Fri Sep 2 18:16:21 PDT 2022


Author: River Riddle
Date: 2022-09-02T18:16:05-07:00
New Revision: f7b8a70e7a1738e0fc6574e3cf8faa4fa1f34eba

URL: https://github.com/llvm/llvm-project/commit/f7b8a70e7a1738e0fc6574e3cf8faa4fa1f34eba
DIFF: https://github.com/llvm/llvm-project/commit/f7b8a70e7a1738e0fc6574e3cf8faa4fa1f34eba.diff

LOG: [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

Added: 
    mlir/lib/Tools/mlir-lsp-server/Protocol.cpp
    mlir/lib/Tools/mlir-lsp-server/Protocol.h
    mlir/utils/vscode/src/MLIR/bytecodeProvider.ts
    mlir/utils/vscode/src/MLIR/mlir.ts

Modified: 
    mlir/lib/Tools/lsp-server-support/Protocol.cpp
    mlir/lib/Tools/lsp-server-support/Protocol.h
    mlir/lib/Tools/mlir-lsp-server/CMakeLists.txt
    mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp
    mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp
    mlir/lib/Tools/mlir-lsp-server/MLIRServer.h
    mlir/lib/Tools/mlir-lsp-server/MlirLspServerMain.cpp
    mlir/utils/vscode/package-lock.json
    mlir/utils/vscode/package.json
    mlir/utils/vscode/src/PDLL/commands/viewOutput.ts
    mlir/utils/vscode/src/PDLL/pdll.ts
    mlir/utils/vscode/src/extension.ts
    mlir/utils/vscode/src/mlirContext.ts

Removed: 
    


################################################################################
diff  --git a/mlir/lib/Tools/lsp-server-support/Protocol.cpp b/mlir/lib/Tools/lsp-server-support/Protocol.cpp
index 6c10be316b8cf..68aac74c16772 100644
--- a/mlir/lib/Tools/lsp-server-support/Protocol.cpp
+++ b/mlir/lib/Tools/lsp-server-support/Protocol.cpp
@@ -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()) {

diff  --git a/mlir/lib/Tools/lsp-server-support/Protocol.h b/mlir/lib/Tools/lsp-server-support/Protocol.h
index 9233f8b477503..1fec0984a4f45 100644
--- a/mlir/lib/Tools/lsp-server-support/Protocol.h
+++ b/mlir/lib/Tools/lsp-server-support/Protocol.h
@@ -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 @@ class URIForFile {
   /// 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 @@ class URIForFile {
   /// 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 @@ class URIForFile {
     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) {}

diff  --git a/mlir/lib/Tools/mlir-lsp-server/CMakeLists.txt b/mlir/lib/Tools/mlir-lsp-server/CMakeLists.txt
index 999005148f326..efe24dd40c702 100644
--- a/mlir/lib/Tools/mlir-lsp-server/CMakeLists.txt
+++ b/mlir/lib/Tools/mlir-lsp-server/CMakeLists.txt
@@ -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

diff  --git a/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp b/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp
index 45fc2329e9f73..da0ddf20e71c9 100644
--- a/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp
+++ b/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp
@@ -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>(

diff  --git a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp
index a09a2e64e8055..22f01bf719d5e 100644
--- a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp
+++ b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp
@@ -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 @@ class MLIRTextFile {
   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();
+}

diff  --git a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.h b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.h
index cee882e7d5d5a..ee6c2cd25bd57 100644
--- a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.h
+++ b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.h
@@ -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 @@ class MLIRServer {
                       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;
 

diff  --git a/mlir/lib/Tools/mlir-lsp-server/MlirLspServerMain.cpp b/mlir/lib/Tools/mlir-lsp-server/MlirLspServerMain.cpp
index ed586818954f4..f7558ad64372d 100644
--- a/mlir/lib/Tools/mlir-lsp-server/MlirLspServerMain.cpp
+++ b/mlir/lib/Tools/mlir-lsp-server/MlirLspServerMain.cpp
@@ -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);

diff  --git a/mlir/lib/Tools/mlir-lsp-server/Protocol.cpp b/mlir/lib/Tools/mlir-lsp-server/Protocol.cpp
new file mode 100644
index 0000000000000..dc0154a2e6e12
--- /dev/null
+++ b/mlir/lib/Tools/mlir-lsp-server/Protocol.cpp
@@ -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}};
+}

diff  --git a/mlir/lib/Tools/mlir-lsp-server/Protocol.h b/mlir/lib/Tools/mlir-lsp-server/Protocol.h
new file mode 100644
index 0000000000000..12a4dd269b537
--- /dev/null
+++ b/mlir/lib/Tools/mlir-lsp-server/Protocol.h
@@ -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

diff  --git a/mlir/utils/vscode/package-lock.json b/mlir/utils/vscode/package-lock.json
index cbe03d7ddc43f..53db5f0b35d91 100644
--- a/mlir/utils/vscode/package-lock.json
+++ b/mlir/utils/vscode/package-lock.json
@@ -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",

diff  --git a/mlir/utils/vscode/package.json b/mlir/utils/vscode/package.json
index 28c54403eb50c..0e403cf92013b 100644
--- a/mlir/utils/vscode/package.json
+++ b/mlir/utils/vscode/package.json
@@ -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"
       },

diff  --git a/mlir/utils/vscode/src/MLIR/bytecodeProvider.ts b/mlir/utils/vscode/src/MLIR/bytecodeProvider.ts
new file mode 100644
index 0000000000000..a9e90555e6118
--- /dev/null
+++ b/mlir/utils/vscode/src/MLIR/bytecodeProvider.ts
@@ -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());
+}

diff  --git a/mlir/utils/vscode/src/MLIR/mlir.ts b/mlir/utils/vscode/src/MLIR/mlir.ts
new file mode 100644
index 0000000000000..bd026bd0a42e8
--- /dev/null
+++ b/mlir/utils/vscode/src/MLIR/mlir.ts
@@ -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);
+}

diff  --git a/mlir/utils/vscode/src/PDLL/commands/viewOutput.ts b/mlir/utils/vscode/src/PDLL/commands/viewOutput.ts
index 4a666a0a2c07d..3c165ad1067d0 100644
--- a/mlir/utils/vscode/src/PDLL/commands/viewOutput.ts
+++ b/mlir/utils/vscode/src/PDLL/commands/viewOutput.ts
@@ -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;
     }

diff  --git a/mlir/utils/vscode/src/PDLL/pdll.ts b/mlir/utils/vscode/src/PDLL/pdll.ts
index bc65837be2a84..79262d9f9572f 100644
--- a/mlir/utils/vscode/src/PDLL/pdll.ts
+++ b/mlir/utils/vscode/src/PDLL/pdll.ts
@@ -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));
 }

diff  --git a/mlir/utils/vscode/src/extension.ts b/mlir/utils/vscode/src/extension.ts
index 72c754d0c9642..133fb8f6cf6ae 100644
--- a/mlir/utils/vscode/src/extension.ts
+++ b/mlir/utils/vscode/src/extension.ts
@@ -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);
 }

diff  --git a/mlir/utils/vscode/src/mlirContext.ts b/mlir/utils/vscode/src/mlirContext.ts
index 9bba50e1c0a3e..c7b6de6322d27 100644
--- a/mlir/utils/vscode/src/mlirContext.ts
+++ b/mlir/utils/vscode/src/mlirContext.ts
@@ -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);


        


More information about the Mlir-commits mailing list