[turbolizer] Refactor NodeLabel from GNode

Refactor NodeLabel from GNode, which saves memory and is a step towards
decoupling the node layout from the graph structure.

Change-Id: I095a2f7a7ab28067161deffbc37952ae15410e0a
Notry: true
Bug: v8:7327
Reviewed-on: https://chromium-review.googlesource.com/c/1396418
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#58571}
This commit is contained in:
Sigurd Schneider 2019-01-05 13:00:16 +01:00 committed by Commit Bot
parent 76f8893699
commit c0f6220914
8 changed files with 198 additions and 122 deletions

View File

@ -13,7 +13,7 @@ const DEFAULT_NODE_ROW_SEPARATION = 130
var traceLayout = false;
function newGraphOccupation(graph:Graph) {
function newGraphOccupation(graph: Graph) {
var isSlotFilled = [];
var maxSlot = 0;
var minSlot = 0;
@ -122,12 +122,12 @@ function newGraphOccupation(graph:Graph) {
}
var occupation = {
occupyNodeInputs: function (node) {
occupyNodeInputs: function (node: GNode, showTypes: boolean) {
for (var i = 0; i < node.inputs.length; ++i) {
if (node.inputs[i].isVisible()) {
var edge = node.inputs[i];
if (!edge.isBackEdge()) {
var horizontalPos = edge.getInputHorizontalPosition(graph);
var horizontalPos = edge.getInputHorizontalPosition(graph, showTypes);
if (traceLayout) {
console.log("Occupying input " + i + " of " + node.id + " at " + horizontalPos);
}
@ -201,13 +201,13 @@ function newGraphOccupation(graph:Graph) {
});
nodeOccupation = [];
},
clearNodeOutputs: function (source) {
clearNodeOutputs: function (source: GNode, showTypes: boolean) {
source.outputs.forEach(function (edge) {
if (edge.isVisible()) {
var target = edge.target;
for (var i = 0; i < target.inputs.length; ++i) {
if (target.inputs[i].source === source) {
var horizontalPos = edge.getInputHorizontalPosition(graph);
var horizontalPos = edge.getInputHorizontalPosition(graph, showTypes);
clearPositionRangeWithMargin(horizontalPos,
horizontalPos,
NODE_INPUT_WIDTH / 2);
@ -343,7 +343,7 @@ export function layoutNodeGraph(graph: Graph, showTypes: boolean): void {
}
firstInput = false;
}
if (n.opcode != "Start" && n.opcode != "Phi" && n.opcode != "EffectPhi") {
if (n.nodeLabel.opcode != "Start" && n.nodeLabel.opcode != "Phi" && n.nodeLabel.opcode != "EffectPhi" && n.nodeLabel.opcode != "InductionVariablePhi") {
n.rank = newRank;
}
}
@ -371,7 +371,7 @@ export function layoutNodeGraph(graph: Graph, showTypes: boolean): void {
n.rank = maxRank + 1;
});
var rankSets = [];
const rankSets: Array<Array<GNode>> = [];
// Collect sets for each rank.
for (const n of graph.nodes()) {
n.y = n.rank * (DEFAULT_NODE_ROW_SEPARATION + n.getNodeHeight(showTypes) +
@ -390,10 +390,10 @@ export function layoutNodeGraph(graph: Graph, showTypes: boolean): void {
// compact and not overlapping live input lines.
var occupation = newGraphOccupation(graph);
rankSets.reverse().forEach(function (rankSet) {
rankSets.reverse().forEach(function (rankSet: Array<GNode>) {
for (var i = 0; i < rankSet.length; ++i) {
occupation.clearNodeOutputs(rankSet[i]);
occupation.clearNodeOutputs(rankSet[i], showTypes);
}
if (traceLayout) {
@ -402,8 +402,14 @@ export function layoutNodeGraph(graph: Graph, showTypes: boolean): void {
}
var placedCount = 0;
rankSet = rankSet.sort(function (a, b) {
return a.visitOrderWithinRank < b.visitOrderWithinRank;
rankSet = rankSet.sort((a: GNode, b: GNode) => {
if (a.visitOrderWithinRank < b.visitOrderWithinRank) {
return -1
} else if (a.visitOrderWithinRank == b.visitOrderWithinRank) {
return 0;
} else {
return 1;
}
});
for (var i = 0; i < rankSet.length; ++i) {
var nodeToPlace = rankSet[i];
@ -434,7 +440,7 @@ export function layoutNodeGraph(graph: Graph, showTypes: boolean): void {
for (var i = 0; i < rankSet.length; ++i) {
var node = rankSet[i];
occupation.occupyNodeInputs(node);
occupation.occupyNodeInputs(node, showTypes);
}
if (traceLayout) {
@ -449,7 +455,7 @@ export function layoutNodeGraph(graph: Graph, showTypes: boolean): void {
});
graph.maxBackEdgeNumber = 0;
graph.forEachEdge((e) => {
graph.forEachEdge((e: Edge) => {
if (e.isBackEdge()) {
e.backEdgeNumber = ++graph.maxBackEdgeNumber;
} else {

View File

@ -32,7 +32,7 @@ interface GraphState {
export class GraphView extends View implements PhaseView {
divElement: d3.Selection<any, any, any, any>;
svg: d3.Selection<any, any, any, any>;
showPhaseByName: (string) => void;
showPhaseByName: (s: string) => void;
state: GraphState;
selectionHandler: NodeSelectionHandler & ClearableHandler;
graphElement: d3.Selection<any, any, any, any>;
@ -53,8 +53,8 @@ export class GraphView extends View implements PhaseView {
return pane;
}
constructor(id, broker, showPhaseByName: (string) => void) {
super(id);
constructor(idOrContainer: string | HTMLElement, broker: SelectionBroker, showPhaseByName: (s: string) => void) {
super(idOrContainer);
const view = this;
this.broker = broker;
this.showPhaseByName = showPhaseByName;
@ -83,21 +83,21 @@ export class GraphView extends View implements PhaseView {
broker.broadcastClear(this);
view.updateGraphVisibility();
},
select: function (nodes, selected) {
select: function (nodes: Iterable<GNode>, selected: boolean) {
let locations = [];
for (const node of nodes) {
if (node.sourcePosition) {
locations.push(node.sourcePosition);
if (node.nodeLabel.sourcePosition) {
locations.push(node.nodeLabel.sourcePosition);
}
if (node.origin && node.origin.bytecodePosition) {
locations.push({ bytecodePosition: node.origin.bytecodePosition });
if (node.nodeLabel.origin && node.nodeLabel.origin.bytecodePosition) {
locations.push({ bytecodePosition: node.nodeLabel.origin.bytecodePosition });
}
}
view.state.selection.select(nodes, selected);
broker.broadcastSourcePositionSelect(this, locations, selected);
view.updateGraphVisibility();
},
brokeredNodeSelect: function (locations, selected) {
brokeredNodeSelect: function (locations, selected: boolean) {
if (!view.graph) return;
let selection = view.graph.nodes((n) => {
return locations.has(nodeToStringKey(n))
@ -772,7 +772,7 @@ export class GraphView extends View implements PhaseView {
.text(function (l) {
return d.getTitle();
})
if (d.type != undefined) {
if (d.nodeLabel.type != undefined) {
d3.select(this).append("text")
.classed("label", true)
.classed("type", true)

View File

@ -1,6 +1,4 @@
import { GNode, MINIMUM_NODE_OUTPUT_APPROACH, NODE_INPUT_WIDTH } from "./node";
import { MAX_RANK_SENTINEL } from "./constants";
import { alignUp, measureText } from "./util";
import { GNode } from "./node";
import { Edge, MINIMUM_EDGE_SEPARATION } from "./edge";
export class Graph {
@ -20,30 +18,8 @@ export class Graph {
this.minGraphY = 0;
this.maxGraphY = 1;
data.nodes.forEach((n: any) => {
n.__proto__ = GNode.prototype;
n.visible = false;
n.x = 0;
n.y = 0;
if (typeof n.pos === "number") {
// Backwards compatibility.
n.sourcePosition = { scriptOffset: n.pos, inliningId: -1 };
}
n.rank = MAX_RANK_SENTINEL;
n.inputs = [];
n.outputs = [];
n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
// Every control node is a CFG node.
n.cfg = n.control;
this.nodeMap[n.id] = n;
n.displayLabel = n.getDisplayLabel();
n.labelbbox = measureText(n.displayLabel);
const typebbox = measureText(n.getDisplayType());
const innerwidth = Math.max(n.labelbbox.width, typebbox.width);
n.width = alignUp(innerwidth + NODE_INPUT_WIDTH * 2,
NODE_INPUT_WIDTH);
const innerheight = Math.max(n.labelbbox.height, typebbox.height);
n.normalheight = innerheight + 20;
data.nodes.forEach((json_node: any) => {
this.nodeMap[json_node.id] = new GNode(json_node.nodeLabel);
});
data.edges.forEach((e: any) => {

View File

@ -0,0 +1,77 @@
// Copyright 2019 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.
function formatOrigin(origin) {
if (origin.nodeId) {
return `#${origin.nodeId} in phase ${origin.phase}/${origin.reducer}`;
}
if (origin.bytecodePosition) {
return `Bytecode line ${origin.bytecodePosition} in phase ${origin.phase}/${origin.reducer}`;
}
return "unknown origin";
}
export class NodeLabel {
id: number;
label: string;
title: string;
live: boolean;
properties: string;
sourcePosition: any;
origin: any;
opcode: string;
control: boolean;
opinfo: string;
type: string;
constructor(id: number, label: string, title: string, live: boolean, properties: string, sourcePosition: any, origin: any, opcode: string, control: boolean, opinfo: string, type: string) {
this.id = id;
this.label = label;
this.title = title;
this.live = live;
this.properties = properties;
this.sourcePosition = sourcePosition;
this.opcode = opcode;
this.control = control;
this.opinfo = opinfo;
this.type = type;
}
equals(that?: NodeLabel) {
if (!that) return false;
if (this.id != that.id) return false;
if (this.label != that.label) return false;
if (this.title != that.title) return false;
if (this.live != that.live) return false;
if (this.properties != that.properties) return false;
if (this.opcode != that.opcode) return false;
if (this.control != that.control) return false;
if (this.opinfo != that.opinfo) return false;
if (this.type != that.type) return false;
return true;
}
getTitle() {
let propsString = "";
if (this.properties === "") {
propsString = "no properties";
} else {
propsString = "[" + this.properties + "]";
}
let title = this.title + "\n" + propsString + "\n" + this.opinfo;
if (this.origin) {
title += `\nOrigin: ${formatOrigin(this.origin)}`;
}
return title;
}
getDisplayLabel() {
const result = `${this.id}: ${this.label}`;
if (result.length > 40) {
return `${this.id}: ${this.opcode}`;
}
return result;
}
}

View File

@ -2,74 +2,83 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { NodeOrigin } from "../src/source-resolver"
import { MINIMUM_EDGE_SEPARATION, Edge } from "../src/edge"
import { NodeLabel } from "./node-label";
import { MAX_RANK_SENTINEL } from "./constants";
import { alignUp, measureText } from "./util";
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;
function formatOrigin(origin) {
if (origin.nodeId) {
return `#${origin.nodeId} in phase ${origin.phase}/${origin.reducer}`;
}
if (origin.bytecodePosition) {
return `Bytecode line ${origin.bytecodePosition} in phase ${origin.phase}/${origin.reducer}`;
}
return "unknown origin";
}
export class GNode {
control: boolean;
opcode: string;
live: boolean;
inputs: Array<Edge>;
width: number;
properties: string;
title: string;
label: string;
origin: NodeOrigin;
outputs: Array<Edge>;
outputApproach: number;
type: string;
id: number;
nodeLabel: NodeLabel;
displayLabel: string;
inputs: Array<Edge>;
outputs: Array<Edge>;
visible: boolean;
x: number;
y: number;
visible: boolean;
rank: number;
opinfo: string;
labelbbox: { width: number, height: number };
visitOrderWithinRank: number;
outputApproach: number;
cfg: boolean;
labelbbox: { width: number, height: number };
width: number;
normalheight: number;
visitOrderWithinRank: number;
constructor(nodeLabel: NodeLabel) {
this.id = nodeLabel.id;
this.nodeLabel = nodeLabel;
this.displayLabel = nodeLabel.getDisplayLabel();
this.inputs = [];
this.outputs = [];
this.visible = false;
this.x = 0;
this.y = 0;
this.rank = MAX_RANK_SENTINEL;
this.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
// Every control node is a CFG node.
this.cfg = nodeLabel.control;
this.labelbbox = measureText(this.displayLabel);
const typebbox = measureText(this.getDisplayType());
const innerwidth = Math.max(this.labelbbox.width, typebbox.width);
this.width = alignUp(innerwidth + NODE_INPUT_WIDTH * 2,
NODE_INPUT_WIDTH);
const innerheight = Math.max(this.labelbbox.height, typebbox.height);
this.normalheight = innerheight + 20;
this.visitOrderWithinRank = 0;
}
isControl() {
return this.control;
return this.nodeLabel.control;
}
isInput() {
return this.opcode == 'Parameter' || this.opcode.endsWith('Constant');
return this.nodeLabel.opcode == 'Parameter' || this.nodeLabel.opcode.endsWith('Constant');
}
isLive() {
return this.live !== false;
return this.nodeLabel.live !== false;
}
isJavaScript() {
return this.opcode.startsWith('JS');
return this.nodeLabel.opcode.startsWith('JS');
}
isSimplified() {
if (this.isJavaScript()) return false;
return this.opcode.endsWith('Phi') ||
this.opcode.startsWith('Boolean') ||
this.opcode.startsWith('Number') ||
this.opcode.startsWith('String') ||
this.opcode.startsWith('Change') ||
this.opcode.startsWith('Object') ||
this.opcode.startsWith('Reference') ||
this.opcode.startsWith('Any') ||
this.opcode.endsWith('ToNumber') ||
(this.opcode == 'AnyToBoolean') ||
(this.opcode.startsWith('Load') && this.opcode.length > 4) ||
(this.opcode.startsWith('Store') && this.opcode.length > 5);
const opcode = this.nodeLabel.opcode;
return opcode.endsWith('Phi') ||
opcode.startsWith('Boolean') ||
opcode.startsWith('Number') ||
opcode.startsWith('String') ||
opcode.startsWith('Change') ||
opcode.startsWith('Object') ||
opcode.startsWith('Reference') ||
opcode.startsWith('Any') ||
opcode.endsWith('ToNumber') ||
(opcode == 'AnyToBoolean') ||
(opcode.startsWith('Load') && opcode.length > 4) ||
(opcode.startsWith('Store') && opcode.length > 5);
}
isMachine() {
return !(this.isControl() || this.isInput() ||
@ -80,33 +89,16 @@ export class GNode {
return Math.max(inputWidth, this.width);
}
getTitle() {
var propsString;
if (this.properties === undefined) {
propsString = "";
} else if (this.properties === "") {
propsString = "no properties";
} else {
propsString = "[" + this.properties + "]";
}
let title = this.title + "\n" + propsString + "\n" + this.opinfo;
if (this.origin) {
title += `\nOrigin: ${formatOrigin(this.origin)}`;
}
return title;
return this.nodeLabel.getTitle();
}
getDisplayLabel() {
var result = this.id + ":" + this.label;
if (result.length > 40) {
return this.id + ":" + this.opcode;
} else {
return result;
}
return this.nodeLabel.getDisplayLabel();
}
getType() {
return this.type;
return this.nodeLabel.type;
}
getDisplayType() {
var type_string = this.type;
var type_string = this.nodeLabel.type;
if (type_string == undefined) return "";
if (type_string.length > 24) {
type_string = type_string.substr(0, 25) + "...";
@ -159,14 +151,14 @@ export class GNode {
return this.y - MINIMUM_NODE_INPUT_APPROACH -
(index % 4) * MINIMUM_EDGE_SEPARATION - DEFAULT_NODE_BUBBLE_RADIUS
}
getNodeHeight(showTypes:boolean): number {
getNodeHeight(showTypes: boolean): number {
if (showTypes) {
return this.normalheight + this.labelbbox.height;
} else {
return this.normalheight;
}
}
getOutputApproach(showTypes:boolean) {
getOutputApproach(showTypes: boolean) {
return this.y + this.outputApproach + this.getNodeHeight(showTypes) +
+ DEFAULT_NODE_BUBBLE_RADIUS;
}
@ -179,9 +171,9 @@ export class GNode {
return this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2);
}
hasBackEdges() {
return (this.opcode == "Loop") ||
((this.opcode == "Phi" || this.opcode == "EffectPhi") &&
this.inputs[this.inputs.length - 1].source.opcode == "Loop");
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");
}
};

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
import { sortUnique, anyToString } from "../src/util"
import { NodeLabel } from "./node-label";
function sourcePositionLe(a, b) {
if (a.inliningId == b.inliningId) {
@ -87,6 +88,7 @@ interface GraphPhase {
name: string;
data: any;
highestNodeId: number;
nodeLabelMap: Array<NodeLabel>;
}
type Phase = GraphPhase | InstructionsPhase | OtherPhase;
@ -333,8 +335,14 @@ export class SourceResolver {
this.positionToNodes.set(key, []);
}
const A = this.positionToNodes.get(key);
if (!A.includes(node.id)) A.push("" + node.id);
if (!A.includes(node.id)) A.push(`${node.id}`);
}
// Backwards compatibility.
if (typeof node.pos === "number") {
node.sourcePosition = { scriptOffset: node.pos, inliningId: -1 };
}
}
}
@ -435,6 +443,7 @@ export class SourceResolver {
}
parsePhases(phases) {
const nodeLabelMap = [];
for (const [, phase] of Object.entries<Phase>(phases)) {
switch (phase.type) {
case 'disassembly':
@ -463,6 +472,8 @@ export class SourceResolver {
const graphPhase: GraphPhase = Object.assign(phase, { highestNodeId: 0 });
this.phases.push(graphPhase);
this.recordOrigins(graphPhase);
this.internNodeLabels(graphPhase, nodeLabelMap);
graphPhase.nodeLabelMap = nodeLabelMap.slice();
this.phaseNames.set(graphPhase.name, this.phases.length);
break;
default:
@ -471,6 +482,19 @@ export class SourceResolver {
}
}
internNodeLabels(phase: GraphPhase, nodeLabelMap: Array<NodeLabel>) {
for (const n of phase.data.nodes) {
const label = new NodeLabel(n.id, n.label, n.title, n.live,
n.properties, n.sourcePosition, n.origin, n.opcode, n.control,
n.opinfo, n.type);
const previous = nodeLabelMap[label.id];
if (!label.equals(previous)) {
nodeLabelMap[label.id] = label;
}
n.nodeLabel = nodeLabelMap[label.id];
}
}
repairPhaseId(anyPhaseId) {
return Math.max(0, Math.min(anyPhaseId, this.phases.length - 1))
}

View File

@ -31,6 +31,6 @@ export abstract class View {
}
export interface PhaseView {
onresize();
searchInputAction(searchInput: HTMLInputElement, e: Event);
onresize(): void;
searchInputAction(searchInput: HTMLInputElement, e: Event): void;
}

View File

@ -16,6 +16,7 @@
"src/node.ts",
"src/edge.ts",
"src/graph.ts",
"src/node-label.ts",
"src/source-resolver.ts",
"src/selection.ts",
"src/selection-broker.ts",