Make argument parsers instances instead of static classes (#8017)

- Made parsers stateful instances instead of shared static state (for
mllvm options). Fixes #8011 as this is caused by multiple clang-based
compilers being run concurrently and stomping over each others' state.
- passes `Compiler` to the constructor, which removes some param passing
- Added some missing awaits
- Tried to get things less dependent on `examples`, only `go` needs it
- Spotted that `zig` c++ might have issues in discovery
- Fly-by fixed a broken go path in ppc64le_gl122
- removed a redundant override in coccinelle
- made the mojo parser actually use the parser it defined
- canonified tablegen's special method
- 

I changed the zig parser too but as best I can tell it was broken before
(the `1` return value from the command it runs:)

```
ubuntu@ip-172-30-0-164:/infra/.deploy$ /opt/compiler-explorer/zig-0.14.1/zig c++ -mllvm --help-list-hidden /infra/.deploy/examples/c++/default.cpp -S -o /tmp/output.s
...
  --x86-use-vzeroupper                                                       - Minimize AVX to SSE transition penalty
  --xcore-max-threads=<number>                                               - Maximum number of threads (for emulation thread-local storage)
/infra/.deploy/examples/c++/default.cpp:1:1: error: FileNotFound
```
return code 1 (means it's not cached)

---------

Co-authored-by: Partouf <partouf@gmail.com>
This commit is contained in:
Matt Godbolt
2025-08-11 12:14:13 -05:00
committed by GitHub
parent bea6e67715
commit 2ae38a0c3f
14 changed files with 450 additions and 409 deletions

View File

@@ -29,6 +29,7 @@ import {CompilerArguments} from './lib/compiler-arguments.js';
import * as Parsers from './lib/compilers/argument-parsers.js';
import {executeDirect} from './lib/exec.js';
import {logger} from './lib/logger.js';
import {BaseParser} from './lib/compilers/argument-parsers.js';
const program = new Command();
program
@@ -90,6 +91,13 @@ class CompilerArgsApp {
execCompilerCached: async (command: string, args: string[]) => {
return executeDirect(command, args, {}, fn => fn);
},
getDefaultExecOptions: () => {
return {
env: process.env,
cwd: process.cwd(),
timeout: 10000,
};
}
};
if (this.parserName === 'juliawrapper') {
@@ -99,22 +107,22 @@ class CompilerArgsApp {
async getPossibleStdvers() {
const parser = this.getParser();
return await parser.getPossibleStdvers(this.compiler);
return await parser.getPossibleStdvers();
}
async getPossibleTargets() {
const parser = this.getParser();
return await parser.getPossibleTargets(this.compiler);
return await parser.getPossibleTargets();
}
async getPossibleEditions() {
const parser = this.getParser();
return await parser.getPossibleEditions(this.compiler);
return await parser.getPossibleEditions();
}
getParser() {
getParser(): BaseParser {
if (compilerParsers[this.parserName as keyof typeof compilerParsers]) {
return compilerParsers[this.parserName as keyof typeof compilerParsers];
return new (compilerParsers[this.parserName as keyof typeof compilerParsers])(this.compiler);
}
console.error('Unknown parser type');
process.exit(1);
@@ -122,7 +130,7 @@ class CompilerArgsApp {
async doTheParsing() {
const parser = this.getParser();
await parser.parse(this.compiler);
await parser.parse();
const options = this.compiler.possibleArguments.possibleArguments;
if (parser.hasSupportStartsWith(options, '--target=')) {
console.log('supportsTargetIs');
@@ -150,6 +158,11 @@ class CompilerArgsApp {
console.log(await this.getPossibleTargets());
console.log('Editions:');
console.log(await this.getPossibleEditions());
console.log('supportsOptOutput:', !!this.compiler.compiler.supportsOptOutput);
console.log('supportsStackUsageOutput', !!this.compiler.compiler.supportsStackUsageOutput);
console.log('optPipeline:', this.compiler.compiler.optPipeline);
console.log('supportsGccDump', !!this.compiler.compiler.supportsGccDump);
}
}

View File

@@ -448,7 +448,7 @@ compiler.ppc64le_gl120.exe=/opt/compiler-explorer/golang-1.20/go/bin/go
compiler.ppc64le_gl120.semver=1.20
compiler.ppc64le_gl121.exe=/opt/compiler-explorer/golang-1.21.13/go/bin/go
compiler.ppc64le_gl121.semver=1.21.13
compiler.ppc64le_gl122.exe=/opt/compiler-explorer/golang-1.22.1)/go/bin/go
compiler.ppc64le_gl122.exe=/opt/compiler-explorer/golang-1.22.1/go/bin/go
compiler.ppc64le_gl122.semver=1.22.12
compiler.ppc64le_gl123.exe=/opt/compiler-explorer/golang-1.23.8/go/bin/go
compiler.ppc64le_gl123.semver=1.23.8

View File

@@ -217,6 +217,7 @@ export class BaseCompiler {
labelNames: [],
});
protected executionEnvironmentClass: any;
protected readonly argParser: BaseParser;
constructor(compilerInfo: PreliminaryCompilerInfo & {disabledFilters?: string[]}, env: CompilationEnvironment) {
// Information about our compiler
@@ -306,6 +307,7 @@ export class BaseCompiler {
}
this.packager = new Packager();
this.argParser = new (this.getArgumentParserClass())(this);
}
copyAndFilterLibraries(allLibraries: Record<string, OptionsHandlerLibrary>, filter: string[]) {
@@ -3598,8 +3600,7 @@ but nothing was dumped. Possible causes are:
async getTargetsAsOverrideValues(): Promise<CompilerOverrideOption[]> {
if (!this.buildenvsetup || !this.buildenvsetup.getCompilerArch()) {
const parserCls = this.getArgumentParserClass();
const targets = await parserCls.getPossibleTargets(this);
const targets = await this.argParser.getPossibleTargets();
return targets.map(target => {
return {
@@ -3612,8 +3613,7 @@ but nothing was dumped. Possible causes are:
}
async getPossibleStdversAsOverrideValues(): Promise<CompilerOverrideOption[]> {
const parser = this.getArgumentParserClass();
return await parser.getPossibleStdvers(this);
return await this.argParser.getPossibleStdvers();
}
async populatePossibleRuntimeTools() {
@@ -3794,7 +3794,7 @@ but nothing was dumped. Possible causes are:
}
return this;
}
const initResult = await this.getArgumentParserClass().parse(this);
const initResult = await this.argParser.parse();
this.possibleArguments.possibleArguments = {};
await this.populatePossibleOverrides();

File diff suppressed because it is too large Load Diff

View File

@@ -133,10 +133,6 @@ export class CoccinelleCCompiler extends BaseCompiler {
return super.getIrOutputFilename(inputFilename, filters);
}
override getArgumentParserClass() {
return super.getArgumentParserClass();
}
override isCfgCompiler() {
return false;
}

View File

@@ -27,6 +27,7 @@ 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';
import {MojoParser} from './argument-parsers.js';
export class MojoCompiler extends BaseCompiler {
static get key() {
@@ -95,4 +96,8 @@ export class MojoCompiler extends BaseCompiler {
const irText = await fs.readFile(llPath, 'utf8');
return {asm: irText.split('\n').map(text => ({text}))};
}
override getArgumentParserClass() {
return MojoParser;
}
}

View File

@@ -117,7 +117,7 @@ export class RustCompiler extends BaseCompiler {
}
override async populatePossibleOverrides() {
const possibleEditions = await RustParser.getPossibleEditions(this);
const possibleEditions = await this.argParser.getPossibleEditions();
if (possibleEditions.length > 0) {
let defaultEdition: undefined | string;
if (!this.compiler.semver || this.isNightly()) {

View File

@@ -26,7 +26,7 @@ export class TableGenCompiler extends BaseCompiler {
}
override async populatePossibleOverrides() {
const possibleActions = await TableGenParser.getPossibleActions(this);
const possibleActions = await this.argParser.getPossibleActions();
if (possibleActions.length > 0) {
this.compiler.possibleOverrides?.push({
name: CompilerOverrideType.action,

View File

@@ -100,6 +100,7 @@ describe('LLVM-mca tool definition', () => {
},
lang: 'analysis',
disabledFilters: 'labels,directives,debugCalls' as any,
exe: 'clang',
});
expect(new AnalysisTool(info, ce).getInfo().disabledFilters).toEqual(['labels', 'directives', 'debugCalls']);
});

View File

@@ -40,13 +40,13 @@ const languages = {
};
const androidJavaInfo = {
exe: null,
exe: 'java',
remote: true,
lang: languages.androidJava.id,
} as unknown as CompilerInfo;
const androidKotlinInfo = {
exe: null,
exe: 'kotlin',
remote: true,
lang: languages.androidKotlin.id,
} as unknown as CompilerInfo;

View File

@@ -129,6 +129,7 @@ describe('Compiler execution', () => {
supportsExecute: true,
supportsBinary: true,
options: '--hello-abc -I"/opt/some thing 1.0/include" -march="magic 8bit"',
exe: 'compiler-exe',
});
const win32CompilerInfo = makeFakeCompilerInfo({
remote: {
@@ -142,6 +143,7 @@ describe('Compiler execution', () => {
supportsExecute: true,
supportsBinary: true,
options: '/std=c++17 /I"C:/program files (x86)/Company name/Compiler 1.2.3/include" /D "MAGIC=magic 8bit"',
exe: 'compiler.exe',
});
const noExecuteSupportCompilerInfo = makeFakeCompilerInfo({
remote: {
@@ -153,6 +155,7 @@ describe('Compiler execution', () => {
lang: 'c++',
ldPath: [],
libPath: [],
exe: 'g++',
});
const someOptionsCompilerInfo = makeFakeCompilerInfo({
remote: {
@@ -167,6 +170,7 @@ describe('Compiler execution', () => {
supportsExecute: true,
supportsBinary: true,
options: '--hello-abc -I"/opt/some thing 1.0/include"',
exe: 'clang++',
});
beforeAll(() => {
@@ -681,6 +685,7 @@ describe('getDefaultExecOptions', () => {
ldPath: [],
libPath: [],
extraPath: ['/tmp/p1', '/tmp/p2'],
exe: 'g++',
});
beforeAll(() => {

View File

@@ -49,13 +49,18 @@ function makeCompiler(stdout?: string, stderr?: string, code?: number) {
describe('option parser', () => {
it('should do nothing for the base parser', async () => {
const compiler = makeCompiler();
await expect(BaseParser.parse(compiler)).resolves.toEqual(compiler);
const parser = new BaseParser(compiler);
await expect(parser.parse()).resolves.toEqual(compiler);
});
it('should handle empty options', async () => {
await expect(BaseParser.getOptions(makeCompiler(), '')).resolves.toEqual({});
const compiler = makeCompiler();
const parser = new BaseParser(compiler);
await expect(parser.getOptions('')).resolves.toEqual({});
});
it('should parse single-dash options', async () => {
await expect(BaseParser.getOptions(makeCompiler('-foo\n'), '')).resolves.toEqual({
const compiler = makeCompiler('-foo\n');
const parser = new BaseParser(compiler);
await expect(parser.getOptions('')).resolves.toEqual({
'-foo': {
description: '',
timesused: 0,
@@ -63,7 +68,9 @@ describe('option parser', () => {
});
});
it('should parse double-dash options', async () => {
await expect(BaseParser.getOptions(makeCompiler('--foo\n'), '')).resolves.toEqual({
const compiler = makeCompiler('--foo\n');
const parser = new BaseParser(compiler);
await expect(parser.getOptions('')).resolves.toEqual({
'--foo': {
description: '',
timesused: 0,
@@ -71,7 +78,9 @@ describe('option parser', () => {
});
});
it('should parse stderr options', async () => {
await expect(BaseParser.getOptions(makeCompiler('', '--bar=monkey\n'), '')).resolves.toEqual({
const compiler = makeCompiler('', '--bar=monkey\n');
const parser = new BaseParser(compiler);
await expect(parser.getOptions('')).resolves.toEqual({
'--bar=monkey': {
description: '',
timesused: 0,
@@ -79,46 +88,56 @@ describe('option parser', () => {
});
});
it('handles non-option text', async () => {
await expect(BaseParser.getOptions(makeCompiler('-foo=123\nthis is a fish\n-badger=123'), '')).resolves.toEqual(
{
'-foo=123': {description: 'this is a fish', timesused: 0},
'-badger=123': {description: '', timesused: 0},
},
);
const compiler = makeCompiler('-foo=123\nthis is a fish\n-badger=123');
const parser = new BaseParser(compiler);
await expect(parser.getOptions('')).resolves.toEqual({
'-foo=123': {description: 'this is a fish', timesused: 0},
'-badger=123': {description: '', timesused: 0},
});
});
it('should ignore if errors occur', async () => {
await expect(BaseParser.getOptions(makeCompiler('--foo\n', '--bar\n', 1), '')).resolves.toEqual({});
const compiler = makeCompiler('--foo\n', '--bar\n', 1);
const parser = new BaseParser(compiler);
await expect(parser.getOptions('')).resolves.toEqual({});
});
});
describe('gcc parser', () => {
it('should handle empty options', async () => {
const result = await GCCParser.parse(makeCompiler());
const compiler = makeCompiler();
const parser = new GCCParser(compiler);
const result = await parser.parse();
expect(result.compiler).not.toHaveProperty('supportsGccDump');
expect(result.compiler.options).toEqual('');
});
it('should handle options', async () => {
const result = await GCCParser.parse(makeCompiler('-masm=intel\n-fdiagnostics-color=[blah]\n-fdump-tree-all'));
const compiler = makeCompiler('-masm=intel\n-fdiagnostics-color=[blah]\n-fdump-tree-all');
const parser = new GCCParser(compiler);
const result = await parser.parse();
expect(result.compiler.supportsGccDump).toBe(true);
expect(result.compiler.supportsIntel).toBe(true);
expect(result.compiler.intelAsm).toEqual('-masm=intel');
expect(result.compiler.options).toEqual('-fdiagnostics-color=always');
});
it('should handle undefined options', async () => {
const result = await GCCParser.parse(makeCompiler('-fdiagnostics-color=[blah]'));
const compiler = makeCompiler('-fdiagnostics-color=[blah]');
const parser = new GCCParser(compiler);
const result = await parser.parse();
expect(result.compiler.options).toEqual('-fdiagnostics-color=always');
});
});
describe('clang parser', () => {
it('should handle empty options', async () => {
const result = await ClangParser.parse(makeCompiler());
const compiler = makeCompiler();
const parser = new ClangParser(compiler);
const result = await parser.parse();
expect(result.compiler.options).toEqual('');
});
it('should handle options', async () => {
const result = await ClangParser.parse(
makeCompiler(' -fno-crash-diagnostics\n -fsave-optimization-record\n -fcolor-diagnostics'),
);
const compiler = makeCompiler(' -fno-crash-diagnostics\n -fsave-optimization-record\n -fcolor-diagnostics');
const parser = new ClangParser(compiler);
const result = await parser.parse();
expect(result.compiler.supportsOptOutput).toBe(true);
expect(result.compiler.optArg).toEqual('-fsave-optimization-record');
expect(result.compiler.options).toContain('-fcolor-diagnostics');
@@ -129,7 +148,9 @@ describe('clang parser', () => {
describe('pascal parser', () => {
it('should handle empty options', async () => {
const result = await PascalParser.parse(makeCompiler());
const compiler = makeCompiler();
const parser = new PascalParser(compiler);
const result = await parser.parse();
expect(result.compiler.options).toEqual('');
});
});
@@ -144,7 +165,8 @@ describe('popular compiler arguments', () => {
});
it('should return 5 arguments', async () => {
const result = await ClangParser.parse(compiler);
const parser = new ClangParser(compiler);
const result = await parser.parse();
expect(result.possibleArguments.getPopularArguments()).toEqual({
'-O<number>': {description: 'Optimization level', timesused: 0},
'-fcolor-diagnostics': {description: '', timesused: 0},
@@ -155,7 +177,8 @@ describe('popular compiler arguments', () => {
});
it('should return arguments except the ones excluded', async () => {
const result = await ClangParser.parse(compiler);
const parser = new ClangParser(compiler);
const result = await parser.parse();
expect(result.possibleArguments.getPopularArguments(['-O3', '--hello'])).toEqual({
'-fcolor-diagnostics': {description: '', timesused: 0},
'-fsave-optimization-record': {description: '', timesused: 0},
@@ -166,7 +189,8 @@ describe('popular compiler arguments', () => {
});
it('should be able to exclude special params with assignments', async () => {
const result = await ClangParser.parse(compiler);
const parser = new ClangParser(compiler);
const result = await parser.parse();
expect(result.possibleArguments.getPopularArguments(['-std=c++14', '-g', '--hello'])).toEqual({
'-O<number>': {description: 'Optimization level', timesused: 0},
'-fcolor-diagnostics': {description: '', timesused: 0},
@@ -188,7 +212,9 @@ describe('VC argument parser', () => {
' /something:<else> Something Else',
' /etc Etcetera',
];
const stdvers = VCParser.extractPossibleStdvers(lines);
const compiler = makeCompiler();
const parser = new VCParser(compiler);
const stdvers = parser.extractPossibleStdvers(lines);
expect(stdvers).toEqual([
{
name: 'c++14: ISO/IEC 14882:2014 (default)',
@@ -221,7 +247,9 @@ describe('ICC argument parser', () => {
' gnu++98 conforms to 1998 ISO C++ standard plus GNU extensions',
'-etc',
];
const stdvers = ICCParser.extractPossibleStdvers(lines);
const compiler = makeCompiler();
const parser = new ICCParser(compiler);
const stdvers = parser.extractPossibleStdvers(lines);
expect(stdvers).toEqual([
{
name: 'c99: conforms to ISO/IEC 9899:1999 standard for C programs',
@@ -255,7 +283,9 @@ describe('TableGen argument parser', () => {
' --gen-x86-mnemonic-tables - Generate X86...',
' --no-warn-on-unused-template-args - Disable...',
];
const actions = TableGenParser.extractPossibleActions(lines);
const compiler = makeCompiler();
const parser = new TableGenParser(compiler);
const actions = parser.extractPossibleActions(lines);
expect(actions).toEqual([
{name: 'gen-attrs: Generate attributes', value: '--gen-attrs'},
{name: 'print-detailed-records: Print full details...', value: '--print-detailed-records'},
@@ -278,7 +308,8 @@ describe('Rust editions parser', () => {
' stable edition is 2024.',
];
const compiler = makeCompiler(lines.join('\n'));
const editions = await RustParser.getPossibleEditions(compiler);
const parser = new RustParser(compiler);
const editions = await parser.getPossibleEditions();
expect(editions).toEqual(['2015', '2018', '2021', '2024', 'future']);
});
@@ -294,7 +325,8 @@ describe('Rust editions parser', () => {
' compiling code.',
];
const compiler = makeCompiler(lines.join('\n'));
const editions = await RustParser.getPossibleEditions(compiler);
const parser = new RustParser(compiler);
const editions = await parser.getPossibleEditions();
expect(editions).toEqual(['2015', '2018']);
});
});
@@ -312,9 +344,10 @@ describe('Rust help message parser', () => {
' -W, --warn OPT Set lint warnings',
];
const compiler = makeCompiler(lines.join('\n'));
await RustParser.parse(compiler);
const parser = new RustParser(compiler);
await parser.parse();
expect(compiler.compiler.supportsTarget).toBe(true);
await expect(RustParser.getOptions(compiler, '--help')).resolves.toEqual({
await expect(parser.getOptions('--help')).resolves.toEqual({
'-l [KIND[:MODIFIERS]=]NAME[:RENAME]': {
description:
'Link the generated crate(s) to the specified native library NAME. The optional KIND can be one of',
@@ -344,9 +377,10 @@ describe('Rust help message parser', () => {
' -W, --warn <LINT> Set lint warnings',
];
const compiler = makeCompiler(lines.join('\n'));
await RustParser.parse(compiler);
const parser = new RustParser(compiler);
await parser.parse();
expect(compiler.compiler.supportsTarget).toBe(true);
await expect(RustParser.getOptions(compiler, '--help')).resolves.toEqual({
await expect(parser.getOptions('--help')).resolves.toEqual({
'-l [<KIND>[:<MODIFIERS>]=]<NAME>[:<RENAME>]': {
description:
'Link the generated crate(s) to the specified native library NAME. The optional KIND can be one of',

View File

@@ -52,7 +52,7 @@ class DummyCompiler extends BaseCompiler {
} as unknown as CompilationEnvironment;
// using c++ as the compiler needs at least one language
const compiler = makeFakeCompilerInfo({lang: 'c++'});
const compiler = makeFakeCompilerInfo({lang: 'c++', exe: 'gcc'});
super(compiler, env);
}

View File

@@ -44,7 +44,7 @@ describe('Pascal', () => {
beforeAll(() => {
const ce = makeCompilationEnvironment({languages});
const info = {
exe: null,
exe: 'pascal-compiler',
remote: true,
lang: languages.pascal.id,
};
@@ -498,7 +498,7 @@ describe('Pascal', () => {
beforeAll(() => {
const ce = makeCompilationEnvironment({languages});
const info = {
exe: null,
exe: 'pascal.exe',
remote: true,
lang: languages.pascal.id,
};