Repository: thlorenz/turbolizer Branch: master Commit: fa3377527f03 Files: 28 Total size: 116.3 KB Directory structure: gitextract_t6om2rjh/ ├── .gitignore ├── .npmignore ├── OWNERS ├── README.md ├── bin/ │ └── turbolizer ├── code-view.js ├── constants.js ├── disassembly-view.js ├── edge.js ├── empty-view.js ├── graph-layout.js ├── graph-view.js ├── index.html ├── lang-disassembly.js ├── lib/ │ ├── client-loader.js │ ├── turbolizer.js │ └── turbolizer.server.js ├── monkey.js ├── node.js ├── package.json ├── schedule-view.js ├── selection-broker.js ├── selection.js ├── text-view.js ├── turbo-visualizer.css ├── turbo-visualizer.js ├── util.js └── view.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ lib-cov *.seed *.log *.csv *.dat *.out *.pid *.gz pids logs results npm-debug.log node_modules ================================================ FILE: .npmignore ================================================ lib-cov *.seed *.log *.csv *.dat *.out *.pid *.gz pids logs results npm-debug.log node_modules assets ================================================ FILE: OWNERS ================================================ danno@chromium.org ================================================ FILE: README.md ================================================ # Turbolizer Turbolizer tool derived from the one included with `v8/tools`. ![turbolizer](./assets/turbolizer.gif) ## Installation ``` npm install -g turbolizer ``` ## Usage Run your app with the `--trace-turbo` flag, i.e. `node --trace-turbo app.js` to produce `turbo-*.json` files. Then just run `turbolizer` in the same directory and select which file (or all) you want to load and the turbolizer application will open in the browser with it preloaded. ## Alternatives If you don't want to install anything, as an alternative can then either load them one by one via the hosted browser version of this repo at [thlorenz.github.io/turbolizer](https://thlorenz.github.io/turbolizer). * * * _Original Readme from the [v8 repository](https://github.com/v8/v8)_ Turbolizer ========== Turbolizer is a HTML-based tool that visualizes optimized code along the various phases of Turbofan's optimization pipeline, allowing easy navigation between source code, Turbofan IR graphs, scheduled IR nodes and generated assembly code. Turbolizer consumes .json files that are generated per-function by d8 by passing the '--trace-turbo' command-line flag. Host the turbolizer locally by starting a web server that serves the contents of the turbolizer directory, e.g.: cd src/tools/turbolizer python -m SimpleHTTPServer 8000 Optionally, profiling data generated by the perf tools in linux can be merged with the .json files using the turbolizer-perf.py file included. The following command is an example of using the perf script: perf script -i perf.data.jitted -s turbolizer-perf.py turbo-main.json The output of the above command is a json object that can be piped to a file which, when uploaded to turbolizer, will display the event counts from perf next to each instruction in the disassembly. Further detail can be found in the bottom of this document under "Using Perf with Turbo." Using the python interface in perf script requires python-dev to be installed and perf be recompiled with python support enabled. Once recompiled, the variable PERF_EXEC_PATH must be set to the location of the recompiled perf binaries. Graph visualization and manipulation based on Mike Bostock's sample code for an interactive tool for creating directed graphs. Original source is at https://github.com/metacademy/directed-graph-creator and released under the MIT/X license. Icons derived from the "White Olive Collection" created by Breezi released under the Creative Commons BY license. Using Perf with Turbo --------------------- In order to generate perf data that matches exactly with the turbofan trace, you must use either a debug build of v8 or a release build with the flag 'disassembler=on'. This flag ensures that the '--trace-turbo' will output the necessary disassembly for linking with the perf profile. The basic example of generating the required data is as follows: perf record -k mono /path/to/d8 --trace-turbo --perf-prof main.js perf inject -j -i perf.data -o perf.data.jitted perf script -i perf.data.jitted -s turbolizer-perf.py turbo-main.json These commands combined will run and profile d8, merge the output into a single 'perf.data.jitted' file, then take the event data from that and link them to the disassembly in the 'turbo-main.json'. Note that, as above, the output of the script command must be piped to a file for uploading to turbolizer. There are many options that can be added to the first command, for example '-e' can be used to specify the counting of specific events (default: cycles), as well as '--cpu' to specify which CPU to sample. ================================================ FILE: bin/turbolizer ================================================ #!/usr/bin/env node 'use strict' const { prompt } = require('promptly') const { mapAllTurboFiles } = require('../lib/turbolizer') const { createServer, openWithFile } = require('../lib/turbolizer.server') function createPromptMsg(map) { let msg = 'Turbolizer - please select a file to view:\n\n' for (const [ selector, { entry } ] of map) { msg += `\t${selector}: ${entry}\n` } msg += '\t0: View ALL' return msg + '\n\nYour choice: ' } function createValidator(map) { return val => { if (val === '0') return val if (map.has(val)) return val throw new Error(`Invalid choice: '${val}', please select one of the given numbers`) } } const root = process.cwd() ;(async () => { try { const map = await mapAllTurboFiles(root) if (map.size === 0) { console.error('Turbolizer - Problem:\n') console.error(' Unable to find any "turbo-*.json" files in the current directory.\n') console.error(' Please run "node --trace-turbo app.js" in order to create them or follow the') console.error(' instructions at https://github.com/thlorenz/turbolizer/blob/master/README.md.') return } const msg = createPromptMsg(map) const result = await prompt(msg, { validator: createValidator(map) }) const { server, address } = createServer(root) server.on('listening', () => { const choice = result.trim() if (choice === '0') { for (const val of map.values()) { openWithFile({ address, file: val.entry }) } } else { openWithFile({ address, file: map.get(choice).entry }) } }) } catch (err) { console.error(err) } })() ================================================ FILE: code-view.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. "use strict"; class CodeView extends View { constructor(divID, PR, sourceText, sourcePosition, broker) { super(divID, broker, null, false); let view = this; view.PR = PR; view.mouseDown = false; view.broker = broker; view.allSpans = []; var selectionHandler = { clear: function() { broker.clear(selectionHandler); }, select: function(items, selected) { var handler = this; var broker = view.broker; for (let span of items) { if (selected) { span.classList.add("selected"); } else { span.classList.remove("selected"); } } var locations = []; for (var span of items) { locations.push({pos_start: span.start, pos_end: span.end}); } broker.clear(selectionHandler); broker.select(selectionHandler, locations, selected); }, selectionDifference: function(span1, inclusive1, span2, inclusive2) { var pos1 = span1.start; var pos2 = span2.start; var result = []; var lineListDiv = view.divNode.firstChild.firstChild.childNodes; for (var i = 0; i < lineListDiv.length; i++) { var currentLineElement = lineListDiv[i]; var spans = currentLineElement.childNodes; for (var j = 0; j < spans.length; ++j) { var currentSpan = spans[j]; if (currentSpan.start > pos1 || (inclusive1 && currentSpan.start == pos1)) { if (currentSpan.start < pos2 || (inclusive2 && currentSpan.start == pos2)) { result.push(currentSpan); } } } } return result; }, brokeredSelect: function(locations, selected) { let firstSelect = view.selection.isEmpty(); for (let location of locations) { let start = location.pos_start; let end = location.pos_end; if (start && end) { let lower = 0; let upper = view.allSpans.length; if (upper > 0) { while ((upper - lower) > 1) { var middle = Math.floor((upper + lower) / 2); var lineStart = view.allSpans[middle].start; if (lineStart < start) { lower = middle; } else if (lineStart > start) { upper = middle; } else { lower = middle; break; } } var currentSpan = view.allSpans[lower]; var currentLineElement = currentSpan.parentNode; if ((currentSpan.start <= start && start < currentSpan.end) || (currentSpan.start <= end && end < currentSpan.end)) { if (firstSelect) { makeContainerPosVisible( view.divNode, currentLineElement.offsetTop); firstSelect = false; } view.selection.select(currentSpan, selected); } } } } }, brokeredClear: function() { view.selection.clear(); }, }; view.selection = new Selection(selectionHandler); broker.addSelectionHandler(selectionHandler); view.handleSpanMouseDown = function(e) { e.stopPropagation(); if (!e.shiftKey) { view.selection.clear(); } view.selection.select(this, true); view.mouseDown = true; } view.handleSpanMouseMove = function(e) { if (view.mouseDown) { view.selection.extendTo(this); } } view.handleCodeMouseDown = function(e) { view.selection.clear(); } document.addEventListener('mouseup', function(e) { view.mouseDown = false; }, false); view.initializeCode(sourceText, sourcePosition); } initializeContent(data, rememberedSelection) { this.data = data; } initializeCode(sourceText, sourcePosition) { var view = this; var codePre = document.createElement("pre"); codePre.classList.add("prettyprint"); view.divNode.innerHTML = ""; view.divNode.appendChild(codePre); if (sourceText) { codePre.classList.add("linenums"); codePre.textContent = sourceText; try { // Wrap in try to work when offline. view.PR.prettyPrint(); } catch (e) { } view.divNode.onmousedown = this.handleCodeMouseDown; var base = sourcePosition; var current = 0; var lineListDiv = view.divNode.firstChild.firstChild.childNodes; for (let i = 0; i < lineListDiv.length; i++) { var currentLineElement = lineListDiv[i]; currentLineElement.id = "li" + i; var pos = base + current; currentLineElement.pos = pos; var spans = currentLineElement.childNodes; for (let j = 0; j < spans.length; ++j) { var currentSpan = spans[j]; if (currentSpan.nodeType == 1) { currentSpan.start = pos; currentSpan.end = pos + currentSpan.textContent.length; currentSpan.onmousedown = this.handleSpanMouseDown; currentSpan.onmousemove = this.handleSpanMouseMove; view.allSpans.push(currentSpan); } current += currentSpan.textContent.length; pos = base + current; } while ((current < sourceText.length) && (sourceText[current] == '\n' || sourceText[current] == '\r')) { ++current; } } } } deleteContent() {} } ================================================ FILE: constants.js ================================================ // Copyright 2014 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var MAX_RANK_SENTINEL = 0; var GRAPH_MARGIN = 250; var WIDTH = 'width'; var HEIGHT = 'height'; var VISIBILITY = 'visibility'; var SOURCE_PANE_ID = 'left'; var SOURCE_COLLAPSE_ID = 'source-shrink'; var SOURCE_EXPAND_ID = 'source-expand'; var INTERMEDIATE_PANE_ID = 'middle'; var EMPTY_PANE_ID = 'empty'; var GRAPH_PANE_ID = 'graph'; var SCHEDULE_PANE_ID = 'schedule'; var GENERATED_PANE_ID = 'right'; var DISASSEMBLY_PANE_ID = 'disassembly'; var DISASSEMBLY_COLLAPSE_ID = 'disassembly-shrink'; var DISASSEMBLY_EXPAND_ID = 'disassembly-expand'; var COLLAPSE_PANE_BUTTON_VISIBLE = 'button-input'; var COLLAPSE_PANE_BUTTON_INVISIBLE = 'button-input-invisible'; var UNICODE_BLOCK = '▋'; var PROF_COLS = [ { perc: 0, col: { r: 255, g: 255, b: 255 } }, { perc: 0.5, col: { r: 255, g: 255, b: 128 } }, { perc: 5, col: { r: 255, g: 128, b: 0 } }, { perc: 15, col: { r: 255, g: 0, b: 0 } }, { perc: 100, col: { r: 0, g: 0, b: 0 } } ]; ================================================ FILE: disassembly-view.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. "use strict"; class DisassemblyView extends TextView { constructor(id, broker) { super(id, broker, null, false); let view = this; let ADDRESS_STYLE = { css: 'tag', location: function(text) { ADDRESS_STYLE.last_address = text; return undefined; } }; let ADDRESS_LINK_STYLE = { css: 'tag', link: function(text) { view.select(function(location) { return location.address == text; }, true, true); } }; let UNCLASSIFIED_STYLE = { css: 'com' }; let NUMBER_STYLE = { css: 'lit' }; let COMMENT_STYLE = { css: 'com' }; let POSITION_STYLE = { css: 'com', location: function(text) { view.pos_start = Number(text); } }; let OPCODE_STYLE = { css: 'kwd', location: function(text) { if (BLOCK_HEADER_STYLE.block_id != undefined) { return { address: ADDRESS_STYLE.last_address, block_id: BLOCK_HEADER_STYLE.block_id }; } else { return { address: ADDRESS_STYLE.last_address }; } } }; const BLOCK_HEADER_STYLE = { css: 'com', block_id: -1, location: function(text) { let matches = /\d+/.exec(text); if (!matches) return undefined; BLOCK_HEADER_STYLE.block_id = Number(matches[0]); return { block_id: BLOCK_HEADER_STYLE.block_id }; }, }; const SOURCE_POSITION_HEADER_STYLE = { css: 'com', location: function(text) { let matches = /(\d+):(\d+)/.exec(text); if (!matches) return undefined; let li = Number(matches[1]); if (view.pos_lines === null) return undefined; let pos = view.pos_lines[li-1] + Number(matches[2]); return { pos_start: pos, pos_end: pos + 1 }; }, }; view.SOURCE_POSITION_HEADER_REGEX = /^(\s*-- .+:)(\d+:\d+)( --)/; let patterns = [ [ [/^0x[0-9a-f]{8,16}/, ADDRESS_STYLE, 1], [view.SOURCE_POSITION_HEADER_REGEX, SOURCE_POSITION_HEADER_STYLE, -1], [/^\s+-- B\d+ start.*/, BLOCK_HEADER_STYLE, -1], [/^.*/, UNCLASSIFIED_STYLE, -1] ], [ [/^\s+[0-9a-f]+\s+[0-9a-f]+\s+/, NUMBER_STYLE, 2], [/^.*/, null, -1] ], [ [/^\S+\s+/, OPCODE_STYLE, 3], [/^\S+$/, OPCODE_STYLE, -1], [/^.*/, null, -1] ], [ [/^\s+/, null], [/^[^\(;]+$/, null, -1], [/^[^\(;]+/, null], [/^\(/, null, 4], [/^;/, COMMENT_STYLE, 5] ], [ [/^0x[0-9a-f]{8,16}/, ADDRESS_LINK_STYLE], [/^[^\)]/, null], [/^\)$/, null, -1], [/^\)/, null, 3] ], [ [/^; debug\: position /, COMMENT_STYLE, 6], [/^.+$/, COMMENT_STYLE, -1] ], [ [/^\d+$/, POSITION_STYLE, -1], ] ]; view.setPatterns(patterns); } lineLocation(li) { let view = this; let result = undefined; for (let i = 0; i < li.children.length; ++i) { let fragment = li.children[i]; let location = fragment.location; if (location != null) { if (location.block_id != undefined) { if (result === undefined) result = {}; result.block_id = location.block_id; } if (location.address != undefined) { if (result === undefined) result = {}; result.address = location.address; } if (location.pos_start != undefined && location.pos_end != undefined) { if (result === undefined) result = {}; result.pos_start = location.pos_start; result.pos_end = location.pos_end; } else if (view.pos_start != -1) { if (result === undefined) result = {}; result.pos_start = view.pos_start; result.pos_end = result.pos_start + 1; } } } return result; } initializeContent(data, rememberedSelection) { this.data = data; super.initializeContent(data, rememberedSelection); } initializeCode(sourceText, sourcePosition) { let view = this; view.pos_start = -1; view.addr_event_counts = null; view.total_event_counts = null; view.max_event_counts = null; view.pos_lines = new Array(); // Comment lines for line 0 include sourcePosition already, only need to // add sourcePosition for lines > 0. view.pos_lines[0] = sourcePosition; if (sourceText) { let base = sourcePosition; let current = 0; let source_lines = sourceText.split("\n"); for (let i = 1; i < source_lines.length; i++) { // Add 1 for newline character that is split off. current += source_lines[i-1].length + 1; view.pos_lines[i] = base + current; } } } initializePerfProfile(eventCounts) { let view = this; if (eventCounts !== undefined) { view.addr_event_counts = eventCounts; view.total_event_counts = {}; view.max_event_counts = {}; for (let ev_name in view.addr_event_counts) { let keys = Object.keys(view.addr_event_counts[ev_name]); let values = keys.map(key => view.addr_event_counts[ev_name][key]); view.total_event_counts[ev_name] = values.reduce((a, b) => a + b); view.max_event_counts[ev_name] = values.reduce((a, b) => Math.max(a, b)); } } else { view.addr_event_counts = null; view.total_event_counts = null; view.max_event_counts = null; } } // Shorten decimals and remove trailing zeroes for readability. humanize(num) { return num.toFixed(3).replace(/\.?0+$/, "") + "%"; } // Interpolate between the given start and end values by a fraction of val/max. interpolate(val, max, start, end) { return start + (end - start) * (val / max); } processLine(line) { let view = this; let func = function(match, p1, p2, p3) { let nums = p2.split(":"); let li = Number(nums[0]); let pos = Number(nums[1]); if(li === 0) pos -= view.pos_lines[0]; li++; return p1 + li + ":" + pos + p3; }; line = line.replace(view.SOURCE_POSITION_HEADER_REGEX, func); let fragments = super.processLine(line); // Add profiling data per instruction if available. if (view.total_event_counts) { let matches = /^(0x[0-9a-fA-F]+)\s+\d+\s+[0-9a-fA-F]+/.exec(line); if (matches) { let newFragments = []; for (let event in view.addr_event_counts) { let count = view.addr_event_counts[event][matches[1]]; let str = " "; let css_cls = "prof"; if(count !== undefined) { let perc = count / view.total_event_counts[event] * 100; let col = { r: 255, g: 255, b: 255 }; for (let i = 0; i < PROF_COLS.length; i++) { if (perc === PROF_COLS[i].perc) { col = PROF_COLS[i].col; break; } else if (perc > PROF_COLS[i].perc && perc < PROF_COLS[i + 1].perc) { let col1 = PROF_COLS[i].col; let col2 = PROF_COLS[i + 1].col; let val = perc - PROF_COLS[i].perc; let max = PROF_COLS[i + 1].perc - PROF_COLS[i].perc; col.r = Math.round(view.interpolate(val, max, col1.r, col2.r)); col.g = Math.round(view.interpolate(val, max, col1.g, col2.g)); col.b = Math.round(view.interpolate(val, max, col1.b, col2.b)); break; } } str = UNICODE_BLOCK; let fragment = view.createFragment(str, css_cls); fragment.title = event + ": " + view.humanize(perc) + " (" + count + ")"; fragment.style.color = "rgb(" + col.r + ", " + col.g + ", " + col.b + ")"; newFragments.push(fragment); } else newFragments.push(view.createFragment(str, css_cls)); } fragments = newFragments.concat(fragments); } } return fragments; } } ================================================ FILE: edge.js ================================================ // Copyright 2014 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var MINIMUM_EDGE_SEPARATION = 20; function isEdgeInitiallyVisible(target, index, source, type) { return type == "control" && (target.cfg || source.cfg); } var Edge = function(target, index, source, type) { this.target = target; this.source = source; this.index = index; this.type = type; this.backEdgeNumber = 0; this.visible = isEdgeInitiallyVisible(target, index, source, type); }; Edge.prototype.stringID = function() { return this.source.id + "," + this.index + "," + this.target.id; }; Edge.prototype.isVisible = function() { return this.visible && this.source.visible && this.target.visible; }; Edge.prototype.getInputHorizontalPosition = function(graph) { if (this.backEdgeNumber > 0) { return graph.maxGraphNodeX + this.backEdgeNumber * MINIMUM_EDGE_SEPARATION; } var source = this.source; var target = this.target; var index = this.index; var input_x = target.x + target.getInputX(index); var inputApproach = target.getInputApproach(this.index); var outputApproach = source.getOutputApproach(graph); if (inputApproach > outputApproach) { return input_x; } else { var inputOffset = MINIMUM_EDGE_SEPARATION * (index + 1); return (target.x < source.x) ? (target.x + target.getTotalNodeWidth() + inputOffset) : (target.x - inputOffset) } } Edge.prototype.generatePath = function(graph) { var target = this.target; var source = this.source; var input_x = target.x + target.getInputX(this.index); var arrowheadHeight = 7; var input_y = target.y - 2 * DEFAULT_NODE_BUBBLE_RADIUS - arrowheadHeight; var output_x = source.x + source.getOutputX(); var output_y = source.y + graph.getNodeHeight(source) + DEFAULT_NODE_BUBBLE_RADIUS; var inputApproach = target.getInputApproach(this.index); var outputApproach = source.getOutputApproach(graph); var horizontalPos = this.getInputHorizontalPosition(graph); var result = "M" + output_x + "," + output_y + "L" + output_x + "," + outputApproach + "L" + horizontalPos + "," + outputApproach; if (horizontalPos != input_x) { result += "L" + horizontalPos + "," + inputApproach; } else { if (inputApproach < outputApproach) { inputApproach = outputApproach; } } result += "L" + input_x + "," + inputApproach + "L" + input_x + "," + input_y; return result; } Edge.prototype.isBackEdge = function() { return this.target.hasBackEdges() && (this.target.rank < this.source.rank); } ================================================ FILE: empty-view.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. "use strict"; class EmptyView extends View { constructor(id, broker) { super(id, broker); this.svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%"); } initializeContent(data, rememberedSelection) { this.svg.attr("height", document.documentElement.clientHeight + "px"); } deleteContent() { } } ================================================ FILE: graph-layout.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var DEFAULT_NODE_ROW_SEPARATION = 130 var traceLayout = false; function newGraphOccupation(graph){ var isSlotFilled = []; var maxSlot = 0; var minSlot = 0; var nodeOccupation = []; function slotToIndex(slot) { if (slot >= 0) { return slot * 2; } else { return slot * 2 + 1; } } function indexToSlot(index) { if ((index % 0) == 0) { return index / 2; } else { return -((index - 1) / 2); } } function positionToSlot(pos) { return Math.floor(pos / NODE_INPUT_WIDTH); } function slotToLeftPosition(slot) { return slot * NODE_INPUT_WIDTH } function slotToRightPosition(slot) { return (slot + 1) * NODE_INPUT_WIDTH } function findSpace(pos, width, direction) { var widthSlots = Math.floor((width + NODE_INPUT_WIDTH - 1) / NODE_INPUT_WIDTH); var currentSlot = positionToSlot(pos + width / 2); var currentScanSlot = currentSlot; var widthSlotsRemainingLeft = widthSlots; var widthSlotsRemainingRight = widthSlots; var slotsChecked = 0; while (true) { var mod = slotsChecked++ % 2; currentScanSlot = currentSlot + (mod ? -1 : 1) * (slotsChecked >> 1); if (!isSlotFilled[slotToIndex(currentScanSlot)]) { if (mod) { if (direction <= 0) --widthSlotsRemainingLeft } else { if (direction >= 0) --widthSlotsRemainingRight } if (widthSlotsRemainingLeft == 0 || widthSlotsRemainingRight == 0 || (widthSlotsRemainingLeft + widthSlotsRemainingRight) == widthSlots && (widthSlots == slotsChecked)) { if (mod) { return [currentScanSlot, widthSlots]; } else { return [currentScanSlot - widthSlots + 1, widthSlots]; } } } else { if (mod) { widthSlotsRemainingLeft = widthSlots; } else { widthSlotsRemainingRight = widthSlots; } } } } function setIndexRange(from, to, value) { if (to < from) { throw("illegal slot range"); } while (from <= to) { if (from > maxSlot) { maxSlot = from; } if (from < minSlot) { minSlot = from; } isSlotFilled[slotToIndex(from++)] = value; } } function occupySlotRange(from, to) { if (traceLayout) { console.log("Occupied [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")"); } setIndexRange(from, to, true); } function clearSlotRange(from, to) { if (traceLayout) { console.log("Cleared [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")"); } setIndexRange(from, to, false); } function occupyPositionRange(from, to) { occupySlotRange(positionToSlot(from), positionToSlot(to - 1)); } function clearPositionRange(from, to) { clearSlotRange(positionToSlot(from), positionToSlot(to - 1)); } function occupyPositionRangeWithMargin(from, to, margin) { var fromMargin = from - Math.floor(margin); var toMargin = to + Math.floor(margin); occupyPositionRange(fromMargin, toMargin); } function clearPositionRangeWithMargin(from, to, margin) { var fromMargin = from - Math.floor(margin); var toMargin = to + Math.floor(margin); clearPositionRange(fromMargin, toMargin); } var occupation = { occupyNodeInputs: function(node) { for (var i = 0; i < node.inputs.length; ++i) { if (node.inputs[i].isVisible()) { var edge = node.inputs[i]; if (!edge.isBackEdge()) { var source = edge.source; var horizontalPos = edge.getInputHorizontalPosition(graph); if (traceLayout) { console.log("Occupying input " + i + " of " + node.id + " at " + horizontalPos); } occupyPositionRangeWithMargin(horizontalPos, horizontalPos, NODE_INPUT_WIDTH / 2); } } } }, occupyNode: function(node) { var getPlacementHint = function(n) { var pos = 0; var direction = -1; var outputEdges = 0; var inputEdges = 0; for (var k = 0; k < n.outputs.length; ++k) { var outputEdge = n.outputs[k]; if (outputEdge.isVisible()) { var output = n.outputs[k].target; for (var l = 0; l < output.inputs.length; ++l) { if (output.rank > n.rank) { var inputEdge = output.inputs[l]; if (inputEdge.isVisible()) { ++inputEdges; } if (output.inputs[l].source == n) { pos += output.x + output.getInputX(l) + NODE_INPUT_WIDTH / 2; outputEdges++; if (l >= (output.inputs.length / 2)) { direction = 1; } } } } } } if (outputEdges != 0) { pos = pos / outputEdges; } if (outputEdges > 1 || inputEdges == 1) { direction = 0; } return [direction, pos]; } var width = node.getTotalNodeWidth(); var margin = MINIMUM_EDGE_SEPARATION; var paddedWidth = width + 2 * margin; var placementHint = getPlacementHint(node); var x = placementHint[1] - paddedWidth + margin; if (traceLayout) { console.log("Node " + node.id + " placement hint [" + x + ", " + (x + paddedWidth) + ")"); } var placement = findSpace(x, paddedWidth, placementHint[0]); var firstSlot = placement[0]; var slotWidth = placement[1]; var endSlotExclusive = firstSlot + slotWidth - 1; occupySlotRange(firstSlot, endSlotExclusive); nodeOccupation.push([firstSlot, endSlotExclusive]); if (placementHint[0] < 0) { return slotToLeftPosition(firstSlot + slotWidth) - width - margin; } else if (placementHint[0] > 0) { return slotToLeftPosition(firstSlot) + margin; } else { return slotToLeftPosition(firstSlot + slotWidth / 2) - (width / 2); } }, clearOccupiedNodes: function() { nodeOccupation.forEach(function(o) { clearSlotRange(o[0], o[1]); }); nodeOccupation = []; }, clearNodeOutputs: function(source) { source.outputs.forEach(function(edge) { if (edge.isVisible()) { var target = edge.target; for (var i = 0; i < target.inputs.length; ++i) { if (target.inputs[i].source === source) { var horizontalPos = edge.getInputHorizontalPosition(graph); clearPositionRangeWithMargin(horizontalPos, horizontalPos, NODE_INPUT_WIDTH / 2); } } } }); }, print: function() { var s = ""; for (var currentSlot = -40; currentSlot < 40; ++currentSlot) { if (currentSlot != 0) { s += " "; } else { s += "|"; } } console.log(s); s = ""; for (var currentSlot2 = -40; currentSlot2 < 40; ++currentSlot2) { if (isSlotFilled[slotToIndex(currentSlot2)]) { s += "*"; } else { s += " "; } } console.log(s); } } return occupation; } function layoutNodeGraph(graph) { // First determine the set of nodes that have no outputs. Those are the // basis for bottom-up DFS to determine rank and node placement. var endNodesHasNoOutputs = []; var startNodesHasNoInputs = []; graph.nodes.forEach(function(n, i){ endNodesHasNoOutputs[n.id] = true; startNodesHasNoInputs[n.id] = true; }); graph.edges.forEach(function(e, i){ endNodesHasNoOutputs[e.source.id] = false; startNodesHasNoInputs[e.target.id] = false; }); // Finialize the list of start and end nodes. var endNodes = []; var startNodes = []; var visited = []; var rank = []; graph.nodes.forEach(function(n, i){ if (endNodesHasNoOutputs[n.id]) { endNodes.push(n); } if (startNodesHasNoInputs[n.id]) { startNodes.push(n); } visited[n.id] = false; rank[n.id] = -1; n.rank = 0; n.visitOrderWithinRank = 0; n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH; }); var maxRank = 0; var visited = []; var dfsStack = []; var visitOrderWithinRank = 0; var worklist = startNodes.slice(); while (worklist.length != 0) { var n = worklist.pop(); var changed = false; if (n.rank == MAX_RANK_SENTINEL) { n.rank = 1; changed = true; } var begin = 0; var end = n.inputs.length; if (n.opcode == 'Phi' || n.opcode == 'EffectPhi') { // Keep with merge or loop node begin = n.inputs.length - 1; } else if (n.hasBackEdges()) { end = 1; } for (var l = begin; l < end; ++l) { var input = n.inputs[l].source; if (input.visible && input.rank >= n.rank) { n.rank = input.rank + 1; changed = true; } } if (changed) { var hasBackEdges = n.hasBackEdges(); for (var l = n.outputs.length - 1; l >= 0; --l) { if (hasBackEdges && (l != 0)) { worklist.unshift(n.outputs[l].target); } else { worklist.push(n.outputs[l].target); } } } if (n.rank > maxRank) { maxRank = n.rank; } } visited = []; function dfsFindRankLate(n) { if (visited[n.id]) return; visited[n.id] = true; var originalRank = n.rank; var newRank = n.rank; var firstInput = true; for (var l = 0; l < n.outputs.length; ++l) { var output = n.outputs[l].target; dfsFindRankLate(output); var outputRank = output.rank; if (output.visible && (firstInput || outputRank <= newRank) && (outputRank > originalRank)) { newRank = outputRank - 1; } firstInput = false; } if (n.opcode != "Start" && n.opcode != "Phi" && n.opcode != "EffectPhi") { n.rank = newRank; } } startNodes.forEach(dfsFindRankLate); visited = []; function dfsRankOrder(n) { if (visited[n.id]) return; visited[n.id] = true; for (var l = 0; l < n.outputs.length; ++l) { var edge = n.outputs[l]; if (edge.isVisible()) { var output = edge.target; dfsRankOrder(output); } } if (n.visitOrderWithinRank == 0) { n.visitOrderWithinRank = ++visitOrderWithinRank; } } startNodes.forEach(dfsRankOrder); endNodes.forEach(function(n) { n.rank = maxRank + 1; }); var rankSets = []; // Collect sets for each rank. graph.nodes.forEach(function(n, i){ n.y = n.rank * (DEFAULT_NODE_ROW_SEPARATION + graph.getNodeHeight(n) + 2 * DEFAULT_NODE_BUBBLE_RADIUS); if (n.visible) { if (rankSets[n.rank] === undefined) { rankSets[n.rank] = [n]; } else { rankSets[n.rank].push(n); } } }); // Iterate backwards from highest to lowest rank, placing nodes so that they // spread out from the "center" as much as possible while still being // compact and not overlapping live input lines. var occupation = newGraphOccupation(graph); var rankCount = 0; rankSets.reverse().forEach(function(rankSet) { for (var i = 0; i < rankSet.length; ++i) { occupation.clearNodeOutputs(rankSet[i]); } if (traceLayout) { console.log("After clearing outputs"); occupation.print(); } var placedCount = 0; rankSet = rankSet.sort(function(a,b) { return a.visitOrderWithinRank < b.visitOrderWithinRank; }); for (var i = 0; i < rankSet.length; ++i) { var nodeToPlace = rankSet[i]; if (nodeToPlace.visible) { nodeToPlace.x = occupation.occupyNode(nodeToPlace); if (traceLayout) { console.log("Node " + nodeToPlace.id + " is placed between [" + nodeToPlace.x + ", " + (nodeToPlace.x + nodeToPlace.getTotalNodeWidth()) + ")"); } var staggeredFlooredI = Math.floor(placedCount++ % 3); var delta = MINIMUM_EDGE_SEPARATION * staggeredFlooredI nodeToPlace.outputApproach += delta; } else { nodeToPlace.x = 0; } } if (traceLayout) { console.log("Before clearing nodes"); occupation.print(); } occupation.clearOccupiedNodes(); if (traceLayout) { console.log("After clearing nodes"); occupation.print(); } for (var i = 0; i < rankSet.length; ++i) { var node = rankSet[i]; occupation.occupyNodeInputs(node); } if (traceLayout) { console.log("After occupying inputs"); occupation.print(); } if (traceLayout) { console.log("After determining bounding box"); occupation.print(); } }); graph.maxBackEdgeNumber = 0; graph.visibleEdges.each(function (e) { if (e.isBackEdge()) { e.backEdgeNumber = ++graph.maxBackEdgeNumber; } else { e.backEdgeNumber = 0; } }); redetermineGraphBoundingBox(graph); } function redetermineGraphBoundingBox(graph) { graph.minGraphX = 0; graph.maxGraphNodeX = 1; graph.maxGraphX = undefined; // see below graph.minGraphY = 0; graph.maxGraphY = 1; for (var i = 0; i < graph.nodes.length; ++i) { var node = graph.nodes[i]; if (!node.visible) { continue; } if (node.x < graph.minGraphX) { graph.minGraphX = node.x; } if ((node.x + node.getTotalNodeWidth()) > graph.maxGraphNodeX) { graph.maxGraphNodeX = node.x + node.getTotalNodeWidth(); } if ((node.y - 50) < graph.minGraphY) { graph.minGraphY = node.y - 50; } if ((node.y + graph.getNodeHeight(node) + 50) > graph.maxGraphY) { graph.maxGraphY = node.y + graph.getNodeHeight(node) + 50; } } graph.maxGraphX = graph.maxGraphNodeX + graph.maxBackEdgeNumber * MINIMUM_EDGE_SEPARATION; } ================================================ FILE: graph-view.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. "use strict"; class GraphView extends View { constructor (d3, id, nodes, edges, broker) { super(id, broker); var graph = this; var svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%"); graph.svg = svg; graph.nodes = nodes || []; graph.edges = edges || []; graph.minGraphX = 0; graph.maxGraphX = 1; graph.minGraphY = 0; graph.maxGraphY = 1; graph.state = { selection: null, mouseDownNode: null, justDragged: false, justScaleTransGraph: false, lastKeyDown: -1, showTypes: false }; var selectionHandler = { clear: function() { broker.clear(selectionHandler); }, select: function(items, selected) { var locations = []; for (var d of items) { if (selected) { d.classList.add("selected"); } else { d.classList.remove("selected"); } var data = d.__data__; locations.push({ pos_start: data.pos, pos_end: data.pos + 1, node_id: data.id}); } broker.select(selectionHandler, locations, selected); }, selectionDifference: function(span1, inclusive1, span2, inclusive2) { // Should not be called }, brokeredSelect: function(locations, selected) { var test = [].entries().next(); var selection = graph.nodes .filter(function(n) { var pos = n.pos; for (var location of locations) { var start = location.pos_start; var end = location.pos_end; var id = location.node_id; if (end != undefined) { if (pos >= start && pos < end) { return true; } } else if (start != undefined) { if (pos === start) { return true; } } else { if (n.id === id) { return true; } } } return false; }); var newlySelected = new Set(); selection.forEach(function(n) { newlySelected.add(n); if (!n.visible) { n.visible = true; } }); graph.updateGraphVisibility(); graph.visibleNodes.each(function(n) { if (newlySelected.has(n)) { graph.state.selection.select(this, selected); } }); graph.updateGraphVisibility(); graph.viewSelection(); }, brokeredClear: function() { graph.state.selection.clear(); } }; broker.addSelectionHandler(selectionHandler); graph.state.selection = new Selection(selectionHandler); var defs = svg.append('svg:defs'); defs.append('svg:marker') .attr('id', 'end-arrow') .attr('viewBox', '0 -4 8 8') .attr('refX', 2) .attr('markerWidth', 2.5) .attr('markerHeight', 2.5) .attr('orient', 'auto') .append('svg:path') .attr('d', 'M0,-4L8,0L0,4'); this.graphElement = svg.append("g"); graph.visibleEdges = this.graphElement.append("g").selectAll("g"); graph.visibleNodes = this.graphElement.append("g").selectAll("g"); graph.drag = d3.behavior.drag() .origin(function(d){ return {x: d.x, y: d.y}; }) .on("drag", function(args){ graph.state.justDragged = true; graph.dragmove.call(graph, args); }) d3.select("#upload").on("click", partial(this.uploadAction, graph)); d3.select("#layout").on("click", partial(this.layoutAction, graph)); d3.select("#show-all").on("click", partial(this.showAllAction, graph)); d3.select("#hide-dead").on("click", partial(this.hideDeadAction, graph)); d3.select("#hide-unselected").on("click", partial(this.hideUnselectedAction, graph)); d3.select("#hide-selected").on("click", partial(this.hideSelectedAction, graph)); d3.select("#zoom-selection").on("click", partial(this.zoomSelectionAction, graph)); d3.select("#toggle-types").on("click", partial(this.toggleTypesAction, graph)); d3.select("#search-input").on("keydown", partial(this.searchInputAction, graph)); // listen for key events d3.select(window).on("keydown", function(e){ graph.svgKeyDown.call(graph); }) .on("keyup", function(){ graph.svgKeyUp.call(graph); }); svg.on("mousedown", function(d){graph.svgMouseDown.call(graph, d);}); svg.on("mouseup", function(d){graph.svgMouseUp.call(graph, d);}); graph.dragSvg = d3.behavior.zoom() .on("zoom", function(){ if (d3.event.sourceEvent.shiftKey){ return false; } else{ graph.zoomed.call(graph); } return true; }) .on("zoomstart", function(){ if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move"); }) .on("zoomend", function(){ d3.select('body').style("cursor", "auto"); }); svg.call(graph.dragSvg).on("dblclick.zoom", null); } static get selectedClass() { return "selected"; } static get rectClass() { return "nodeStyle"; } static get activeEditId() { return "active-editing"; } static get nodeRadius() { return 50; } getNodeHeight(d) { if (this.state.showTypes) { return d.normalheight + d.labelbbox.height; } else { return d.normalheight; } } getEdgeFrontier(nodes, inEdges, edgeFilter) { let frontier = new Set(); nodes.forEach(function(element) { var edges = inEdges ? element.__data__.inputs : element.__data__.outputs; var edgeNumber = 0; edges.forEach(function(edge) { if (edgeFilter == undefined || edgeFilter(edge, edgeNumber)) { frontier.add(edge); } ++edgeNumber; }); }); return frontier; } getNodeFrontier(nodes, inEdges, edgeFilter) { let graph = this; var frontier = new Set(); var newState = true; var edgeFrontier = graph.getEdgeFrontier(nodes, inEdges, edgeFilter); // Control key toggles edges rather than just turning them on if (d3.event.ctrlKey) { edgeFrontier.forEach(function(edge) { if (edge.visible) { newState = false; } }); } edgeFrontier.forEach(function(edge) { edge.visible = newState; if (newState) { var node = inEdges ? edge.source : edge.target; node.visible = true; frontier.add(node); } }); graph.updateGraphVisibility(); if (newState) { return graph.visibleNodes.filter(function(n) { return frontier.has(n); }); } else { return undefined; } } dragmove(d) { var graph = this; d.x += d3.event.dx; d.y += d3.event.dy; graph.updateGraphVisibility(); } initializeContent(data, rememberedSelection) { this.createGraph(data, rememberedSelection); if (rememberedSelection != null) { this.attachSelection(rememberedSelection); this.connectVisibleSelectedNodes(); this.viewSelection(); } this.updateGraphVisibility(); } deleteContent() { if (this.visibleNodes) { this.nodes = []; this.edges = []; this.nodeMap = []; this.updateGraphVisibility(); } }; measureText(text) { var textMeasure = document.getElementById('text-measure'); textMeasure.textContent = text; return { width: textMeasure.getBBox().width, height: textMeasure.getBBox().height, }; } createGraph(data, initiallyVisibileIds) { var g = this; g.nodes = data.nodes; g.nodeMap = []; g.nodes.forEach(function(n, i){ n.__proto__ = Node; n.visible = false; n.x = 0; n.y = 0; n.rank = MAX_RANK_SENTINEL; n.inputs = []; n.outputs = []; n.rpo = -1; n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH; n.cfg = n.control; g.nodeMap[n.id] = n; n.displayLabel = n.getDisplayLabel(); n.labelbbox = g.measureText(n.displayLabel); n.typebbox = g.measureText(n.getDisplayType()); var innerwidth = Math.max(n.labelbbox.width, n.typebbox.width); n.width = Math.alignUp(innerwidth + NODE_INPUT_WIDTH * 2, NODE_INPUT_WIDTH); var innerheight = Math.max(n.labelbbox.height, n.typebbox.height); n.normalheight = innerheight + 20; }); g.edges = []; data.edges.forEach(function(e, i){ var t = g.nodeMap[e.target]; var s = g.nodeMap[e.source]; var newEdge = new Edge(t, e.index, s, e.type); t.inputs.push(newEdge); s.outputs.push(newEdge); g.edges.push(newEdge); if (e.type == 'control') { s.cfg = true; } }); g.nodes.forEach(function(n, i) { n.visible = isNodeInitiallyVisible(n); if (initiallyVisibileIds != undefined) { if (initiallyVisibileIds.has(n.id)) { n.visible = true; } } }); g.fitGraphViewToWindow(); g.updateGraphVisibility(); g.layoutGraph(); g.updateGraphVisibility(); g.viewWholeGraph(); } connectVisibleSelectedNodes() { var graph = this; graph.state.selection.selection.forEach(function(element) { var edgeNumber = 0; element.__data__.inputs.forEach(function(edge) { if (edge.source.visible && edge.target.visible) { edge.visible = true; } }); element.__data__.outputs.forEach(function(edge) { if (edge.source.visible && edge.target.visible) { edge.visible = true; } }); }); } updateInputAndOutputBubbles() { var g = this; var s = g.visibleBubbles; s.classed("filledBubbleStyle", function(c) { var components = this.id.split(','); if (components[0] == "ib") { var edge = g.nodeMap[components[3]].inputs[components[2]]; return edge.isVisible(); } else { return g.nodeMap[components[1]].areAnyOutputsVisible() == 2; } }).classed("halfFilledBubbleStyle", function(c) { var components = this.id.split(','); if (components[0] == "ib") { var edge = g.nodeMap[components[3]].inputs[components[2]]; return false; } else { return g.nodeMap[components[1]].areAnyOutputsVisible() == 1; } }).classed("bubbleStyle", function(c) { var components = this.id.split(','); if (components[0] == "ib") { var edge = g.nodeMap[components[3]].inputs[components[2]]; return !edge.isVisible(); } else { return g.nodeMap[components[1]].areAnyOutputsVisible() == 0; } }); s.each(function(c) { var components = this.id.split(','); if (components[0] == "ob") { var from = g.nodeMap[components[1]]; var x = from.getOutputX(); var y = g.getNodeHeight(from) + DEFAULT_NODE_BUBBLE_RADIUS; var transform = "translate(" + x + "," + y + ")"; this.setAttribute('transform', transform); } }); } attachSelection(s) { var graph = this; if (s.size != 0) { this.visibleNodes.each(function(n) { if (s.has(this.__data__.id)) { graph.state.selection.select(this, true); } }); } } detachSelection() { var selection = this.state.selection.detachSelection(); var s = new Set(); for (var i of selection) { s.add(i.__data__.id); }; return s; } pathMouseDown(path, d) { d3.event.stopPropagation(); this.state.selection.clear(); this.state.selection.add(path); }; nodeMouseDown(node, d) { d3.event.stopPropagation(); this.state.mouseDownNode = d; } nodeMouseUp(d3node, d) { var graph = this, state = graph.state, consts = graph.consts; var mouseDownNode = state.mouseDownNode; if (!mouseDownNode) return; if (state.justDragged) { // dragged, not clicked redetermineGraphBoundingBox(graph); state.justDragged = false; } else{ // clicked, not dragged var extend = d3.event.shiftKey; var selection = graph.state.selection; if (!extend) { selection.clear(); } selection.select(d3node[0][0], true); } } selectSourcePositions(start, end, selected) { var graph = this; var map = []; var sel = graph.nodes.filter(function(n) { var pos = (n.pos === undefined) ? -1 : n.getFunctionRelativeSourcePosition(graph); if (pos >= start && pos < end) { map[n.id] = true; n.visible = true; } }); graph.updateGraphVisibility(); graph.visibleNodes.filter(function(n) { return map[n.id]; }) .each(function(n) { var selection = graph.state.selection; selection.select(d3.select(this), selected); }); } selectAllNodes(inEdges, filter) { var graph = this; if (!d3.event.shiftKey) { graph.state.selection.clear(); } graph.state.selection.select(graph.visibleNodes[0], true); graph.updateGraphVisibility(); } uploadAction(graph) { document.getElementById("hidden-file-upload").click(); } layoutAction(graph) { graph.updateGraphVisibility(); graph.layoutGraph(); graph.updateGraphVisibility(); graph.viewWholeGraph(); } showAllAction(graph) { graph.nodes.filter(function(n) { n.visible = true; }) graph.edges.filter(function(e) { e.visible = true; }) graph.updateGraphVisibility(); graph.viewWholeGraph(); } hideDeadAction(graph) { graph.nodes.filter(function(n) { if (!n.isLive()) n.visible = false; }) graph.updateGraphVisibility(); } hideUnselectedAction(graph) { var unselected = graph.visibleNodes.filter(function(n) { return !this.classList.contains("selected"); }); unselected.each(function(n) { n.visible = false; }); graph.updateGraphVisibility(); } hideSelectedAction(graph) { var selected = graph.visibleNodes.filter(function(n) { return this.classList.contains("selected"); }); selected.each(function(n) { n.visible = false; }); graph.state.selection.clear(); graph.updateGraphVisibility(); } zoomSelectionAction(graph) { graph.viewSelection(); } toggleTypesAction(graph) { graph.toggleTypes(); } searchInputAction(graph) { if (d3.event.keyCode == 13) { graph.state.selection.clear(); var query = this.value; window.sessionStorage.setItem("lastSearch", query); var reg = new RegExp(query); var filterFunction = function(n) { return (reg.exec(n.getDisplayLabel()) != null || (graph.state.showTypes && reg.exec(n.getDisplayType())) || reg.exec(n.opcode) != null); }; if (d3.event.ctrlKey) { graph.nodes.forEach(function(n, i) { if (filterFunction(n)) { n.visible = true; } }); graph.updateGraphVisibility(); } var selected = graph.visibleNodes.each(function(n) { if (filterFunction(n)) { graph.state.selection.select(this, true); } }); graph.connectVisibleSelectedNodes(); graph.updateGraphVisibility(); this.blur(); graph.viewSelection(); } d3.event.stopPropagation(); } svgMouseDown() { this.state.graphMouseDown = true; } svgMouseUp() { var graph = this, state = graph.state; if (state.justScaleTransGraph) { // Dragged state.justScaleTransGraph = false; } else { // Clicked if (state.mouseDownNode == null) { graph.state.selection.clear(); } } state.mouseDownNode = null; state.graphMouseDown = false; } svgKeyDown() { var state = this.state; var graph = this; // Don't handle key press repetition if(state.lastKeyDown !== -1) return; var showSelectionFrontierNodes = function(inEdges, filter, select) { var frontier = graph.getNodeFrontier(state.selection.selection, inEdges, filter); if (frontier != undefined) { if (select) { if (!d3.event.shiftKey) { state.selection.clear(); } state.selection.select(frontier[0], true); } graph.updateGraphVisibility(); } allowRepetition = false; } var allowRepetition = true; var eventHandled = true; // unless the below switch defaults switch(d3.event.keyCode) { case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // '1'-'9' showSelectionFrontierNodes(true, (edge, index) => { return index == (d3.event.keyCode - 49); }, false); break; case 97: case 98: case 99: case 100: case 101: case 102: case 103: case 104: case 105: // 'numpad 1'-'numpad 9' showSelectionFrontierNodes(true, (edge, index) => { return index == (d3.event.keyCode - 97); }, false); break; case 67: // 'c' showSelectionFrontierNodes(true, (edge, index) => { return edge.type == 'control'; }, false); break; case 69: // 'e' showSelectionFrontierNodes(true, (edge, index) => { return edge.type == 'effect'; }, false); break; case 79: // 'o' showSelectionFrontierNodes(false, undefined, false); break; case 73: // 'i' showSelectionFrontierNodes(true, undefined, false); break; case 65: // 'a' graph.selectAllNodes(); allowRepetition = false; break; case 38: case 40: { showSelectionFrontierNodes(d3.event.keyCode == 38, undefined, true); break; } case 82: // 'r' if (!d3.event.ctrlKey) { this.layoutAction(this); } else { eventHandled = false; } break; case 191: // '/' document.getElementById("search-input").focus(); document.getElementById("search-input").select(); break; default: eventHandled = false; break; } if (eventHandled) { d3.event.preventDefault(); } if (!allowRepetition) { state.lastKeyDown = d3.event.keyCode; } } svgKeyUp() { this.state.lastKeyDown = -1 }; layoutEdges() { var graph = this; graph.maxGraphX = graph.maxGraphNodeX; this.visibleEdges.attr("d", function(edge){ return edge.generatePath(graph); }); } layoutGraph() { layoutNodeGraph(this); } // call to propagate changes to graph updateGraphVisibility() { var graph = this, state = graph.state; var filteredEdges = graph.edges.filter(function(e) { return e.isVisible(); }); var visibleEdges = graph.visibleEdges.data(filteredEdges, function(edge) { return edge.stringID(); }); // add new paths visibleEdges.enter() .append('path') .style('marker-end','url(#end-arrow)') .classed('hidden', function(e) { return !e.isVisible(); }) .attr("id", function(edge){ return "e," + edge.stringID(); }) .on("mousedown", function(d){ graph.pathMouseDown.call(graph, d3.select(this), d); }) .attr("adjacentToHover", "false"); // Set the correct styles on all of the paths visibleEdges.classed('value', function(e) { return e.type == 'value' || e.type == 'context'; }).classed('control', function(e) { return e.type == 'control'; }).classed('effect', function(e) { return e.type == 'effect'; }).classed('frame-state', function(e) { return e.type == 'frame-state'; }).attr('stroke-dasharray', function(e) { if (e.type == 'frame-state') return "10,10"; return (e.type == 'effect') ? "5,5" : ""; }); // remove old links visibleEdges.exit().remove(); graph.visibleEdges = visibleEdges; // update existing nodes var filteredNodes = graph.nodes.filter(function(n) { return n.visible; }); graph.visibleNodes = graph.visibleNodes.data(filteredNodes, function(d) { return d.id; }); graph.visibleNodes.attr("transform", function(n){ return "translate(" + n.x + "," + n.y + ")"; }).select('rect'). attr(HEIGHT, function(d) { return graph.getNodeHeight(d); }); // add new nodes var newGs = graph.visibleNodes.enter() .append("g"); newGs.classed("turbonode", function(n) { return true; }) .classed("control", function(n) { return n.isControl(); }) .classed("live", function(n) { return n.isLive(); }) .classed("dead", function(n) { return !n.isLive(); }) .classed("javascript", function(n) { return n.isJavaScript(); }) .classed("input", function(n) { return n.isInput(); }) .classed("simplified", function(n) { return n.isSimplified(); }) .classed("machine", function(n) { return n.isMachine(); }) .attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")";}) .on("mousedown", function(d){ graph.nodeMouseDown.call(graph, d3.select(this), d); }) .on("mouseup", function(d){ graph.nodeMouseUp.call(graph, d3.select(this), d); }) .on('mouseover', function(d){ var nodeSelection = d3.select(this); let node = graph.nodeMap[d.id]; let adjInputEdges = graph.visibleEdges.filter(e => { return e.target === node; }); let adjOutputEdges = graph.visibleEdges.filter(e => { return e.source === node; }); adjInputEdges.attr('relToHover', "input"); adjOutputEdges.attr('relToHover', "output"); let adjInputNodes = adjInputEdges.data().map(e => e.source); graph.visibleNodes.data(adjInputNodes, function(d) { return d.id; }).attr('relToHover', "input"); let adjOutputNodes = adjOutputEdges.data().map(e => e.target); graph.visibleNodes.data(adjOutputNodes, function(d) { return d.id; }).attr('relToHover', "output"); graph.updateGraphVisibility(); }) .on('mouseout', function(d){ var nodeSelection = d3.select(this); let node = graph.nodeMap[d.id]; let adjEdges = graph.visibleEdges.filter(e => { return e.target === node || e.source === node; }); adjEdges.attr('relToHover', "none"); let adjNodes = adjEdges.data().map(e => e.target).concat(adjEdges.data().map(e => e.source)); let nodes = graph.visibleNodes.data(adjNodes, function(d) { return d.id; }).attr('relToHover', "none"); graph.updateGraphVisibility(); }) .call(graph.drag); newGs.append("rect") .attr("rx", 10) .attr("ry", 10) .attr(WIDTH, function(d) { return d.getTotalNodeWidth(); }) .attr(HEIGHT, function(d) { return graph.getNodeHeight(d); }) function appendInputAndOutputBubbles(g, d) { for (var i = 0; i < d.inputs.length; ++i) { var x = d.getInputX(i); var y = -DEFAULT_NODE_BUBBLE_RADIUS; var s = g.append('circle') .classed("filledBubbleStyle", function(c) { return d.inputs[i].isVisible(); } ) .classed("bubbleStyle", function(c) { return !d.inputs[i].isVisible(); } ) .attr("id", "ib," + d.inputs[i].stringID()) .attr("r", DEFAULT_NODE_BUBBLE_RADIUS) .attr("transform", function(d) { return "translate(" + x + "," + y + ")"; }) .on("mousedown", function(d){ var components = this.id.split(','); var node = graph.nodeMap[components[3]]; var edge = node.inputs[components[2]]; var visible = !edge.isVisible(); node.setInputVisibility(components[2], visible); d3.event.stopPropagation(); graph.updateGraphVisibility(); }); } if (d.outputs.length != 0) { var x = d.getOutputX(); var y = graph.getNodeHeight(d) + DEFAULT_NODE_BUBBLE_RADIUS; var s = g.append('circle') .classed("filledBubbleStyle", function(c) { return d.areAnyOutputsVisible() == 2; } ) .classed("halFilledBubbleStyle", function(c) { return d.areAnyOutputsVisible() == 1; } ) .classed("bubbleStyle", function(c) { return d.areAnyOutputsVisible() == 0; } ) .attr("id", "ob," + d.id) .attr("r", DEFAULT_NODE_BUBBLE_RADIUS) .attr("transform", function(d) { return "translate(" + x + "," + y + ")"; }) .on("mousedown", function(d) { d.setOutputVisibility(d.areAnyOutputsVisible() == 0); d3.event.stopPropagation(); graph.updateGraphVisibility(); }); } } newGs.each(function(d){ appendInputAndOutputBubbles(d3.select(this), d); }); newGs.each(function(d){ d3.select(this).append("text") .classed("label", true) .attr("text-anchor","right") .attr("dx", 5) .attr("dy", 5) .append('tspan') .text(function(l) { return d.getDisplayLabel(); }) .append("title") .text(function(l) { return d.getTitle(); }) if (d.type != undefined) { d3.select(this).append("text") .classed("label", true) .classed("type", true) .attr("text-anchor","right") .attr("dx", 5) .attr("dy", d.labelbbox.height + 5) .append('tspan') .text(function(l) { return d.getDisplayType(); }) .append("title") .text(function(l) { return d.getType(); }) } }); graph.visibleNodes.select('.type').each(function (d) { this.setAttribute('visibility', graph.state.showTypes ? 'visible' : 'hidden'); }); // remove old nodes graph.visibleNodes.exit().remove(); graph.visibleBubbles = d3.selectAll('circle'); graph.updateInputAndOutputBubbles(); graph.layoutEdges(); graph.svg.style.height = '100%'; } getVisibleTranslation(translate, scale) { var graph = this; var height = (graph.maxGraphY - graph.minGraphY + 2 * GRAPH_MARGIN) * scale; var width = (graph.maxGraphX - graph.minGraphX + 2 * GRAPH_MARGIN) * scale; var dimensions = this.getSvgViewDimensions(); var baseY = translate[1]; var minY = (graph.minGraphY - GRAPH_MARGIN) * scale; var maxY = (graph.maxGraphY + GRAPH_MARGIN) * scale; var adjustY = 0; var adjustYCandidate = 0; if ((maxY + baseY) < dimensions[1]) { adjustYCandidate = dimensions[1] - (maxY + baseY); if ((minY + baseY + adjustYCandidate) > 0) { adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY; } else { adjustY = adjustYCandidate; } } else if (-baseY < minY) { adjustYCandidate = -(baseY + minY); if ((maxY + baseY + adjustYCandidate) < dimensions[1]) { adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY; } else { adjustY = adjustYCandidate; } } translate[1] += adjustY; var baseX = translate[0]; var minX = (graph.minGraphX - GRAPH_MARGIN) * scale; var maxX = (graph.maxGraphX + GRAPH_MARGIN) * scale; var adjustX = 0; var adjustXCandidate = 0; if ((maxX + baseX) < dimensions[0]) { adjustXCandidate = dimensions[0] - (maxX + baseX); if ((minX + baseX + adjustXCandidate) > 0) { adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX; } else { adjustX = adjustXCandidate; } } else if (-baseX < minX) { adjustXCandidate = -(baseX + minX); if ((maxX + baseX + adjustXCandidate) < dimensions[0]) { adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX; } else { adjustX = adjustXCandidate; } } translate[0] += adjustX; return translate; } translateClipped(translate, scale, transition) { var graph = this; var graphNode = this.graphElement[0][0]; var translate = this.getVisibleTranslation(translate, scale); if (transition) { graphNode.classList.add('visible-transition'); clearTimeout(graph.transitionTimout); graph.transitionTimout = setTimeout(function(){ graphNode.classList.remove('visible-transition'); }, 1000); } var translateString = "translate(" + translate[0] + "px," + translate[1] + "px) scale(" + scale + ")"; graphNode.style.transform = translateString; graph.dragSvg.translate(translate); graph.dragSvg.scale(scale); } zoomed(){ this.state.justScaleTransGraph = true; var scale = this.dragSvg.scale(); this.translateClipped(d3.event.translate, scale); } getSvgViewDimensions() { var canvasWidth = this.parentNode.clientWidth; var documentElement = document.documentElement; var canvasHeight = documentElement.clientHeight; return [canvasWidth, canvasHeight]; } minScale() { var graph = this; var dimensions = this.getSvgViewDimensions(); var width = graph.maxGraphX - graph.minGraphX; var height = graph.maxGraphY - graph.minGraphY; var minScale = dimensions[0] / (width + GRAPH_MARGIN * 2); var minScaleYCandidate = dimensions[1] / (height + GRAPH_MARGIN * 2); if (minScaleYCandidate < minScale) { minScale = minScaleYCandidate; } this.dragSvg.scaleExtent([minScale, 1.5]); return minScale; } fitGraphViewToWindow() { this.svg.attr("height", document.documentElement.clientHeight + "px"); this.translateClipped(this.dragSvg.translate(), this.dragSvg.scale()); } toggleTypes() { var graph = this; graph.state.showTypes = !graph.state.showTypes; var element = document.getElementById('toggle-types'); if (graph.state.showTypes) { element.classList.add('button-input-toggled'); } else { element.classList.remove('button-input-toggled'); } graph.updateGraphVisibility(); } viewSelection() { var graph = this; var minX, maxX, minY, maxY; var hasSelection = false; graph.visibleNodes.each(function(n) { if (this.classList.contains("selected")) { hasSelection = true; minX = minX ? Math.min(minX, n.x) : n.x; maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) : n.x + n.getTotalNodeWidth(); minY = minY ? Math.min(minY, n.y) : n.y; maxY = maxY ? Math.max(maxY, n.y + graph.getNodeHeight(n)) : n.y + graph.getNodeHeight(n); } }); if (hasSelection) { graph.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60, maxX + NODE_INPUT_WIDTH, maxY + 60, true); } } viewGraphRegion(minX, minY, maxX, maxY, transition) { var graph = this; var dimensions = this.getSvgViewDimensions(); var width = maxX - minX; var height = maxY - minY; var scale = Math.min(dimensions[0] / width, dimensions[1] / height); scale = Math.min(1.5, scale); scale = Math.max(graph.minScale(), scale); var translation = [-minX*scale, -minY*scale]; translation = graph.getVisibleTranslation(translation, scale); graph.translateClipped(translation, scale, transition); } viewWholeGraph() { var graph = this; var minScale = graph.minScale(); var translation = [0, 0]; translation = graph.getVisibleTranslation(translation, minScale); graph.translateClipped(translation, minScale); } } ================================================ FILE: index.html ================================================ Turbolizer
      
          
================================================ FILE: lang-disassembly.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. PR.registerLangHandler( PR.createSimpleLexer( [ [PR.PR_STRING, /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$))/, null, '\''], [PR.PR_PLAIN, /^\s+/, null, ' \r\n\t\xA0'] ], [ // fallthroughStylePatterns [PR.PR_COMMENT, /;; debug: position \d+/, null], ]), ['disassembly']); ================================================ FILE: lib/client-loader.js ================================================ 'use strict' /* global location fetch */ async function preload() { try { if (!(/^\?preload=/).test(location.search)) return const fileUrl = location.search.replace(/^\?preload=/, '') const fileData = await fetch(fileUrl, { mode: 'no-cors' }) const txt = await fileData.text() window.renderTurbolizerData(txt) } catch (err) { console.error(err) } } preload() ================================================ FILE: lib/turbolizer.js ================================================ 'use strict' const { promisify } = require('util') const path = require('path') const fs = require('fs') const readdir = promisify(fs.readdir) const stat = promisify(fs.stat) const access = promisify(fs.access) async function canRead(p) { try { await access(p, fs.constants.R_OK) return true } catch (err) { return false } } async function findAllTurboFiles(root) { const allEntries = await readdir(root) const turboFiles = [] for (const entry of allEntries) { if (!(/^turbo-.+\.json$/).test(entry)) continue const fullPath = path.join(root, entry) if (!(await canRead(fullPath))) continue if (!(await stat(fullPath)).isFile()) continue turboFiles.push({ fullPath, entry }) } return turboFiles } function makeSelectable(arr) { const map = new Map() for (var i = 0; i < arr.length; i++) { map.set(`${i + 1}`, arr[i]) } return map } async function mapAllTurboFiles(root) { const arr = await findAllTurboFiles(root) return makeSelectable(arr) } module.exports = { findAllTurboFiles , mapAllTurboFiles } ================================================ FILE: lib/turbolizer.server.js ================================================ 'use strict' const path = require('path') const ecstatic = require('ecstatic') const connect = require('connect') const opener = require('opener') function createServer(turboFilesDir, PORT = null) { const appDir = path.join(__dirname, '..') const ecstaticApp = ecstatic({ root: appDir }) const ecstaticFiles = ecstatic({ root: turboFilesDir }) const app = connect() .use('/', ecstaticApp) .use('/data/', ecstaticFiles) const server = app.listen(PORT) const { port } = server.address() const address = `http://localhost:${port}` return { server, address } } function openWithFile({ address, file }) { const url = `${address}?preload=${address}/data/${file}` opener(url) } module.exports = { createServer, openWithFile } ================================================ FILE: monkey.js ================================================ // Copyright 2014 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. Math.alignUp = function(raw, multiple) { return Math.floor((raw + multiple - 1) / multiple) * multiple; } ================================================ FILE: node.js ================================================ // Copyright 2014 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var TYPE_HEIGHT = 25; var DEFAULT_NODE_BUBBLE_RADIUS = 12; var NODE_INPUT_WIDTH = 50; var MINIMUM_NODE_INPUT_APPROACH = 15 + 2 * DEFAULT_NODE_BUBBLE_RADIUS; var MINIMUM_NODE_OUTPUT_APPROACH = 15; function isNodeInitiallyVisible(node) { return node.cfg; } var Node = { isControl: function() { return this.control; }, isInput: function() { return this.opcode == 'Parameter' || this.opcode.endsWith('Constant'); }, isLive: function() { return this.live !== false; }, isJavaScript: function() { return this.opcode.startsWith('JS'); }, isSimplified: function() { if (this.isJavaScript()) return false; return this.opcode.endsWith('Phi') || this.opcode.startsWith('Boolean') || this.opcode.startsWith('Number') || this.opcode.startsWith('String') || this.opcode.startsWith('Change') || this.opcode.startsWith('Object') || this.opcode.startsWith('Reference') || this.opcode.startsWith('Any') || this.opcode.endsWith('ToNumber') || (this.opcode == 'AnyToBoolean') || (this.opcode.startsWith('Load') && this.opcode.length > 4) || (this.opcode.startsWith('Store') && this.opcode.length > 5); }, isMachine: function() { return !(this.isControl() || this.isInput() || this.isJavaScript() || this.isSimplified()); }, getTotalNodeWidth: function() { var inputWidth = this.inputs.length * NODE_INPUT_WIDTH; return Math.max(inputWidth, this.width); }, getTitle: function() { var propsString; if (this.properties === undefined) { propsString = ""; } else if (this.properties === "") { propsString = "no properties"; } else { propsString = "[" + this.properties + "]"; } return this.title + "\n" + propsString + "\n" + this.opinfo; }, getDisplayLabel: function() { var result = this.id + ":" + this.label; if (result.length > 40) { return this.id + ":" + this.opcode; } else { return result; } }, getType: function() { return this.type; }, getDisplayType: function() { var type_string = this.type; if (type_string == undefined) return ""; if (type_string.length > 24) { type_string = type_string.substr(0, 25) + "..."; } return type_string; }, deepestInputRank: function() { var deepestRank = 0; this.inputs.forEach(function(e) { if (e.isVisible() && !e.isBackEdge()) { if (e.source.rank > deepestRank) { deepestRank = e.source.rank; } } }); return deepestRank; }, areAnyOutputsVisible: function() { var visibleCount = 0; this.outputs.forEach(function(e) { if (e.isVisible()) ++visibleCount; }); if (this.outputs.length == visibleCount) return 2; if (visibleCount != 0) return 1; return 0; }, setOutputVisibility: function(v) { var result = false; this.outputs.forEach(function(e) { e.visible = v; if (v) { if (!e.target.visible) { e.target.visible = true; result = true; } } }); return result; }, setInputVisibility: function(i, v) { var edge = this.inputs[i]; edge.visible = v; if (v) { if (!edge.source.visible) { edge.source.visible = true; return true; } } return false; }, getInputApproach: function(index) { return this.y - MINIMUM_NODE_INPUT_APPROACH - (index % 4) * MINIMUM_EDGE_SEPARATION - DEFAULT_NODE_BUBBLE_RADIUS }, getOutputApproach: function(graph, index) { return this.y + this.outputApproach + graph.getNodeHeight(this) + + DEFAULT_NODE_BUBBLE_RADIUS; }, getInputX: function(index) { var result = this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2) + (index - this.inputs.length + 1) * NODE_INPUT_WIDTH; return result; }, getOutputX: function() { return this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2); }, getFunctionRelativeSourcePosition: function(graph) { return this.pos - graph.sourcePosition; }, hasBackEdges: function() { return (this.opcode == "Loop") || ((this.opcode == "Phi" || this.opcode == "EffectPhi") && this.inputs[this.inputs.length - 1].source.opcode == "Loop"); } }; ================================================ FILE: package.json ================================================ { "name": "turbolizer", "version": "0.2.0", "description": "Turbolizer tool from the v8 repository with added support to preload a profile", "main": "lib/turbolizer.js", "bin": "bin/turbolizer", "repository": { "type": "git", "url": "git://github.com/thlorenz/turbolizer.git" }, "homepage": "https://github.com/thlorenz/turbolizer", "dependencies": { "connect": "~3.6.6", "ecstatic": "~4.1.4", "opener": "~1.4.3", "promptly": "~3.0.3" }, "devDependencies": {}, "keywords": [], "author": { "name": "Thorsten Lorenz", "email": "thlorenz@gmx.de", "url": "http://thlorenz.com" }, "license": { "type": "MIT", "url": "https://github.com/thlorenz/turbolizer/blob/master/LICENSE" }, "engine": { "node": ">=8" } } ================================================ FILE: schedule-view.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. "use strict"; class ScheduleView extends TextView { constructor(id, broker) { super(id, broker, null, false); let view = this; let BLOCK_STYLE = { css: 'tag' }; const BLOCK_HEADER_STYLE = { css: 'com', block_id: -1, location: function(text) { let matches = /\d+/.exec(text); if (!matches) return undefined; BLOCK_HEADER_STYLE.block_id = Number(matches[0]); return { block_id: BLOCK_HEADER_STYLE.block_id }; }, }; const BLOCK_LINK_STYLE = { css: 'tag', link: function(text) { let id = Number(text.substr(1)); view.select(function(location) { return location.block_id == id; }, true, true); } }; const ID_STYLE = { css: 'tag', location: function(text) { let matches = /\d+/.exec(text); return { node_id: Number(matches[0]), block_id: BLOCK_HEADER_STYLE.block_id }; }, }; const ID_LINK_STYLE = { css: 'tag', link: function(text) { let id = Number(text); view.select(function(location) { return location.node_id == id; }, true, true); } }; const NODE_STYLE = { css: 'kwd' }; const GOTO_STYLE = { css: 'kwd', goto_id: -2, location: function(text) { return { node_id: GOTO_STYLE.goto_id--, block_id: BLOCK_HEADER_STYLE.block_id }; } } const ARROW_STYLE = { css: 'kwd' }; let patterns = [ [ [/^--- BLOCK B\d+/, BLOCK_HEADER_STYLE, 1], [/^\s+\d+: /, ID_STYLE, 2], [/^\s+Goto/, GOTO_STYLE, 6], [/^.*/, null, -1] ], [ [/^ +/, null], [/^\(deferred\)/, BLOCK_HEADER_STYLE], [/^B\d+/, BLOCK_LINK_STYLE], [/^<-/, ARROW_STYLE], [/^->/, ARROW_STYLE], [/^,/, null], [/^---/, BLOCK_HEADER_STYLE, -1] ], // Parse opcode including [] [ [/^[A-Za-z0-9_]+(\[.*\])?$/, NODE_STYLE, -1], [/^[A-Za-z0-9_]+(\[(\[.*?\]|.)*?\])?/, NODE_STYLE, 3] ], // Parse optional parameters [ [/^ /, null, 4], [/^\(/, null], [/^\d+/, ID_LINK_STYLE], [/^, /, null], [/^\)$/, null, -1], [/^\)/, null, 4], ], [ [/^ -> /, ARROW_STYLE, 5], [/^.*/, null, -1] ], [ [/^B\d+$/, BLOCK_LINK_STYLE, -1], [/^B\d+/, BLOCK_LINK_STYLE], [/^, /, null] ], [ [/^ -> /, ARROW_STYLE], [/^B\d+$/, BLOCK_LINK_STYLE, -1] ] ]; this.setPatterns(patterns); } initializeContent(data, rememberedSelection) { super.initializeContent(data, rememberedSelection); var graph = this; var locations = []; for (var id of rememberedSelection) { locations.push({ node_id : id }); } this.selectLocations(locations, true, true); } detachSelection() { var selection = this.selection.detachSelection(); var s = new Set(); for (var i of selection) { if (i.location.node_id != undefined && i.location.node_id > 0) { s.add(i.location.node_id); } }; return s; } } ================================================ FILE: selection-broker.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var SelectionBroker = function() { this.brokers = []; this.dispatching = false; this.lastDispatchingHandler = null; this.nodePositionMap = []; this.sortedPositionList = []; this.positionNodeMap = []; }; SelectionBroker.prototype.addSelectionHandler = function(handler) { this.brokers.push(handler); } SelectionBroker.prototype.setNodePositionMap = function(map) { let broker = this; if (!map) return; broker.nodePositionMap = map; broker.positionNodeMap = []; broker.sortedPositionList = []; let next = 0; for (let i in broker.nodePositionMap) { broker.sortedPositionList[next] = Number(broker.nodePositionMap[i]); broker.positionNodeMap[next++] = i; } broker.sortedPositionList = sortUnique(broker.sortedPositionList, function(a,b) { return a - b; }); this.positionNodeMap.sort(function(a,b) { let result = broker.nodePositionMap[a] - broker.nodePositionMap[b]; if (result != 0) return result; return a - b; }); } SelectionBroker.prototype.select = function(from, locations, selected) { let broker = this; if (!broker.dispatching) { broker.lastDispatchingHandler = from; try { broker.dispatching = true; let enrichLocations = function(locations) { result = []; for (let location of locations) { let newLocation = {}; if (location.pos_start != undefined) { newLocation.pos_start = location.pos_start; } if (location.pos_end != undefined) { newLocation.pos_end = location.pos_end; } if (location.node_id != undefined) { newLocation.node_id = location.node_id; } if (location.block_id != undefined) { newLocation.block_id = location.block_id; } if (newLocation.pos_start == undefined && newLocation.pos_end == undefined && newLocation.node_id != undefined) { if (broker.nodePositionMap && broker.nodePositionMap[location.node_id]) { newLocation.pos_start = broker.nodePositionMap[location.node_id]; newLocation.pos_end = location.pos_start + 1; } } result.push(newLocation); } return result; } locations = enrichLocations(locations); for (var b of this.brokers) { if (b != from) { b.brokeredSelect(locations, selected); } } } finally { broker.dispatching = false; } } } SelectionBroker.prototype.clear = function(from) { this.lastDispatchingHandler = null; if (!this.dispatching) { try { this.dispatching = true; this.brokers.forEach(function(b) { if (b != from) { b.brokeredClear(); } }); } finally { this.dispatching = false; } } } ================================================ FILE: selection.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var Selection = function(handler) { this.handler = handler; this.selectionBase = null; this.lastSelection = null; this.selection = new Set(); } Selection.prototype.isEmpty = function() { return this.selection.size == 0; } Selection.prototype.clear = function() { var handler = this.handler; this.selectionBase = null; this.lastSelection = null; handler.select(this.selection, false); handler.clear(); this.selection = new Set(); } count = 0; Selection.prototype.select = function(s, isSelected) { var handler = this.handler; if (!(Symbol.iterator in Object(s))) { s = [s]; } if (isSelected) { let first = true; for (let i of s) { if (first) { this.selectionBase = i; this.lastSelection = i; first = false; } this.selection.add(i); } handler.select(this.selection, true); } else { let unselectSet = new Set(); for (let i of s) { if (this.selection.has(i)) { unselectSet.add(i); this.selection.delete(i); } } handler.select(unselectSet, false); } } Selection.prototype.extendTo = function(pos) { if (pos == this.lastSelection || this.lastSelection === null) return; var handler = this.handler; var pos_diff = handler.selectionDifference(pos, true, this.lastSelection, false); var unselect_diff = []; if (pos_diff.length == 0) { pos_diff = handler.selectionDifference(this.selectionBase, false, pos, true); if (pos_diff.length != 0) { unselect_diff = handler.selectionDifference(this.lastSelection, true, this.selectionBase, false); this.selection = new Set(); this.selection.add(this.selectionBase); for (var d of pos_diff) { this.selection.add(d); } } else { unselect_diff = handler.selectionDifference(this.lastSelection, true, pos, false); for (var d of unselect_diff) { this.selection.delete(d); } } } else { unselect_diff = handler.selectionDifference(this.selectionBase, false, this.lastSelection, true); if (unselect_diff != 0) { pos_diff = handler.selectionDifference(pos, true, this.selectionBase, false); if (pos_diff.length == 0) { unselect_diff = handler.selectionDifference(pos, false, this.lastSelection, true); } for (var d of unselect_diff) { this.selection.delete(d); } } if (pos_diff.length != 0) { for (var d of pos_diff) { this.selection.add(d); } } } handler.select(unselect_diff, false); handler.select(pos_diff, true); this.lastSelection = pos; } Selection.prototype.detachSelection = function() { var result = new Set(); for (var i of this.selection) { result.add(i); } this.clear(); return result; } ================================================ FILE: text-view.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. "use strict"; class TextView extends View { constructor(id, broker, patterns, allowSpanSelection) { super(id, broker); let view = this; view.hide(); view.textListNode = view.divNode.getElementsByTagName('ul')[0]; view.fillerSvgElement = view.divElement.append("svg").attr('version','1.1').attr("width", "0"); view.patterns = patterns; view.allowSpanSelection = allowSpanSelection; view.nodeToLineMap = []; var selectionHandler = { clear: function() { broker.clear(selectionHandler); }, select: function(items, selected) { for (let i of items) { if (selected) { i.classList.add("selected"); } else { i.classList.remove("selected"); } } broker.clear(selectionHandler); broker.select(selectionHandler, view.getLocations(items), selected); }, selectionDifference: function(span1, inclusive1, span2, inclusive2) { return null; }, brokeredSelect: function(locations, selected) { view.selectLocations(locations, selected, true); }, brokeredClear: function() { view.selection.clear(); } }; view.selection = new Selection(selectionHandler); broker.addSelectionHandler(selectionHandler); } setPatterns(patterns) { let view = this; view.patterns = patterns; } clearText() { let view = this; while (view.textListNode.firstChild) { view.textListNode.removeChild(view.textListNode.firstChild); } } sameLocation(l1, l2) { let view = this; if (l1.block_id != undefined && l2.block_id != undefined && l1.block_id == l2.block_id && l1.node_id === undefined) { return true; } if (l1.address != undefined && l1.address == l2.address) { return true; } let node1 = l1.node_id; let node2 = l2.node_id; if (node1 === undefined || node2 == undefined) { if (l1.pos_start === undefined || l2.pos_start == undefined) { return false; } if (l1.pos_start == -1 || l2.pos_start == -1) { return false; } if (l1.pos_start < l2.pos_start) { return l1.pos_end > l2.pos_start; } { return l1.pos_start < l2.pos_end; } } return l1.node_id == l2.node_id; } selectLocations(locations, selected, makeVisible) { let view = this; let s = new Set(); for (let l of locations) { for (let i = 0; i < view.textListNode.children.length; ++i) { let child = view.textListNode.children[i]; if (child.location != undefined && view.sameLocation(l, child.location)) { s.add(child); } } } view.selectCommon(s, selected, makeVisible); } getLocations(items) { let result = []; let lastObject = null; for (let i of items) { if (i.location) { result.push(i.location); } } return result; } createFragment(text, style) { let view = this; let span = document.createElement("SPAN"); span.onmousedown = function(e) { view.mouseDownSpan(span, e); } if (style != undefined) { span.classList.add(style); } span.innerHTML = text; return span; } appendFragment(li, fragment) { li.appendChild(fragment); } processLine(line) { let view = this; let result = []; let patternSet = 0; while (true) { let beforeLine = line; for (let pattern of view.patterns[patternSet]) { let matches = line.match(pattern[0]); if (matches != null) { if (matches[0] != '') { let style = pattern[1] != null ? pattern[1] : {}; let text = matches[0]; if (text != '') { let fragment = view.createFragment(matches[0], style.css); if (style.link) { fragment.classList.add('linkable-text'); fragment.link = style.link; } result.push(fragment); if (style.location != undefined) { let location = style.location(text); if (location != undefined) { fragment.location = location; } } } line = line.substr(matches[0].length); } let nextPatternSet = patternSet; if (pattern.length > 2) { nextPatternSet = pattern[2]; } if (line == "") { if (nextPatternSet != -1) { throw("illegal parsing state in text-view in patternSet" + patternSet); } return result; } patternSet = nextPatternSet; break; } } if (beforeLine == line) { throw("input not consumed in text-view in patternSet" + patternSet); } } } select(s, selected, makeVisible) { let view = this; view.selection.clear(); view.selectCommon(s, selected, makeVisible); } selectCommon(s, selected, makeVisible) { let view = this; let firstSelect = makeVisible && view.selection.isEmpty(); if ((typeof s) === 'function') { for (let i = 0; i < view.textListNode.children.length; ++i) { let child = view.textListNode.children[i]; if (child.location && s(child.location)) { if (firstSelect) { makeContainerPosVisible(view.parentNode, child.offsetTop); firstSelect = false; } view.selection.select(child, selected); } } } else if (typeof s[Symbol.iterator] === 'function') { if (firstSelect) { for (let i of s) { makeContainerPosVisible(view.parentNode, i.offsetTop); break; } } view.selection.select(s, selected); } else { if (firstSelect) { makeContainerPosVisible(view.parentNode, s.offsetTop); } view.selection.select(s, selected); } } mouseDownLine(li, e) { let view = this; e.stopPropagation(); if (!e.shiftKey) { view.selection.clear(); } if (li.location != undefined) { view.selectLocations([li.location], true, false); } } mouseDownSpan(span, e) { let view = this; if (view.allowSpanSelection) { e.stopPropagation(); if (!e.shiftKey) { view.selection.clear(); } select(li, true); } else if (span.link) { span.link(span.textContent); e.stopPropagation(); } } processText(text) { let view = this; let textLines = text.split(/[\n]/); let lineNo = 0; for (let line of textLines) { let li = document.createElement("LI"); li.onmousedown = function(e) { view.mouseDownLine(li, e); } li.className = "nolinenums"; li.lineNo = lineNo++; let fragments = view.processLine(line); for (let fragment of fragments) { view.appendFragment(li, fragment); } let lineLocation = view.lineLocation(li); if (lineLocation != undefined) { li.location = lineLocation; } view.textListNode.appendChild(li); } } initializeContent(data, rememberedSelection) { let view = this; view.selection.clear(); view.clearText(); view.processText(data); var fillerSize = document.documentElement.clientHeight - view.textListNode.clientHeight; if (fillerSize < 0) { fillerSize = 0; } view.fillerSvgElement.attr("height", fillerSize); } deleteContent() { } isScrollable() { return true; } detachSelection() { return null; } lineLocation(li) { let view = this; for (let i = 0; i < li.children.length; ++i) { let fragment = li.children[i]; if (fragment.location != undefined && !view.allowSpanSelection) { return fragment.location; } } } } ================================================ FILE: turbo-visualizer.css ================================================ .visible-transition { transition-delay: 0s; transition-duration: 1s; transition-property: all; transition-timing-function: ease; } .collapse-pane { background: #A0A0A0; bottom: 0; position: absolute; margin-bottom: 0.5em; margin-right: 0.5em; margin-left: 0.5em; border-radius: 5px; padding: 0.5em; z-index: 5; opacity: 0.7; cursor: pointer; } .search-input { vertical-align: middle; width: 145px; opacity: 1; } .button-input { vertical-align: middle; width: 24px; opacity: 0.4; cursor: pointer; } .button-input-toggled { border-radius: 5px; background-color: #505050; } .button-input:focus { outline: none; } .invisible { display: none; } .selected { background-color: #FFFF33; } .prettyprint ol.linenums > li { list-style-type: decimal; !important } body { margin: 0; padding: 0; height: 100vh; width: 100vw; overflow:hidden; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } p { text-align: center; overflow: overlay; position: relative; } marker { fill: #080808; } g rect { fill: #F0F0F0; stroke: #080808; stroke-width: 2px; } g.dead { opacity: .5; } g.unsorted rect { opacity: 0.5; } div.scrollable { overflow-y: _croll; overflow-x: hidden; } g.turbonode[relToHover="input"] rect { stroke: #67e62c; stroke-width: 16px; } g.turbonode[relToHover="output"] rect { stroke: #d23b14; stroke-width: 16px; } path[relToHover="input"] { stroke: #67e62c; stroke-width: 16px; } path[relToHover="output"] { stroke: #d23b14; stroke-width: 16px; } g.turbonode:hover rect { stroke: #000000; stroke-width: 7px; } g.control rect { fill: #EFCC00; stroke: #080808; stroke-width: 5px; } g.javascript rect { fill: #DD7E6B; } g.simplified rect { fill: #3C78D8; } g.machine rect { fill: #6AA84F; } g.input rect { fill: #CFE2F3; } g.selected rect { fill: #FFFF33; } circle.bubbleStyle { fill: #080808; fill-opacity: 0.0; stroke: #080808; stroke-width: 2px; } circle.bubbleStyle:hover { stroke-width: 3px; } circle.filledBubbleStyle { fill: #080808; stroke: #080808; stroke-width: 2px; } circle.filledBubbleStyle:hover { fill: #080808; stroke-width: 3px; } circle.halfFilledBubbleStyle { fill: #808080; stroke: #101010; stroke-width: 2px; } circle.halfFilledBubbleStyle:hover { fill: #808080; stroke-width: 3px; } path { fill: none; stroke: #080808; stroke-width: 4px; cursor: default; } path:hover { stroke-width: 6px; } path.hidden { fill: none; stroke-width: 0; } path.link.selected { stroke: #FFFF33; } pre.prettyprint { border: none !important; padding: 0px; } li.L1, li.L3, li.L5, li.L7, li.L9 { background: none !important } li.nolinenums { list-style-type:none; } ul.noindent { -webkit-padding-start: 0px; -webkit-margin-before: 0px; -webkit-margin-after: 0px; } input:hover, .collapse-pane:hover input { opacity: 1; cursor: pointer; } span.linkable-text { text-decoration: underline; } span.linkable-text:hover { cursor: pointer; font-weight: bold; } #left { float: left; } #middle { float:left; background-color: #F8F8F8; } #right { float: right; } .viewpane { height: 100vh; background-color: #FFFFFF; } #disassembly-collapse { right: 0; } #source-collapse { left: 0; } #graph-toolbox-anchor { height: 0px; } #graph-toolbox { position: relative; top: 1em; left: 25px; border: 2px solid #eee8d5; border-radius: 5px; padding: 0.7em; z-index: 5; background: rgba(100%, 100%, 100%, 0.7); } #disassembly-toolbox { position: relative; top: 1em; left: 0.7em; border: 2px solid #eee8d5; border-radius: 5px; padding: 0.7em; z-index: 5; } #load-file { position: absolute; top: 0; right: 0; margin-top: 0.5em; margin-right: 0.5em; z-index: 5; opacity: 0.7; } #load-file input { background: #A0A0A0; border-radius: 5px; padding: 0.5em; } #hidden-file-upload { display: none; } .prof { cursor: default; } tspan { font-size: 500%; font-family: sans-serif; } text { dominant-baseline: text-before-edge; } .resizer-left { position:absolute; width: 4px; height:100%; background: #a0a0a0; cursor: pointer; } .resizer-left.snapped { width: 12px; } .resizer-left:hover { background: orange; } .resizer-left.dragged { background: orange; } .resizer-right { position:absolute; width: 4px; height:100%; background: #a0a0a0; cursor: pointer; } .resizer-right.snapped { width: 12px; } .resizer-right:hover { background: orange; } .resizer-right.dragged { background: orange; } ================================================ FILE: turbo-visualizer.js ================================================ // Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. class Snapper { constructor(resizer) { let snapper = this; snapper.resizer = resizer; snapper.sourceExpand = d3.select("#" + SOURCE_EXPAND_ID); snapper.sourceCollapse = d3.select("#" + SOURCE_COLLAPSE_ID); snapper.disassemblyExpand = d3.select("#" + DISASSEMBLY_EXPAND_ID); snapper.disassemblyCollapse = d3.select("#" + DISASSEMBLY_COLLAPSE_ID); d3.select("#source-collapse").on("click", function(){ resizer.snapper.toggleSourceExpanded(); }); d3.select("#disassembly-collapse").on("click", function(){ resizer.snapper.toggleDisassemblyExpanded(); }); } getLastExpandedState(type, default_state) { var state = window.sessionStorage.getItem("expandedState-"+type); if (state === null) return default_state; return state === 'true'; } setLastExpandedState(type, state) { window.sessionStorage.setItem("expandedState-"+type, state); } toggleSourceExpanded() { this.setSourceExpanded(!this.sourceExpand.classed("invisible")); } sourceExpandUpdate(newState) { this.setLastExpandedState("source", newState); this.sourceExpand.classed("invisible", newState); this.sourceCollapse.classed("invisible", !newState); } setSourceExpanded(newState) { if (this.sourceExpand.classed("invisible") === newState) return; this.sourceExpandUpdate(newState); let resizer = this.resizer; if (newState) { resizer.sep_left = resizer.sep_left_snap; resizer.sep_left_snap = 0; } else { resizer.sep_left_snap = resizer.sep_left; resizer.sep_left = 0; } resizer.updatePanes(); } toggleDisassemblyExpanded() { this.setDisassemblyExpanded(!this.disassemblyExpand.classed("invisible")); } disassemblyExpandUpdate(newState) { this.setLastExpandedState("disassembly", newState); this.disassemblyExpand.classed("invisible", newState); this.disassemblyCollapse.classed("invisible", !newState); } setDisassemblyExpanded(newState) { if (this.disassemblyExpand.classed("invisible") === newState) return; this.disassemblyExpandUpdate(newState); let resizer = this.resizer; if (newState) { resizer.sep_right = resizer.sep_right_snap; resizer.sep_right_snap = resizer.client_width; } else { resizer.sep_right_snap = resizer.sep_right; resizer.sep_right = resizer.client_width; } resizer.updatePanes(); } panesUpated() { this.sourceExpandUpdate(this.resizer.sep_left > this.resizer.dead_width); this.disassemblyExpandUpdate(this.resizer.sep_right < (this.resizer.client_width - this.resizer.dead_width)); } } class Resizer { constructor(panes_updated_callback, dead_width) { let resizer = this; resizer.snapper = new Snapper(resizer) resizer.panes_updated_callback = panes_updated_callback; resizer.dead_width = dead_width resizer.client_width = d3.select("body").node().getBoundingClientRect().width; resizer.left = d3.select("#" + SOURCE_PANE_ID); resizer.middle = d3.select("#" + INTERMEDIATE_PANE_ID); resizer.right = d3.select("#" + GENERATED_PANE_ID); resizer.resizer_left = d3.select('.resizer-left'); resizer.resizer_right = d3.select('.resizer-right'); resizer.sep_left = resizer.client_width/3; resizer.sep_right = resizer.client_width/3*2; resizer.sep_left_snap = 0; resizer.sep_right_snap = 0; // Offset to prevent resizers from sliding slightly over one another. resizer.sep_width_offset = 7; let dragResizeLeft = d3.behavior.drag() .on('drag', function() { let x = d3.mouse(this.parentElement)[0]; resizer.sep_left = Math.min(Math.max(0,x), resizer.sep_right-resizer.sep_width_offset); resizer.updatePanes(); }) .on('dragstart', function() { resizer.resizer_left.classed("dragged", true); let x = d3.mouse(this.parentElement)[0]; if (x > dead_width) { resizer.sep_left_snap = resizer.sep_left; } }) .on('dragend', function() { resizer.resizer_left.classed("dragged", false); }); resizer.resizer_left.call(dragResizeLeft); let dragResizeRight = d3.behavior.drag() .on('drag', function() { let x = d3.mouse(this.parentElement)[0]; resizer.sep_right = Math.max(resizer.sep_left+resizer.sep_width_offset, Math.min(x, resizer.client_width)); resizer.updatePanes(); }) .on('dragstart', function() { resizer.resizer_right.classed("dragged", true); let x = d3.mouse(this.parentElement)[0]; if (x < (resizer.client_width-dead_width)) { resizer.sep_right_snap = resizer.sep_right; } }) .on('dragend', function() { resizer.resizer_right.classed("dragged", false); });; resizer.resizer_right.call(dragResizeRight); window.onresize = function(){ resizer.updateWidths(); /*fitPanesToParents();*/ resizer.updatePanes(); }; } updatePanes() { let left_snapped = this.sep_left === 0; let right_snapped = this.sep_right >= this.client_width - 1; this.resizer_left.classed("snapped", left_snapped); this.resizer_right.classed("snapped", right_snapped); this.left.style('width', this.sep_left + 'px'); this.middle.style('width', (this.sep_right-this.sep_left) + 'px'); this.right.style('width', (this.client_width - this.sep_right) + 'px'); this.resizer_left.style('left', this.sep_left + 'px'); this.resizer_right.style('right', (this.client_width - this.sep_right - 1) + 'px'); this.snapper.panesUpated(); this.panes_updated_callback(); } updateWidths() { this.client_width = d3.select("body").node().getBoundingClientRect().width; this.sep_right = Math.min(this.sep_right, this.client_width); this.sep_left = Math.min(Math.max(0, this.sep_left), this.sep_right); } } document.onload = (function(d3){ "use strict"; var jsonObj; var svg = null; var graph = null; var schedule = null; var empty = null; var currentPhaseView = null; var disassemblyView = null; var sourceView = null; var selectionBroker = null; let resizer = new Resizer(panesUpdatedCallback, 100); function renderTurbolizerData(txtRes) { // If the JSON isn't properly terminated, assume compiler crashed and // add best-guess empty termination if (txtRes[txtRes.length-2] == ',') { txtRes += '{"name":"disassembly","type":"disassembly","data":""}]}'; } try{ jsonObj = JSON.parse(txtRes); hideCurrentPhase(); selectionBroker.setNodePositionMap(jsonObj.nodePositions); sourceView.initializeCode(jsonObj.source, jsonObj.sourcePosition); disassemblyView.initializeCode(jsonObj.source); var selectMenu = document.getElementById('display-selector'); var disassemblyPhase = null; selectMenu.innerHTML = ''; for (var i = 0; i < jsonObj.phases.length; ++i) { var optionElement = document.createElement("option"); optionElement.text = jsonObj.phases[i].name; if (optionElement.text == 'disassembly') { disassemblyPhase = jsonObj.phases[i]; } else { selectMenu.add(optionElement, null); } } disassemblyView.initializePerfProfile(jsonObj.eventCounts); disassemblyView.show(disassemblyPhase.data, null); var initialPhaseIndex = +window.sessionStorage.getItem("lastSelectedPhase"); if (!(initialPhaseIndex in jsonObj.phases)) { initialPhaseIndex = 0; } // We wish to show the remembered phase {lastSelectedPhase}, but // this will crash if the first view we switch to is a // ScheduleView. So we first switch to the first phase, which // should never be a ScheduleView. displayPhase(jsonObj.phases[0]); displayPhase(jsonObj.phases[initialPhaseIndex]); selectMenu.selectedIndex = initialPhaseIndex; selectMenu.onchange = function(item) { window.sessionStorage.setItem("lastSelectedPhase", selectMenu.selectedIndex); displayPhase(jsonObj.phases[selectMenu.selectedIndex]); } fitPanesToParents(); d3.select("#search-input").attr("value", window.sessionStorage.getItem("lastSearch") || ""); } catch(err) { window.console.log("caught exception, clearing session storage just in case"); window.sessionStorage.clear(); // just in case window.console.log("showing error"); window.alert("Invalid TurboFan JSON file\n" + "error: " + err.message); return; } } window.renderTurbolizerData = renderTurbolizerData function panesUpdatedCallback() { graph.fitGraphViewToWindow(); } function hideCurrentPhase() { var rememberedSelection = null; if (currentPhaseView != null) { rememberedSelection = currentPhaseView.detachSelection(); currentPhaseView.hide(); currentPhaseView = null; } return rememberedSelection; } function displayPhaseView(view, data) { var rememberedSelection = hideCurrentPhase(); view.show(data, rememberedSelection); d3.select("#middle").classed("scrollable", view.isScrollable()); currentPhaseView = view; } function displayPhase(phase) { if (phase.type == 'graph') { displayPhaseView(graph, phase.data); } else if (phase.type == 'schedule') { displayPhaseView(schedule, phase.data); } else { displayPhaseView(empty, null); } } function fitPanesToParents() { d3.select("#left").classed("scrollable", false) d3.select("#right").classed("scrollable", false); graph.fitGraphViewToWindow(); d3.select("#left").classed("scrollable", true); d3.select("#right").classed("scrollable", true); } selectionBroker = new SelectionBroker(); function initializeHandlers(g) { d3.select("#hidden-file-upload").on("change", function() { if (window.File && window.FileReader && window.FileList) { var uploadFile = this.files[0]; var filereader = new window.FileReader(); var consts = Node.consts; filereader.onload = function(){ var txtRes = filereader.result; renderTurbolizerData(txtRes); }; filereader.readAsText(uploadFile); } else { alert("Can't load graph"); } }); } sourceView = new CodeView(SOURCE_PANE_ID, PR, "", 0, selectionBroker); disassemblyView = new DisassemblyView(DISASSEMBLY_PANE_ID, selectionBroker); graph = new GraphView(d3, GRAPH_PANE_ID, [], [], selectionBroker); schedule = new ScheduleView(SCHEDULE_PANE_ID, selectionBroker); empty = new EmptyView(EMPTY_PANE_ID, selectionBroker); initializeHandlers(graph); resizer.snapper.setSourceExpanded(resizer.snapper.getLastExpandedState("source", true)); resizer.snapper.setDisassemblyExpanded(resizer.snapper.getLastExpandedState("disassembly", false)); displayPhaseView(empty, null); fitPanesToParents(); resizer.updatePanes(); })(window.d3); ================================================ FILE: util.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. "use strict"; function makeContainerPosVisible(container, pos) { var height = container.offsetHeight; var margin = Math.floor(height / 4); if (pos < container.scrollTop + margin) { pos -= margin; if (pos < 0) pos = 0; container.scrollTop = pos; return; } if (pos > (container.scrollTop + 3 * margin)) { pos = pos - 3 * margin; if (pos < 0) pos = 0; container.scrollTop = pos; } } function lowerBound(a, value, compare, lookup) { let first = 0; let count = a.length; while (count > 0) { let step = Math.floor(count / 2); let middle = first + step; let middle_value = (lookup === undefined) ? a[middle] : lookup(a, middle); let result = (compare === undefined) ? (middle_value < value) : compare(middle_value, value); if (result) { first = middle + 1; count -= step + 1; } else { count = step; } } return first; } function upperBound(a, value, compare, lookup) { let first = 0; let count = a.length; while (count > 0) { let step = Math.floor(count / 2); let middle = first + step; let middle_value = (lookup === undefined) ? a[middle] : lookup(a, middle); let result = (compare === undefined) ? (value < middle_value) : compare(value, middle_value); if (!result) { first = middle + 1; count -= step + 1; } else { count = step; } } return first; } function sortUnique(arr, f) { arr = arr.sort(f); let ret = [arr[0]]; for (var i = 1; i < arr.length; i++) { if (arr[i-1] !== arr[i]) { ret.push(arr[i]); } } return ret; } // Partial application without binding the receiver function partial(f) { var arguments1 = Array.prototype.slice.call(arguments, 1); return function() { var arguments2 = Array.from(arguments); f.apply(this, arguments1.concat(arguments2)); } } ================================================ FILE: view.js ================================================ // Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. "use strict"; class View { constructor(id, broker) { this.divElement = d3.select("#" + id); this.divNode = this.divElement[0][0]; this.parentNode = this.divNode.parentNode; } isScrollable() { return false; } show(data, rememberedSelection) { this.parentNode.appendChild(this.divElement[0][0]); this.initializeContent(data, rememberedSelection); this.divElement.attr(VISIBILITY, 'visible'); } hide() { this.divElement.attr(VISIBILITY, 'hidden'); this.deleteContent(); this.parentNode.removeChild(this.divNode); } detachSelection() { return null; } }