Repository: compiler-explorer/compiler-explorer
Pull request #7230 (2024-12-17 11:02:26)

Opt-remarks for gcc
![gccoptrem](https://github.com/user-attachments/assets/4662c5d1-3a4e-443b-a989-bc4dc109e90b)



Patch:
diff --git a/etc/config/c++.amazonwin.properties b/etc/config/c++.amazonwin.properties
index 5073e44b2..589664554 100644
--- a/etc/config/c++.amazonwin.properties
+++ b/etc/config/c++.amazonwin.properties
@@ -69,7 +69,7 @@ compiler.mingw64_ucrt_clang_1403.exe=Z:/compilers/mingw-w64-11.3.0-14.0.3-10.0.0
 compiler.mingw64_ucrt_clang_1403.semver=14.0.3
 
 group.vcpp.compilers=&vcpp_x86:&vcpp_x64:&vcpp_arm64
-group.vcpp.options=/EHsc /utf-8
+group.vcpp.options=/EHsc /source-charset:utf-8
 group.vcpp.compilerType=win32-vc
 group.vcpp.needsMulti=false
 group.vcpp.includeFlag=/I
diff --git a/etc/config/c.amazonwin.properties b/etc/config/c.amazonwin.properties
index 0b5542f19..f5b1f5da8 100644
--- a/etc/config/c.amazonwin.properties
+++ b/etc/config/c.amazonwin.properties
@@ -50,7 +50,7 @@ compiler.cmingw64_ucrt_clang_1403.exe=Z:/compilers/mingw-w64-11.3.0-14.0.3-10.0.
 compiler.cmingw64_ucrt_clang_1403.semver=14.0.3
 
 group.vc.compilers=&vc_x86:&vc_x64:&vc_arm64
-group.vc.options=/EHsc /utf-8
+group.vc.options=/EHsc /source-charset:utf-8
 group.vc.compilerType=win32-vc
 group.vc.needsMulti=false
 group.vc.includeFlag=/I
diff --git a/etc/config/rust.amazon.properties b/etc/config/rust.amazon.properties
index abcff166a..91d720058 100644
--- a/etc/config/rust.amazon.properties
+++ b/etc/config/rust.amazon.properties
@@ -205,7 +205,6 @@ group.rustgcc.compilers=&gcc86:&gcccross
 group.rustgcc.supportsBinary=true
 group.rustgcc.supportsBinaryObject=true
 group.rustgcc.compilerType=gccrs
-group.rustgcc.versionFlag=--version
 group.rustgcc.isSemVer=true
 group.rustgcc.unwiseOptions=-march=native
 ## needed, until its support is on-par with rustc (at some arbitrary version).
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/lib/compilers/win32.ts b/lib/compilers/win32.ts
index 85db574f3..14966ac63 100644
--- a/lib/compilers/win32.ts
+++ b/lib/compilers/win32.ts
@@ -125,7 +125,7 @@ export class Win32Compiler extends BaseCompiler {
         }
 
         userOptions = this.filterUserOptions(userOptions) || [];
-        [options, overrides] = this.fixIncompatibleOptions(options, userOptions, overrides);
+        this.fixIncompatibleOptions(options, userOptions, overrides);
         this.changeOptionsBasedOnOverrides(options, overrides);
 
         return options.concat(
@@ -140,20 +140,6 @@ export class Win32Compiler extends BaseCompiler {
         );
     }
 
-    override fixIncompatibleOptions(
-        options: string[],
-        userOptions: string[],
-        overrides: ConfiguredOverrides,
-    ): [string[], ConfiguredOverrides] {
-        // If userOptions contains anything starting with /source-charset or /execution-charset, remove /utf-8 from options
-        if (
-            userOptions.some(option => option.startsWith('/source-charset') || option.startsWith('/execution-charset'))
-        ) {
-            options = options.filter(option => option !== '/utf-8');
-        }
-        return [options, overrides];
-    }
-
     override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string, userOptions?: string[]) {
         if (filters.binary) {
             const mapFilename = outputFilename + '.map';
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...
diff --git a/test/compilers/msvc-test.ts b/test/compilers/msvc-test.ts
deleted file mode 100644
index cdb2b6e64..000000000
--- a/test/compilers/msvc-test.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2024, 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 {describe, expect, it} from 'vitest';
-
-import {Win32VcCompiler} from '../../lib/compilers/index.js';
-import {makeCompilationEnvironment} from '../utils.js';
-
-describe('msvc tests', () => {
-    const languages = {'c++': {id: 'c++'}};
-
-    const info = {
-        exe: 'foobar',
-        remote: true,
-        lang: 'c++',
-        ldPath: [],
-        options: '/EHsc /utf-8',
-    };
-
-    describe('utf8 replacement', async () => {
-        const msvc = new Win32VcCompiler(info as any, makeCompilationEnvironment({languages}));
-        it('should do the normal situation', async () => {
-            const prepared: string[] = msvc.prepareArguments([], {}, {}, 'example.cpp', 'output.s', [], []);
-            expect(prepared).toEqual([
-                '/nologo',
-                '/FA',
-                '/c',
-                '/Faoutput.s',
-                '/Fooutput.s.obj',
-                '/EHsc',
-                '/utf-8',
-                'example.cpp',
-            ]);
-        });
-
-        it('should replace arguments', async () => {
-            const prepared: string[] = msvc.prepareArguments(
-                ['/O2', '/source-charset:windows-1252'],
-                {},
-                {},
-                'example.cpp',
-                'output.s',
-                [],
-                [],
-            );
-            expect(prepared).toEqual([
-                '/nologo',
-                '/FA',
-                '/c',
-                '/Faoutput.s',
-                '/Fooutput.s.obj',
-                '/EHsc',
-                '/O2',
-                '/source-charset:windows-1252',
-                'example.cpp',
-            ]);
-        });
-    });
-});