aboutsummaryrefslogtreecommitdiff
path: root/lib/llvm-ir.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/llvm-ir.js')
-rw-r--r--lib/llvm-ir.js182
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;