mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2026-05-16 16:02:45 -04:00
in `dev-2025-02`, odin changed from single-module debug builds to multi-module and prevents it from spitting out a single `.s` file. this adds version checking to make sure that any odin version after `dev-2025-02` uses `-use-single-module`. additionally, changes were made around name canonicalization that required changing the matching for label names to avoid clogging the assembly output (both standard and binary)
288 lines
9.8 KiB
TypeScript
288 lines
9.8 KiB
TypeScript
import path from 'node:path';
|
|
|
|
import {CompilationResult} from '../../types/compilation/compilation.interfaces.js';
|
|
import type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
|
|
import type {CompilerOutputOptions, ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
|
|
import {BaseCompiler} from '../base-compiler.js';
|
|
import {CompilationEnvironment} from '../compilation-env.js';
|
|
import {OdinAsmParser} from '../parsers/asm-parser-odin.js';
|
|
import * as utils from '../utils.js';
|
|
|
|
class OdinVersion {
|
|
year: number = 0;
|
|
month: number = 0;
|
|
|
|
constructor(version: string) {
|
|
const match = version.match(/(\d{4})-(\d{1,2})[a-zA-Z]*/);
|
|
if (!match) {
|
|
throw new Error(`Invalid version format: "${version}"`);
|
|
}
|
|
|
|
const year = Number(match[1]);
|
|
const month = Number(match[2]);
|
|
|
|
if (month < 1 || month > 12) {
|
|
throw new Error(`Invalid month in version: "${version}"`);
|
|
}
|
|
|
|
this.year = year;
|
|
this.month = month;
|
|
}
|
|
|
|
gte(version: OdinVersion) {
|
|
if (this.year > version.year) {
|
|
return true;
|
|
} else if (this.year === version.year) {
|
|
return this.month >= version.month;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class OdinCompiler extends BaseCompiler {
|
|
private clangPath?: string;
|
|
|
|
static get key() {
|
|
return 'odin';
|
|
}
|
|
|
|
constructor(info: PreliminaryCompilerInfo, env: CompilationEnvironment) {
|
|
super(info, env);
|
|
this.asm = new OdinAsmParser(this.compilerProps);
|
|
this.compiler.supportsIrView = true;
|
|
this.compiler.irArg = [];
|
|
this.compiler.supportsIntel = false;
|
|
this.clangPath = this.compilerProps<string>('clangPath', undefined);
|
|
}
|
|
|
|
override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string) {
|
|
const options: string[] = ['-debug', '-keep-temp-files', `-out:${this.filename(outputFilename)}`];
|
|
|
|
const version = new OdinVersion(this.compiler.version);
|
|
if (version.gte(new OdinVersion('dev-2025-02'))) {
|
|
options.push('-use-single-module');
|
|
}
|
|
|
|
if (!(filters.execute || filters.binary)) {
|
|
filters.preProcessLines = this.preProcessLines.bind(this);
|
|
options.push('-build-mode:asm');
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
override orderArguments(
|
|
options: string[],
|
|
inputFilename: string,
|
|
libIncludes: string[],
|
|
libOptions: string[],
|
|
libPaths: string[],
|
|
libLinks: string[],
|
|
userOptions: string[],
|
|
staticLibLinks: string[],
|
|
) {
|
|
return ['build', this.filename(inputFilename), '-file'].concat(options, userOptions);
|
|
}
|
|
|
|
override getDefaultExecOptions() {
|
|
const execOptions = super.getDefaultExecOptions();
|
|
if (this.clangPath) {
|
|
execOptions.env.ODIN_CLANG_PATH = this.clangPath;
|
|
}
|
|
|
|
return execOptions;
|
|
}
|
|
|
|
override async checkOutputFileAndDoPostProcess(
|
|
asmResult: CompilationResult,
|
|
outputFilename: string,
|
|
filters: ParseFiltersAndOutputOptions,
|
|
) {
|
|
let newOutputFilename = outputFilename;
|
|
if (!filters.binary && !filters.execute) newOutputFilename = outputFilename.replace(/.s$/, '.S');
|
|
return super.checkOutputFileAndDoPostProcess(asmResult, newOutputFilename, filters);
|
|
}
|
|
|
|
override getIrOutputFilename(inputFilename: string): string {
|
|
return this.filename(path.dirname(inputFilename) + '/output.ll');
|
|
}
|
|
|
|
override async postProcessAsm(result, filters?: ParseFiltersAndOutputOptions) {
|
|
// we dont need demangling
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Preprocess the source code to '@require' all the functions
|
|
* so that their asm is emitted
|
|
*/
|
|
override preProcess(source: string, filters: CompilerOutputOptions): string {
|
|
if (filters.binary && !this.stubRe.test(source)) {
|
|
source += `\n${this.stubText}\n`;
|
|
}
|
|
|
|
if (!filters.binary && !filters.execute) {
|
|
const sourceLines = utils.splitLines(source);
|
|
const outputLines: string[] = [];
|
|
const procRE = /^\s*([A-Za-z]\w+)\s*:\s*:\s*proc\s*("\w+")?\s*\(/;
|
|
let lastLine = '';
|
|
for (const line of sourceLines) {
|
|
// skip if the line doesn't contain proc keyword
|
|
if (!line.includes('proc')) {
|
|
outputLines.push(line);
|
|
lastLine = line;
|
|
continue;
|
|
}
|
|
|
|
const match = line.match(procRE);
|
|
if (!match) {
|
|
outputLines.push(line);
|
|
lastLine = line;
|
|
continue;
|
|
}
|
|
|
|
// last line already has require?
|
|
if (lastLine.includes('@require') || lastLine.includes('@(require)')) {
|
|
outputLines.push(line);
|
|
continue;
|
|
}
|
|
|
|
// @require the function so they dont get inlined
|
|
// put @require on the same line so that output source
|
|
// has same number of lines so that asm<->source mapping
|
|
// in ui works correctly.
|
|
outputLines.push('@(require) ' + line);
|
|
lastLine = line;
|
|
}
|
|
|
|
let text = outputLines.join('\n');
|
|
// append a final trailing newline
|
|
if (!text.endsWith('\n')) {
|
|
text += '\n';
|
|
}
|
|
return text;
|
|
}
|
|
|
|
return source;
|
|
}
|
|
|
|
preProcessLines(asmLines: string[]) {
|
|
let i = 0;
|
|
let funcStart = -1;
|
|
while (i < asmLines.length) {
|
|
const line = asmLines[i];
|
|
// filter out __$ and "__$" builtin functions
|
|
const hasBuiltinPrefix = line.startsWith('__$') || line.startsWith('"__$');
|
|
if (funcStart === -1 && hasBuiltinPrefix && line.endsWith(':')) {
|
|
// ensure there is cfi_startproc
|
|
for (let j = i; j < asmLines.length && j < i + 5; j++) {
|
|
if (asmLines[j].includes('.cfi_startproc')) {
|
|
funcStart = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// make sure this is a globl
|
|
if (funcStart !== -1) {
|
|
for (let j = i; j >= 0 && j >= i - 3; --j) {
|
|
if (asmLines[j].includes('.globl')) {
|
|
funcStart = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (funcStart !== -1 && line.includes('.cfi_endproc')) {
|
|
const len = i - funcStart;
|
|
asmLines.splice(funcStart, len + 1);
|
|
i = funcStart - 1;
|
|
funcStart = -1;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
return this.removeNonMainSourceFunctions(asmLines);
|
|
}
|
|
|
|
parseFiles(asmLines: string[]) {
|
|
const files: Record<number, string> = {};
|
|
const fileFind = /^\s*\.(?:cv_)?file\s+(\d+)\s+"([^"]+)"(\s+"([^"]+)")?.*/;
|
|
for (const line of asmLines) {
|
|
const match = line.match(fileFind);
|
|
if (match) {
|
|
const lineNum = Number.parseInt(match[1], 10);
|
|
if (match[4] && !line.includes('.cv_file')) {
|
|
// Clang-style file directive '.file X "dir" "filename"'
|
|
if (match[4].startsWith('/')) {
|
|
files[lineNum] = match[4];
|
|
} else {
|
|
files[lineNum] = match[2] + '/' + match[4];
|
|
}
|
|
} else {
|
|
files[lineNum] = match[2];
|
|
}
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
|
|
removeNonMainSourceFunctions(asmLines: string[]): string[] {
|
|
const files = this.parseFiles(asmLines);
|
|
const sourceTag = /^\s*\.loc\s+(\d+)\s+(\d+)\s+(.*)/;
|
|
const stdInLooking = /<stdin>|^-$|example\.[^/]+$|<source>/;
|
|
let i = 0;
|
|
let funcStart = -1;
|
|
while (i < asmLines.length) {
|
|
const line = asmLines[i];
|
|
// find a label
|
|
if (funcStart === -1 && line.endsWith(':')) {
|
|
// scan the next 5 lines for a source loc label
|
|
for (let j = i; j < i + 5 && j < asmLines.length; j++) {
|
|
const fline = asmLines[j];
|
|
const match = fline.match(sourceTag);
|
|
if (!match) continue;
|
|
const file = files[match[1]];
|
|
if (!file) continue;
|
|
// if the file is not current file we remove this function
|
|
if (!stdInLooking.test(file)) {
|
|
funcStart = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ensure there is cfi_startproc
|
|
if (funcStart !== -1) {
|
|
funcStart = -1;
|
|
for (let j = i; j < asmLines.length && j < i + 5; j++) {
|
|
if (asmLines[j].includes('.cfi_startproc')) {
|
|
funcStart = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (funcStart !== -1) {
|
|
for (let j = i; j >= 0 && j >= i - 3; --j) {
|
|
if (asmLines[j].includes('.type') && asmLines[j].endsWith('@function')) {
|
|
funcStart = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (funcStart !== -1 && line.includes('.cfi_endproc')) {
|
|
const len = i - funcStart;
|
|
asmLines.splice(funcStart, len + 1);
|
|
i = funcStart - 1;
|
|
funcStart = -1;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
return asmLines;
|
|
}
|
|
}
|