Repository: compiler-explorer/compiler-explorer
Pull request #7235 (2024-12-20 01:34:17)

Add Monaco LangConfiguration to GPU languages
I realize basic things like `ctrl`+`/` and auto complete `}` for my `{` were missing from HLSL/GLSL/Slang

After digging, found that it was just a lack of setting `setLanguageConfiguration` for the monaco-editor for each of them

Confirmed the config in `node_modules/monaco-editor/esm/vs/basic-languages/cpp/cpp.js` suites the 3 languages (as they are also all derived from C based languages) 


Patch:
diff --git a/Makefile b/Makefile
index d21735146..e8657b854 100644
--- a/Makefile
+++ b/Makefile
@@ -37,8 +37,8 @@ info: node-installed ## print out some useful variables
 # disassemblers are needed for local deploys: #4225
 .PHONY: scripts
 scripts:
-	mkdir --parents out/dist/etc/scripts/disasms
-	cp --recursive --update etc/scripts/disasms/* out/dist/etc/scripts/disasms
+	mkdir -p out/dist/etc/scripts/disasms
+	cp -R --update etc/scripts/disasms/* out/dist/etc/scripts/disasms
 
 .PHONY: prereqs
 prereqs: $(NODE_MODULES)
diff --git a/lib/base-compiler.ts b/lib/base-compiler.ts
index 4381c6f77..67c3aeb34 100644
--- a/lib/base-compiler.ts
+++ b/lib/base-compiler.ts
@@ -123,6 +123,7 @@ import type {PropertyGetter} from './properties.interfaces.js';
 import {propsFor} from './properties.js';
 import {HeaptrackWrapper} from './runtime-tools/heaptrack-wrapper.js';
 import {LibSegFaultHelper} from './runtime-tools/libsegfault-helper.js';
+import {SentryCapture} from './sentry.js';
 import * as StackUsage from './stack-usage-transformer.js';
 import {
     clang_style_sysroot_flag,
@@ -3025,8 +3026,8 @@ export class BaseCompiler {
 
         if (result.optPath) {
             delete result.optPath;
-            result.optOutput = optOutput;
         }
+        result.optOutput = optOutput;
 
         if (result.stackUsagePath) {
             delete result.stackUsagePath;
@@ -3136,12 +3137,77 @@ export class BaseCompiler {
         return await demangler.process(result);
     }
 
-    async processOptOutput(optPath: string) {
-        const optRemarksRaw = await fs.readFile(optPath, 'utf8');
-        const output: OptRemark[] = processRawOptRemarks(optRemarksRaw, this.compileFilename);
+    processGccOptInfo(stderr: ResultLine[], compileFileName: string): {remarks: OptRemark[]; newStdErr: ResultLine[]} {
+        const remarks: OptRemark[] = [];
+        const nonRemarkStderr: ResultLine[] = [];
 
-        if (this.compiler.demangler) {
-            const result = JSON.stringify(output, null, 4);
+        // example stderr lines:
+        // <source>:3:20: optimized: loop vectorized using 8 byte vectors
+        // <source>: 2: 6: note: vectorized 1 loops in function.
+        // <source>:11:13: missed: statement clobbers memory: somefunc (&i);
+        const remarkRegex = /^(.*?):\s*(\d+):\s*(\d+): (.*?): (.*)$/;
+
+        const mapOptType = (type: string, line: ResultLine): 'Missed' | 'Passed' | 'Analysis' => {
+            if (type === 'missed') return 'Missed';
+            if (type === 'optimized') return 'Passed';
+            if (type === 'note') return 'Analysis';
+
+            // Did we miss any types?
+            SentryCapture(line, `Unexpected opt type: ${type}`);
+            return 'Analysis';
+        };
+
+        for (const line of stderr) {
+            const match = line.text.match(remarkRegex);
+            if (match) {
+                const [_, file, lineNum, colNum, type, message] = match;
+                if (!file || (file !== '<source>' && !file.includes(compileFileName))) continue;
+
+                // convert to llvm-emitted OptRemark format, just because it was here first
+                remarks.push({
+                    DebugLoc: {
+                        File: file,
+                        // Could use line.tag for these too:
+                        Line: parseInt(lineNum, 10),
+                        Column: parseInt(colNum, 10),
+                    },
+                    optType: mapOptType(type, line),
+                    displayString: message,
+                    // TODO: make these optional?
+                    Function: '',
+                    Pass: '',
+                    Name: '',
+                    Args: [],
+                });
+            } else {
+                nonRemarkStderr.push(line);
+            }
+        }
+        // We omit remark lines from stderr to avoid it causing red squigglies in the source pane
+        return {remarks: remarks, newStdErr: nonRemarkStderr};
+    }
+
+    async processOptOutput(compilationRes: CompilationResult) {
+        // The distinction between clang and gcc opt remarks is a bit ad-hoc. A cleaner way might have been
+        // to override processOptOutput in ClangCompiler and GCCCompiler, but that would have required having
+        // all llvm-based compilers inherit ClangCompiler and all gcc-based ones inherit GCCCompiler.
+        // Might be a good idea to refactor this some day.
+
+        let remarks: OptRemark[] = [];
+        if (this.compiler.optArg && this.compiler.optArg === '-fopt-info-all') {
+            // gcc-like
+            ({remarks, newStdErr: compilationRes.stderr} = this.processGccOptInfo(
+                compilationRes.stderr,
+                this.compileFilename,
+            ));
+        } else if (compilationRes.optPath) {
+            // clang-like
+            const optRemarksRaw = await fs.readFile(compilationRes.optPath, 'utf8');
+            remarks = processRawOptRemarks(optRemarksRaw, this.compileFilename);
+        }
+
+        if (remarks.length > 0 && this.compiler.demangler) {
+            const result = JSON.stringify(remarks, null, 4);
             const demangleResult: UnprocessedExecResult = await this.exec(
                 this.compiler.demangler,
                 [...this.compiler.demanglerArgs, '-n'],
@@ -3156,8 +3222,7 @@ export class BaseCompiler {
                 }
             }
         }
-
-        return output;
+        return remarks;
     }
 
     async processStackUsageOutput(suPath: string): Promise<StackUsage.StackUsageInfo[]> {
@@ -3342,7 +3407,7 @@ but nothing was dumped. Possible causes are:
     async postProcess(result: CompilationResult, outputFilename: string, filters: ParseFiltersAndOutputOptions) {
         const postProcess = _.compact(this.compiler.postProcess);
         const maxSize = this.env.ceProps('max-asm-size', 64 * 1024 * 1024);
-        const optPromise = result.optPath ? this.processOptOutput(unwrap(result.optPath)) : ([] as OptRemark[]);
+        const optPromise = this.processOptOutput(result);
         const stackUsagePromise = result.stackUsagePath
             ? this.processStackUsageOutput(result.stackUsagePath)
             : ([] as StackUsage.StackUsageInfo[]);
diff --git a/lib/compilers/argument-parsers.ts b/lib/compilers/argument-parsers.ts
index 016a8ce38..ac240a140 100644
--- a/lib/compilers/argument-parsers.ts
+++ b/lib/compilers/argument-parsers.ts
@@ -180,6 +180,10 @@ export class GCCParser extends BaseParser {
         if (this.hasSupport(options, '-fverbose-asm')) {
             compiler.compiler.supportsVerboseAsm = true;
         }
+        if (this.hasSupport(options, '-fopt-info')) {
+            compiler.compiler.optArg = '-fopt-info-all';
+            compiler.compiler.supportsOptOutput = true;
+        }
         // This check is not infallible, but takes care of Rust and Swift being picked up :)
         if (_.find(keys, key => key.startsWith('-fdump-'))) {
             compiler.compiler.supportsGccDump = true;
diff --git a/static/modes/glsl-mode.ts b/static/modes/glsl-mode.ts
index 7a6533bb8..1384d547a 100644
--- a/static/modes/glsl-mode.ts
+++ b/static/modes/glsl-mode.ts
@@ -737,5 +737,6 @@ function definition(): monaco.languages.IMonarchLanguage {
 
 monaco.languages.register({id: 'glsl'});
 monaco.languages.setMonarchTokensProvider('glsl', definition());
+monaco.languages.setLanguageConfiguration('glsl', cpp.conf);
 
 export {};
diff --git a/static/modes/hlsl-mode.ts b/static/modes/hlsl-mode.ts
index 7d78f30c1..e265858f9 100644
--- a/static/modes/hlsl-mode.ts
+++ b/static/modes/hlsl-mode.ts
@@ -321,5 +321,6 @@ function definition(): monaco.languages.IMonarchLanguage {
 
 monaco.languages.register({id: 'hlsl'});
 monaco.languages.setMonarchTokensProvider('hlsl', definition());
+monaco.languages.setLanguageConfiguration('hlsl', cpp.conf);
 
 export {};
diff --git a/static/modes/slang-mode.ts b/static/modes/slang-mode.ts
index 02b607af5..7b0784e2c 100644
--- a/static/modes/slang-mode.ts
+++ b/static/modes/slang-mode.ts
@@ -268,5 +268,6 @@ function definition(): monaco.languages.IMonarchLanguage {
 
 monaco.languages.register({id: 'slang'});
 monaco.languages.setMonarchTokensProvider('slang', definition());
+monaco.languages.setLanguageConfiguration('slang', cpp.conf);
 
 export {};
diff --git a/static/modes/spirv-mode.ts b/static/modes/spirv-mode.ts
index 34fd2fd84..cdfc1bb8d 100644
--- a/static/modes/spirv-mode.ts
+++ b/static/modes/spirv-mode.ts
@@ -47,7 +47,16 @@ export function definition(): monaco.languages.IMonarchLanguage {
     };
 }
 
+function configuration(): monaco.languages.LanguageConfiguration {
+    return {
+        comments: {
+            lineComment: ';',
+        },
+    };
+}
+
 monaco.languages.register({id: 'spirv'});
 monaco.languages.setMonarchTokensProvider('spirv', definition());
+monaco.languages.setLanguageConfiguration('spirv', configuration());
 
 export {};
diff --git a/static/panes/opt-view.interfaces.ts b/static/panes/opt-view.interfaces.ts
index b3fa0741e..3a271c8d2 100644
--- a/static/panes/opt-view.interfaces.ts
+++ b/static/panes/opt-view.interfaces.ts
@@ -39,12 +39,12 @@ type SourceLocation = {
     Column: number;
 };
 
+// TODO: move from static to interfaces
 export type OptRemark = {
     DebugLoc: SourceLocation;
     Function: string;
     Pass: string;
     Name: string;
-    text: string;
     optType: 'Missed' | 'Passed' | 'Analysis';
     displayString: string;
     Args: Array<object>;
diff --git a/static/panes/pp-view.interfaces.ts b/static/panes/pp-view.interfaces.ts
index 10bcba83f..0c94e5a61 100644
--- a/static/panes/pp-view.interfaces.ts
+++ b/static/panes/pp-view.interfaces.ts
@@ -26,6 +26,7 @@ export interface PPViewState {
     ppOutput: any;
 }
 
+// TODO: move to interfaces
 export type PPOptions = {
     'filter-headers': boolean;
     'clang-format': boolean;


Test patch:
diff --git a/test/base-compiler-tests.ts b/test/base-compiler-tests.ts
index 39aa320f2..6da95af7e 100644
--- a/test/base-compiler-tests.ts
+++ b/test/base-compiler-tests.ts
@@ -32,6 +32,7 @@ import {RustCompiler} from '../lib/compilers/rust.js';
 import {Win32Compiler} from '../lib/compilers/win32.js';
 import * as props from '../lib/properties.js';
 import {splitArguments} from '../shared/common-utils.js';
+import {CompilationResult} from '../types/compilation/compilation.interfaces.js';
 import {CompilerOverrideType, ConfiguredOverrides} from '../types/compilation/compiler-overrides.interfaces.js';
 import {CompilerInfo} from '../types/compiler.interfaces.js';
 
@@ -626,7 +627,7 @@ describe('Compiler execution', () => {
     //     return objdumpTest('llvm', ['-d', 'output', '-l', '-C', '--x86-asm-syntax=intel']);
     // });
 
-    it('should run process opt output', async () => {
+    it('should run process llvm opt output', async () => {
         const test = `--- !Missed
 Pass: inline
 Name: NeverInline
@@ -638,7 +639,8 @@ Args: []
         const dirPath = await compiler.newTempDir();
         const optPath = path.join(dirPath, 'temp.out');
         await fs.writeFile(optPath, test);
-        expect(await compiler.processOptOutput(optPath)).toEqual([
+        const dummyResult: CompilationResult = {optPath: optPath, code: 0, stdout: [], stderr: [], timedOut: false};
+        expect(await compiler.processOptOutput(dummyResult)).toEqual([
             {
                 Args: [],
                 DebugLoc: {Column: 21, File: 'example.cpp', Line: 4},
@@ -651,6 +653,59 @@ Args: []
         ]);
     });
 
+    it('should run process gcc opt output', async () => {
+        const test = [
+            {
+                text: '<source>:5:9: optimized: loop with 1 iterations completely unrolled (header execution count 78082503)',
+            },
+            {text: '<source>:3:6: note: ***** Analysis failed with vector mode V4SI'},
+            {
+                text: '<source>:11:6: missed: splitting region at control altering definition _44 = std::basic_filebuf<char>::open (&fs._M_filebuf, "myfile", 16);',
+            },
+            {
+                text: '/opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/basic_ios.h:466:59: missed: statement clobbers memory: std::ios_base::ios_base (&MEM[(struct basic_ios *)&fs + 248B].D.46591);',
+            },
+        ];
+        const dummyResult: CompilationResult = {
+            code: 0,
+            stdout: [],
+            stderr: test,
+            timedOut: false,
+        };
+        const tmpCompiler = compiler;
+        tmpCompiler.compiler.optArg = '-fopt-info-all';
+        expect(await tmpCompiler.processOptOutput(dummyResult)).toEqual([
+            {
+                Args: [],
+                DebugLoc: {File: '<source>', Line: 5, Column: 9},
+                Function: '',
+                Name: '',
+                Pass: '',
+                displayString: 'loop with 1 iterations completely unrolled (header execution count 78082503)',
+                optType: 'Passed',
+            },
+            {
+                Args: [],
+                DebugLoc: {File: '<source>', Line: 3, Column: 6},
+                Function: '',
+                Name: '',
+                Pass: '',
+                displayString: '***** Analysis failed with vector mode V4SI',
+                optType: 'Analysis',
+            },
+            {
+                Args: [],
+                DebugLoc: {File: '<source>', Line: 11, Column: 6},
+                Function: '',
+                Name: '',
+                Pass: '',
+                displayString:
+                    'splitting region at control altering definition _44 = std::basic_filebuf<char>::open (&fs._M_filebuf, "myfile", 16);',
+                optType: 'Missed',
+            },
+        ]);
+    });
+
     it('should normalize extra file path', () => {
         const withDemangler = {...noExecuteSupportCompilerInfo, demangler: 'demangler-exe', demanglerType: 'cpp'};
         const compiler = new BaseCompiler(withDemangler, ce) as any; // to get to the protected...