Add a html-based visualizer for TurboFan graphs

Review-Url: https://codereview.chromium.org/729913004
Cr-Commit-Position: refs/heads/master@{#36351}
This commit is contained in:
danno 2016-05-19 01:17:29 -07:00 committed by Commit bot
parent 5a88c04741
commit 9c15c05596
31 changed files with 3322 additions and 0 deletions

1
tools/turbolizer/OWNERS Normal file
View File

@ -0,0 +1 @@
danno@chromium.org

20
tools/turbolizer/README Normal file
View 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.

View 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";
}

View 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';

View 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
View 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);
}

View 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() {
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View 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;
}

View 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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View 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>

View 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']);

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View 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
View 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");
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

View 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;
}
}
}

View 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;
}

View 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;
}
}
}
}

View 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;
}

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

71
tools/turbolizer/util.js Normal file
View 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
View 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;
}
}