mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 10:33:59 -05:00
for https://github.com/compiler-explorer/compiler-explorer/issues/2331 Hey, so I am currently trying to improve the SPIR-V ecosystem, in-charge of the [SPIR-V Guide](https://github.com/KhronosGroup/SPIRV-Guide), and part of the SPIR-V Working Group. We talked at our last Face-2-Face gathering about getting more support for GPU tooling in something like Compiler Explorer because you have all made this such an amazing tool! I have enjoyed working in this codebase and happy to help keep improving the SPIR-V as people find issues or want enhancements (I was already about to spend time rebuilding Compiler Explorer, so happy to donate my time to a single tool's effort) (For those who haven't spent last few years staring at SPIR-V) - SPIR-V is a an IR (very much like LLVM IR) but for GPUs - SPIR-V can be used for Graphics and Compute - Compute: - Can be tied to things like clang, llvm, mlir, OpenCL, etc - One could take `spirv` as a language and compile it to something like x86 - (Future PRs for this realm of things) - Graphics: - Much harder to actually "compile" to any ISA because other graphics pipeline information is missing that will effect the final ISA - Microsoft has even now planned to move to [SPIR-V for DirectX soon](https://devblogs.microsoft.com/directx/directx-adopting-spir-v/) - Both: - There are many tools that run on SPIR-V regardless (hence why they share the same IR) - Being able to do tooling in compiler explorer would be AMAZING This PR adds `SPIR-V` as a language and a new `SPIRV-Tools` compiler as an initial compiler for it the language (since we already have the SPIRV-Tools repo being pulled in on the infra side!) I personally use `spirv-opt` and `spirv-val` the most and have been testing this locally for a few days and feel it is ready to go. I plan to slowly add more tools (ex. `spirv-fuzz`, `spirv-cross`, `spirv-reflect`, etc) Cheers! (some screenshots of course)  
177 lines
7.4 KiB
TypeScript
177 lines
7.4 KiB
TypeScript
// 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 path from 'path';
|
|
|
|
import type {ExecutionOptionsWithEnv} from '../../types/compilation/compilation.interfaces.js';
|
|
import type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
|
|
import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
|
|
import {BaseCompiler} from '../base-compiler.js';
|
|
import {CompilationEnvironment} from '../compilation-env.js';
|
|
import {logger} from '../logger.js';
|
|
import {SPIRVAsmParser} from '../parsers/asm-parser-spirv.js';
|
|
import * as utils from '../utils.js';
|
|
|
|
// If you want to output SPIR-V, most likely you want SPIRVAsmParser
|
|
//
|
|
// SPIR-V is an IR that targets heterogeneous (like a GPU) and has a "compute" and "graphics" mode.
|
|
// When used for graphics, it inself is not enough info to "compile" down to the GPU's assembly because normally there
|
|
// is other graphics // pipeline information that is required (ex. is the depth test enable/disabled)
|
|
//
|
|
// SPIR-V has a lot of tooling around it to optimize, validate, fuzz, etc. This compiler is only used for tooling.
|
|
export class SPIRVToolsCompiler extends BaseCompiler {
|
|
protected assemblerPath: string;
|
|
protected disassemblerPath: string;
|
|
|
|
static get key() {
|
|
return 'spirv-tools';
|
|
}
|
|
|
|
constructor(compilerInfo: PreliminaryCompilerInfo, env: CompilationEnvironment) {
|
|
super(compilerInfo, env);
|
|
|
|
this.asm = new SPIRVAsmParser(this.compilerProps);
|
|
|
|
// spirv-as
|
|
this.assemblerPath = this.compilerProps<string>('assemblerPath');
|
|
// spirv-dis
|
|
this.disassemblerPath = this.compilerProps<string>('disassemblerPath');
|
|
}
|
|
|
|
override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string) {
|
|
const sourceDir = path.dirname(outputFilename);
|
|
const spvBinFilename = this.getPrimaryOutputFilename(sourceDir, this.outputFilebase);
|
|
return ['-o', spvBinFilename];
|
|
}
|
|
|
|
getPrimaryOutputFilename(dirPath: string, outputFilebase: string) {
|
|
return path.join(dirPath, `${outputFilebase}.spv`);
|
|
}
|
|
|
|
override getOutputFilename(dirPath: string, outputFilebase: string) {
|
|
return path.join(dirPath, `${outputFilebase}.spvasm`);
|
|
}
|
|
|
|
// Some tools (ex. spirv-opt) needs this as a single argument, so merge with "="
|
|
mergeSpirvTargetEnv(options: string[]) {
|
|
const index = options.indexOf('--target-env');
|
|
if (index !== -1) {
|
|
options.splice(index, 2, `--target-env=${options[index + 1]}`);
|
|
}
|
|
return options;
|
|
}
|
|
|
|
// Some tools (ex. spirv-val) needs this as a two argument, so unmerge with "="
|
|
unmergeSpirvTargetEnv(options: string[]) {
|
|
for (let i = 0; i < options.length; i++) {
|
|
if (options[i].indexOf('--target-env=') === 0) {
|
|
const parts = options[i].split('=');
|
|
options.splice(i, 1, parts[0], parts[1]);
|
|
break;
|
|
}
|
|
}
|
|
return options;
|
|
}
|
|
|
|
getSpirvTargetEnv(options: string[]) {
|
|
const index = options.indexOf('--target-env');
|
|
if (index !== -1) {
|
|
return [options[index], options[index + 1]];
|
|
}
|
|
|
|
for (const i in options) {
|
|
if (options[i].indexOf('--target-env=') === 0) {
|
|
return [options[i]];
|
|
}
|
|
}
|
|
|
|
return []; // no target found, use tool's default
|
|
}
|
|
|
|
// Most flows follow the same flow:
|
|
// 1. Assemble from spirv disassembly to a spirv binary
|
|
// 2. Run the tool (which will dump out a binary)
|
|
// a. Most tools let you set the input and out file to the same binary file
|
|
// 3. Disassemble back to disassembly
|
|
override async runCompiler(
|
|
compiler: string,
|
|
options: string[],
|
|
inputFilename: string,
|
|
execOptions: ExecutionOptionsWithEnv,
|
|
) {
|
|
const sourceDir = path.dirname(inputFilename);
|
|
const spvBinFilename = this.getPrimaryOutputFilename(sourceDir, this.outputFilebase);
|
|
|
|
if (!execOptions) {
|
|
execOptions = this.getDefaultExecOptions();
|
|
}
|
|
execOptions.customCwd = sourceDir;
|
|
|
|
let assemblerFlags = [inputFilename, '-o', spvBinFilename];
|
|
assemblerFlags = assemblerFlags.concat(this.getSpirvTargetEnv(options));
|
|
// Will fail if input SPIR-V is so bad assembler can't understand, so should let user know
|
|
let spvasmOutput = await this.exec(this.assemblerPath, assemblerFlags, execOptions);
|
|
let result = this.transformToCompilationResult(spvasmOutput, inputFilename);
|
|
if (spvasmOutput.code !== 0 || !(await utils.fileExists(spvBinFilename))) {
|
|
return result;
|
|
}
|
|
|
|
// needs to update options depending on the tool
|
|
const isValidator = compiler.endsWith('spirv-val');
|
|
if (isValidator) {
|
|
// there is no output file, so remove what we added in optionsForFilter
|
|
options = options.splice(2);
|
|
options = this.unmergeSpirvTargetEnv(options);
|
|
} else {
|
|
options = this.mergeSpirvTargetEnv(options);
|
|
}
|
|
|
|
// have tools input a binary and output it to same binary temp file
|
|
for (const i in options) {
|
|
if (options[i] === inputFilename) {
|
|
options[i] = spvBinFilename;
|
|
break;
|
|
}
|
|
}
|
|
|
|
const spvBin = await this.exec(compiler, options, execOptions);
|
|
result = this.transformToCompilationResult(spvBin, inputFilename);
|
|
if (spvBin.code !== 0 || !(await utils.fileExists(spvBinFilename)) || isValidator) {
|
|
return result;
|
|
}
|
|
|
|
const spvasmFilename = path.join(sourceDir, this.outputFilebase + '.spvasm');
|
|
const disassemblerFlags = [spvBinFilename, '-o', spvasmFilename];
|
|
|
|
// Will likely never fail
|
|
spvasmOutput = await this.exec(this.disassemblerPath, disassemblerFlags, execOptions);
|
|
if (spvasmOutput.code !== 0) {
|
|
logger.error('spirv-dis failed to disassemble binary', spvasmOutput);
|
|
}
|
|
|
|
result = this.transformToCompilationResult(spvasmOutput, spvBinFilename);
|
|
return result;
|
|
}
|
|
}
|