[Mlir-commits] [mlir] [mlir][vscode plugin] Add feature to detect and run LIT tests commands encoded in '.mlir' test files (PR #186840)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sat Apr 11 12:25:51 PDT 2026
https://github.com/lanluo-nvidia updated https://github.com/llvm/llvm-project/pull/186840
>From e6c9007e4c0b327db9abe6de31250bd9c5a6d472 Mon Sep 17 00:00:00 2001
From: Lan Luo <lanl at nvidia.com>
Date: Thu, 5 Mar 2026 13:39:48 -0800
Subject: [PATCH 1/4] test
---
mlir/utils/vscode/package-lock.json | 186 ++++++-
mlir/utils/vscode/package.json | 10 +-
.../src/MLIR/commands/runLitWithIRDump.ts | 466 ++++++++++++++++++
mlir/utils/vscode/src/MLIR/mlir.ts | 2 +
4 files changed, 656 insertions(+), 8 deletions(-)
create mode 100644 mlir/utils/vscode/src/MLIR/commands/runLitWithIRDump.ts
diff --git a/mlir/utils/vscode/package-lock.json b/mlir/utils/vscode/package-lock.json
index 72b36057e3421..14a63286d1074 100644
--- a/mlir/utils/vscode/package-lock.json
+++ b/mlir/utils/vscode/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "vscode-mlir",
- "version": "0.0.13",
+ "version": "0.0.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vscode-mlir",
- "version": "0.0.13",
+ "version": "0.0.14",
"dependencies": {
"base64-js": "^1.5.1",
"chokidar": "3.5.2",
@@ -82,8 +82,77 @@
"node": ">=20.0.0"
}
},
- "node_modules/@azure/core-rest-pipeline": {
- "dev": true
+ "node_modules/@azure/core-client/node_modules/@azure/core-rest-pipeline": {
+ "version": "1.22.2",
+ "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz",
+ "integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@azure/abort-controller": "^2.1.2",
+ "@azure/core-auth": "^1.10.0",
+ "@azure/core-tracing": "^1.3.0",
+ "@azure/core-util": "^1.13.0",
+ "@azure/logger": "^1.3.0",
+ "@typespec/ts-http-runtime": "^0.3.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@azure/core-client/node_modules/@typespec/ts-http-runtime": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz",
+ "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@azure/core-client/node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@azure/core-client/node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@azure/core-client/node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
},
"node_modules/@azure/core-tracing": {
"version": "1.3.1",
@@ -109,6 +178,59 @@
"node": ">=20.0.0"
}
},
+ "node_modules/@azure/core-util/node_modules/@typespec/ts-http-runtime": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz",
+ "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@azure/core-util/node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@azure/core-util/node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@azure/core-util/node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/@azure/logger": {
"version": "1.3.0",
"dev": true,
@@ -121,6 +243,59 @@
"node": ">=20.0.0"
}
},
+ "node_modules/@azure/logger/node_modules/@typespec/ts-http-runtime": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz",
+ "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@azure/logger/node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@azure/logger/node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@azure/logger/node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.28.6",
"dev": true,
@@ -862,9 +1037,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@typespec/ts-http-runtime": {
- "dev": true
- },
"node_modules/@vscode/vsce": {
"version": "3.7.1",
"dev": true,
diff --git a/mlir/utils/vscode/package.json b/mlir/utils/vscode/package.json
index c52da0af1b18b..033ee724efeca 100644
--- a/mlir/utils/vscode/package.json
+++ b/mlir/utils/vscode/package.json
@@ -5,7 +5,6 @@
"version": "0.0.14",
"publisher": "llvm-vs-code-extensions",
"homepage": "https://mlir.llvm.org/",
- "icon": "icon.png",
"engines": {
"vscode": "^1.75.0"
},
@@ -219,6 +218,10 @@
{
"command": "mlir.viewPDLLOutput",
"title": "mlir-pdll: View PDLL output"
+ },
+ {
+ "command": "mlir.runLitWithIRDump",
+ "title": "mlir:Run with IR Dump"
}
],
"menus": {
@@ -227,6 +230,11 @@
"command": "mlir.viewPDLLOutput",
"group": "z_commands",
"when": "editorLangId == pdll"
+ },
+ {
+ "command": "mlir.runLitWithIRDump",
+ "group": "z_commands",
+ "when": "editorLangId == mlir"
}
]
}
diff --git a/mlir/utils/vscode/src/MLIR/commands/runLitWithIRDump.ts b/mlir/utils/vscode/src/MLIR/commands/runLitWithIRDump.ts
new file mode 100644
index 0000000000000..42658c860c1e3
--- /dev/null
+++ b/mlir/utils/vscode/src/MLIR/commands/runLitWithIRDump.ts
@@ -0,0 +1,466 @@
+import * as vscode from 'vscode';
+import * as path from 'path';
+import * as fs from 'fs';
+import {spawn} from 'child_process';
+
+import {Command} from '../../command';
+import {MLIRContext} from '../../mlirContext';
+
+/**
+ * A command that runs lit with IR dump on the current MLIR file.
+ */
+export class RunLitWithIRDumpCommand extends Command {
+ constructor(context: MLIRContext) {
+ super('mlir.runLitWithIRDump', context);
+ }
+
+ /**
+ * Check if a file is executable
+ */
+ private isExecutable(filePath: string): boolean {
+ try {
+ fs.accessSync(filePath, fs.constants.X_OK);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
+ /**
+ * Check if a command is available in the system PATH
+ */
+ private async checkCommandAvailable(command: string): Promise<boolean> {
+ return new Promise((resolve) => {
+ const childProcess = spawn('which', [command], {shell: true});
+ childProcess.on('close', (code) => {
+ resolve(code === 0);
+ });
+ childProcess.on('error', () => {
+ resolve(false);
+ });
+ });
+ }
+
+ /**
+ * Run a command and return the result
+ */
+ private async runCommand(
+ command: string,
+ args: string[],
+ cwd: string,
+ outputChannel: vscode.OutputChannel,
+ venvPath?: string
+ ): Promise<{success: boolean, output: string}> {
+ return new Promise((resolve) => {
+ let output = '';
+ let errorOutput = '';
+
+ // If venv is provided, activate it first
+ const env = {...process.env};
+ if (venvPath) {
+ const pythonPath = path.join(venvPath, 'bin', 'python');
+ if (fs.existsSync(pythonPath)) {
+ env.PATH = `${path.join(venvPath, 'bin')}:${env.PATH}`;
+ env.VIRTUAL_ENV = venvPath;
+ }
+ }
+
+ const childProcess = spawn(command, args, {
+ cwd: cwd,
+ shell: true,
+ env: env,
+ });
+
+ childProcess.stdout?.on('data', (data) => {
+ const text = data.toString();
+ output += text;
+ outputChannel.append(text);
+ });
+
+ childProcess.stderr?.on('data', (data) => {
+ const text = data.toString();
+ errorOutput += text;
+ outputChannel.append(text);
+ });
+
+ childProcess.on('close', (code) => {
+ resolve({
+ success: code === 0,
+ output: output + errorOutput,
+ });
+ });
+
+ childProcess.on('error', (error) => {
+ vscode.window.showErrorMessage(`[RunLitWithIRDump] Error running command: ${error.message}`);
+ resolve({
+ success: false,
+ output: error.message,
+ });
+ });
+ });
+ }
+
+ async execute() {
+ // Ensure output channel exists and is shown
+ let outputChannel = this.context.outputChannel;
+ if (!outputChannel) {
+ // Fallback: create a new output channel if context doesn't have one
+ outputChannel = vscode.window.createOutputChannel('MLIR');
+ console.warn('[RunLitWithIRDump] WARNING: Using fallback output channel');
+ }
+
+ vscode.window.showInformationMessage('[RunLitWithIRDump] Command started');
+
+ const editor = vscode.window.activeTextEditor;
+ if (!editor) {
+ vscode.window.showErrorMessage('No active editor');
+ return;
+ }
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Active editor found: ${editor.document.fileName}`);
+
+ if (editor.document.languageId !== 'mlir') {
+ vscode.window.showErrorMessage(
+ 'Current file is not an MLIR file. Please open a .mlir file first.');
+ return;
+ }
+ vscode.window.showInformationMessage('[RunLitWithIRDump] File is MLIR format');
+
+ const fileUri = editor.document.uri;
+ if (fileUri.scheme !== 'file') {
+ vscode.window.showErrorMessage('File must be saved to disk');
+ return;
+ }
+
+ const filePath = fileUri.fsPath;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] File path: ${filePath}`);
+
+ // Try to find the build directory
+ // First, check if we're in a workspace
+ const workspaceFolder = vscode.workspace.getWorkspaceFolder(fileUri);
+ if (!workspaceFolder) {
+ vscode.window.showErrorMessage(
+ 'No workspace folder found. Please open a workspace.');
+ return;
+ }
+
+ // Look for build directory in common locations
+ const workspacePath = workspaceFolder.uri.fsPath;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Workspace path: ${workspacePath}`);
+
+ const possibleBuildDirs = [
+ path.join(workspacePath, 'build'),
+ path.join(workspacePath, '..', 'build'),
+ path.join(workspacePath, '..', '..', 'build'),
+ ];
+
+ let buildDir = null;
+ for (const buildPath of possibleBuildDirs) {
+ if (fs.existsSync(buildPath) && fs.statSync(buildPath).isDirectory()) {
+ buildDir = buildPath;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Found build directory: ${buildDir}`);
+ break;
+ }
+ }
+
+ if (!buildDir) {
+ // Ask user for build directory
+ const userBuildDir = await vscode.window.showInputBox({
+ prompt: 'Enter the path to the build directory',
+ placeHolder: 'e.g., /path/to/build or build',
+ value: 'build',
+ });
+
+ if (!userBuildDir) {
+ return;
+ }
+
+ // Resolve the path - try multiple locations for relative paths
+ if (path.isAbsolute(userBuildDir)) {
+ buildDir = userBuildDir;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] User provided absolute path: ${buildDir}`);
+ } else {
+ // Try to find build directory by walking up from the file's directory
+ // looking for a build/ directory or resolving relative to common locations
+ let currentDir = path.dirname(filePath);
+ const maxDepth = 10; // Prevent infinite loops
+ let depth = 0;
+
+ // First, try to find an existing build directory by walking up
+ while (depth < maxDepth && currentDir !== path.dirname(currentDir)) {
+ const buildPath = path.join(currentDir, userBuildDir);
+ if (fs.existsSync(buildPath) && fs.statSync(buildPath).isDirectory()) {
+ buildDir = buildPath;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Found build directory: ${buildDir}`);
+ break;
+ }
+ currentDir = path.dirname(currentDir);
+ depth++;
+ }
+
+ // If not found, try resolving relative path in common locations
+ if (!buildDir) {
+ const candidatePaths = [
+ path.join(workspacePath, userBuildDir),
+ path.join(path.dirname(filePath), userBuildDir),
+ ];
+
+ for (const candidate of candidatePaths) {
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
+ buildDir = candidate;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Found build directory: ${buildDir}`);
+ break;
+ }
+ }
+
+ // If still not found, use workspace path as fallback (will error if doesn't exist)
+ if (!buildDir) {
+ buildDir = path.join(workspacePath, userBuildDir);
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Using fallback path: ${buildDir}`);
+ }
+ }
+ }
+
+ if (!fs.existsSync(buildDir)) {
+ vscode.window.showErrorMessage(
+ `Build directory does not exist: ${buildDir}`);
+ return;
+ }
+ }
+
+ // Calculate relative path for lit command
+ // Lit typically runs from the build directory
+ // The path should be relative to the parent directory of the build directory
+ // For example: if build is at <project>/build and file is at <project>/compiler/test/.../file.mlir
+ // lit expects: compiler/test/.../file.mlir
+
+ // Get the parent directory of the build directory (the project root)
+ const buildParentDir = path.dirname(buildDir);
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Build parent directory: ${buildParentDir}`);
+
+ // Get path relative to build parent directory
+ let relativePath = path.relative(buildParentDir, filePath);
+
+ // Normalize the path separators for the shell (use forward slashes)
+ relativePath = relativePath.replace(/\\/g, '/');
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Relative path: ${relativePath}`);
+
+ // Try to find or setup lit executable
+ let litCommand: string;
+ let pythonEnvActivate: string | null = null;
+
+ // Step 1: Ask user for lit path or virtual env path (optional)
+ // User can press Escape or leave empty to skip
+ const userLitPath = await vscode.window.showInputBox({
+ prompt: 'Enter path to llvm-lit executable or virtual env (optional - press Escape to auto-detect/create)',
+ placeHolder: 'e.g., /path/to/llvm-lit or /path/to/.venv',
+ ignoreFocusOut: false,
+ });
+
+ // If user cancelled (undefined), treat as empty string to proceed with auto-detection
+ const userProvidedPath = userLitPath === undefined ? '' : userLitPath;
+ // userProvidedPath is a llvm-lit or lit path:
+ // if it is a file, check if it is a llvm-lit or lit executable
+ // if it is a directory, check if */llvm-lit or */lit exists and it is a executable
+ // userProvidedPath is a virtual env directory:
+ // check if */bin/lit exists and it is a executable
+
+ let litExecutable: string | null = null;
+ let venvPath: string | null = null;
+
+ if (userProvidedPath && userProvidedPath.trim() !== '') {
+ const userPath = userProvidedPath.trim();
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] User provided path: ${userPath}`);
+
+ if (fs.existsSync(userPath)) {
+ const stats = fs.statSync(userPath);
+
+ if (stats.isFile()) {
+ // It's a file - check if it's a llvm-lit or lit executable
+ const fileName = path.basename(userPath);
+ if ((fileName === 'lit' || fileName === 'llvm-lit') && this.isExecutable(userPath)) {
+ litExecutable = userPath;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Using executable: ${litExecutable}`);
+ } else {
+ vscode.window.showWarningMessage(
+ `[RunLitWithIRDump] File is not a valid lit/llvm-lit executable: ${userPath}`);
+ }
+ } else if (stats.isDirectory()) {
+ // It's a directory - check if it's a virtual env or contains lit executables
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] User provided directory: ${userPath}`);
+
+ // First, check if it's a virtual env (has bin/activate or bin/lit)
+ // In virtual env, only check for lit (Python package), not llvm-lit
+ const activateScript = path.join(userPath, 'bin', 'activate');
+ const litInBin = path.join(userPath, 'bin', 'lit');
+
+ if (fs.existsSync(activateScript)) {
+ // It's a virtual env
+ venvPath = userPath;
+ if (fs.existsSync(litInBin) && this.isExecutable(litInBin)) {
+ litExecutable = litInBin;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Using lit from virtual env: ${litExecutable}`);
+ } else {
+ // Virtual env exists but lit not installed yet - will activate and use lit from PATH
+ pythonEnvActivate = `source ${activateScript} && `;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Will activate virtual env: ${venvPath}`);
+ }
+ } else {
+ // Not a virtual env - check if directory contains llvm-lit or lit executables directly
+ const litInDir = path.join(userPath, 'lit');
+ const llvmLitInDir = path.join(userPath, 'llvm-lit');
+
+ if (fs.existsSync(llvmLitInDir) && this.isExecutable(llvmLitInDir)) {
+ litExecutable = llvmLitInDir;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Using llvm-lit from directory: ${litExecutable}`);
+ } else if (fs.existsSync(litInDir) && this.isExecutable(litInDir)) {
+ litExecutable = litInDir;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Using lit from directory: ${litExecutable}`);
+ } else {
+ vscode.window.showWarningMessage(
+ `[RunLitWithIRDump] Directory does not contain a valid lit/llvm-lit executable: ${userPath}`);
+ }
+ }
+ }
+ } else {
+ vscode.window.showWarningMessage(`[RunLitWithIRDump] Path does not exist: ${userPath}. Will create venv and install lit.`);
+ }
+ }
+
+ // Step 2: If no user-provided path, create uv venv and install lit
+ if (!litExecutable && !pythonEnvActivate) {
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] No user-provided path. Will create uv venv and install lit.`);
+
+ // Determine where to create the venv (prefer workspace root)
+ venvPath = path.join(workspacePath, '.venv');
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Target venv path: ${venvPath}`);
+
+ // Check if uv is available
+ const uvAvailable = await this.checkCommandAvailable('uv');
+ if (!uvAvailable) {
+ vscode.window.showErrorMessage(
+ 'uv is not available. Please install uv or provide a path to llvm-lit.',
+ 'Install uv'
+ ).then(selection => {
+ if (selection === 'Install uv') {
+ vscode.env.openExternal(vscode.Uri.parse('https://github.com/astral-sh/uv'));
+ }
+ });
+ return;
+ }
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] uv is available`);
+
+ // Create venv if it doesn't exist
+ if (!fs.existsSync(venvPath)) {
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Creating uv venv at: ${venvPath}`);
+ const createVenvResult = await this.runCommand('uv', ['venv', venvPath], workspacePath, outputChannel);
+ if (!createVenvResult.success) {
+ vscode.window.showErrorMessage('Failed to create virtual environment');
+ return;
+ }
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Successfully created venv`);
+ } else {
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Venv already exists at: ${venvPath}`);
+ }
+
+ // Check if lit or llvm-lit is already installed
+ const litInVenv = path.join(venvPath, 'bin', 'lit');
+ const llvmLitInVenv = path.join(venvPath, 'bin', 'llvm-lit');
+ const activateScript = path.join(venvPath, 'bin', 'activate');
+
+ if (fs.existsSync(activateScript)) {
+ if (fs.existsSync(llvmLitInVenv)) {
+ litExecutable = llvmLitInVenv;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Found existing llvm-lit in venv: ${litExecutable}`);
+ } else if (fs.existsSync(litInVenv)) {
+ litExecutable = litInVenv;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Found existing lit in venv: ${litExecutable}`);
+ } else {
+ // Install lit from testpypi (newer version >=22)
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Installing lit>=22 from testpypi...`);
+ const installLitResult = await this.runCommand(
+ 'uv',
+ ['pip', 'install', '--index-url', 'https://test.pypi.org/simple/', '--extra-index-url', 'https://pypi.org/simple/', 'lit>=22'],
+ workspacePath,
+ outputChannel,
+ venvPath
+ );
+ if (!installLitResult.success) {
+ vscode.window.showErrorMessage('Failed to install lit from testpypi');
+ return;
+ }
+
+ // Check if lit or llvm-lit is now available
+ if (fs.existsSync(llvmLitInVenv)) {
+ litExecutable = llvmLitInVenv;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Successfully installed llvm-lit: ${litExecutable}`);
+ } else if (fs.existsSync(litInVenv)) {
+ litExecutable = litInVenv;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Successfully installed lit: ${litExecutable}`);
+ } else {
+ // Fallback: use activation script
+ pythonEnvActivate = `source ${activateScript} && `;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Lit installed but not found at expected path, will activate venv`);
+ }
+ }
+ } else {
+ vscode.window.showErrorMessage('Virtual environment is invalid');
+ return;
+ }
+ }
+
+ // Step 3: Construct the final lit command
+ if (litExecutable) {
+ litCommand = `${litExecutable} -vv -a ${relativePath}`;
+ } else if (pythonEnvActivate) {
+ // Try uv run first if in a uv project
+ const possibleUvDirs = [
+ path.dirname(filePath),
+ workspacePath,
+ path.join(workspacePath, '..'),
+ ];
+
+ let useUvRun = false;
+ for (const dir of possibleUvDirs) {
+ const pyprojectToml = path.join(dir, 'pyproject.toml');
+ const uvLock = path.join(dir, 'uv.lock');
+ if (fs.existsSync(pyprojectToml) || fs.existsSync(uvLock)) {
+ useUvRun = true;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Detected uv project, will use 'uv run lit'`);
+ break;
+ }
+ }
+
+ if (useUvRun) {
+ litCommand = `uv run lit -vv -a ${relativePath}`;
+ pythonEnvActivate = null; // Don't activate if using uv run
+ } else {
+ litCommand = `lit -vv -a ${relativePath}`;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Will activate Python environment before running lit`);
+ }
+ } else {
+ litCommand = `lit -vv -a ${relativePath}`;
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Using system lit (may fail if not in PATH)`);
+ }
+
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Lit command: ${litCommand}`);
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Build directory (cwd): ${buildDir}`);
+
+ // Create a terminal and run the command from the build directory
+ const terminal = vscode.window.createTerminal({
+ name: 'MLIR Lit with IR Dump',
+ cwd: buildDir,
+ });
+
+ terminal.show();
+
+ // Send command with environment activation if needed
+ if (pythonEnvActivate) {
+ terminal.sendText(`${pythonEnvActivate}${litCommand}`);
+ } else {
+ terminal.sendText(litCommand);
+ }
+
+ vscode.window.showInformationMessage(`[RunLitWithIRDump] Terminal command sent successfully`);
+ vscode.window.showInformationMessage(`Running lit with IR dump on: ${relativePath}`);
+ }
+}
diff --git a/mlir/utils/vscode/src/MLIR/mlir.ts b/mlir/utils/vscode/src/MLIR/mlir.ts
index bd026bd0a42e8..4ff1c8628755c 100644
--- a/mlir/utils/vscode/src/MLIR/mlir.ts
+++ b/mlir/utils/vscode/src/MLIR/mlir.ts
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
import {MLIRContext} from '../mlirContext';
import {registerMLIRBytecodeExtensions} from './bytecodeProvider';
+import {RunLitWithIRDumpCommand} from './commands/runLitWithIRDump';
/**
* Register the necessary extensions for supporting MLIR.
@@ -9,4 +10,5 @@ import {registerMLIRBytecodeExtensions} from './bytecodeProvider';
export function registerMLIRExtensions(context: vscode.ExtensionContext,
mlirContext: MLIRContext) {
registerMLIRBytecodeExtensions(context, mlirContext);
+ context.subscriptions.push(new RunLitWithIRDumpCommand(mlirContext));
}
>From 2a753fe11377af97e57a18eea7a71b9b5cc5306b Mon Sep 17 00:00:00 2001
From: Lan Luo <lanl at nvidia.com>
Date: Mon, 16 Mar 2026 09:37:21 -0700
Subject: [PATCH 2/4] add mlir lit test command
---
mlir/utils/vscode/package.json | 16 +-
.../src/MLIR/commands/runLitWithIRDump.ts | 466 ------------------
.../utils/vscode/src/MLIR/commands/runTest.ts | 376 ++++++++++++++
mlir/utils/vscode/src/MLIR/mlir.ts | 4 +-
4 files changed, 391 insertions(+), 471 deletions(-)
delete mode 100644 mlir/utils/vscode/src/MLIR/commands/runLitWithIRDump.ts
create mode 100644 mlir/utils/vscode/src/MLIR/commands/runTest.ts
diff --git a/mlir/utils/vscode/package.json b/mlir/utils/vscode/package.json
index 033ee724efeca..8d883cdd1fc25 100644
--- a/mlir/utils/vscode/package.json
+++ b/mlir/utils/vscode/package.json
@@ -207,6 +207,16 @@
"Automatically restart the server",
"Do nothing"
]
+ },
+ "mlir.litBuildDirectory": {
+ "scope": "resource",
+ "type": "string",
+ "description": "The build directory path for running lit tests. Can be absolute or relative to workspace root. If not set, user will be prompted."
+ },
+ "mlir.litExecutablePath": {
+ "scope": "resource",
+ "type": "string",
+ "description": "The path to llvm-lit or lit executable, or path to a virtual environment containing lit. If not set, user will be prompted or a venv will be auto-created."
}
}
},
@@ -220,8 +230,8 @@
"title": "mlir-pdll: View PDLL output"
},
{
- "command": "mlir.runLitWithIRDump",
- "title": "mlir:Run with IR Dump"
+ "command": "mlir.runTest",
+ "title": "mlir:Run Test"
}
],
"menus": {
@@ -232,7 +242,7 @@
"when": "editorLangId == pdll"
},
{
- "command": "mlir.runLitWithIRDump",
+ "command": "mlir.runTest",
"group": "z_commands",
"when": "editorLangId == mlir"
}
diff --git a/mlir/utils/vscode/src/MLIR/commands/runLitWithIRDump.ts b/mlir/utils/vscode/src/MLIR/commands/runLitWithIRDump.ts
deleted file mode 100644
index 42658c860c1e3..0000000000000
--- a/mlir/utils/vscode/src/MLIR/commands/runLitWithIRDump.ts
+++ /dev/null
@@ -1,466 +0,0 @@
-import * as vscode from 'vscode';
-import * as path from 'path';
-import * as fs from 'fs';
-import {spawn} from 'child_process';
-
-import {Command} from '../../command';
-import {MLIRContext} from '../../mlirContext';
-
-/**
- * A command that runs lit with IR dump on the current MLIR file.
- */
-export class RunLitWithIRDumpCommand extends Command {
- constructor(context: MLIRContext) {
- super('mlir.runLitWithIRDump', context);
- }
-
- /**
- * Check if a file is executable
- */
- private isExecutable(filePath: string): boolean {
- try {
- fs.accessSync(filePath, fs.constants.X_OK);
- return true;
- } catch {
- return false;
- }
- }
-
- /**
- * Check if a command is available in the system PATH
- */
- private async checkCommandAvailable(command: string): Promise<boolean> {
- return new Promise((resolve) => {
- const childProcess = spawn('which', [command], {shell: true});
- childProcess.on('close', (code) => {
- resolve(code === 0);
- });
- childProcess.on('error', () => {
- resolve(false);
- });
- });
- }
-
- /**
- * Run a command and return the result
- */
- private async runCommand(
- command: string,
- args: string[],
- cwd: string,
- outputChannel: vscode.OutputChannel,
- venvPath?: string
- ): Promise<{success: boolean, output: string}> {
- return new Promise((resolve) => {
- let output = '';
- let errorOutput = '';
-
- // If venv is provided, activate it first
- const env = {...process.env};
- if (venvPath) {
- const pythonPath = path.join(venvPath, 'bin', 'python');
- if (fs.existsSync(pythonPath)) {
- env.PATH = `${path.join(venvPath, 'bin')}:${env.PATH}`;
- env.VIRTUAL_ENV = venvPath;
- }
- }
-
- const childProcess = spawn(command, args, {
- cwd: cwd,
- shell: true,
- env: env,
- });
-
- childProcess.stdout?.on('data', (data) => {
- const text = data.toString();
- output += text;
- outputChannel.append(text);
- });
-
- childProcess.stderr?.on('data', (data) => {
- const text = data.toString();
- errorOutput += text;
- outputChannel.append(text);
- });
-
- childProcess.on('close', (code) => {
- resolve({
- success: code === 0,
- output: output + errorOutput,
- });
- });
-
- childProcess.on('error', (error) => {
- vscode.window.showErrorMessage(`[RunLitWithIRDump] Error running command: ${error.message}`);
- resolve({
- success: false,
- output: error.message,
- });
- });
- });
- }
-
- async execute() {
- // Ensure output channel exists and is shown
- let outputChannel = this.context.outputChannel;
- if (!outputChannel) {
- // Fallback: create a new output channel if context doesn't have one
- outputChannel = vscode.window.createOutputChannel('MLIR');
- console.warn('[RunLitWithIRDump] WARNING: Using fallback output channel');
- }
-
- vscode.window.showInformationMessage('[RunLitWithIRDump] Command started');
-
- const editor = vscode.window.activeTextEditor;
- if (!editor) {
- vscode.window.showErrorMessage('No active editor');
- return;
- }
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Active editor found: ${editor.document.fileName}`);
-
- if (editor.document.languageId !== 'mlir') {
- vscode.window.showErrorMessage(
- 'Current file is not an MLIR file. Please open a .mlir file first.');
- return;
- }
- vscode.window.showInformationMessage('[RunLitWithIRDump] File is MLIR format');
-
- const fileUri = editor.document.uri;
- if (fileUri.scheme !== 'file') {
- vscode.window.showErrorMessage('File must be saved to disk');
- return;
- }
-
- const filePath = fileUri.fsPath;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] File path: ${filePath}`);
-
- // Try to find the build directory
- // First, check if we're in a workspace
- const workspaceFolder = vscode.workspace.getWorkspaceFolder(fileUri);
- if (!workspaceFolder) {
- vscode.window.showErrorMessage(
- 'No workspace folder found. Please open a workspace.');
- return;
- }
-
- // Look for build directory in common locations
- const workspacePath = workspaceFolder.uri.fsPath;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Workspace path: ${workspacePath}`);
-
- const possibleBuildDirs = [
- path.join(workspacePath, 'build'),
- path.join(workspacePath, '..', 'build'),
- path.join(workspacePath, '..', '..', 'build'),
- ];
-
- let buildDir = null;
- for (const buildPath of possibleBuildDirs) {
- if (fs.existsSync(buildPath) && fs.statSync(buildPath).isDirectory()) {
- buildDir = buildPath;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Found build directory: ${buildDir}`);
- break;
- }
- }
-
- if (!buildDir) {
- // Ask user for build directory
- const userBuildDir = await vscode.window.showInputBox({
- prompt: 'Enter the path to the build directory',
- placeHolder: 'e.g., /path/to/build or build',
- value: 'build',
- });
-
- if (!userBuildDir) {
- return;
- }
-
- // Resolve the path - try multiple locations for relative paths
- if (path.isAbsolute(userBuildDir)) {
- buildDir = userBuildDir;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] User provided absolute path: ${buildDir}`);
- } else {
- // Try to find build directory by walking up from the file's directory
- // looking for a build/ directory or resolving relative to common locations
- let currentDir = path.dirname(filePath);
- const maxDepth = 10; // Prevent infinite loops
- let depth = 0;
-
- // First, try to find an existing build directory by walking up
- while (depth < maxDepth && currentDir !== path.dirname(currentDir)) {
- const buildPath = path.join(currentDir, userBuildDir);
- if (fs.existsSync(buildPath) && fs.statSync(buildPath).isDirectory()) {
- buildDir = buildPath;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Found build directory: ${buildDir}`);
- break;
- }
- currentDir = path.dirname(currentDir);
- depth++;
- }
-
- // If not found, try resolving relative path in common locations
- if (!buildDir) {
- const candidatePaths = [
- path.join(workspacePath, userBuildDir),
- path.join(path.dirname(filePath), userBuildDir),
- ];
-
- for (const candidate of candidatePaths) {
- if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
- buildDir = candidate;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Found build directory: ${buildDir}`);
- break;
- }
- }
-
- // If still not found, use workspace path as fallback (will error if doesn't exist)
- if (!buildDir) {
- buildDir = path.join(workspacePath, userBuildDir);
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Using fallback path: ${buildDir}`);
- }
- }
- }
-
- if (!fs.existsSync(buildDir)) {
- vscode.window.showErrorMessage(
- `Build directory does not exist: ${buildDir}`);
- return;
- }
- }
-
- // Calculate relative path for lit command
- // Lit typically runs from the build directory
- // The path should be relative to the parent directory of the build directory
- // For example: if build is at <project>/build and file is at <project>/compiler/test/.../file.mlir
- // lit expects: compiler/test/.../file.mlir
-
- // Get the parent directory of the build directory (the project root)
- const buildParentDir = path.dirname(buildDir);
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Build parent directory: ${buildParentDir}`);
-
- // Get path relative to build parent directory
- let relativePath = path.relative(buildParentDir, filePath);
-
- // Normalize the path separators for the shell (use forward slashes)
- relativePath = relativePath.replace(/\\/g, '/');
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Relative path: ${relativePath}`);
-
- // Try to find or setup lit executable
- let litCommand: string;
- let pythonEnvActivate: string | null = null;
-
- // Step 1: Ask user for lit path or virtual env path (optional)
- // User can press Escape or leave empty to skip
- const userLitPath = await vscode.window.showInputBox({
- prompt: 'Enter path to llvm-lit executable or virtual env (optional - press Escape to auto-detect/create)',
- placeHolder: 'e.g., /path/to/llvm-lit or /path/to/.venv',
- ignoreFocusOut: false,
- });
-
- // If user cancelled (undefined), treat as empty string to proceed with auto-detection
- const userProvidedPath = userLitPath === undefined ? '' : userLitPath;
- // userProvidedPath is a llvm-lit or lit path:
- // if it is a file, check if it is a llvm-lit or lit executable
- // if it is a directory, check if */llvm-lit or */lit exists and it is a executable
- // userProvidedPath is a virtual env directory:
- // check if */bin/lit exists and it is a executable
-
- let litExecutable: string | null = null;
- let venvPath: string | null = null;
-
- if (userProvidedPath && userProvidedPath.trim() !== '') {
- const userPath = userProvidedPath.trim();
- vscode.window.showInformationMessage(`[RunLitWithIRDump] User provided path: ${userPath}`);
-
- if (fs.existsSync(userPath)) {
- const stats = fs.statSync(userPath);
-
- if (stats.isFile()) {
- // It's a file - check if it's a llvm-lit or lit executable
- const fileName = path.basename(userPath);
- if ((fileName === 'lit' || fileName === 'llvm-lit') && this.isExecutable(userPath)) {
- litExecutable = userPath;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Using executable: ${litExecutable}`);
- } else {
- vscode.window.showWarningMessage(
- `[RunLitWithIRDump] File is not a valid lit/llvm-lit executable: ${userPath}`);
- }
- } else if (stats.isDirectory()) {
- // It's a directory - check if it's a virtual env or contains lit executables
- vscode.window.showInformationMessage(`[RunLitWithIRDump] User provided directory: ${userPath}`);
-
- // First, check if it's a virtual env (has bin/activate or bin/lit)
- // In virtual env, only check for lit (Python package), not llvm-lit
- const activateScript = path.join(userPath, 'bin', 'activate');
- const litInBin = path.join(userPath, 'bin', 'lit');
-
- if (fs.existsSync(activateScript)) {
- // It's a virtual env
- venvPath = userPath;
- if (fs.existsSync(litInBin) && this.isExecutable(litInBin)) {
- litExecutable = litInBin;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Using lit from virtual env: ${litExecutable}`);
- } else {
- // Virtual env exists but lit not installed yet - will activate and use lit from PATH
- pythonEnvActivate = `source ${activateScript} && `;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Will activate virtual env: ${venvPath}`);
- }
- } else {
- // Not a virtual env - check if directory contains llvm-lit or lit executables directly
- const litInDir = path.join(userPath, 'lit');
- const llvmLitInDir = path.join(userPath, 'llvm-lit');
-
- if (fs.existsSync(llvmLitInDir) && this.isExecutable(llvmLitInDir)) {
- litExecutable = llvmLitInDir;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Using llvm-lit from directory: ${litExecutable}`);
- } else if (fs.existsSync(litInDir) && this.isExecutable(litInDir)) {
- litExecutable = litInDir;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Using lit from directory: ${litExecutable}`);
- } else {
- vscode.window.showWarningMessage(
- `[RunLitWithIRDump] Directory does not contain a valid lit/llvm-lit executable: ${userPath}`);
- }
- }
- }
- } else {
- vscode.window.showWarningMessage(`[RunLitWithIRDump] Path does not exist: ${userPath}. Will create venv and install lit.`);
- }
- }
-
- // Step 2: If no user-provided path, create uv venv and install lit
- if (!litExecutable && !pythonEnvActivate) {
- vscode.window.showInformationMessage(`[RunLitWithIRDump] No user-provided path. Will create uv venv and install lit.`);
-
- // Determine where to create the venv (prefer workspace root)
- venvPath = path.join(workspacePath, '.venv');
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Target venv path: ${venvPath}`);
-
- // Check if uv is available
- const uvAvailable = await this.checkCommandAvailable('uv');
- if (!uvAvailable) {
- vscode.window.showErrorMessage(
- 'uv is not available. Please install uv or provide a path to llvm-lit.',
- 'Install uv'
- ).then(selection => {
- if (selection === 'Install uv') {
- vscode.env.openExternal(vscode.Uri.parse('https://github.com/astral-sh/uv'));
- }
- });
- return;
- }
- vscode.window.showInformationMessage(`[RunLitWithIRDump] uv is available`);
-
- // Create venv if it doesn't exist
- if (!fs.existsSync(venvPath)) {
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Creating uv venv at: ${venvPath}`);
- const createVenvResult = await this.runCommand('uv', ['venv', venvPath], workspacePath, outputChannel);
- if (!createVenvResult.success) {
- vscode.window.showErrorMessage('Failed to create virtual environment');
- return;
- }
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Successfully created venv`);
- } else {
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Venv already exists at: ${venvPath}`);
- }
-
- // Check if lit or llvm-lit is already installed
- const litInVenv = path.join(venvPath, 'bin', 'lit');
- const llvmLitInVenv = path.join(venvPath, 'bin', 'llvm-lit');
- const activateScript = path.join(venvPath, 'bin', 'activate');
-
- if (fs.existsSync(activateScript)) {
- if (fs.existsSync(llvmLitInVenv)) {
- litExecutable = llvmLitInVenv;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Found existing llvm-lit in venv: ${litExecutable}`);
- } else if (fs.existsSync(litInVenv)) {
- litExecutable = litInVenv;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Found existing lit in venv: ${litExecutable}`);
- } else {
- // Install lit from testpypi (newer version >=22)
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Installing lit>=22 from testpypi...`);
- const installLitResult = await this.runCommand(
- 'uv',
- ['pip', 'install', '--index-url', 'https://test.pypi.org/simple/', '--extra-index-url', 'https://pypi.org/simple/', 'lit>=22'],
- workspacePath,
- outputChannel,
- venvPath
- );
- if (!installLitResult.success) {
- vscode.window.showErrorMessage('Failed to install lit from testpypi');
- return;
- }
-
- // Check if lit or llvm-lit is now available
- if (fs.existsSync(llvmLitInVenv)) {
- litExecutable = llvmLitInVenv;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Successfully installed llvm-lit: ${litExecutable}`);
- } else if (fs.existsSync(litInVenv)) {
- litExecutable = litInVenv;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Successfully installed lit: ${litExecutable}`);
- } else {
- // Fallback: use activation script
- pythonEnvActivate = `source ${activateScript} && `;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Lit installed but not found at expected path, will activate venv`);
- }
- }
- } else {
- vscode.window.showErrorMessage('Virtual environment is invalid');
- return;
- }
- }
-
- // Step 3: Construct the final lit command
- if (litExecutable) {
- litCommand = `${litExecutable} -vv -a ${relativePath}`;
- } else if (pythonEnvActivate) {
- // Try uv run first if in a uv project
- const possibleUvDirs = [
- path.dirname(filePath),
- workspacePath,
- path.join(workspacePath, '..'),
- ];
-
- let useUvRun = false;
- for (const dir of possibleUvDirs) {
- const pyprojectToml = path.join(dir, 'pyproject.toml');
- const uvLock = path.join(dir, 'uv.lock');
- if (fs.existsSync(pyprojectToml) || fs.existsSync(uvLock)) {
- useUvRun = true;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Detected uv project, will use 'uv run lit'`);
- break;
- }
- }
-
- if (useUvRun) {
- litCommand = `uv run lit -vv -a ${relativePath}`;
- pythonEnvActivate = null; // Don't activate if using uv run
- } else {
- litCommand = `lit -vv -a ${relativePath}`;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Will activate Python environment before running lit`);
- }
- } else {
- litCommand = `lit -vv -a ${relativePath}`;
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Using system lit (may fail if not in PATH)`);
- }
-
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Lit command: ${litCommand}`);
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Build directory (cwd): ${buildDir}`);
-
- // Create a terminal and run the command from the build directory
- const terminal = vscode.window.createTerminal({
- name: 'MLIR Lit with IR Dump',
- cwd: buildDir,
- });
-
- terminal.show();
-
- // Send command with environment activation if needed
- if (pythonEnvActivate) {
- terminal.sendText(`${pythonEnvActivate}${litCommand}`);
- } else {
- terminal.sendText(litCommand);
- }
-
- vscode.window.showInformationMessage(`[RunLitWithIRDump] Terminal command sent successfully`);
- vscode.window.showInformationMessage(`Running lit with IR dump on: ${relativePath}`);
- }
-}
diff --git a/mlir/utils/vscode/src/MLIR/commands/runTest.ts b/mlir/utils/vscode/src/MLIR/commands/runTest.ts
new file mode 100644
index 0000000000000..bc5c47f4b68bc
--- /dev/null
+++ b/mlir/utils/vscode/src/MLIR/commands/runTest.ts
@@ -0,0 +1,376 @@
+import * as vscode from 'vscode';
+import * as path from 'path';
+import * as fs from 'fs';
+import {spawn} from 'child_process';
+
+import {Command} from '../../command';
+import {MLIRContext} from '../../mlirContext';
+import * as config from '../../config';
+
+/**
+ * A command that runs lit with IR dump on the current MLIR file.
+ */
+export class RunTestCommand extends Command {
+ constructor(context: MLIRContext) {
+ super('mlir.runTest', context);
+ }
+
+ /**
+ * Check if a file is executable
+ */
+ private isExecutable(filePath: string): boolean {
+ try {
+ fs.accessSync(filePath, fs.constants.X_OK);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
+ /**
+ * Check if a command is available in the system PATH
+ */
+ private async checkCommandAvailable(command: string): Promise<boolean> {
+ return new Promise((resolve) => {
+ const childProcess = spawn('which', [command], {shell: true});
+ childProcess.on('close', (code) => {
+ resolve(code === 0);
+ });
+ childProcess.on('error', () => {
+ resolve(false);
+ });
+ });
+ }
+
+ /**
+ * Get or setup lit executable and construct the command
+ * @param workspaceFolder The workspace folder (for reading settings)
+ * @param relativePath The relative path to the test file (for lit command)
+ * @param outputChannel The output channel for command output
+ * @returns The lit command and activation string, or null if setup failed
+ */
+ private async getLitSetup(
+ workspaceFolder: vscode.WorkspaceFolder,
+ relativePath: string,
+ outputChannel: vscode.OutputChannel
+ ): Promise<{litCommand: string; pythonEnvActivate: string | null} | null> {
+ let litExecutable: string | null = null;
+ let pythonEnvActivate: string | null = null;
+
+ // Get lit executable path from workspace settings only
+ const settingsLitPath = config.get<string>('litExecutablePath', workspaceFolder);
+
+ if (!settingsLitPath || settingsLitPath.trim() === '') {
+ const errorMsg = 'Lit executable path not found in workspace settings. Please set "mlir.litExecutablePath" in .vscode/settings.json';
+ vscode.window.showErrorMessage(errorMsg);
+ outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
+ return null;
+ }
+
+ const workspacePath = workspaceFolder.uri.fsPath;
+ const trimmedPath = settingsLitPath.trim();
+ outputChannel.appendLine(`[RunTest] Using lit path from settings: ${trimmedPath}`);
+
+ // Resolve the path - handle both absolute and relative paths
+ let userPath: string;
+ if (path.isAbsolute(trimmedPath)) {
+ // Absolute path - use as is
+ userPath = trimmedPath;
+ } else {
+ // Relative path - resolve relative to workspace directory
+ userPath = path.join(workspacePath, trimmedPath);
+ }
+
+ outputChannel.appendLine(`[RunTest] Resolved path: ${userPath}`);
+
+ // Validate that the path exists
+ if (!fs.existsSync(userPath)) {
+ const errorMsg = `Lit executable path does not exist: ${userPath} (resolved from: ${trimmedPath})`;
+ vscode.window.showErrorMessage(errorMsg);
+ outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
+ return null;
+ }
+
+ const stats = fs.statSync(userPath);
+
+ if (!stats.isDirectory()) {
+ const errorMsg = `Path is not a directory: ${userPath}`;
+ vscode.window.showErrorMessage(errorMsg);
+ outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
+ return null;
+ }
+
+ // It's a directory - check if it's a virtual env or contains lit executables
+ outputChannel.appendLine(`[RunTest] lit executable directory: ${userPath}`);
+
+ // First, check if it's a virtual env (has bin/activate)
+ const activateScript = path.join(userPath, 'bin', 'activate');
+ const litInBin = path.join(userPath, 'bin', 'lit');
+ const llvmLitInBin = path.join(userPath, 'bin', 'llvm-lit');
+
+ if (fs.existsSync(activateScript)) {
+ // It's a virtual env - check for lit or llvm-lit in bin/
+ if (fs.existsSync(llvmLitInBin) && this.isExecutable(llvmLitInBin)) {
+ litExecutable = llvmLitInBin;
+ outputChannel.appendLine(`[RunTest] Using llvm-lit from virtual env: ${litExecutable}`);
+ } else if (fs.existsSync(litInBin) && this.isExecutable(litInBin)) {
+ litExecutable = litInBin;
+ outputChannel.appendLine(`[RunTest] Using lit from virtual env: ${litExecutable}`);
+ } else {
+ // Virtual env exists but lit not found - will activate and use lit from PATH
+ pythonEnvActivate = `source ${activateScript} && `;
+ outputChannel.appendLine(`[RunTest] Will activate virtual env: ${userPath}`);
+ }
+ } else {
+ // Not a virtual env - check if directory contains llvm-lit or lit executables directly
+ const litInDir = path.join(userPath, 'lit');
+ const llvmLitInDir = path.join(userPath, 'llvm-lit');
+
+ if (fs.existsSync(llvmLitInDir) && this.isExecutable(llvmLitInDir)) {
+ litExecutable = llvmLitInDir;
+ outputChannel.appendLine(`[RunTest] Using llvm-lit executable: ${litExecutable}`);
+ } else if (fs.existsSync(litInDir) && this.isExecutable(litInDir)) {
+ litExecutable = litInDir;
+ outputChannel.appendLine(`[RunTest] Using lit executable: ${litExecutable}`);
+ } else {
+ const errorMsg = `Directory does not contain a valid lit/llvm-lit executable: ${userPath}`;
+ vscode.window.showErrorMessage(errorMsg);
+ outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
+ return null;
+ }
+ }
+
+ // Construct the final lit command
+ let litCommand: string;
+ if (litExecutable) {
+ litCommand = `${litExecutable} -vv -a ${relativePath}`;
+ } else if (pythonEnvActivate) {
+ litCommand = `lit -vv -a ${relativePath}`;
+ outputChannel.appendLine(`[RunTest] Will activate Python environment before running lit`);
+ } else {
+ const errorMsg = 'Failed to determine lit executable or activation method';
+ vscode.window.showErrorMessage(errorMsg);
+ outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
+ return null;
+ }
+
+ return {
+ litCommand,
+ pythonEnvActivate,
+ };
+ }
+
+ /**
+ * Get the build directory from workspace settings
+ * @param workspaceFolder The workspace folder
+ * @param outputChannel The output channel for logging
+ * @returns The resolved build directory path, or null if not found in settings or invalid
+ */
+ private async getBuildDirectory(
+ workspaceFolder: vscode.WorkspaceFolder,
+ outputChannel: vscode.OutputChannel
+ ): Promise<string | null> {
+ // workspacePath: /workspaces/TensorRT-Incubator/mlir-tensorrt
+ const workspacePath = workspaceFolder.uri.fsPath;
+ // Get build directory from workspace settings only
+ const settingsBuildDir = config.get<string>('litBuildDirectory', workspaceFolder);
+
+ if (!settingsBuildDir || settingsBuildDir.trim() === '') {
+ const errorMsg = 'Build directory not found in workspace settings. Please set "mlir.litBuildDirectory" in .vscode/settings.json';
+ vscode.window.showErrorMessage(errorMsg);
+ outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
+ return null;
+ }
+
+ // Resolve the build directory path
+ let buildDir: string;
+ const trimmedPath = settingsBuildDir.trim();
+ if (path.isAbsolute(trimmedPath)) {
+ // Absolute path - use as is
+ buildDir = trimmedPath;
+ } else {
+ // Relative path - resolve relative to workspace's directory
+ const candidatePaths = [
+ path.join(workspacePath, trimmedPath),
+ ];
+
+ // Check if any candidate exists
+ let found = false;
+ for (const candidate of candidatePaths) {
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
+ buildDir = candidate;
+ found = true;
+ break;
+ }
+ }
+
+ // If not found, use workspace path as default (will validate existence below)
+ if (!found) {
+ buildDir = path.join(workspacePath, trimmedPath);
+ }
+ }
+
+ // Validate that the build directory exists
+ if (!fs.existsSync(buildDir)) {
+ vscode.window.showErrorMessage(
+ `Build directory does not exist: ${buildDir}`);
+ return null;
+ }
+
+ if (!fs.statSync(buildDir).isDirectory()) {
+ vscode.window.showErrorMessage(
+ `Build Path is not a directory: ${buildDir}`);
+ return null;
+ }
+
+ outputChannel.appendLine(`[RunTest] Using build directory: ${buildDir}`);
+ return buildDir;
+ }
+
+ /**
+ * Run a command and return the result
+ */
+ private async runCommand(
+ command: string,
+ args: string[],
+ cwd: string,
+ outputChannel: vscode.OutputChannel,
+ venvPath?: string
+ ): Promise<{success: boolean, output: string}> {
+ return new Promise((resolve) => {
+ let output = '';
+ let errorOutput = '';
+
+ // If venv is provided, activate it first
+ const env = {...process.env};
+ if (venvPath) {
+ const pythonPath = path.join(venvPath, 'bin', 'python');
+ if (fs.existsSync(pythonPath)) {
+ env.PATH = `${path.join(venvPath, 'bin')}:${env.PATH}`;
+ env.VIRTUAL_ENV = venvPath;
+ }
+ }
+
+ const childProcess = spawn(command, args, {
+ cwd: cwd,
+ shell: true,
+ env: env,
+ });
+
+ childProcess.stdout?.on('data', (data) => {
+ const text = data.toString();
+ output += text;
+ outputChannel.append(text);
+ });
+
+ childProcess.stderr?.on('data', (data) => {
+ const text = data.toString();
+ errorOutput += text;
+ outputChannel.append(text);
+ });
+
+ childProcess.on('close', (code) => {
+ resolve({
+ success: code === 0,
+ output: output + errorOutput,
+ });
+ });
+
+ childProcess.on('error', (error) => {
+ vscode.window.showErrorMessage(`[RunTest] Error running command: ${error.message}`);
+ resolve({
+ success: false,
+ output: error.message,
+ });
+ });
+ });
+ }
+
+ async execute() {
+ // Ensure output channel exists and is shown
+ let outputChannel = this.context.outputChannel;
+ if (!outputChannel) {
+ // Fallback: create a new output channel if context doesn't have one
+ outputChannel = vscode.window.createOutputChannel('MLIR');
+ console.warn('[RunTest] WARNING: Using fallback output channel');
+ }
+ // Show the output channel so messages are visible
+ outputChannel.show(true);
+ outputChannel.clear();
+ outputChannel.appendLine('=== Run Lit with IR Dump ===');
+
+ const editor = vscode.window.activeTextEditor;
+ if (!editor) {
+ vscode.window.showErrorMessage('No active editor');
+ return;
+ }
+
+ if (editor.document.languageId !== 'mlir') {
+ vscode.window.showErrorMessage(
+ 'Current file is not an MLIR file. Please open a .mlir file first.');
+ return;
+ }
+
+ const fileUri = editor.document.uri;
+ if (fileUri.scheme !== 'file') {
+ vscode.window.showErrorMessage('File must be saved to disk');
+ return;
+ }
+
+ const filePath = fileUri.fsPath;
+ // Get workspace folder for later use
+ const workspaceFolder = vscode.workspace.getWorkspaceFolder(fileUri);
+ if (!workspaceFolder) {
+ vscode.window.showErrorMessage(
+ 'No workspace folder found. Please open a workspace.');
+ return;
+ }
+
+ // Get build directory from user
+ const buildDir = await this.getBuildDirectory(workspaceFolder, outputChannel);
+ if (!buildDir) {
+ return;
+ }
+
+ const workspacePath = workspaceFolder.uri.fsPath;
+
+ // Get the parent directory of the build directory (the project root)
+ const buildParentDir = path.dirname(buildDir);
+ outputChannel.appendLine(`[RunTest] Build parent directory: ${buildParentDir}`);
+
+ // Get path relative to build parent directory
+ let relativePath = path.relative(buildParentDir, filePath);
+
+ // Normalize the path separators for the shell (use forward slashes)
+ relativePath = relativePath.replace(/\\/g, '/');
+ outputChannel.appendLine(`[RunTest] mlir file relative path: ${relativePath}`);
+
+ // Get or setup lit executable and construct the command
+ const litSetup = await this.getLitSetup(workspaceFolder, relativePath, outputChannel);
+ if (!litSetup) {
+ return;
+ }
+
+ const {litCommand, pythonEnvActivate} = litSetup;
+ outputChannel.appendLine(`[RunTest] Lit command: ${litCommand}`);
+ outputChannel.appendLine(`[RunTest] Build directory (cwd): ${buildDir}`);
+
+ // Create a terminal and run the command from the build directory
+ const terminal = vscode.window.createTerminal({
+ name: 'Run MLIR Lit',
+ cwd: buildDir,
+ });
+
+ terminal.show();
+
+ // Send command with environment activation if needed
+ if (pythonEnvActivate) {
+ terminal.sendText(`${pythonEnvActivate}${litCommand}`);
+ } else {
+ terminal.sendText(litCommand);
+ }
+
+ outputChannel.appendLine(`[RunTest] Terminal command sent successfully`);
+ outputChannel.appendLine(`[RunTest] Running MLIR lit on: ${relativePath}`);
+ }
+}
diff --git a/mlir/utils/vscode/src/MLIR/mlir.ts b/mlir/utils/vscode/src/MLIR/mlir.ts
index 4ff1c8628755c..8ba6553beb3cc 100644
--- a/mlir/utils/vscode/src/MLIR/mlir.ts
+++ b/mlir/utils/vscode/src/MLIR/mlir.ts
@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
import {MLIRContext} from '../mlirContext';
import {registerMLIRBytecodeExtensions} from './bytecodeProvider';
-import {RunLitWithIRDumpCommand} from './commands/runLitWithIRDump';
+import {RunTestCommand} from './commands/runTest';
/**
* Register the necessary extensions for supporting MLIR.
@@ -10,5 +10,5 @@ import {RunLitWithIRDumpCommand} from './commands/runLitWithIRDump';
export function registerMLIRExtensions(context: vscode.ExtensionContext,
mlirContext: MLIRContext) {
registerMLIRBytecodeExtensions(context, mlirContext);
- context.subscriptions.push(new RunLitWithIRDumpCommand(mlirContext));
+ context.subscriptions.push(new RunTestCommand(mlirContext));
}
>From 1296d4954eb0c671cfad32939579cbf228450217 Mon Sep 17 00:00:00 2001
From: Lan Luo <lanl at nvidia.com>
Date: Tue, 31 Mar 2026 16:39:14 -0700
Subject: [PATCH 3/4] resolve comments
---
mlir/utils/vscode/package-lock.json | 26 +-
mlir/utils/vscode/package.json | 27 +-
.../utils/vscode/src/MLIR/commands/runTest.ts | 350 ++++++++++--------
3 files changed, 229 insertions(+), 174 deletions(-)
diff --git a/mlir/utils/vscode/package-lock.json b/mlir/utils/vscode/package-lock.json
index 14a63286d1074..c9b610f6adf7c 100644
--- a/mlir/utils/vscode/package-lock.json
+++ b/mlir/utils/vscode/package-lock.json
@@ -83,9 +83,9 @@
}
},
"node_modules/@azure/core-client/node_modules/@azure/core-rest-pipeline": {
- "version": "1.22.2",
- "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz",
- "integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==",
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz",
+ "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -94,7 +94,7 @@
"@azure/core-tracing": "^1.3.0",
"@azure/core-util": "^1.13.0",
"@azure/logger": "^1.3.0",
- "@typespec/ts-http-runtime": "^0.3.0",
+ "@typespec/ts-http-runtime": "^0.3.4",
"tslib": "^2.6.2"
},
"engines": {
@@ -102,9 +102,9 @@
}
},
"node_modules/@azure/core-client/node_modules/@typespec/ts-http-runtime": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz",
- "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==",
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.4.tgz",
+ "integrity": "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -179,9 +179,9 @@
}
},
"node_modules/@azure/core-util/node_modules/@typespec/ts-http-runtime": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz",
- "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==",
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.4.tgz",
+ "integrity": "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -244,9 +244,9 @@
}
},
"node_modules/@azure/logger/node_modules/@typespec/ts-http-runtime": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz",
- "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==",
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.4.tgz",
+ "integrity": "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/mlir/utils/vscode/package.json b/mlir/utils/vscode/package.json
index 8d883cdd1fc25..8fe2164f20dce 100644
--- a/mlir/utils/vscode/package.json
+++ b/mlir/utils/vscode/package.json
@@ -5,6 +5,7 @@
"version": "0.0.14",
"publisher": "llvm-vs-code-extensions",
"homepage": "https://mlir.llvm.org/",
+ "icon": "icon.png",
"engines": {
"vscode": "^1.75.0"
},
@@ -208,15 +209,33 @@
"Do nothing"
]
},
- "mlir.litBuildDirectory": {
+ "mlir.litSuites": {
"scope": "resource",
- "type": "string",
- "description": "The build directory path for running lit tests. Can be absolute or relative to workspace root. If not set, user will be prompted."
+ "type": "array",
+ "markdownDescription": "Required for **mlir: Run Test**. Ordered list of suites: the first entry whose `source` directory contains the open `.mlir` file supplies the `build` directory (lit cwd). Paths are absolute or relative to the workspace root.",
+ "items": {
+ "type": "object",
+ "required": [
+ "source",
+ "build"
+ ],
+ "properties": {
+ "source": {
+ "type": "string",
+ "description": "Source tree d irectory for this suite (e.g. mlir/test)."
+ },
+ "build": {
+ "type": "string",
+ "description": "Corresponding build output directory for lit cwd (e.g. build/tools/mlir/test)."
+ }
+ }
+ },
+ "default": []
},
"mlir.litExecutablePath": {
"scope": "resource",
"type": "string",
- "description": "The path to llvm-lit or lit executable, or path to a virtual environment containing lit. If not set, user will be prompted or a venv will be auto-created."
+ "description": "Full path to the lit or llvm-lit executable (file path, not a directory). Can be absolute or relative to the workspace root. If unset, the extension looks for lit or llvm-lit on PATH, then prompts for a path."
}
}
},
diff --git a/mlir/utils/vscode/src/MLIR/commands/runTest.ts b/mlir/utils/vscode/src/MLIR/commands/runTest.ts
index bc5c47f4b68bc..b8bdc7ecb8257 100644
--- a/mlir/utils/vscode/src/MLIR/commands/runTest.ts
+++ b/mlir/utils/vscode/src/MLIR/commands/runTest.ts
@@ -7,6 +7,12 @@ import {Command} from '../../command';
import {MLIRContext} from '../../mlirContext';
import * as config from '../../config';
+/** One entry in `mlir.litSuites`: source dir on disk maps to lit cwd in build tree. */
+interface LitSuite {
+ source: string;
+ build: string;
+}
+
/**
* A command that runs lit with IR dump on the current MLIR file.
*/
@@ -28,203 +34,238 @@ export class RunTestCommand extends Command {
}
/**
- * Check if a command is available in the system PATH
+ * Resolve a command name to an absolute path using PATH (which / where).
*/
- private async checkCommandAvailable(command: string): Promise<boolean> {
+ private async resolveInPath(command: string): Promise<string | null> {
+ const tool = process.platform === 'win32' ? 'where' : 'which';
return new Promise((resolve) => {
- const childProcess = spawn('which', [command], {shell: true});
+ const childProcess = spawn(tool, [command], {shell: true});
+ let out = '';
+ childProcess.stdout?.on('data', (data) => {
+ out += data.toString();
+ });
childProcess.on('close', (code) => {
- resolve(code === 0);
+ if (code !== 0) {
+ resolve(null);
+ return;
+ }
+ const firstLine = out.trim().split(/\r?\n/)[0]?.trim();
+ resolve(firstLine && firstLine.length > 0 ? firstLine : null);
});
childProcess.on('error', () => {
- resolve(false);
+ resolve(null);
});
});
}
/**
- * Get or setup lit executable and construct the command
- * @param workspaceFolder The workspace folder (for reading settings)
- * @param relativePath The relative path to the test file (for lit command)
- * @param outputChannel The output channel for command output
- * @returns The lit command and activation string, or null if setup failed
+ * Resolve the lit or llvm-lit executable: workspace setting, then PATH, then user prompt.
*/
- private async getLitSetup(
+ private async resolveLitExecutablePath(
workspaceFolder: vscode.WorkspaceFolder,
- relativePath: string,
outputChannel: vscode.OutputChannel
- ): Promise<{litCommand: string; pythonEnvActivate: string | null} | null> {
- let litExecutable: string | null = null;
- let pythonEnvActivate: string | null = null;
-
- // Get lit executable path from workspace settings only
+ ): Promise<string | null> {
+ const workspacePath = workspaceFolder.uri.fsPath;
const settingsLitPath = config.get<string>('litExecutablePath', workspaceFolder);
-
- if (!settingsLitPath || settingsLitPath.trim() === '') {
- const errorMsg = 'Lit executable path not found in workspace settings. Please set "mlir.litExecutablePath" in .vscode/settings.json';
- vscode.window.showErrorMessage(errorMsg);
- outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
- return null;
+ const trimmedSetting = settingsLitPath?.trim() ?? '';
+
+ if (trimmedSetting) {
+ const resolved = path.isAbsolute(trimmedSetting) ?
+ trimmedSetting :
+ path.join(workspacePath, trimmedSetting);
+ outputChannel.appendLine(
+ `[RunTest] mlir.litExecutablePath (resolved): ${resolved}`);
+
+ if (!fs.existsSync(resolved)) {
+ const msg =
+ `Lit executable does not exist: ${resolved} (from mlir.litExecutablePath)`;
+ vscode.window.showErrorMessage(msg);
+ outputChannel.appendLine(`[RunTest] ERROR: ${msg}`);
+ return null;
+ }
+ if (fs.statSync(resolved).isDirectory()) {
+ const msg =
+ 'mlir.litExecutablePath must be the full path to the lit or llvm-lit executable, not a directory.';
+ vscode.window.showErrorMessage(msg);
+ outputChannel.appendLine(`[RunTest] ERROR: ${msg}`);
+ return null;
+ }
+ if (!this.isExecutable(resolved)) {
+ const msg = `Lit executable is not executable: ${resolved}`;
+ vscode.window.showErrorMessage(msg);
+ outputChannel.appendLine(`[RunTest] ERROR: ${msg}`);
+ return null;
+ }
+ return resolved;
}
- const workspacePath = workspaceFolder.uri.fsPath;
- const trimmedPath = settingsLitPath.trim();
- outputChannel.appendLine(`[RunTest] Using lit path from settings: ${trimmedPath}`);
-
- // Resolve the path - handle both absolute and relative paths
- let userPath: string;
- if (path.isAbsolute(trimmedPath)) {
- // Absolute path - use as is
- userPath = trimmedPath;
- } else {
- // Relative path - resolve relative to workspace directory
- userPath = path.join(workspacePath, trimmedPath);
+ for (const cmd of ['lit', 'llvm-lit']) {
+ const found = await this.resolveInPath(cmd);
+ if (found && fs.existsSync(found) && !fs.statSync(found).isDirectory() &&
+ this.isExecutable(found)) {
+ outputChannel.appendLine(`[RunTest] Using ${cmd} from PATH: ${found}`);
+ return found;
+ }
}
- outputChannel.appendLine(`[RunTest] Resolved path: ${userPath}`);
+ const input = await vscode.window.showInputBox({
+ title: 'Lit or llvm-lit executable',
+ prompt:
+ 'lit and llvm-lit were not found in PATH. Enter the full path to lit or llvm-lit.',
+ ignoreFocusOut: true,
+ validateInput: (value) => {
+ const t = value.trim();
+ if (!t) {
+ return 'Enter a path, or cancel.';
+ }
+ const candidate =
+ path.isAbsolute(t) ? t : path.join(workspacePath, t);
+ if (!fs.existsSync(candidate)) {
+ return 'Path does not exist.';
+ }
+ if (fs.statSync(candidate).isDirectory()) {
+ return 'Path must be the executable file, not a directory.';
+ }
+ if (!this.isExecutable(candidate)) {
+ return 'File is not executable.';
+ }
+ return null;
+ },
+ });
- // Validate that the path exists
- if (!fs.existsSync(userPath)) {
- const errorMsg = `Lit executable path does not exist: ${userPath} (resolved from: ${trimmedPath})`;
- vscode.window.showErrorMessage(errorMsg);
- outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
+ if (!input?.trim()) {
+ outputChannel.appendLine('[RunTest] No lit path: cancelled or empty input');
return null;
}
- const stats = fs.statSync(userPath);
+ const resolved = path.isAbsolute(input.trim()) ?
+ input.trim() :
+ path.join(workspacePath, input.trim());
+ outputChannel.appendLine(`[RunTest] Using lit from user input: ${resolved}`);
+ return resolved;
+ }
- if (!stats.isDirectory()) {
- const errorMsg = `Path is not a directory: ${userPath}`;
- vscode.window.showErrorMessage(errorMsg);
- outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
+ /**
+ * Get lit executable and construct the command
+ * @param workspaceFolder The workspace folder (for reading settings)
+ * @param relativePath The relative path to the test file (for lit command)
+ * @param outputChannel The output channel for command output
+ * @returns The lit shell command, or null if setup failed
+ */
+ private async getLitSetup(
+ workspaceFolder: vscode.WorkspaceFolder,
+ relativePath: string,
+ outputChannel: vscode.OutputChannel
+ ): Promise<{litCommand: string} | null> {
+ const litExecutable = await this.resolveLitExecutablePath(
+ workspaceFolder, outputChannel);
+ if (!litExecutable) {
return null;
}
- // It's a directory - check if it's a virtual env or contains lit executables
- outputChannel.appendLine(`[RunTest] lit executable directory: ${userPath}`);
-
- // First, check if it's a virtual env (has bin/activate)
- const activateScript = path.join(userPath, 'bin', 'activate');
- const litInBin = path.join(userPath, 'bin', 'lit');
- const llvmLitInBin = path.join(userPath, 'bin', 'llvm-lit');
-
- if (fs.existsSync(activateScript)) {
- // It's a virtual env - check for lit or llvm-lit in bin/
- if (fs.existsSync(llvmLitInBin) && this.isExecutable(llvmLitInBin)) {
- litExecutable = llvmLitInBin;
- outputChannel.appendLine(`[RunTest] Using llvm-lit from virtual env: ${litExecutable}`);
- } else if (fs.existsSync(litInBin) && this.isExecutable(litInBin)) {
- litExecutable = litInBin;
- outputChannel.appendLine(`[RunTest] Using lit from virtual env: ${litExecutable}`);
- } else {
- // Virtual env exists but lit not found - will activate and use lit from PATH
- pythonEnvActivate = `source ${activateScript} && `;
- outputChannel.appendLine(`[RunTest] Will activate virtual env: ${userPath}`);
- }
- } else {
- // Not a virtual env - check if directory contains llvm-lit or lit executables directly
- const litInDir = path.join(userPath, 'lit');
- const llvmLitInDir = path.join(userPath, 'llvm-lit');
-
- if (fs.existsSync(llvmLitInDir) && this.isExecutable(llvmLitInDir)) {
- litExecutable = llvmLitInDir;
- outputChannel.appendLine(`[RunTest] Using llvm-lit executable: ${litExecutable}`);
- } else if (fs.existsSync(litInDir) && this.isExecutable(litInDir)) {
- litExecutable = litInDir;
- outputChannel.appendLine(`[RunTest] Using lit executable: ${litExecutable}`);
- } else {
- const errorMsg = `Directory does not contain a valid lit/llvm-lit executable: ${userPath}`;
- vscode.window.showErrorMessage(errorMsg);
- outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
- return null;
- }
+ const quoted =
+ litExecutable.includes(' ') ? JSON.stringify(litExecutable) : litExecutable;
+ const litCommand = `${quoted} -vv -a ${relativePath}`;
+ return {litCommand};
+ }
+
+ /**
+ * Resolve a workspace-relative or absolute path string against the workspace root.
+ */
+ private resolveWorkspacePath(workspacePath: string, p: string): string {
+ const t = p.trim();
+ if (!t) {
+ return '';
}
+ return path.isAbsolute(t) ? path.normalize(t) :
+ path.normalize(path.join(workspacePath, t));
+ }
- // Construct the final lit command
- let litCommand: string;
- if (litExecutable) {
- litCommand = `${litExecutable} -vv -a ${relativePath}`;
- } else if (pythonEnvActivate) {
- litCommand = `lit -vv -a ${relativePath}`;
- outputChannel.appendLine(`[RunTest] Will activate Python environment before running lit`);
- } else {
- const errorMsg = 'Failed to determine lit executable or activation method';
- vscode.window.showErrorMessage(errorMsg);
- outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
+ /**
+ * True if `filePath` is `sourceDir` or a file/directory under it.
+ */
+ private isPathUnderDirectory(filePath: string, sourceDir: string): boolean {
+ const normFile = path.normalize(filePath);
+ const normDir = path.normalize(sourceDir);
+ const rel = path.relative(normDir, normFile);
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
+ }
+
+ /**
+ * Validate resolved build directory exists and return it, or null with error.
+ */
+ private finishBuildDirectory(
+ buildDir: string,
+ outputChannel: vscode.OutputChannel
+ ): string | null {
+ if (!fs.existsSync(buildDir)) {
+ vscode.window.showErrorMessage(
+ `Build directory does not exist: ${buildDir}`);
return null;
}
-
- return {
- litCommand,
- pythonEnvActivate,
- };
+ if (!fs.statSync(buildDir).isDirectory()) {
+ vscode.window.showErrorMessage(
+ `Build path is not a directory: ${buildDir}`);
+ return null;
+ }
+ outputChannel.appendLine(`[RunTest] Using build directory: ${buildDir}`);
+ return buildDir;
}
/**
- * Get the build directory from workspace settings
+ * Resolve lit cwd from the first matching `mlir.litSuites` entry for this file.
* @param workspaceFolder The workspace folder
+ * @param filePath Absolute path to the .mlir file being tested
* @param outputChannel The output channel for logging
- * @returns The resolved build directory path, or null if not found in settings or invalid
+ * @returns The resolved build directory path, or null if not found or invalid
*/
private async getBuildDirectory(
workspaceFolder: vscode.WorkspaceFolder,
+ filePath: string,
outputChannel: vscode.OutputChannel
): Promise<string | null> {
- // workspacePath: /workspaces/TensorRT-Incubator/mlir-tensorrt
const workspacePath = workspaceFolder.uri.fsPath;
- // Get build directory from workspace settings only
- const settingsBuildDir = config.get<string>('litBuildDirectory', workspaceFolder);
-
- if (!settingsBuildDir || settingsBuildDir.trim() === '') {
- const errorMsg = 'Build directory not found in workspace settings. Please set "mlir.litBuildDirectory" in .vscode/settings.json';
+ const suites = config.get<LitSuite[]>('litSuites', workspaceFolder, []);
+
+ if (!Array.isArray(suites) || suites.length === 0) {
+ const errorMsg =
+ 'mlir.litSuites is empty or missing. Add at least one { "source", "build" } entry in workspace settings.';
vscode.window.showErrorMessage(errorMsg);
outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
return null;
}
- // Resolve the build directory path
- let buildDir: string;
- const trimmedPath = settingsBuildDir.trim();
- if (path.isAbsolute(trimmedPath)) {
- // Absolute path - use as is
- buildDir = trimmedPath;
- } else {
- // Relative path - resolve relative to workspace's directory
- const candidatePaths = [
- path.join(workspacePath, trimmedPath),
- ];
-
- // Check if any candidate exists
- let found = false;
- for (const candidate of candidatePaths) {
- if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
- buildDir = candidate;
- found = true;
- break;
- }
+ for (const suite of suites) {
+ if (!suite || typeof suite.source !== 'string' ||
+ typeof suite.build !== 'string') {
+ continue;
}
-
- // If not found, use workspace path as default (will validate existence below)
- if (!found) {
- buildDir = path.join(workspacePath, trimmedPath);
+ const sourceResolved = this.resolveWorkspacePath(
+ workspacePath, suite.source);
+ if (!sourceResolved) {
+ continue;
}
+ if (!fs.existsSync(sourceResolved) ||
+ !fs.statSync(sourceResolved).isDirectory()) {
+ outputChannel.appendLine(
+ `[RunTest] Skip suite (source missing or not a dir): ${sourceResolved}`);
+ continue;
+ }
+ if (!this.isPathUnderDirectory(filePath, sourceResolved)) {
+ continue;
+ }
+ const buildDir =
+ this.resolveWorkspacePath(workspacePath, suite.build);
+ outputChannel.appendLine(
+ `[RunTest] Matched lit suite source="${suite.source}" -> build="${suite.build}"`);
+ return this.finishBuildDirectory(buildDir, outputChannel);
}
- // Validate that the build directory exists
- if (!fs.existsSync(buildDir)) {
- vscode.window.showErrorMessage(
- `Build directory does not exist: ${buildDir}`);
- return null;
- }
-
- if (!fs.statSync(buildDir).isDirectory()) {
- vscode.window.showErrorMessage(
- `Build Path is not a directory: ${buildDir}`);
- return null;
- }
-
- outputChannel.appendLine(`[RunTest] Using build directory: ${buildDir}`);
- return buildDir;
+ const errorMsg =
+ 'No mlir.litSuites entry contains this file. Add or reorder a suite so its source directory includes the test path.';
+ vscode.window.showErrorMessage(errorMsg);
+ outputChannel.appendLine(`[RunTest] ERROR: ${errorMsg}`);
+ return null;
}
/**
@@ -326,8 +367,9 @@ export class RunTestCommand extends Command {
return;
}
- // Get build directory from user
- const buildDir = await this.getBuildDirectory(workspaceFolder, outputChannel);
+ // Resolve build directory from mlir.litSuites (first matching source)
+ const buildDir =
+ await this.getBuildDirectory(workspaceFolder, filePath, outputChannel);
if (!buildDir) {
return;
}
@@ -351,7 +393,7 @@ export class RunTestCommand extends Command {
return;
}
- const {litCommand, pythonEnvActivate} = litSetup;
+ const {litCommand} = litSetup;
outputChannel.appendLine(`[RunTest] Lit command: ${litCommand}`);
outputChannel.appendLine(`[RunTest] Build directory (cwd): ${buildDir}`);
@@ -362,13 +404,7 @@ export class RunTestCommand extends Command {
});
terminal.show();
-
- // Send command with environment activation if needed
- if (pythonEnvActivate) {
- terminal.sendText(`${pythonEnvActivate}${litCommand}`);
- } else {
- terminal.sendText(litCommand);
- }
+ terminal.sendText(litCommand);
outputChannel.appendLine(`[RunTest] Terminal command sent successfully`);
outputChannel.appendLine(`[RunTest] Running MLIR lit on: ${relativePath}`);
>From e4a32be978fca109e0e09da36e70d7fd96e25c5a Mon Sep 17 00:00:00 2001
From: Lan Luo <lanl at nvidia.com>
Date: Sat, 11 Apr 2026 12:25:31 -0700
Subject: [PATCH 4/4] add mlir: Dump output cmd
---
mlir/utils/vscode/package.json | 10 +-
.../vscode/src/MLIR/commands/dumpOutput.ts | 301 ++++++++++++++++++
mlir/utils/vscode/src/MLIR/mlir.ts | 2 +
3 files changed, 312 insertions(+), 1 deletion(-)
create mode 100644 mlir/utils/vscode/src/MLIR/commands/dumpOutput.ts
diff --git a/mlir/utils/vscode/package.json b/mlir/utils/vscode/package.json
index 8fe2164f20dce..c03b42d40f729 100644
--- a/mlir/utils/vscode/package.json
+++ b/mlir/utils/vscode/package.json
@@ -5,7 +5,6 @@
"version": "0.0.14",
"publisher": "llvm-vs-code-extensions",
"homepage": "https://mlir.llvm.org/",
- "icon": "icon.png",
"engines": {
"vscode": "^1.75.0"
},
@@ -251,6 +250,10 @@
{
"command": "mlir.runTest",
"title": "mlir:Run Test"
+ },
+ {
+ "command": "mlir.dumpOutput",
+ "title": "mlir: Dump output"
}
],
"menus": {
@@ -264,6 +267,11 @@
"command": "mlir.runTest",
"group": "z_commands",
"when": "editorLangId == mlir"
+ },
+ {
+ "command": "mlir.dumpOutput",
+ "group": "z_commands",
+ "when": "editorLangId == mlir"
}
]
}
diff --git a/mlir/utils/vscode/src/MLIR/commands/dumpOutput.ts b/mlir/utils/vscode/src/MLIR/commands/dumpOutput.ts
new file mode 100644
index 0000000000000..8be59f7227781
--- /dev/null
+++ b/mlir/utils/vscode/src/MLIR/commands/dumpOutput.ts
@@ -0,0 +1,301 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import * as vscode from 'vscode';
+import {spawn} from 'child_process';
+
+import {Command} from '../../command';
+import {MLIRContext} from '../../mlirContext';
+import * as config from '../../config';
+
+/** One entry in `mlir.litSuites`. */
+interface LitSuite {
+ source: string;
+ build: string;
+}
+
+export class DumpOutputCommand extends Command {
+ constructor(context: MLIRContext) { super('mlir.dumpOutput', context); }
+
+ /**
+ * Walk up from startDir until a directory containing CMakeCache.txt is
+ * found. Returns that directory, or null if the filesystem root is reached.
+ */
+ private findCmakeBuildRoot(startDir: string): string|null {
+ let current = path.normalize(startDir);
+ while (true) {
+ if (fs.existsSync(path.join(current, 'CMakeCache.txt'))) {
+ return current;
+ }
+ const parent = path.dirname(current);
+ if (parent === current) {
+ return null; // reached filesystem root
+ }
+ current = parent;
+ }
+ }
+
+ /**
+ * True if filePath is inside (or equal to) dir.
+ */
+ private isPathUnderDirectory(filePath: string, dir: string): boolean {
+ const normFile = path.normalize(filePath);
+ const normDir = path.normalize(dir);
+ const rel = path.relative(normDir, normFile);
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
+ }
+
+ /**
+ * Parse all // RUN: lines from fileContent, handling backslash continuations.
+ * Returns one logical command string per RUN directive.
+ */
+ private extractRunLines(fileContent: string): string[] {
+ const lines = fileContent.split(/\r?\n/);
+ const result: string[] = [];
+ const runPrefix = /^\s*\/\/\s*RUN:\s*/;
+
+ let i = 0;
+ while (i < lines.length) {
+ const line = lines[i];
+ const match = runPrefix.exec(line);
+ if (!match) {
+ i++;
+ continue;
+ }
+
+ // Strip the "// RUN:" prefix.
+ let logical = line.slice(match[0].length);
+
+ // Follow backslash continuations. In LLVM/MLIR convention each
+ // continuation line also starts with "// RUN:", so strip that same
+ // prefix from every continuation line.
+ while (logical.endsWith('\\')) {
+ logical = logical.slice(0, -1); // strip trailing backslash
+ i++;
+ if (i >= lines.length) break;
+ const next = lines[i];
+ const contMatch = runPrefix.exec(next);
+ logical += contMatch ? next.slice(contMatch[0].length) : next;
+ }
+
+ result.push(logical.trim());
+ i++;
+ }
+ return result;
+ }
+
+ /**
+ * Substitute %s and %S in a raw RUN line. Returns null if any unresolvable
+ * substitution variable remains after replacement.
+ */
+ private applyLitSubstitutions(rawLine: string,
+ filePath: string): string|null {
+ const fileDir = path.dirname(filePath);
+
+ // Quote a path if it contains spaces.
+ const q = (p: string) => p.includes(' ') ? `"${p}"` : p;
+
+ let result = rawLine;
+ result = result.replace(/%%/g, '\x00'); // temporarily hide %%
+ result = result.replace(/%s/g, q(filePath));
+ result = result.replace(/%S/g, q(fileDir));
+ result = result.replace(/\x00/g, '%'); // restore %
+
+ // Check for remaining unresolved substitutions.
+ if (/%((\{[^}]*\})|[a-zA-Z])/.test(result)) {
+ return null;
+ }
+ return result;
+ }
+
+ /**
+ * Split a pipeline on | and drop all FileCheck stages.
+ * Returns null if no non-FileCheck stages remain.
+ */
+ private stripFileCheckStages(pipeline: string): string|null {
+ const stages = pipeline.split('|');
+ const kept = stages.filter(stage => {
+ const first = stage.trim().split(/\s+/)[0] ?? '';
+ return path.basename(first) !== 'FileCheck';
+ });
+ if (kept.length === 0) return null;
+ return kept.join('|').trim();
+ }
+
+ /**
+ * Run a shell pipeline, returning captured stdout and the exit code.
+ * stderr is forwarded live to outputChannel.
+ */
+ private runPipeline(pipeline: string, cmakeBinDir: string|null,
+ outputChannel: vscode.OutputChannel):
+ Promise<{stdout: string, exitCode: number}> {
+ return new Promise((resolve) => {
+ const env = {...process.env};
+ if (cmakeBinDir) {
+ env['PATH'] = cmakeBinDir + path.delimiter + (env['PATH'] ?? '');
+ }
+
+ let stdout = '';
+ const child = spawn(pipeline, [], {shell : true, env});
+
+ child.stdout?.on('data', (data: Buffer) => { stdout += data.toString(); });
+ child.stderr?.on(
+ 'data',
+ (data: Buffer) => { outputChannel.append(data.toString()); });
+
+ child.on('close', (code: number|null) => {
+ resolve({stdout, exitCode : code ?? -1});
+ });
+ child.on('error', (err: Error) => {
+ outputChannel.appendLine(
+ `[DumpOutput] spawn error: ${err.message}`);
+ resolve({stdout : '', exitCode : -1});
+ });
+ });
+ }
+
+ async execute() {
+ let outputChannel = this.context.outputChannel;
+ if (!outputChannel) {
+ outputChannel = vscode.window.createOutputChannel('MLIR');
+ }
+ outputChannel.show(true);
+ outputChannel.clear();
+ outputChannel.appendLine('=== Dump Output ===');
+
+ // Validate active editor.
+ const editor = vscode.window.activeTextEditor;
+ if (!editor) {
+ vscode.window.showErrorMessage('No active editor');
+ return;
+ }
+ if (editor.document.languageId !== 'mlir') {
+ vscode.window.showErrorMessage(
+ 'Current file is not an MLIR file. Please open a .mlir file first.');
+ return;
+ }
+ if (editor.document.uri.scheme !== 'file') {
+ vscode.window.showErrorMessage('File must be saved to disk');
+ return;
+ }
+
+ const fileUri = editor.document.uri;
+ const workspaceFolder = vscode.workspace.getWorkspaceFolder(fileUri);
+ if (!workspaceFolder) {
+ vscode.window.showErrorMessage(
+ 'No workspace folder found. Please open a workspace.');
+ return;
+ }
+
+ const filePath = fileUri.fsPath;
+ const dumpPath = filePath + '.dump';
+
+ // Resolve cmake bin dir from mlir.litSuites for PATH augmentation.
+ let cmakeBinDir: string|null = null;
+ const suites = config.get<LitSuite[]>('litSuites', workspaceFolder, []);
+ if (Array.isArray(suites)) {
+ for (const suite of suites) {
+ if (!suite?.source || !suite?.build) continue;
+ const workspacePath = workspaceFolder.uri.fsPath;
+ const sourceResolved = path.isAbsolute(suite.source) ?
+ suite.source :
+ path.join(workspacePath, suite.source);
+ if (!this.isPathUnderDirectory(filePath, sourceResolved)) continue;
+ const buildResolved = path.isAbsolute(suite.build) ?
+ suite.build :
+ path.join(workspacePath, suite.build);
+ const cmakeRoot = this.findCmakeBuildRoot(buildResolved);
+ if (cmakeRoot) {
+ cmakeBinDir = path.join(cmakeRoot, 'bin');
+ outputChannel.appendLine(
+ `[DumpOutput] cmake build root: ${cmakeRoot}`);
+ outputChannel.appendLine(
+ `[DumpOutput] prepending to PATH: ${cmakeBinDir}`);
+ } else {
+ outputChannel.appendLine(
+ `[DumpOutput] WARN: CMakeCache.txt not found above ${
+ buildResolved}, falling back to system PATH`);
+ }
+ break;
+ }
+ }
+ if (!cmakeBinDir) {
+ outputChannel.appendLine(
+ '[DumpOutput] No matching litSuite found; using system PATH');
+ }
+
+ // Parse and process RUN lines.
+ const content = editor.document.getText();
+ const rawLines = this.extractRunLines(content);
+ if (rawLines.length === 0) {
+ vscode.window.showInformationMessage('No RUN: lines found in this file.');
+ return;
+ }
+
+ const runnableLines: string[] = [];
+ for (const rawLine of rawLines) {
+ const substituted = this.applyLitSubstitutions(rawLine, filePath);
+ if (substituted === null) {
+ outputChannel.appendLine(
+ `[DumpOutput] WARN: skipping (unresolvable substitution): ${
+ rawLine}`);
+ continue;
+ }
+ const stripped = this.stripFileCheckStages(substituted);
+ if (stripped === null) {
+ outputChannel.appendLine(
+ `[DumpOutput] WARN: skipping (all stages are FileCheck): ${
+ substituted}`);
+ continue;
+ }
+ runnableLines.push(stripped);
+ }
+
+ if (runnableLines.length === 0) {
+ vscode.window.showInformationMessage(
+ 'All RUN: lines were skipped (unresolvable substitutions or FileCheck-only).');
+ return;
+ }
+
+ // Execute pipelines and collect stdout.
+ const outputChunks: string[] = [];
+ const n = runnableLines.length;
+ for (let i = 0; i < n; i++) {
+ const pipeline = runnableLines[i];
+ outputChannel.appendLine(
+ `[DumpOutput] Running (${i + 1}/${n}): ${pipeline}`);
+ const {stdout, exitCode} =
+ await this.runPipeline(pipeline, cmakeBinDir, outputChannel);
+ if (exitCode !== 0) {
+ outputChannel.appendLine(
+ `[DumpOutput] WARN: exit code ${exitCode} for pipeline ${i + 1}`);
+ }
+ const prefix = n > 1 ? `// --- RUN ${i + 1} ---\n` : '';
+ outputChunks.push(prefix + stdout);
+ }
+
+ const combined = outputChunks.join('\n');
+
+ // Write dump file.
+ try {
+ await fs.promises.writeFile(dumpPath, combined, 'utf8');
+ outputChannel.appendLine(`[DumpOutput] Written: ${dumpPath}`);
+ } catch (e: any) {
+ vscode.window.showErrorMessage(
+ `Failed to write dump file: ${e.message}`);
+ return;
+ }
+
+ // Open dump file in editor with mlir language.
+ try {
+ const doc =
+ await vscode.workspace.openTextDocument(vscode.Uri.file(dumpPath));
+ await vscode.window.showTextDocument(doc, {preview : false});
+ await vscode.languages.setTextDocumentLanguage(doc, 'mlir');
+ } catch (e: any) {
+ outputChannel.appendLine(
+ `[DumpOutput] WARN: could not open dump file: ${e.message}`);
+ }
+
+ outputChannel.appendLine('[DumpOutput] Done.');
+ }
+}
diff --git a/mlir/utils/vscode/src/MLIR/mlir.ts b/mlir/utils/vscode/src/MLIR/mlir.ts
index 8ba6553beb3cc..1974ed2300ceb 100644
--- a/mlir/utils/vscode/src/MLIR/mlir.ts
+++ b/mlir/utils/vscode/src/MLIR/mlir.ts
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
import {MLIRContext} from '../mlirContext';
import {registerMLIRBytecodeExtensions} from './bytecodeProvider';
+import {DumpOutputCommand} from './commands/dumpOutput';
import {RunTestCommand} from './commands/runTest';
/**
@@ -11,4 +12,5 @@ export function registerMLIRExtensions(context: vscode.ExtensionContext,
mlirContext: MLIRContext) {
registerMLIRBytecodeExtensions(context, mlirContext);
context.subscriptions.push(new RunTestCommand(mlirContext));
+ context.subscriptions.push(new DumpOutputCommand(mlirContext));
}
More information about the Mlir-commits
mailing list