1// Copyright 2015 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5var DEFAULT_NODE_ROW_SEPARATION = 130 6 7var traceLayout = false; 8 9function newGraphOccupation(graph){ 10 var isSlotFilled = []; 11 var maxSlot = 0; 12 var minSlot = 0; 13 var nodeOccupation = []; 14 15 function slotToIndex(slot) { 16 if (slot >= 0) { 17 return slot * 2; 18 } else { 19 return slot * 2 + 1; 20 } 21 } 22 23 function indexToSlot(index) { 24 if ((index % 0) == 0) { 25 return index / 2; 26 } else { 27 return -((index - 1) / 2); 28 } 29 } 30 31 function positionToSlot(pos) { 32 return Math.floor(pos / NODE_INPUT_WIDTH); 33 } 34 35 function slotToLeftPosition(slot) { 36 return slot * NODE_INPUT_WIDTH 37 } 38 39 function slotToRightPosition(slot) { 40 return (slot + 1) * NODE_INPUT_WIDTH 41 } 42 43 function findSpace(pos, width, direction) { 44 var widthSlots = Math.floor((width + NODE_INPUT_WIDTH - 1) / 45 NODE_INPUT_WIDTH); 46 var currentSlot = positionToSlot(pos + width / 2); 47 var currentScanSlot = currentSlot; 48 var widthSlotsRemainingLeft = widthSlots; 49 var widthSlotsRemainingRight = widthSlots; 50 var slotsChecked = 0; 51 while (true) { 52 var mod = slotsChecked++ % 2; 53 currentScanSlot = currentSlot + (mod ? -1 : 1) * (slotsChecked >> 1); 54 if (!isSlotFilled[slotToIndex(currentScanSlot)]) { 55 if (mod) { 56 if (direction <= 0) --widthSlotsRemainingLeft 57 } else { 58 if (direction >= 0) --widthSlotsRemainingRight 59 } 60 if (widthSlotsRemainingLeft == 0 || 61 widthSlotsRemainingRight == 0 || 62 (widthSlotsRemainingLeft + widthSlotsRemainingRight) == widthSlots && 63 (widthSlots == slotsChecked)) { 64 if (mod) { 65 return [currentScanSlot, widthSlots]; 66 } else { 67 return [currentScanSlot - widthSlots + 1, widthSlots]; 68 } 69 } 70 } else { 71 if (mod) { 72 widthSlotsRemainingLeft = widthSlots; 73 } else { 74 widthSlotsRemainingRight = widthSlots; 75 } 76 } 77 } 78 } 79 80 function setIndexRange(from, to, value) { 81 if (to < from) { 82 throw("illegal slot range"); 83 } 84 while (from <= to) { 85 if (from > maxSlot) { 86 maxSlot = from; 87 } 88 if (from < minSlot) { 89 minSlot = from; 90 } 91 isSlotFilled[slotToIndex(from++)] = value; 92 } 93 } 94 95 function occupySlotRange(from, to) { 96 if (traceLayout) { 97 console.log("Occupied [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")"); 98 } 99 setIndexRange(from, to, true); 100 } 101 102 function clearSlotRange(from, to) { 103 if (traceLayout) { 104 console.log("Cleared [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")"); 105 } 106 setIndexRange(from, to, false); 107 } 108 109 function occupyPositionRange(from, to) { 110 occupySlotRange(positionToSlot(from), positionToSlot(to - 1)); 111 } 112 113 function clearPositionRange(from, to) { 114 clearSlotRange(positionToSlot(from), positionToSlot(to - 1)); 115 } 116 117 function occupyPositionRangeWithMargin(from, to, margin) { 118 var fromMargin = from - Math.floor(margin); 119 var toMargin = to + Math.floor(margin); 120 occupyPositionRange(fromMargin, toMargin); 121 } 122 123 function clearPositionRangeWithMargin(from, to, margin) { 124 var fromMargin = from - Math.floor(margin); 125 var toMargin = to + Math.floor(margin); 126 clearPositionRange(fromMargin, toMargin); 127 } 128 129 var occupation = { 130 occupyNodeInputs: function(node) { 131 for (var i = 0; i < node.inputs.length; ++i) { 132 if (node.inputs[i].isVisible()) { 133 var edge = node.inputs[i]; 134 if (!edge.isBackEdge()) { 135 var source = edge.source; 136 var horizontalPos = edge.getInputHorizontalPosition(graph); 137 if (traceLayout) { 138 console.log("Occupying input " + i + " of " + node.id + " at " + horizontalPos); 139 } 140 occupyPositionRangeWithMargin(horizontalPos, 141 horizontalPos, 142 NODE_INPUT_WIDTH / 2); 143 } 144 } 145 } 146 }, 147 occupyNode: function(node) { 148 var getPlacementHint = function(n) { 149 var pos = 0; 150 var direction = -1; 151 var outputEdges = 0; 152 var inputEdges = 0; 153 for (var k = 0; k < n.outputs.length; ++k) { 154 var outputEdge = n.outputs[k]; 155 if (outputEdge.isVisible()) { 156 var output = n.outputs[k].target; 157 for (var l = 0; l < output.inputs.length; ++l) { 158 if (output.rank > n.rank) { 159 var inputEdge = output.inputs[l]; 160 if (inputEdge.isVisible()) { 161 ++inputEdges; 162 } 163 if (output.inputs[l].source == n) { 164 pos += output.x + output.getInputX(l) + NODE_INPUT_WIDTH / 2; 165 outputEdges++; 166 if (l >= (output.inputs.length / 2)) { 167 direction = 1; 168 } 169 } 170 } 171 } 172 } 173 } 174 if (outputEdges != 0) { 175 pos = pos / outputEdges; 176 } 177 if (outputEdges > 1 || inputEdges == 1) { 178 direction = 0; 179 } 180 return [direction, pos]; 181 } 182 var width = node.getTotalNodeWidth(); 183 var margin = MINIMUM_EDGE_SEPARATION; 184 var paddedWidth = width + 2 * margin; 185 var placementHint = getPlacementHint(node); 186 var x = placementHint[1] - paddedWidth + margin; 187 if (traceLayout) { 188 console.log("Node " + node.id + " placement hint [" + x + ", " + (x + paddedWidth) + ")"); 189 } 190 var placement = findSpace(x, paddedWidth, placementHint[0]); 191 var firstSlot = placement[0]; 192 var slotWidth = placement[1]; 193 var endSlotExclusive = firstSlot + slotWidth - 1; 194 occupySlotRange(firstSlot, endSlotExclusive); 195 nodeOccupation.push([firstSlot, endSlotExclusive]); 196 if (placementHint[0] < 0) { 197 return slotToLeftPosition(firstSlot + slotWidth) - width - margin; 198 } else if (placementHint[0] > 0) { 199 return slotToLeftPosition(firstSlot) + margin; 200 } else { 201 return slotToLeftPosition(firstSlot + slotWidth / 2) - (width / 2); 202 } 203 }, 204 clearOccupiedNodes: function() { 205 nodeOccupation.forEach(function(o) { 206 clearSlotRange(o[0], o[1]); 207 }); 208 nodeOccupation = []; 209 }, 210 clearNodeOutputs: function(source) { 211 source.outputs.forEach(function(edge) { 212 if (edge.isVisible()) { 213 var target = edge.target; 214 for (var i = 0; i < target.inputs.length; ++i) { 215 if (target.inputs[i].source === source) { 216 var horizontalPos = edge.getInputHorizontalPosition(graph); 217 clearPositionRangeWithMargin(horizontalPos, 218 horizontalPos, 219 NODE_INPUT_WIDTH / 2); 220 } 221 } 222 } 223 }); 224 }, 225 print: function() { 226 var s = ""; 227 for (var currentSlot = -40; currentSlot < 40; ++currentSlot) { 228 if (currentSlot != 0) { 229 s += " "; 230 } else { 231 s += "|"; 232 } 233 } 234 console.log(s); 235 s = ""; 236 for (var currentSlot2 = -40; currentSlot2 < 40; ++currentSlot2) { 237 if (isSlotFilled[slotToIndex(currentSlot2)]) { 238 s += "*"; 239 } else { 240 s += " "; 241 } 242 } 243 console.log(s); 244 } 245 } 246 return occupation; 247} 248 249function layoutNodeGraph(graph) { 250 graph.minGraphX = 0; 251 graph.maxGraphNodeX = 1; 252 graph.maxGraphX = 1; 253 graph.minGraphY = 0; 254 graph.maxGraphY = 1; 255 256 // First determine the set of nodes that have no outputs. Those are the 257 // basis for bottom-up DFS to determine rank and node placement. 258 var endNodesHasNoOutputs = []; 259 var startNodesHasNoInputs = []; 260 graph.nodes.forEach(function(n, i){ 261 endNodesHasNoOutputs[n.id] = true; 262 startNodesHasNoInputs[n.id] = true; 263 }); 264 graph.edges.forEach(function(e, i){ 265 endNodesHasNoOutputs[e.source.id] = false; 266 startNodesHasNoInputs[e.target.id] = false; 267 }); 268 269 // Finialize the list of start and end nodes. 270 var endNodes = []; 271 var startNodes = []; 272 var visited = []; 273 var rank = []; 274 graph.nodes.forEach(function(n, i){ 275 if (endNodesHasNoOutputs[n.id]) { 276 endNodes.push(n); 277 } 278 if (startNodesHasNoInputs[n.id]) { 279 startNodes.push(n); 280 } 281 visited[n.id] = false; 282 rank[n.id] = -1; 283 n.rank = 0; 284 n.visitOrderWithinRank = 0; 285 n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH; 286 }); 287 288 289 var maxRank = 0; 290 var visited = []; 291 var dfsStack = []; 292 var visitOrderWithinRank = 0; 293 294 var worklist = startNodes.slice(); 295 while (worklist.length != 0) { 296 var n = worklist.pop(); 297 var changed = false; 298 if (n.rank == MAX_RANK_SENTINEL) { 299 n.rank = 1; 300 changed = true; 301 } 302 var begin = 0; 303 var end = n.inputs.length; 304 if (n.opcode == 'Phi' || n.opcode == 'EffectPhi') { 305 // Keep with merge or loop node 306 begin = n.inputs.length - 1; 307 } else if (n.hasBackEdges()) { 308 end = 1; 309 } 310 for (var l = begin; l < end; ++l) { 311 var input = n.inputs[l].source; 312 if (input.visible && input.rank >= n.rank) { 313 n.rank = input.rank + 1; 314 changed = true; 315 } 316 } 317 if (changed) { 318 var hasBackEdges = n.hasBackEdges(); 319 for (var l = n.outputs.length - 1; l >= 0; --l) { 320 if (hasBackEdges && (l != 0)) { 321 worklist.unshift(n.outputs[l].target); 322 } else { 323 worklist.push(n.outputs[l].target); 324 } 325 } 326 } 327 if (n.rank > maxRank) { 328 maxRank = n.rank; 329 } 330 } 331 332 visited = []; 333 function dfsFindRankLate(n) { 334 if (visited[n.id]) return; 335 visited[n.id] = true; 336 var originalRank = n.rank; 337 var newRank = n.rank; 338 var firstInput = true; 339 for (var l = 0; l < n.outputs.length; ++l) { 340 var output = n.outputs[l].target; 341 dfsFindRankLate(output); 342 var outputRank = output.rank; 343 if (output.visible && (firstInput || outputRank <= newRank) && 344 (outputRank > originalRank)) { 345 newRank = outputRank - 1; 346 } 347 firstInput = false; 348 } 349 if (n.opcode != "Start" && n.opcode != "Phi" && n.opcode != "EffectPhi") { 350 n.rank = newRank; 351 } 352 } 353 354 startNodes.forEach(dfsFindRankLate); 355 356 visited = []; 357 function dfsRankOrder(n) { 358 if (visited[n.id]) return; 359 visited[n.id] = true; 360 for (var l = 0; l < n.outputs.length; ++l) { 361 var edge = n.outputs[l]; 362 if (edge.isVisible()) { 363 var output = edge.target; 364 dfsRankOrder(output); 365 } 366 } 367 if (n.visitOrderWithinRank == 0) { 368 n.visitOrderWithinRank = ++visitOrderWithinRank; 369 } 370 } 371 startNodes.forEach(dfsRankOrder); 372 373 endNodes.forEach(function(n) { 374 n.rank = maxRank + 1; 375 }); 376 377 var rankSets = []; 378 // Collect sets for each rank. 379 graph.nodes.forEach(function(n, i){ 380 n.y = n.rank * (DEFAULT_NODE_ROW_SEPARATION + graph.getNodeHeight() + 381 2 * DEFAULT_NODE_BUBBLE_RADIUS); 382 if (n.visible) { 383 if (rankSets[n.rank] === undefined) { 384 rankSets[n.rank] = [n]; 385 } else { 386 rankSets[n.rank].push(n); 387 } 388 } 389 }); 390 391 // Iterate backwards from highest to lowest rank, placing nodes so that they 392 // spread out from the "center" as much as possible while still being 393 // compact and not overlapping live input lines. 394 var occupation = newGraphOccupation(graph); 395 var rankCount = 0; 396 397 rankSets.reverse().forEach(function(rankSet) { 398 399 for (var i = 0; i < rankSet.length; ++i) { 400 occupation.clearNodeOutputs(rankSet[i]); 401 } 402 403 if (traceLayout) { 404 console.log("After clearing outputs"); 405 occupation.print(); 406 } 407 408 var placedCount = 0; 409 rankSet = rankSet.sort(function(a,b) { 410 return a.visitOrderWithinRank < b.visitOrderWithinRank; 411 }); 412 for (var i = 0; i < rankSet.length; ++i) { 413 var nodeToPlace = rankSet[i]; 414 if (nodeToPlace.visible) { 415 nodeToPlace.x = occupation.occupyNode(nodeToPlace); 416 if (traceLayout) { 417 console.log("Node " + nodeToPlace.id + " is placed between [" + nodeToPlace.x + ", " + (nodeToPlace.x + nodeToPlace.getTotalNodeWidth()) + ")"); 418 } 419 var staggeredFlooredI = Math.floor(placedCount++ % 3); 420 var delta = MINIMUM_EDGE_SEPARATION * staggeredFlooredI 421 nodeToPlace.outputApproach += delta; 422 } else { 423 nodeToPlace.x = 0; 424 } 425 426 if (nodeToPlace.x < graph.minGraphX) { 427 graph.minGraphX = nodeToPlace.x; 428 } 429 if ((nodeToPlace.y - 50) < graph.minGraphY) { 430 graph.minGraphY = nodeToPlace.y - 50; 431 } 432 if ((nodeToPlace.x + nodeToPlace.getTotalNodeWidth()) > graph.maxGraphNodeX) { 433 graph.maxGraphNodeX = nodeToPlace.x + nodeToPlace.getTotalNodeWidth(); 434 } 435 if ((nodeToPlace.y + graph.getNodeHeight() + 50) > graph.maxGraphY) { 436 graph.maxGraphY = nodeToPlace.y + graph.getNodeHeight() + 50; 437 } 438 } 439 440 if (traceLayout) { 441 console.log("Before clearing nodes"); 442 occupation.print(); 443 } 444 445 occupation.clearOccupiedNodes(); 446 447 if (traceLayout) { 448 console.log("After clearing nodes"); 449 occupation.print(); 450 } 451 452 for (var i = 0; i < rankSet.length; ++i) { 453 var node = rankSet[i]; 454 occupation.occupyNodeInputs(node); 455 } 456 457 if (traceLayout) { 458 console.log("After occupying inputs"); 459 occupation.print(); 460 } 461 }); 462 463 var backEdgeNumber = 0; 464 graph.visibleEdges.each(function (e) { 465 if (e.isBackEdge()) { 466 e.backEdgeNumber = ++backEdgeNumber; 467 } else { 468 e.backEdgeNumber = 0; 469 } 470 }); 471 472 graph.maxGraphX = graph.maxGraphNodeX + 473 backEdgeNumber * MINIMUM_EDGE_SEPARATION; 474} 475