Repository: compiler-explorer/compiler-explorer
Pull request #7716 (2025-05-23 00:24:25)

[mojo-lang] Fixing LLVM IR panel due to calling the mojo compiler with incorrect args.
This PR fixes the LLVM IR output for the Mojo compiler integration, ensuring the LLVM IR panel works correctly and no longer shows "Internal error; unable to open output path" errors.


rparolin: @partouf Changes applied to the PR. Thanks for the feedback!  
rparolin: Oops. Sorry. Missed that one.
rparolin: @partouf is there anything I have to do to merge?  The approver merged my previous PRs.
rparolin: Hey @mattgodbolt!
Is there something I'm missing that's preventing this PR from being merged?
Many thanks.

Patch:
diff --git a/etc/config/rust.amazon.properties b/etc/config/rust.amazon.properties
index 5c1b8e8c6..8f4328f9b 100644
--- a/etc/config/rust.amazon.properties
+++ b/etc/config/rust.amazon.properties
@@ -1011,7 +1011,7 @@ libs.zerocopy.versions.0732.path=libzerocopy.rlib
 #################################
 # Installed tools
 
-tools=clippy:ldd:llvm-mcatrunk:llvm-dwarfdumptrunk:osacatrunk:pahole:readelf:nm:strings:rustfmt1436:bloaty11
+tools=clippy:miri:ldd:llvm-mcatrunk:llvm-dwarfdumptrunk:osacatrunk:pahole:readelf:nm:strings:rustfmt1436:bloaty11
 
 tools.clippy.name=Clippy
 # overridden by `runTool`
@@ -1024,6 +1024,13 @@ tools.clippy.includeKey=supportsClippy
 # https://github.com/compiler-explorer/compiler-explorer/pull/7477#pullrequestreview-2662514775
 tools.clippy.exclude=r1280:r1271:r1270:r1260:r1250:r1240:r1230:r1220:r1210:r1200:r1190:r1180:r1170:r1160:r1151:r1140:r1130:r1120:r1110:r1100:r190$:r180$:r170$:r160$:r150$:r140$:r130$:r120$:r110$:r100$
 
+tools.miri.name=Miri nightly
+tools.miri.exe=/opt/compiler-explorer/rust-miri-nightly/bin/miri
+tools.miri.options=--sysroot=/opt/compiler-explorer/rust-miri-nightly/miri-sysroot --color=always
+tools.miri.type=independent
+tools.miri.class=miri-tool
+tools.miri.stdinHint=disabled
+
 tools.ldd.name=ldd
 tools.ldd.exe=/usr/bin/ldd
 tools.ldd.type=postcompilation
diff --git a/lib/base-compiler.ts b/lib/base-compiler.ts
index ba95a2602..90938867f 100644
--- a/lib/base-compiler.ts
+++ b/lib/base-compiler.ts
@@ -960,6 +960,19 @@ export class BaseCompiler {
         });
     }
 
+    getSharedLibraryBinPaths(libraries: SelectedLibraryVersion[], dirPath?: string): string[] {
+        return libraries.flatMap(selectedLib => {
+            const foundVersion = this.findLibVersion(selectedLib);
+            if (!foundVersion) return [];
+
+            const paths: string[] = [];
+            if (this.buildenvsetup && !this.buildenvsetup.extractAllToRoot && dirPath) {
+                paths.push(path.join(dirPath, selectedLib.id, 'bin'));
+            }
+            return paths;
+        });
+    }
+
     protected getSharedLibraryPathsAsArguments(
         libraries: SelectedLibraryVersion[],
         libDownloadPath: string | undefined,
@@ -2168,6 +2181,13 @@ export class BaseCompiler {
             );
         }
 
