diff options
Diffstat (limited to 'lib/llvm-ir.js')
-rw-r--r-- | lib/llvm-ir.js | 182 |
1 files changed, 182 insertions, 0 deletions
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; |