diff options
-rw-r--r-- | lib/base-compiler.js | 148 | ||||
-rw-r--r-- | lib/compilers/ada.js | 1 | ||||
-rw-r--r-- | lib/compilers/argument-parsers.js | 5 | ||||
-rw-r--r-- | static/panes/compiler.js | 6 | ||||
-rw-r--r-- | static/panes/gccdump-view.js | 61 |
5 files changed, 159 insertions, 62 deletions
diff --git a/lib/base-compiler.js b/lib/base-compiler.js index 461e842ee..a2c7e0eac 100644 --- a/lib/base-compiler.js +++ b/lib/base-compiler.js @@ -753,11 +753,33 @@ export class BaseCompiler { return [{text: 'Internal error; unable to open output path'}]; } - async generateGccDump(inputFilename, options, gccDumpOptions) { - // Maybe we should not force any RTL dump and let user hand-pick what he needs - const addOpts = []; - /* if not defined, consider it true */ + /** + * @returns {{filename_suffix: string, name: string, command_prefix: string}} + * `filename_suffix`: dump file name suffix if GCC default dump names is used + * + * `name`: the name to be displayed in the UI + * + * `command_prefix`: command prefix to be used in case this dump is to be + * created using a targeted option (eg. -fdump-rtl-expand) + */ + fromInternalGccDumpName(internalDumpName, selectedPasses) { + if (!selectedPasses) + selectedPasses = ['ipa', 'tree', 'rtl']; + + const internalNameRe = new RegExp('^\\s*(' + selectedPasses.join('|') +')-([\\w_-]+).*ON$'); + const match = internalDumpName.match(internalNameRe); + if (match) + return { + filename_suffix: `${match[1][0]}.${match[2]}`, + name: match[2] + ' (' + match[1] + ')', + command_prefix: `-fdump-${match[1]}-${match[2]}`, + }; + else + return null; + } + getGccDumpOptions(gccDumpOptions, removeEmptyPasses) { + const addOpts = ['-fdump-passes']; // Build dump options to append to the end of the -fdump command-line flag. // GCC accepts these options as a list of '-' separated names that may // appear in any order. @@ -793,17 +815,35 @@ export class BaseCompiler { flags += '-all'; } - if (gccDumpOptions.treeDump !== false) { - addOpts.push('-fdump-tree-all' + flags); - } - if (gccDumpOptions.rtlDump !== false) { - addOpts.push('-fdump-rtl-all' + flags); - } - if (gccDumpOptions.ipaDump !== false) { - addOpts.push('-fdump-ipa-all' + flags); + // If we want to remove the passes that won't produce anything from the + // drop down menu, we need to ask for all dump files and see what's + // really created. This is currently only possible with regular GCC, not + // for compilers that us libgccjit. The later can't easily move dump + // files outside of the tempdir created on the fly. + if (removeEmptyPasses){ + if (gccDumpOptions.treeDump !== false) { + addOpts.push('-fdump-tree-all' + flags); + } + if (gccDumpOptions.rtlDump !== false) { + addOpts.push('-fdump-rtl-all' + flags); + } + if (gccDumpOptions.ipaDump !== false) { + addOpts.push('-fdump-ipa-all' + flags); + } + } else { + // If not dumping everything, create a specific command like + // -fdump-tree-fixup_cfg1-some-flags=stdout + if (gccDumpOptions.pass) { + const dumpCmd = gccDumpOptions.pass.command_prefix + flags + '=stdout'; + addOpts.push(dumpCmd); + } } - const newOptions = options.concat(addOpts); + return addOpts; + } + + async generateGccDump(inputFilename, options, gccDumpOptions, removeEmptyPasses) { + const newOptions = options.concat(this.getGccDumpOptions(gccDumpOptions, removeEmptyPasses)); const execOptions = this.getDefaultExecOptions(); // A higher max output is needed for when the user includes headers @@ -811,7 +851,8 @@ export class BaseCompiler { return this.processGccDumpOutput( gccDumpOptions, - await this.runCompiler(this.compiler.exe, newOptions, this.filename(inputFilename), execOptions)); + await this.runCompiler(this.compiler.exe, newOptions, this.filename(inputFilename), execOptions), + removeEmptyPasses); } async checkOutputFileAndDoPostProcess(asmResult, outputFilename, filters) { @@ -1170,7 +1211,9 @@ export class BaseCompiler { ] = await Promise.all([ this.runCompiler(this.compiler.exe, options, inputFilenameSafe, execOptions), (makeAst ? this.generateAST(inputFilename, options) : ''), - (makeGccDump ? this.generateGccDump(inputFilename, options, backendOptions.produceGccDump) : ''), + (makeGccDump ? this.generateGccDump(inputFilename, options, + backendOptions.produceGccDump, + this.compiler.removeEmptyGccDump) : ''), (makeGnatDebug ? this.generateGnatDebug(inputFilename, options) : ''), (makeIr ? this.generateIR(inputFilename, options, filters) : ''), (makeRustMir ? this.generateRustMir(inputFilename, options) : ''), @@ -1624,15 +1667,14 @@ export class BaseCompiler { } - async processGccDumpOutput(opts, result) { + async processGccDumpOutput(opts, result, removeEmptyPasses) { const rootDir = path.dirname(result.inputFilename); - const allFiles = await fs.readdir(rootDir); if (opts.treeDump === false && opts.rtlDump === false && opts.ipaDump === false) { return { all: [], - selectedPass: '', - currentPassOutput: 'Nothing selected for dump:\nselect at least one of Tree/RTL filter', + selectedPass: null, + currentPassOutput: 'Nothing selected for dump:\nselect at least one of Tree/IPA/RTL filter', syntaxHighlight: false, }; } @@ -1643,34 +1685,60 @@ export class BaseCompiler { currentPassOutput: '<No pass selected>', syntaxHighlight: false, }; + const selectedPasses = []; + + if (opts.treeDump) selectedPasses.push('tree'); + if (opts.ipaDump) selectedPasses.push('ipa'); + if (opts.rtlDump) selectedPasses.push('rtl'); + + let dumpFileName; let passFound = false; - // Phase letter is one of {i, l, r, t} - // {outpufilename}.{extension}.{passNumber}{phaseLetter}.{phaseName} - const dumpFilenameRegex = /^.+?\..+?\.(\d+?[ilrt]\..+)$/; - for (let filename of allFiles) { - const match = dumpFilenameRegex.exec(filename); - if (match) { - const pass = match[1]; - output.all.push(pass); - const filePath = path.join(rootDir, filename); - if (opts.pass === pass && (await fs.stat(filePath)).isFile()) { + + for (const obj of Object.values(result.stderr)) { + const selectizeObject = this.fromInternalGccDumpName(obj.text, selectedPasses); + if (selectizeObject){ + if (opts.pass && opts.pass.name === selectizeObject.name) passFound = true; - output.currentPassOutput = await fs.readFile(filePath, 'utf-8'); - if (/^\s*$/.test(output.currentPassOutput)) { - output.currentPassOutput = 'File for selected pass is empty.'; - } else { - output.syntaxHighlight = true; - } + + if (removeEmptyPasses){ + const f = fs.readdirSync(rootDir).filter(fn => fn.endsWith(selectizeObject.filename_suffix)); + + // pass is enabled, but the dump hasn't produced anything: + // don't add it to the drop down menu + if (f.length === 0) + continue; + + if (opts.pass && opts.pass.name === selectizeObject.name) + dumpFileName = path.join(rootDir, f[0]); } + + output.all.push(selectizeObject); } } - if (opts.pass && !passFound) { - output.currentPassOutput = `Pass '${opts.pass}' was requested -but is not valid anymore with current filters. -Please select another pass or change filters.`; - } + if (opts.pass && passFound){ + output.currentPassOutput = ''; + if (removeEmptyPasses) { + if (dumpFileName) + output.currentPassOutput = await fs.readFile(dumpFileName, 'utf-8'); + // else leave the currentPassOutput empty. Can happen when some + // UI options are changed and a now disabled pass is still + // requested. + } else { + for (const obj of Object.values(result.stdout)) { + output.currentPassOutput += obj.text + '\n'; + } + } + if (/^\s*$/.test(output.currentPassOutput)) { + output.currentPassOutput = `Pass '${opts.pass.name}' was requested +but nothing was dumped. Possible causes are: + - pass is not valid in this (maybe you changed the compiler options); + - pass is valid but did not emit anything (eg. it was not executed).`; + } else { + output.syntaxHighlight = true; + } + } return output; } diff --git a/lib/compilers/ada.js b/lib/compilers/ada.js index 7d61d70c4..971564edb 100644 --- a/lib/compilers/ada.js +++ b/lib/compilers/ada.js @@ -33,6 +33,7 @@ export class AdaCompiler extends BaseCompiler { constructor(info, env) { super(info, env); this.compiler.supportsGccDump = true; + this.compiler.removeEmptyGccDump = true; this.compiler.supportsIntel = true; this.compiler.supportsGnatDebugView = true; } diff --git a/lib/compilers/argument-parsers.js b/lib/compilers/argument-parsers.js index c64de8549..1944711a1 100644 --- a/lib/compilers/argument-parsers.js +++ b/lib/compilers/argument-parsers.js @@ -100,6 +100,11 @@ export class GCCParser extends BaseParser { // 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; + + // By default, consider the compiler to be a regular GCC (eg. gcc, + // g++) and do the extra work of filtering out enabled pass that did + // not produce anything. + compiler.compiler.removeEmptyGccDump = true; } } diff --git a/static/panes/compiler.js b/static/panes/compiler.js index 82ca5da26..a3276d4d6 100644 --- a/static/panes/compiler.js +++ b/static/panes/compiler.js @@ -1334,11 +1334,11 @@ Compiler.prototype.onGccDumpFiltersChanged = function (id, filters, reqCompile) } }; -Compiler.prototype.onGccDumpPassSelected = function (id, passId, reqCompile) { +Compiler.prototype.onGccDumpPassSelected = function (id, passObject, reqCompile) { if (this.id === id) { - this.gccDumpPassSelected = passId; + this.gccDumpPassSelected = passObject; - if (reqCompile && passId !== '') { + if (reqCompile && passObject !== null) { this.compile(); } } diff --git a/static/panes/gccdump-view.js b/static/panes/gccdump-view.js index b43b9e8e4..42f72c799 100644 --- a/static/panes/gccdump-view.js +++ b/static/panes/gccdump-view.js @@ -1,4 +1,5 @@ // Copyright (c) 2017, Marc Poulhiès - Kalray Inc. +// Copyright (c) 2021, Compiler Explorer Authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -54,7 +55,7 @@ function GccDump(hub, container, state) { var gccdump_picker = this.domRoot[0].querySelector('.gccdump-pass-picker'); this.selectize = new TomSelect(gccdump_picker, { - sortField: 'name', + sortField: -1, // do not sort valueField: 'name', labelField: 'name', searchField: ['name'], @@ -78,7 +79,24 @@ function GccDump(hub, container, state) { if (state && state.selectedPass) { this.state.selectedPass = state.selectedPass; - this.eventHub.emit('gccDumpPassSelected', this.state._compilerid, state.selectedPass, false); + + // To keep URL format stable wrt GccDump, only a string of the form 'r.expand' is stored. + // Old links also have the pass number prefixed but this can be ignored. + // Create the object that will be used instead of this bare string. + var selectedPassRe = /[0-9]*(i|t|r)\.([\w-_]*)/; + var passType = { + i: 'ipa', + r: 'rtl', + t: 'tree', + }; + var match = state.selectedPass.match(selectedPassRe); + var selectedPassO = { + filename_suffix: match[1] + '.' + match[2], + name: match[2] + ' (' + passType[match[1]] + ')', + command_prefix: '-fdump-' + passType[match[1]] + '-' + match[2], + }; + + this.eventHub.emit('gccDumpPassSelected', this.state._compilerid, selectedPassO, false); } // until we get our first result from compilation backend with all fields, @@ -211,10 +229,21 @@ GccDump.prototype.onUiReady = function () { }; GccDump.prototype.onPassSelect = function (passId) { + var selectedPass = this.selectize.options[passId]; + if (this.inhibitPassSelect !== true) { - this.eventHub.emit('gccDumpPassSelected', this.state._compilerid, passId, true); + this.eventHub.emit('gccDumpPassSelected', this.state._compilerid, selectedPass, true); } - this.state.selectedPass = passId; + + // To keep shared URL compatible, we keep on storing only a string in the + // state and stick to the original format. + // Previously, we were simply storing the full file suffix (the part after [...]): + // [file.c.]123t.expand + // We don't have the number now, but we can store the file suffix without this number + // (the number is useless and should probably have never been there in the + // first place). + + this.state.selectedPass = selectedPass.filename_suffix; this.saveState(); }; @@ -235,23 +264,17 @@ GccDump.prototype.updatePass = function (filters, selectize, gccDumpOutput) { // trigger new compilation this.inhibitPassSelect = true; - _.each(selectize.options, function (p) { - if (passes.indexOf(p.name) === -1) { - selectize.removeOption(p.name); - } - }, this); + selectize.clear(true); + selectize.clearOptions(true); _.each(passes, function (p) { - selectize.addOption({ - name: p, - }); + selectize.addOption(p); }, this); - if (gccDumpOutput.selectedPass && gccDumpOutput.selectedPass !== '') { - selectize.addItem(gccDumpOutput.selectedPass, true); - } else { + if (gccDumpOutput.selectedPass) + selectize.addItem(gccDumpOutput.selectedPass.name, true); + else selectize.clear(true); - } this.eventHub.emit('gccDumpPassSelected', this.state._compilerid, gccDumpOutput.selectedPass, false); @@ -271,9 +294,9 @@ GccDump.prototype.onCompileResult = function (id, compiler, result) { // if result contains empty selected pass, probably means // we requested an invalid/outdated pass. - if (result.gccDumpOutput.selectedPass === '') { + if (!result.gccDumpOutput.selectedPass) { this.selectize.clear(true); - this.state.selectedPass = ''; + this.state.selectedPass = null; } this.updatePass(this.filters, this.selectize, result.gccDumpOutput); this.showGccDumpResults(currOutput); @@ -285,7 +308,7 @@ GccDump.prototype.onCompileResult = function (id, compiler, result) { } } else { this.selectize.clear(true); - this.state.selectedPass = ''; + this.state.selectedPass = null; this.updatePass(this.filters, this.selectize, false); this.uiIsReady = false; this.onUiNotReady(); |