+        executeParameters.env.PATH = [
+            ...this.getSharedLibraryBinPaths(key.libraries, buildResult.dirPath),
+            executeParameters.env.PATH,
+        ]
+            .filter(Boolean)
+            .join(path.delimiter);
+
         const execTriple = await RemoteExecutionQuery.guessExecutionTripleForBuildresult(buildResult);
         if (!this.compiler.emulated && !matchesCurrentHost(execTriple)) {
             if (await RemoteExecutionQuery.isPossible(execTriple)) {
diff --git a/lib/binaries/win-utils.ts b/lib/binaries/win-utils.ts
index 44a28025a..722488cb6 100644
--- a/lib/binaries/win-utils.ts
+++ b/lib/binaries/win-utils.ts
@@ -101,9 +101,13 @@ export async function copyNeededDlls(
     execoptions: ExecutionOptionsWithEnv,
 ): Promise<void> {
     const winutils = new WinUtils(execFunction, objdumper, execoptions);
-    const dlls = await winutils.get_dlls_used(executableFilename);
-    for (const dll of dlls) {
-        const infolder = path.join(dirPath, path.basename(dll));
-        await fs.copyFile(dll, infolder);
+    try {
+        const dlls = await winutils.get_dlls_used(executableFilename);
+        for (const dll of dlls) {
+            const infolder = path.join(dirPath, path.basename(dll));
+            await fs.copyFile(dll, infolder);
+        }
+    } catch (e) {
+        logger.error(`Error while retreiving or copying dll dependencies of ${executableFilename}`);
     }
 }
diff --git a/lib/compilers/mojo.ts b/lib/compilers/mojo.ts
index 6f15c26ca..5f1a8bef5 100644
--- a/lib/compilers/mojo.ts
+++ b/lib/compilers/mojo.ts
@@ -22,9 +22,11 @@
 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 // POSSIBILITY OF SUCH DAMAGE.
 
+import fs from 'node:fs/promises';
 import path from 'node:path';
 import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
 import {BaseCompiler} from '../base-compiler.js';
+import {changeExtension} from '../utils.js';
 
 export class MojoCompiler extends BaseCompiler {
     static get key() {
@@ -34,19 +36,21 @@ export class MojoCompiler extends BaseCompiler {
     constructor(info, env) {
         super(info, env);
         this.delayCleanupTemp = true;
+        this.compiler.supportsOptOutput = true;
         this.compiler.supportsIrView = true;
-        this.compiler.irArg = ['--emit=llvm'];
+        this.compiler.irArg = ['--emit', 'llvm'];
+        this.compiler.minIrArgs = ['--emit=llvm'];
     }
 
     override getOutputFilename(dirPath: string, inputFileBase: string) {
         // This method tells CE where to find the assembly output
-        const outputPath = path.join(dirPath, 'example.s');
+        const outputPath = path.join(dirPath, inputFileBase + '.s');
         return outputPath;
     }
 
     override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string, userOptions: string[]) {
         if (filters.binary) return ['build'];
-        return ['build', '--emit=asm'];
+        return ['build', '--emit=asm', '-o', outputFilename];
     }
 
     override getSharedLibraryPathsAsArguments() {
@@ -54,6 +58,41 @@ export class MojoCompiler extends BaseCompiler {
     }
 
     override getExecutableFilename(dirPath: string, outputFilebase: string) {
-        return path.join(dirPath, 'example');
+        const executablePath = path.join(dirPath, 'example');
+        return executablePath;
+    }
+
+    override getIrOutputFilename(inputFilename: string): string {
+        return inputFilename.replace(/\.mojo$/, '.ll');
+    }
+
+    override async generateIR(
+        inputFilename: string,
+        options: string[],
+        irOptions: any,
+        produceCfg: boolean,
+        filters: any,
+    ) {
+        // Remove -o/--emit and their values from options
+        const filteredOptions: string[] = options.filter(
+            (opt, idx, arr) =>
+                opt !== '-o' &&
+                opt !== '--emit' &&
+                !opt.startsWith('--emit=') &&
+                arr[idx - 1] !== '-o' &&
+                arr[idx - 1] !== '--emit',
+        );
+        // Compute the .ll output path
+        const llPath = changeExtension(inputFilename, '.ll');
+        const newOptions = [...filteredOptions, '--emit=llvm', '-o', llPath];
+        const execOptions = this.getDefaultExecOptions();
+        execOptions.maxOutput = 1024 * 1024 * 1024;
+        const output = await this.runCompiler(this.compiler.exe, newOptions, this.compileFilename, execOptions);
+        if (output.code !== 0) {
+            console.error('Mojo IR build failed:', newOptions, output.stderr, output.stdout);
+            return {asm: [{text: 'Failed to run compiler to get IR code'}]};
+        }
+        const irText = await fs.readFile(llPath, 'utf8');
+        return {asm: irText.split('\n').map(text => ({text}))};
     }
 }
diff --git a/lib/tooling/_all.ts b/lib/tooling/_all.ts
index 408974a01..650f49ab1 100644
--- a/lib/tooling/_all.ts
+++ b/lib/tooling/_all.ts
@@ -43,3 +43,4 @@ export {TestingTool} from './testing-tool.js';
 export {BloatyTool} from './bloaty-tool.js';
 export {ClippyTool} from './clippy-tool.js';
 export {BrontoRefactorTool} from './bronto-refactor-tool.js';
+export {MiriTool} from './miri-tool.js';
diff --git a/lib/tooling/miri-tool.ts b/lib/tooling/miri-tool.ts
new file mode 100644
index 000000000..267825ae0
--- /dev/null
+++ b/lib/tooling/miri-tool.ts
@@ -0,0 +1,38 @@
+// Copyright (c) 2025, Compiler Explorer Authors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+import type {ResultLine} from '../../types/resultline/resultline.interfaces.js';
+import {parseRustOutput} from '../utils.js';
+
+import {BaseTool} from './base-tool.js';
+
+export class MiriTool extends BaseTool {
+    static get key() {
+        return 'miri-tool';
+    }
+
+    override parseOutput(lines: string, inputFilename?: string, pathPrefix?: string): ResultLine[] {
+        return parseRustOutput(lines, inputFilename, pathPrefix);
+    }
+}


Test patch:
diff --git a/test/filter-tests.ts b/test/filter-tests.ts
index 4b3706392..06eda3d95 100644
--- a/test/filter-tests.ts
+++ b/test/filter-tests.ts
@@ -71,7 +71,7 @@ function testFilter(filename: string, suffix: string, filters: ParseFiltersAndOu
 }
 
 describe('Filter test cases', () => {
-    if (process.platform === 'win32') {
+    if (process.platform === 'win32' || process.platform === 'darwin') {
         it('should skip filter-tests on Windows', () => {
             expect(true).toBe(true);
         });
@@ -93,7 +93,7 @@ describe('Filter test cases', () => {
         }
     });
     describe('Binary, directives, labels and comments', () => {
-        if (process.platform !== 'win32') {
+        if (process.platform !== 'win32' && process.platform !== 'darwin') {
             for (const x of cases) {
                 testFilter(x, '.binary.directives.labels.comments', {
                     binary: true,
@@ -105,7 +105,7 @@ describe('Filter test cases', () => {
         }
     });
     describe('Binary, directives, labels, comments and library code', () => {
-        if (process.platform !== 'win32') {
+        if (process.platform !== 'win32' && process.platform !== 'darwin') {
             for (const x of cases) {
                 if (!x.endsWith('-bin.asm')) continue;
 
@@ -120,7 +120,7 @@ describe('Filter test cases', () => {
         }
     });
     describe('Binary, directives, labels, comments and library code with dontMaskFilenames', () => {
-        if (process.platform !== 'win32') {
+        if (process.platform !== 'win32' && process.platform !== 'darwin') {
             for (const x of cases) {
                 if (!x.endsWith('-bin.asm')) continue;