[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