xref: /llvm-project/mlir/utils/vscode/src/MLIR/bytecodeProvider.ts (revision 82b65496540fbee7720a4fa47ebc3c3dac128980)
1f7b8a70eSRiver Riddleimport * as base64 from 'base64-js'
2f7b8a70eSRiver Riddleimport * as vscode from 'vscode'
3f7b8a70eSRiver Riddle
4f7b8a70eSRiver Riddleimport {MLIRContext} from '../mlirContext';
5f7b8a70eSRiver Riddle
6f7b8a70eSRiver Riddle/**
7f7b8a70eSRiver Riddle * The parameters to the mlir/convert(To|From)Bytecode commands. These
8f7b8a70eSRiver Riddle * parameters are:
9f7b8a70eSRiver Riddle * - `uri`: The URI of the file to convert.
10f7b8a70eSRiver Riddle */
11f7b8a70eSRiver Riddletype ConvertBytecodeParams = Partial<{uri : string}>;
12f7b8a70eSRiver Riddle
13f7b8a70eSRiver Riddle/**
14f7b8a70eSRiver Riddle * The output of the mlir/convert(To|From)Bytecode commands:
15f7b8a70eSRiver Riddle * - `output`: The output buffer of the command, e.g. a .mlir or bytecode
16f7b8a70eSRiver Riddle *             buffer.
17f7b8a70eSRiver Riddle */
18f7b8a70eSRiver Riddletype ConvertBytecodeResult = Partial<{output : string}>;
19f7b8a70eSRiver Riddle
20f7b8a70eSRiver Riddle/**
21f7b8a70eSRiver Riddle * A custom filesystem that is used to convert MLIR bytecode files to text for
22f7b8a70eSRiver Riddle * use in the editor, but still use bytecode on disk.
23f7b8a70eSRiver Riddle */
24f7b8a70eSRiver Riddleclass BytecodeFS implements vscode.FileSystemProvider {
25f7b8a70eSRiver Riddle  mlirContext: MLIRContext;
26f7b8a70eSRiver Riddle
27f7b8a70eSRiver Riddle  constructor(mlirContext: MLIRContext) { this.mlirContext = mlirContext; }
28f7b8a70eSRiver Riddle
29f7b8a70eSRiver Riddle  /*
30f7b8a70eSRiver Riddle   * Forward to the default filesystem for the various methods that don't need
31f7b8a70eSRiver Riddle   * to understand the bytecode <-> text translation.
32f7b8a70eSRiver Riddle   */
33f7b8a70eSRiver Riddle  readDirectory(uri: vscode.Uri): Thenable<[ string, vscode.FileType ][]> {
34f7b8a70eSRiver Riddle    return vscode.workspace.fs.readDirectory(uri);
35f7b8a70eSRiver Riddle  }
36f7b8a70eSRiver Riddle  delete(uri: vscode.Uri): void {
37f7b8a70eSRiver Riddle    vscode.workspace.fs.delete(uri.with({scheme : "file"}));
38f7b8a70eSRiver Riddle  }
39f7b8a70eSRiver Riddle  stat(uri: vscode.Uri): Thenable<vscode.FileStat> {
40f7b8a70eSRiver Riddle    return vscode.workspace.fs.stat(uri.with({scheme : "file"}));
41f7b8a70eSRiver Riddle  }
42f7b8a70eSRiver Riddle  rename(oldUri: vscode.Uri, newUri: vscode.Uri,
43f7b8a70eSRiver Riddle         options: {overwrite: boolean}): void {
44f7b8a70eSRiver Riddle    vscode.workspace.fs.rename(oldUri.with({scheme : "file"}),
45f7b8a70eSRiver Riddle                               newUri.with({scheme : "file"}), options);
46f7b8a70eSRiver Riddle  }
47f7b8a70eSRiver Riddle  createDirectory(uri: vscode.Uri): void {
48f7b8a70eSRiver Riddle    vscode.workspace.fs.createDirectory(uri.with({scheme : "file"}));
49f7b8a70eSRiver Riddle  }
50f7b8a70eSRiver Riddle  watch(_uri: vscode.Uri, _options: {
51f7b8a70eSRiver Riddle    readonly recursive: boolean; readonly excludes : readonly string[]
52f7b8a70eSRiver Riddle  }): vscode.Disposable {
53f7b8a70eSRiver Riddle    return new vscode.Disposable(() => {});
54f7b8a70eSRiver Riddle  }
55f7b8a70eSRiver Riddle
56f7b8a70eSRiver Riddle  private _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
57f7b8a70eSRiver Riddle  readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> =
58f7b8a70eSRiver Riddle      this._emitter.event;
59f7b8a70eSRiver Riddle
60f7b8a70eSRiver Riddle  /*
61f7b8a70eSRiver Riddle   * Read in a bytecode file, converting it to text before returning it to the
62f7b8a70eSRiver Riddle   * caller.
63f7b8a70eSRiver Riddle   */
64f7b8a70eSRiver Riddle  async readFile(uri: vscode.Uri): Promise<Uint8Array> {
65f7b8a70eSRiver Riddle    // Try to start a language client for this file so that we can parse
66f7b8a70eSRiver Riddle    // it.
67f7b8a70eSRiver Riddle    const client =
68f7b8a70eSRiver Riddle        await this.mlirContext.getOrActivateLanguageClient(uri, 'mlir');
69f7b8a70eSRiver Riddle    if (!client) {
70f7b8a70eSRiver Riddle      throw new Error(
71f7b8a70eSRiver Riddle          'Failed to activate mlir language server to read bytecode');
72f7b8a70eSRiver Riddle    }
73*82b65496SRiver Riddle
74f7b8a70eSRiver Riddle    // Ask the client to do the conversion.
75*82b65496SRiver Riddle    let result: ConvertBytecodeResult;
76f7b8a70eSRiver Riddle    try {
77*82b65496SRiver Riddle      let params: ConvertBytecodeParams = {uri : uri.toString()};
78*82b65496SRiver Riddle      result = await client.sendRequest('mlir/convertFromBytecode', params);
79f7b8a70eSRiver Riddle    } catch (e) {
80f7b8a70eSRiver Riddle      vscode.window.showErrorMessage(e.message);
81f7b8a70eSRiver Riddle      throw new Error(`Failed to read bytecode file: ${e}`);
82f7b8a70eSRiver Riddle    }
83*82b65496SRiver Riddle    let resultBuffer = new TextEncoder().encode(result.output);
84*82b65496SRiver Riddle
85*82b65496SRiver Riddle    // NOTE: VSCode does not allow for extensions to manage files above 50mb.
86*82b65496SRiver Riddle    // Detect that here and if our result is too large for us to manage, alert
87*82b65496SRiver Riddle    // the user and open it as a new temporary .mlir file.
88*82b65496SRiver Riddle    if (resultBuffer.length > (50 * 1024 * 1024)) {
89*82b65496SRiver Riddle      const openAsTempInstead: vscode.MessageItem = {
90*82b65496SRiver Riddle        title : 'Open as temporary .mlir instead',
91*82b65496SRiver Riddle      };
92*82b65496SRiver Riddle      const message: string = `Failed to open bytecode file "${
93*82b65496SRiver Riddle          uri.toString()}". Cannot edit converted bytecode files larger than 50MB.`;
94*82b65496SRiver Riddle      const errorResult: vscode.MessageItem|undefined =
95*82b65496SRiver Riddle          await vscode.window.showErrorMessage(message, openAsTempInstead);
96*82b65496SRiver Riddle      if (errorResult === openAsTempInstead) {
97*82b65496SRiver Riddle        let tempFile = await vscode.workspace.openTextDocument({
98*82b65496SRiver Riddle          language : 'mlir',
99*82b65496SRiver Riddle          content : result.output,
100*82b65496SRiver Riddle        });
101*82b65496SRiver Riddle        await vscode.window.showTextDocument(tempFile);
102*82b65496SRiver Riddle      }
103*82b65496SRiver Riddle      throw new Error(message);
104*82b65496SRiver Riddle    }
105*82b65496SRiver Riddle
106*82b65496SRiver Riddle    return resultBuffer;
107f7b8a70eSRiver Riddle  }
108f7b8a70eSRiver Riddle
109f7b8a70eSRiver Riddle  /*
110f7b8a70eSRiver Riddle   * Save the provided content, which contains MLIR text, as bytecode.
111f7b8a70eSRiver Riddle   */
112f7b8a70eSRiver Riddle  async writeFile(uri: vscode.Uri, content: Uint8Array,
113f7b8a70eSRiver Riddle                  _options: {create: boolean, overwrite: boolean}) {
114f7b8a70eSRiver Riddle    // Get the language client managing this file.
115f7b8a70eSRiver Riddle    let client = this.mlirContext.getLanguageClient(uri, 'mlir');
116f7b8a70eSRiver Riddle    if (!client) {
117f7b8a70eSRiver Riddle      throw new Error(
118f7b8a70eSRiver Riddle          'Failed to activate mlir language server to write bytecode');
119f7b8a70eSRiver Riddle    }
120f7b8a70eSRiver Riddle
121f7b8a70eSRiver Riddle    // Ask the client to do the conversion.
122f7b8a70eSRiver Riddle    let convertParams: ConvertBytecodeParams = {
123f7b8a70eSRiver Riddle      uri : uri.toString(),
124f7b8a70eSRiver Riddle    };
125f7b8a70eSRiver Riddle    const result: ConvertBytecodeResult =
126f7b8a70eSRiver Riddle        await client.sendRequest('mlir/convertToBytecode', convertParams);
127f7b8a70eSRiver Riddle    await vscode.workspace.fs.writeFile(uri.with({scheme : "file"}),
128f7b8a70eSRiver Riddle                                        base64.toByteArray(result.output));
129f7b8a70eSRiver Riddle  }
130f7b8a70eSRiver Riddle}
131f7b8a70eSRiver Riddle
132f7b8a70eSRiver Riddle/**
133f7b8a70eSRiver Riddle * A custom bytecode document for use by the custom editor provider below.
134f7b8a70eSRiver Riddle */
135f7b8a70eSRiver Riddleclass BytecodeDocument implements vscode.CustomDocument {
136f7b8a70eSRiver Riddle  readonly uri: vscode.Uri;
137f7b8a70eSRiver Riddle
138f7b8a70eSRiver Riddle  constructor(uri: vscode.Uri) { this.uri = uri; }
139f7b8a70eSRiver Riddle  dispose(): void {}
140f7b8a70eSRiver Riddle}
141f7b8a70eSRiver Riddle
142f7b8a70eSRiver Riddle/**
143f7b8a70eSRiver Riddle * A custom editor provider for MLIR bytecode that allows for non-binary
144f7b8a70eSRiver Riddle * interpretation.
145f7b8a70eSRiver Riddle */
146f7b8a70eSRiver Riddleclass BytecodeEditorProvider implements
147f7b8a70eSRiver Riddle    vscode.CustomReadonlyEditorProvider<BytecodeDocument> {
148f7b8a70eSRiver Riddle  public async openCustomDocument(uri: vscode.Uri, _openContext: any,
149f7b8a70eSRiver Riddle                                  _token: vscode.CancellationToken):
150f7b8a70eSRiver Riddle      Promise<BytecodeDocument> {
151f7b8a70eSRiver Riddle    return new BytecodeDocument(uri);
152f7b8a70eSRiver Riddle  }
153f7b8a70eSRiver Riddle
154f7b8a70eSRiver Riddle  public async resolveCustomEditor(document: BytecodeDocument,
155f7b8a70eSRiver Riddle                                   _webviewPanel: vscode.WebviewPanel,
156f7b8a70eSRiver Riddle                                   _token: vscode.CancellationToken):
157f7b8a70eSRiver Riddle      Promise<void> {
158f7b8a70eSRiver Riddle    // Ask the user for the desired view type.
159f7b8a70eSRiver Riddle    const editType = await vscode.window.showQuickPick(
160f7b8a70eSRiver Riddle        [ {label : '.mlir', description : "Edit as a .mlir text file"} ],
161f7b8a70eSRiver Riddle        {title : 'Select an editor for the bytecode.'},
162f7b8a70eSRiver Riddle    );
163f7b8a70eSRiver Riddle
164f7b8a70eSRiver Riddle    // If we don't have a valid view type, just bail.
165f7b8a70eSRiver Riddle    if (!editType) {
166f7b8a70eSRiver Riddle      await vscode.commands.executeCommand(
167f7b8a70eSRiver Riddle          'workbench.action.closeActiveEditor');
168f7b8a70eSRiver Riddle      return;
169f7b8a70eSRiver Riddle    }
170f7b8a70eSRiver Riddle
171f7b8a70eSRiver Riddle    // TODO: We should also provide a non-`.mlir` way of viewing the
172f7b8a70eSRiver Riddle    // bytecode, which should also ideally have some support for invalid
173f7b8a70eSRiver Riddle    // bytecode files.
174f7b8a70eSRiver Riddle
175f7b8a70eSRiver Riddle    // Close the active editor given that we aren't using it.
176f7b8a70eSRiver Riddle    await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
177f7b8a70eSRiver Riddle
178f7b8a70eSRiver Riddle    // Display the file using a .mlir format.
179f7b8a70eSRiver Riddle    await vscode.window.showTextDocument(
180f7b8a70eSRiver Riddle        document.uri.with({scheme : "mlir.bytecode-mlir"}),
181f7b8a70eSRiver Riddle        {preview : true, preserveFocus : false});
182f7b8a70eSRiver Riddle  }
183f7b8a70eSRiver Riddle}
184f7b8a70eSRiver Riddle
185f7b8a70eSRiver Riddle/**
186f7b8a70eSRiver Riddle *  Register the necessary providers for supporting MLIR bytecode.
187f7b8a70eSRiver Riddle */
188f7b8a70eSRiver Riddleexport function registerMLIRBytecodeExtensions(context: vscode.ExtensionContext,
189f7b8a70eSRiver Riddle                                               mlirContext: MLIRContext) {
190f7b8a70eSRiver Riddle  vscode.workspace.registerFileSystemProvider("mlir.bytecode-mlir",
191f7b8a70eSRiver Riddle                                              new BytecodeFS(mlirContext));
192f7b8a70eSRiver Riddle  vscode.window.registerCustomEditorProvider('mlir.bytecode',
193f7b8a70eSRiver Riddle                                             new BytecodeEditorProvider());
194f7b8a70eSRiver Riddle}
195