aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/asm-parser.js5
-rw-r--r--lib/base-compiler.js57
-rw-r--r--lib/compilers/argument-parsers.js4
-rw-r--r--lib/llvm-ir.js182
-rw-r--r--lib/utils.js16
5 files changed, 260 insertions, 4 deletions
diff --git a/lib/asm-parser.js b/lib/asm-parser.js
index 76591f4ef..0fd4c3345 100644
--- a/lib/asm-parser.js
+++ b/lib/asm-parser.js
@@ -84,6 +84,11 @@ class AsmParser extends AsmRegex {
return !!line.match(this.hasOpcodeRe);
}
+ filterAsmLine(line, filters) {
+ if (!filters.trim) return line;
+ utils.trimLine(line);
+ }
+
labelFindFor(asmLines) {
const isMips = _.any(asmLines, line => !!line.match(this.mipsLabelDefinition));
return isMips ? this.labelFindMips : this.labelFindNonMips;
diff --git a/lib/base-compiler.js b/lib/base-compiler.js
index 0ea1a53eb..7b19b4149 100644
--- a/lib/base-compiler.js
+++ b/lib/base-compiler.js
@@ -26,6 +26,7 @@ const temp = require('temp'),
path = require('path'),
fs = require('fs-extra'),
denodeify = require('denodeify'),
+ LlvmIrParser = require('./llvm-ir'),
AsmParser = require('./asm-parser'),
utils = require('./utils'),
_ = require('underscore'),
@@ -65,6 +66,7 @@ class BaseCompiler {
this.readFile = denodeify(fs.readFile);
this.stat = denodeify(fs.stat);
this.asm = new AsmParser(this.compilerProps);
+ this.llvmIr = new LlvmIrParser(this.compilerProps);
this.possibleArguments = new CompilerArguments(this.compiler.id);
this.possibleTools = _.values(compilerInfo.tools);
@@ -214,6 +216,29 @@ class BaseCompiler {
.then(this.processAstOutput);
}
+ generateIR(inputFilename, options, filters) {
+ // These options make Clang produce an IR
+ let newOptions = _.filter(options, option => option !== '-fcolor-diagnostics')
+ .concat(this.compiler.irArg);
+
+ let execOptions = this.getDefaultExecOptions();
+ // A higher max output is needed for when the user includes headers
+ execOptions.maxOutput = 1024 * 1024 * 1024;
+
+ return this.runCompiler(this.compiler.exe, newOptions, this.filename(inputFilename), execOptions)
+ .then((output) => this.processIrOutput(output, filters));
+ }
+
+ processIrOutput(output, filters) {
+ const irPath = output.inputFilename.replace(path.extname(output.inputFilename), '.ll');
+ if (fs.existsSync(irPath)) {
+ const output = fs.readFileSync(irPath, 'utf-8');
+ // uses same filters as main compiler
+ return this.llvmIr.process(output, filters);
+ }
+ return this.llvmIr.process(output.stdout, filters);
+ }
+
getOutputFilename(dirPath, outputFilebase) {
// NB keep lower case as ldc compiler `tolower`s the output name
return path.join(dirPath, `${outputFilebase}.s`);
@@ -439,6 +464,13 @@ class BaseCompiler {
astPromise = Promise.resolve("");
}
+ let irPromise;
+ if (backendOptions && backendOptions.produceIr && this.compiler.supportsIrView) {
+ irPromise = this.generateIR(inputFilename, options, filters);
+ } else {
+ irPromise = Promise.resolve("");
+ }
+
let gccDumpPromise;
if (backendOptions && backendOptions.produceGccDump && backendOptions.produceGccDump.opened &&
this.compiler.supportsGccDump) {
@@ -450,8 +482,14 @@ class BaseCompiler {
gccDumpPromise = Promise.resolve("");
}
- return Promise.all([asmPromise, astPromise, gccDumpPromise, Promise.all(toolsPromise)])
- .then(([asmResult, astResult, gccDumpResult, toolsPromise]) => {
+ return Promise.all([
+ asmPromise,
+ astPromise,
+ gccDumpPromise,
+ irPromise,
+ Promise.all(toolsPromise)
+ ])
+ .then(([asmResult, astResult, gccDumpResult, irResult, toolsPromise]) => {
asmResult.dirPath = dirPath;
asmResult.compilationOptions = options;
// Here before the check to ensure dump reports even on failure cases
@@ -465,7 +503,7 @@ class BaseCompiler {
asmResult.asm = "<Compilation failed>";
return asmResult;
}
- asmResult.hasOptOutput = false;
+
if (this.compiler.supportsOptOutput && this.optOutputRequested(options)) {
const optPath = path.join(dirPath, `${this.outputFilebase}.opt.yaml`);
if (fs.existsSync(optPath)) {
@@ -477,6 +515,10 @@ class BaseCompiler {
asmResult.hasAstOutput = true;
asmResult.astOutput = astResult;
}
+ if (irResult) {
+ asmResult.hasIrOutput = true;
+ asmResult.irOutput = irResult;
+ }
return this.checkOutputFileAndDoPostProcess(asmResult, outputFilename, filters);
});
@@ -527,7 +569,7 @@ class BaseCompiler {
result.dirPath = undefined;
}
if (result.okToCache) {
- result.asm = this.asm.process(result.asm, filters);
+ result.asm = this.processAsm(result, filters);
} else {
result.asm = [{text: result.asm}];
}
@@ -563,6 +605,13 @@ class BaseCompiler {
});
}
+ processAsm(result, filters) {
+ if (this.llvmIr.isLlvmIr(result.asm)) {
+ return this.llvmIr.process(result.asm, filters);
+ }
+ return this.asm.process(result.asm, filters);
+ }
+
postProcessAsm(result) {
if (!result.okToCache) return result;
const demanglerExe = this.compiler.demangler;
diff --git a/lib/compilers/argument-parsers.js b/lib/compilers/argument-parsers.js
index b5d534818..0476ad827 100644
--- a/lib/compilers/argument-parsers.js
+++ b/lib/compilers/argument-parsers.js
@@ -122,6 +122,10 @@ class ClangParser extends BaseParser {
if (compiler.compiler.options) compiler.compiler.options += " ";
compiler.compiler.options += "-fcolor-diagnostics";
}
+ if (BaseParser.hasSupport(options, "-emit-llvm")) {
+ compiler.compiler.supportsIrView = true;
+ compiler.compiler.irArg = ['-Xclang', '-emit-llvm', '-fsyntax-only'];
+ }
if (BaseParser.hasSupport(options, "-fno-crash-diagnostics")) {
if (compiler.compiler.options) compiler.compiler.options += " ";
compiler.compiler.options += "-fno-crash-diagnostics";
diff --git a/lib/llvm-ir.js b/lib/llvm-ir.js
new file mode 100644
index 000000000..260c7944e
--- /dev/null
+++ b/lib/llvm-ir.js
@@ -0,0 +1,182 @@
+// Copyright (c) 2018, 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.
+
+const utils = require('./utils'),
+ _ = require('underscore');
+
+class LlvmIrParser {
+ constructor(compilerProps) {
+ this.maxIrLines = 500;
+ if (compilerProps) {
+ this.maxIrLines = compilerProps('maxLinesOfAsm', this.maxIrLines);
+ }
+
+ this.debugReference = /!dbg (!\d+)/;
+ this.metaNodeRe = /^(!\d+) = (?:distinct )?!DI([A-Za-z]+)\(([^)]+?)\)/;
+ this.metaNodeOptionsRe = /(\w+): (!?\d+|\w+|""|"(?:[^"]|\\")*[^\\]")/gi;
+ }
+
+ getFileName(debugInfo, scope) {
+ const stdInLooking = /.*<stdin>|^-$|example\.[^/]+$|<source>/;
+
+ if (!debugInfo[scope]) {
+ // No such meta info.
+ return null;
+ }
+ // MetaInfo is a file node
+ if (debugInfo[scope].filename) {
+ const filename = debugInfo[scope].filename;
+ return !filename.match(stdInLooking) ? filename : null;
+ }
+ // MetaInfo has a file reference.
+ if (debugInfo[scope].file) {
+ return this.getFileName(debugInfo, debugInfo[scope].file);
+ }
+ if (!debugInfo[scope].scope) {
+ // No higher scope => can't find file.
+ return null;
+ }
+ // "Bubbling" up.
+ return this.getFileName(debugInfo, debugInfo[scope].scope);
+ }
+
+ getSourceLineNumber(debugInfo, scope) {
+ if (!debugInfo[scope]) {
+ return null;
+ }
+ if (debugInfo[scope].line) {
+ return Number(debugInfo[scope].line);
+ }
+ if (debugInfo[scope].scope) {
+ return this.getSourceLineNumber(debugInfo, debugInfo[scope].scope);
+ }
+ return null;
+ }
+
+ parseMetaNode(line) {
+ // Metadata Nodes
+ // See: https://llvm.org/docs/LangRef.html#metadata
+ const match = line.match(this.metaNodeRe);
+ if (!match) {
+ return null;
+ }
+ let metaNode = {};
+ metaNode.metaId = match[1];
+ metaNode.metaType = match[2];
+
+ let keyValuePair;
+ while ((keyValuePair = this.metaNodeOptionsRe.exec(match[3]))) {
+ const key = keyValuePair[1];
+ metaNode[key] = keyValuePair[2];
+ // Remove "" from string
+ if (metaNode[key][0] === '"') {
+ metaNode[key] = metaNode[key].substr(1, metaNode[key].length - 2);
+ }
+ }
+
+ return metaNode;
+ }
+
+ processIr(ir, filters) {
+ const result = [];
+ let irLines = utils.splitLines(ir);
+ let debugInfo = {};
+ let prevLineEmpty = false;
+
+ // Filters
+ const commentOnly = /^\s*(;.*)$/;
+
+ irLines.forEach(line => {
+ let source = null;
+ let match;
+
+ if (!line.trim().length) {
+ // Avoid multiple successive empty lines.
+ if (!prevLineEmpty) {
+ result.push({text: "", source: null});
+ }
+ prevLineEmpty = true;
+ return;
+ }
+
+ if (filters.commentOnly && line.match(commentOnly)) {
+ return;
+ }
+
+ // Non-Meta IR line. Metadata is attached to it using "!dbg !123"
+ match = line.match(this.debugReference);
+ if (match) {
+ result.push({text: (filters.trim ? utils.trimLine(line) : line), source: source, scope: match[1]});
+ prevLineEmpty = false;
+ return;
+ }
+
+ const metaNode = this.parseMetaNode(line);
+ if (metaNode) {
+ debugInfo[metaNode.metaId] = metaNode;
+ }
+
+ if (filters.directives && this.isLineLlvmDirective(line)) {
+ return;
+ }
+ result.push({text: (filters.trim ? utils.trimLine(line) : line), source: source});
+ prevLineEmpty = false;
+ });
+
+ if (result.length >= this.maxIrLines) {
+ result.length = this.maxIrLines + 1;
+ result[this.maxIrLines] = {text: "[truncated; too many lines]", source: null};
+ }
+
+ result.forEach(line => {
+ if (!line.scope) return;
+ line.source = {
+ file: this.getFileName(debugInfo, line.scope),
+ line: this.getSourceLineNumber(debugInfo, line.scope)
+ };
+ });
+
+ return result;
+ }
+
+ process(ir, filters) {
+ if (_.isString(ir)) {
+ return this.processIr(ir, filters);
+ }
+ }
+
+ isLineLlvmDirective(line) {
+ return !!(line.match(/^!\d+ = (distinct )?!(DI|{)/)
+ || line.match(/^!llvm./)
+ || line.match(/^source_filename = /)
+ || line.match(/^target datalayout = /)
+ || line.match(/^target triple = /));
+ }
+
+ isLlvmIr(code) {
+ return code.includes("@llvm") && code.includes("!DI") && code.includes("!dbg");
+ }
+}
+
+module.exports = LlvmIrParser;
diff --git a/lib/utils.js b/lib/utils.js
index 8af638d3c..585c4fced 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -29,6 +29,7 @@ const tabsRe = /\t/g;
const lineRe = /\r?\n/;
function splitLines(text) {
+ if (!text) return [];
const result = text.split(lineRe);
if (result.length > 0 && result[result.length - 1] === '')
return result.slice(0, result.length - 1);
@@ -175,3 +176,18 @@ exports.glGetMainContents = function glGetMainContents(content) {
});
return contents;
};
+
+function trimLine(line) {
+ if (!line.trim().length) {
+ return "";
+ }
+ const splat = line.split(/\s+/);
+ if (splat[0] === "") {
+ // An indented line: preserve a two-space indent (max)
+ const intent = line[1] !== " " ? " " : " ";
+ return intent + splat.slice(1).join(" ");
+ }
+ return splat.join(" ");
+}
+
+exports.trimLine = trimLine;