aboutsummaryrefslogtreecommitdiff
path: root/lib/base-compiler.js
diff options
context:
space:
mode:
authorpartouf <partouf@gmail.com>2021-12-28 17:22:35 +0000
committerpartouf <partouf@gmail.com>2021-12-28 17:22:35 +0000
commit193e207ae9098ce57c65caec1a6b26efecf5fc43 (patch)
tree5f69a62d352ba7b67376277a22b78a0086e93822 /lib/base-compiler.js
parentc750f3c71c0497fc080dadd03590a660c4eee522 (diff)
parentd7157c47e617b3c2a8e4956d559fb61cc34b88df (diff)
downloadcompiler-explorer-gh-1479.tar.gz
compiler-explorer-gh-1479.zip
Merge remote-tracking branch 'origin/main' into frontendtestinggh-1479
Diffstat (limited to 'lib/base-compiler.js')
-rw-r--r--lib/base-compiler.js267
1 files changed, 205 insertions, 62 deletions
diff --git a/lib/base-compiler.js b/lib/base-compiler.js
index 0ebe7f005..059f5d0b0 100644
--- a/lib/base-compiler.js
+++ b/lib/base-compiler.js
@@ -725,13 +725,35 @@ export class BaseCompiler {
};
}
- async processRustMirOutput(outputFilename, output) {
- const mirPath = this.getRustMirOutputFilename(outputFilename);
+ getRustMacroExpansionOutputFilename(inputFilename) {
+ return inputFilename.replace(path.extname(inputFilename), '.expanded.rs');
+ }
+
+ getRustHirOutputFilename(inputFilename) {
+ return inputFilename.replace(path.extname(inputFilename), '.hir');
+ }
+
+ getRustMirOutputFilename(outputFilename) {
+ return outputFilename.replace(path.extname(outputFilename), '.mir');
+ }
+
+ // Currently called for getting macro expansion and HIR.
+ // It returns the content of the output file created after using -Z unpretty=<unprettyOpt>.
+ // The outputFriendlyName is a free form string used in case of error.
+ async generateRustUnprettyOutput(inputFilename, options, unprettyOpt, outputFilename, outputFriendlyName){
+ const execOptions = this.getDefaultExecOptions();
+
+ const rustcOptions = [...options];
+ rustcOptions.splice(options.indexOf('-o', 2));
+ rustcOptions.push(inputFilename, '-o', outputFilename, `-Zunpretty=${unprettyOpt}`);
+
+ const output = await this.runCompiler(this.compiler.exe, rustcOptions, inputFilename,
+ execOptions);
if (output.code !== 0) {
- return [{text: 'Failed to run compiler to get Rust MIR'}];
+ return [{text: `Failed to run compiler to get Rust ${outputFriendlyName}`}];
}
- if (await fs.exists(mirPath)) {
- const content = await fs.readFile(mirPath, 'utf-8');
+ if (await fs.exists(outputFilename)) {
+ const content = await fs.readFile(outputFilename, 'utf-8');
return content.split('\n').map((line) => ({
text: line,
}));
@@ -739,25 +761,23 @@ export class BaseCompiler {
return [{text: 'Internal error; unable to open output path'}];
}
- async generateRustMacroExpansion(inputFilename) {
- const execOptions = this.getDefaultExecOptions();
+ async generateRustMacroExpansion(inputFilename, options) {
const macroExpPath = this.getRustMacroExpansionOutputFilename(inputFilename);
- const rustcOptions = [
- inputFilename,
- '-o',
- macroExpPath,
- '-Zunpretty=expanded',
- '--crate-type',
- 'rlib',
- ];
-
- const output = await this.runCompiler(this.compiler.exe, rustcOptions, this.filename(inputFilename),
- execOptions);
+ return this.generateRustUnprettyOutput(inputFilename, options, 'expanded', macroExpPath, 'Macro Expansion');
+ }
+
+ async generateRustHir(inputFilename, options) {
+ const hirPath = this.getRustHirOutputFilename(inputFilename);
+ return this.generateRustUnprettyOutput(inputFilename, options, 'hir-tree', hirPath, 'HIR');
+ }
+
+ async processRustMirOutput(outputFilename, output) {
+ const mirPath = this.getRustMirOutputFilename(outputFilename);
if (output.code !== 0) {
- return [{text: 'Failed to run compiler to get Rust Macro Expansion'}];
+ return [{text: 'Failed to run compiler to get Rust MIR'}];
}
- if (await fs.exists(macroExpPath)) {
- const content = await fs.readFile(macroExpPath, 'utf-8');
+ if (await fs.exists(mirPath)) {
+ const content = await fs.readFile(mirPath, 'utf-8');
return content.split('\n').map((line) => ({
text: line,
}));
@@ -769,14 +789,6 @@ export class BaseCompiler {
return inputFilename.replace(path.extname(inputFilename), '.ll');
}
- getGnatDebugOutputFilename(inputFilename) {
- return inputFilename + '.dg';
- }
-
- getRustMacroExpansionOutputFilename(inputFilename) {
- return inputFilename.replace(path.extname(inputFilename), '.expanded.rs');
- }
-
getOutputFilename(dirPath, outputFilebase, key) {
let filename;
if (key && key.backendOptions && key.backendOptions.customOutputFilename) {
@@ -796,30 +808,80 @@ export class BaseCompiler {
return this.getOutputFilename(dirPath, outputFilebase, key);
}
- async processGnatDebugOutput(inputFilename, output) {
- const gnatDebugPath = this.getGnatDebugOutputFilename(inputFilename);
+ async processGnatDebugOutput(inputFilename, result) {
+ const contentDebugExpanded = [];
+ const contentDebugTree = [];
+ const keep_stdout = [];
+
+ // stdout layout:
+ //
+ // ----- start
+ // everything here stays
+ // ... in stdout
+ // ... until :
+ // Source recreated from tree... <-\
+ // everything here is |
+ // ... sent in expanded | this is optionnal
+ // ... pane... until : <-/
+ // Tree created for ... <-\
+ // everything after is | this is optionnal
+ // ... sent in Tree pane <-/
+ // ----- EOF
+ const startOfExpandedCode = /^Source recreated from tree/;
+ const startOfTree = /^Tree created for/;
+
+ let isInExpandedCode = false;
+ let isInTree = false;
+
+ for (const obj of Object.values(result.stdout)) {
+ if (!isInExpandedCode && startOfExpandedCode.test(obj.text)) {
+ isInExpandedCode = true;
+ isInTree = false;
+ }
+
+ if (!isInTree && startOfTree.test(obj.text)) {
+ isInExpandedCode = false;
+ isInTree = true;
+ }
+
+ if (isInExpandedCode) {
+ contentDebugExpanded.push(obj);
+ } else if (isInTree){
+ contentDebugTree.push(obj);
+ } else {
+ keep_stdout.push(obj);
+ }
+ }
// Do not check compiler result before looking for expanded code. The
- // compiler may exit with an error after the emission. This file is also
+ // compiler may exit with an error after the emission. This dump is also
// very usefull to debug error message.
- if (await fs.exists(gnatDebugPath)) {
- const content = await fs.readFile(gnatDebugPath, 'utf-8');
- return content.split('\n').map((line) => ({
- text: line,
- }));
- } else {
- // check for possible cause for missing file
- if (output.code !== 0) {
- return [{text: 'GNAT exited with an error and did not create the expanded code'}];
+
+ if (contentDebugExpanded.length === 0)
+ if (result.code !== 0) {
+ contentDebugExpanded.push({text: 'GNAT exited with an error and did not create the expanded code'});
} else {
- return [{text: 'GNAT exited successfully but the expanded code is missing, something is wrong'}];
+ contentDebugExpanded.push(
+ {text: 'GNAT exited successfully but the expanded code is missing, something is wrong'});
}
- }
+
+ if (contentDebugTree.length === 0)
+ if (result.code !== 0) {
+ contentDebugTree.push({text: 'GNAT exited with an error and did not create the Tree'});
+ } else {
+ contentDebugTree.push({text: 'GNAT exited successfully but the Tree is missing, something is wrong'});
+ }
+
+ return {
+ stdout: keep_stdout,
+ tree: contentDebugTree,
+ expandedcode: contentDebugExpanded,
+ };
}
/**
* @returns {{filename_suffix: string, name: string, command_prefix: string}}
- * `filename_suffix`: dump file name suffix if GCC default dump names is used
+ * `filename_suffix`: dump file name suffix if GCC default dump name is used
*
* `name`: the name to be displayed in the UI
*
@@ -899,6 +961,8 @@ export class BaseCompiler {
const filesToWrite = [];
for (let file of files) {
+ if (!file.filename) throw new Error('One of more files do not have a filename');
+
const fullpath = this.getExtraFilepath(dirPath, file.filename);
filesToWrite.push(fs.outputFile(fullpath, file.contents));
}
@@ -907,6 +971,8 @@ export class BaseCompiler {
}
async writeAllFiles(dirPath, source, files, filters) {
+ if (!source) throw new Error(`File ${this.compileFilename} has no content or file is missing`);
+
const inputFilename = path.join(dirPath, this.compileFilename);
await fs.writeFile(inputFilename, source);
@@ -922,6 +988,8 @@ export class BaseCompiler {
}
async writeAllFilesCMake(dirPath, source, files, filters) {
+ if (!source) throw new Error('File CMakeLists.txt has no content or file is missing');
+
const inputFilename = path.join(dirPath, 'CMakeLists.txt');
await fs.writeFile(inputFilename, source);
@@ -973,9 +1041,14 @@ export class BaseCompiler {
const buildResults = await this.loadPackageWithExecutable(key, dirPath);
if (buildResults) return buildResults;
- const compilationResult = await this.buildExecutableInFolder(key, dirPath);
- if (compilationResult.code !== 0) {
- return compilationResult;
+ let compilationResult;
+ try {
+ compilationResult = await this.buildExecutableInFolder(key, dirPath);
+ if (compilationResult.code !== 0) {
+ return compilationResult;
+ }
+ } catch (e) {
+ return this.handleUserError(e, dirPath);
}
await this.storePackageWithExecutable(key, dirPath, compilationResult);
@@ -1178,10 +1251,12 @@ export class BaseCompiler {
execOptions.ldPath = this.getSharedLibraryPathsAsLdLibraryPaths([]);
const makeAst = backendOptions.produceAst && this.compiler.supportsAstView;
- const makeGnatDebug = backendOptions.produceGnatDebug && this.compiler.supportsGnatDebugView;
+ const makeGnatDebug = backendOptions.produceGnatDebug && this.compiler.supportsGnatDebugViews;
+ const makeGnatDebugTree = backendOptions.produceGnatDebugTree && this.compiler.supportsGnatDebugViews;
const makeIr = backendOptions.produceIr && this.compiler.supportsIrView;
const makeRustMir = backendOptions.produceRustMir && this.compiler.supportsRustMirView;
const makeRustMacroExp = backendOptions.produceRustMacroExp && this.compiler.supportsRustMacroExpView;
+ const makeRustHir = backendOptions.produceRustHir && this.compiler.supportsRustHirView;
const makeGccDump = backendOptions.produceGccDump && backendOptions.produceGccDump.opened
&& this.compiler.supportsGccDump;
@@ -1190,12 +1265,14 @@ export class BaseCompiler {
asmResult,
astResult,
irResult,
+ rustHirResult,
rustMacroExpResult,
toolsResult,
] = await Promise.all([
this.runCompiler(this.compiler.exe, options, inputFilenameSafe, execOptions),
(makeAst ? this.generateAST(inputFilename, options) : ''),
(makeIr ? this.generateIR(inputFilename, options, filters) : ''),
+ (makeRustHir ? this.generateRustHir(inputFilename, options) : ''),
(makeRustMacroExp ? this.generateRustMacroExpansion(inputFilename, options) : ''),
Promise.all(this.runToolsOfType(tools, 'independent', this.getCompilationInfo(key, {
inputFilename,
@@ -1206,9 +1283,10 @@ export class BaseCompiler {
// GNAT, GCC and rustc can produce their extra output files along
// with the main compilation command.
- const gnatDebugResult = (makeGnatDebug ?
- await this.processGnatDebugOutput(inputFilenameSafe, asmResult)
- : '');
+ const gnatDebugResults = ((makeGnatDebug || makeGnatDebugTree)?
+ await this.processGnatDebugOutput(inputFilenameSafe, asmResult)
+ : '');
+
const gccDumpResult = (makeGccDump ?
await this.processGccDumpOutput(backendOptions.produceGccDump,
asmResult, this.compiler.removeEmptyGccDump,
@@ -1226,9 +1304,17 @@ export class BaseCompiler {
asmResult.gccDumpOutput = gccDumpResult;
}
- if (this.compiler.supportsGnatDebugView && gnatDebugResult) {
- asmResult.hasGnatDebugOutput = true;
- asmResult.gnatDebugOutput = gnatDebugResult;
+ if (this.compiler.supportsGnatDebugViews && gnatDebugResults) {
+ asmResult.stdout = gnatDebugResults.stdout;
+
+ if (makeGnatDebug && gnatDebugResults.expandedcode.length > 0) {
+ asmResult.hasGnatDebugOutput = true;
+ asmResult.gnatDebugOutput = gnatDebugResults.expandedcode;
+ }
+ if (makeGnatDebugTree && gnatDebugResults.tree.length > 0) {
+ asmResult.hasGnatDebugTreeOutput = true;
+ asmResult.gnatDebugTreeOutput = gnatDebugResults.tree;
+ }
}
asmResult.tools = toolsResult;
@@ -1262,6 +1348,11 @@ export class BaseCompiler {
asmResult.rustMacroExpOutput = rustMacroExpResult;
}
+ if (rustHirResult) {
+ asmResult.hasRustHirOutput = true;
+ asmResult.rustHirOutput = rustHirResult;
+ }
+
return this.checkOutputFileAndDoPostProcess(asmResult, outputFilename, filters);
}
@@ -1289,6 +1380,17 @@ export class BaseCompiler {
return result;
}
+ handleUserError(error, dirPath) {
+ return {
+ dirPath,
+ okToCache: false,
+ code: -1,
+ asm: [{text: `<${error.message}>`}],
+ stdout: [],
+ stderr: [{text: `<${error.message}>` }],
+ };
+ }
+
async doBuildstepAndAddToResult(result, name, command, args, execParams) {
const stepResult = await this.doBuildstep(command, args, execParams);
stepResult.step = name;
@@ -1359,7 +1461,12 @@ export class BaseCompiler {
let fullResult = await this.loadPackageWithExecutable(cacheKey, dirPath);
if (!fullResult) {
- const writeSummary = await this.writeAllFilesCMake(dirPath, cacheKey.source, files, cacheKey.filters);
+ let writeSummary;
+ try {
+ writeSummary = await this.writeAllFilesCMake(dirPath, cacheKey.source, files, cacheKey.filters);
+ } catch (e) {
+ return this.handleUserError(e, dirPath);
+ }
const execParams = this.getDefaultExecOptions();
execParams.appHome = dirPath;
@@ -1535,7 +1642,12 @@ export class BaseCompiler {
const dirPath = await this.newTempDir();
- const writeSummary = await this.writeAllFiles(dirPath, source, files, filters);
+ let writeSummary;
+ try {
+ writeSummary = await this.writeAllFiles(dirPath, source, files, filters);
+ } catch (e) {
+ return this.handleUserError(e, dirPath);
+ }
const inputFilename = writeSummary.inputFilename;
const [result, optOutput] = await this.doCompilation(
@@ -1679,24 +1791,55 @@ export class BaseCompiler {
currentPassOutput: '<No pass selected>',
syntaxHighlight: false,
};
+ const treeDumpsNotInPasses = [];
+
const selectedPasses = [];
- if (opts.treeDump) selectedPasses.push('tree');
+ if (opts.treeDump) {
+ selectedPasses.push('tree');
+
+ // Fake 2 lines as coming from -fdump-passes
+ // This allows the insertion of 'gimple' and 'original'
+ // tree dumps that are not really part of a tree pass.
+ treeDumpsNotInPasses.push (
+ [ { text: 'tree-original: ON'},
+ {
+ filename_suffix: 't.original',
+ name: 'original (tree)',
+ command_prefix: '-fdump-tree-original',
+ },
+ ],
+ [ { text: 'tree-gimple: ON'},
+ {
+ filename_suffix: 't.gimple',
+ name: 'gimple (tree)',
+ command_prefix: '-fdump-tree-gimple',
+ },
+ ]);
+ }
+
if (opts.ipaDump) selectedPasses.push('ipa');
if (opts.rtlDump) selectedPasses.push('rtl');
+ // Defaults to a custom file derived from output file name. Works when
+ // using the -fdump-tree-foo=FILE variant (!removeEmptyPasses).
+ // Will be overriden later if not.
let dumpFileName = this.getGccDumpFileName(outputFilename);
let passFound = false;
const filtered_stderr = [];
const toRemoveFromStderr = /^\s*((ipa|tree|rtl)-)|(\*)([\w-]+).*(ON|OFF)$/;
- for (const obj of Object.values(result.stderr)) {
- const selectizeObject = this.fromInternalGccDumpName(obj.text, selectedPasses);
- if (selectizeObject){
+
+ const dumpPassesLines = treeDumpsNotInPasses.concat(
+ Object.values(result.stderr).map(
+ x => [x, this.fromInternalGccDumpName(x.text, selectedPasses)]));
+
+ for (const [obj, selectizeObject] of dumpPassesLines) {
+ if (selectizeObject) {
if (opts.pass && opts.pass.name === selectizeObject.name)
passFound = true;
- if (removeEmptyPasses){
+ if (removeEmptyPasses) {
const f = fs.readdirSync(rootDir).filter(fn => fn.endsWith(selectizeObject.filename_suffix));
// pass is enabled, but the dump hasn't produced anything:
@@ -1711,7 +1854,7 @@ export class BaseCompiler {
output.all.push(selectizeObject);
}
- if (!toRemoveFromStderr.test(obj.text)){
+ if (!toRemoveFromStderr.test(obj.text)) {
filtered_stderr.push(obj);
}
}