diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/asm-parser.js | 5 | ||||
-rw-r--r-- | lib/base-compiler.js | 57 | ||||
-rw-r--r-- | lib/compilers/argument-parsers.js | 4 | ||||
-rw-r--r-- | lib/llvm-ir.js | 182 | ||||
-rw-r--r-- | lib/utils.js | 16 |
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; |