diff --git a/tools/turbolizer/src/graph-layout.ts b/tools/turbolizer/src/graph-layout.ts index 1c74e6b8e4..c417a263d9 100644 --- a/tools/turbolizer/src/graph-layout.ts +++ b/tools/turbolizer/src/graph-layout.ts @@ -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> = []; // 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) { 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 { diff --git a/tools/turbolizer/src/graph-view.ts b/tools/turbolizer/src/graph-view.ts index 9d0d145faf..2288b29a0a 100644 --- a/tools/turbolizer/src/graph-view.ts +++ b/tools/turbolizer/src/graph-view.ts @@ -32,7 +32,7 @@ interface GraphState { export class GraphView extends View implements PhaseView { divElement: d3.Selection; svg: d3.Selection; - showPhaseByName: (string) => void; + showPhaseByName: (s: string) => void; state: GraphState; selectionHandler: NodeSelectionHandler & ClearableHandler; graphElement: d3.Selection; @@ -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, 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) diff --git a/tools/turbolizer/src/graph.ts b/tools/turbolizer/src/graph.ts index bcd2bc9d10..dfbbecf49d 100644 --- a/tools/turbolizer/src/graph.ts +++ b/tools/turbolizer/src/graph.ts @@ -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) => { diff --git a/tools/turbolizer/src/node-label.ts b/tools/turbolizer/src/node-label.ts new file mode 100644 index 0000000000..03a7d9e674 --- /dev/null +++ b/tools/turbolizer/src/node-label.ts @@ -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; + } + +} \ No newline at end of file diff --git a/tools/turbolizer/src/node.ts b/tools/turbolizer/src/node.ts index 79386dc235..62657d0045 100644 --- a/tools/turbolizer/src/node.ts +++ b/tools/turbolizer/src/node.ts @@ -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; - width: number; - properties: string; - title: string; - label: string; - origin: NodeOrigin; - outputs: Array; - outputApproach: number; - type: string; id: number; + nodeLabel: NodeLabel; + displayLabel: string; + inputs: Array; + outputs: Array; + 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"); } }; diff --git a/tools/turbolizer/src/source-resolver.ts b/tools/turbolizer/src/source-resolver.ts index eb89339767..de9c67e1cd 100644 --- a/tools/turbolizer/src/source-resolver.ts +++ b/tools/turbolizer/src/source-resolver.ts @@ -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; } 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(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) { + 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)) } diff --git a/tools/turbolizer/src/view.ts b/tools/turbolizer/src/view.ts index 1891aa3939..519951e50a 100644 --- a/tools/turbolizer/src/view.ts +++ b/tools/turbolizer/src/view.ts @@ -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; } diff --git a/tools/turbolizer/tsconfig.json b/tools/turbolizer/tsconfig.json index ee61f71985..78dc23ec82 100644 --- a/tools/turbolizer/tsconfig.json +++ b/tools/turbolizer/tsconfig.json @@ -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",