• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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