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