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