Commit 7a0392b6 authored by Danylo Boiko's avatar Danylo Boiko Committed by V8 LUCI CQ

[turbolizer] Turboshaft layout changes and new interaction features

New nenu buttons:
- Uncollapse all blocks
- Compress layout
- Collapse selected blocks
- Uncollapse selected blocks
New hotkeys:
- Layout graph
- Select all nodes
- Select all selected block's nodes
- Collapse selected blocks
- Uncollapse selected blocks
- Select node's input nodes
- Select node's output nodes
- Collapse unused blocks (blocks that don't have direct inputs and outputs of a hovered node)
- Copy hovered node's info

Bug: v8:7327
Change-Id: I942fe595ffea878f10cfbd962c3eff1786f1b954
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3773778Reviewed-by: 's avatarNico Hartmann <nicohartmann@chromium.org>
Commit-Queue: Danylo Boiko <danielboyko02@gmail.com>
Cr-Commit-Position: refs/heads/main@{#82008}
parent d0a0d1bc
......@@ -91,6 +91,10 @@ ol.linenums {
background-color: #FFFF33 !important;
}
.linenums li > span {
user-select: text;
}
li.selected .line-number {
background-color: #FFFF33;
}
......
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
......@@ -14,8 +14,8 @@
</table>
</div>
</div>
<div id="info-graph-view" class="info-topic">
<div class="info-topic-header">Graph view</div>
<div id="info-common-graph-view" class="info-topic">
<div class="info-topic-header">Sea of Nodes/Turboshaft graph view</div>
<div class="info-topic-content">
<table>
<tr>
......@@ -38,6 +38,13 @@
<td>/</td>
<td>Select search box</td>
</tr>
</table>
</div>
</div>
<div id="info-graph-view" class="info-topic">
<div class="info-topic-header">Sea of Nodes graph view</div>
<div class="info-topic-content">
<table>
<tr>
<td>i</td>
<td>Reveal node's input nodes</td>
......@@ -57,8 +64,35 @@
</table>
</div>
</div>
<div id="info-graph-nodes" class="info-topic">
<div class="info-topic-header">TurboFan graph nodes</div>
<div id="info-turboshaft-graph-view" class="info-topic">
<div class="info-topic-header">Turboshaft graph view</div>
<div class="info-topic-content">
<table>
<tr>
<td>i</td>
<td>Select all selected block's nodes</td>
</tr>
<tr>
<td>p</td>
<td>Collapse selected blocks</td>
</tr>
<tr>
<td>s</td>
<td>Uncollapse selected blocks</td>
</tr>
<tr>
<td>c</td>
<td>Copy hovered node's info</td>
</tr>
<tr>
<td>u</td>
<td>Collapse unused blocks (blocks that don't have direct inputs and outputs of a hovered node)</td>
</tr>
</table>
</div>
</div>
<div id="info-common-nodes" class="info-topic">
<div class="info-topic-header">Sea of Nodes/Turboshaft graph nodes</div>
<div class="info-topic-content">
<div>The following commands transform node selections, i.e. each operation will be applied
to each node in the current selection and the union of the resulting nodes will become the
......@@ -72,6 +106,13 @@
<td>DOWN</td>
<td>Select all output nodes</td>
</tr>
</table>
</div>
</div>
<div id="info-graph-nodes" class="info-topic">
<div class="info-topic-header">Sea of Nodes graph nodes</div>
<div class="info-topic-content">
<table>
<tr>
<td>1-9</td>
<td>Select input node 1-9</td>
......@@ -96,7 +137,7 @@
</div>
</div>
<div id="info-graph-search" class="info-topic">
<div class="info-topic-header">Graph search</div>
<div class="info-topic-header">Sea of Nodes/Turboshaft graph search</div>
<div class="info-topic-content">
<table>
<tr>
......
......@@ -89,3 +89,8 @@ export function storageSetItem(key: string, value: any): void {
export function storageSetIfIsNotExist(key: string, toSet: any): void {
if (storageGetItem(key, null, false) === null) storageSetItem(key, toSet);
}
export function copyToClipboard(text: string): void {
if (!text || text.length == 0) return;
navigator.clipboard.writeText(text);
}
......@@ -55,6 +55,13 @@ export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGra
return this.width;
}
public compressHeight(): void {
if (this.collapsed) {
this.height = this.getHeight(null);
this.showProperties = null;
}
}
public getRankIndent() {
return this.rank * (C.TURBOSHAFT_BLOCK_ROW_SEPARATION + 2 * C.DEFAULT_NODE_BUBBLE_RADIUS);
}
......
......@@ -6,10 +6,7 @@ import * as C from "./common/constants";
import { TurboshaftGraph } from "./turboshaft-graph";
import { GraphStateType } from "./phases/phase";
import { LayoutOccupation } from "./layout-occupation";
import {
TurboshaftGraphBlock,
TurboshaftGraphBlockType
} from "./phases/turboshaft-graph-phase/turboshaft-graph-block";
import { TurboshaftGraphBlock } from "./phases/turboshaft-graph-phase/turboshaft-graph-block";
export class TurboshaftGraphLayout {
graph: TurboshaftGraph;
......@@ -46,9 +43,7 @@ export class TurboshaftGraphLayout {
const blocks = this.initBlocks();
this.initWorkList(blocks);
let visited = new Array<boolean>();
blocks.forEach((block: TurboshaftGraphBlock) => this.dfsFindRankLate(visited, block));
visited = new Array<boolean>();
const visited = new Array<boolean>();
blocks.forEach((block: TurboshaftGraphBlock) => this.dfsRankOrder(visited, block));
const rankSets = this.getRankSets(showProperties);
......@@ -90,24 +85,21 @@ export class TurboshaftGraphLayout {
}
private initWorkList(blocks: Array<TurboshaftGraphBlock>): void {
const workList: Array<TurboshaftGraphBlock> = blocks.slice();
const workList = blocks.slice();
while (workList.length != 0) {
const block: TurboshaftGraphBlock = workList.pop();
const block = 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()) {
if (block.hasBackEdges()) {
end = 1;
}
for (let l = begin; l < end; ++l) {
for (let l = 0; l < end; ++l) {
const input = block.inputs[l].source;
if (input.visible && input.rank >= block.rank) {
if (input.rank >= block.rank) {
block.rank = input.rank + 1;
changed = true;
}
......@@ -127,27 +119,6 @@ export class TurboshaftGraphLayout {
}
}
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.type != TurboshaftGraphBlockType.Merge) {
block.rank = newRank;
}
}
private dfsRankOrder(visited: Array<boolean>, block: TurboshaftGraphBlock): void {
if (visited[block.id]) return;
visited[block.id] = true;
......@@ -163,23 +134,16 @@ export class TurboshaftGraphLayout {
}
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 ranksMaxBlockHeight = this.graph.getRanksMaxBlockHeight(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) => {
block.y = ranksMaxBlockHeight.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);
}
}, block.getRankIndent());
if (!rankSets[block.rank]) {
rankSets[block.rank] = new Array<TurboshaftGraphBlock>(block);
} else {
rankSets[block.rank].push(block);
}
}
return rankSets;
......@@ -199,16 +163,12 @@ export class TurboshaftGraphLayout {
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;
}
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;
}
this.traceOccupation("Before clearing blocks");
......
......@@ -61,8 +61,6 @@ export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> {
this.maxGraphY = 1;
for (const block of this.blocks()) {
if (!block.visible) continue;
this.minGraphX = Math.min(this.minGraphX, block.x);
this.maxGraphNodeX = Math.max(this.maxGraphNodeX, block.x + block.getWidth());
......
......@@ -216,8 +216,8 @@ export class CodeView extends View {
}
private getHtmlCodeLines(): NodeListOf<HTMLElement> {
const ordereList = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`);
return ordereList.childNodes as NodeListOf<HTMLElement>;
const orderList = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`);
return orderList.childNodes as NodeListOf<HTMLElement>;
}
private onSelectLine(lineNumber: number, doClear: boolean) {
......
......@@ -304,9 +304,6 @@ export class GraphView extends MovableView<Graph> {
case 80: // 'p'
this.selectOrigins();
break;
default:
eventHandled = false;
break;
case 83: // 's'
if (!d3.event.ctrlKey && !d3.event.shiftKey) {
this.hideSelectedAction(this);
......@@ -321,6 +318,9 @@ export class GraphView extends MovableView<Graph> {
eventHandled = false;
}
break;
default:
eventHandled = false;
break;
}
if (eventHandled) d3.event.preventDefault();
}
......
......@@ -224,7 +224,7 @@ export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> ext
}
}
protected showVisible() {
protected showVisible(): void {
this.updateGraphVisibility();
this.viewWholeGraph();
this.focusOnSvg();
......
......@@ -4,7 +4,7 @@
import * as C from "../common/constants";
import * as d3 from "d3";
import { partial, storageSetItem } from "../common/util";
import { copyToClipboard, partial, storageSetItem } from "../common/util";
import { MovableView } from "./movable-view";
import { SelectionBroker } from "../selection/selection-broker";
import { SelectionMap } from "../selection/selection-map";
......@@ -33,6 +33,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
visibleEdges: d3.Selection<any, TurboshaftGraphEdge<TurboshaftGraphBlock>, any, any>;
visibleBubbles: d3.Selection<any, any, any, any>;
blockDrag: d3.DragBehavior<any, TurboshaftGraphBlock, TurboshaftGraphBlock>;
hoveredNodeIdentifier: string;
constructor(idOrContainer: string | HTMLElement, broker: SelectionBroker,
showPhaseByName: (name: string) => void, toolbox: HTMLElement) {
......@@ -68,8 +69,14 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
this.show();
this.addImgInput("layout", "layout graph",
partial(this.layoutAction, this));
this.addImgInput("show-all", "show all blocks",
partial(this.showAllBlocksAction, this));
this.addImgInput("show-all", "uncollapse all blocks",
partial(this.uncollapseAllBlocksAction, this));
this.addImgInput("compress-layout", "compress layout",
partial(this.compressLayoutAction, this));
this.addImgInput("collapse-selected", "collapse selected blocks",
partial(this.changeSelectedCollapsingAction, this, true));
this.addImgInput("uncollapse-selected", "uncollapse selected blocks",
partial(this.changeSelectedCollapsingAction, this, false));
this.addImgInput("zoom-selection", "zoom selection",
partial(this.zoomSelectionAction, this));
this.addToggleImgInput("toggle-properties", "toggle properties",
......@@ -104,7 +111,54 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
}
public svgKeyDown(): void {
d3.event.preventDefault();
let eventHandled = true; // unless the below switch defaults
switch (d3.event.keyCode) {
case 38: // UP
case 40: // DOWN
this.showSelectionFrontierNodes(d3.event.keyCode == 38, undefined, true);
break;
case 65: // 'a'
this.selectAllNodes();
break;
case 67: // 'c'
if (!d3.event.ctrlKey && !d3.event.shiftKey) {
this.copyToClipboardHoveredNodeInfo();
} else {
eventHandled = false;
}
break;
case 73: // 'i'
this.selectNodesOfSelectedBlocks();
break;
case 80: // 'p'
if (!d3.event.ctrlKey && !d3.event.shiftKey) {
this.changeSelectedCollapsingAction(this, true);
} else {
eventHandled = false;
}
break;
case 82: // 'r'
if (!d3.event.ctrlKey && !d3.event.shiftKey) {
this.layoutAction(this);
} else {
eventHandled = false;
}
break;
case 83: // 's'
if (!d3.event.ctrlKey && !d3.event.shiftKey) {
this.changeSelectedCollapsingAction(this, false);
} else {
eventHandled = false;
}
break;
case 85: // 'u'
this.collapseUnusedBlocks();
break;
default:
eventHandled = false;
break;
}
if (eventHandled) d3.event.preventDefault();
}
public searchInputAction(searchInput: HTMLInputElement, e: KeyboardEvent, onlyVisible: boolean):
......@@ -117,9 +171,10 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const reg = new RegExp(query);
const filterFunction = (node: TurboshaftGraphNode) => {
return reg.exec(node.displayLabel) !== null ||
if (!onlyVisible) node.block.collapsed = false;
return (!onlyVisible || !node.block.collapsed) && (reg.exec(node.displayLabel) !== null ||
(this.state.showProperties && reg.exec(node.properties)) ||
reg.exec(node.getTitle());
reg.exec(node.getTitle()));
};
const selection = this.searchNodes(filterFunction, e, onlyVisible);
......@@ -235,11 +290,10 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
if (!this.state.cacheLayout ||
this.graph.graphPhase.stateType == GraphStateType.NeedToFullRebuild) {
this.updateGraphStateType(GraphStateType.NeedToFullRebuild);
this.showAllBlocksAction(this);
} else {
this.showVisible();
}
this.showVisible();
const adaptedSelection = this.adaptSelection(rememberedSelection);
this.layoutGraph();
......@@ -278,8 +332,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
// select existing edges
const filteredEdges = [
...this.graph.blocksEdges(edge => this.graph.isRendered()
&& edge.source.visible && edge.target.visible)
...this.graph.blocksEdges(_ => this.graph.isRendered())
];
const selEdges = view.visibleEdges
......@@ -310,7 +363,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
// select existing blocks
const filteredBlocks = [
...this.graph.blocks(block => this.graph.isRendered() && block.visible)
...this.graph.blocks(_ => this.graph.isRendered())
];
const allBlocks = view.visibleBlocks
.selectAll<SVGGElement, TurboshaftGraphBlock>(".turboshaft-block");
......@@ -451,6 +504,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
view.visibleNodes.data<TurboshaftGraphNode>(
node.outputs.map(edge => edge.target), target => target.toString())
.classed("output", true);
view.hoveredNodeIdentifier = node.identifier();
view.updateGraphVisibility();
})
.on("mouseleave", (node: TurboshaftGraphNode) => {
......@@ -458,6 +512,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
.concat(node.outputs.map(edge => edge.target));
view.visibleNodes.data<TurboshaftGraphNode>(inOutNodes, inOut => inOut.toString())
.classed("input output", false);
view.hoveredNodeIdentifier = null;
view.updateGraphVisibility();
})
.on("click", (node: TurboshaftGraphNode) => {
......@@ -481,9 +536,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
});
newNodes.merge(selNodes)
.classed("selected", node => state.selection.isSelected(node))
.select("rect")
.attr("height", node => node.getHeight(state.showProperties));
.classed("selected", node => state.selection.isSelected(node));
}
private updateInlineNodes(): void {
......@@ -500,11 +553,13 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const nodeY = state.showProperties && node.properties
? totalHeight - node.labelBox.height
: totalHeight;
nodeSvg
.select(".inline-node-label")
.classed("selected", node => state.selection.isSelected(node))
.attr("dy", nodeY)
.attr("visibility", !node.block.collapsed ? "visible" : "hidden");
nodeSvg
.select(".inline-node-properties")
.attr("visibility", !node.block.collapsed && state.showProperties ? "visible" : "hidden");
......@@ -525,8 +580,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
}
if (block.outputs.length > 0) {
const x = block.getOutputX();
const y = block.getHeight(this.state.showProperties)
+ C.DEFAULT_NODE_BUBBLE_RADIUS;
const y = block.getHeight(this.state.showProperties) + C.DEFAULT_NODE_BUBBLE_RADIUS;
svg.append("circle")
.classed("filledBubbleStyle", true)
.attr("id", `ob,${block.id}`)
......@@ -542,8 +596,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
if (components[0] === "ob") {
const from = view.graph.blockMap[components[1]];
const x = from.getOutputX();
const y = from.getHeight(view.state.showProperties)
+ C.DEFAULT_NODE_BUBBLE_RADIUS;
const y = from.getHeight(view.state.showProperties) + C.DEFAULT_NODE_BUBBLE_RADIUS;
this.setAttribute("transform", `translate(${x},${y})`);
}
});
......@@ -605,15 +658,38 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
view.focusOnSvg();
}
// TODO (danylo boiko) Uncollapse all blocks
private showAllBlocksAction(view: TurboshaftGraphView): void {
for (const node of view.graph.blocks()) {
node.visible = true;
private uncollapseAllBlocksAction(view: TurboshaftGraphView): void {
for (const block of view.graph.blocks()) {
block.collapsed = false;
}
for (const edge of view.graph.blocksEdges()) {
edge.visible = true;
view.updateGraphVisibility();
view.focusOnSvg();
}
private compressLayoutAction(view: TurboshaftGraphView): void {
for (const block of view.graph.blocks()) {
block.compressHeight();
}
view.showVisible();
const ranksMaxBlockHeight = view.graph.getRanksMaxBlockHeight(view.state.showProperties);
for (const block of view.graph.blocks()) {
block.y = ranksMaxBlockHeight.slice(1, block.rank).reduce<number>((accumulator, current) => {
return accumulator + current;
}, block.getRankIndent());
}
view.adaptiveUpdateGraphVisibility();
}
private changeSelectedCollapsingAction(view: TurboshaftGraphView, collapsed: boolean): void {
for (const key of view.state.blocksSelection.selectedKeys()) {
const block = view.graph.blockMap[key];
if (!block) continue;
block.collapsed = collapsed;
}
view.updateGraphVisibility();
view.focusOnSvg();
}
private zoomSelectionAction(view: TurboshaftGraphView): void {
......@@ -623,7 +699,14 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
private togglePropertiesAction(view: TurboshaftGraphView): void {
view.state.showProperties = !view.state.showProperties;
const ranksMaxBlockHeight = view.graph.getRanksMaxBlockHeight(view.state.showProperties);
const ranksMaxBlockHeight = new Array<number>();
for (const block of view.graph.blocks()) {
ranksMaxBlockHeight[block.rank] = Math.max(ranksMaxBlockHeight[block.rank] ?? 0,
block.collapsed
? block.height
: block.getHeight(view.state.showProperties));
}
for (const block of view.graph.blocks()) {
block.y = ranksMaxBlockHeight.slice(1, block.rank).reduce<number>((accumulator, current) => {
......@@ -641,4 +724,47 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const element = document.getElementById("toggle-cache-layout");
element.classList.toggle("button-input-toggled", view.state.cacheLayout);
}
// Hotkeys handlers
private selectAllNodes(): void {
this.state.selection.select(this.graph.nodeMap, true);
this.updateGraphVisibility();
}
private collapseUnusedBlocks(): void {
const node = this.graph.nodeMap[this.hoveredNodeIdentifier];
if (!node) return;
const usedBlocks = new Set<TurboshaftGraphBlock>([node.block]);
for (const input of node.inputs) {
usedBlocks.add(input.source.block);
}
for (const output of node.outputs) {
usedBlocks.add(output.target.block);
}
for (const block of this.graph.blockMap) {
block.collapsed = !usedBlocks.has(block);
}
this.updateGraphVisibility();
}
private copyToClipboardHoveredNodeInfo(): void {
const node = this.graph.nodeMap[this.hoveredNodeIdentifier];
if (!node) return;
copyToClipboard(node.getTitle());
}
private selectNodesOfSelectedBlocks(): void {
let selectedNodes = new Array<TurboshaftGraphNode>();
for (const key of this.state.blocksSelection.selectedKeys()) {
const block = this.graph.blockMap[key];
if (!block) continue;
block.collapsed = false;
selectedNodes = selectedNodes.concat(block.nodes);
}
this.state.selection.select(selectedNodes, true);
this.updateGraphVisibility();
}
}
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