Commit a53332fe authored by Sigurd Schneider's avatar Sigurd Schneider Committed by Commit Bot

[turbolizer] Improve handling of graph layout/redraw

This removes duplicate storage of edges in the graph view, thereby
reducing memory overhead.

Bug: v8:7327
Notry: true
Change-Id: I70df4bc102add8c89bc5145f01c0555b3e0a73d7
Reviewed-on: https://chromium-review.googlesource.com/c/1396085
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#58548}
parent f5e1353f
......@@ -6,10 +6,6 @@ import {GNode, DEFAULT_NODE_BUBBLE_RADIUS} from "../src/node"
export const MINIMUM_EDGE_SEPARATION = 20;
export function isEdgeInitiallyVisible(target, index, source, type) {
return type == "control" && (target.cfg || source.cfg);
}
export class Edge {
target: GNode;
source: GNode;
......@@ -24,7 +20,7 @@ export class Edge {
this.index = index;
this.type = type;
this.backEdgeNumber = 0;
this.visible = isEdgeInitiallyVisible(target, index, source, type);
this.visible = false;
}
......
......@@ -3,9 +3,9 @@
// found in the LICENSE file.
import {MAX_RANK_SENTINEL} from "../src/constants"
import {MINIMUM_EDGE_SEPARATION} from "../src/edge"
import {NODE_INPUT_WIDTH, MINIMUM_NODE_OUTPUT_APPROACH, DEFAULT_NODE_BUBBLE_RADIUS} from "../src/node"
import { MAX_RANK_SENTINEL } from "../src/constants"
import { MINIMUM_EDGE_SEPARATION, Edge } from "../src/edge"
import { NODE_INPUT_WIDTH, MINIMUM_NODE_OUTPUT_APPROACH, DEFAULT_NODE_BUBBLE_RADIUS, GNode } from "../src/node"
const DEFAULT_NODE_ROW_SEPARATION = 130
......@@ -255,13 +255,16 @@ function newGraphOccupation(graph) {
export function layoutNodeGraph(graph) {
// First determine the set of nodes that have no outputs. Those are the
// basis for bottom-up DFS to determine rank and node placement.
var endNodesHasNoOutputs = [];
var startNodesHasNoInputs = [];
graph.nodes.forEach(function (n, i) {
const start = performance.now();
const endNodesHasNoOutputs = [];
const startNodesHasNoInputs = [];
graph.nodes.forEach(function (n: GNode) {
endNodesHasNoOutputs[n.id] = true;
startNodesHasNoInputs[n.id] = true;
});
graph.edges.forEach(function (e, i) {
graph.forEachEdge((e: Edge) => {
endNodesHasNoOutputs[e.source.id] = false;
startNodesHasNoInputs[e.target.id] = false;
});
......@@ -285,10 +288,12 @@ export function layoutNodeGraph(graph) {
n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
});
if (traceLayout) {
console.log(`layoutGraph init ${performance.now() - start}`);
}
var maxRank = 0;
var visited = [];
var dfsStack = [];
var visitOrderWithinRank = 0;
var worklist = startNodes.slice();
......@@ -329,8 +334,12 @@ export function layoutNodeGraph(graph) {
}
}
if (traceLayout) {
console.log(`layoutGraph worklist ${performance.now() - start}`);
}
visited = [];
function dfsFindRankLate(n) {
function dfsFindRankLate(n: GNode) {
if (visited[n.id]) return;
visited[n.id] = true;
var originalRank = n.rank;
......@@ -354,7 +363,7 @@ export function layoutNodeGraph(graph) {
startNodes.forEach(dfsFindRankLate);
visited = [];
function dfsRankOrder(n) {
function dfsRankOrder(n: GNode) {
if (visited[n.id]) return;
visited[n.id] = true;
for (var l = 0; l < n.outputs.length; ++l) {
......
......@@ -5,7 +5,7 @@
import * as d3 from "d3"
import { layoutNodeGraph } from "../src/graph-layout"
import { MAX_RANK_SENTINEL } from "../src/constants"
import { GNode, nodeToStr, isNodeInitiallyVisible } from "../src/node"
import { GNode, nodeToStr } from "../src/node"
import { NODE_INPUT_WIDTH, MINIMUM_NODE_OUTPUT_APPROACH } from "../src/node"
import { DEFAULT_NODE_BUBBLE_RADIUS } from "../src/node"
import { Edge, edgeToStr } from "../src/edge"
......@@ -34,8 +34,7 @@ export class GraphView extends View implements PhaseView {
showPhaseByName: (string) => void;
state: GraphState;
nodes: Array<GNode>;
edges: Array<any>;
selectionHandler: NodeSelectionHandler&ClearableHandler;
selectionHandler: NodeSelectionHandler & ClearableHandler;
graphElement: d3.Selection<any, any, any, any>;
visibleNodes: d3.Selection<any, GNode, any, any>;
visibleEdges: d3.Selection<any, Edge, any, any>;
......@@ -58,6 +57,22 @@ export class GraphView extends View implements PhaseView {
return pane;
}
*filteredEdges(p: (e: Edge) => boolean) {
for (const node of this.nodes) {
for (const edge of node.inputs) {
if (p(edge)) yield edge;
}
}
}
forEachEdge(p: (e: Edge) => void) {
for (const node of this.nodes) {
for (const edge of node.inputs) {
p(edge);
}
}
}
constructor(id, broker, showPhaseByName: (string) => void) {
super(id);
var graph = this;
......@@ -72,7 +87,6 @@ export class GraphView extends View implements PhaseView {
graph.svg = svg;
graph.nodes = [];
graph.edges = [];
graph.minGraphX = 0;
graph.maxGraphX = 1;
......@@ -118,11 +132,15 @@ export class GraphView extends View implements PhaseView {
graph.state.selection.select(selection, selected);
// Update edge visibility based on selection.
graph.nodes.forEach((n) => {
if (graph.state.selection.isSelected(n)) n.visible = true;
});
graph.edges.forEach(function (e) {
e.visible = e.visible ||
(graph.state.selection.isSelected(e.source) && graph.state.selection.isSelected(e.target));
if (graph.state.selection.isSelected(n)) {
n.visible = true;
n.inputs.forEach((e) => {
e.visible = e.visible || graph.state.selection.isSelected(e.source);
});
n.outputs.forEach((e) => {
e.visible = e.visible || graph.state.selection.isSelected(e.target);
});
}
});
graph.updateGraphVisibility();
},
......@@ -212,9 +230,9 @@ export class GraphView extends View implements PhaseView {
getEdgeFrontier(nodes, inEdges, edgeFilter) {
let frontier = new Set();
for (const n of nodes) {
var edges = inEdges ? n.inputs : n.outputs;
const edges = inEdges ? n.inputs : n.outputs;
var edgeNumber = 0;
edges.forEach(function (edge) {
edges.forEach((edge: Edge) => {
if (edgeFilter == undefined || edgeFilter(edge, edgeNumber)) {
frontier.add(edge);
}
......@@ -279,7 +297,6 @@ export class GraphView extends View implements PhaseView {
deleteContent() {
if (this.visibleNodes) {
this.nodes = [];
this.edges = [];
this.nodeMap = [];
this.updateGraphVisibility();
}
......@@ -314,6 +331,7 @@ export class GraphView extends View implements PhaseView {
n.outputs = [];
n.rpo = -1;
n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
// Every control node is a CFG node.
n.cfg = n.control;
g.nodeMap[n.id] = n;
n.displayLabel = n.getDisplayLabel();
......@@ -326,27 +344,26 @@ export class GraphView extends View implements PhaseView {
n.normalheight = innerheight + 20;
g.nodes.push(n);
});
g.edges = [];
data.edges.forEach(function (e, i) {
data.edges.forEach((e: any) => {
var t = g.nodeMap[e.target];
var s = g.nodeMap[e.source];
var newEdge = new Edge(t, e.index, s, e.type);
t.inputs.push(newEdge);
s.outputs.push(newEdge);
g.edges.push(newEdge);
if (e.type == 'control') {
// Every source of a control edge is a CFG node.
s.cfg = true;
}
});
g.nodes.forEach(function (n, i) {
n.visible = isNodeInitiallyVisible(n) && (!g.state.hideDead || n.isLive());
if (rememberedSelection != undefined) {
if (rememberedSelection.has(nodeToStringKey(n))) {
n.visible = true;
}
n.visible = n.cfg && (!g.state.hideDead || n.isLive());
if (rememberedSelection != undefined && rememberedSelection.has(nodeToStringKey(n))) {
n.visible = true;
}
});
g.updateGraphVisibility();
g.forEachEdge((e: Edge) => {
e.visible = e.type == 'control' && e.source.visible && e.target.visible;
});
g.layoutGraph();
g.updateGraphVisibility();
}
......@@ -431,18 +448,17 @@ export class GraphView extends View implements PhaseView {
}
layoutAction(graph) {
graph.updateGraphVisibility();
graph.layoutGraph();
graph.updateGraphVisibility();
graph.viewWholeGraph();
}
showAllAction(graph) {
graph.nodes.forEach(function (n) {
graph.nodes.forEach((n: GNode) => {
n.visible = !graph.state.hideDead || n.isLive();
});
graph.edges.forEach(function (e) {
e.visible = !graph.state.hideDead || (e.source.isLive() && e.target.isLive());
graph.forEachEdge((e: Edge) => {
e.visible = e.source.visible || e.target.visible;
});
graph.updateGraphVisibility();
graph.viewWholeGraph();
......@@ -462,7 +478,7 @@ export class GraphView extends View implements PhaseView {
n.visible = false;
graph.state.selection.select([n], false);
}
})
});
graph.updateGraphVisibility();
}
......@@ -641,7 +657,9 @@ export class GraphView extends View implements PhaseView {
};
layoutGraph() {
console.time("layoutGraph");
layoutNodeGraph(this);
console.timeEnd("layoutGraph");
}
selectOrigins() {
......@@ -669,21 +687,19 @@ export class GraphView extends View implements PhaseView {
let graph = this;
let state = graph.state;
var filteredEdges = graph.edges.filter(function (e) {
return e.isVisible();
});
var filteredEdges = [...graph.filteredEdges(function (e) {
return e.source.visible && e.target.visible;
})];
const selEdges = graph.visibleEdges.selectAll<SVGPathElement, Edge>("path").data(filteredEdges, edgeToStr);
// remove old links
selEdges.exit().remove();
// add new paths
selEdges.enter()
.append('path')
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', function (e) {
return !e.isVisible();
})
const newEdges = selEdges.enter()
.append('path');
newEdges.style('marker-end', 'url(#end-arrow)')
.attr("id", function (edge) { return "e," + edge.stringID(); })
.on("click", function (edge) {
d3.event.stopPropagation();
......@@ -692,21 +708,23 @@ export class GraphView extends View implements PhaseView {
}
graph.selectionHandler.select([edge.source, edge.target], true);
})
.attr("adjacentToHover", "false");
// Set the correct styles on all of the paths
selEdges.classed('value', function (e) {
return e.type == 'value' || e.type == 'context';
}).classed('control', function (e) {
return e.type == 'control';
}).classed('effect', function (e) {
return e.type == 'effect';
}).classed('frame-state', function (e) {
return e.type == 'frame-state';
}).attr('stroke-dasharray', function (e) {
if (e.type == 'frame-state') return "10,10";
return (e.type == 'effect') ? "5,5" : "";
});
.attr("adjacentToHover", "false")
.classed('value', function (e) {
return e.type == 'value' || e.type == 'context';
}).classed('control', function (e) {
return e.type == 'control';
}).classed('effect', function (e) {
return e.type == 'effect';
}).classed('frame-state', function (e) {
return e.type == 'frame-state';
}).attr('stroke-dasharray', function (e) {
if (e.type == 'frame-state') return "10,10";
return (e.type == 'effect') ? "5,5" : "";
});
const newAndOldEdges = newEdges.merge(selEdges);
newAndOldEdges.classed('hidden', (e) => !e.isVisible());
// select existing nodes
const filteredNodes = graph.nodes.filter(n => n.visible);
......@@ -878,7 +896,7 @@ export class GraphView extends View implements PhaseView {
graph.updateInputAndOutputBubbles();
graph.maxGraphX = graph.maxGraphNodeX;
selEdges.attr("d", function (edge) {
newAndOldEdges.attr("d", function (edge) {
return edge.generatePath(graph);
});
}
......
......@@ -3,17 +3,13 @@
// found in the LICENSE file.
import {NodeOrigin} from "../src/source-resolver"
import {MINIMUM_EDGE_SEPARATION} from "../src/edge"
import {MINIMUM_EDGE_SEPARATION, Edge} from "../src/edge"
export const DEFAULT_NODE_BUBBLE_RADIUS = 12;
export const NODE_INPUT_WIDTH = 50;
export const MINIMUM_NODE_OUTPUT_APPROACH = 15;
const MINIMUM_NODE_INPUT_APPROACH = 15 + 2 * DEFAULT_NODE_BUBBLE_RADIUS;
export function isNodeInitiallyVisible(node) {
return node.cfg;
}
function formatOrigin(origin) {
if (origin.nodeId) {
return `#${origin.nodeId} in phase ${origin.phase}/${origin.reducer}`;
......@@ -28,13 +24,13 @@ export class GNode {
control: boolean;
opcode: string;
live: boolean;
inputs: Array<any>;
inputs: Array<Edge>;
width: number;
properties: string;
title: string;
label: string;
origin: NodeOrigin;
outputs: Array<any>;
outputs: Array<Edge>;
outputApproach: number;
type: string;
id: number;
......@@ -44,6 +40,8 @@ export class GNode {
rank: number;
opinfo: string;
labelbbox: { width: number, height: number };
visitOrderWithinRank: number;
cfg: boolean;
isControl() {
return this.control;
......
......@@ -180,7 +180,7 @@ export abstract class TextView extends View {
fragment.classList.add(cls);
}
}
fragment.innerHTML = text;
fragment.innerText = text;
}
return fragment;
......
......@@ -238,7 +238,7 @@ window.onload = function () {
backwardsCompatibility: true
};
} else {
fnc = Object.assign(jsonObj.function, {backwardsCompatibility: false});
fnc = Object.assign(jsonObj.function, { backwardsCompatibility: false });
}
sourceResolver.setInlinings(jsonObj.inlinings);
......@@ -297,7 +297,7 @@ window.onload = function () {
var uploadFile = this.files && this.files[0];
var filereader = new FileReader();
filereader.onload = function (e) {
var txtRes = e.target.result;
var txtRes = filereader.result;
loadFile(txtRes);
};
if (uploadFile)
......
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