Commit 3118c60c authored by Danylo Boiko's avatar Danylo Boiko Committed by V8 LUCI CQ

[turbolizer] Turboshaft layout generation

- accelerated nodes selection for the old IR layout;
- implemented turboshaft blocks layout building (blocks coordinates and edges);
- extended interaction with user (selecting/hovering) for such things like: blocks/nodes/edges.

Bug: v8:7327
Change-Id: I0b01679e9dde0bb7d94ba80dd0ee744f334e1968
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3747871Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Commit-Queue: Danylo Boiko <danielboyko02@gmail.com>
Cr-Commit-Position: refs/heads/main@{#81810}
parent bc0ca547
:root {
--input: #50de89;
--output: #ff5b64;
--select: #ffd800;
}
path.input {
stroke: var(--input);
stroke-width: 16px;
}
path.output {
stroke: var(--output);
stroke-width: 16px;
}
g.turboshaft-block rect { g.turboshaft-block rect {
stroke-dasharray: 20; stroke-dasharray: 20;
stroke-width: 7; stroke-width: 7;
...@@ -8,18 +24,24 @@ g.turboshaft-block:hover rect { ...@@ -8,18 +24,24 @@ g.turboshaft-block:hover rect {
stroke-width: 10; stroke-width: 10;
} }
g.turboshaft-block.selected rect {
stroke-dasharray: 0;
stroke-width: 15;
stroke: var(--select);
}
g.block rect { g.block rect {
fill: #ecf3fe; fill: #eef4fd;
stroke: #4285f4; stroke: #4285f4;
} }
g.merge rect { g.merge rect {
fill: #e9fcee; fill: #ecfcf0;
stroke: #2bde5a; stroke: #2bde5a;
} }
g.loop rect { g.loop rect {
fill: #fdecea; fill: #fdf0ee;
stroke: #e94235; stroke: #e94235;
} }
...@@ -39,6 +61,32 @@ g.loop rect { ...@@ -39,6 +61,32 @@ g.loop rect {
fill: #ea4335; fill: #ea4335;
} }
.inline-node-label tspan {
fill: #344344;
}
.inline-node-label:hover tspan {
fill: #5a6c6c;
}
.inline-node-label.selected tspan {
fill: var(--select);
}
.inline-node-properties tspan { .inline-node-properties tspan {
fill: #9227b0; fill: #ca48f6;
}
g.turboshaft-node.input .inline-node-label tspan {
fill: var(--input);
}
g.turboshaft-node.output .inline-node-label tspan {
fill: var(--output);
}
#layout-type-select {
box-sizing: border-box;
height: 1.5em;
margin-left: 5px;
} }
...@@ -5,6 +5,10 @@ ...@@ -5,6 +5,10 @@
export const MAX_RANK_SENTINEL = 0; export const MAX_RANK_SENTINEL = 0;
export const BEZIER_CONSTANT = 0.3; export const BEZIER_CONSTANT = 0.3;
export const GRAPH_MARGIN = 250; export const GRAPH_MARGIN = 250;
export const TURBOSHAFT_NODE_X_INDENT = 25;
export const TURBOSHAFT_BLOCK_BORDER_RADIUS = 35;
export const TURBOSHAFT_BLOCK_ROW_SEPARATION = 200;
export const ARROW_HEAD_HEIGHT = 7;
export const DEFAULT_NODE_BUBBLE_RADIUS = 12; export const DEFAULT_NODE_BUBBLE_RADIUS = 12;
export const NODE_INPUT_WIDTH = 50; export const NODE_INPUT_WIDTH = 50;
export const MINIMUM_NODE_OUTPUT_APPROACH = 15; export const MINIMUM_NODE_OUTPUT_APPROACH = 15;
......
...@@ -27,12 +27,13 @@ export function camelize(obj: any): any { ...@@ -27,12 +27,13 @@ export function camelize(obj: any): any {
return obj; return obj;
} }
export function sortUnique<T>(arr: Array<T>, f: (a: T, b: T) => number, equal: (a: T, b: T) => boolean): Array<T> { export function sortUnique<T>(arr: Array<T>, comparator: (a: T, b: T) => number,
equals: (a: T, b: T) => boolean): Array<T> {
if (arr.length == 0) return arr; if (arr.length == 0) return arr;
arr = arr.sort(f); arr = arr.sort(comparator);
const uniqueArr = [arr[0]]; const uniqueArr = [arr[0]];
for (let i = 1; i < arr.length; i++) { for (let i = 1; i < arr.length; i++) {
if (!equal(arr[i - 1], arr[i])) { if (!equals(arr[i - 1], arr[i])) {
uniqueArr.push(arr[i]); uniqueArr.push(arr[i]);
} }
} }
......
...@@ -2,25 +2,82 @@ ...@@ -2,25 +2,82 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import * as C from "./common/constants";
import { GraphNode } from "./phases/graph-phase/graph-node"; import { GraphNode } from "./phases/graph-phase/graph-node";
import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node"; import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";
import { TurboshaftGraphBlock } from "./phases/turboshaft-graph-phase/turboshaft-graph-block"; import { TurboshaftGraphBlock } from "./phases/turboshaft-graph-phase/turboshaft-graph-block";
import { Graph } from "./graph";
import { TurboshaftGraph } from "./turboshaft-graph";
export abstract class Edge<NodeType extends GraphNode | TurboshaftGraphNode export abstract class Edge<NodeType extends GraphNode | TurboshaftGraphNode
| TurboshaftGraphBlock> { | TurboshaftGraphBlock> {
target: NodeType; target: NodeType;
source: NodeType; source: NodeType;
index: number;
backEdgeNumber: number; backEdgeNumber: number;
visible: boolean; visible: boolean;
constructor(target: NodeType, source: NodeType) { constructor(target: NodeType, index: number, source: NodeType) {
this.target = target; this.target = target;
this.index = index;
this.source = source; this.source = source;
this.backEdgeNumber = 0; this.backEdgeNumber = 0;
this.visible = false; this.visible = false;
} }
public getInputHorizontalPosition(graph: Graph | TurboshaftGraph, extendHeight: boolean): number {
if (graph.graphPhase.rendered && this.backEdgeNumber > 0) {
return graph.maxGraphNodeX + this.backEdgeNumber * C.MINIMUM_EDGE_SEPARATION;
}
const source = this.source;
const target = this.target;
const index = this.index;
const inputX = target.x + target.getInputX(index);
const inputApproach = target.getInputApproach(this.index);
const outputApproach = source.getOutputApproach(extendHeight);
if (inputApproach > outputApproach) {
return inputX;
}
const inputOffset = C.MINIMUM_EDGE_SEPARATION * (index + 1);
return target.x < source.x
? target.x + target.getWidth() + inputOffset
: target.x - inputOffset;
}
public generatePath(graph: Graph | TurboshaftGraph, extendHeight: boolean): string {
const target = this.target;
const source = this.source;
const inputX = target.x + target.getInputX(this.index);
const inputY = target.y - 2 * C.DEFAULT_NODE_BUBBLE_RADIUS - C.ARROW_HEAD_HEIGHT;
const outputX = source.x + source.getOutputX();
const outputY = source.y + source.getHeight(extendHeight) + C.DEFAULT_NODE_BUBBLE_RADIUS;
let inputApproach = target.getInputApproach(this.index);
const outputApproach = source.getOutputApproach(extendHeight);
const horizontalPos = this.getInputHorizontalPosition(graph, extendHeight);
let path: string;
if (inputY < outputY) {
path = `M ${outputX} ${outputY}\nL ${outputX} ${outputApproach}\nL ${horizontalPos} ${outputApproach}`;
if (horizontalPos !== inputX) {
path += `L ${horizontalPos} ${inputApproach}`;
} else if (inputApproach < outputApproach) {
inputApproach = outputApproach;
}
path += `L ${inputX} ${inputApproach}\nL ${inputX} ${inputY}`;
} else {
const controlY = outputY + (inputY - outputY) * C.BEZIER_CONSTANT;
path = `M ${outputX} ${outputY}\nC ${outputX} ${controlY},\n${inputX} ${outputY},\n${inputX} ${inputY}`;
}
return path;
}
public isVisible(): boolean { public isVisible(): boolean {
return this.visible && this.source.visible && this.target.visible; return this.visible && this.source.visible && this.target.visible;
} }
public toString(): string {
return `${this.source.id},${this.index},${this.target.id}`;
}
} }
...@@ -7,17 +7,18 @@ import { Graph } from "./graph"; ...@@ -7,17 +7,18 @@ import { Graph } from "./graph";
import { GraphNode } from "./phases/graph-phase/graph-node"; import { GraphNode } from "./phases/graph-phase/graph-node";
import { GraphEdge } from "./phases/graph-phase/graph-edge"; import { GraphEdge } from "./phases/graph-phase/graph-edge";
import { GraphStateType } from "./phases/graph-phase/graph-phase"; import { GraphStateType } from "./phases/graph-phase/graph-phase";
import { LayoutOccupation } from "./layout-occupation";
export class GraphLayout { export class GraphLayout {
graph: Graph; graph: Graph;
graphOccupation: GraphOccupation; layoutOccupation: LayoutOccupation;
startTime: number; startTime: number;
maxRank: number; maxRank: number;
visitOrderWithinRank: number; visitOrderWithinRank: number;
constructor(graph: Graph) { constructor(graph: Graph) {
this.graph = graph; this.graph = graph;
this.graphOccupation = new GraphOccupation(graph); this.layoutOccupation = new LayoutOccupation(graph);
this.maxRank = 0; this.maxRank = 0;
this.visitOrderWithinRank = 0; this.visitOrderWithinRank = 0;
} }
...@@ -194,7 +195,7 @@ export class GraphLayout { ...@@ -194,7 +195,7 @@ export class GraphLayout {
// compact and not overlapping live input lines. // compact and not overlapping live input lines.
rankSets.reverse().forEach((rankSet: Array<GraphNode>) => { rankSets.reverse().forEach((rankSet: Array<GraphNode>) => {
for (const node of rankSet) { for (const node of rankSet) {
this.graphOccupation.clearNodeOutputs(node, showTypes); this.layoutOccupation.clearOutputs(node, showTypes);
} }
this.traceOccupation("After clearing outputs"); this.traceOccupation("After clearing outputs");
...@@ -203,7 +204,7 @@ export class GraphLayout { ...@@ -203,7 +204,7 @@ export class GraphLayout {
rankSet = rankSet.sort((a: GraphNode, b: GraphNode) => a.compare(b)); rankSet = rankSet.sort((a: GraphNode, b: GraphNode) => a.compare(b));
for (const node of rankSet) { for (const node of rankSet) {
if (node.visible) { if (node.visible) {
node.x = this.graphOccupation.occupyNode(node); node.x = this.layoutOccupation.occupy(node);
this.trace(`Node ${node.id} is placed between [${node.x}, ${node.x + node.getWidth()})`); this.trace(`Node ${node.id} is placed between [${node.x}, ${node.x + node.getWidth()})`);
const staggeredFlooredI = Math.floor(placedCount++ % 3); const staggeredFlooredI = Math.floor(placedCount++ % 3);
const delta = C.MINIMUM_EDGE_SEPARATION * staggeredFlooredI; const delta = C.MINIMUM_EDGE_SEPARATION * staggeredFlooredI;
...@@ -215,12 +216,12 @@ export class GraphLayout { ...@@ -215,12 +216,12 @@ export class GraphLayout {
this.traceOccupation("Before clearing nodes"); this.traceOccupation("Before clearing nodes");
this.graphOccupation.clearOccupiedNodes(); this.layoutOccupation.clearOccupied();
this.traceOccupation("After clearing nodes"); this.traceOccupation("After clearing nodes");
for (const node of rankSet) { for (const node of rankSet) {
this.graphOccupation.occupyNodeInputs(node, showTypes); this.layoutOccupation.occupyInputs(node, showTypes);
} }
this.traceOccupation("After occupying inputs and determining bounding box"); this.traceOccupation("After occupying inputs and determining bounding box");
...@@ -247,219 +248,7 @@ export class GraphLayout { ...@@ -247,219 +248,7 @@ export class GraphLayout {
private traceOccupation(message: string): void { private traceOccupation(message: string): void {
if (C.TRACE_LAYOUT) { if (C.TRACE_LAYOUT) {
console.log(message); console.log(message);
this.graphOccupation.print(); this.layoutOccupation.print();
}
}
}
class GraphOccupation {
graph: Graph;
filledSlots: Array<boolean>;
nodeOccupations: Array<[number, number]>;
minSlot: number;
maxSlot: number;
constructor(graph: Graph) {
this.graph = graph;
this.filledSlots = new Array<boolean>();
this.nodeOccupations = new Array<[number, number]>();
this.minSlot = 0;
this.maxSlot = 0;
}
public clearNodeOutputs(source: GraphNode, showTypes: boolean): void {
for (const edge of source.outputs) {
if (!edge.isVisible()) continue;
for (const inputEdge of edge.target.inputs) {
if (inputEdge.source === source) {
const horizontalPos = edge.getInputHorizontalPosition(this.graph, showTypes);
this.clearPositionRangeWithMargin(horizontalPos, horizontalPos, C.NODE_INPUT_WIDTH / 2);
}
}
}
}
public clearOccupiedNodes(): void {
for (const [firstSlot, endSlotExclusive] of this.nodeOccupations) {
this.clearSlotRange(firstSlot, endSlotExclusive);
}
this.nodeOccupations = new Array<[number, number]>();
}
public occupyNode(node: GraphNode): number {
const width = node.getWidth();
const margin = C.MINIMUM_EDGE_SEPARATION;
const paddedWidth = width + 2 * margin;
const [direction, position] = this.getPlacementHint(node);
const x = position - paddedWidth + margin;
this.trace(`Node ${node.id} placement hint [${x}, ${(x + paddedWidth)})`);
const placement = this.findSpace(x, paddedWidth, direction);
const [firstSlot, slotWidth] = placement;
const endSlotExclusive = firstSlot + slotWidth - 1;
this.occupySlotRange(firstSlot, endSlotExclusive);
this.nodeOccupations.push([firstSlot, endSlotExclusive]);
if (direction < 0) {
return this.slotToLeftPosition(firstSlot + slotWidth) - width - margin;
} else if (direction > 0) {
return this.slotToLeftPosition(firstSlot) + margin;
} else {
return this.slotToLeftPosition(firstSlot + slotWidth / 2) - (width / 2);
}
}
public occupyNodeInputs(node: GraphNode, showTypes: boolean): void {
for (let i = 0; i < node.inputs.length; ++i) {
if (node.inputs[i].isVisible()) {
const edge = node.inputs[i];
if (!edge.isBackEdge()) {
const horizontalPos = edge.getInputHorizontalPosition(this.graph, showTypes);
this.trace(`Occupying input ${i} of ${node.id} at ${horizontalPos}`);
this.occupyPositionRangeWithMargin(horizontalPos, horizontalPos, C.NODE_INPUT_WIDTH / 2);
}
}
}
}
public print(): void {
let output = "";
for (let currentSlot = -40; currentSlot < 40; ++currentSlot) {
if (currentSlot != 0) {
output += " ";
} else {
output += "|";
}
}
console.log(output);
output = "";
for (let currentSlot2 = -40; currentSlot2 < 40; ++currentSlot2) {
if (this.filledSlots[this.slotToIndex(currentSlot2)]) {
output += "*";
} else {
output += " ";
}
}
console.log(output);
}
private getPlacementHint(node: GraphNode): [number, number] {
let position = 0;
let direction = -1;
let outputEdges = 0;
let inputEdges = 0;
for (const outputEdge of node.outputs) {
if (!outputEdge.isVisible()) continue;
const output = outputEdge.target;
for (let l = 0; l < output.inputs.length; ++l) {
if (output.rank > node.rank) {
const inputEdge = output.inputs[l];
if (inputEdge.isVisible()) ++inputEdges;
if (output.inputs[l].source == node) {
position += output.x + output.getInputX(l) + C.NODE_INPUT_WIDTH / 2;
outputEdges++;
if (l >= (output.inputs.length / 2)) {
direction = 1;
}
}
}
}
}
if (outputEdges != 0) {
position /= outputEdges;
}
if (outputEdges > 1 || inputEdges == 1) {
direction = 0;
}
return [direction, position];
}
private occupyPositionRange(from: number, to: number): void {
this.occupySlotRange(this.positionToSlot(from), this.positionToSlot(to - 1));
}
private clearPositionRange(from: number, to: number): void {
this.clearSlotRange(this.positionToSlot(from), this.positionToSlot(to - 1));
}
private occupySlotRange(from: number, to: number): void {
this.trace(`Occupied [${this.slotToLeftPosition(from)} ${this.slotToLeftPosition(to + 1)})`);
this.setIndexRange(from, to, true);
}
private clearSlotRange(from: number, to: number): void {
this.trace(`Cleared [${this.slotToLeftPosition(from)} ${this.slotToLeftPosition(to + 1)})`);
this.setIndexRange(from, to, false);
}
private clearPositionRangeWithMargin(from: number, to: number, margin: number): void {
const fromMargin = from - Math.floor(margin);
const toMargin = to + Math.floor(margin);
this.clearPositionRange(fromMargin, toMargin);
}
private occupyPositionRangeWithMargin(from: number, to: number, margin: number): void {
const fromMargin = from - Math.floor(margin);
const toMargin = to + Math.floor(margin);
this.occupyPositionRange(fromMargin, toMargin);
}
private findSpace(pos: number, width: number, direction: number): [number, number] {
const widthSlots = Math.floor((width + C.NODE_INPUT_WIDTH - 1) /
C.NODE_INPUT_WIDTH);
const currentSlot = this.positionToSlot(pos + width / 2);
let widthSlotsRemainingLeft = widthSlots;
let widthSlotsRemainingRight = widthSlots;
let slotsChecked = 0;
while (true) {
const mod = slotsChecked++ % 2;
const currentScanSlot = currentSlot + (mod ? -1 : 1) * (slotsChecked >> 1);
if (!this.filledSlots[this.slotToIndex(currentScanSlot)]) {
if (mod) {
if (direction <= 0) --widthSlotsRemainingLeft;
} else if (direction >= 0) {
--widthSlotsRemainingRight;
}
if (widthSlotsRemainingLeft == 0 || widthSlotsRemainingRight == 0 ||
(widthSlotsRemainingLeft + widthSlotsRemainingRight) == widthSlots &&
(widthSlots == slotsChecked)) {
return mod ? [currentScanSlot, widthSlots]
: [currentScanSlot - widthSlots + 1, widthSlots];
}
} else {
if (mod) {
widthSlotsRemainingLeft = widthSlots;
} else {
widthSlotsRemainingRight = widthSlots;
}
}
}
}
private setIndexRange(from: number, to: number, value: boolean): void {
if (to < from) throw ("Illegal slot range");
while (from <= to) {
this.maxSlot = Math.max(from, this.maxSlot);
this.minSlot = Math.min(from, this.minSlot);
this.filledSlots[this.slotToIndex(from++)] = value;
}
}
private positionToSlot(position: number): number {
return Math.floor(position / C.NODE_INPUT_WIDTH);
}
private slotToIndex(slot: number): number {
return slot >= 0 ? slot * 2 : slot * 2 + 1;
}
private slotToLeftPosition(slot: number): number {
return slot * C.NODE_INPUT_WIDTH;
}
private trace(message): void {
if (C.TRACE_LAYOUT) {
console.log(message);
} }
} }
} }
...@@ -10,11 +10,12 @@ import { MovableContainer } from "./movable-container"; ...@@ -10,11 +10,12 @@ import { MovableContainer } from "./movable-container";
export class Graph extends MovableContainer<GraphPhase> { export class Graph extends MovableContainer<GraphPhase> {
nodeMap: Array<GraphNode>; nodeMap: Array<GraphNode>;
maxBackEdgeNumber: number; originNodesMap: Map<string, Array<GraphNode>>;
constructor(graphPhase: GraphPhase) { constructor(graphPhase: GraphPhase) {
super(graphPhase); super(graphPhase);
this.nodeMap = graphPhase.nodeIdToNodeMap; this.nodeMap = graphPhase.nodeIdToNodeMap;
this.originNodesMap = graphPhase.originIdToNodesMap;
} }
public *nodes(func = (n: GraphNode) => true) { public *nodes(func = (n: GraphNode) => true) {
......
// Copyright 2022 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 * as C from "./common/constants";
import { Graph } from "./graph";
import { GraphNode } from "./phases/graph-phase/graph-node";
import { TurboshaftGraph } from "./turboshaft-graph";
import { TurboshaftGraphBlock } from "./phases/turboshaft-graph-phase/turboshaft-graph-block";
export class LayoutOccupation {
graph: Graph | TurboshaftGraph;
filledSlots: Array<boolean>;
occupations: Array<[number, number]>;
minSlot: number;
maxSlot: number;
constructor(graph: Graph | TurboshaftGraph) {
this.graph = graph;
this.filledSlots = new Array<boolean>();
this.occupations = new Array<[number, number]>();
this.minSlot = 0;
this.maxSlot = 0;
}
public clearOutputs(source: GraphNode | TurboshaftGraphBlock, showTypes: boolean): void {
for (const edge of source.outputs) {
if (!edge.isVisible()) continue;
for (const inputEdge of edge.target.inputs) {
if (inputEdge.source === source) {
const horizontalPos = edge.getInputHorizontalPosition(this.graph, showTypes);
this.clearPositionRangeWithMargin(horizontalPos, horizontalPos, C.NODE_INPUT_WIDTH / 2);
}
}
}
}
public clearOccupied(): void {
for (const [firstSlot, endSlotExclusive] of this.occupations) {
this.clearSlotRange(firstSlot, endSlotExclusive);
}
this.occupations = new Array<[number, number]>();
}
public occupy(item: GraphNode | TurboshaftGraphBlock): number {
const width = item.getWidth();
const margin = C.MINIMUM_EDGE_SEPARATION;
const paddedWidth = width + 2 * margin;
const [direction, position] = this.getPlacementHint(item);
const x = position - paddedWidth + margin;
this.trace(`${item.id} placement hint [${x}, ${(x + paddedWidth)})`);
const placement = this.findSpace(x, paddedWidth, direction);
const [firstSlot, slotWidth] = placement;
const endSlotExclusive = firstSlot + slotWidth - 1;
this.occupySlotRange(firstSlot, endSlotExclusive);
this.occupations.push([firstSlot, endSlotExclusive]);
if (direction < 0) {
return this.slotToLeftPosition(firstSlot + slotWidth) - width - margin;
} else if (direction > 0) {
return this.slotToLeftPosition(firstSlot) + margin;
} else {
return this.slotToLeftPosition(firstSlot + slotWidth / 2) - (width / 2);
}
}
public occupyInputs(item: GraphNode | TurboshaftGraphBlock, showTypes: boolean): void {
for (let i = 0; i < item.inputs.length; ++i) {
if (item.inputs[i].isVisible()) {
const edge = item.inputs[i];
if (!edge.isBackEdge()) {
const horizontalPos = edge.getInputHorizontalPosition(this.graph, showTypes);
this.trace(`Occupying input ${i} of ${item.id} at ${horizontalPos}`);
this.occupyPositionRangeWithMargin(horizontalPos, horizontalPos, C.NODE_INPUT_WIDTH / 2);
}
}
}
}
public print(): void {
let output = "";
for (let currentSlot = -40; currentSlot < 40; ++currentSlot) {
if (currentSlot != 0) {
output += " ";
} else {
output += "|";
}
}
console.log(output);
output = "";
for (let currentSlot2 = -40; currentSlot2 < 40; ++currentSlot2) {
if (this.filledSlots[this.slotToIndex(currentSlot2)]) {
output += "*";
} else {
output += " ";
}
}
console.log(output);
}
private getPlacementHint(item: GraphNode | TurboshaftGraphBlock): [number, number] {
let position = 0;
let direction = -1;
let outputEdges = 0;
let inputEdges = 0;
for (const outputEdge of item.outputs) {
if (!outputEdge.isVisible()) continue;
const output = outputEdge.target;
for (let l = 0; l < output.inputs.length; ++l) {
if (output.rank > item.rank) {
const inputEdge = output.inputs[l];
if (inputEdge.isVisible()) ++inputEdges;
if (output.inputs[l].source == item) {
position += output.x + output.getInputX(l) + C.NODE_INPUT_WIDTH / 2;
outputEdges++;
if (l >= (output.inputs.length / 2)) {
direction = 1;
}
}
}
}
}
if (outputEdges != 0) {
position /= outputEdges;
}
if (outputEdges > 1 || inputEdges == 1) {
direction = 0;
}
return [direction, position];
}
private occupyPositionRange(from: number, to: number): void {
this.occupySlotRange(this.positionToSlot(from), this.positionToSlot(to - 1));
}
private clearPositionRange(from: number, to: number): void {
this.clearSlotRange(this.positionToSlot(from), this.positionToSlot(to - 1));
}
private occupySlotRange(from: number, to: number): void {
this.trace(`Occupied [${this.slotToLeftPosition(from)} ${this.slotToLeftPosition(to + 1)})`);
this.setIndexRange(from, to, true);
}
private clearSlotRange(from: number, to: number): void {
this.trace(`Cleared [${this.slotToLeftPosition(from)} ${this.slotToLeftPosition(to + 1)})`);
this.setIndexRange(from, to, false);
}
private clearPositionRangeWithMargin(from: number, to: number, margin: number): void {
const fromMargin = from - Math.floor(margin);
const toMargin = to + Math.floor(margin);
this.clearPositionRange(fromMargin, toMargin);
}
private occupyPositionRangeWithMargin(from: number, to: number, margin: number): void {
const fromMargin = from - Math.floor(margin);
const toMargin = to + Math.floor(margin);
this.occupyPositionRange(fromMargin, toMargin);
}
private findSpace(pos: number, width: number, direction: number): [number, number] {
const widthSlots = Math.floor((width + C.NODE_INPUT_WIDTH - 1) /
C.NODE_INPUT_WIDTH);
const currentSlot = this.positionToSlot(pos + width / 2);
let widthSlotsRemainingLeft = widthSlots;
let widthSlotsRemainingRight = widthSlots;
let slotsChecked = 0;
while (true) {
const mod = slotsChecked++ % 2;
const currentScanSlot = currentSlot + (mod ? -1 : 1) * (slotsChecked >> 1);
if (!this.filledSlots[this.slotToIndex(currentScanSlot)]) {
if (mod) {
if (direction <= 0) --widthSlotsRemainingLeft;
} else if (direction >= 0) {
--widthSlotsRemainingRight;
}
if (widthSlotsRemainingLeft == 0 || widthSlotsRemainingRight == 0 ||
(widthSlotsRemainingLeft + widthSlotsRemainingRight) == widthSlots &&
(widthSlots == slotsChecked)) {
return mod ? [currentScanSlot, widthSlots]
: [currentScanSlot - widthSlots + 1, widthSlots];
}
} else {
if (mod) {
widthSlotsRemainingLeft = widthSlots;
} else {
widthSlotsRemainingRight = widthSlots;
}
}
}
}
private setIndexRange(from: number, to: number, value: boolean): void {
if (to < from) throw ("Illegal slot range");
while (from <= to) {
this.maxSlot = Math.max(from, this.maxSlot);
this.minSlot = Math.min(from, this.minSlot);
this.filledSlots[this.slotToIndex(from++)] = value;
}
}
private positionToSlot(position: number): number {
return Math.floor(position / C.NODE_INPUT_WIDTH);
}
private slotToIndex(slot: number): number {
return slot >= 0 ? slot * 2 : slot * 2 + 1;
}
private slotToLeftPosition(slot: number): number {
return slot * C.NODE_INPUT_WIDTH;
}
private trace(message): void {
if (C.TRACE_LAYOUT) {
console.log(message);
}
}
}
...@@ -7,6 +7,7 @@ import { TurboshaftGraphPhase } from "./phases/turboshaft-graph-phase/turboshaft ...@@ -7,6 +7,7 @@ import { TurboshaftGraphPhase } from "./phases/turboshaft-graph-phase/turboshaft
export abstract class MovableContainer<GraphPhaseType extends GraphPhase | TurboshaftGraphPhase> { export abstract class MovableContainer<GraphPhaseType extends GraphPhase | TurboshaftGraphPhase> {
graphPhase: GraphPhaseType; graphPhase: GraphPhaseType;
maxBackEdgeNumber: number;
minGraphX: number; minGraphX: number;
maxGraphX: number; maxGraphX: number;
maxGraphNodeX: number; maxGraphNodeX: number;
......
...@@ -16,6 +16,9 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge<Turb ...@@ -16,6 +16,9 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge<Turb
inputs: Array<EdgeType>; inputs: Array<EdgeType>;
outputs: Array<EdgeType>; outputs: Array<EdgeType>;
visible: boolean; visible: boolean;
outputApproach: number;
visitOrderWithinRank: number;
rank: number;
x: number; x: number;
y: number; y: number;
labelBox: { width: number, height: number }; labelBox: { width: number, height: number };
...@@ -29,9 +32,12 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge<Turb ...@@ -29,9 +32,12 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge<Turb
this.inputs = new Array<EdgeType>(); this.inputs = new Array<EdgeType>();
this.outputs = new Array<EdgeType>(); this.outputs = new Array<EdgeType>();
this.visible = false; this.visible = false;
this.outputApproach = C.MINIMUM_NODE_OUTPUT_APPROACH;
this.visitOrderWithinRank = 0;
this.rank = C.MAX_RANK_SENTINEL;
this.x = 0; this.x = 0;
this.y = 0; this.y = 0;
this.labelBox = measureText(this.displayLabel); if (displayLabel) this.labelBox = measureText(this.displayLabel);
} }
public areAnyOutputsVisible(): OutputVisibilityType { public areAnyOutputsVisible(): OutputVisibilityType {
...@@ -81,6 +87,25 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge<Turb ...@@ -81,6 +87,25 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge<Turb
return this.getWidth() - (C.NODE_INPUT_WIDTH / 2); return this.getWidth() - (C.NODE_INPUT_WIDTH / 2);
} }
public getInputApproach(index: number): number {
return this.y - C.MINIMUM_NODE_INPUT_APPROACH -
(index % 4) * C.MINIMUM_EDGE_SEPARATION - C.DEFAULT_NODE_BUBBLE_RADIUS;
}
public getOutputApproach(showTypes: boolean): number {
return this.y + this.outputApproach + this.getHeight(showTypes) +
+ C.DEFAULT_NODE_BUBBLE_RADIUS;
}
public compare(other: Node<any>): number {
if (this.visitOrderWithinRank < other.visitOrderWithinRank) {
return -1;
} else if (this.visitOrderWithinRank == other.visitOrderWithinRank) {
return 0;
}
return 1;
}
public identifier(): string { public identifier(): string {
return `${this.id}`; return `${this.id}`;
} }
......
...@@ -2,75 +2,18 @@ ...@@ -2,75 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import * as C from "../../common/constants";
import { Graph } from "../../graph";
import { Edge } from "../../edge"; import { Edge } from "../../edge";
import { GraphNode } from "./graph-node"; import { GraphNode } from "./graph-node";
export class GraphEdge extends Edge<GraphNode> { export class GraphEdge extends Edge<GraphNode> {
index: number;
type: string; type: string;
constructor(target: GraphNode, index: number, source: GraphNode, type: string) { constructor(target: GraphNode, index: number, source: GraphNode, type: string) {
super(target, source); super(target, index, source);
this.index = index;
this.type = type; this.type = type;
} }
public getInputHorizontalPosition(graph: Graph, showTypes: boolean): number {
if (graph.graphPhase.rendered && this.backEdgeNumber > 0) {
return graph.maxGraphNodeX + this.backEdgeNumber * C.MINIMUM_EDGE_SEPARATION;
}
const source = this.source;
const target = this.target;
const index = this.index;
const inputX = target.x + target.getInputX(index);
const inputApproach = target.getInputApproach(this.index);
const outputApproach = source.getOutputApproach(showTypes);
if (inputApproach > outputApproach) {
return inputX;
}
const inputOffset = C.MINIMUM_EDGE_SEPARATION * (index + 1);
return target.x < source.x
? target.x + target.getWidth() + inputOffset
: target.x - inputOffset;
}
public generatePath(graph: Graph, showTypes: boolean): string {
const target = this.target;
const source = this.source;
const inputX = target.x + target.getInputX(this.index);
const arrowheadHeight = 7;
const inputY = target.y - 2 * C.DEFAULT_NODE_BUBBLE_RADIUS - arrowheadHeight;
const outputX = source.x + source.getOutputX();
const outputY = source.y + source.getHeight(showTypes) + C.DEFAULT_NODE_BUBBLE_RADIUS;
let inputApproach = target.getInputApproach(this.index);
const outputApproach = source.getOutputApproach(showTypes);
const horizontalPos = this.getInputHorizontalPosition(graph, showTypes);
let path: string;
if (inputY < outputY) {
path = `M ${outputX} ${outputY}\nL ${outputX} ${outputApproach}\nL ${horizontalPos} ${outputApproach}`;
if (horizontalPos !== inputX) {
path += `L ${horizontalPos} ${inputApproach}`;
} else if (inputApproach < outputApproach) {
inputApproach = outputApproach;
}
path += `L ${inputX} ${inputApproach}\nL ${inputX} ${inputY}`;
} else {
const controlY = outputY + (inputY - outputY) * C.BEZIER_CONSTANT;
path = `M ${outputX} ${outputY}\nC ${outputX} ${controlY},\n${inputX} ${outputY},\n${inputX} ${inputY}`;
}
return path;
}
public isBackEdge(): boolean { public isBackEdge(): boolean {
return this.target.hasBackEdges() && (this.target.rank < this.source.rank); return this.target.hasBackEdges() && (this.target.rank < this.source.rank);
} }
public toString(): string {
return `${this.source.id},${this.index},${this.target.id}`;
}
} }
...@@ -10,18 +10,13 @@ import { GraphEdge } from "./graph-edge"; ...@@ -10,18 +10,13 @@ import { GraphEdge } from "./graph-edge";
export class GraphNode extends Node<GraphEdge> { export class GraphNode extends Node<GraphEdge> {
nodeLabel: NodeLabel; nodeLabel: NodeLabel;
rank: number;
outputApproach: number;
cfg: boolean; cfg: boolean;
width: number; width: number;
normalHeight: number; normalHeight: number;
visitOrderWithinRank: number;
constructor(nodeLabel: NodeLabel) { constructor(nodeLabel: NodeLabel) {
super(nodeLabel.id, nodeLabel.getDisplayLabel()); super(nodeLabel.id, nodeLabel.getDisplayLabel());
this.nodeLabel = nodeLabel; this.nodeLabel = nodeLabel;
this.rank = C.MAX_RANK_SENTINEL;
this.outputApproach = C.MINIMUM_NODE_OUTPUT_APPROACH;
// Every control node is a CFG node. // Every control node is a CFG node.
this.cfg = nodeLabel.control; this.cfg = nodeLabel.control;
const typeBox = measureText(this.getDisplayType()); const typeBox = measureText(this.getDisplayType());
...@@ -29,7 +24,6 @@ export class GraphNode extends Node<GraphEdge> { ...@@ -29,7 +24,6 @@ export class GraphNode extends Node<GraphEdge> {
this.width = alignUp(innerWidth + C.NODE_INPUT_WIDTH * 2, C.NODE_INPUT_WIDTH); this.width = alignUp(innerWidth + C.NODE_INPUT_WIDTH * 2, C.NODE_INPUT_WIDTH);
const innerHeight = Math.max(this.labelBox.height, typeBox.height); const innerHeight = Math.max(this.labelBox.height, typeBox.height);
this.normalHeight = innerHeight + 20; this.normalHeight = innerHeight + 20;
this.visitOrderWithinRank = 0;
} }
public getHeight(showTypes: boolean): number { public getHeight(showTypes: boolean): number {
...@@ -109,29 +103,10 @@ export class GraphNode extends Node<GraphEdge> { ...@@ -109,29 +103,10 @@ export class GraphNode extends Node<GraphEdge> {
return deepestRank; return deepestRank;
} }
public getInputApproach(index: number): number {
return this.y - C.MINIMUM_NODE_INPUT_APPROACH -
(index % 4) * C.MINIMUM_EDGE_SEPARATION - C.DEFAULT_NODE_BUBBLE_RADIUS;
}
public getOutputApproach(showTypes: boolean): number {
return this.y + this.outputApproach + this.getHeight(showTypes) +
+ C.DEFAULT_NODE_BUBBLE_RADIUS;
}
public hasBackEdges(): boolean { public hasBackEdges(): boolean {
return (this.nodeLabel.opcode === "Loop") || return (this.nodeLabel.opcode === "Loop") ||
((this.nodeLabel.opcode === "Phi" || this.nodeLabel.opcode === "EffectPhi" || ((this.nodeLabel.opcode === "Phi" || this.nodeLabel.opcode === "EffectPhi" ||
this.nodeLabel.opcode === "InductionVariablePhi") && this.nodeLabel.opcode === "InductionVariablePhi") &&
this.inputs[this.inputs.length - 1].source.nodeLabel.opcode === "Loop"); this.inputs[this.inputs.length - 1].source.nodeLabel.opcode === "Loop");
} }
public compare(other: GraphNode): number {
if (this.visitOrderWithinRank < other.visitOrderWithinRank) {
return -1;
} else if (this.visitOrderWithinRank == other.visitOrderWithinRank) {
return 0;
}
return 1;
}
} }
...@@ -15,6 +15,7 @@ export class GraphPhase extends Phase { ...@@ -15,6 +15,7 @@ export class GraphPhase extends Phase {
stateType: GraphStateType; stateType: GraphStateType;
nodeLabelMap: Array<NodeLabel>; nodeLabelMap: Array<NodeLabel>;
nodeIdToNodeMap: Array<GraphNode>; nodeIdToNodeMap: Array<GraphNode>;
originIdToNodesMap: Map<string, Array<GraphNode>>;
rendered: boolean; rendered: boolean;
transform: { x: number, y: number, scale: number }; transform: { x: number, y: number, scale: number };
...@@ -26,6 +27,7 @@ export class GraphPhase extends Phase { ...@@ -26,6 +27,7 @@ export class GraphPhase extends Phase {
this.stateType = GraphStateType.NeedToFullRebuild; this.stateType = GraphStateType.NeedToFullRebuild;
this.nodeLabelMap = nodeLabelMap ?? new Array<NodeLabel>(); this.nodeLabelMap = nodeLabelMap ?? new Array<NodeLabel>();
this.nodeIdToNodeMap = nodeIdToNodeMap ?? new Array<GraphNode>(); this.nodeIdToNodeMap = nodeIdToNodeMap ?? new Array<GraphNode>();
this.originIdToNodesMap = new Map<string, Array<GraphNode>>();
this.rendered = false; this.rendered = false;
} }
...@@ -42,10 +44,10 @@ export class GraphPhase extends Phase { ...@@ -42,10 +44,10 @@ export class GraphPhase extends Phase {
const jsonOrigin = node.origin; const jsonOrigin = node.origin;
if (jsonOrigin) { if (jsonOrigin) {
if (jsonOrigin.nodeId) { if (jsonOrigin.nodeId) {
origin = new NodeOrigin(jsonOrigin.nodeId, jsonOrigin.reducer, jsonOrigin.phase); origin = new NodeOrigin(jsonOrigin.nodeId, jsonOrigin.phase, jsonOrigin.reducer);
} else { } else {
origin = new BytecodeOrigin(jsonOrigin.bytecodePosition, jsonOrigin.reducer, origin = new BytecodeOrigin(jsonOrigin.bytecodePosition, jsonOrigin.phase,
jsonOrigin.phase); jsonOrigin.reducer);
} }
} }
...@@ -68,7 +70,14 @@ export class GraphPhase extends Phase { ...@@ -68,7 +70,14 @@ export class GraphPhase extends Phase {
} }
const newNode = new GraphNode(label); const newNode = new GraphNode(label);
this.data.nodes.push(newNode); this.data.nodes.push(newNode);
nodeIdToNodeMap[newNode.id] = newNode; nodeIdToNodeMap[newNode.identifier()] = newNode;
if (origin) {
const identifier = origin.identifier();
if (!this.originIdToNodesMap.has(identifier)) {
this.originIdToNodesMap.set(identifier, new Array<GraphNode>());
}
this.originIdToNodesMap.get(identifier).push(newNode);
}
} }
return nodeIdToNodeMap; return nodeIdToNodeMap;
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import * as C from "../../common/constants";
import { TurboshaftGraphNode } from "./turboshaft-graph-node"; import { TurboshaftGraphNode } from "./turboshaft-graph-node";
import { Node } from "../../node"; import { Node } from "../../node";
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge"; import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
...@@ -11,6 +12,9 @@ export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGra ...@@ -11,6 +12,9 @@ export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGra
deferred: boolean; deferred: boolean;
predecessors: Array<string>; predecessors: Array<string>;
nodes: Array<TurboshaftGraphNode>; nodes: Array<TurboshaftGraphNode>;
showProperties: boolean;
width: number;
height: number;
constructor(id: number, type: TurboshaftGraphBlockType, deferred: boolean, constructor(id: number, type: TurboshaftGraphBlockType, deferred: boolean,
predecessors: Array<string>) { predecessors: Array<string>) {
...@@ -23,14 +27,29 @@ export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGra ...@@ -23,14 +27,29 @@ export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGra
} }
public getHeight(showProperties: boolean): number { public getHeight(showProperties: boolean): number {
return this.nodes.reduce<number>((accumulator: number, node: TurboshaftGraphNode) => { if (this.showProperties != showProperties) {
return accumulator + node.getHeight(showProperties); this.height = this.nodes.reduce<number>((accumulator: number, node: TurboshaftGraphNode) => {
}, this.labelBox.height); return accumulator + node.getHeight(showProperties);
}, this.labelBox.height);
this.showProperties = showProperties;
}
return this.height;
} }
public getWidth(): number { public getWidth(): number {
const maxWidth = Math.max(...this.nodes.map((node: TurboshaftGraphNode) => node.getWidth())); if (!this.width) {
return Math.max(maxWidth, this.labelBox.width) + 50; const maxNodesWidth = Math.max(...this.nodes.map((node: TurboshaftGraphNode) =>
node.getWidth()));
this.width = Math.max(maxNodesWidth, this.labelBox.width) + C.TURBOSHAFT_NODE_X_INDENT * 2;
}
return this.width;
}
public hasBackEdges(): boolean {
return (this.type == TurboshaftGraphBlockType.Loop) ||
(this.type == TurboshaftGraphBlockType.Merge &&
this.inputs.length > 0 &&
this.inputs[this.inputs.length - 1].source.type == TurboshaftGraphBlockType.Loop);
} }
public toString(): string { public toString(): string {
......
...@@ -9,13 +9,15 @@ import { TurboshaftGraphBlock } from "./turboshaft-graph-block"; ...@@ -9,13 +9,15 @@ import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
export class TurboshaftGraphEdge<Type extends TurboshaftGraphNode | TurboshaftGraphBlock> extends export class TurboshaftGraphEdge<Type extends TurboshaftGraphNode | TurboshaftGraphBlock> extends
Edge<Type> { Edge<Type> {
constructor(target: Type, source: Type) { constructor(target: Type, index: number, source: Type) {
super(target, source); super(target, index, source);
this.visible = target.visible && source.visible; this.visible = target.visible && source.visible;
} }
public toString(idx?: number): string { public isBackEdge(): boolean {
if (idx !== null) return `${this.source.id},${idx},${this.target.id}`; if (this.target instanceof TurboshaftGraphBlock) {
return `${this.source.id},${this.target.id}`; return this.target.hasBackEdges() && (this.target.rank < this.source.rank);
}
return this.target.rank < this.source.rank;
} }
} }
...@@ -13,29 +13,45 @@ export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGrap ...@@ -13,29 +13,45 @@ export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGrap
block: TurboshaftGraphBlock; block: TurboshaftGraphBlock;
opPropertiesType: OpPropertiesType; opPropertiesType: OpPropertiesType;
properties: string; properties: string;
propertiesBox: { width: number, height: number };
constructor(id: number, title: string, block: TurboshaftGraphBlock, constructor(id: number, title: string, block: TurboshaftGraphBlock,
opPropertiesType: OpPropertiesType, properties: string) { opPropertiesType: OpPropertiesType, properties: string) {
super(id, `${id} ${title}`); super(id);
this.title = title; this.title = title;
this.block = block; this.block = block;
this.opPropertiesType = opPropertiesType; this.opPropertiesType = opPropertiesType;
this.properties = properties; this.properties = properties;
this.propertiesBox = measureText(this.properties);
this.visible = true; this.visible = true;
} }
public getHeight(showProperties: boolean): number { public getHeight(showProperties: boolean): number {
if (this.properties && showProperties) { if (this.properties && showProperties) {
return this.labelBox.height * 2; return this.labelBox.height + this.propertiesBox.height;
} }
return this.labelBox.height; return this.labelBox.height;
} }
public getWidth(): number { public getWidth(): number {
const measure = measureText( return Math.max(this.inputs.length * C.NODE_INPUT_WIDTH, this.labelBox.width);
`${this.getInlineLabel()}[${this.getPropertiesTypeAbbreviation()}]` }
);
return Math.max(this.inputs.length * C.NODE_INPUT_WIDTH, measure.width); public initDisplayLabel() {
this.displayLabel = this.getInlineLabel();
this.labelBox = measureText(this.displayLabel);
}
public getTitle(): string {
let title = `${this.id} ${this.title} ${this.opPropertiesType}`;
if (this.inputs.length > 0) {
title += `\nInputs: ${this.inputs.map(i => i.source.id).join(", ")}`;
}
if (this.outputs.length > 0) {
title += `\nOutputs: ${this.outputs.map(i => i.target.id).join(", ")}`;
}
const opPropertiesStr = this.properties.length > 0 ? this.properties : "No op properties";
return title + `\n${opPropertiesStr}`;
} }
public getInlineLabel(): string { public getInlineLabel(): string {
...@@ -44,29 +60,11 @@ export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGrap ...@@ -44,29 +60,11 @@ export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGrap
} }
public getReadableProperties(blockWidth: number): string { public getReadableProperties(blockWidth: number): string {
const propertiesWidth = measureText(this.properties).width; if (blockWidth > this.propertiesBox.width) return this.properties;
if (blockWidth > propertiesWidth) return this.properties; const widthOfOneSymbol = Math.floor(this.propertiesBox.width / this.properties.length);
const widthOfOneSymbol = Math.floor(propertiesWidth / this.properties.length);
const lengthOfReadableProperties = Math.floor(blockWidth / widthOfOneSymbol); const lengthOfReadableProperties = Math.floor(blockWidth / widthOfOneSymbol);
return `${this.properties.slice(0, lengthOfReadableProperties - 3)}..`; return `${this.properties.slice(0, lengthOfReadableProperties - 3)}..`;
} }
public getPropertiesTypeAbbreviation(): string {
switch (this.opPropertiesType) {
case OpPropertiesType.Pure:
return "P";
case OpPropertiesType.Reading:
return "R";
case OpPropertiesType.Writing:
return "W";
case OpPropertiesType.CanDeopt:
return "CD";
case OpPropertiesType.AnySideEffects:
return "ASE";
case OpPropertiesType.BlockTerminator:
return "BT";
}
}
} }
export enum OpPropertiesType { export enum OpPropertiesType {
......
...@@ -8,7 +8,7 @@ import { TurboshaftGraphEdge } from "./turboshaft-graph-edge"; ...@@ -8,7 +8,7 @@ import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
import { TurboshaftGraphBlock } from "./turboshaft-graph-block"; import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
export class TurboshaftGraphPhase extends Phase { export class TurboshaftGraphPhase extends Phase {
highestBlockId: number; highestBlockId: number; // TODO (danylo boiko) Delete this field
data: TurboshaftGraphData; data: TurboshaftGraphData;
stateType: GraphStateType; stateType: GraphStateType;
layoutType: TurboshaftLayoutType; layoutType: TurboshaftLayoutType;
...@@ -24,7 +24,6 @@ export class TurboshaftGraphPhase extends Phase { ...@@ -24,7 +24,6 @@ export class TurboshaftGraphPhase extends Phase {
this.highestBlockId = highestBlockId; this.highestBlockId = highestBlockId;
this.data = data ?? new TurboshaftGraphData(); this.data = data ?? new TurboshaftGraphData();
this.stateType = GraphStateType.NeedToFullRebuild; this.stateType = GraphStateType.NeedToFullRebuild;
this.layoutType = TurboshaftLayoutType.Inline;
this.nodeIdToNodeMap = nodeIdToNodeMap ?? new Array<TurboshaftGraphNode>(); this.nodeIdToNodeMap = nodeIdToNodeMap ?? new Array<TurboshaftGraphNode>();
this.blockIdToBlockMap = blockIdToBlockMap ?? new Array<TurboshaftGraphBlock>(); this.blockIdToBlockMap = blockIdToBlockMap ?? new Array<TurboshaftGraphBlock>();
this.rendered = false; this.rendered = false;
...@@ -42,12 +41,12 @@ export class TurboshaftGraphPhase extends Phase { ...@@ -42,12 +41,12 @@ export class TurboshaftGraphPhase extends Phase {
const block = new TurboshaftGraphBlock(blockJson.id, blockJson.type, const block = new TurboshaftGraphBlock(blockJson.id, blockJson.type,
blockJson.deferred, blockJson.predecessors); blockJson.deferred, blockJson.predecessors);
this.data.blocks.push(block); this.data.blocks.push(block);
this.blockIdToBlockMap[block.id] = block; this.blockIdToBlockMap[block.identifier()] = block;
} }
for (const block of this.blockIdToBlockMap) { for (const block of this.blockIdToBlockMap) {
for (const predecessor of block.predecessors) { for (const [idx, predecessor] of block.predecessors.entries()) {
const source = this.blockIdToBlockMap[predecessor]; const source = this.blockIdToBlockMap[predecessor];
const edge = new TurboshaftGraphEdge(block, source); const edge = new TurboshaftGraphEdge(block, idx, source);
block.inputs.push(edge); block.inputs.push(edge);
source.outputs.push(edge); source.outputs.push(edge);
} }
...@@ -61,7 +60,7 @@ export class TurboshaftGraphPhase extends Phase { ...@@ -61,7 +60,7 @@ export class TurboshaftGraphPhase extends Phase {
block, nodeJson.op_properties_type, nodeJson.properties); block, nodeJson.op_properties_type, nodeJson.properties);
block.nodes.push(node); block.nodes.push(node);
this.data.nodes.push(node); this.data.nodes.push(node);
this.nodeIdToNodeMap[node.id] = node; this.nodeIdToNodeMap[node.identifier()] = node;
} }
} }
...@@ -69,11 +68,14 @@ export class TurboshaftGraphPhase extends Phase { ...@@ -69,11 +68,14 @@ export class TurboshaftGraphPhase extends Phase {
for (const edgeJson of edgesJson) { for (const edgeJson of edgesJson) {
const target = this.nodeIdToNodeMap[edgeJson.target]; const target = this.nodeIdToNodeMap[edgeJson.target];
const source = this.nodeIdToNodeMap[edgeJson.source]; const source = this.nodeIdToNodeMap[edgeJson.source];
const edge = new TurboshaftGraphEdge(target, source); const edge = new TurboshaftGraphEdge(target, -1, source);
this.data.edges.push(edge); this.data.edges.push(edge);
target.inputs.push(edge); target.inputs.push(edge);
source.outputs.push(edge); source.outputs.push(edge);
} }
for (const node of this.data.nodes) {
node.initDisplayLabel();
}
} }
} }
......
...@@ -34,11 +34,21 @@ export class SelectionBroker { ...@@ -34,11 +34,21 @@ export class SelectionBroker {
this.nodeHandlers.push(handler); this.nodeHandlers.push(handler);
} }
public deleteNodeHandler(handler: NodeSelectionHandler & ClearableHandler): void {
this.allHandlers = this.allHandlers.filter(h => h != handler);
this.nodeHandlers = this.nodeHandlers.filter(h => h != handler);
}
public addBlockHandler(handler: BlockSelectionHandler & ClearableHandler): void { public addBlockHandler(handler: BlockSelectionHandler & ClearableHandler): void {
this.allHandlers.push(handler); this.allHandlers.push(handler);
this.blockHandlers.push(handler); this.blockHandlers.push(handler);
} }
public deleteBlockHandler(handler: BlockSelectionHandler & ClearableHandler): void {
this.allHandlers = this.allHandlers.filter(h => h != handler);
this.blockHandlers = this.blockHandlers.filter(h => h != handler);
}
public addInstructionHandler(handler: InstructionSelectionHandler & ClearableHandler): void { public addInstructionHandler(handler: InstructionSelectionHandler & ClearableHandler): void {
this.allHandlers.push(handler); this.allHandlers.push(handler);
this.instructionHandlers.push(handler); this.instructionHandlers.push(handler);
......
...@@ -46,6 +46,7 @@ export class SourceResolver { ...@@ -46,6 +46,7 @@ export class SourceResolver {
this.phases = new Array<GenericPhase>(); this.phases = new Array<GenericPhase>();
// Maps phase names to phaseIds. // Maps phase names to phaseIds.
this.phaseNames = new Map<string, number>(); this.phaseNames = new Map<string, number>();
this.instructionsPhase = new InstructionsPhase("");
// The disassembly phase is stored separately. // The disassembly phase is stored separately.
this.disassemblyPhase = undefined; this.disassemblyPhase = undefined;
// Maps line numbers to source positions // Maps line numbers to source positions
...@@ -155,22 +156,19 @@ export class SourceResolver { ...@@ -155,22 +156,19 @@ export class SourceResolver {
break; break;
case PhaseType.Instructions: case PhaseType.Instructions:
const castedInstructions = genericPhase as InstructionsPhase; const castedInstructions = genericPhase as InstructionsPhase;
let instructionsPhase: InstructionsPhase = null; if (this.instructionsPhase.name === "") {
if (this.instructionsPhase) { this.instructionsPhase.name = castedInstructions.name;
instructionsPhase = this.instructionsPhase;
instructionsPhase.name += `, ${castedInstructions.name}`;
} else { } else {
instructionsPhase = new InstructionsPhase(castedInstructions.name); this.instructionsPhase.name += `, ${castedInstructions.name}`;
} }
instructionsPhase.parseNodeIdToInstructionRangeFromJSON(castedInstructions this.instructionsPhase.parseNodeIdToInstructionRangeFromJSON(castedInstructions
?.nodeIdToInstructionRange); ?.nodeIdToInstructionRange);
instructionsPhase.parseBlockIdToInstructionRangeFromJSON(castedInstructions this.instructionsPhase.parseBlockIdToInstructionRangeFromJSON(castedInstructions
?.blockIdToInstructionRange); ?.blockIdToInstructionRange);
instructionsPhase.parseInstructionOffsetToPCOffsetFromJSON(castedInstructions this.instructionsPhase.parseInstructionOffsetToPCOffsetFromJSON(castedInstructions
?.instructionOffsetToPCOffset); ?.instructionOffsetToPCOffset);
instructionsPhase.parseCodeOffsetsInfoFromJSON(castedInstructions this.instructionsPhase.parseCodeOffsetsInfoFromJSON(castedInstructions
?.codeOffsetsInfo); ?.codeOffsetsInfo);
this.instructionsPhase = instructionsPhase;
break; break;
case PhaseType.Graph: case PhaseType.Graph:
const castedGraph = genericPhase as GraphPhase; const castedGraph = genericPhase as GraphPhase;
......
// Copyright 2022 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 * as C from "./common/constants";
import { TurboshaftGraph } from "./turboshaft-graph"; import { TurboshaftGraph } from "./turboshaft-graph";
import { GraphStateType } from "./phases/phase"; import { GraphStateType } from "./phases/phase";
import { TurboshaftLayoutType } from "./phases/turboshaft-graph-phase/turboshaft-graph-phase"; import {
TurboshaftGraphBlock,
TurboshaftGraphBlockType
} from "./phases/turboshaft-graph-phase/turboshaft-graph-block";
import { LayoutOccupation } from "./layout-occupation";
export class TurboshaftGraphLayout { export class TurboshaftGraphLayout {
graph: TurboshaftGraph; graph: TurboshaftGraph;
layoutOccupation: LayoutOccupation;
startTime: number;
maxRank: number;
visitOrderWithinRank: number;
constructor(graph: TurboshaftGraph) { constructor(graph: TurboshaftGraph) {
this.graph = graph; this.graph = graph;
this.layoutOccupation = new LayoutOccupation(graph);
this.maxRank = 0;
this.visitOrderWithinRank = 0;
} }
public rebuild(showProperties: boolean): void { public rebuild(showProperties: boolean): void {
if (this.graph.graphPhase.stateType == GraphStateType.NeedToFullRebuild) { switch (this.graph.graphPhase.stateType) {
switch (this.graph.graphPhase.layoutType) { case GraphStateType.NeedToFullRebuild:
case TurboshaftLayoutType.Inline: this.fullRebuild(showProperties);
this.inlineRebuild(showProperties); break;
break; case GraphStateType.Cached:
default: this.cachedRebuild();
throw "Unsupported graph layout type"; break;
} default:
this.graph.graphPhase.stateType = GraphStateType.Cached; throw "Unsupported graph state type";
} }
this.graph.graphPhase.rendered = true; this.graph.graphPhase.rendered = true;
} }
private inlineRebuild(showProperties): void { private fullRebuild(showProperties: boolean): void {
// Useless logic to simulate blocks coordinates (will be replaced in the future) this.startTime = performance.now();
let x = 0; this.maxRank = 0;
let y = 0; this.visitOrderWithinRank = 0;
for (const block of this.graph.blockMap) {
block.x = x; const blocks = this.initBlocks();
block.y = y; this.initWorkList(blocks);
y += block.getHeight(showProperties) + 50;
if (y > 1800) { let visited = new Array<boolean>();
x += block.getWidth() * 1.5; blocks.forEach((block: TurboshaftGraphBlock) => this.dfsFindRankLate(visited, block));
y = 0; visited = new Array<boolean>();
blocks.forEach((block: TurboshaftGraphBlock) => this.dfsRankOrder(visited, block));
const rankSets = this.getRankSets(showProperties);
this.placeBlocks(rankSets, showProperties);
this.calculateBackEdgeNumbers();
this.graph.graphPhase.stateType = GraphStateType.Cached;
}
private cachedRebuild(): void {
this.calculateBackEdgeNumbers();
}
private initBlocks(): Array<TurboshaftGraphBlock> {
// First determine the set of blocks that have no inputs. Those are the
// basis for top-down DFS to determine rank and block placement.
const blocksHasNoInputs = new Array<boolean>();
for (const block of this.graph.blocks()) {
blocksHasNoInputs[block.id] = true;
}
for (const edge of this.graph.blocksEdges()) {
blocksHasNoInputs[edge.target.id] = false;
}
// Finialize the list of blocks.
const blocks = new Array<TurboshaftGraphBlock>();
const visited = new Array<boolean>();
for (const block of this.graph.blocks()) {
if (blocksHasNoInputs[block.id]) {
blocks.push(block);
} }
visited[block.id] = false;
block.rank = 0;
block.visitOrderWithinRank = 0;
block.outputApproach = C.MINIMUM_NODE_OUTPUT_APPROACH;
}
this.trace("layoutGraph init");
return blocks;
}
private initWorkList(blocks: Array<TurboshaftGraphBlock>): void {
const workList: Array<TurboshaftGraphBlock> = blocks.slice();
while (workList.length != 0) {
const block: TurboshaftGraphBlock = workList.pop();
let changed = false;
if (block.rank == C.MAX_RANK_SENTINEL) {
block.rank = 1;
changed = true;
}
let begin = 0;
let end = block.inputs.length;
if (block.type == TurboshaftGraphBlockType.Merge && block.inputs.length > 0) {
begin = block.inputs.length - 1;
} else if (block.hasBackEdges()) {
end = 1;
}
for (let l = begin; l < end; ++l) {
const input = block.inputs[l].source;
if (input.visible && input.rank >= block.rank) {
block.rank = input.rank + 1;
changed = true;
}
}
if (changed) {
const hasBackEdges = block.hasBackEdges();
for (let l = block.outputs.length - 1; l >= 0; --l) {
if (hasBackEdges && (l != 0)) {
workList.unshift(block.outputs[l].target);
} else {
workList.push(block.outputs[l].target);
}
}
}
this.maxRank = Math.max(block.rank, this.maxRank);
this.trace("layoutGraph work list");
}
}
private dfsFindRankLate(visited: Array<boolean>, block: TurboshaftGraphBlock): void {
if (visited[block.id]) return;
visited[block.id] = true;
const originalRank = block.rank;
let newRank = block.rank;
let isFirstInput = true;
for (const outputEdge of block.outputs) {
const output = outputEdge.target;
this.dfsFindRankLate(visited, output);
const outputRank = output.rank;
if (output.visible && (isFirstInput || outputRank <= newRank) &&
(outputRank > originalRank)) {
newRank = outputRank - 1;
}
isFirstInput = false;
}
if (block.hasBackEdges()) {
block.rank = newRank;
}
}
private dfsRankOrder(visited: Array<boolean>, block: TurboshaftGraphBlock): void {
if (visited[block.id]) return;
visited[block.id] = true;
for (const outputEdge of block.outputs) {
if (outputEdge.isVisible()) {
const output = outputEdge.target;
this.dfsRankOrder(visited, output);
}
}
if (block.visitOrderWithinRank == 0) {
block.visitOrderWithinRank = ++this.visitOrderWithinRank;
}
}
private getRankSets(showProperties: boolean): Array<Array<TurboshaftGraphBlock>> {
const rankMaxBlockHeight = new Array<number>();
for (const block of this.graph.blocks()) {
rankMaxBlockHeight[block.rank] = Math.max(rankMaxBlockHeight[block.rank] ?? 0,
block.getHeight(showProperties));
}
const rankSets = new Array<Array<TurboshaftGraphBlock>>();
for (const block of this.graph.blocks()) {
block.y = rankMaxBlockHeight.slice(1, block.rank).reduce<number>((accumulator, current) => {
return accumulator + current;
}, block.rank * (C.TURBOSHAFT_BLOCK_ROW_SEPARATION + 2 * C.DEFAULT_NODE_BUBBLE_RADIUS));
if (block.visible) {
if (!rankSets[block.rank]) {
rankSets[block.rank] = new Array<TurboshaftGraphBlock>(block);
} else {
rankSets[block.rank].push(block);
}
}
}
return rankSets;
}
private placeBlocks(rankSets: Array<Array<TurboshaftGraphBlock>>, showProperties: boolean): void {
// Iterate backwards from highest to lowest rank, placing blocks so that they
// spread out from the "center" as much as possible while still being
// compact and not overlapping live input lines.
rankSets.reverse().forEach((rankSet: Array<TurboshaftGraphBlock>) => {
for (const block of rankSet) {
this.layoutOccupation.clearOutputs(block, showProperties);
}
this.traceOccupation("After clearing outputs");
let placedCount = 0;
rankSet = rankSet.sort((a: TurboshaftGraphBlock, b: TurboshaftGraphBlock) => a.compare(b));
for (const block of rankSet) {
if (block.visible) {
block.x = this.layoutOccupation.occupy(block);
const blockWidth = block.getWidth();
this.trace(`Block ${block.id} is placed between [${block.x}, ${block.x + blockWidth})`);
const staggeredFlooredI = Math.floor(placedCount++ % 3);
const delta = C.MINIMUM_EDGE_SEPARATION * staggeredFlooredI;
block.outputApproach += delta;
} else {
block.x = 0;
}
}
this.traceOccupation("Before clearing blocks");
this.layoutOccupation.clearOccupied();
this.traceOccupation("After clearing blocks");
for (const block of rankSet) {
this.layoutOccupation.occupyInputs(block, showProperties);
}
this.traceOccupation("After occupying inputs and determining bounding box");
});
}
private calculateBackEdgeNumbers(): void {
this.graph.maxBackEdgeNumber = 0;
for (const edge of this.graph.blocksEdges()) {
if (edge.isBackEdge()) {
edge.backEdgeNumber = ++this.graph.maxBackEdgeNumber;
} else {
edge.backEdgeNumber = 0;
}
}
}
private trace(message: string): void {
if (C.TRACE_LAYOUT) {
console.log(`${message} ${performance.now() - this.startTime}`);
}
}
private traceOccupation(message: string): void {
if (C.TRACE_LAYOUT) {
console.log(message);
this.layoutOccupation.print();
} }
} }
} }
...@@ -37,7 +37,17 @@ export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> { ...@@ -37,7 +37,17 @@ export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> {
for (const block of this.blockMap) { for (const block of this.blockMap) {
if (!block) continue; if (!block) continue;
for (const edge of block.inputs) { for (const edge of block.inputs) {
if (!edge || func(edge)) continue; if (!edge || !func(edge)) continue;
yield edge;
}
}
}
public *nodesEdges(func = (e: TurboshaftGraphEdge<TurboshaftGraphNode>) => true) {
for (const block of this.nodeMap) {
if (!block) continue;
for (const edge of block.inputs) {
if (!edge || !func(edge)) continue;
yield edge; yield edge;
} }
} }
...@@ -61,7 +71,7 @@ export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> { ...@@ -61,7 +71,7 @@ export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> {
+ C.NODE_INPUT_WIDTH); + C.NODE_INPUT_WIDTH);
} }
this.maxGraphX = this.maxGraphNodeX + 3 * C.MINIMUM_EDGE_SEPARATION; this.maxGraphX = this.maxGraphNodeX + this.maxBackEdgeNumber * C.MINIMUM_EDGE_SEPARATION;
this.width = this.maxGraphX - this.minGraphX; this.width = this.maxGraphX - this.minGraphX;
this.height = this.maxGraphY - this.minGraphY; this.height = this.maxGraphY - this.minGraphY;
......
...@@ -3,6 +3,12 @@ ...@@ -3,6 +3,12 @@
// found in the LICENSE file. // found in the LICENSE file.
import { Source } from "../source"; import { Source } from "../source";
import { GenericPosition, SourceResolver } from "../source-resolver";
import { SelectionBroker } from "../selection/selection-broker";
import { View } from "./view";
import { SelectionMap } from "../selection/selection";
import { ViewElements } from "../common/view-elements";
import { SelectionHandler } from "../selection/selection-handler";
interface PR { interface PR {
prettyPrint(_: unknown, el: HTMLElement): void; prettyPrint(_: unknown, el: HTMLElement): void;
...@@ -12,13 +18,6 @@ declare global { ...@@ -12,13 +18,6 @@ declare global {
const PR: PR; const PR: PR;
} }
import { GenericPosition, SourceResolver } from "../source-resolver";
import { SelectionBroker } from "../selection/selection-broker";
import { View } from "./view";
import { SelectionMap } from "../selection/selection";
import { ViewElements } from "../common/view-elements";
import { SelectionHandler } from "../selection/selection-handler";
export enum CodeMode { export enum CodeMode {
MAIN_SOURCE = "main function", MAIN_SOURCE = "main function",
INLINED_SOURCE = "inlined function" INLINED_SOURCE = "inlined function"
...@@ -81,7 +80,7 @@ export class CodeView extends View { ...@@ -81,7 +80,7 @@ export class CodeView extends View {
view.updateSelection(); view.updateSelection();
}, },
}; };
view.selection = new SelectionMap((sp: GenericPosition) => sp.toString()); view.selection = new SelectionMap((gp: GenericPosition) => gp.toString());
broker.addSourcePositionHandler(selectionHandler); broker.addSourcePositionHandler(selectionHandler);
this.selectionHandler = selectionHandler; this.selectionHandler = selectionHandler;
this.initializeCode(); this.initializeCode();
...@@ -280,5 +279,4 @@ export class CodeView extends View { ...@@ -280,5 +279,4 @@ export class CodeView extends View {
view.addHtmlElementToSourcePosition(sourcePosition, lineElement); view.addHtmlElementToSourcePosition(sourcePosition, lineElement);
} }
} }
} }
...@@ -13,7 +13,7 @@ import { GraphEdge } from "../phases/graph-phase/graph-edge"; ...@@ -13,7 +13,7 @@ import { GraphEdge } from "../phases/graph-phase/graph-edge";
import { GraphLayout } from "../graph-layout"; import { GraphLayout } from "../graph-layout";
import { GraphData, GraphPhase, GraphStateType } from "../phases/graph-phase/graph-phase"; import { GraphData, GraphPhase, GraphStateType } from "../phases/graph-phase/graph-phase";
import { BytecodePosition } from "../position"; import { BytecodePosition } from "../position";
import { BytecodeOrigin } from "../origin"; import { BytecodeOrigin, NodeOrigin } from "../origin";
import { MovableView } from "./movable-view"; import { MovableView } from "./movable-view";
import { ClearableHandler, NodeSelectionHandler } from "../selection/selection-handler"; import { ClearableHandler, NodeSelectionHandler } from "../selection/selection-handler";
import { GenericPosition } from "../source-resolver"; import { GenericPosition } from "../source-resolver";
...@@ -69,10 +69,8 @@ export class GraphView extends MovableView<Graph> { ...@@ -69,10 +69,8 @@ export class GraphView extends MovableView<Graph> {
this.addToggleImgInput("toggle-cache-layout", "toggle saving graph layout", this.addToggleImgInput("toggle-cache-layout", "toggle saving graph layout",
this.state.cacheLayout, partial(this.toggleLayoutCachingAction, this)); this.state.cacheLayout, partial(this.toggleLayoutCachingAction, this));
const adaptedSelection = this.adaptSelectionToCurrentPhase(data.data, rememberedSelection);
this.phaseName = data.name; this.phaseName = data.name;
this.createGraph(data, adaptedSelection); const adaptedSelection = this.createGraph(data, rememberedSelection);
this.broker.addNodeHandler(this.nodesSelectionHandler); this.broker.addNodeHandler(this.nodesSelectionHandler);
const selectedNodes = adaptedSelection?.size > 0 const selectedNodes = adaptedSelection?.size > 0
...@@ -162,8 +160,8 @@ export class GraphView extends MovableView<Graph> { ...@@ -162,8 +160,8 @@ export class GraphView extends MovableView<Graph> {
const adjOutputEdges = visibleEdges.filter(edge => edge.source === node); const adjOutputEdges = visibleEdges.filter(edge => edge.source === node);
adjInputEdges.attr("relToHover", "input"); adjInputEdges.attr("relToHover", "input");
adjOutputEdges.attr("relToHover", "output"); adjOutputEdges.attr("relToHover", "output");
const adjInputNodes = adjInputEdges.data().map(edge => edge.source);
const visibleNodes = view.visibleNodes.selectAll<SVGGElement, GraphNode>("g"); const visibleNodes = view.visibleNodes.selectAll<SVGGElement, GraphNode>("g");
const adjInputNodes = adjInputEdges.data().map(edge => edge.source);
visibleNodes.data<GraphNode>(adjInputNodes, node => node.toString()) visibleNodes.data<GraphNode>(adjInputNodes, node => node.toString())
.attr("relToHover", "input"); .attr("relToHover", "input");
const adjOutputNodes = adjOutputEdges.data().map(edge => edge.target); const adjOutputNodes = adjOutputEdges.data().map(edge => edge.target);
...@@ -238,7 +236,7 @@ export class GraphView extends MovableView<Graph> { ...@@ -238,7 +236,7 @@ export class GraphView extends MovableView<Graph> {
view.updateInputAndOutputBubbles(); view.updateInputAndOutputBubbles();
graph.maxGraphX = graph.maxGraphNodeX; graph.maxGraphX = graph.maxGraphNodeX;
newAndOldEdges.attr("d", node => node.generatePath(graph, view.state.showTypes)); newAndOldEdges.attr("d", edge => edge.generatePath(graph, view.state.showTypes));
} }
public svgKeyDown(): void { public svgKeyDown(): void {
...@@ -365,11 +363,6 @@ export class GraphView extends MovableView<Graph> { ...@@ -365,11 +363,6 @@ export class GraphView extends MovableView<Graph> {
private initializeNodesSelectionHandler(): NodeSelectionHandler & ClearableHandler { private initializeNodesSelectionHandler(): NodeSelectionHandler & ClearableHandler {
const view = this; const view = this;
return { return {
clear: function () {
view.state.selection.clear();
view.broker.broadcastClear(this);
view.updateGraphVisibility();
},
select: function (selectedNodes: Array<GraphNode>, selected: boolean) { select: function (selectedNodes: Array<GraphNode>, selected: boolean) {
const locations = new Array<GenericPosition>(); const locations = new Array<GenericPosition>();
for (const node of selectedNodes) { for (const node of selectedNodes) {
...@@ -384,22 +377,27 @@ export class GraphView extends MovableView<Graph> { ...@@ -384,22 +377,27 @@ export class GraphView extends MovableView<Graph> {
view.broker.broadcastSourcePositionSelect(this, locations, selected); view.broker.broadcastSourcePositionSelect(this, locations, selected);
view.updateGraphVisibility(); view.updateGraphVisibility();
}, },
clear: function () {
view.state.selection.clear();
view.broker.broadcastClear(this);
view.updateGraphVisibility();
},
brokeredNodeSelect: function (locations, selected: boolean) { brokeredNodeSelect: function (locations, selected: boolean) {
if (!view.graph) return; if (!view.graph) return;
const selection = view.graph.nodes(node => const selection = view.graph.nodes(node =>
locations.has(node.identifier()) && (!view.state.hideDead || node.isLive())); locations.has(node.identifier()) && (!view.state.hideDead || node.isLive()));
view.state.selection.select(selection, selected); view.state.selection.select(selection, selected);
// Update edge visibility based on selection. // Update edge visibility based on selection.
for (const node of view.graph.nodes()) { for (const item of view.state.selection.selectedKeys()) {
if (view.state.selection.isSelected(node)) { const node = view.graph.nodeMap[item];
node.visible = true; if (!node) continue;
node.inputs.forEach(edge => { node.visible = true;
edge.visible = edge.visible || view.state.selection.isSelected(edge.source); node.inputs.forEach(edge => {
}); edge.visible = edge.visible || view.state.selection.isSelected(edge.source);
node.outputs.forEach(edge => { });
edge.visible = edge.visible || view.state.selection.isSelected(edge.target); node.outputs.forEach(edge => {
}); edge.visible = edge.visible || view.state.selection.isSelected(edge.target);
} });
} }
view.updateGraphVisibility(); view.updateGraphVisibility();
}, },
...@@ -410,7 +408,7 @@ export class GraphView extends MovableView<Graph> { ...@@ -410,7 +408,7 @@ export class GraphView extends MovableView<Graph> {
}; };
} }
private createGraph(data: GraphPhase, selection): void { private createGraph(data: GraphPhase, rememberedSelection: Map<string, GraphNode>): Set<string> {
this.graph = new Graph(data); this.graph = new Graph(data);
this.graphLayout = new GraphLayout(this.graph); this.graphLayout = new GraphLayout(this.graph);
...@@ -422,8 +420,10 @@ export class GraphView extends MovableView<Graph> { ...@@ -422,8 +420,10 @@ export class GraphView extends MovableView<Graph> {
this.showVisible(); this.showVisible();
} }
if (selection !== undefined) { const adaptedSelection = this.adaptSelectionToCurrentPhase(data.data, rememberedSelection);
for (const item of selection) {
if (adaptedSelection !== undefined) {
for (const item of adaptedSelection) {
if (this.graph.nodeMap[item]) this.graph.nodeMap[item].visible = true; if (this.graph.nodeMap[item]) this.graph.nodeMap[item].visible = true;
} }
} }
...@@ -432,6 +432,7 @@ export class GraphView extends MovableView<Graph> { ...@@ -432,6 +432,7 @@ export class GraphView extends MovableView<Graph> {
this.layoutGraph(); this.layoutGraph();
this.updateGraphVisibility(); this.updateGraphVisibility();
return adaptedSelection;
} }
private layoutGraph(): void { private layoutGraph(): void {
...@@ -530,31 +531,28 @@ export class GraphView extends MovableView<Graph> { ...@@ -530,31 +531,28 @@ export class GraphView extends MovableView<Graph> {
private adaptSelectionToCurrentPhase(data: GraphData, selection: Map<string, GraphNode>): private adaptSelectionToCurrentPhase(data: GraphData, selection: Map<string, GraphNode>):
Set<string> { Set<string> {
// TODO (danylo boiko) Speed up adapting const updatedSelection = new Set<string>();
const updatedGraphSelection = new Set<string>(); if (!data || !(selection instanceof Map)) return updatedSelection;
if (!data || !(selection instanceof Map)) return updatedGraphSelection; for (const [key, node] of selection.entries()) {
// Adding survived nodes (with the same id) // Adding survived nodes (with the same id)
for (const node of data.nodes) { const survivedNode = this.graph.nodeMap[key];
const stringKey = this.state.selection.stringKey(node); if (survivedNode) {
if (selection.has(stringKey)) { updatedSelection.add(this.state.selection.stringKey(survivedNode));
updatedGraphSelection.add(stringKey);
} }
} // Adding children of nodes
// Adding children of nodes const childNodes = this.graph.originNodesMap.get(key);
for (const node of data.nodes) { if (childNodes?.length > 0) {
const originStringKey = this.state.selection.originStringKey(node); for (const childNode of childNodes) {
if (originStringKey && selection.has(originStringKey)) { updatedSelection.add(this.state.selection.stringKey(childNode));
updatedGraphSelection.add(this.state.selection.stringKey(node)); }
} }
} // Adding ancestors of nodes
// Adding ancestors of nodes const originStringKey = this.state.selection.originStringKey(node);
selection.forEach(selectedNode => {
const originStringKey = this.state.selection.originStringKey(selectedNode);
if (originStringKey) { if (originStringKey) {
updatedGraphSelection.add(originStringKey); updatedSelection.add(originStringKey);
} }
}); }
return updatedGraphSelection; return updatedSelection;
} }
private attachSelection(selection: Set<string>): Array<GraphNode> { private attachSelection(selection: Set<string>): Array<GraphNode> {
...@@ -703,27 +701,26 @@ export class GraphView extends MovableView<Graph> { ...@@ -703,27 +701,26 @@ export class GraphView extends MovableView<Graph> {
this.updateGraphVisibility(); this.updateGraphVisibility();
} }
private selectOrigins() { private selectOrigins(): void {
const state = this.state; const origins = new Array<GraphNode>();
// TODO (danylo boiko) Add array type
const origins = [];
let phase = this.phaseName; let phase = this.phaseName;
const selection = new Set<any>(); const selection = new Set<string>();
for (const node of state.selection) { for (const node of this.state.selection) {
const origin = node.nodeLabel.origin; const origin = node.nodeLabel.origin;
if (origin) { if (origin && origin instanceof NodeOrigin) {
phase = origin.phase; phase = origin.phase;
const node = this.graph.nodeMap[origin.nodeId]; const node = this.graph.nodeMap[origin.identifier()];
if (node && phase === this.phaseName) { if (phase === this.phaseName && node) {
origins.push(node); origins.push(node);
} else { } else {
selection.add(`${origin.nodeId}`); selection.add(origin.identifier());
} }
} }
} }
// Only go through phase reselection if we actually need // Only go through phase reselection if we actually need
// to display another phase. // to display another phase.
if (selection.size > 0 && phase !== this.phaseName) { if (selection.size > 0 && phase !== this.phaseName) {
this.hide();
this.showPhaseByName(phase, selection); this.showPhaseByName(phase, selection);
} else if (origins.length > 0) { } else if (origins.length > 0) {
this.nodesSelectionHandler.clear(); this.nodesSelectionHandler.clear();
......
...@@ -14,6 +14,7 @@ import { Edge } from "../edge"; ...@@ -14,6 +14,7 @@ import { Edge } from "../edge";
import { Node } from "../node"; import { Node } from "../node";
import { TurboshaftGraph } from "../turboshaft-graph"; import { TurboshaftGraph } from "../turboshaft-graph";
import { Graph } from "../graph"; import { Graph } from "../graph";
import { TurboshaftLayoutType } from "../phases/turboshaft-graph-phase/turboshaft-graph-phase";
export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> extends PhaseView { export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> extends PhaseView {
phaseName: string; phaseName: string;
...@@ -102,6 +103,7 @@ export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> ext ...@@ -102,6 +103,7 @@ export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> ext
} else { } else {
this.graph.graphPhase.transform = null; this.graph.graphPhase.transform = null;
} }
this.broker.deleteNodeHandler(this.nodesSelectionHandler);
super.hide(); super.hide();
this.deleteContent(); this.deleteContent();
} }
...@@ -270,6 +272,7 @@ export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> ext ...@@ -270,6 +272,7 @@ export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> ext
export class MovableViewState { export class MovableViewState {
public selection: SelectionMap; public selection: SelectionMap;
public blocksSelection: SelectionMap;
public get hideDead(): boolean { public get hideDead(): boolean {
return storageGetItem("toggle-hide-dead", false); return storageGetItem("toggle-hide-dead", false);
...@@ -302,4 +305,12 @@ export class MovableViewState { ...@@ -302,4 +305,12 @@ export class MovableViewState {
public set cacheLayout(value: boolean) { public set cacheLayout(value: boolean) {
storageSetItem("toggle-cache-layout", value); storageSetItem("toggle-cache-layout", value);
} }
public get turboshaftLayoutType() {
return storageGetItem("turboshaft-layout-type", TurboshaftLayoutType.Inline);
}
public set turboshaftLayoutType(layoutType: TurboshaftLayoutType) {
storageSetItem("turboshaft-layout-type", layoutType);
}
} }
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
"src/views/info-view.ts", "src/views/info-view.ts",
"src/views/movable-view.ts", "src/views/movable-view.ts",
"src/views/turboshaft-graph-view.ts", "src/views/turboshaft-graph-view.ts",
"src/layout-occupation.ts",
"src/origin.ts", "src/origin.ts",
"src/movable-container.ts", "src/movable-container.ts",
"src/turboshaft-graph.ts", "src/turboshaft-graph.ts",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment