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 { 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(); readonly onDidChangeFile: vscode.Event = this._emitter.event; /* * Read in a bytecode file, converting it to text before returning it to the * caller. */ async readFile(uri: vscode.Uri): Promise { // 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 result: ConvertBytecodeResult; try { let params: ConvertBytecodeParams = {uri : uri.toString()}; result = await client.sendRequest('mlir/convertFromBytecode', params); } catch (e) { vscode.window.showErrorMessage(e.message); throw new Error(`Failed to read bytecode file: ${e}`); } let resultBuffer = new TextEncoder().encode(result.output); // NOTE: VSCode does not allow for extensions to manage files above 50mb. // Detect that here and if our result is too large for us to manage, alert // the user and open it as a new temporary .mlir file. if (resultBuffer.length > (50 * 1024 * 1024)) { const openAsTempInstead: vscode.MessageItem = { title : 'Open as temporary .mlir instead', }; const message: string = `Failed to open bytecode file "${ uri.toString()}". Cannot edit converted bytecode files larger than 50MB.`; const errorResult: vscode.MessageItem|undefined = await vscode.window.showErrorMessage(message, openAsTempInstead); if (errorResult === openAsTempInstead) { let tempFile = await vscode.workspace.openTextDocument({ language : 'mlir', content : result.output, }); await vscode.window.showTextDocument(tempFile); } throw new Error(message); } return resultBuffer; } /* * 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 { public async openCustomDocument(uri: vscode.Uri, _openContext: any, _token: vscode.CancellationToken): Promise { return new BytecodeDocument(uri); } public async resolveCustomEditor(document: BytecodeDocument, _webviewPanel: vscode.WebviewPanel, _token: vscode.CancellationToken): Promise { // 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()); }