From 41d5c9cb15964b426e26d7dec86cb36e2439284d Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Mon, 22 Aug 2022 23:23:27 +0300 Subject: [PATCH] [turbolizer] Source and bytecode positions New features: - bytecode source view handlers - turboshaft's nodes origins - turboshaft's nodes history - turboshaft's nodes source/bytecode positions Bug: v8:7327 Change-Id: Icb240dd84762284f1aa37db3c93bd133f8e70960 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3829481 Reviewed-by: Nico Hartmann Commit-Queue: Danylo Boiko Cr-Commit-Position: refs/heads/main@{#82682} --- tools/turbolizer/info-view.html | 8 +- tools/turbolizer/src/graphmultiview.ts | 26 +- tools/turbolizer/src/node-label.ts | 10 +- tools/turbolizer/src/origin.ts | 8 +- .../src/phases/graph-phase/graph-node.ts | 14 + .../src/phases/graph-phase/graph-phase.ts | 100 ++++--- .../src/phases/instructions-phase.ts | 12 + tools/turbolizer/src/phases/phase.ts | 7 +- tools/turbolizer/src/phases/schedule-phase.ts | 6 + tools/turbolizer/src/phases/sequence-phase.ts | 6 + .../turboshaft-graph-node.ts | 28 +- .../turboshaft-graph-phase.ts | 86 +++++- tools/turbolizer/src/position.ts | 56 +++- .../src/selection/selection-broker.ts | 70 ++++- .../src/selection/selection-handler.ts | 9 +- tools/turbolizer/src/source-resolver.ts | 260 ++++++++++++------ tools/turbolizer/src/source.ts | 5 +- tools/turbolizer/src/turbo-visualizer.ts | 2 +- .../src/views/bytecode-source-view.ts | 70 +++++ tools/turbolizer/src/views/code-view.ts | 3 +- tools/turbolizer/src/views/graph-view.ts | 28 +- tools/turbolizer/src/views/history-view.ts | 67 +++-- tools/turbolizer/src/views/movable-view.ts | 6 + .../src/views/turboshaft-graph-view.ts | 61 ++-- 24 files changed, 731 insertions(+), 217 deletions(-) diff --git a/tools/turbolizer/info-view.html b/tools/turbolizer/info-view.html index 6b396d1421..ea2ceaeb10 100644 --- a/tools/turbolizer/info-view.html +++ b/tools/turbolizer/info-view.html @@ -30,6 +30,10 @@ b Show graph with selected nodes for previous phase + + h + Show hovered node's history + a Select all nodes @@ -61,10 +65,6 @@ u Hide unselected nodes - - h - Show hovered node's history - diff --git a/tools/turbolizer/src/graphmultiview.ts b/tools/turbolizer/src/graphmultiview.ts index 53b1c95caa..8942526313 100644 --- a/tools/turbolizer/src/graphmultiview.ts +++ b/tools/turbolizer/src/graphmultiview.ts @@ -7,13 +7,14 @@ import { storageGetItem, storageSetItem } from "./common/util"; import { GraphView } from "./views/graph-view"; import { ScheduleView } from "./views/schedule-view"; import { SequenceView } from "./views/sequence-view"; -import { GenericPhase, SourceResolver } from "./source-resolver"; +import { DynamicPhase, SourceResolver } from "./source-resolver"; import { SelectionBroker } from "./selection/selection-broker"; import { PhaseView, View } from "./views/view"; import { GraphPhase } from "./phases/graph-phase/graph-phase"; import { PhaseType } from "./phases/phase"; import { TurboshaftGraphView } from "./views/turboshaft-graph-view"; import { SelectionStorage } from "./selection/selection-storage"; +import { TurboshaftGraphPhase } from "./phases/turboshaft-graph-phase/turboshaft-graph-phase"; const toolboxHTML = `
@@ -90,21 +91,23 @@ export class GraphMultiView extends View { const lastPhaseIndex = storageGetItem("lastSelectedPhase"); const initialPhaseIndex = this.sourceResolver.repairPhaseId(lastPhaseIndex); this.selectMenu.selectedIndex = initialPhaseIndex; - this.displayPhase(this.sourceResolver.getPhase(initialPhaseIndex)); + this.displayPhase(this.sourceResolver.getDynamicPhase(initialPhaseIndex)); } public displayPhaseByName(phaseName: string, selection?: SelectionStorage): void { this.currentPhaseView.hide(); const phaseId = this.sourceResolver.getPhaseIdByName(phaseName); this.selectMenu.selectedIndex = phaseId; - this.displayPhase(this.sourceResolver.getPhase(phaseId), selection); + this.displayPhase(this.sourceResolver.getDynamicPhase(phaseId), selection); } public onresize(): void { this.currentPhaseView?.onresize(); } - private displayPhase(phase: GenericPhase, selection?: SelectionStorage): void { + private displayPhase(phase: DynamicPhase, selection?: SelectionStorage): void { + this.sourceResolver.positions = phase.positions; + this.sourceResolver.instructionsPhase = phase.instructionsPhase; if (phase.type == PhaseType.Graph) { this.displayPhaseView(this.graph, phase, selection); } else if (phase.type == PhaseType.TurboshaftGraph) { @@ -116,7 +119,7 @@ export class GraphMultiView extends View { } } - private displayPhaseView(view: PhaseView, data: GenericPhase, selection?: SelectionStorage): + private displayPhaseView(view: PhaseView, data: DynamicPhase, selection?: SelectionStorage): void { const rememberedSelection = selection ? selection : this.hideCurrentPhase(); view.initializeContent(data, rememberedSelection); @@ -126,8 +129,8 @@ export class GraphMultiView extends View { private displayNextGraphPhase(): void { let nextPhaseIndex = this.selectMenu.selectedIndex + 1; while (nextPhaseIndex < this.sourceResolver.phases.length) { - const nextPhase = this.sourceResolver.getPhase(nextPhaseIndex); - if (nextPhase.isGraph()) { + const nextPhase = this.sourceResolver.getDynamicPhase(nextPhaseIndex); + if (nextPhase && nextPhase.isGraph()) { this.selectMenu.selectedIndex = nextPhaseIndex; storageSetItem("lastSelectedPhase", nextPhaseIndex); this.displayPhase(nextPhase); @@ -140,8 +143,8 @@ export class GraphMultiView extends View { private displayPreviousGraphPhase(): void { let previousPhaseIndex = this.selectMenu.selectedIndex - 1; while (previousPhaseIndex >= 0) { - const previousPhase = this.sourceResolver.getPhase(previousPhaseIndex); - if (previousPhase.isGraph()) { + const previousPhase = this.sourceResolver.getDynamicPhase(previousPhaseIndex); + if (previousPhase && previousPhase.isGraph()) { this.selectMenu.selectedIndex = previousPhaseIndex; storageSetItem("lastSelectedPhase", previousPhaseIndex); this.displayPhase(previousPhase); @@ -157,7 +160,8 @@ export class GraphMultiView extends View { for (const phase of view.sourceResolver.phases) { const optionElement = document.createElement("option"); let maxNodeId = ""; - if (phase instanceof GraphPhase && phase.highestNodeId != 0) { + if ((phase instanceof GraphPhase || phase instanceof TurboshaftGraphPhase) + && phase.highestNodeId != 0) { maxNodeId = ` ${phase.highestNodeId}`; } optionElement.text = `${phase.name}${maxNodeId}`; @@ -166,7 +170,7 @@ export class GraphMultiView extends View { this.selectMenu.onchange = function (this: HTMLSelectElement) { const phaseIndex = this.selectedIndex; storageSetItem("lastSelectedPhase", phaseIndex); - view.displayPhase(view.sourceResolver.getPhase(phaseIndex)); + view.displayPhase(view.sourceResolver.getDynamicPhase(phaseIndex)); }; } diff --git a/tools/turbolizer/src/node-label.ts b/tools/turbolizer/src/node-label.ts index 4b6d52335e..4ad6bbea16 100644 --- a/tools/turbolizer/src/node-label.ts +++ b/tools/turbolizer/src/node-label.ts @@ -11,7 +11,8 @@ export class NodeLabel { title: string; live: boolean; properties: string; - sourcePosition: SourcePosition | BytecodePosition; + sourcePosition: SourcePosition; + bytecodePosition: BytecodePosition; origin: NodeOrigin | BytecodeOrigin; opcode: string; control: boolean; @@ -20,15 +21,16 @@ export class NodeLabel { inplaceUpdatePhase: string; constructor(id: number, label: string, title: string, live: boolean, - properties: string, sourcePosition: SourcePosition | BytecodePosition, - origin: NodeOrigin | BytecodeOrigin, opcode: string, control: boolean, - opinfo: string, type: string) { + properties: string, sourcePosition: SourcePosition, + bytecodePosition: BytecodePosition, origin: NodeOrigin | BytecodeOrigin, + 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.bytecodePosition = bytecodePosition; this.origin = origin; this.opcode = opcode; this.control = control; diff --git a/tools/turbolizer/src/origin.ts b/tools/turbolizer/src/origin.ts index 9f80462c4b..8a0cdf65ad 100644 --- a/tools/turbolizer/src/origin.ts +++ b/tools/turbolizer/src/origin.ts @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import { GraphNode } from "./phases/graph-phase/graph-node"; +import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node"; + export abstract class Origin { phase: string; reducer: string; @@ -14,10 +17,13 @@ export abstract class Origin { export class NodeOrigin extends Origin { nodeId: number; + node: GraphNode | TurboshaftGraphNode; - constructor(nodeId: number, phase: string, reducer: string) { + constructor(nodeId: number, node: GraphNode | TurboshaftGraphNode, phase: string, + reducer: string) { super(phase, reducer); this.nodeId = nodeId; + this.node = node; } public identifier(): string { diff --git a/tools/turbolizer/src/phases/graph-phase/graph-node.ts b/tools/turbolizer/src/phases/graph-phase/graph-node.ts index 25c299189a..3ffe02cb8d 100644 --- a/tools/turbolizer/src/phases/graph-phase/graph-node.ts +++ b/tools/turbolizer/src/phases/graph-phase/graph-node.ts @@ -7,6 +7,7 @@ import { alignUp, measureText } from "../../common/util"; import { NodeLabel } from "../../node-label"; import { Node } from "../../node"; import { GraphEdge } from "./graph-edge"; +import { NodeOrigin } from "../../origin"; export class GraphNode extends Node { nodeLabel: NodeLabel; @@ -79,6 +80,19 @@ export class GraphNode extends Node { return this.nodeLabel.getTitle(); } + public getHistoryLabel(): string { + return `${this.id} ${this.nodeLabel.opcode}`; + } + + public getNodeOrigin(): NodeOrigin { + const origin = this.nodeLabel.origin; + return origin instanceof NodeOrigin ? origin : null; + } + + public getInplaceUpdatePhase(): string { + return this?.nodeLabel?.inplaceUpdatePhase; + } + public getDisplayLabel(): string { return this.nodeLabel.getDisplayLabel(); } diff --git a/tools/turbolizer/src/phases/graph-phase/graph-phase.ts b/tools/turbolizer/src/phases/graph-phase/graph-phase.ts index 34870bd019..bb0b9ed1f5 100644 --- a/tools/turbolizer/src/phases/graph-phase/graph-phase.ts +++ b/tools/turbolizer/src/phases/graph-phase/graph-phase.ts @@ -5,81 +5,109 @@ import { Phase, PhaseType } from "../phase"; import { NodeLabel } from "../../node-label"; import { BytecodeOrigin, NodeOrigin } from "../../origin"; -import { SourcePosition } from "../../position"; import { GraphNode } from "./graph-node"; import { GraphEdge } from "./graph-edge"; +import { Source } from "../../source"; +import { InstructionsPhase } from "../instructions-phase"; +import { + BytecodePosition, + InliningPosition, + PositionsContainer, + SourcePosition +} from "../../position"; export class GraphPhase extends Phase { - highestNodeId: number; data: GraphData; stateType: GraphStateType; - nodeLabelMap: Array; + instructionsPhase: InstructionsPhase; nodeIdToNodeMap: Array; originIdToNodesMap: Map>; + positions: PositionsContainer; + highestNodeId: number; rendered: boolean; transform: { x: number, y: number, scale: number }; - constructor(name: string, highestNodeId: number, dataJson, nodeLabelMap?: Array) { + constructor(name: string, dataJson, nodeMap: Array, sources: Array, + inlinings: Array) { super(name, PhaseType.Graph); - this.highestNodeId = highestNodeId; this.data = new GraphData(); this.stateType = GraphStateType.NeedToFullRebuild; + this.instructionsPhase = new InstructionsPhase(); this.nodeIdToNodeMap = new Array(); this.originIdToNodesMap = new Map>(); + this.positions = new PositionsContainer(); + this.highestNodeId = 0; this.rendered = false; - this.parseDataFromJSON(dataJson, nodeLabelMap); - this.nodeLabelMap = nodeLabelMap?.slice(); + this.parseDataFromJSON(dataJson, nodeMap, sources, inlinings); } - private parseDataFromJSON(dataJson, nodeLabelMap: Array): void { + private parseDataFromJSON(dataJson, nodeMap: Array, sources: Array, + inlinings: Array): void { this.data = new GraphData(); - this.nodeIdToNodeMap = this.parseNodesFromJSON(dataJson.nodes, nodeLabelMap); + this.parseNodesFromJSON(dataJson.nodes, nodeMap, sources, inlinings); this.parseEdgesFromJSON(dataJson.edges); } - private parseNodesFromJSON(nodesJSON, nodeLabelMap: Array): Array { - const nodeIdToNodeMap = new Array(); + private parseNodesFromJSON(nodesJSON, nodeMap: Array, sources: Array, + inlinings: Array): void { for (const node of nodesJSON) { - let origin: NodeOrigin | BytecodeOrigin = null; - const jsonOrigin = node.origin; - if (jsonOrigin) { - if (jsonOrigin.nodeId) { - origin = new NodeOrigin(jsonOrigin.nodeId, jsonOrigin.phase, jsonOrigin.reducer); - } else { - origin = new BytecodeOrigin(jsonOrigin.bytecodePosition, jsonOrigin.phase, - jsonOrigin.reducer); - } - } - let sourcePosition: SourcePosition = null; - if (node.sourcePosition) { - const scriptOffset = node.sourcePosition.scriptOffset; - const inliningId = node.sourcePosition.inliningId; + const sourcePositionJson = node.sourcePosition; + if (sourcePositionJson) { + const scriptOffset = sourcePositionJson.scriptOffset; + const inliningId = sourcePositionJson.inliningId; sourcePosition = new SourcePosition(scriptOffset, inliningId); } - const label = new NodeLabel(node.id, node.label, node.title, node.live, node.properties, - sourcePosition, origin, node.opcode, node.control, node.opinfo, node.type); - - const previous = nodeLabelMap[label.id]; - if (!label.equals(previous)) { - if (previous !== undefined) { - label.setInplaceUpdatePhase(this.name); + let origin: NodeOrigin | BytecodeOrigin = null; + let bytecodePosition: BytecodePosition = null; + const originJson = node.origin; + if (originJson) { + const nodeId = originJson.nodeId; + if (nodeId) { + origin = new NodeOrigin(nodeId, nodeMap[nodeId], originJson.phase, originJson.reducer); + bytecodePosition = nodeMap[nodeId]?.nodeLabel.bytecodePosition; + } else { + origin = new BytecodeOrigin(originJson.bytecodePosition, originJson.phase, + originJson.reducer); + const inliningId = sourcePosition ? sourcePosition.inliningId : -1; + bytecodePosition = new BytecodePosition(originJson.bytecodePosition, inliningId); } - nodeLabelMap[label.id] = label; } + + const label = new NodeLabel(node.id, node.label, node.title, node.live, node.properties, + sourcePosition, bytecodePosition, origin, node.opcode, node.control, node.opinfo, + node.type); + const newNode = new GraphNode(label); this.data.nodes.push(newNode); - nodeIdToNodeMap[newNode.identifier()] = newNode; - if (origin) { + this.highestNodeId = Math.max(this.highestNodeId, newNode.id); + this.nodeIdToNodeMap[newNode.identifier()] = newNode; + + const previous = nodeMap[newNode.id]; + if (!newNode.equals(previous)) { + if (previous) newNode.nodeLabel.setInplaceUpdatePhase(this.name); + nodeMap[newNode.id] = newNode; + } + + if (origin && origin instanceof NodeOrigin) { const identifier = origin.identifier(); if (!this.originIdToNodesMap.has(identifier)) { this.originIdToNodesMap.set(identifier, new Array()); } this.originIdToNodesMap.get(identifier).push(newNode); } + + if (sourcePosition) { + const inlining = inlinings[sourcePosition.inliningId]; + if (inlining) sources[inlining.sourceId].sourcePositions.push(sourcePosition); + this.positions.addSourcePosition(newNode.identifier(), sourcePosition); + } + + if (bytecodePosition) { + this.positions.addBytecodePosition(newNode.identifier(), bytecodePosition); + } } - return nodeIdToNodeMap; } private parseEdgesFromJSON(edgesJSON): void { diff --git a/tools/turbolizer/src/phases/instructions-phase.ts b/tools/turbolizer/src/phases/instructions-phase.ts index 261a6527ec..5e0d94dc48 100644 --- a/tools/turbolizer/src/phases/instructions-phase.ts +++ b/tools/turbolizer/src/phases/instructions-phase.ts @@ -37,6 +37,18 @@ export class InstructionsPhase extends Phase { return -1; } + public merge(other: InstructionsPhase): void { + if (!other) return; + if (this.name == "") this.name = "merged data"; + this.nodeIdToInstructionRange = new Array<[number, number]>(); + this.blockIdToInstructionRange = other.blockIdToInstructionRange; + this.instructionOffsetToPCOffset = other.instructionOffsetToPCOffset; + this.codeOffsetsInfo = other.codeOffsetsInfo; + this.instructionToPCOffset = other.instructionToPCOffset; + this.pcOffsetToInstructions = other.pcOffsetToInstructions; + this.pcOffsets = other.pcOffsets; + } + public instructionToPcOffsets(instruction: number): TurbolizerInstructionStartInfo { return this.instructionToPCOffset[instruction]; } diff --git a/tools/turbolizer/src/phases/phase.ts b/tools/turbolizer/src/phases/phase.ts index 7c90228f7f..523805aa4b 100644 --- a/tools/turbolizer/src/phases/phase.ts +++ b/tools/turbolizer/src/phases/phase.ts @@ -12,8 +12,11 @@ export abstract class Phase { } public isGraph(): boolean { - return this.type == PhaseType.Graph || - this.type == PhaseType.TurboshaftGraph; + return this.type == PhaseType.Graph || this.type == PhaseType.TurboshaftGraph; + } + + public isDynamic(): boolean { + return this.isGraph() || this.type == PhaseType.Schedule || this.type == PhaseType.Sequence; } } diff --git a/tools/turbolizer/src/phases/schedule-phase.ts b/tools/turbolizer/src/phases/schedule-phase.ts index 97629228de..85bab8c533 100644 --- a/tools/turbolizer/src/phases/schedule-phase.ts +++ b/tools/turbolizer/src/phases/schedule-phase.ts @@ -3,13 +3,19 @@ // found in the LICENSE file. import { Phase, PhaseType } from "./phase"; +import { PositionsContainer } from "../position"; +import { InstructionsPhase } from "./instructions-phase"; export class SchedulePhase extends Phase { data: ScheduleData; + instructionsPhase: InstructionsPhase; + positions: PositionsContainer; constructor(name: string, dataJson) { super(name, PhaseType.Schedule); this.data = new ScheduleData(); + this.instructionsPhase = new InstructionsPhase(); + this.positions = new PositionsContainer(); this.parseScheduleFromJSON(dataJson); } diff --git a/tools/turbolizer/src/phases/sequence-phase.ts b/tools/turbolizer/src/phases/sequence-phase.ts index 37f48d800d..df5cc30d8e 100644 --- a/tools/turbolizer/src/phases/sequence-phase.ts +++ b/tools/turbolizer/src/phases/sequence-phase.ts @@ -4,13 +4,19 @@ import * as C from "../common/constants"; import { Phase, PhaseType } from "./phase"; +import { PositionsContainer } from "../position"; +import { InstructionsPhase } from "./instructions-phase"; export class SequencePhase extends Phase { blocks: Array; + instructionsPhase: InstructionsPhase; + positions: PositionsContainer; registerAllocation: RegisterAllocation; constructor(name: string, blocksJSON, registerAllocationJSON) { super(name, PhaseType.Sequence); + this.instructionsPhase = new InstructionsPhase(); + this.positions = new PositionsContainer(); this.parseBlocksFromJSON(blocksJSON); this.parseRegisterAllocationFromJSON(registerAllocationJSON); } diff --git a/tools/turbolizer/src/phases/turboshaft-graph-phase/turboshaft-graph-node.ts b/tools/turbolizer/src/phases/turboshaft-graph-phase/turboshaft-graph-node.ts index a8a8811141..759aed54f2 100644 --- a/tools/turbolizer/src/phases/turboshaft-graph-phase/turboshaft-graph-node.ts +++ b/tools/turbolizer/src/phases/turboshaft-graph-phase/turboshaft-graph-node.ts @@ -7,17 +7,26 @@ import { measureText } from "../../common/util"; import { TurboshaftGraphEdge } from "./turboshaft-graph-edge"; import { TurboshaftGraphBlock } from "./turboshaft-graph-block"; import { Node } from "../../node"; +import { BytecodePosition, SourcePosition } from "../../position"; +import { NodeOrigin } from "../../origin"; export class TurboshaftGraphNode extends Node> { title: string; block: TurboshaftGraphBlock; + sourcePosition: SourcePosition; + bytecodePosition: BytecodePosition; + origin: NodeOrigin; opPropertiesType: OpPropertiesType; constructor(id: number, title: string, block: TurboshaftGraphBlock, - opPropertiesType: OpPropertiesType) { + sourcePosition: SourcePosition, bytecodePosition: BytecodePosition, + origin: NodeOrigin, opPropertiesType: OpPropertiesType) { super(id); this.title = title; this.block = block; + this.sourcePosition = sourcePosition; + this.bytecodePosition = bytecodePosition; + this.origin = origin; this.opPropertiesType = opPropertiesType; this.visible = true; } @@ -37,6 +46,9 @@ export class TurboshaftGraphNode extends Node 0) { title += `\nInputs: ${this.inputs.map(i => i.source.id).join(", ")}`; } @@ -46,10 +58,24 @@ export class TurboshaftGraphNode extends Node i.source.id).join(",")})`; } + + public equals(that?: TurboshaftGraphNode): boolean { + if (!that) return false; + if (this.id !== that.id) return false; + return this.title === that.title; + } } export enum OpPropertiesType { diff --git a/tools/turbolizer/src/phases/turboshaft-graph-phase/turboshaft-graph-phase.ts b/tools/turbolizer/src/phases/turboshaft-graph-phase/turboshaft-graph-phase.ts index 0c39138146..a5da370ad7 100644 --- a/tools/turbolizer/src/phases/turboshaft-graph-phase/turboshaft-graph-phase.ts +++ b/tools/turbolizer/src/phases/turboshaft-graph-phase/turboshaft-graph-phase.ts @@ -7,31 +7,51 @@ import { TurboshaftGraphNode } from "./turboshaft-graph-node"; import { TurboshaftGraphEdge } from "./turboshaft-graph-edge"; import { TurboshaftGraphBlock } from "./turboshaft-graph-block"; import { DataTarget, TurboshaftCustomDataPhase } from "../turboshaft-custom-data-phase"; +import { GraphNode } from "../graph-phase/graph-node"; +import { NodeOrigin } from "../../origin"; +import { Source } from "../../source"; +import { InstructionsPhase } from "../instructions-phase"; +import { + BytecodePosition, + InliningPosition, + PositionsContainer, + SourcePosition +} from "../../position"; export class TurboshaftGraphPhase extends Phase { data: TurboshaftGraphData; customData: TurboshaftCustomData; stateType: GraphStateType; + instructionsPhase: InstructionsPhase; nodeIdToNodeMap: Array; blockIdToBlockMap: Array; + originIdToNodesMap: Map>; + positions: PositionsContainer; + highestNodeId: number; rendered: boolean; customDataShowed: boolean; transform: { x: number, y: number, scale: number }; - constructor(name: string, dataJson) { + constructor(name: string, dataJson, nodeMap: Array, + sources: Array, inlinings: Array) { super(name, PhaseType.TurboshaftGraph); this.stateType = GraphStateType.NeedToFullRebuild; + this.instructionsPhase = new InstructionsPhase(); this.customData = new TurboshaftCustomData(); this.nodeIdToNodeMap = new Array(); this.blockIdToBlockMap = new Array(); + this.originIdToNodesMap = new Map>(); + this.positions = new PositionsContainer(); + this.highestNodeId = 0; this.rendered = false; - this.parseDataFromJSON(dataJson); + this.parseDataFromJSON(dataJson, nodeMap, sources, inlinings); } - private parseDataFromJSON(dataJson): void { + private parseDataFromJSON(dataJson, nodeMap: Array, + sources: Array, inlinings: Array): void { this.data = new TurboshaftGraphData(); this.parseBlocksFromJSON(dataJson.blocks); - this.parseNodesFromJSON(dataJson.nodes); + this.parseNodesFromJSON(dataJson.nodes, nodeMap, sources, inlinings); this.parseEdgesFromJSON(dataJson.edges); } @@ -52,14 +72,62 @@ export class TurboshaftGraphPhase extends Phase { } } - private parseNodesFromJSON(nodesJson): void { + private parseNodesFromJSON(nodesJson, nodeMap: Array, + sources: Array, inlinings: Array): void { for (const nodeJson of nodesJson) { const block = this.blockIdToBlockMap[nodeJson.block_id]; - const node = new TurboshaftGraphNode(nodeJson.id, nodeJson.title, - block, nodeJson.op_properties_type); + + let sourcePosition: SourcePosition = null; + const sourcePositionJson = nodeJson.sourcePosition; + if (sourcePositionJson) { + const scriptOffset = sourcePositionJson.scriptOffset; + const inliningId = sourcePositionJson.inliningId; + sourcePosition = new SourcePosition(scriptOffset, inliningId); + } + + let origin: NodeOrigin = null; + let bytecodePosition: BytecodePosition = null; + const originJson = nodeJson.origin; + if (originJson) { + const nodeId = originJson.nodeId; + const originNode = nodeMap[nodeId]; + origin = new NodeOrigin(nodeId, originNode, originJson.phase, originJson.reducer); + if (originNode) { + if (originNode instanceof GraphNode) { + bytecodePosition = originNode.nodeLabel.bytecodePosition; + } else { + bytecodePosition = originNode.bytecodePosition; + } + } + } + + const node = new TurboshaftGraphNode(nodeJson.id, nodeJson.title, block, sourcePosition, + bytecodePosition, origin, nodeJson.op_properties_type); + block.nodes.push(node); this.data.nodes.push(node); this.nodeIdToNodeMap[node.identifier()] = node; + this.highestNodeId = Math.max(this.highestNodeId, node.id); + + if (origin) { + const identifier = origin.identifier(); + if (!this.originIdToNodesMap.has(identifier)) { + this.originIdToNodesMap.set(identifier, new Array()); + } + this.originIdToNodesMap.get(identifier).push(node); + } + + nodeMap[node.id] = node; + + if (sourcePosition) { + const inlining = inlinings[sourcePosition.inliningId]; + if (inlining) sources[inlining.sourceId].sourcePositions.push(sourcePosition); + this.positions.addSourcePosition(node.identifier(), sourcePosition); + } + + if (bytecodePosition) { + this.positions.addBytecodePosition(node.identifier(), bytecodePosition); + } } for (const block of this.blockIdToBlockMap) { block.initCollapsedLabel(); @@ -127,7 +195,9 @@ export class TurboshaftCustomData { private concatCustomData(key: number, items: Map): string { let customData = ""; for (const [name, dataPhase] of items.entries()) { - customData += `\n${name}: ${dataPhase.data[key] ?? ""}`; + if (dataPhase.data[key] && dataPhase.data[key].length > 0) { + customData += `\n${name}: ${dataPhase.data[key]}`; + } } return customData; } diff --git a/tools/turbolizer/src/position.ts b/tools/turbolizer/src/position.ts index 2a87133842..265c63d935 100644 --- a/tools/turbolizer/src/position.ts +++ b/tools/turbolizer/src/position.ts @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node"; + export class InliningPosition { sourceId: number; inliningPosition: SourcePosition; @@ -43,9 +45,11 @@ export class SourcePosition { export class BytecodePosition { bytecodePosition: number; + inliningId: number; - constructor(bytecodePosition: number) { + constructor(bytecodePosition: number, inliningId: number) { this.bytecodePosition = bytecodePosition; + this.inliningId = inliningId; } public isValid(): boolean { @@ -53,6 +57,54 @@ export class BytecodePosition { } public toString(): string { - return `BCP:${this.bytecodePosition}`; + return `BCP:${this.inliningId}:${this.bytecodePosition}`; + } +} + +export class PositionsContainer { + nodeIdToSourcePositionMap: Array; + nodeIdToBytecodePositionMap: Array; + sourcePositionToNodes: Map>; + bytecodePositionToNodes: Map>; + + constructor() { + this.nodeIdToSourcePositionMap = new Array(); + this.nodeIdToBytecodePositionMap = new Array(); + this.sourcePositionToNodes = new Map>(); + this.bytecodePositionToNodes = new Map>(); + } + + public addSourcePosition(nodeIdentifier: string, sourcePosition: SourcePosition): void { + this.nodeIdToSourcePositionMap[nodeIdentifier] = sourcePosition; + const key = sourcePosition.toString(); + if (!this.sourcePositionToNodes.has(key)) { + this.sourcePositionToNodes.set(key, new Array()); + } + const nodes = this.sourcePositionToNodes.get(key); + if (!nodes.includes(nodeIdentifier)) nodes.push(nodeIdentifier); + } + + public addBytecodePosition(nodeIdentifier: string, bytecodePosition: BytecodePosition): void { + this.nodeIdToBytecodePositionMap[nodeIdentifier] = bytecodePosition; + const key = bytecodePosition.toString(); + if (!this.bytecodePositionToNodes.has(key)) { + this.bytecodePositionToNodes.set(key, new Array()); + } + const nodes = this.bytecodePositionToNodes.get(key); + if (!nodes.includes(nodeIdentifier)) nodes.push(nodeIdentifier); + } + + public merge(nodes: Array, replacements: Map): void { + for (const node of nodes) { + const sourcePosition = node.sourcePosition; + const bytecodePosition = node.bytecodePosition; + const nodeId = replacements.has(node.id) ? replacements.get(node.id) : node.id; + if (sourcePosition && !this.nodeIdToSourcePositionMap[nodeId]) { + this.addSourcePosition(String(nodeId), sourcePosition); + } + if (bytecodePosition && !this.nodeIdToBytecodePositionMap[nodeId]) { + this.addBytecodePosition(String(nodeId), bytecodePosition); + } + } } } diff --git a/tools/turbolizer/src/selection/selection-broker.ts b/tools/turbolizer/src/selection/selection-broker.ts index 3a1284cf36..9240551b32 100644 --- a/tools/turbolizer/src/selection/selection-broker.ts +++ b/tools/turbolizer/src/selection/selection-broker.ts @@ -4,6 +4,8 @@ import { GenericPosition, SourceResolver } from "../source-resolver"; import { GraphNode } from "../phases/graph-phase/graph-node"; +import { BytecodePosition } from "../position"; +import { TurboshaftGraphNode } from "../phases/turboshaft-graph-phase/turboshaft-graph-node"; import { ClearableHandler, SourcePositionSelectionHandler, @@ -11,7 +13,8 @@ import { BlockSelectionHandler, InstructionSelectionHandler, RegisterAllocationSelectionHandler, - HistoryHandler + HistoryHandler, + BytecodeOffsetSelectionHandler } from "./selection-handler"; export class SelectionBroker { @@ -22,6 +25,7 @@ export class SelectionBroker { blockHandlers: Array; instructionHandlers: Array; sourcePositionHandlers: Array; + bytecodeOffsetHandlers: Array; registerAllocationHandlers: Array; constructor(sourceResolver: SourceResolver) { @@ -32,6 +36,7 @@ export class SelectionBroker { this.blockHandlers = new Array(); this.instructionHandlers = new Array(); this.sourcePositionHandlers = new Array(); + this.bytecodeOffsetHandlers = new Array(); this.registerAllocationHandlers = new Array(); } @@ -74,15 +79,22 @@ export class SelectionBroker { this.sourcePositionHandlers.push(handler); } + public addBytecodeOffsetHandler(handler: BytecodeOffsetSelectionHandler & ClearableHandler): + void { + this.allHandlers.push(handler); + this.bytecodeOffsetHandlers.push(handler); + } + public addRegisterAllocatorHandler(handler: RegisterAllocationSelectionHandler & ClearableHandler): void { this.allHandlers.push(handler); this.registerAllocationHandlers.push(handler); } - public broadcastHistoryShow(from, node: GraphNode, phaseName: string): void { + public broadcastHistoryShow(from, node: GraphNode | TurboshaftGraphNode, phaseName: string): + void { for (const handler of this.historyHandlers) { - if (handler != from) handler.showTurbofanNodeHistory(node, phaseName); + if (handler != from) handler.showNodeHistory(node, phaseName); } } @@ -93,7 +105,7 @@ export class SelectionBroker { if (handler != from) handler.brokeredInstructionSelect([instructionOffsets], selected); } - // Select the lines from the source panel (left panel) + // Select the lines from the source and bytecode panels (left panels) const pcOffsets = this.sourceResolver.instructionsPhase .instructionsToKeyPcOffsets(instructionOffsets); @@ -103,12 +115,16 @@ export class SelectionBroker { for (const handler of this.sourcePositionHandlers) { if (handler != from) handler.brokeredSourcePositionSelect(sourcePositions, selected); } + const bytecodePositions = this.sourceResolver.nodeIdsToBytecodePositions(nodes); + for (const handler of this.bytecodeOffsetHandlers) { + if (handler != from) handler.brokeredBytecodeOffsetSelect(bytecodePositions, selected); + } } // The middle panel lines have already been selected so there's no need to reselect them. } public broadcastSourcePositionSelect(from, sourcePositions: Array, - selected: boolean): void { + selected: boolean, selectedNodes?: Set): void { sourcePositions = sourcePositions.filter(sourcePosition => { if (!sourcePosition.isValid()) { console.warn("Invalid source position"); @@ -128,6 +144,44 @@ export class SelectionBroker { if (handler != from) handler.brokeredNodeSelect(nodes, selected); } + // Select bytecode source panel (left panel) + const bytecodePositions = selectedNodes + ? this.sourceResolver.nodeIdsToBytecodePositions(selectedNodes) + : this.sourceResolver.nodeIdsToBytecodePositions(nodes); + for (const handler of this.bytecodeOffsetHandlers) { + if (handler != from) handler.brokeredBytecodeOffsetSelect(bytecodePositions, selected); + } + + this.selectInstructionsAndRegisterAllocations(from, nodes, selected); + } + + public broadcastBytecodePositionsSelect(from, bytecodePositions: Array, + selected: boolean): void { + bytecodePositions = bytecodePositions.filter(bytecodePosition => { + if (!bytecodePosition.isValid()) { + console.warn("Invalid bytecode position"); + return false; + } + return true; + }); + + // Select the lines from the bytecode panel (left panel) + for (const handler of this.bytecodeOffsetHandlers) { + if (handler != from) handler.brokeredBytecodeOffsetSelect(bytecodePositions, selected); + } + + // Select the nodes (middle panel) + const nodes = this.sourceResolver.bytecodePositionsToNodeIds(bytecodePositions); + for (const handler of this.nodeHandlers) { + if (handler != from) handler.brokeredNodeSelect(nodes, selected); + } + + // Select the lines from the source panel (left panel) + const sourcePositions = this.sourceResolver.nodeIdsToSourcePositions(nodes); + for (const handler of this.sourcePositionHandlers) { + if (handler != from) handler.brokeredSourcePositionSelect(sourcePositions, selected); + } + this.selectInstructionsAndRegisterAllocations(from, nodes, selected); } @@ -143,6 +197,12 @@ export class SelectionBroker { if (handler != from) handler.brokeredSourcePositionSelect(sourcePositions, selected); } + // Select bytecode source panel (left panel) + const bytecodePositions = this.sourceResolver.nodeIdsToBytecodePositions(nodes); + for (const handler of this.bytecodeOffsetHandlers) { + if (handler != from) handler.brokeredBytecodeOffsetSelect(bytecodePositions, selected); + } + this.selectInstructionsAndRegisterAllocations(from, nodes, selected); } diff --git a/tools/turbolizer/src/selection/selection-handler.ts b/tools/turbolizer/src/selection/selection-handler.ts index 3c62ea440c..af8d5a0cc7 100644 --- a/tools/turbolizer/src/selection/selection-handler.ts +++ b/tools/turbolizer/src/selection/selection-handler.ts @@ -6,13 +6,14 @@ import { TurboshaftGraphNode } from "../phases/turboshaft-graph-phase/turboshaft import { GraphNode } from "../phases/graph-phase/graph-node"; import { TurboshaftGraphBlock } from "../phases/turboshaft-graph-phase/turboshaft-graph-block"; import { GenericPosition } from "../source-resolver"; +import { BytecodePosition } from "../position"; export interface ClearableHandler { brokeredClear(): void; } export interface HistoryHandler { - showTurbofanNodeHistory(node: GraphNode, phaseName: string): void; + showNodeHistory(node: GraphNode | TurboshaftGraphNode, phaseName: string): void; } export interface NodeSelectionHandler { @@ -41,6 +42,12 @@ export interface SourcePositionSelectionHandler { brokeredSourcePositionSelect(sourcePositions: Array, selected: boolean): void; } +export interface BytecodeOffsetSelectionHandler { + select(offsets: Array, selected: boolean): void; + clear(): void; + brokeredBytecodeOffsetSelect(positions: Array, selected: boolean): void; +} + export interface RegisterAllocationSelectionHandler { // These are called instructionIds since the class of the divs is "instruction-id" select(instructionIds: Array, selected: boolean): void; diff --git a/tools/turbolizer/src/source-resolver.ts b/tools/turbolizer/src/source-resolver.ts index ac46ec7291..a4ab3c3b0f 100644 --- a/tools/turbolizer/src/source-resolver.ts +++ b/tools/turbolizer/src/source-resolver.ts @@ -6,36 +6,36 @@ import { camelize, sortUnique } from "./common/util"; import { PhaseType } from "./phases/phase"; import { GraphPhase } from "./phases/graph-phase/graph-phase"; import { DisassemblyPhase } from "./phases/disassembly-phase"; -import { BytecodePosition, InliningPosition, SourcePosition } from "./position"; import { InstructionsPhase } from "./phases/instructions-phase"; import { SchedulePhase } from "./phases/schedule-phase"; import { SequencePhase } from "./phases/sequence-phase"; -import { BytecodeOrigin } from "./origin"; import { BytecodeSource, BytecodeSourceData, Source } from "./source"; -import { NodeLabel } from "./node-label"; import { TurboshaftCustomDataPhase } from "./phases/turboshaft-custom-data-phase"; import { TurboshaftGraphPhase } from "./phases/turboshaft-graph-phase/turboshaft-graph-phase"; +import { GraphNode } from "./phases/graph-phase/graph-node"; +import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node"; +import { BytecodePosition, InliningPosition, PositionsContainer, SourcePosition } from "./position"; +import { NodeOrigin } from "./origin"; export type GenericPosition = SourcePosition | BytecodePosition; +export type DynamicPhase = GraphPhase | TurboshaftGraphPhase | SchedulePhase | SequencePhase; export type GenericPhase = GraphPhase | TurboshaftGraphPhase | TurboshaftCustomDataPhase | DisassemblyPhase | InstructionsPhase | SchedulePhase | SequencePhase; export class SourceResolver { - nodePositionMap: Array; sources: Array; bytecodeSources: Map; inlinings: Array; inliningsMap: Map; - positionToNodes: Map>; phases: Array; phaseNames: Map; disassemblyPhase: DisassemblyPhase; - instructionsPhase: InstructionsPhase; linePositionMap: Map>; + finalNodeOrigins: Array; + instructionsPhase: InstructionsPhase; + positions: PositionsContainer; constructor() { - // Maps node ids to source positions. - this.nodePositionMap = new Array(); // Maps source ids to source objects. this.sources = new Array(); // Maps bytecode source ids to bytecode source objects. @@ -44,15 +44,14 @@ export class SourceResolver { this.inlinings = new Array(); // Maps source position keys to inlinings. this.inliningsMap = new Map(); - // Maps source position keys to node ids. - this.positionToNodes = new Map>(); // Maps phase ids to phases. this.phases = new Array(); // Maps phase names to phaseIds. this.phaseNames = new Map(); - this.instructionsPhase = new InstructionsPhase(); // Maps line numbers to source positions this.linePositionMap = new Map>(); + // Maps node ids to node origin + this.finalNodeOrigins = new Array(); } public getMainFunction(jsonObj): Source { @@ -60,12 +59,10 @@ export class SourceResolver { // Backwards compatibility. if (typeof fncJson === "string") { return new Source(null, null, jsonObj.source, -1, true, - new Array(), jsonObj.sourcePosition, - jsonObj.sourcePosition + jsonObj.source.length); + jsonObj.sourcePosition, jsonObj.sourcePosition + jsonObj.source.length); } return new Source(fncJson.sourceName, fncJson.functionName, fncJson.sourceText, - fncJson.sourceId, false, new Array(), fncJson.startPosition, - fncJson.endPosition); + fncJson.sourceId, false, fncJson.startPosition, fncJson.endPosition); } public setInlinings(inliningsJson): void { @@ -88,8 +85,7 @@ export class SourceResolver { if (sourcesJson) { for (const [sourceId, source] of Object.entries(sourcesJson)) { const src = new Source(source.sourceName, source.functionName, source.sourceText, - source.sourceId, source.backwardsCompatibility, new Array(), - source.startPosition, source.endPosition); + source.sourceId, source.backwardsCompatibility, source.startPosition, source.endPosition); this.sources[sourceId] = src; } } @@ -115,41 +111,20 @@ export class SourceResolver { } } - public setNodePositionMap(mapJson): void { - if (!mapJson) return; - if (typeof mapJson[0] !== "object") { - const alternativeMap = new Map(); - for (const [nodeId, scriptOffset] of Object.entries(mapJson)) { - alternativeMap[nodeId] = new SourcePosition(scriptOffset, -1); - } - mapJson = alternativeMap; - } - - for (const [nodeId, sourcePosition] of Object.entries(mapJson)) { - if (sourcePosition === undefined) { - console.warn(`Undefined source position for node id ${nodeId}`); - } - const inlining = this.inlinings[sourcePosition.inliningId]; - const sp = new SourcePosition(sourcePosition.scriptOffset, sourcePosition.inliningId); - if (inlining) this.sources[inlining.sourceId].sourcePositions.push(sp); - this.nodePositionMap[nodeId] = sp; - const key = sp.toString(); - if (!this.positionToNodes.has(key)) { - this.positionToNodes.set(key, new Array()); - } - this.positionToNodes.get(key).push(nodeId); - } - - for (const [, source] of Object.entries(this.sources)) { - source.sourcePositions = sortUnique(source.sourcePositions, - (a, b) => a.lessOrEquals(b), - (a, b) => a.equals(b)); + public setFinalNodeOrigins(nodeOriginsJson): void { + if (!nodeOriginsJson) return; + for (const [nodeId, nodeOrigin] of Object.entries(nodeOriginsJson)) { + this.finalNodeOrigins[nodeId] = new NodeOrigin(nodeOrigin.nodeId, null, nodeOrigin.phase, + nodeOrigin.reducer); } } public parsePhases(phasesJson): void { - const nodeLabelMap = new Array(); + const instructionsPhase = new InstructionsPhase(); + const selectedDynamicPhases = new Array(); + const nodeMap = new Array(); let lastTurboshaftGraphPhase: TurboshaftGraphPhase = null; + let lastGraphPhase: GraphPhase | TurboshaftGraphPhase = null; for (const [, genericPhase] of Object.entries(phasesJson)) { switch (genericPhase.type) { case PhaseType.Disassembly: @@ -163,44 +138,58 @@ export class SourceResolver { const schedulePhase = new SchedulePhase(castedSchedule.name, castedSchedule.data); this.phaseNames.set(schedulePhase.name, this.phases.length); this.phases.push(schedulePhase); + selectedDynamicPhases.push(schedulePhase); + if (lastGraphPhase instanceof GraphPhase) { + schedulePhase.positions = lastGraphPhase.positions; + } else { + const oldIdToNewIdMap = this.getOldIdToNewIdMap(this.phases.length - 1); + schedulePhase.positions.merge(lastGraphPhase.data.nodes, oldIdToNewIdMap); + } + schedulePhase.instructionsPhase = instructionsPhase; break; case PhaseType.Sequence: const castedSequence = camelize(genericPhase) as SequencePhase; const sequencePhase = new SequencePhase(castedSequence.name, castedSequence.blocks, castedSequence.registerAllocation); + const prevPhase = this.getDynamicPhase(this.phases.length - 1); + sequencePhase.positions = prevPhase.positions; + sequencePhase.instructionsPhase = prevPhase.instructionsPhase; this.phaseNames.set(sequencePhase.name, this.phases.length); this.phases.push(sequencePhase); break; case PhaseType.Instructions: const castedInstructions = genericPhase as InstructionsPhase; - if (this.instructionsPhase.name === "") { - this.instructionsPhase.name = castedInstructions.name; + if (instructionsPhase.name === "") { + instructionsPhase.name = castedInstructions.name; } else { - this.instructionsPhase.name += `, ${castedInstructions.name}`; + instructionsPhase.name += `, ${castedInstructions.name}`; } - this.instructionsPhase.parseNodeIdToInstructionRangeFromJSON(castedInstructions + instructionsPhase.parseNodeIdToInstructionRangeFromJSON(castedInstructions ?.nodeIdToInstructionRange); - this.instructionsPhase.parseBlockIdToInstructionRangeFromJSON(castedInstructions + instructionsPhase.parseBlockIdToInstructionRangeFromJSON(castedInstructions ?.blockIdToInstructionRange); - this.instructionsPhase.parseInstructionOffsetToPCOffsetFromJSON(castedInstructions + instructionsPhase.parseInstructionOffsetToPCOffsetFromJSON(castedInstructions ?.instructionOffsetToPCOffset); - this.instructionsPhase.parseCodeOffsetsInfoFromJSON(castedInstructions - ?.codeOffsetsInfo); + instructionsPhase.parseCodeOffsetsInfoFromJSON(castedInstructions?.codeOffsetsInfo); break; case PhaseType.Graph: const castedGraph = genericPhase as GraphPhase; - const graphPhase = new GraphPhase(castedGraph.name, 0, castedGraph.data, nodeLabelMap); - this.recordOrigins(graphPhase); + const graphPhase = new GraphPhase(castedGraph.name, castedGraph.data, + nodeMap as Array, this.sources, this.inlinings); this.phaseNames.set(graphPhase.name, this.phases.length); this.phases.push(graphPhase); + selectedDynamicPhases.push(graphPhase); + lastGraphPhase = graphPhase; break; case PhaseType.TurboshaftGraph: const castedTurboshaftGraph = genericPhase as TurboshaftGraphPhase; const turboshaftGraphPhase = new TurboshaftGraphPhase(castedTurboshaftGraph.name, - castedTurboshaftGraph.data); + castedTurboshaftGraph.data, nodeMap, this.sources, this.inlinings); this.phaseNames.set(turboshaftGraphPhase.name, this.phases.length); this.phases.push(turboshaftGraphPhase); + selectedDynamicPhases.push(turboshaftGraphPhase); lastTurboshaftGraphPhase = turboshaftGraphPhase; + lastGraphPhase = turboshaftGraphPhase; break; case PhaseType.TurboshaftCustomData: const castedCustomData = camelize(genericPhase) as TurboshaftCustomDataPhase; @@ -212,12 +201,43 @@ export class SourceResolver { throw "Unsupported phase type"; } } + this.sortSourcePositions(); + this.instructionsPhase = instructionsPhase; + if (!lastTurboshaftGraphPhase) { + for (const phase of selectedDynamicPhases) { + if (!phase.isDynamic()) continue; + phase.instructionsPhase = instructionsPhase; + } + return; + } + if (instructionsPhase.name == "") return; + // Adapting 'nodeIdToInstructionRange' array to fix Turboshaft's nodes recreation + this.adaptInstructionsPhases(selectedDynamicPhases); } public sourcePositionsToNodeIds(sourcePositions: Array): Set { const nodeIds = new Set(); - for (const sp of sourcePositions) { - const nodeIdsForPosition = this.positionToNodes.get(sp.toString()); + for (const position of sourcePositions) { + const key = position.toString(); + let nodeIdsForPosition: Array = null; + if (position instanceof SourcePosition) { + nodeIdsForPosition = this.positions.sourcePositionToNodes.get(key); + } else { + // Wasm support + nodeIdsForPosition = this.positions.bytecodePositionToNodes.get(key); + } + if (!nodeIdsForPosition) continue; + for (const nodeId of nodeIdsForPosition) { + nodeIds.add(nodeId); + } + } + return nodeIds; + } + + public bytecodePositionsToNodeIds(bytecodePositions: Array): Set { + const nodeIds = new Set(); + for (const position of bytecodePositions) { + const nodeIdsForPosition = this.positions.bytecodePositionToNodes.get(position.toString()); if (!nodeIdsForPosition) continue; for (const nodeId of nodeIdsForPosition) { nodeIds.add(nodeId); @@ -229,9 +249,17 @@ export class SourceResolver { public nodeIdsToSourcePositions(nodeIds: Iterable): Array { const sourcePositions = new Map(); for (const nodeId of nodeIds) { - const position = this.nodePositionMap[nodeId]; - if (!position) continue; - sourcePositions.set(position.toString(), position); + const sourcePosition = this.positions.nodeIdToSourcePositionMap[nodeId]; + if (sourcePosition) { + sourcePositions.set(sourcePosition.toString(), sourcePosition); + } + // Wasm support + if (this.bytecodeSources.size == 0) { + const bytecodePosition = this.positions.nodeIdToBytecodePositionMap[nodeId]; + if (bytecodePosition) { + sourcePositions.set(bytecodePosition.toString(), bytecodePosition); + } + } } const sourcePositionArray = new Array(); for (const sourcePosition of sourcePositions.values()) { @@ -240,6 +268,16 @@ export class SourceResolver { return sourcePositionArray; } + public nodeIdsToBytecodePositions(nodeIds: Iterable): Array { + const bytecodePositions = new Map(); + for (const nodeId of nodeIds) { + const position = this.positions.nodeIdToBytecodePositionMap[nodeId]; + if (!position) continue; + bytecodePositions.set(position.toString(), position); + } + return Array.from(bytecodePositions.values()); + } + public translateToSourceId(sourceId: number, location?: SourcePosition): SourcePosition { for (const position of this.getInlineStack(location)) { const inlining = this.inlinings[position.inliningId]; @@ -287,6 +325,16 @@ export class SourceResolver { return this.phases[phaseId]; } + public getGraphPhase(phaseId: number): GraphPhase | TurboshaftGraphPhase { + const phase = this.phases[phaseId]; + return phase.isGraph() ? phase as GraphPhase | TurboshaftGraphPhase : null; + } + + public getDynamicPhase(phaseId: number): DynamicPhase { + const phase = this.phases[phaseId]; + return phase.isDynamic() ? phase as DynamicPhase : null; + } + public getPhaseNameById(phaseId: number): string { return this.getPhase(phaseId).name; } @@ -320,7 +368,7 @@ export class SourceResolver { public setSourceLineToBytecodePosition(sourceLineToBytecodePositionJson): void { if (!sourceLineToBytecodePositionJson) return; sourceLineToBytecodePositionJson.forEach((position, idx) => { - this.addAnyPositionToLine(idx, new BytecodePosition(position)); + this.addAnyPositionToLine(idx, new BytecodePosition(position, -1)); }); } @@ -340,22 +388,78 @@ export class SourceResolver { return inliningStack; } - private recordOrigins(graphPhase: GraphPhase): void { - if (graphPhase.type !== PhaseType.Graph) return; - for (const node of graphPhase.data.nodes) { - graphPhase.highestNodeId = Math.max(graphPhase.highestNodeId, node.id); - const origin = node.nodeLabel.origin; - if (origin instanceof BytecodeOrigin) { - const position = new BytecodePosition(origin.bytecodePosition); - this.nodePositionMap[node.id] = position; - const key = position.toString(); - if (!this.positionToNodes.has(key)) { - this.positionToNodes.set(key, new Array()); + private sortSourcePositions(): void { + for (const source of Object.values(this.sources)) { + source.sourcePositions = sortUnique(source.sourcePositions, + (a, b) => a.lessOrEquals(b), + (a, b) => a.equals(b)); + } + } + + private adaptInstructionsPhases(dynamicPhases: Array): void { + const seaOfNodesInstructions = new InstructionsPhase("sea of nodes"); + for (let phaseId = dynamicPhases.length - 2; phaseId >= 0; phaseId--) { + const phase = dynamicPhases[phaseId]; + const prevPhase = dynamicPhases[phaseId + 1]; + if (phase.type == PhaseType.TurboshaftGraph && prevPhase.type == PhaseType.Schedule) { + phase.instructionsPhase.merge(prevPhase.instructionsPhase); + const oldIdToNewIdMap = this.getOldIdToNewIdMap(phaseId + 1); + const maxNodeId = (phase as TurboshaftGraphPhase).highestNodeId; + for (let nodeId = 0; nodeId <= maxNodeId; nodeId++) { + const prevNodeId = oldIdToNewIdMap.has(nodeId) ? oldIdToNewIdMap.get(nodeId) : nodeId; + phase.instructionsPhase.nodeIdToInstructionRange[nodeId] = + prevPhase.instructionsPhase.getInstruction(prevNodeId); } - const nodes = this.positionToNodes.get(key); - const identifier = node.identifier(); - if (!nodes.includes(identifier)) nodes.push(identifier); + } else if (phase.type == PhaseType.TurboshaftGraph && + prevPhase.type == PhaseType.TurboshaftGraph) { + phase.instructionsPhase.merge(prevPhase.instructionsPhase); + const maxNodeId = (phase as TurboshaftGraphPhase).highestNodeId; + const originIdToNodesMap = (prevPhase as TurboshaftGraphPhase).originIdToNodesMap; + for (let nodeId = 0; nodeId <= maxNodeId; nodeId++) { + const nodes = originIdToNodesMap.get(String(nodeId)); + const prevNodeId = nodes?.length > 0 ? nodes[0]?.id : nodeId; + phase.instructionsPhase.nodeIdToInstructionRange[nodeId] = + prevPhase.instructionsPhase.getInstruction(prevNodeId); + } + } else if (phase.type == PhaseType.Schedule && prevPhase.type == PhaseType.TurboshaftGraph) { + seaOfNodesInstructions.merge(prevPhase.instructionsPhase); + phase.instructionsPhase = seaOfNodesInstructions; + const originIdToNodesMap = (prevPhase as TurboshaftGraphPhase).originIdToNodesMap; + for (const [originId, nodes] of originIdToNodesMap.entries()) { + if (!originId || nodes.length == 0) continue; + phase.instructionsPhase.nodeIdToInstructionRange[originId] = + prevPhase.instructionsPhase.getInstruction(nodes[0].id); + } + } else if (phase.type == PhaseType.Graph && prevPhase.type == PhaseType.Graph) { + phase.instructionsPhase = seaOfNodesInstructions; + const prevGraphPhase = prevPhase as GraphPhase; + for (const [originId, nodes] of prevGraphPhase.originIdToNodesMap.entries()) { + if (!originId || nodes.length == 0) continue; + for (const node of nodes) { + if (!phase.instructionsPhase.nodeIdToInstructionRange[originId]) { + if (!prevPhase.instructionsPhase.nodeIdToInstructionRange[node.id]) continue; + phase.instructionsPhase.nodeIdToInstructionRange[originId] = + prevPhase.instructionsPhase.getInstruction(node.id); + } else { + break; + } + } + } + } else { + phase.instructionsPhase = seaOfNodesInstructions; } } } + + private getOldIdToNewIdMap(phaseId: number): Map { + // This function works with final node origins (we can have overwriting for Turboshaft IR) + const oldIdToNewIdMap = new Map(); + for (const [newId, nodeOrigin] of this.finalNodeOrigins.entries()) { + if (!nodeOrigin) continue; + if (nodeOrigin.phase === this.phases[phaseId].name) { + oldIdToNewIdMap.set(nodeOrigin.nodeId, newId); + } + } + return oldIdToNewIdMap; + } } diff --git a/tools/turbolizer/src/source.ts b/tools/turbolizer/src/source.ts index 0fb94810df..c52c445866 100644 --- a/tools/turbolizer/src/source.ts +++ b/tools/turbolizer/src/source.ts @@ -15,16 +15,15 @@ export class Source { endPosition?: number; constructor(sourceName: string, functionName: string, sourceText: string, sourceId: number, - backwardsCompatibility: boolean, sourcePositions?: Array, - startPosition?: number, endPosition?: number) { + backwardsCompatibility: boolean, startPosition?: number, endPosition?: number) { this.sourceName = sourceName; this.functionName = functionName; this.sourceText = sourceText; this.sourceId = sourceId; this.backwardsCompatibility = backwardsCompatibility; - this.sourcePositions = sourcePositions ?? new Array(); this.startPosition = startPosition; this.endPosition = endPosition; + this.sourcePositions = new Array(); } public toString(): string { diff --git a/tools/turbolizer/src/turbo-visualizer.ts b/tools/turbolizer/src/turbo-visualizer.ts index 7680e69475..7e9678590c 100644 --- a/tools/turbolizer/src/turbo-visualizer.ts +++ b/tools/turbolizer/src/turbo-visualizer.ts @@ -70,7 +70,7 @@ window.onload = function () { sourceResolver.setSourceLineToBytecodePosition(jsonObj.sourceLineToBytecodePosition); sourceResolver.setSources(jsonObj.sources, mainFunction); sourceResolver.setBytecodeSources(jsonObj.bytecodeSources); - sourceResolver.setNodePositionMap(jsonObj.nodePositions); + sourceResolver.setFinalNodeOrigins(jsonObj.nodeOrigins); sourceResolver.parsePhases(jsonObj.phases); const [sourceTab, sourceContainer] = sourceTabs.addTabAndContent("Source"); diff --git a/tools/turbolizer/src/views/bytecode-source-view.ts b/tools/turbolizer/src/views/bytecode-source-view.ts index 9670990035..9ab061c323 100644 --- a/tools/turbolizer/src/views/bytecode-source-view.ts +++ b/tools/turbolizer/src/views/bytecode-source-view.ts @@ -7,6 +7,10 @@ import { CodeMode, View } from "./view"; import { SelectionBroker } from "../selection/selection-broker"; import { BytecodeSource } from "../source"; import { SourceResolver } from "../source-resolver"; +import { SelectionMap } from "../selection/selection-map"; +import { ViewElements } from "../common/view-elements"; +import { BytecodeOffsetSelectionHandler, ClearableHandler } from "../selection/selection-handler"; +import { BytecodePosition } from "../position"; export class BytecodeSourceView extends View { broker: SelectionBroker; @@ -14,6 +18,8 @@ export class BytecodeSourceView extends View { sourceResolver: SourceResolver; codeMode: CodeMode; bytecodeOffsetToHtmlElement: Map; + bytecodeOffsetSelection: SelectionMap; + bytecodeOffsetSelectionHandler: BytecodeOffsetSelectionHandler & ClearableHandler; constructor(parent: HTMLElement, broker: SelectionBroker, sourceFunction: BytecodeSource, sourceResolver: SourceResolver, codeMode: CodeMode) { @@ -23,6 +29,9 @@ export class BytecodeSourceView extends View { this.sourceResolver = sourceResolver; this.codeMode = codeMode; this.bytecodeOffsetToHtmlElement = new Map(); + this.bytecodeOffsetSelection = new SelectionMap((offset: number) => String(offset)); + this.bytecodeOffsetSelectionHandler = this.initializeBytecodeOffsetSelectionHandler(); + this.broker.addBytecodeOffsetHandler(this.bytecodeOffsetSelectionHandler); this.initializeCode(); } @@ -86,6 +95,44 @@ export class BytecodeSourceView extends View { codePre.appendChild(constantList); } + private initializeBytecodeOffsetSelectionHandler(): BytecodeOffsetSelectionHandler + & ClearableHandler { + const view = this; + const broker = this.broker; + return { + select: function (offsets: Array, selected: boolean) { + const bytecodePositions = new Array(); + for (const offset of offsets) { + bytecodePositions.push(new BytecodePosition(offset, view.source.sourceId)); + } + view.bytecodeOffsetSelection.select(offsets, selected); + view.updateSelection(); + broker.broadcastBytecodePositionsSelect(this, bytecodePositions, selected); + }, + clear: function () { + view.bytecodeOffsetSelection.clear(); + view.updateSelection(); + broker.broadcastClear(this); + }, + brokeredBytecodeOffsetSelect: function (positions: Array, + selected: boolean) { + const offsets = new Array(); + const firstSelect = view.bytecodeOffsetSelection.isEmpty(); + for (const position of positions) { + if (position.inliningId == view.source.sourceId) { + offsets.push(position.bytecodePosition); + } + } + view.bytecodeOffsetSelection.select(offsets, selected); + view.updateSelection(firstSelect); + }, + brokeredClear: function () { + view.bytecodeOffsetSelection.clear(); + view.updateSelection(); + }, + }; + } + private getBytecodeHeaderHtmlElementName(): string { return `source-pre-${this.source.sourceId}-header`; } @@ -98,13 +145,36 @@ export class BytecodeSourceView extends View { return this.codeMode == CodeMode.MainSource ? "main-source" : "inlined-source"; } + private updateSelection(scrollIntoView: boolean = false): void { + const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement); + for (const [offset, element] of this.bytecodeOffsetToHtmlElement.entries()) { + const key = this.bytecodeOffsetSelection.stringKey(offset); + const isSelected = this.bytecodeOffsetSelection.isKeySelected(key); + mkVisible.consider(element, isSelected); + element.classList.toggle("selected", isSelected); + } + mkVisible.apply(scrollIntoView); + } + + private onSelectBytecodeOffset(offset: number, doClear: boolean) { + if (doClear) { + this.bytecodeOffsetSelectionHandler.clear(); + } + this.bytecodeOffsetSelectionHandler.select([offset], undefined); + } + private insertLineContent(lineElement: HTMLElement, content: string): void { const lineContentElement = createElement("span", "", content); lineElement.appendChild(lineContentElement); } private insertLineNumber(lineElement: HTMLElement, lineNumber: number): void { + const view = this; const lineNumberElement = createElement("div", "line-number", String(lineNumber)); + lineNumberElement.onclick = function (e: MouseEvent) { + e.stopPropagation(); + view.onSelectBytecodeOffset(lineNumber, !e.shiftKey); + }; lineElement.insertBefore(lineNumberElement, lineElement.firstChild); } } diff --git a/tools/turbolizer/src/views/code-view.ts b/tools/turbolizer/src/views/code-view.ts index 8968c69664..3af3fec0bb 100644 --- a/tools/turbolizer/src/views/code-view.ts +++ b/tools/turbolizer/src/views/code-view.ts @@ -182,7 +182,8 @@ export class CodeView extends View { }; } - private addHtmlElementToSourcePosition(sourcePosition, element): void { + private addHtmlElementToSourcePosition(sourcePosition: GenericPosition, element: HTMLElement): + void { const key = sourcePosition.toString(); if (!this.sourcePositionToHtmlElements.has(key)) { this.sourcePositionToHtmlElements.set(key, new Array()); diff --git a/tools/turbolizer/src/views/graph-view.ts b/tools/turbolizer/src/views/graph-view.ts index af9b0e26a0..98403e6a01 100644 --- a/tools/turbolizer/src/views/graph-view.ts +++ b/tools/turbolizer/src/views/graph-view.ts @@ -12,8 +12,7 @@ import { GraphNode } from "../phases/graph-phase/graph-node"; import { GraphEdge } from "../phases/graph-phase/graph-edge"; import { GraphLayout } from "../graph-layout"; import { GraphPhase, GraphStateType } from "../phases/graph-phase/graph-phase"; -import { BytecodePosition } from "../position"; -import { BytecodeOrigin, NodeOrigin } from "../origin"; +import { NodeOrigin } from "../origin"; import { MovableView } from "./movable-view"; import { ClearableHandler, NodeSelectionHandler } from "../selection/selection-handler"; import { GenericPosition } from "../source-resolver"; @@ -32,8 +31,10 @@ export class GraphView extends MovableView { toolbox: HTMLElement) { super(idOrContainer, broker, showPhaseByName, toolbox); - this.state.selection = new SelectionMap(node => node.identifier(), - node => node.nodeLabel?.origin?.identifier()); + this.state.selection = new SelectionMap(node => node.identifier(), node => { + if (node instanceof GraphNode) return node.nodeLabel?.origin?.identifier(); + return node?.origin?.identifier(); + }); this.nodeSelectionHandler = this.initializeNodeSelectionHandler(); this.svg.on("click", () => this.nodeSelectionHandler.clear()); @@ -406,16 +407,19 @@ export class GraphView extends MovableView { return { select: function (selectedNodes: Array, selected: boolean) { const locations = new Array(); + const nodes = new Set(); for (const node of selectedNodes) { if (node.nodeLabel.sourcePosition) { locations.push(node.nodeLabel.sourcePosition); + nodes.add(node.identifier()); } - if (node.nodeLabel.origin && node.nodeLabel.origin instanceof BytecodeOrigin) { - locations.push(new BytecodePosition(node.nodeLabel.origin.bytecodePosition)); + if (node.nodeLabel.bytecodePosition) { + locations.push(node.nodeLabel.bytecodePosition); + nodes.add(node.identifier()); } } view.state.selection.select(selectedNodes, selected); - view.broker.broadcastSourcePositionSelect(this, locations, selected); + view.broker.broadcastSourcePositionSelect(this, locations, selected, nodes); view.updateGraphVisibility(); }, clear: function () { @@ -434,10 +438,10 @@ export class GraphView extends MovableView { if (!node) continue; node.visible = true; node.inputs.forEach(edge => { - edge.visible = edge.visible || view.state.selection.isSelected(edge.source); + edge.visible = edge.visible || edge.source.visible; }); node.outputs.forEach(edge => { - edge.visible = edge.visible || view.state.selection.isSelected(edge.target); + edge.visible = edge.visible || edge.target.visible; }); } view.updateGraphVisibility(); @@ -710,12 +714,6 @@ export class GraphView extends MovableView { this.updateGraphVisibility(); } - public showHoveredNodeHistory(): void { - const node = this.graph.nodeMap[this.hoveredNodeIdentifier]; - if (!node) return; - this.broker.broadcastHistoryShow(null, node, this.phaseName); - } - private selectOrigins(): void { const selection = new SelectionStorage(); const origins = new Array(); diff --git a/tools/turbolizer/src/views/history-view.ts b/tools/turbolizer/src/views/history-view.ts index 4ed2c0e7c5..c31e40e99d 100644 --- a/tools/turbolizer/src/views/history-view.ts +++ b/tools/turbolizer/src/views/history-view.ts @@ -11,11 +11,15 @@ import { SourceResolver } from "../source-resolver"; import { GraphNode } from "../phases/graph-phase/graph-node"; import { HistoryHandler } from "../selection/selection-handler"; import { GraphPhase } from "../phases/graph-phase/graph-phase"; -import { NodeOrigin } from "../origin"; import { SelectionStorage } from "../selection/selection-storage"; +import { TurboshaftGraphNode } from "../phases/turboshaft-graph-phase/turboshaft-graph-node"; +import { TurboshaftGraphPhase } from "../phases/turboshaft-graph-phase/turboshaft-graph-phase"; + +type GNode = GraphNode | TurboshaftGraphNode; +type GPhase = GraphPhase | TurboshaftGraphPhase; export class HistoryView extends View { - node: GraphNode; + node: GNode; broker: SelectionBroker; sourceResolver: SourceResolver; historyHandler: HistoryHandler; @@ -78,7 +82,7 @@ export class HistoryView extends View { private initializeNodeSelectionHandler(): HistoryHandler { const view = this; return { - showTurbofanNodeHistory: function (node: GraphNode, phaseName: string) { + showNodeHistory: function (node: GNode, phaseName: string) { view.clear(); view.node = node; const phaseId = view.sourceResolver.getPhaseIdByName(phaseName); @@ -264,7 +268,7 @@ export class HistoryView extends View { } private setLabel(): void { - this.label = `${this.node.id} ${this.node.nodeLabel.opcode}`; + this.label = this.node.getHistoryLabel(); const coefficient = this.getCoefficient("history-tspan-font-size"); this.labelBox = measureText(this.label, coefficient); } @@ -275,14 +279,14 @@ export class HistoryView extends View { return Math.min(tspanSize, varSize) / Math.max(tspanSize, varSize); } - private getPhaseHistory(historyChain: Map): void { + private getPhaseHistory(historyChain: Map): void { const uniqueAncestors = new Set(); const coefficient = this.getCoefficient("history-item-tspan-font-size"); let prevNode = null; let first = true; for (let i = 0; i < this.sourceResolver.phases.length; i++) { - const phase = this.sourceResolver.getPhase(i); - if (!(phase instanceof GraphPhase)) continue; + const phase = this.sourceResolver.getGraphPhase(i); + if (!phase) continue; const phaseNameMeasure = measureText(phase.name, coefficient); this.maxPhaseNameWidth = Math.max(this.maxPhaseNameWidth, phaseNameMeasure.width); @@ -298,10 +302,11 @@ export class HistoryView extends View { if (prevNode && !prevNode.equals(node) && phase.originIdToNodesMap.has(prevNode.identifier())) { const prevNodeCurrentState = phase.nodeIdToNodeMap[prevNode.identifier()]; - const inplaceUpdate = prevNodeCurrentState?.nodeLabel?.inplaceUpdatePhase; if (!prevNodeCurrentState) { this.addToHistory(i, prevNode, HistoryChange.Removed); - } else if (!prevNodeCurrentState?.equals(node) && inplaceUpdate == phase.name) { + } else if (!this.nodeEquals(prevNodeCurrentState, node) && + prevNodeCurrentState instanceof GraphNode && + prevNodeCurrentState.getInplaceUpdatePhase() == phase.name) { this.addToHistory(i, prevNodeCurrentState, HistoryChange.InplaceUpdated); } else if (node.identifier() != prevNode.identifier()) { this.addToHistory(i, prevNodeCurrentState, HistoryChange.Survived); @@ -314,7 +319,7 @@ export class HistoryView extends View { continue; } - if (node.nodeLabel.inplaceUpdatePhase && node.nodeLabel.inplaceUpdatePhase == phase.name) { + if (node instanceof GraphNode && node.getInplaceUpdatePhase() == phase.name) { this.addToHistory(i, node, HistoryChange.InplaceUpdated); } @@ -328,7 +333,7 @@ export class HistoryView extends View { } } - private addHistoryAncestors(key: string, phase: GraphPhase, uniqueAncestors: Set): + private addHistoryAncestors(key: string, phase: GPhase, uniqueAncestors: Set): boolean { let changed = false; const phaseId = this.sourceResolver.getPhaseIdByName(phase.name); @@ -343,22 +348,22 @@ export class HistoryView extends View { return changed; } - private getHistoryChain(phaseId: number, node: GraphNode): Map { + private getHistoryChain(phaseId: number, node: GNode): Map { const leftChain = this.getLeftHistoryChain(phaseId, node); const rightChain = this.getRightHistoryChain(phaseId, node); return new Map([...leftChain, ...rightChain]); } - private getLeftHistoryChain(phaseId: number, node: GraphNode): Map { - const leftChain = new Map(); + private getLeftHistoryChain(phaseId: number, node: GNode): Map { + const leftChain = new Map(); for (let i = phaseId; i >= 0; i--) { - const phase = this.sourceResolver.getPhase(i); - if (!(phase instanceof GraphPhase)) continue; + const phase = this.sourceResolver.getGraphPhase(i); + if (!phase) continue; let currentNode = phase.nodeIdToNodeMap[node.identifier()]; if (!currentNode) { - const nodeOrigin = node.nodeLabel.origin; - if (nodeOrigin instanceof NodeOrigin) { + const nodeOrigin = node.getNodeOrigin(); + if (nodeOrigin) { currentNode = phase.nodeIdToNodeMap[nodeOrigin.identifier()]; } if (!currentNode) return leftChain; @@ -370,12 +375,12 @@ export class HistoryView extends View { return leftChain; } - private getRightHistoryChain(phaseId: number, node: GraphNode): Map { - const rightChain = new Map(); + private getRightHistoryChain(phaseId: number, node: GNode): Map { + const rightChain = new Map(); for (let i = phaseId + 1; i < this.sourceResolver.phases.length; i++) { - const phase = this.sourceResolver.getPhase(i); - if (!(phase instanceof GraphPhase)) continue; + const phase = this.sourceResolver.getGraphPhase(i); + if (!phase) continue; const currentNode = phase.nodeIdToNodeMap[node.identifier()]; if (!currentNode) return rightChain; rightChain.set(i, currentNode); @@ -385,7 +390,7 @@ export class HistoryView extends View { return rightChain; } - private addToHistory(phaseId: number, node: GraphNode, change: HistoryChange): void { + private addToHistory(phaseId: number, node: GNode, change: HistoryChange): void { if (!this.phaseIdToHistory.has(phaseId)) { this.phaseIdToHistory.set(phaseId, new PhaseHistory(phaseId)); } @@ -397,6 +402,16 @@ export class HistoryView extends View { } } + private nodeEquals(first: GNode, second: GNode): boolean { + if (!first || !second) return false; + if ((first instanceof GraphNode && second instanceof GraphNode)) { + return first.equals(second); + } else if (first instanceof TurboshaftGraphNode && second instanceof TurboshaftGraphNode) { + return first.equals(second); + } + return first.getHistoryLabel() == second.getHistoryLabel(); + } + private clear(): void { this.phaseIdToHistory.clear(); this.maxNodeWidth = 0; @@ -458,7 +473,7 @@ export class PhaseHistory { this.nodeIdToRecord = new Map(); } - public addChange(node: GraphNode, change: HistoryChange): void { + public addChange(node: GNode, change: HistoryChange): void { const key = node.identifier(); if (!this.nodeIdToRecord.has(key)) { this.nodeIdToRecord.set(key, new HistoryRecord(node)); @@ -475,10 +490,10 @@ export class PhaseHistory { } export class HistoryRecord { - node: GraphNode; + node: GNode; changes: Set; - constructor(node: GraphNode) { + constructor(node: GNode) { this.node = node; this.changes = new Set(); } diff --git a/tools/turbolizer/src/views/movable-view.ts b/tools/turbolizer/src/views/movable-view.ts index 557f56f72c..61bf16fb5d 100644 --- a/tools/turbolizer/src/views/movable-view.ts +++ b/tools/turbolizer/src/views/movable-view.ts @@ -252,6 +252,12 @@ export abstract class MovableView ext })]; } + protected showHoveredNodeHistory(): void { + const node = this.graph.nodeMap[this.hoveredNodeIdentifier]; + if (!node) return; + this.broker.broadcastHistoryShow(null, node, this.phaseName); + } + protected createImgToggleInput(id: string, title: string, initState: boolean, onClick): HTMLElement { const input = this.createImgInput(id, title, onClick); diff --git a/tools/turbolizer/src/views/turboshaft-graph-view.ts b/tools/turbolizer/src/views/turboshaft-graph-view.ts index 4db9c26c0e..cd2820e7ad 100644 --- a/tools/turbolizer/src/views/turboshaft-graph-view.ts +++ b/tools/turbolizer/src/views/turboshaft-graph-view.ts @@ -21,6 +21,7 @@ import { TurboshaftGraphLayout } from "../turboshaft-graph-layout"; import { GraphStateType } from "../phases/graph-phase/graph-phase"; import { SelectionStorage } from "../selection/selection-storage"; import { DataTarget } from "../phases/turboshaft-custom-data-phase"; +import { SourcePosition } from "../position"; import { TurboshaftCustomData, TurboshaftGraphPhase @@ -99,8 +100,12 @@ export class TurboshaftGraphView extends MovableView { this.broker.addNodeHandler(this.nodeSelectionHandler); this.broker.addBlockHandler(this.blockSelectionHandler); - if (adaptedSelection.isAdapted()) { - this.attachSelection(adaptedSelection); + const countOfSelectedItems = adaptedSelection.isAdapted() + ? this.attachSelection(adaptedSelection) + : 0; + + if (countOfSelectedItems > 0) { + this.updateGraphVisibility(); this.viewSelection(); } else { if (this.state.cacheLayout && data.transform) { @@ -142,6 +147,9 @@ export class TurboshaftGraphView extends MovableView { eventHandled = false; } break; + case 72: // 'h' + this.showHoveredNodeHistory(); + break; case 73: // 'i' this.selectNodesOfSelectedBlocks(); break; @@ -255,7 +263,16 @@ export class TurboshaftGraphView extends MovableView { const view = this; return { select: function (selectedNodes: Array, selected: boolean) { + const sourcePositions = new Array(); + const nodes = new Set(); + for (const node of selectedNodes) { + if (node.sourcePosition) { + sourcePositions.push(node.sourcePosition); + nodes.add(node.identifier()); + } + } view.state.selection.select(selectedNodes, selected); + view.broker.broadcastSourcePositionSelect(this, sourcePositions, selected, nodes); view.updateGraphVisibility(); }, clear: function () { @@ -367,6 +384,7 @@ export class TurboshaftGraphView extends MovableView { const selectedCustomData = select.options[this.selectedIndex].text; storageSetItem(storageKey, selectedCustomData); view.updateGraphVisibility(); + view.updateInlineNodesCustomData(); }; this.toolbox.appendChild(select); @@ -603,13 +621,11 @@ export class TurboshaftGraphView extends MovableView { private updateInlineNodes(): void { const view = this; const state = this.state; - const storageKey = this.customDataStorageKey(); - const selectedCustomData = storageGetItem(storageKey, null, false); + const showCustomData = this.nodesCustomDataShowed(); let totalHeight = 0; let blockId = 0; view.visibleNodes.each(function (node: TurboshaftGraphNode) { const nodeSvg = d3.select(this); - const showCustomData = view.nodesCustomDataShowed(); if (blockId != node.block.id) { blockId = node.block.id; totalHeight = 0; @@ -623,18 +639,25 @@ export class TurboshaftGraphView extends MovableView { .attr("dy", nodeY) .attr("visibility", !node.block.collapsed ? "visible" : "hidden"); - const svgNodeCustomData = nodeSvg + nodeSvg .select(".inline-node-custom-data") .attr("visibility", !node.block.collapsed && showCustomData ? "visible" : "hidden"); + }); + } - if (!node.block.collapsed && showCustomData) { - const customData = view.graph.getCustomData(selectedCustomData, node.id, DataTarget.Nodes); - svgNodeCustomData - .select("tspan") - .text(view.getReadableString(customData, node.block.width)) - .append("title") - .text(customData); - } + private updateInlineNodesCustomData(): void { + const view = this; + const storageKey = this.customDataStorageKey(); + const selectedCustomData = storageGetItem(storageKey, null, false); + if (!this.nodesCustomDataShowed()) return; + view.visibleNodes.each(function (node: TurboshaftGraphNode) { + const customData = view.graph.getCustomData(selectedCustomData, node.id, DataTarget.Nodes); + d3.select(this) + .select(".inline-node-custom-data") + .select("tspan") + .text(view.getReadableString(customData, node.block.width)) + .append("title") + .text(customData); }); } @@ -705,8 +728,8 @@ export class TurboshaftGraphView extends MovableView { } } - private attachSelection(selection: SelectionStorage): void { - if (!(selection instanceof SelectionStorage)) return; + private attachSelection(selection: SelectionStorage): number { + if (!(selection instanceof SelectionStorage)) return 0; this.nodeSelectionHandler.clear(); this.blockSelectionHandler.clear(); const selectedNodes = [ @@ -719,6 +742,7 @@ export class TurboshaftGraphView extends MovableView { selection.adaptedBocks.has(this.state.blocksSelection.stringKey(block))) ]; this.blockSelectionHandler.select(selectedBlocks, true); + return selectedNodes.length + selectedBlocks.length; } private nodesCustomDataShowed(): boolean { @@ -813,6 +837,7 @@ export class TurboshaftGraphView extends MovableView { const extent = view.graph.redetermineGraphBoundingBox(view.state.showCustomData); view.panZoom.translateExtent(extent); view.adaptiveUpdateGraphVisibility(); + view.updateInlineNodesCustomData(); } private toggleLayoutCachingAction(view: TurboshaftGraphView): void { @@ -823,7 +848,7 @@ export class TurboshaftGraphView extends MovableView { // Hotkeys handlers private selectAllNodes(): void { - this.state.selection.select(this.graph.nodeMap, true); + this.nodeSelectionHandler.select(this.graph.nodeMap, true); this.updateGraphVisibility(); } @@ -865,7 +890,7 @@ export class TurboshaftGraphView extends MovableView { block.collapsed = false; selectedNodes = selectedNodes.concat(block.nodes); } - this.state.selection.select(selectedNodes, true); + this.nodeSelectionHandler.select(selectedNodes, true); this.updateGraphVisibility(); } }