[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/+/3747871 Reviewed-by: Tobias Tebbi <tebbi@chromium.org> Commit-Queue: Danylo Boiko <danielboyko02@gmail.com> Cr-Commit-Position: refs/heads/main@{#81810}
This commit is contained in:
parent
bc0ca547b9
commit
3118c60cb5
@ -1,3 +1,19 @@
|
||||
: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 {
|
||||
stroke-dasharray: 20;
|
||||
stroke-width: 7;
|
||||
@ -8,18 +24,24 @@ g.turboshaft-block:hover rect {
|
||||
stroke-width: 10;
|
||||
}
|
||||
|
||||
g.turboshaft-block.selected rect {
|
||||
stroke-dasharray: 0;
|
||||
stroke-width: 15;
|
||||
stroke: var(--select);
|
||||
}
|
||||
|
||||
g.block rect {
|
||||
fill: #ecf3fe;
|
||||
fill: #eef4fd;
|
||||
stroke: #4285f4;
|
||||
}
|
||||
|
||||
g.merge rect {
|
||||
fill: #e9fcee;
|
||||
fill: #ecfcf0;
|
||||
stroke: #2bde5a;
|
||||
}
|
||||
|
||||
g.loop rect {
|
||||
fill: #fdecea;
|
||||
fill: #fdf0ee;
|
||||
stroke: #e94235;
|
||||
}
|
||||
|
||||
@ -39,6 +61,32 @@ g.loop rect {
|
||||
fill: #ea4335;
|
||||
}
|
||||
|
||||
.inline-node-properties tspan {
|
||||
fill: #9227b0;
|
||||
.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 {
|
||||
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 @@
|
||||
export const MAX_RANK_SENTINEL = 0;
|
||||
export const BEZIER_CONSTANT = 0.3;
|
||||
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 NODE_INPUT_WIDTH = 50;
|
||||
export const MINIMUM_NODE_OUTPUT_APPROACH = 15;
|
||||
|
@ -27,12 +27,13 @@ export function camelize(obj: any): any {
|
||||
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;
|
||||
arr = arr.sort(f);
|
||||
arr = arr.sort(comparator);
|
||||
const uniqueArr = [arr[0]];
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
@ -2,25 +2,82 @@
|
||||
// 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 { GraphNode } from "./phases/graph-phase/graph-node";
|
||||
import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";
|
||||
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
|
||||
| TurboshaftGraphBlock> {
|
||||
target: NodeType;
|
||||
source: NodeType;
|
||||
index: number;
|
||||
backEdgeNumber: number;
|
||||
visible: boolean;
|
||||
|
||||
constructor(target: NodeType, source: NodeType) {
|
||||
constructor(target: NodeType, index: number, source: NodeType) {
|
||||
this.target = target;
|
||||
this.index = index;
|
||||
this.source = source;
|
||||
this.backEdgeNumber = 0;
|
||||
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 {
|
||||
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";
|
||||
import { GraphNode } from "./phases/graph-phase/graph-node";
|
||||
import { GraphEdge } from "./phases/graph-phase/graph-edge";
|
||||
import { GraphStateType } from "./phases/graph-phase/graph-phase";
|
||||
import { LayoutOccupation } from "./layout-occupation";
|
||||
|
||||
export class GraphLayout {
|
||||
graph: Graph;
|
||||
graphOccupation: GraphOccupation;
|
||||
layoutOccupation: LayoutOccupation;
|
||||
startTime: number;
|
||||
maxRank: number;
|
||||
visitOrderWithinRank: number;
|
||||
|
||||
constructor(graph: Graph) {
|
||||
this.graph = graph;
|
||||
this.graphOccupation = new GraphOccupation(graph);
|
||||
this.layoutOccupation = new LayoutOccupation(graph);
|
||||
this.maxRank = 0;
|
||||
this.visitOrderWithinRank = 0;
|
||||
}
|
||||
@ -194,7 +195,7 @@ export class GraphLayout {
|
||||
// compact and not overlapping live input lines.
|
||||
rankSets.reverse().forEach((rankSet: Array<GraphNode>) => {
|
||||
for (const node of rankSet) {
|
||||
this.graphOccupation.clearNodeOutputs(node, showTypes);
|
||||
this.layoutOccupation.clearOutputs(node, showTypes);
|
||||
}
|
||||
|
||||
this.traceOccupation("After clearing outputs");
|
||||
@ -203,7 +204,7 @@ export class GraphLayout {
|
||||
rankSet = rankSet.sort((a: GraphNode, b: GraphNode) => a.compare(b));
|
||||
for (const node of rankSet) {
|
||||
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()})`);
|
||||
const staggeredFlooredI = Math.floor(placedCount++ % 3);
|
||||
const delta = C.MINIMUM_EDGE_SEPARATION * staggeredFlooredI;
|
||||
@ -215,12 +216,12 @@ export class GraphLayout {
|
||||
|
||||
this.traceOccupation("Before clearing nodes");
|
||||
|
||||
this.graphOccupation.clearOccupiedNodes();
|
||||
this.layoutOccupation.clearOccupied();
|
||||
|
||||
this.traceOccupation("After clearing nodes");
|
||||
|
||||
for (const node of rankSet) {
|
||||
this.graphOccupation.occupyNodeInputs(node, showTypes);
|
||||
this.layoutOccupation.occupyInputs(node, showTypes);
|
||||
}
|
||||
|
||||
this.traceOccupation("After occupying inputs and determining bounding box");
|
||||
@ -247,219 +248,7 @@ export class GraphLayout {
|
||||
private traceOccupation(message: string): void {
|
||||
if (C.TRACE_LAYOUT) {
|
||||
console.log(message);
|
||||
this.graphOccupation.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);
|
||||
this.layoutOccupation.print();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,12 @@ import { MovableContainer } from "./movable-container";
|
||||
|
||||
export class Graph extends MovableContainer<GraphPhase> {
|
||||
nodeMap: Array<GraphNode>;
|
||||
maxBackEdgeNumber: number;
|
||||
originNodesMap: Map<string, Array<GraphNode>>;
|
||||
|
||||
constructor(graphPhase: GraphPhase) {
|
||||
super(graphPhase);
|
||||
this.nodeMap = graphPhase.nodeIdToNodeMap;
|
||||
this.originNodesMap = graphPhase.originIdToNodesMap;
|
||||
}
|
||||
|
||||
public *nodes(func = (n: GraphNode) => true) {
|
||||
|
221
tools/turbolizer/src/layout-occupation.ts
Normal file
221
tools/turbolizer/src/layout-occupation.ts
Normal file
@ -0,0 +1,221 @@
|
||||
// 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
|
||||
|
||||
export abstract class MovableContainer<GraphPhaseType extends GraphPhase | TurboshaftGraphPhase> {
|
||||
graphPhase: GraphPhaseType;
|
||||
maxBackEdgeNumber: number;
|
||||
minGraphX: number;
|
||||
maxGraphX: number;
|
||||
maxGraphNodeX: number;
|
||||
|
@ -16,6 +16,9 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge<Turb
|
||||
inputs: Array<EdgeType>;
|
||||
outputs: Array<EdgeType>;
|
||||
visible: boolean;
|
||||
outputApproach: number;
|
||||
visitOrderWithinRank: number;
|
||||
rank: number;
|
||||
x: number;
|
||||
y: number;
|
||||
labelBox: { width: number, height: number };
|
||||
@ -29,9 +32,12 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge<Turb
|
||||
this.inputs = new Array<EdgeType>();
|
||||
this.outputs = new Array<EdgeType>();
|
||||
this.visible = false;
|
||||
this.outputApproach = C.MINIMUM_NODE_OUTPUT_APPROACH;
|
||||
this.visitOrderWithinRank = 0;
|
||||
this.rank = C.MAX_RANK_SENTINEL;
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.labelBox = measureText(this.displayLabel);
|
||||
if (displayLabel) this.labelBox = measureText(this.displayLabel);
|
||||
}
|
||||
|
||||
public areAnyOutputsVisible(): OutputVisibilityType {
|
||||
@ -81,6 +87,25 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge<Turb
|
||||
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 {
|
||||
return `${this.id}`;
|
||||
}
|
||||
|
@ -2,75 +2,18 @@
|
||||
// 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 { Edge } from "../../edge";
|
||||
import { GraphNode } from "./graph-node";
|
||||
|
||||
export class GraphEdge extends Edge<GraphNode> {
|
||||
index: number;
|
||||
type: string;
|
||||
|
||||
constructor(target: GraphNode, index: number, source: GraphNode, type: string) {
|
||||
super(target, source);
|
||||
this.index = index;
|
||||
super(target, index, source);
|
||||
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 {
|
||||
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";
|
||||
|
||||
export class GraphNode extends Node<GraphEdge> {
|
||||
nodeLabel: NodeLabel;
|
||||
rank: number;
|
||||
outputApproach: number;
|
||||
cfg: boolean;
|
||||
width: number;
|
||||
normalHeight: number;
|
||||
visitOrderWithinRank: number;
|
||||
|
||||
constructor(nodeLabel: NodeLabel) {
|
||||
super(nodeLabel.id, nodeLabel.getDisplayLabel());
|
||||
this.nodeLabel = nodeLabel;
|
||||
this.rank = C.MAX_RANK_SENTINEL;
|
||||
this.outputApproach = C.MINIMUM_NODE_OUTPUT_APPROACH;
|
||||
// Every control node is a CFG node.
|
||||
this.cfg = nodeLabel.control;
|
||||
const typeBox = measureText(this.getDisplayType());
|
||||
@ -29,7 +24,6 @@ export class GraphNode extends Node<GraphEdge> {
|
||||
this.width = alignUp(innerWidth + C.NODE_INPUT_WIDTH * 2, C.NODE_INPUT_WIDTH);
|
||||
const innerHeight = Math.max(this.labelBox.height, typeBox.height);
|
||||
this.normalHeight = innerHeight + 20;
|
||||
this.visitOrderWithinRank = 0;
|
||||
}
|
||||
|
||||
public getHeight(showTypes: boolean): number {
|
||||
@ -109,29 +103,10 @@ export class GraphNode extends Node<GraphEdge> {
|
||||
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 {
|
||||
return (this.nodeLabel.opcode === "Loop") ||
|
||||
((this.nodeLabel.opcode === "Phi" || this.nodeLabel.opcode === "EffectPhi" ||
|
||||
this.nodeLabel.opcode === "InductionVariablePhi") &&
|
||||
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 {
|
||||
stateType: GraphStateType;
|
||||
nodeLabelMap: Array<NodeLabel>;
|
||||
nodeIdToNodeMap: Array<GraphNode>;
|
||||
originIdToNodesMap: Map<string, Array<GraphNode>>;
|
||||
rendered: boolean;
|
||||
transform: { x: number, y: number, scale: number };
|
||||
|
||||
@ -26,6 +27,7 @@ export class GraphPhase extends Phase {
|
||||
this.stateType = GraphStateType.NeedToFullRebuild;
|
||||
this.nodeLabelMap = nodeLabelMap ?? new Array<NodeLabel>();
|
||||
this.nodeIdToNodeMap = nodeIdToNodeMap ?? new Array<GraphNode>();
|
||||
this.originIdToNodesMap = new Map<string, Array<GraphNode>>();
|
||||
this.rendered = false;
|
||||
}
|
||||
|
||||
@ -42,10 +44,10 @@ export class GraphPhase extends Phase {
|
||||
const jsonOrigin = node.origin;
|
||||
if (jsonOrigin) {
|
||||
if (jsonOrigin.nodeId) {
|
||||
origin = new NodeOrigin(jsonOrigin.nodeId, jsonOrigin.reducer, jsonOrigin.phase);
|
||||
origin = new NodeOrigin(jsonOrigin.nodeId, jsonOrigin.phase, jsonOrigin.reducer);
|
||||
} else {
|
||||
origin = new BytecodeOrigin(jsonOrigin.bytecodePosition, jsonOrigin.reducer,
|
||||
jsonOrigin.phase);
|
||||
origin = new BytecodeOrigin(jsonOrigin.bytecodePosition, jsonOrigin.phase,
|
||||
jsonOrigin.reducer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,7 +70,14 @@ export class GraphPhase extends Phase {
|
||||
}
|
||||
const newNode = new GraphNode(label);
|
||||
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;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// 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 { TurboshaftGraphNode } from "./turboshaft-graph-node";
|
||||
import { Node } from "../../node";
|
||||
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
|
||||
@ -11,6 +12,9 @@ export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGra
|
||||
deferred: boolean;
|
||||
predecessors: Array<string>;
|
||||
nodes: Array<TurboshaftGraphNode>;
|
||||
showProperties: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
constructor(id: number, type: TurboshaftGraphBlockType, deferred: boolean,
|
||||
predecessors: Array<string>) {
|
||||
@ -23,14 +27,29 @@ export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGra
|
||||
}
|
||||
|
||||
public getHeight(showProperties: boolean): number {
|
||||
return this.nodes.reduce<number>((accumulator: number, node: TurboshaftGraphNode) => {
|
||||
return accumulator + node.getHeight(showProperties);
|
||||
}, this.labelBox.height);
|
||||
if (this.showProperties != showProperties) {
|
||||
this.height = this.nodes.reduce<number>((accumulator: number, node: TurboshaftGraphNode) => {
|
||||
return accumulator + node.getHeight(showProperties);
|
||||
}, this.labelBox.height);
|
||||
this.showProperties = showProperties;
|
||||
}
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public getWidth(): number {
|
||||
const maxWidth = Math.max(...this.nodes.map((node: TurboshaftGraphNode) => node.getWidth()));
|
||||
return Math.max(maxWidth, this.labelBox.width) + 50;
|
||||
if (!this.width) {
|
||||
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 {
|
||||
|
@ -9,13 +9,15 @@ import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
|
||||
export class TurboshaftGraphEdge<Type extends TurboshaftGraphNode | TurboshaftGraphBlock> extends
|
||||
Edge<Type> {
|
||||
|
||||
constructor(target: Type, source: Type) {
|
||||
super(target, source);
|
||||
constructor(target: Type, index: number, source: Type) {
|
||||
super(target, index, source);
|
||||
this.visible = target.visible && source.visible;
|
||||
}
|
||||
|
||||
public toString(idx?: number): string {
|
||||
if (idx !== null) return `${this.source.id},${idx},${this.target.id}`;
|
||||
return `${this.source.id},${this.target.id}`;
|
||||
public isBackEdge(): boolean {
|
||||
if (this.target instanceof TurboshaftGraphBlock) {
|
||||
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
|
||||
block: TurboshaftGraphBlock;
|
||||
opPropertiesType: OpPropertiesType;
|
||||
properties: string;
|
||||
propertiesBox: { width: number, height: number };
|
||||
|
||||
constructor(id: number, title: string, block: TurboshaftGraphBlock,
|
||||
opPropertiesType: OpPropertiesType, properties: string) {
|
||||
super(id, `${id} ${title}`);
|
||||
super(id);
|
||||
this.title = title;
|
||||
this.block = block;
|
||||
this.opPropertiesType = opPropertiesType;
|
||||
this.properties = properties;
|
||||
this.propertiesBox = measureText(this.properties);
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
public getHeight(showProperties: boolean): number {
|
||||
if (this.properties && showProperties) {
|
||||
return this.labelBox.height * 2;
|
||||
return this.labelBox.height + this.propertiesBox.height;
|
||||
}
|
||||
return this.labelBox.height;
|
||||
}
|
||||
|
||||
public getWidth(): number {
|
||||
const measure = measureText(
|
||||
`${this.getInlineLabel()}[${this.getPropertiesTypeAbbreviation()}]`
|
||||
);
|
||||
return Math.max(this.inputs.length * C.NODE_INPUT_WIDTH, measure.width);
|
||||
return Math.max(this.inputs.length * C.NODE_INPUT_WIDTH, this.labelBox.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 {
|
||||
@ -44,29 +60,11 @@ export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGrap
|
||||
}
|
||||
|
||||
public getReadableProperties(blockWidth: number): string {
|
||||
const propertiesWidth = measureText(this.properties).width;
|
||||
if (blockWidth > propertiesWidth) return this.properties;
|
||||
const widthOfOneSymbol = Math.floor(propertiesWidth / this.properties.length);
|
||||
if (blockWidth > this.propertiesBox.width) return this.properties;
|
||||
const widthOfOneSymbol = Math.floor(this.propertiesBox.width / this.properties.length);
|
||||
const lengthOfReadableProperties = Math.floor(blockWidth / widthOfOneSymbol);
|
||||
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 {
|
||||
|
@ -8,7 +8,7 @@ import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
|
||||
import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
|
||||
|
||||
export class TurboshaftGraphPhase extends Phase {
|
||||
highestBlockId: number;
|
||||
highestBlockId: number; // TODO (danylo boiko) Delete this field
|
||||
data: TurboshaftGraphData;
|
||||
stateType: GraphStateType;
|
||||
layoutType: TurboshaftLayoutType;
|
||||
@ -24,7 +24,6 @@ export class TurboshaftGraphPhase extends Phase {
|
||||
this.highestBlockId = highestBlockId;
|
||||
this.data = data ?? new TurboshaftGraphData();
|
||||
this.stateType = GraphStateType.NeedToFullRebuild;
|
||||
this.layoutType = TurboshaftLayoutType.Inline;
|
||||
this.nodeIdToNodeMap = nodeIdToNodeMap ?? new Array<TurboshaftGraphNode>();
|
||||
this.blockIdToBlockMap = blockIdToBlockMap ?? new Array<TurboshaftGraphBlock>();
|
||||
this.rendered = false;
|
||||
@ -42,12 +41,12 @@ export class TurboshaftGraphPhase extends Phase {
|
||||
const block = new TurboshaftGraphBlock(blockJson.id, blockJson.type,
|
||||
blockJson.deferred, blockJson.predecessors);
|
||||
this.data.blocks.push(block);
|
||||
this.blockIdToBlockMap[block.id] = block;
|
||||
this.blockIdToBlockMap[block.identifier()] = block;
|
||||
}
|
||||
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 edge = new TurboshaftGraphEdge(block, source);
|
||||
const edge = new TurboshaftGraphEdge(block, idx, source);
|
||||
block.inputs.push(edge);
|
||||
source.outputs.push(edge);
|
||||
}
|
||||
@ -61,7 +60,7 @@ export class TurboshaftGraphPhase extends Phase {
|
||||
block, nodeJson.op_properties_type, nodeJson.properties);
|
||||
block.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 {
|
||||
for (const edgeJson of edgesJson) {
|
||||
const target = this.nodeIdToNodeMap[edgeJson.target];
|
||||
const source = this.nodeIdToNodeMap[edgeJson.source];
|
||||
const edge = new TurboshaftGraphEdge(target, source);
|
||||
const edge = new TurboshaftGraphEdge(target, -1, source);
|
||||
this.data.edges.push(edge);
|
||||
target.inputs.push(edge);
|
||||
source.outputs.push(edge);
|
||||
}
|
||||
for (const node of this.data.nodes) {
|
||||
node.initDisplayLabel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,11 +34,21 @@ export class SelectionBroker {
|
||||
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 {
|
||||
this.allHandlers.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 {
|
||||
this.allHandlers.push(handler);
|
||||
this.instructionHandlers.push(handler);
|
||||
|
@ -46,6 +46,7 @@ export class SourceResolver {
|
||||
this.phases = new Array<GenericPhase>();
|
||||
// Maps phase names to phaseIds.
|
||||
this.phaseNames = new Map<string, number>();
|
||||
this.instructionsPhase = new InstructionsPhase("");
|
||||
// The disassembly phase is stored separately.
|
||||
this.disassemblyPhase = undefined;
|
||||
// Maps line numbers to source positions
|
||||
@ -155,22 +156,19 @@ export class SourceResolver {
|
||||
break;
|
||||
case PhaseType.Instructions:
|
||||
const castedInstructions = genericPhase as InstructionsPhase;
|
||||
let instructionsPhase: InstructionsPhase = null;
|
||||
if (this.instructionsPhase) {
|
||||
instructionsPhase = this.instructionsPhase;
|
||||
instructionsPhase.name += `, ${castedInstructions.name}`;
|
||||
if (this.instructionsPhase.name === "") {
|
||||
this.instructionsPhase.name = castedInstructions.name;
|
||||
} else {
|
||||
instructionsPhase = new InstructionsPhase(castedInstructions.name);
|
||||
this.instructionsPhase.name += `, ${castedInstructions.name}`;
|
||||
}
|
||||
instructionsPhase.parseNodeIdToInstructionRangeFromJSON(castedInstructions
|
||||
this.instructionsPhase.parseNodeIdToInstructionRangeFromJSON(castedInstructions
|
||||
?.nodeIdToInstructionRange);
|
||||
instructionsPhase.parseBlockIdToInstructionRangeFromJSON(castedInstructions
|
||||
this.instructionsPhase.parseBlockIdToInstructionRangeFromJSON(castedInstructions
|
||||
?.blockIdToInstructionRange);
|
||||
instructionsPhase.parseInstructionOffsetToPCOffsetFromJSON(castedInstructions
|
||||
this.instructionsPhase.parseInstructionOffsetToPCOffsetFromJSON(castedInstructions
|
||||
?.instructionOffsetToPCOffset);
|
||||
instructionsPhase.parseCodeOffsetsInfoFromJSON(castedInstructions
|
||||
this.instructionsPhase.parseCodeOffsetsInfoFromJSON(castedInstructions
|
||||
?.codeOffsetsInfo);
|
||||
this.instructionsPhase = instructionsPhase;
|
||||
break;
|
||||
case PhaseType.Graph:
|
||||
const castedGraph = genericPhase as GraphPhase;
|
||||
|
@ -1,40 +1,251 @@
|
||||
// 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 { 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 {
|
||||
graph: TurboshaftGraph;
|
||||
layoutOccupation: LayoutOccupation;
|
||||
startTime: number;
|
||||
maxRank: number;
|
||||
visitOrderWithinRank: number;
|
||||
|
||||
constructor(graph: TurboshaftGraph) {
|
||||
this.graph = graph;
|
||||
this.layoutOccupation = new LayoutOccupation(graph);
|
||||
this.maxRank = 0;
|
||||
this.visitOrderWithinRank = 0;
|
||||
}
|
||||
|
||||
public rebuild(showProperties: boolean): void {
|
||||
if (this.graph.graphPhase.stateType == GraphStateType.NeedToFullRebuild) {
|
||||
switch (this.graph.graphPhase.layoutType) {
|
||||
case TurboshaftLayoutType.Inline:
|
||||
this.inlineRebuild(showProperties);
|
||||
break;
|
||||
default:
|
||||
throw "Unsupported graph layout type";
|
||||
}
|
||||
this.graph.graphPhase.stateType = GraphStateType.Cached;
|
||||
switch (this.graph.graphPhase.stateType) {
|
||||
case GraphStateType.NeedToFullRebuild:
|
||||
this.fullRebuild(showProperties);
|
||||
break;
|
||||
case GraphStateType.Cached:
|
||||
this.cachedRebuild();
|
||||
break;
|
||||
default:
|
||||
throw "Unsupported graph state type";
|
||||
}
|
||||
this.graph.graphPhase.rendered = true;
|
||||
}
|
||||
|
||||
private inlineRebuild(showProperties): void {
|
||||
// Useless logic to simulate blocks coordinates (will be replaced in the future)
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
for (const block of this.graph.blockMap) {
|
||||
block.x = x;
|
||||
block.y = y;
|
||||
y += block.getHeight(showProperties) + 50;
|
||||
if (y > 1800) {
|
||||
x += block.getWidth() * 1.5;
|
||||
y = 0;
|
||||
private fullRebuild(showProperties: boolean): void {
|
||||
this.startTime = performance.now();
|
||||
this.maxRank = 0;
|
||||
this.visitOrderWithinRank = 0;
|
||||
|
||||
const blocks = this.initBlocks();
|
||||
this.initWorkList(blocks);
|
||||
|
||||
let visited = new Array<boolean>();
|
||||
blocks.forEach((block: TurboshaftGraphBlock) => this.dfsFindRankLate(visited, block));
|
||||
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> {
|
||||
for (const block of this.blockMap) {
|
||||
if (!block) continue;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -61,7 +71,7 @@ export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> {
|
||||
+ 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.height = this.maxGraphY - this.minGraphY;
|
||||
|
@ -3,6 +3,12 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
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 {
|
||||
prettyPrint(_: unknown, el: HTMLElement): void;
|
||||
@ -12,13 +18,6 @@ declare global {
|
||||
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 {
|
||||
MAIN_SOURCE = "main function",
|
||||
INLINED_SOURCE = "inlined function"
|
||||
@ -81,7 +80,7 @@ export class CodeView extends View {
|
||||
view.updateSelection();
|
||||
},
|
||||
};
|
||||
view.selection = new SelectionMap((sp: GenericPosition) => sp.toString());
|
||||
view.selection = new SelectionMap((gp: GenericPosition) => gp.toString());
|
||||
broker.addSourcePositionHandler(selectionHandler);
|
||||
this.selectionHandler = selectionHandler;
|
||||
this.initializeCode();
|
||||
@ -280,5 +279,4 @@ export class CodeView extends View {
|
||||
view.addHtmlElementToSourcePosition(sourcePosition, lineElement);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import { GraphEdge } from "../phases/graph-phase/graph-edge";
|
||||
import { GraphLayout } from "../graph-layout";
|
||||
import { GraphData, GraphPhase, GraphStateType } from "../phases/graph-phase/graph-phase";
|
||||
import { BytecodePosition } from "../position";
|
||||
import { BytecodeOrigin } from "../origin";
|
||||
import { BytecodeOrigin, NodeOrigin } from "../origin";
|
||||
import { MovableView } from "./movable-view";
|
||||
import { ClearableHandler, NodeSelectionHandler } from "../selection/selection-handler";
|
||||
import { GenericPosition } from "../source-resolver";
|
||||
@ -69,10 +69,8 @@ export class GraphView extends MovableView<Graph> {
|
||||
this.addToggleImgInput("toggle-cache-layout", "toggle saving graph layout",
|
||||
this.state.cacheLayout, partial(this.toggleLayoutCachingAction, this));
|
||||
|
||||
const adaptedSelection = this.adaptSelectionToCurrentPhase(data.data, rememberedSelection);
|
||||
|
||||
this.phaseName = data.name;
|
||||
this.createGraph(data, adaptedSelection);
|
||||
const adaptedSelection = this.createGraph(data, rememberedSelection);
|
||||
this.broker.addNodeHandler(this.nodesSelectionHandler);
|
||||
|
||||
const selectedNodes = adaptedSelection?.size > 0
|
||||
@ -162,8 +160,8 @@ export class GraphView extends MovableView<Graph> {
|
||||
const adjOutputEdges = visibleEdges.filter(edge => edge.source === node);
|
||||
adjInputEdges.attr("relToHover", "input");
|
||||
adjOutputEdges.attr("relToHover", "output");
|
||||
const adjInputNodes = adjInputEdges.data().map(edge => edge.source);
|
||||
const visibleNodes = view.visibleNodes.selectAll<SVGGElement, GraphNode>("g");
|
||||
const adjInputNodes = adjInputEdges.data().map(edge => edge.source);
|
||||
visibleNodes.data<GraphNode>(adjInputNodes, node => node.toString())
|
||||
.attr("relToHover", "input");
|
||||
const adjOutputNodes = adjOutputEdges.data().map(edge => edge.target);
|
||||
@ -238,7 +236,7 @@ export class GraphView extends MovableView<Graph> {
|
||||
view.updateInputAndOutputBubbles();
|
||||
|
||||
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 {
|
||||
@ -365,11 +363,6 @@ export class GraphView extends MovableView<Graph> {
|
||||
private initializeNodesSelectionHandler(): NodeSelectionHandler & ClearableHandler {
|
||||
const view = this;
|
||||
return {
|
||||
clear: function () {
|
||||
view.state.selection.clear();
|
||||
view.broker.broadcastClear(this);
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
select: function (selectedNodes: Array<GraphNode>, selected: boolean) {
|
||||
const locations = new Array<GenericPosition>();
|
||||
for (const node of selectedNodes) {
|
||||
@ -384,22 +377,27 @@ export class GraphView extends MovableView<Graph> {
|
||||
view.broker.broadcastSourcePositionSelect(this, locations, selected);
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
clear: function () {
|
||||
view.state.selection.clear();
|
||||
view.broker.broadcastClear(this);
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
brokeredNodeSelect: function (locations, selected: boolean) {
|
||||
if (!view.graph) return;
|
||||
const selection = view.graph.nodes(node =>
|
||||
locations.has(node.identifier()) && (!view.state.hideDead || node.isLive()));
|
||||
view.state.selection.select(selection, selected);
|
||||
// Update edge visibility based on selection.
|
||||
for (const node of view.graph.nodes()) {
|
||||
if (view.state.selection.isSelected(node)) {
|
||||
node.visible = true;
|
||||
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);
|
||||
});
|
||||
}
|
||||
for (const item of view.state.selection.selectedKeys()) {
|
||||
const node = view.graph.nodeMap[item];
|
||||
if (!node) continue;
|
||||
node.visible = true;
|
||||
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);
|
||||
});
|
||||
}
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
@ -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.graphLayout = new GraphLayout(this.graph);
|
||||
|
||||
@ -422,8 +420,10 @@ export class GraphView extends MovableView<Graph> {
|
||||
this.showVisible();
|
||||
}
|
||||
|
||||
if (selection !== undefined) {
|
||||
for (const item of selection) {
|
||||
const adaptedSelection = this.adaptSelectionToCurrentPhase(data.data, rememberedSelection);
|
||||
|
||||
if (adaptedSelection !== undefined) {
|
||||
for (const item of adaptedSelection) {
|
||||
if (this.graph.nodeMap[item]) this.graph.nodeMap[item].visible = true;
|
||||
}
|
||||
}
|
||||
@ -432,6 +432,7 @@ export class GraphView extends MovableView<Graph> {
|
||||
|
||||
this.layoutGraph();
|
||||
this.updateGraphVisibility();
|
||||
return adaptedSelection;
|
||||
}
|
||||
|
||||
private layoutGraph(): void {
|
||||
@ -530,31 +531,28 @@ export class GraphView extends MovableView<Graph> {
|
||||
|
||||
private adaptSelectionToCurrentPhase(data: GraphData, selection: Map<string, GraphNode>):
|
||||
Set<string> {
|
||||
// TODO (danylo boiko) Speed up adapting
|
||||
const updatedGraphSelection = new Set<string>();
|
||||
if (!data || !(selection instanceof Map)) return updatedGraphSelection;
|
||||
// Adding survived nodes (with the same id)
|
||||
for (const node of data.nodes) {
|
||||
const stringKey = this.state.selection.stringKey(node);
|
||||
if (selection.has(stringKey)) {
|
||||
updatedGraphSelection.add(stringKey);
|
||||
const updatedSelection = new Set<string>();
|
||||
if (!data || !(selection instanceof Map)) return updatedSelection;
|
||||
for (const [key, node] of selection.entries()) {
|
||||
// Adding survived nodes (with the same id)
|
||||
const survivedNode = this.graph.nodeMap[key];
|
||||
if (survivedNode) {
|
||||
updatedSelection.add(this.state.selection.stringKey(survivedNode));
|
||||
}
|
||||
}
|
||||
// Adding children of nodes
|
||||
for (const node of data.nodes) {
|
||||
// Adding children of nodes
|
||||
const childNodes = this.graph.originNodesMap.get(key);
|
||||
if (childNodes?.length > 0) {
|
||||
for (const childNode of childNodes) {
|
||||
updatedSelection.add(this.state.selection.stringKey(childNode));
|
||||
}
|
||||
}
|
||||
// Adding ancestors of nodes
|
||||
const originStringKey = this.state.selection.originStringKey(node);
|
||||
if (originStringKey && selection.has(originStringKey)) {
|
||||
updatedGraphSelection.add(this.state.selection.stringKey(node));
|
||||
if (originStringKey) {
|
||||
updatedSelection.add(originStringKey);
|
||||
}
|
||||
}
|
||||
// Adding ancestors of nodes
|
||||
selection.forEach(selectedNode => {
|
||||
const originStringKey = this.state.selection.originStringKey(selectedNode);
|
||||
if (originStringKey) {
|
||||
updatedGraphSelection.add(originStringKey);
|
||||
}
|
||||
});
|
||||
return updatedGraphSelection;
|
||||
return updatedSelection;
|
||||
}
|
||||
|
||||
private attachSelection(selection: Set<string>): Array<GraphNode> {
|
||||
@ -703,27 +701,26 @@ export class GraphView extends MovableView<Graph> {
|
||||
this.updateGraphVisibility();
|
||||
}
|
||||
|
||||
private selectOrigins() {
|
||||
const state = this.state;
|
||||
// TODO (danylo boiko) Add array type
|
||||
const origins = [];
|
||||
private selectOrigins(): void {
|
||||
const origins = new Array<GraphNode>();
|
||||
let phase = this.phaseName;
|
||||
const selection = new Set<any>();
|
||||
for (const node of state.selection) {
|
||||
const selection = new Set<string>();
|
||||
for (const node of this.state.selection) {
|
||||
const origin = node.nodeLabel.origin;
|
||||
if (origin) {
|
||||
if (origin && origin instanceof NodeOrigin) {
|
||||
phase = origin.phase;
|
||||
const node = this.graph.nodeMap[origin.nodeId];
|
||||
if (node && phase === this.phaseName) {
|
||||
const node = this.graph.nodeMap[origin.identifier()];
|
||||
if (phase === this.phaseName && node) {
|
||||
origins.push(node);
|
||||
} else {
|
||||
selection.add(`${origin.nodeId}`);
|
||||
selection.add(origin.identifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only go through phase reselection if we actually need
|
||||
// to display another phase.
|
||||
if (selection.size > 0 && phase !== this.phaseName) {
|
||||
this.hide();
|
||||
this.showPhaseByName(phase, selection);
|
||||
} else if (origins.length > 0) {
|
||||
this.nodesSelectionHandler.clear();
|
||||
|
@ -14,6 +14,7 @@ import { Edge } from "../edge";
|
||||
import { Node } from "../node";
|
||||
import { TurboshaftGraph } from "../turboshaft-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 {
|
||||
phaseName: string;
|
||||
@ -102,6 +103,7 @@ export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> ext
|
||||
} else {
|
||||
this.graph.graphPhase.transform = null;
|
||||
}
|
||||
this.broker.deleteNodeHandler(this.nodesSelectionHandler);
|
||||
super.hide();
|
||||
this.deleteContent();
|
||||
}
|
||||
@ -270,6 +272,7 @@ export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> ext
|
||||
|
||||
export class MovableViewState {
|
||||
public selection: SelectionMap;
|
||||
public blocksSelection: SelectionMap;
|
||||
|
||||
public get hideDead(): boolean {
|
||||
return storageGetItem("toggle-hide-dead", false);
|
||||
@ -302,4 +305,12 @@ export class MovableViewState {
|
||||
public set cacheLayout(value: boolean) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,31 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import * as d3 from "d3";
|
||||
import * as C from "../common/constants";
|
||||
import * as d3 from "d3";
|
||||
import { partial } from "../common/util";
|
||||
import { MovableView } from "./movable-view";
|
||||
import { TurboshaftGraphPhase } from "../phases/turboshaft-graph-phase/turboshaft-graph-phase";
|
||||
import { SelectionBroker } from "../selection/selection-broker";
|
||||
import { SelectionMap } from "../selection/selection";
|
||||
import { TurboshaftGraphBlock, TurboshaftGraphBlockType } from "../phases/turboshaft-graph-phase/turboshaft-graph-block";
|
||||
import { TurboshaftGraphNode } from "../phases/turboshaft-graph-phase/turboshaft-graph-node";
|
||||
import { TurboshaftGraphEdge } from "../phases/turboshaft-graph-phase/turboshaft-graph-edge";
|
||||
import { TurboshaftGraph } from "../turboshaft-graph";
|
||||
import { TurboshaftGraphLayout } from "../turboshaft-graph-layout";
|
||||
import { GraphStateType } from "../phases/graph-phase/graph-phase";
|
||||
import { OutputVisibilityType } from "../node";
|
||||
import { BlockSelectionHandler, ClearableHandler } from "../selection/selection-handler";
|
||||
import {
|
||||
BlockSelectionHandler,
|
||||
ClearableHandler,
|
||||
NodeSelectionHandler
|
||||
} from "../selection/selection-handler";
|
||||
import {
|
||||
TurboshaftGraphPhase,
|
||||
TurboshaftLayoutType
|
||||
} from "../phases/turboshaft-graph-phase/turboshaft-graph-phase";
|
||||
import {
|
||||
TurboshaftGraphBlock,
|
||||
TurboshaftGraphBlockType
|
||||
} from "../phases/turboshaft-graph-phase/turboshaft-graph-block";
|
||||
|
||||
export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
graphLayout: TurboshaftGraphLayout;
|
||||
@ -31,17 +41,26 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
showPhaseByName: (name: string) => void, toolbox: HTMLElement) {
|
||||
super(idOrContainer, broker, showPhaseByName, toolbox);
|
||||
|
||||
this.state.selection = new SelectionMap((b: TurboshaftGraphBlock) => b.identifier());
|
||||
this.state.selection = new SelectionMap(node => node.identifier());
|
||||
this.state.blocksSelection = new SelectionMap(blockId => String(blockId));
|
||||
|
||||
this.nodesSelectionHandler = this.initializeNodesSelectionHandler();
|
||||
this.blocksSelectionHandler = this.initializeBlocksSelectionHandler();
|
||||
|
||||
this.svg.on("click", () => {
|
||||
this.nodesSelectionHandler.clear();
|
||||
this.blocksSelectionHandler.clear();
|
||||
});
|
||||
|
||||
this.visibleBlocks = this.graphElement.append("g");
|
||||
this.visibleEdges = this.graphElement.append("g");
|
||||
this.visibleBlocks = this.graphElement.append("g");
|
||||
this.visibleNodes = this.graphElement.append("g");
|
||||
|
||||
this.blockDrag = d3.drag<any, TurboshaftGraphBlock, TurboshaftGraphBlock>()
|
||||
.on("drag", (block: TurboshaftGraphBlock) => {
|
||||
block.x += d3.event.dx;
|
||||
block.y += d3.event.dy;
|
||||
this.updateVisibleBlocks();
|
||||
this.updateBlockLocation(block);
|
||||
});
|
||||
}
|
||||
|
||||
@ -52,13 +71,16 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
partial(this.layoutAction, this));
|
||||
this.addImgInput("show-all", "show all blocks",
|
||||
partial(this.showAllBlocksAction, this));
|
||||
this.addToggleImgInput("toggle-properties", "toggle propetries",
|
||||
this.addToggleImgInput("toggle-properties", "toggle properties",
|
||||
this.state.showProperties, partial(this.togglePropertiesAction, this));
|
||||
this.addToggleImgInput("toggle-cache-layout", "toggle saving graph layout",
|
||||
this.state.cacheLayout, partial(this.toggleLayoutCachingAction, this));
|
||||
this.addLayoutTypeSelect();
|
||||
|
||||
this.phaseName = data.name;
|
||||
this.createGraph(data, rememberedSelection);
|
||||
this.broker.addNodeHandler(this.nodesSelectionHandler);
|
||||
this.broker.addBlockHandler(this.blocksSelectionHandler);
|
||||
|
||||
this.viewWholeGraph();
|
||||
if (this.state.cacheLayout && data.transform) {
|
||||
@ -70,8 +92,8 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
|
||||
public updateGraphVisibility(): void {
|
||||
if (!this.graph) return;
|
||||
this.updateVisibleBlocks();
|
||||
this.visibleNodes = d3.selectAll(".turboshaft-node");
|
||||
this.updateVisibleBlocksAndEdges();
|
||||
this.visibleNodes = this.visibleBlocks.selectAll(".turboshaft-node");
|
||||
this.visibleBubbles = d3.selectAll("circle");
|
||||
this.updateInlineNodes();
|
||||
this.updateInputAndOutputBubbles();
|
||||
@ -83,6 +105,78 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
public searchInputAction(searchInput: HTMLInputElement, e: Event, onlyVisible: boolean): void {
|
||||
}
|
||||
|
||||
public hide() {
|
||||
this.broker.deleteBlockHandler(this.blocksSelectionHandler);
|
||||
super.hide();
|
||||
}
|
||||
|
||||
private initializeNodesSelectionHandler(): NodeSelectionHandler & ClearableHandler {
|
||||
const view = this;
|
||||
return {
|
||||
select: function (selectedNodes: Array<TurboshaftGraphNode>, selected: boolean) {
|
||||
view.state.selection.select(selectedNodes, selected);
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
clear: function () {
|
||||
view.state.selection.clear();
|
||||
view.broker.broadcastClear(this);
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
brokeredNodeSelect: function (nodeIds: Set<string>, selected: boolean) {
|
||||
const selection = view.graph.nodes(node => nodeIds.has(node.identifier()));
|
||||
view.state.selection.select(selection, selected);
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
brokeredClear: function () {
|
||||
view.state.selection.clear();
|
||||
view.updateGraphVisibility();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private initializeBlocksSelectionHandler(): BlockSelectionHandler & ClearableHandler {
|
||||
const view = this;
|
||||
return {
|
||||
select: function (selectedBlocks: Array<TurboshaftGraphBlock>, selected: boolean) {
|
||||
view.state.blocksSelection.select(selectedBlocks, selected);
|
||||
view.broker.broadcastBlockSelect(this, selectedBlocks, selected);
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
clear: function () {
|
||||
view.state.blocksSelection.clear();
|
||||
view.broker.broadcastClear(this);
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
brokeredBlockSelect: function (blockIds: Array<string>, selected: boolean) {
|
||||
view.state.blocksSelection.select(blockIds, selected);
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
brokeredClear: function () {
|
||||
view.state.blocksSelection.clear();
|
||||
view.updateGraphVisibility();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private addLayoutTypeSelect(): void {
|
||||
const view = this;
|
||||
const select = document.createElement("select") as HTMLSelectElement;
|
||||
select.id = "layout-type-select";
|
||||
select.className = "graph-toolbox-item";
|
||||
const keys = Object.keys(TurboshaftLayoutType).filter(t => isNaN(Number(t)));
|
||||
for (const key of keys) {
|
||||
const option = document.createElement("option");
|
||||
option.text = key;
|
||||
select.add(option);
|
||||
}
|
||||
select.selectedIndex = this.state.turboshaftLayoutType;
|
||||
select.onchange = function (this: HTMLSelectElement) {
|
||||
view.state.turboshaftLayoutType = this.selectedIndex as TurboshaftLayoutType;
|
||||
view.layoutAction(view);
|
||||
};
|
||||
this.toolbox.appendChild(select);
|
||||
}
|
||||
|
||||
private createGraph(data: TurboshaftGraphPhase, selection) {
|
||||
this.graph = new TurboshaftGraph(data);
|
||||
this.graphLayout = new TurboshaftGraphLayout(this.graph);
|
||||
@ -101,10 +195,11 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
|
||||
private layoutGraph(): void {
|
||||
const layoutMessage = this.graph.graphPhase.stateType == GraphStateType.Cached
|
||||
? "Layout graph from cache"
|
||||
: "Layout graph";
|
||||
? "Layout turboshaft graph from cache"
|
||||
: "Layout turboshaft graph";
|
||||
|
||||
console.time(layoutMessage);
|
||||
this.graph.graphPhase.layoutType = this.state.turboshaftLayoutType;
|
||||
this.graphLayout.rebuild(this.state.showProperties);
|
||||
const extent = this.graph.redetermineGraphBoundingBox(this.state.showProperties);
|
||||
this.panZoom.translateExtent(extent);
|
||||
@ -112,8 +207,56 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
console.timeEnd(layoutMessage);
|
||||
}
|
||||
|
||||
private updateVisibleBlocks(): void {
|
||||
private updateBlockLocation(block: TurboshaftGraphBlock): void {
|
||||
this.visibleBlocks
|
||||
.selectAll<SVGGElement, TurboshaftGraphBlock>(".turboshaft-block")
|
||||
.filter(b => b == block)
|
||||
.attr("transform", block => `translate(${block.x},${block.y})`);
|
||||
|
||||
this.visibleEdges
|
||||
.selectAll<SVGPathElement, TurboshaftGraphEdge<TurboshaftGraphBlock>>("path")
|
||||
.filter(edge => edge.target === block || edge.source === block)
|
||||
.attr("d", edge => edge.generatePath(this.graph, this.state.showProperties));
|
||||
}
|
||||
|
||||
private updateVisibleBlocksAndEdges(): void {
|
||||
const view = this;
|
||||
|
||||
// select existing edges
|
||||
const filteredEdges = [
|
||||
...this.graph.blocksEdges(edge => this.graph.isRendered()
|
||||
&& edge.source.visible && edge.target.visible)
|
||||
];
|
||||
|
||||
const selEdges = view.visibleEdges
|
||||
.selectAll<SVGPathElement, TurboshaftGraphEdge<TurboshaftGraphBlock>>("path")
|
||||
.data(filteredEdges, edge => edge.toString());
|
||||
|
||||
// remove old edges
|
||||
selEdges.exit().remove();
|
||||
|
||||
// add new edges
|
||||
const newEdges = selEdges
|
||||
.enter()
|
||||
.append("path")
|
||||
.style("marker-end", "url(#end-arrow)")
|
||||
.attr("id", edge => `e,${edge.toString()}`)
|
||||
.on("click", edge => {
|
||||
d3.event.stopPropagation();
|
||||
if (!d3.event.shiftKey) {
|
||||
view.blocksSelectionHandler.clear();
|
||||
}
|
||||
view.blocksSelectionHandler.select(
|
||||
[edge.source.identifier(), edge.target.identifier()],
|
||||
true
|
||||
);
|
||||
})
|
||||
.attr("adjacentToHover", "false");
|
||||
|
||||
const newAndOldEdges = newEdges.merge(selEdges);
|
||||
|
||||
newAndOldEdges.classed("hidden", edge => !edge.isVisible());
|
||||
|
||||
// select existing blocks
|
||||
const filteredBlocks = [
|
||||
...this.graph.blocks(block => this.graph.isRendered() && block.visible)
|
||||
@ -133,12 +276,34 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
.classed("block", b => b.type == TurboshaftGraphBlockType.Block)
|
||||
.classed("merge", b => b.type == TurboshaftGraphBlockType.Merge)
|
||||
.classed("loop", b => b.type == TurboshaftGraphBlockType.Loop)
|
||||
.on("mouseenter", (block: TurboshaftGraphBlock) => {
|
||||
const visibleEdges = view.visibleEdges
|
||||
.selectAll<SVGPathElement, TurboshaftGraphEdge<TurboshaftGraphBlock>>("path");
|
||||
const adjInputEdges = visibleEdges.filter(edge => edge.target === block);
|
||||
const adjOutputEdges = visibleEdges.filter(edge => edge.source === block);
|
||||
adjInputEdges.classed("input", true);
|
||||
adjOutputEdges.classed("output", true);
|
||||
view.updateGraphVisibility();
|
||||
})
|
||||
.on("mouseleave", (block: TurboshaftGraphBlock) => {
|
||||
const visibleEdges = view.visibleEdges
|
||||
.selectAll<SVGPathElement, TurboshaftGraphEdge<TurboshaftGraphBlock>>("path");
|
||||
const adjEdges = visibleEdges
|
||||
.filter(edge => edge.target === block || edge.source === block);
|
||||
adjEdges.classed("input output", false);
|
||||
view.updateGraphVisibility();
|
||||
})
|
||||
.on("click", (block: TurboshaftGraphBlock) => {
|
||||
if (!d3.event.shiftKey) view.blocksSelectionHandler.clear();
|
||||
view.blocksSelectionHandler.select([block.identifier()], undefined);
|
||||
d3.event.stopPropagation();
|
||||
})
|
||||
.call(view.blockDrag);
|
||||
|
||||
newBlocks
|
||||
.append("rect")
|
||||
.attr("rx", 35)
|
||||
.attr("ry", 35)
|
||||
.attr("rx", C.TURBOSHAFT_BLOCK_BORDER_RADIUS)
|
||||
.attr("ry", C.TURBOSHAFT_BLOCK_BORDER_RADIUS)
|
||||
.attr("width", block => block.getWidth())
|
||||
.attr("height", block => block.getHeight(view.state.showProperties));
|
||||
|
||||
@ -156,10 +321,12 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
});
|
||||
|
||||
newBlocks.merge(selBlocks)
|
||||
.classed("selected", block => view.state.selection.isSelected(block))
|
||||
.classed("selected", block => view.state.blocksSelection.isSelected(block.identifier()))
|
||||
.attr("transform", block => `translate(${block.x},${block.y})`)
|
||||
.select("rect")
|
||||
.attr("height", block => block.getHeight(view.state.showProperties));
|
||||
|
||||
newAndOldEdges.attr("d", edge => edge.generatePath(this.graph, view.state.showProperties));
|
||||
}
|
||||
|
||||
private appendInlineNodes(svg: d3.Selection<SVGGElement, TurboshaftGraphBlock, any, any>,
|
||||
@ -177,33 +344,57 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
const newNodes = selNodes
|
||||
.enter()
|
||||
.append("g")
|
||||
.classed("turboshaft-node", true)
|
||||
.classed("inline-node", true);
|
||||
.classed("turboshaft-node inline-node", true);
|
||||
|
||||
let nodeY = block.labelBox.height;
|
||||
const blockWidth = block.getWidth();
|
||||
const view = this;
|
||||
newNodes.each(function (node: TurboshaftGraphNode) {
|
||||
const nodeSvg = d3.select(this);
|
||||
nodeSvg
|
||||
.attr("id", node.id)
|
||||
.append("text")
|
||||
.attr("dx", 25)
|
||||
.attr("dx", C.TURBOSHAFT_NODE_X_INDENT)
|
||||
.classed("inline-node-label", true)
|
||||
.attr("dy", nodeY)
|
||||
.append("tspan")
|
||||
.text(`${node.getInlineLabel()}[${node.getPropertiesTypeAbbreviation()}]`);
|
||||
.text(node.displayLabel)
|
||||
.append("title")
|
||||
.text(node.getTitle());
|
||||
nodeSvg
|
||||
.on("mouseenter", (node: TurboshaftGraphNode) => {
|
||||
view.visibleNodes.data<TurboshaftGraphNode>(
|
||||
node.inputs.map(edge => edge.source), source => source.toString())
|
||||
.classed("input", true);
|
||||
view.visibleNodes.data<TurboshaftGraphNode>(
|
||||
node.outputs.map(edge => edge.target), target => target.toString())
|
||||
.classed("output", true);
|
||||
view.updateGraphVisibility();
|
||||
})
|
||||
.on("mouseleave", (node: TurboshaftGraphNode) => {
|
||||
const inOutNodes = node.inputs.map(edge => edge.source)
|
||||
.concat(node.outputs.map(edge => edge.target));
|
||||
view.visibleNodes.data<TurboshaftGraphNode>(inOutNodes, inOut => inOut.toString())
|
||||
.classed("input output", false);
|
||||
view.updateGraphVisibility();
|
||||
})
|
||||
.on("click", (node: TurboshaftGraphNode) => {
|
||||
if (!d3.event.shiftKey) view.nodesSelectionHandler.clear();
|
||||
view.nodesSelectionHandler.select([node], undefined);
|
||||
d3.event.stopPropagation();
|
||||
});
|
||||
nodeY += node.labelBox.height;
|
||||
if (node.properties) {
|
||||
nodeSvg
|
||||
.append("text")
|
||||
.attr("dx", 25)
|
||||
.attr("dx", C.TURBOSHAFT_NODE_X_INDENT)
|
||||
.classed("inline-node-properties", true)
|
||||
.attr("dy", nodeY)
|
||||
.append("tspan")
|
||||
.text(node.getReadableProperties(blockWidth))
|
||||
.append("title")
|
||||
.text(node.properties);
|
||||
nodeY += node.labelBox.height;
|
||||
nodeY += node.propertiesBox.height;
|
||||
}
|
||||
});
|
||||
|
||||
@ -256,7 +447,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
svg.append("circle")
|
||||
.classed("filledBubbleStyle", block.inputs[i].isVisible())
|
||||
.classed("bubbleStyle", !block.inputs[i].isVisible())
|
||||
.attr("id", `ib,${block.inputs[i].toString(i)}`)
|
||||
.attr("id", `ib,${block.inputs[i].toString()}`)
|
||||
.attr("r", C.DEFAULT_NODE_BUBBLE_RADIUS)
|
||||
.attr("transform", `translate(${x},${y})`);
|
||||
}
|
||||
@ -290,10 +481,13 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||
const nodeY = state.showProperties && node.properties
|
||||
? totalHeight - node.labelBox.height
|
||||
: totalHeight;
|
||||
nodeSvg.select(".inline-node-label").attr("dy", nodeY);
|
||||
nodeSvg.select(".inline-node-properties").attr("visibility", state.showProperties
|
||||
? "visible"
|
||||
: "hidden");
|
||||
nodeSvg
|
||||
.select(".inline-node-label")
|
||||
.classed("selected", node => state.selection.isSelected(node))
|
||||
.attr("dy", nodeY);
|
||||
nodeSvg
|
||||
.select(".inline-node-properties")
|
||||
.attr("visibility", state.showProperties ? "visible" : "hidden");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@
|
||||
"src/views/info-view.ts",
|
||||
"src/views/movable-view.ts",
|
||||
"src/views/turboshaft-graph-view.ts",
|
||||
"src/layout-occupation.ts",
|
||||
"src/origin.ts",
|
||||
"src/movable-container.ts",
|
||||
"src/turboshaft-graph.ts",
|
||||
|
Loading…
Reference in New Issue
Block a user