Add a html-based visualizer for TurboFan graphs
Review-Url: https://codereview.chromium.org/729913004 Cr-Commit-Position: refs/heads/master@{#36351}
1
tools/turbolizer/OWNERS
Normal file
@ -0,0 +1 @@
|
||||
danno@chromium.org
|
20
tools/turbolizer/README
Normal file
@ -0,0 +1,20 @@
|
||||
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
|
||||
|
||||
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 dervied from the "White Olive Collection" created by Breezi released under
|
||||
the Creative Commons BY license.
|
177
tools/turbolizer/code-view.js
Normal file
@ -0,0 +1,177 @@
|
||||
// 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 CodeView = function(divID, PR, sourceText, sourcePosition, broker) {
|
||||
"use strict";
|
||||
var view = this;
|
||||
|
||||
view.divElement = document.getElementById(divID);
|
||||
view.broker = broker;
|
||||
view.codeSelection = null;
|
||||
view.allSpans = [];
|
||||
|
||||
var selectionHandler = {
|
||||
clear: function() {
|
||||
broker.clear(selectionHandler);
|
||||
},
|
||||
select: function(items, selected) {
|
||||
var handler = this;
|
||||
var divElement = view.divElement;
|
||||
var broker = view.broker;
|
||||
for (let span of items) {
|
||||
if (selected) {
|
||||
span.classList.add("selected");
|
||||
} else {
|
||||
span.classList.remove("selected");
|
||||
}
|
||||
}
|
||||
var ranges = [];
|
||||
for (var span of items) {
|
||||
ranges.push([span.start, span.end, null]);
|
||||
}
|
||||
broker.select(selectionHandler, ranges, selected);
|
||||
},
|
||||
selectionDifference: function(span1, inclusive1, span2, inclusive2) {
|
||||
var pos1 = span1.start;
|
||||
var pos2 = span2.start;
|
||||
var result = [];
|
||||
var lineListDiv = view.divElement.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(ranges, selected) {
|
||||
var firstSelect = view.codeSelection.isEmpty();
|
||||
for (var range of ranges) {
|
||||
var start = range[0];
|
||||
var end = range[1];
|
||||
var lower = 0;
|
||||
var 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.divElement, currentLineElement.offsetTop);
|
||||
firstSelect = false;
|
||||
}
|
||||
view.codeSelection.select(currentSpan, selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
brokeredClear: function() {
|
||||
view.codeSelection.clear();
|
||||
},
|
||||
};
|
||||
|
||||
view.codeSelection = new Selection(selectionHandler);
|
||||
broker.addSelectionHandler(selectionHandler);
|
||||
|
||||
var mouseDown = false;
|
||||
|
||||
this.handleSpanMouseDown = function(e) {
|
||||
e.stopPropagation();
|
||||
if (!e.shiftKey) {
|
||||
view.codeSelection.clear();
|
||||
}
|
||||
view.codeSelection.select(this, true);
|
||||
mouseDown = true;
|
||||
}
|
||||
|
||||
this.handleSpanMouseMove = function(e) {
|
||||
if (mouseDown) {
|
||||
view.codeSelection.extendTo(this);
|
||||
}
|
||||
}
|
||||
|
||||
this.handleCodeMouseDown = function(e) {
|
||||
view.codeSelection.clear();
|
||||
}
|
||||
|
||||
document.addEventListener('mouseup', function(e){
|
||||
mouseDown = false;
|
||||
}, false);
|
||||
|
||||
this.initializeCode(sourceText, sourcePosition);
|
||||
}
|
||||
|
||||
CodeView.prototype.initializeCode = function(sourceText, sourcePosition) {
|
||||
var view = this;
|
||||
if (sourceText == "") {
|
||||
var newHtml = "<pre class=\"prettyprint\"</pre>";
|
||||
view.divElement.innerHTML = newHtml;
|
||||
} else {
|
||||
var newHtml = "<pre class=\"prettyprint linenums\">"
|
||||
+ sourceText + "</pre>";
|
||||
view.divElement.innerHTML = newHtml;
|
||||
try {
|
||||
// Wrap in try to work when offline.
|
||||
PR.prettyPrint();
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
view.divElement.onmousedown = this.handleCodeMouseDown;
|
||||
|
||||
var base = sourcePosition;
|
||||
var current = 0;
|
||||
var lineListDiv = view.divElement.firstChild.firstChild.childNodes;
|
||||
for (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 (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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.resizeToParent();
|
||||
}
|
||||
|
||||
CodeView.prototype.resizeToParent = function() {
|
||||
var view = this;
|
||||
var documentElement = document.documentElement;
|
||||
var y = view.divElement.parentNode.clientHeight || documentElement.clientHeight;
|
||||
view.divElement.style.height = y + "px";
|
||||
}
|
22
tools/turbolizer/constants.js
Normal file
@ -0,0 +1,22 @@
|
||||
// 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';
|
106
tools/turbolizer/disassembly-view.js
Normal file
@ -0,0 +1,106 @@
|
||||
// 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, sortedPositionList) {
|
||||
super(id, broker, null, false);
|
||||
this.pos_start = -1;
|
||||
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) {
|
||||
return {
|
||||
address: ADDRESS_STYLE.last_address
|
||||
};
|
||||
}
|
||||
};
|
||||
let patterns = [
|
||||
[
|
||||
[/^0x[0-9a-f]{8,16}/, ADDRESS_STYLE, 1],
|
||||
[/^.*/, UNCLASSIFIED_STYLE, -1]
|
||||
],
|
||||
[
|
||||
[/^\s+\d+\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.address != undefined) {
|
||||
if (result === undefined) result = {};
|
||||
result.address = location.address;
|
||||
}
|
||||
if (view.pos_start != -1) {
|
||||
if (result === undefined) result = {};
|
||||
result.pos_start = view.pos_start;
|
||||
result.pos_end = result.pos_start + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
77
tools/turbolizer/edge.js
Normal file
@ -0,0 +1,77 @@
|
||||
// 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 output_x = source.x + source.getOutputX();
|
||||
var output_y = source.y + DEFAULT_NODE_HEIGHT + 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 + "," + (target.y - DEFAULT_NODE_BUBBLE_RADIUS - 12);
|
||||
return result;
|
||||
}
|
||||
|
||||
Edge.prototype.isBackEdge = function() {
|
||||
return this.target.hasBackEdges() && (this.target.rank < this.source.rank);
|
||||
}
|
19
tools/turbolizer/empty-view.js
Normal file
@ -0,0 +1,19 @@
|
||||
// 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() {
|
||||
}
|
||||
}
|
BIN
tools/turbolizer/expand-all.jpg
Normal file
After Width: | Height: | Size: 2.8 KiB |
474
tools/turbolizer/graph-layout.js
Normal file
@ -0,0 +1,474 @@
|
||||
// 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) {
|
||||
graph.minGraphX = 0;
|
||||
graph.maxGraphNodeX = 1;
|
||||
graph.maxGraphX = 1;
|
||||
graph.minGraphY = 0;
|
||||
graph.maxGraphY = 1;
|
||||
|
||||
// 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() +
|
||||
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 (nodeToPlace.x < graph.minGraphX) {
|
||||
graph.minGraphX = nodeToPlace.x;
|
||||
}
|
||||
if ((nodeToPlace.y - 50) < graph.minGraphY) {
|
||||
graph.minGraphY = nodeToPlace.y - 50;
|
||||
}
|
||||
if ((nodeToPlace.x + nodeToPlace.getTotalNodeWidth()) > graph.maxGraphNodeX) {
|
||||
graph.maxGraphNodeX = nodeToPlace.x + nodeToPlace.getTotalNodeWidth();
|
||||
}
|
||||
if ((nodeToPlace.y + graph.getNodeHeight() + 50) > graph.maxGraphY) {
|
||||
graph.maxGraphY = nodeToPlace.y + graph.getNodeHeight() + 50;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
var backEdgeNumber = 0;
|
||||
graph.visibleEdges.each(function (e) {
|
||||
if (e.isBackEdge()) {
|
||||
e.backEdgeNumber = ++backEdgeNumber;
|
||||
} else {
|
||||
e.backEdgeNumber = 0;
|
||||
}
|
||||
});
|
||||
|
||||
graph.maxGraphX = graph.maxGraphNodeX +
|
||||
backEdgeNumber * MINIMUM_EDGE_SEPARATION;
|
||||
}
|
860
tools/turbolizer/graph-view.js
Normal file
@ -0,0 +1,860 @@
|
||||
// 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 ranges = [];
|
||||
for (var d of items) {
|
||||
if (selected) {
|
||||
d.classList.add("selected");
|
||||
} else {
|
||||
d.classList.remove("selected");
|
||||
}
|
||||
var data = d.__data__;
|
||||
ranges.push([data.pos, data.pos + 1, data.id]);
|
||||
}
|
||||
broker.select(selectionHandler, ranges, selected);
|
||||
},
|
||||
selectionDifference: function(span1, inclusive1, span2, inclusive2) {
|
||||
// Should not be called
|
||||
},
|
||||
brokeredSelect: function(ranges, selected) {
|
||||
var test = [].entries().next();
|
||||
var selection = graph.nodes
|
||||
.filter(function(n) {
|
||||
var pos = n.pos;
|
||||
for (var range of ranges) {
|
||||
var start = range[0];
|
||||
var end = range[1];
|
||||
var id = range[2];
|
||||
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", function(){
|
||||
document.getElementById("hidden-file-upload").click();
|
||||
});
|
||||
|
||||
d3.select("#layout").on("click", function(){
|
||||
graph.updateGraphVisibility();
|
||||
graph.layoutGraph();
|
||||
graph.updateGraphVisibility();
|
||||
graph.viewWholeGraph();
|
||||
});
|
||||
|
||||
d3.select("#show-all").on("click", function(){
|
||||
graph.nodes.filter(function(n) { n.visible = true; })
|
||||
graph.edges.filter(function(e) { e.visible = true; })
|
||||
graph.updateGraphVisibility();
|
||||
graph.viewWholeGraph();
|
||||
});
|
||||
|
||||
d3.select("#hide-unselected").on("click", function() {
|
||||
var unselected = graph.visibleNodes.filter(function(n) {
|
||||
return !this.classList.contains("selected");
|
||||
});
|
||||
unselected.each(function(n) {
|
||||
n.visible = false;
|
||||
});
|
||||
graph.updateGraphVisibility();
|
||||
});
|
||||
|
||||
d3.select("#hide-selected").on("click", function() {
|
||||
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();
|
||||
});
|
||||
|
||||
d3.select("#zoom-selection").on("click", function() {
|
||||
graph.viewSelection();
|
||||
});
|
||||
|
||||
d3.select("#toggle-types").on("click", function() {
|
||||
graph.toggleTypes();
|
||||
});
|
||||
|
||||
d3.select("#search-input").on("keydown", function() {
|
||||
if (d3.event.keyCode == 13) {
|
||||
graph.state.selection.clear();
|
||||
var reg = new RegExp(this.value);
|
||||
var selected = graph.visibleNodes.each(function(n) {
|
||||
if (reg.exec(n.getDisplayLabel()) != null ||
|
||||
(graph.state.showTypes && reg.exec(n.getDisplayType())) ||
|
||||
reg.exec(n.opcode) != null) {
|
||||
graph.state.selection.select(this, true);
|
||||
}
|
||||
});
|
||||
this.blur();
|
||||
graph.viewSelection();
|
||||
}
|
||||
});
|
||||
|
||||
// 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(graph) {
|
||||
if (this.state.showTypes) {
|
||||
return DEFAULT_NODE_HEIGHT + TYPE_HEIGHT;
|
||||
} else {
|
||||
return DEFAULT_NODE_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
dragmove(d) {
|
||||
var graph = this;
|
||||
d.x += d3.event.dx;
|
||||
d.y += d3.event.dy;
|
||||
graph.updateGraphVisibility();
|
||||
}
|
||||
|
||||
initializeContent(data, rememberedSelection) {
|
||||
this.createGraph(data);
|
||||
if (rememberedSelection != null) {
|
||||
this.attachSelection(rememberedSelection);
|
||||
}
|
||||
this.updateGraphVisibility();
|
||||
}
|
||||
|
||||
deleteContent() {
|
||||
if (this.visibleNodes) {
|
||||
this.nodes = [];
|
||||
this.edges = [];
|
||||
this.nodeMap = [];
|
||||
this.updateGraphVisibility();
|
||||
}
|
||||
};
|
||||
|
||||
createGraph(data) {
|
||||
var g = this;
|
||||
g.nodes = data.nodes;
|
||||
g.nodeMap = [];
|
||||
var textMeasure = document.getElementById('text-measure');
|
||||
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();
|
||||
textMeasure.textContent = n.getDisplayLabel();
|
||||
var width = textMeasure.getComputedTextLength();
|
||||
textMeasure.textContent = n.getDisplayType();
|
||||
width = Math.max(width, textMeasure.getComputedTextLength());
|
||||
n.width = Math.alignUp(width + NODE_INPUT_WIDTH * 2,
|
||||
NODE_INPUT_WIDTH);
|
||||
});
|
||||
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);
|
||||
});
|
||||
g.fitGraphViewToWindow();
|
||||
g.updateGraphVisibility();
|
||||
g.layoutGraph();
|
||||
g.updateGraphVisibility();
|
||||
g.viewWholeGraph();
|
||||
}
|
||||
|
||||
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() + DEFAULT_NODE_BUBBLE_RADIUS / 2 + 4;
|
||||
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 result = new Set();
|
||||
for (var i of selection) {
|
||||
result.add(i.__data__.id);
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
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 (mouseDownNode !== d){
|
||||
// we're in a different node: create new edge for mousedown edge and add to graph
|
||||
var newEdge = {source: mouseDownNode, target: d};
|
||||
var filtRes = graph.visibleEdges.filter(function(d){
|
||||
if (d.source === newEdge.target && d.target === newEdge.source){
|
||||
graph.edges.splice(graph.edges.indexOf(d), 1);
|
||||
}
|
||||
return d.source === newEdge.source && d.target === newEdge.target;
|
||||
});
|
||||
if (!filtRes[0].length){
|
||||
graph.edges.push(newEdge);
|
||||
graph.updateGraphVisibility();
|
||||
}
|
||||
} else{
|
||||
// we're in the same node
|
||||
if (state.justDragged) {
|
||||
// dragged, not clicked
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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 getEdgeFrontier = function(inEdges) {
|
||||
var frontierSet = new Set();
|
||||
state.selection.selection.forEach(function(element) {
|
||||
var nodes = inEdges ? element.__data__.inputs : element.__data__.outputs;
|
||||
nodes.forEach(function(i) {
|
||||
i.visible = true;
|
||||
var candidate = inEdges ? i.source : i.target;
|
||||
candidate.visible = true;
|
||||
frontierSet.add(candidate);
|
||||
});
|
||||
});
|
||||
graph.updateGraphVisibility();
|
||||
return graph.visibleNodes.filter(function(n) {
|
||||
return frontierSet.has(n);
|
||||
});
|
||||
}
|
||||
|
||||
var allowRepetition = true;
|
||||
switch(d3.event.keyCode) {
|
||||
case 38:
|
||||
case 40: {
|
||||
var frontier = getEdgeFrontier(d3.event.keyCode == 38);
|
||||
if (!d3.event.shiftKey) {
|
||||
state.selection.clear();
|
||||
}
|
||||
frontier.each(function(n) {
|
||||
state.selection.select(this, true);
|
||||
});
|
||||
graph.updateGraphVisibility();
|
||||
allowRepetition = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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);
|
||||
})
|
||||
|
||||
// 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(); });
|
||||
|
||||
// add new nodes
|
||||
var newGs = graph.visibleNodes.enter()
|
||||
.append("g");
|
||||
|
||||
newGs.classed("control", function(n) { return n.isControl(); })
|
||||
.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);
|
||||
})
|
||||
.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(); })
|
||||
|
||||
function appendInputAndOutputBubbles(g, d) {
|
||||
for (var i = 0; i < d.inputs.length; ++i) {
|
||||
var x = d.getInputX(i);
|
||||
var y = -DEFAULT_NODE_BUBBLE_RADIUS / 2 - 4;
|
||||
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() + DEFAULT_NODE_BUBBLE_RADIUS / 2 + 4;
|
||||
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", DEFAULT_NODE_HEIGHT / 2 + 5)
|
||||
.append('tspan')
|
||||
.text(function(l) {
|
||||
return d.getDisplayLabel();
|
||||
})
|
||||
.append("title")
|
||||
.text(function(l) {
|
||||
return d.getLabel();
|
||||
})
|
||||
if (d.type != undefined) {
|
||||
d3.select(this).append("text")
|
||||
.classed("label", true)
|
||||
.classed("type", true)
|
||||
.attr("text-anchor","right")
|
||||
.attr("dx", "5")
|
||||
.attr("dy", DEFAULT_NODE_HEIGHT / 2 + TYPE_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 + DEFAULT_NODE_HEIGHT) :
|
||||
n.y + DEFAULT_NODE_HEIGHT;
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
BIN
tools/turbolizer/hide-selected.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
tools/turbolizer/hide-unselected.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
88
tools/turbolizer/index.html
Normal file
@ -0,0 +1,88 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="turbo-visualizer.css" />
|
||||
</head>
|
||||
<body width="100%">
|
||||
<div id="left">
|
||||
<div id='source-text'>
|
||||
<pre id='source-text-pre'\>
|
||||
</div>
|
||||
</div>
|
||||
<div id="middle">
|
||||
<span id="graph-toolbox">
|
||||
<input id="layout" type="image" title="layout graph" src="layout-icon.png"
|
||||
alt="layout graph" class="button-input">
|
||||
<input id="show-all" type="image" title="show all nodes" src="expand-all.jpg"
|
||||
alt="show all nodes" class="button-input">
|
||||
<input id="hide-unselected" type="image" title="hide unselected nodes"
|
||||
src="hide-unselected.png" alt="hide unselected nodes" class="button-input">
|
||||
<input id="hide-selected" type="image" title="hide selected nodes"
|
||||
src="hide-selected.png" alt="hide selected nodes" class="button-input">
|
||||
<input id="zoom-selection" type="image" title="zoom to selection"
|
||||
src="search.png" alt="zoom to selection" class="button-input">
|
||||
<input id="toggle-types" type="image" title="show/hide types"
|
||||
src="types.png" alt="show/hide types" class="button-input">
|
||||
<input id="search-input" type="text" title="search nodes for regex"
|
||||
alt="search node for regex" class="search-input">
|
||||
<select id="display-selector"></select>
|
||||
</span>
|
||||
<div id="load-file">
|
||||
<input type="file" id="hidden-file-upload">
|
||||
<input id="upload" type="image" title="load graph" class="button-input"
|
||||
src="upload-icon.png" alt="upload graph">
|
||||
</div>
|
||||
<div id="empty" width="100%" height="100%"></div>
|
||||
<div id="graph" width="100%" height="100%"></div>
|
||||
<div id="schedule" width="100%">
|
||||
<pre id="schedule-text-pre" class='prettyprint prettyprinted'>
|
||||
<ul id="schedule-list" class='nolinenums noindent'>
|
||||
</ul>
|
||||
</pre>
|
||||
</div>
|
||||
<div id='text-placeholder' width="0px" height="0px" style="position: absolute; top:100000px;" ><svg><text text-anchor="right">
|
||||
<tspan white-space="inherit" id="text-measure"/>
|
||||
</text></svg></div>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id='disassembly'>
|
||||
<pre id='disassembly-text-pre' class='prettyprint prettyprinted'>
|
||||
<ul id='disassembly-list' class='nolinenums noindent'>
|
||||
</ul>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div id="source-collapse" class="collapse-pane">
|
||||
<input id="source-expand" type="image" title="show source"
|
||||
src="right-arrow.png" class="button-input-invisible">
|
||||
<input id="source-shrink" type="image" title="hide source"
|
||||
src="left-arrow.png" class="button-input">
|
||||
</div>
|
||||
<div id="disassembly-collapse" class="collapse-pane">
|
||||
<input id="disassembly-expand" type="image" title="show disassembly"
|
||||
src="left-arrow.png" class="button-input">
|
||||
<input id="disassembly-shrink" type="image" title="hide disassembly"
|
||||
src="right-arrow.png" class="button-input-invisible">
|
||||
</div>
|
||||
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
|
||||
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
|
||||
<script src="https://cdn.jsdelivr.net/filesaver.js/0.1/FileSaver.min.js"></script>
|
||||
<script src="monkey.js"></script>
|
||||
<script src="util.js"></script>
|
||||
<script src="lang-disassembly.js"></script>
|
||||
<script src="node.js"></script>
|
||||
<script src="edge.js"></script>
|
||||
<script src="selection.js"></script>
|
||||
<script src="selection-broker.js"></script>
|
||||
<script src="constants.js"></script>
|
||||
<script src="view.js"></script>
|
||||
<script src="text-view.js"></script>
|
||||
<script src="empty-view.js"></script>
|
||||
<script src="code-view.js"></script>
|
||||
<script src="graph-layout.js"></script>
|
||||
<script src="graph-view.js"></script>
|
||||
<script src="schedule-view.js"></script>
|
||||
<script src="disassembly-view.js"></script>
|
||||
<script src="turbo-visualizer.js"></script>
|
||||
</body>
|
||||
</html>
|
14
tools/turbolizer/lang-disassembly.js
Normal file
@ -0,0 +1,14 @@
|
||||
// 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']);
|
BIN
tools/turbolizer/layout-icon.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
tools/turbolizer/left-arrow.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
26
tools/turbolizer/monkey.js
Normal file
@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
|
||||
Array.prototype.getStaggeredFromMiddle = function(i) {
|
||||
if (i >= this.length) {
|
||||
throw("getStaggeredFromMiddle: OOB");
|
||||
}
|
||||
var middle = Math.floor(this.length / 2);
|
||||
var index = middle + (((i % 2) == 0) ? (i / 2) : (((1 - i) / 2) - 1));
|
||||
return this[index];
|
||||
}
|
||||
|
||||
Array.prototype.contains = function(obj) {
|
||||
var i = this.length;
|
||||
while (i--) {
|
||||
if (this[i] === obj) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Math.alignUp = function(raw, multiple) {
|
||||
return Math.floor((raw + multiple - 1) / multiple) * multiple;
|
||||
}
|
138
tools/turbolizer/node.js
Normal file
@ -0,0 +1,138 @@
|
||||
// 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 DEFAULT_NODE_WIDTH = 240;
|
||||
var DEFAULT_NODE_HEIGHT = 40;
|
||||
var TYPE_HEIGHT = 25;
|
||||
var DEFAULT_NODE_BUBBLE_RADIUS = 4;
|
||||
var NODE_INPUT_WIDTH = 20;
|
||||
var MINIMUM_NODE_INPUT_APPROACH = 20;
|
||||
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');
|
||||
},
|
||||
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);
|
||||
},
|
||||
getLabel: function() {
|
||||
return this.label;
|
||||
},
|
||||
getDisplayLabel: function() {
|
||||
var result = this.id + ":" + this.label;
|
||||
if (result.length > 30) {
|
||||
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() +
|
||||
+ 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");
|
||||
}
|
||||
};
|
BIN
tools/turbolizer/right-arrow.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
108
tools/turbolizer/schedule-view.js
Normal file
@ -0,0 +1,108 @@
|
||||
// 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, nodePositionMap) {
|
||||
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);
|
||||
this.setNodePositionMap(nodePositionMap);
|
||||
}
|
||||
}
|
BIN
tools/turbolizer/search.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
tools/turbolizer/search2.png
Normal file
After Width: | Height: | Size: 689 B |
46
tools/turbolizer/selection-broker.js
Normal file
@ -0,0 +1,46 @@
|
||||
// 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;
|
||||
};
|
||||
|
||||
SelectionBroker.prototype.addSelectionHandler = function(handler) {
|
||||
this.brokers.push(handler);
|
||||
}
|
||||
|
||||
SelectionBroker.prototype.select = function(from, ranges, selected) {
|
||||
if (!this.dispatching) {
|
||||
this.lastDispatchingHandler = from;
|
||||
try {
|
||||
this.dispatching = true;
|
||||
for (var b of this.brokers) {
|
||||
if (b != from) {
|
||||
b.brokeredSelect(ranges, selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.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;
|
||||
}
|
||||
}
|
||||
}
|
98
tools/turbolizer/selection.js
Normal file
@ -0,0 +1,98 @@
|
||||
// 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, selected) {
|
||||
var handler = this.handler;
|
||||
if (this.selection.has(s) && !selected) {
|
||||
handler.select([s], false);
|
||||
this.selection.delete(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
this.selection.add(s);
|
||||
this.selectionBase = s;
|
||||
this.lastSelection = s;
|
||||
handler.select(this.selection, selected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
394
tools/turbolizer/text-view.js
Normal file
@ -0,0 +1,394 @@
|
||||
// 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.sortedPositionList = [];
|
||||
view.nodePositionMap = [];
|
||||
view.positionNodeMap = [];
|
||||
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.select(selectionHandler, view.getRanges(items), selected);
|
||||
},
|
||||
selectionDifference: function(span1, inclusive1, span2, inclusive2) {
|
||||
return null;
|
||||
},
|
||||
brokeredSelect: function(ranges, selected) {
|
||||
let locations = view.rangesToLocations(ranges);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
rangeToLocation(range) {
|
||||
return range;
|
||||
}
|
||||
|
||||
rangesToLocations(ranges) {
|
||||
let view = this;
|
||||
let nodes = new Set();
|
||||
let result = [];
|
||||
for (let range of ranges) {
|
||||
let start = range[0];
|
||||
let end = range[1];
|
||||
let location = { pos_start: start, pos_end: end };
|
||||
if (range[2] !== null && range[2] != -1) {
|
||||
location.node_id = range[2];
|
||||
if (range[0] == -1 && range[1] == -1) {
|
||||
location.pos_start = view.nodePositionMap[location.node_id];
|
||||
location.pos_end = location.pos_start + 1;
|
||||
}
|
||||
} else {
|
||||
if (range[0] != undefined) {
|
||||
location.pos_start = range[0];
|
||||
location.pos_end = range[1];
|
||||
}
|
||||
}
|
||||
result.push(location);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (node1 === undefined) {
|
||||
let lower = lowerBound(view.positionNodeMap, l1.pos_start, undefined, function(a, b) {
|
||||
var node = a[b];
|
||||
return view.nodePositionMap[node];
|
||||
} );
|
||||
while (++lower < view.positionNodeMap.length &&
|
||||
view.nodePositionMap[view.positionNodeMap[lower]] < l1.pos_end) {
|
||||
if (view.positionNodeMap[lower] == node2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node2 === undefined) {
|
||||
let lower = lowerBound(view.positionNodeMap, l2.pos_start, undefined, function(a, b) {
|
||||
var node = a[b];
|
||||
return view.nodePositionMap[node];
|
||||
} );
|
||||
while (++lower < view.positionNodeMap.length &&
|
||||
view.nodePositionMap[view.positionNodeMap[lower]] < l2.pos_end) {
|
||||
if (view.positionNodeMap[lower] == node1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return l1.node_id == l2.node_id;
|
||||
}
|
||||
|
||||
setNodePositionMap(map) {
|
||||
let view = this;
|
||||
view.nodePositionMap = map;
|
||||
view.positionNodeMap = [];
|
||||
view.sortedPositionList = [];
|
||||
let next = 0;
|
||||
for (let i in view.nodePositionMap) {
|
||||
view.sortedPositionList[next] = Number(view.nodePositionMap[i]);
|
||||
view.positionNodeMap[next++] = i;
|
||||
}
|
||||
view.sortedPositionList = sortUnique(view.sortedPositionList,
|
||||
function(a,b) { return a - b; });
|
||||
this.positionNodeMap.sort(function(a,b) {
|
||||
let result = view.nodePositionMap[a] - view.nodePositionMap[b];
|
||||
if (result != 0) return result;
|
||||
return a - b;
|
||||
});
|
||||
}
|
||||
|
||||
selectLocations(locations, selected, makeVisible) {
|
||||
let view = this;
|
||||
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)) {
|
||||
view.selectCommon(child, selected, makeVisible);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getRanges(items) {
|
||||
let result = [];
|
||||
let lastObject = null;
|
||||
for (let i of items) {
|
||||
if (i.location) {
|
||||
let location = i.location;
|
||||
let start = -1;
|
||||
let end = -1;
|
||||
let node_id = -1;
|
||||
if (location.node_id !== undefined) {
|
||||
node_id = location.node_id;
|
||||
}
|
||||
if (location.pos_start !== undefined) {
|
||||
start = location.pos_start;
|
||||
end = location.pos_end;
|
||||
} else {
|
||||
if (this.nodePositionMap && this.nodePositionMap[node_id]) {
|
||||
start = this.nodePositionMap[node_id];
|
||||
end = start + 1;
|
||||
}
|
||||
}
|
||||
if (lastObject == null ||
|
||||
(lastObject[2] != node_id ||
|
||||
lastObject[0] != start ||
|
||||
lastObject[1] != end)) {
|
||||
lastObject = [start, end, node_id];
|
||||
result.push(lastObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
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.innerText = 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 (s.length) {
|
||||
for (let i of s) {
|
||||
if (firstSelect) {
|
||||
makeContainerPosVisible(view.parentNode, i.offsetTop);
|
||||
firstSelect = false;
|
||||
}
|
||||
view.selection.select(i, selected);
|
||||
}
|
||||
} else {
|
||||
if (firstSelect) {
|
||||
makeContainerPosVisible(view.parentNode, s.offsetTop);
|
||||
firstSelect = false;
|
||||
}
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
326
tools/turbolizer/turbo-visualizer.css
Normal file
@ -0,0 +1,326 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.button-input-invisible {
|
||||
vertical-align: middle;
|
||||
width: 0px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
.selected {
|
||||
background-color: #FFFF33;
|
||||
}
|
||||
|
||||
.prettyprint ol.linenums > li {
|
||||
list-style-type: decimal;
|
||||
!important
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
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.unsorted rect {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
div.scrollable {
|
||||
overflow-y: _croll; overflow-x: hidden;
|
||||
}
|
||||
|
||||
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.effect {
|
||||
fill: none;
|
||||
stroke: #080808;
|
||||
stroke-width: 4px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
path.effect:hover {
|
||||
stroke-width: 6px;
|
||||
}
|
||||
|
||||
path.control {
|
||||
fill: none;
|
||||
stroke: #080808;
|
||||
stroke-width: 4px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
path.control:hover {
|
||||
stroke-width: 6px;
|
||||
}
|
||||
|
||||
path.value {
|
||||
fill: none;
|
||||
stroke: #888888;
|
||||
stroke-width: 4px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
path.value:hover {
|
||||
stroke-width: 6px;
|
||||
}
|
||||
|
||||
path.frame-state {
|
||||
fill: none;
|
||||
stroke: #080808;
|
||||
stroke-width: 4px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
path.frame-state: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 {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span.linkable-text {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
span.linkable-text:hover {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#left {
|
||||
float: left; height: 100%; background-color: #FFFFFF;
|
||||
-webkit-transition: all 1s ease-in-out;
|
||||
-moz-transition: all 1s ease-in-out;
|
||||
-o-transition: all 1s ease-in-out;
|
||||
transition: all 1s ease-in-out;
|
||||
transition-property: width;
|
||||
transition-duration: 1s, 1s;
|
||||
}
|
||||
|
||||
#middle {
|
||||
float:left; height: 100%; background-color: #F8F8F8;
|
||||
-webkit-transition: all 1s ease-in-out;
|
||||
-moz-transition: all 1s ease-in-out;
|
||||
-o-transition: all 1s ease-in-out;
|
||||
transition: all 1s ease-in-out;
|
||||
transition-property: width;
|
||||
transition-duration: 1s, 1s;
|
||||
}
|
||||
|
||||
#right {
|
||||
float: right; background-color: #FFFFFF;
|
||||
-webkit-transition: all 1s ease-in-out;
|
||||
-moz-transition: all 1s ease-in-out;
|
||||
-o-transition: all 1s ease-in-out;
|
||||
transition: all 1s ease-in-out;
|
||||
transition-property: width;
|
||||
transition-duration: 1s, 1s;
|
||||
}
|
||||
|
||||
#disassembly-collapse {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#source-collapse {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#graph-toolbox {
|
||||
position: relative;
|
||||
top: 1em;
|
||||
left: 0.7em;
|
||||
border: 2px solid #eee8d5;
|
||||
border-radius: 5px;
|
||||
padding: 0.7em;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#disassembly-collapse {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#source-collapse {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#graph-toolbox {
|
||||
position: relative;
|
||||
top: 1em;
|
||||
left: 0.7em;
|
||||
border: 2px solid #eee8d5;
|
||||
border-radius: 5px;
|
||||
padding: 0.7em;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#load-file {
|
||||
background: #A0A0A0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin-top: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
border-radius: 5px;
|
||||
padding: 0.5em;
|
||||
z-index: 5;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#hidden-file-upload{
|
||||
display: none;
|
||||
}
|
||||
|
215
tools/turbolizer/turbo-visualizer.js
Normal file
@ -0,0 +1,215 @@
|
||||
// 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.
|
||||
|
||||
document.onload = (function(d3){
|
||||
"use strict";
|
||||
var jsonObj;
|
||||
var sourceExpandClassList = document.getElementById(SOURCE_EXPAND_ID).classList;
|
||||
var sourceCollapseClassList = document.getElementById(SOURCE_COLLAPSE_ID).classList;
|
||||
var sourceExpanded = sourceCollapseClassList.contains(COLLAPSE_PANE_BUTTON_VISIBLE);
|
||||
var disassemblyExpandClassList = document.getElementById(DISASSEMBLY_EXPAND_ID).classList;
|
||||
var disassemblyCollapseClassList = document.getElementById(DISASSEMBLY_COLLAPSE_ID).classList;
|
||||
var disassemblyExpanded = disassemblyCollapseClassList.contains(COLLAPSE_PANE_BUTTON_VISIBLE);
|
||||
var svg = null;
|
||||
var graph = null;
|
||||
var schedule = null;
|
||||
var empty = null;
|
||||
var currentPhaseView = null;
|
||||
var disassemblyView = null;
|
||||
var sourceView = null;
|
||||
var selectionBroker = null;
|
||||
|
||||
function updatePanes() {
|
||||
if (sourceExpanded) {
|
||||
if (disassemblyExpanded) {
|
||||
d3.select("#" + SOURCE_PANE_ID).style(WIDTH, "30%");
|
||||
d3.select("#" + INTERMEDIATE_PANE_ID).style(WIDTH, "40%");
|
||||
d3.select("#" + GENERATED_PANE_ID).style(WIDTH, "30%");
|
||||
} else {
|
||||
d3.select("#" + SOURCE_PANE_ID).style(WIDTH, "50%");
|
||||
d3.select("#" + INTERMEDIATE_PANE_ID).style(WIDTH, "50%");
|
||||
d3.select("#" + GENERATED_PANE_ID).style(WIDTH, "0%");
|
||||
}
|
||||
} else {
|
||||
if (disassemblyExpanded) {
|
||||
d3.select("#" + SOURCE_PANE_ID).style(WIDTH, "0%");
|
||||
d3.select("#" + INTERMEDIATE_PANE_ID).style(WIDTH, "50%");
|
||||
d3.select("#" + GENERATED_PANE_ID).style(WIDTH, "50%");
|
||||
} else {
|
||||
d3.select("#" + SOURCE_PANE_ID).style(WIDTH, "0%");
|
||||
d3.select("#" + INTERMEDIATE_PANE_ID).style(WIDTH, "100%");
|
||||
d3.select("#" + GENERATED_PANE_ID).style(WIDTH, "0%");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setSourceExpanded(newState) {
|
||||
sourceExpanded = newState;
|
||||
updatePanes();
|
||||
if (newState) {
|
||||
sourceCollapseClassList.add(COLLAPSE_PANE_BUTTON_VISIBLE);
|
||||
sourceCollapseClassList.remove(COLLAPSE_PANE_BUTTON_INVISIBLE);
|
||||
sourceExpandClassList.add(COLLAPSE_PANE_BUTTON_INVISIBLE);
|
||||
sourceExpandClassList.remove(COLLAPSE_PANE_BUTTON_VISIBLE);
|
||||
} else {
|
||||
sourceCollapseClassList.add(COLLAPSE_PANE_BUTTON_INVISIBLE);
|
||||
sourceCollapseClassList.remove(COLLAPSE_PANE_BUTTON_VISIBLE);
|
||||
sourceExpandClassList.add(COLLAPSE_PANE_BUTTON_VISIBLE);
|
||||
sourceExpandClassList.remove(COLLAPSE_PANE_BUTTON_INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
function setDisassemblyExpanded(newState) {
|
||||
disassemblyExpanded = newState;
|
||||
updatePanes();
|
||||
if (newState) {
|
||||
disassemblyCollapseClassList.add(COLLAPSE_PANE_BUTTON_VISIBLE);
|
||||
disassemblyCollapseClassList.remove(COLLAPSE_PANE_BUTTON_INVISIBLE);
|
||||
disassemblyExpandClassList.add(COLLAPSE_PANE_BUTTON_INVISIBLE);
|
||||
disassemblyExpandClassList.remove(COLLAPSE_PANE_BUTTON_VISIBLE);
|
||||
} else {
|
||||
disassemblyCollapseClassList.add(COLLAPSE_PANE_BUTTON_INVISIBLE);
|
||||
disassemblyCollapseClassList.remove(COLLAPSE_PANE_BUTTON_VISIBLE);
|
||||
disassemblyExpandClassList.add(COLLAPSE_PANE_BUTTON_VISIBLE);
|
||||
disassemblyExpandClassList.remove(COLLAPSE_PANE_BUTTON_INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
disassemblyView.resizeToParent();
|
||||
sourceView.resizeToParent();
|
||||
|
||||
d3.select("#left").classed("scrollable", true);
|
||||
d3.select("#right").classed("scrollable", true);
|
||||
}
|
||||
|
||||
selectionBroker = new SelectionBroker();
|
||||
|
||||
function initializeHandlers(g) {
|
||||
d3.select("#source-expand").on("click", function(){
|
||||
setSourceExpanded(true);
|
||||
setTimeout(function(){
|
||||
g.fitGraphViewToWindow();
|
||||
}, 1000);
|
||||
});
|
||||
d3.select("#source-shrink").on("click", function(){
|
||||
setSourceExpanded(false);
|
||||
setTimeout(function(){
|
||||
g.fitGraphViewToWindow();
|
||||
}, 1000);
|
||||
});
|
||||
d3.select("#disassembly-expand").on("click", function(){
|
||||
setDisassemblyExpanded(true);
|
||||
setTimeout(function(){
|
||||
g.fitGraphViewToWindow();
|
||||
}, 1000);
|
||||
});
|
||||
d3.select("#disassembly-shrink").on("click", function(){
|
||||
setDisassemblyExpanded(false);
|
||||
setTimeout(function(){
|
||||
g.fitGraphViewToWindow();
|
||||
}, 1000);
|
||||
});
|
||||
window.onresize = function(){
|
||||
fitPanesToParents();
|
||||
};
|
||||
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;
|
||||
// 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);
|
||||
|
||||
sourceView.initializeCode(jsonObj.source, jsonObj.sourcePosition);
|
||||
schedule.setNodePositionMap(jsonObj.nodePositions);
|
||||
|
||||
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.setNodePositionMap(jsonObj.nodePositions);
|
||||
disassemblyView.show(disassemblyPhase.data, null);
|
||||
|
||||
displayPhase(jsonObj.phases[0]);
|
||||
|
||||
selectMenu.onchange = function(item) {
|
||||
displayPhase(jsonObj.phases[selectMenu.selectedIndex]);
|
||||
}
|
||||
|
||||
fitPanesToParents();
|
||||
}
|
||||
catch(err) {
|
||||
window.alert("Invalid TurboFan JSON file\n" +
|
||||
"error: " + err.message);
|
||||
return;
|
||||
}
|
||||
};
|
||||
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);
|
||||
|
||||
setSourceExpanded(true);
|
||||
setDisassemblyExpanded(false);
|
||||
|
||||
displayPhaseView(empty, null);
|
||||
fitPanesToParents();
|
||||
})(window.d3);
|
BIN
tools/turbolizer/types.png
Normal file
After Width: | Height: | Size: 753 B |
BIN
tools/turbolizer/upload-icon.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
71
tools/turbolizer/util.js
Normal file
@ -0,0 +1,71 @@
|
||||
// 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;
|
||||
}
|
42
tools/turbolizer/view.js
Normal file
@ -0,0 +1,42 @@
|
||||
// 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;
|
||||
this.hide();
|
||||
}
|
||||
|
||||
isScrollable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
show(data, rememberedSelection) {
|
||||
this.parentNode.appendChild(this.divElement[0][0]);
|
||||
this.initializeContent(data, rememberedSelection);
|
||||
this.resizeToParent();
|
||||
this.divElement.attr(VISIBILITY, 'visible');
|
||||
}
|
||||
|
||||
resizeToParent() {
|
||||
var view = this;
|
||||
var documentElement = document.documentElement;
|
||||
var y = this.parentNode.clientHeight || documentElement.clientHeight;
|
||||
this.parentNode.style.height = y + 'px';
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.divElement.attr(VISIBILITY, 'hidden');
|
||||
this.deleteContent();
|
||||
this.parentNode.removeChild(this.divNode);
|
||||
}
|
||||
|
||||
detachSelection() {
|
||||
return null;
|
||||
}
|
||||
}
|