From adb8bd774a3e01c6f664ed097730f9b8459a78c3 Mon Sep 17 00:00:00 2001 From: Patrick Quist Date: Mon, 22 Apr 2024 18:40:56 +0200 Subject: [PATCH] Dynamic instruction set for documentation (#6173) --- lib/base-compiler.ts | 84 +++++++++++++++++++-- lib/instructionsets.ts | 54 ++++++------- static/panes/compiler.ts | 13 +++- test/base-compiler-tests.ts | 38 ++++++++++ test/instructionsets-tests.ts | 20 +++-- types/compilation/compilation.interfaces.ts | 3 + 6 files changed, 162 insertions(+), 50 deletions(-) diff --git a/lib/base-compiler.ts b/lib/base-compiler.ts index 48db1f92f..1705b3c67 100644 --- a/lib/base-compiler.ts +++ b/lib/base-compiler.ts @@ -71,6 +71,7 @@ import { UnprocessedExecResult, } from '../types/execution/execution.interfaces.js'; import type {CompilerOutputOptions, ParseFiltersAndOutputOptions} from '../types/features/filters.interfaces.js'; +import {InstructionSet} from '../types/instructionsets.js'; import type {Language} from '../types/languages.interfaces.js'; import type {Library, LibraryVersion, SelectedLibraryVersion} from '../types/libraries/libraries.interfaces.js'; import type {ResultLine} from '../types/resultline/resultline.interfaces.js'; @@ -270,16 +271,16 @@ export class BaseCompiler implements ICompiler { if (!this.compiler.instructionSet) { const isets = new InstructionSets(); if (this.buildenvsetup) { - isets - .getCompilerInstructionSetHint(this.buildenvsetup.compilerArch, this.compiler.exe) - .then(res => (this.compiler.instructionSet = res)) - .catch(() => {}); + this.compiler.instructionSet = isets.getCompilerInstructionSetHint( + this.buildenvsetup.compilerArch, + this.compiler.exe, + ); } else { const temp = new BuildEnvSetupBase(this.compiler, this.env); - isets - .getCompilerInstructionSetHint(temp.compilerArch, this.compiler.exe) - .then(res => (this.compiler.instructionSet = res)) - .catch(() => {}); + this.compiler.instructionSet = isets.getCompilerInstructionSetHint( + temp.compilerArch, + this.compiler.exe, + ); } } @@ -474,6 +475,63 @@ export class BaseCompiler implements ICompiler { return undefined; } + getTargetHintFromCompilerArgs(args: string[]): string | undefined { + const allFlags = this.getAllPossibleTargetFlags(); + + if (allFlags.length > 0) { + for (const possibleFlag of allFlags) { + // possible.flags contains either something like ['--target', ''] or ['--target='], we want the flags without + const filteredFlags: string[] = []; + let targetFlagOffset = -1; + for (const [i, flag] of possibleFlag.entries()) { + if (flag.includes(c_value_placeholder)) { + filteredFlags.push(flag.replace(c_value_placeholder, '')); + targetFlagOffset = i; + } else { + filteredFlags.push(flag); + } + } + + if (targetFlagOffset === -1) continue; + + // try to find matching flags in args + let foundFlag = -1; + for (const arg of args) { + if (arg.startsWith(filteredFlags[foundFlag + 1])) { + foundFlag = foundFlag + 1; + } + + if (foundFlag === targetFlagOffset) { + if (arg.length > filteredFlags[foundFlag].length) { + return arg.substring(filteredFlags[foundFlag].length); + } + return arg; + } + } + } + } + + return undefined; + } + + getInstructionSetFromCompilerArgs(args: string[]): InstructionSet { + try { + const archHint = this.getTargetHintFromCompilerArgs(args); + if (archHint) { + const isets = new InstructionSets(); + return isets.getCompilerInstructionSetHint(archHint, this.compiler.exe); + } + } catch (e) { + logger.debug('Unexpected error in getInstructionSetFromCompilerArgs(): ', e); + } + + if (this.compiler.instructionSet) { + return this.compiler.instructionSet; + } else { + return 'amd64'; + } + } + async runCompiler( compiler: string, options: string[], @@ -493,6 +551,7 @@ export class BaseCompiler implements ICompiler { return { ...this.transformToCompilationResult(result, inputFilename), languageId: this.getCompilerResultLanguageId(filters), + instructionSet: this.getInstructionSetFromCompilerArgs(options), }; } @@ -3485,6 +3544,15 @@ but nothing was dumped. Possible causes are: return []; } + getAllPossibleTargetFlags(): string[][] { + const all: string[][] = []; + if (this.compiler.supportsMarch) all.push([`-march=${c_value_placeholder}`]); + if (this.compiler.supportsTargetIs) all.push([`--target=${c_value_placeholder}`]); + if (this.compiler.supportsTarget) all.push(['--target', c_value_placeholder]); + + return all; + } + async getPossibleToolchains(): Promise { return this.env.getPossibleToolchains(); } diff --git a/lib/instructionsets.ts b/lib/instructionsets.ts index bde67e2e2..c20006b14 100644 --- a/lib/instructionsets.ts +++ b/lib/instructionsets.ts @@ -80,15 +80,15 @@ export class InstructionSets { path: ['/msp430-'], }, powerpc: { - target: ['powerpc'], + target: ['powerpc', 'ppc64', 'ppc'], path: ['/powerpc-', '/powerpc64-', '/powerpc64le-'], }, riscv64: { - target: ['rv64'], + target: ['rv64', 'riscv64'], path: ['/riscv64-'], }, riscv32: { - target: ['rv32'], + target: ['rv32', 'riscv32'], path: ['/riscv32-'], }, sh: { @@ -182,35 +182,31 @@ export class InstructionSets { }; } - async getCompilerInstructionSetHint(compilerArch: string | boolean, exe: string): Promise { - return new Promise(resolve => { - if (compilerArch && typeof compilerArch === 'string') { - for (const [instructionSet, method] of Object.entries(this.supported) as [ - InstructionSet, - InstructionSetMethod, - ][]) { - for (const target of method.target) { - if (compilerArch.includes(target)) { - resolve(instructionSet); - return; - } - } - } - } else { - for (const [instructionSet, method] of Object.entries(this.supported) as [ - InstructionSet, - InstructionSetMethod, - ][]) { - for (const path of method.path) { - if (exe.includes(path)) { - resolve(instructionSet); - return; - } + getCompilerInstructionSetHint(compilerArch: string | boolean, exe?: string): InstructionSet { + if (compilerArch && typeof compilerArch === 'string') { + for (const [instructionSet, method] of Object.entries(this.supported) as [ + InstructionSet, + InstructionSetMethod, + ][]) { + for (const target of method.target) { + if (compilerArch.includes(target)) { + return instructionSet; } } } + } else { + for (const [instructionSet, method] of Object.entries(this.supported) as [ + InstructionSet, + InstructionSetMethod, + ][]) { + for (const path of method.path) { + if (exe?.includes(path)) { + return instructionSet; + } + } + } + } - resolve(this.defaultInstructionset); - }); + return this.defaultInstructionset; } } diff --git a/static/panes/compiler.ts b/static/panes/compiler.ts index 1ca2fd655..c2aa56a33 100644 --- a/static/panes/compiler.ts +++ b/static/panes/compiler.ts @@ -176,6 +176,7 @@ export class Compiler extends MonacoPane; private compilerPicker: CompilerPicker; private compiler: CompilerInfo | null; + private recentInstructionSet: InstructionSet | null; private currentLangId: string | null; private filters: Toggles; private optButton: JQuery; @@ -1469,6 +1470,8 @@ export class Compiler extends MonacoPane, filteredCount = 0) { + this.recentInstructionSet = result.instructionSet || null; + const asm = result.asm || this.fakeAsm(''); this.assembly = asm; if (!this.editor.getModel()) return; @@ -3615,7 +3618,10 @@ export class Compiler extends MonacoPane { diff --git a/test/base-compiler-tests.ts b/test/base-compiler-tests.ts index 0ec6ad701..3150dacbe 100644 --- a/test/base-compiler-tests.ts +++ b/test/base-compiler-tests.ts @@ -27,7 +27,9 @@ import {beforeAll, describe, expect, it} from 'vitest'; import {BaseCompiler} from '../lib/base-compiler.js'; import {BuildEnvSetupBase} from '../lib/buildenvsetup/index.js'; import {CompilationEnvironment} from '../lib/compilation-env.js'; +import {ClangCompiler} from '../lib/compilers/clang.js'; import {Win32Compiler} from '../lib/compilers/win32.js'; +import {splitArguments} from '../lib/utils.js'; import {CompilerOverrideType, ConfiguredOverrides} from '../types/compilation/compiler-overrides.interfaces.js'; import {CompilerInfo} from '../types/compiler.interfaces.js'; @@ -714,3 +716,39 @@ describe('getDefaultExecOptions', () => { expect(paths).toEqual(['/usr/local/ninja', '/tmp/p1', '/tmp/p2']); }); }); + +describe('Target hints', () => { + let ce: CompilationEnvironment; + + const noExecuteSupportCompilerInfo = makeFakeCompilerInfo({ + exe: '/usr/bin/clang++', + lang: 'c++', + supportsTargetIs: true, + supportsTarget: true, + ldPath: [], + libPath: [], + extraPath: [], + }); + + beforeAll(() => { + ce = makeCompilationEnvironment({ + languages, + props: { + environmentPassThrough: '', + ninjaPath: '/usr/local/ninja', + }, + }); + }); + + it('Should determine the target for Clang', async () => { + const compiler = new ClangCompiler(noExecuteSupportCompilerInfo, ce); + + const args = + '-gdwarf-4 -g -o output.s -mllvm --x86-asm-syntax=intel -S --gcc-toolchain=/opt/compiler-explorer/gcc-13.2.0 -fcolor-diagnostics -fno-crash-diagnostics --target=riscv64 example.cpp -isystem/opt/compiler-explorer/libs/abseil'; + const argArray = splitArguments(args); + const hint = compiler.getTargetHintFromCompilerArgs(argArray); + expect(hint).toBe('riscv64'); + const iset = await compiler.getInstructionSetFromCompilerArgs(argArray); + expect(iset).toBe('riscv64'); + }); +}); diff --git a/test/instructionsets-tests.ts b/test/instructionsets-tests.ts index 544cd10d9..84f9a630e 100644 --- a/test/instructionsets-tests.ts +++ b/test/instructionsets-tests.ts @@ -26,31 +26,29 @@ import {describe, expect, it} from 'vitest'; import {InstructionSets} from '../lib/instructionsets.js'; -describe('InstructionSets', async () => { - it('should recognize aarch64 for clang target', async () => { +describe('InstructionSets', () => { + it('should recognize aarch64 for clang target', () => { const isets = new InstructionSets(); - await expect( + expect( isets.getCompilerInstructionSetHint('aarch64-linux-gnu', '/opt/compiler-explorer/clang-11.0.1/bin/clang++'), - ).resolves.toEqual('aarch64'); + ).toBe('aarch64'); }); - it('should recognize gcc aarch64 from filepath', async () => { + it('should recognize gcc aarch64 from filepath', () => { const isets = new InstructionSets(); - await expect( + expect( isets.getCompilerInstructionSetHint( false, '/opt/compiler-explorer/arm64/gcc-12.1.0/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu-g++', ), - ).resolves.toEqual('aarch64'); + ).toBe('aarch64'); }); - it('should default to amd64 when not apparent', async () => { + it('should default to amd64 when not apparent', () => { const isets = new InstructionSets(); - await expect( - isets.getCompilerInstructionSetHint(false, '/opt/compiler-explorer/gcc-12.2.0/bin/g++'), - ).resolves.toEqual('amd64'); + expect(isets.getCompilerInstructionSetHint(false, '/opt/compiler-explorer/gcc-12.2.0/bin/g++')).toBe('amd64'); }); }); diff --git a/types/compilation/compilation.interfaces.ts b/types/compilation/compilation.interfaces.ts index ec4f45530..87bf1a0ec 100644 --- a/types/compilation/compilation.interfaces.ts +++ b/types/compilation/compilation.interfaces.ts @@ -31,6 +31,7 @@ import {ParsedAsmResultLine} from '../asmresult/asmresult.interfaces.js'; import {CompilerInfo} from '../compiler.interfaces.js'; import {BasicExecutionResult, ConfiguredRuntimeTools} from '../execution/execution.interfaces.js'; import {ParseFiltersAndOutputOptions} from '../features/filters.interfaces.js'; +import {InstructionSet} from '../instructionsets.js'; import {ResultLine} from '../resultline/resultline.interfaces.js'; import {Artifact, ToolResult} from '../tool.interfaces.js'; @@ -226,6 +227,8 @@ export type CompilationResult = { parsingTime?: number; source?: string; // todo: this is a crazy hack, we should get rid of it + + instructionSet?: InstructionSet; }; export type ExecutionOptions = {