summaryrefslogtreecommitdiff
path: root/deps/v8/tools/turbolizer/src/graph-layout.ts
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/tools/turbolizer/src/graph-layout.ts')
-rw-r--r--deps/v8/tools/turbolizer/src/graph-layout.ts509
1 files changed, 509 insertions, 0 deletions
diff --git a/deps/v8/tools/turbolizer/src/graph-layout.ts b/deps/v8/tools/turbolizer/src/graph-layout.ts
new file mode 100644
index 0000000000..302a98aacc
--- /dev/null
+++ b/deps/v8/tools/turbolizer/src/graph-layout.ts
@@ -0,0 +1,509 @@
+// 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.
+
+
+import {MAX_RANK_SENTINEL} from "./constants.js"
+import {MINIMUM_EDGE_SEPARATION} from "./edge.js"
+import {NODE_INPUT_WIDTH, MINIMUM_NODE_OUTPUT_APPROACH, DEFAULT_NODE_BUBBLE_RADIUS} from "./node.js"
+
+
+const 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;
+}
+
+export 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.selectAll("path").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;
+
+ const width = (graph.maxGraphX - graph.minGraphX);
+ const height = graph.maxGraphY - graph.minGraphY;
+ graph.width = width;
+ graph.height = height;
+
+ const extent = [
+ [graph.minGraphX - width / 2, graph.minGraphY - height / 2],
+ [graph.maxGraphX + width / 2, graph.maxGraphY + height / 2]
+ ];
+ graph.panZoom.translateExtent(extent);
+ graph.minScale();
+}