mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
493 lines
14 KiB
493 lines
14 KiB
// Copyright 2015 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
var DEFAULT_NODE_ROW_SEPARATION = 130
|
|
|
|
var traceLayout = false;
|
|
|
|
function newGraphOccupation(graph){
|
|
var isSlotFilled = [];
|
|
var maxSlot = 0;
|
|
var minSlot = 0;
|
|
var nodeOccupation = [];
|
|
|
|
function slotToIndex(slot) {
|
|
if (slot >= 0) {
|
|
return slot * 2;
|
|
} else {
|
|
return slot * 2 + 1;
|
|
}
|
|
}
|
|
|
|
function indexToSlot(index) {
|
|
if ((index % 0) == 0) {
|
|
return index / 2;
|
|
} else {
|
|
return -((index - 1) / 2);
|
|
}
|
|
}
|
|
|
|
function positionToSlot(pos) {
|
|
return Math.floor(pos / NODE_INPUT_WIDTH);
|
|
}
|
|
|
|
function slotToLeftPosition(slot) {
|
|
return slot * NODE_INPUT_WIDTH
|
|
}
|
|
|
|
function slotToRightPosition(slot) {
|
|
return (slot + 1) * NODE_INPUT_WIDTH
|
|
}
|
|
|
|
function findSpace(pos, width, direction) {
|
|
var widthSlots = Math.floor((width + NODE_INPUT_WIDTH - 1) /
|
|
NODE_INPUT_WIDTH);
|
|
var currentSlot = positionToSlot(pos + width / 2);
|
|
var currentScanSlot = currentSlot;
|
|
var widthSlotsRemainingLeft = widthSlots;
|
|
var widthSlotsRemainingRight = widthSlots;
|
|
var slotsChecked = 0;
|
|
while (true) {
|
|
var mod = slotsChecked++ % 2;
|
|
currentScanSlot = currentSlot + (mod ? -1 : 1) * (slotsChecked >> 1);
|
|
if (!isSlotFilled[slotToIndex(currentScanSlot)]) {
|
|
if (mod) {
|
|
if (direction <= 0) --widthSlotsRemainingLeft
|
|
} else {
|
|
if (direction >= 0) --widthSlotsRemainingRight
|
|
}
|
|
if (widthSlotsRemainingLeft == 0 ||
|
|
widthSlotsRemainingRight == 0 ||
|
|
(widthSlotsRemainingLeft + widthSlotsRemainingRight) == widthSlots &&
|
|
(widthSlots == slotsChecked)) {
|
|
if (mod) {
|
|
return [currentScanSlot, widthSlots];
|
|
} else {
|
|
return [currentScanSlot - widthSlots + 1, widthSlots];
|
|
}
|
|
}
|
|
} else {
|
|
if (mod) {
|
|
widthSlotsRemainingLeft = widthSlots;
|
|
} else {
|
|
widthSlotsRemainingRight = widthSlots;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function setIndexRange(from, to, value) {
|
|
if (to < from) {
|
|
throw("illegal slot range");
|
|
}
|
|
while (from <= to) {
|
|
if (from > maxSlot) {
|
|
maxSlot = from;
|
|
}
|
|
if (from < minSlot) {
|
|
minSlot = from;
|
|
}
|
|
isSlotFilled[slotToIndex(from++)] = value;
|
|
}
|
|
}
|
|
|
|
function occupySlotRange(from, to) {
|
|
if (traceLayout) {
|
|
console.log("Occupied [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")");
|
|
}
|
|
setIndexRange(from, to, true);
|
|
}
|
|
|
|
function clearSlotRange(from, to) {
|
|
if (traceLayout) {
|
|
console.log("Cleared [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")");
|
|
}
|
|
setIndexRange(from, to, false);
|
|
}
|
|
|
|
function occupyPositionRange(from, to) {
|
|
occupySlotRange(positionToSlot(from), positionToSlot(to - 1));
|
|
}
|
|
|
|
function clearPositionRange(from, to) {
|
|
clearSlotRange(positionToSlot(from), positionToSlot(to - 1));
|
|
}
|
|
|
|
function occupyPositionRangeWithMargin(from, to, margin) {
|
|
var fromMargin = from - Math.floor(margin);
|
|
var toMargin = to + Math.floor(margin);
|
|
occupyPositionRange(fromMargin, toMargin);
|
|
}
|
|
|
|
function clearPositionRangeWithMargin(from, to, margin) {
|
|
var fromMargin = from - Math.floor(margin);
|
|
var toMargin = to + Math.floor(margin);
|
|
clearPositionRange(fromMargin, toMargin);
|
|
}
|
|
|
|
var occupation = {
|
|
occupyNodeInputs: function(node) {
|
|
for (var i = 0; i < node.inputs.length; ++i) {
|
|
if (node.inputs[i].isVisible()) {
|
|
var edge = node.inputs[i];
|
|
if (!edge.isBackEdge()) {
|
|
var source = edge.source;
|
|
var horizontalPos = edge.getInputHorizontalPosition(graph);
|
|
if (traceLayout) {
|
|
console.log("Occupying input " + i + " of " + node.id + " at " + horizontalPos);
|
|
}
|
|
occupyPositionRangeWithMargin(horizontalPos,
|
|
horizontalPos,
|
|
NODE_INPUT_WIDTH / 2);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
occupyNode: function(node) {
|
|
var getPlacementHint = function(n) {
|
|
var pos = 0;
|
|
var direction = -1;
|
|
var outputEdges = 0;
|
|
var inputEdges = 0;
|
|
for (var k = 0; k < n.outputs.length; ++k) {
|
|
var outputEdge = n.outputs[k];
|
|
if (outputEdge.isVisible()) {
|
|
var output = n.outputs[k].target;
|
|
for (var l = 0; l < output.inputs.length; ++l) {
|
|
if (output.rank > n.rank) {
|
|
var inputEdge = output.inputs[l];
|
|
if (inputEdge.isVisible()) {
|
|
++inputEdges;
|
|
}
|
|
if (output.inputs[l].source == n) {
|
|
pos += output.x + output.getInputX(l) + NODE_INPUT_WIDTH / 2;
|
|
outputEdges++;
|
|
if (l >= (output.inputs.length / 2)) {
|
|
direction = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (outputEdges != 0) {
|
|
pos = pos / outputEdges;
|
|
}
|
|
if (outputEdges > 1 || inputEdges == 1) {
|
|
direction = 0;
|
|
}
|
|
return [direction, pos];
|
|
}
|
|
var width = node.getTotalNodeWidth();
|
|
var margin = MINIMUM_EDGE_SEPARATION;
|
|
var paddedWidth = width + 2 * margin;
|
|
var placementHint = getPlacementHint(node);
|
|
var x = placementHint[1] - paddedWidth + margin;
|
|
if (traceLayout) {
|
|
console.log("Node " + node.id + " placement hint [" + x + ", " + (x + paddedWidth) + ")");
|
|
}
|
|
var placement = findSpace(x, paddedWidth, placementHint[0]);
|
|
var firstSlot = placement[0];
|
|
var slotWidth = placement[1];
|
|
var endSlotExclusive = firstSlot + slotWidth - 1;
|
|
occupySlotRange(firstSlot, endSlotExclusive);
|
|
nodeOccupation.push([firstSlot, endSlotExclusive]);
|
|
if (placementHint[0] < 0) {
|
|
return slotToLeftPosition(firstSlot + slotWidth) - width - margin;
|
|
} else if (placementHint[0] > 0) {
|
|
return slotToLeftPosition(firstSlot) + margin;
|
|
} else {
|
|
return slotToLeftPosition(firstSlot + slotWidth / 2) - (width / 2);
|
|
}
|
|
},
|
|
clearOccupiedNodes: function() {
|
|
nodeOccupation.forEach(function(o) {
|
|
clearSlotRange(o[0], o[1]);
|
|
});
|
|
nodeOccupation = [];
|
|
},
|
|
clearNodeOutputs: function(source) {
|
|
source.outputs.forEach(function(edge) {
|
|
if (edge.isVisible()) {
|
|
var target = edge.target;
|
|
for (var i = 0; i < target.inputs.length; ++i) {
|
|
if (target.inputs[i].source === source) {
|
|
var horizontalPos = edge.getInputHorizontalPosition(graph);
|
|
clearPositionRangeWithMargin(horizontalPos,
|
|
horizontalPos,
|
|
NODE_INPUT_WIDTH / 2);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
print: function() {
|
|
var s = "";
|
|
for (var currentSlot = -40; currentSlot < 40; ++currentSlot) {
|
|
if (currentSlot != 0) {
|
|
s += " ";
|
|
} else {
|
|
s += "|";
|
|
}
|
|
}
|
|
console.log(s);
|
|
s = "";
|
|
for (var currentSlot2 = -40; currentSlot2 < 40; ++currentSlot2) {
|
|
if (isSlotFilled[slotToIndex(currentSlot2)]) {
|
|
s += "*";
|
|
} else {
|
|
s += " ";
|
|
}
|
|
}
|
|
console.log(s);
|
|
}
|
|
}
|
|
return occupation;
|
|
}
|
|
|
|
function layoutNodeGraph(graph) {
|
|
// First determine the set of nodes that have no outputs. Those are the
|
|
// basis for bottom-up DFS to determine rank and node placement.
|
|
var endNodesHasNoOutputs = [];
|
|
var startNodesHasNoInputs = [];
|
|
graph.nodes.forEach(function(n, i){
|
|
endNodesHasNoOutputs[n.id] = true;
|
|
startNodesHasNoInputs[n.id] = true;
|
|
});
|
|
graph.edges.forEach(function(e, i){
|
|
endNodesHasNoOutputs[e.source.id] = false;
|
|
startNodesHasNoInputs[e.target.id] = false;
|
|
});
|
|
|
|
// Finialize the list of start and end nodes.
|
|
var endNodes = [];
|
|
var startNodes = [];
|
|
var visited = [];
|
|
var rank = [];
|
|
graph.nodes.forEach(function(n, i){
|
|
if (endNodesHasNoOutputs[n.id]) {
|
|
endNodes.push(n);
|
|
}
|
|
if (startNodesHasNoInputs[n.id]) {
|
|
startNodes.push(n);
|
|
}
|
|
visited[n.id] = false;
|
|
rank[n.id] = -1;
|
|
n.rank = 0;
|
|
n.visitOrderWithinRank = 0;
|
|
n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
|
|
});
|
|
|
|
|
|
var maxRank = 0;
|
|
var visited = [];
|
|
var dfsStack = [];
|
|
var visitOrderWithinRank = 0;
|
|
|
|
var worklist = startNodes.slice();
|
|
while (worklist.length != 0) {
|
|
var n = worklist.pop();
|
|
var changed = false;
|
|
if (n.rank == MAX_RANK_SENTINEL) {
|
|
n.rank = 1;
|
|
changed = true;
|
|
}
|
|
var begin = 0;
|
|
var end = n.inputs.length;
|
|
if (n.opcode == 'Phi' || n.opcode == 'EffectPhi') {
|
|
// Keep with merge or loop node
|
|
begin = n.inputs.length - 1;
|
|
} else if (n.hasBackEdges()) {
|
|
end = 1;
|
|
}
|
|
for (var l = begin; l < end; ++l) {
|
|
var input = n.inputs[l].source;
|
|
if (input.visible && input.rank >= n.rank) {
|
|
n.rank = input.rank + 1;
|
|
changed = true;
|
|
}
|
|
}
|
|
if (changed) {
|
|
var hasBackEdges = n.hasBackEdges();
|
|
for (var l = n.outputs.length - 1; l >= 0; --l) {
|
|
if (hasBackEdges && (l != 0)) {
|
|
worklist.unshift(n.outputs[l].target);
|
|
} else {
|
|
worklist.push(n.outputs[l].target);
|
|
}
|
|
}
|
|
}
|
|
if (n.rank > maxRank) {
|
|
maxRank = n.rank;
|
|
}
|
|
}
|
|
|
|
visited = [];
|
|
function dfsFindRankLate(n) {
|
|
if (visited[n.id]) return;
|
|
visited[n.id] = true;
|
|
var originalRank = n.rank;
|
|
var newRank = n.rank;
|
|
var firstInput = true;
|
|
for (var l = 0; l < n.outputs.length; ++l) {
|
|
var output = n.outputs[l].target;
|
|
dfsFindRankLate(output);
|
|
var outputRank = output.rank;
|
|
if (output.visible && (firstInput || outputRank <= newRank) &&
|
|
(outputRank > originalRank)) {
|
|
newRank = outputRank - 1;
|
|
}
|
|
firstInput = false;
|
|
}
|
|
if (n.opcode != "Start" && n.opcode != "Phi" && n.opcode != "EffectPhi") {
|
|
n.rank = newRank;
|
|
}
|
|
}
|
|
|
|
startNodes.forEach(dfsFindRankLate);
|
|
|
|
visited = [];
|
|
function dfsRankOrder(n) {
|
|
if (visited[n.id]) return;
|
|
visited[n.id] = true;
|
|
for (var l = 0; l < n.outputs.length; ++l) {
|
|
var edge = n.outputs[l];
|
|
if (edge.isVisible()) {
|
|
var output = edge.target;
|
|
dfsRankOrder(output);
|
|
}
|
|
}
|
|
if (n.visitOrderWithinRank == 0) {
|
|
n.visitOrderWithinRank = ++visitOrderWithinRank;
|
|
}
|
|
}
|
|
startNodes.forEach(dfsRankOrder);
|
|
|
|
endNodes.forEach(function(n) {
|
|
n.rank = maxRank + 1;
|
|
});
|
|
|
|
var rankSets = [];
|
|
// Collect sets for each rank.
|
|
graph.nodes.forEach(function(n, i){
|
|
n.y = n.rank * (DEFAULT_NODE_ROW_SEPARATION + graph.getNodeHeight(n) +
|
|
2 * DEFAULT_NODE_BUBBLE_RADIUS);
|
|
if (n.visible) {
|
|
if (rankSets[n.rank] === undefined) {
|
|
rankSets[n.rank] = [n];
|
|
} else {
|
|
rankSets[n.rank].push(n);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Iterate backwards from highest to lowest rank, placing nodes so that they
|
|
// spread out from the "center" as much as possible while still being
|
|
// compact and not overlapping live input lines.
|
|
var occupation = newGraphOccupation(graph);
|
|
var rankCount = 0;
|
|
|
|
rankSets.reverse().forEach(function(rankSet) {
|
|
|
|
for (var i = 0; i < rankSet.length; ++i) {
|
|
occupation.clearNodeOutputs(rankSet[i]);
|
|
}
|
|
|
|
if (traceLayout) {
|
|
console.log("After clearing outputs");
|
|
occupation.print();
|
|
}
|
|
|
|
var placedCount = 0;
|
|
rankSet = rankSet.sort(function(a,b) {
|
|
return a.visitOrderWithinRank < b.visitOrderWithinRank;
|
|
});
|
|
for (var i = 0; i < rankSet.length; ++i) {
|
|
var nodeToPlace = rankSet[i];
|
|
if (nodeToPlace.visible) {
|
|
nodeToPlace.x = occupation.occupyNode(nodeToPlace);
|
|
if (traceLayout) {
|
|
console.log("Node " + nodeToPlace.id + " is placed between [" + nodeToPlace.x + ", " + (nodeToPlace.x + nodeToPlace.getTotalNodeWidth()) + ")");
|
|
}
|
|
var staggeredFlooredI = Math.floor(placedCount++ % 3);
|
|
var delta = MINIMUM_EDGE_SEPARATION * staggeredFlooredI
|
|
nodeToPlace.outputApproach += delta;
|
|
} else {
|
|
nodeToPlace.x = 0;
|
|
}
|
|
}
|
|
|
|
if (traceLayout) {
|
|
console.log("Before clearing nodes");
|
|
occupation.print();
|
|
}
|
|
|
|
occupation.clearOccupiedNodes();
|
|
|
|
if (traceLayout) {
|
|
console.log("After clearing nodes");
|
|
occupation.print();
|
|
}
|
|
|
|
for (var i = 0; i < rankSet.length; ++i) {
|
|
var node = rankSet[i];
|
|
occupation.occupyNodeInputs(node);
|
|
}
|
|
|
|
if (traceLayout) {
|
|
console.log("After occupying inputs");
|
|
occupation.print();
|
|
}
|
|
|
|
if (traceLayout) {
|
|
console.log("After determining bounding box");
|
|
occupation.print();
|
|
}
|
|
});
|
|
|
|
graph.maxBackEdgeNumber = 0;
|
|
graph.visibleEdges.each(function (e) {
|
|
if (e.isBackEdge()) {
|
|
e.backEdgeNumber = ++graph.maxBackEdgeNumber;
|
|
} else {
|
|
e.backEdgeNumber = 0;
|
|
}
|
|
});
|
|
|
|
redetermineGraphBoundingBox(graph);
|
|
|
|
}
|
|
|
|
function redetermineGraphBoundingBox(graph) {
|
|
graph.minGraphX = 0;
|
|
graph.maxGraphNodeX = 1;
|
|
graph.maxGraphX = undefined; // see below
|
|
graph.minGraphY = 0;
|
|
graph.maxGraphY = 1;
|
|
|
|
for (var i = 0; i < graph.nodes.length; ++i) {
|
|
var node = graph.nodes[i];
|
|
|
|
if (!node.visible) {
|
|
continue;
|
|
}
|
|
|
|
if (node.x < graph.minGraphX) {
|
|
graph.minGraphX = node.x;
|
|
}
|
|
if ((node.x + node.getTotalNodeWidth()) > graph.maxGraphNodeX) {
|
|
graph.maxGraphNodeX = node.x + node.getTotalNodeWidth();
|
|
}
|
|
if ((node.y - 50) < graph.minGraphY) {
|
|
graph.minGraphY = node.y - 50;
|
|
}
|
|
if ((node.y + graph.getNodeHeight(node) + 50) > graph.maxGraphY) {
|
|
graph.maxGraphY = node.y + graph.getNodeHeight(node) + 50;
|
|
}
|
|
}
|
|
|
|
graph.maxGraphX = graph.maxGraphNodeX +
|
|
graph.maxBackEdgeNumber * MINIMUM_EDGE_SEPARATION;
|
|
|
|
}
|
|
|