Index: tools/turbolizer/graph-layout.js |
diff --git a/tools/turbolizer/graph-layout.js b/tools/turbolizer/graph-layout.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8e411b7635e3afbf72a4fa86cb51e3a67150bbbd |
--- /dev/null |
+++ b/tools/turbolizer/graph-layout.js |
@@ -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; |
+} |