blob: 8e411b7635e3afbf72a4fa86cb51e3a67150bbbd [file] [log] [blame]
Ben Murdoch61f157c2016-09-16 13:49:30 +01001// 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}