[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 <nicohartmann@chromium.org>
Commit-Queue: Danylo Boiko <danielboyko02@gmail.com>
Cr-Commit-Position: refs/heads/main@{#82682}
This commit is contained in:
Danylo Boiko 2022-08-22 23:23:27 +03:00 committed by V8 LUCI CQ
parent bf5e3a8a0e
commit 41d5c9cb15
24 changed files with 731 additions and 217 deletions

View File

@ -30,6 +30,10 @@
<td>b</td> <td>b</td>
<td>Show graph with selected nodes for previous phase</td> <td>Show graph with selected nodes for previous phase</td>
</tr> </tr>
<tr>
<td>h</td>
<td>Show hovered node's history</td>
</tr>
<tr> <tr>
<td>a</td> <td>a</td>
<td>Select all nodes</td> <td>Select all nodes</td>
@ -61,10 +65,6 @@
<td>u</td> <td>u</td>
<td>Hide unselected nodes</td> <td>Hide unselected nodes</td>
</tr> </tr>
<tr>
<td>h</td>
<td>Show hovered node's history</td>
</tr>
</table> </table>
</div> </div>
</div> </div>

View File

@ -7,13 +7,14 @@ import { storageGetItem, storageSetItem } from "./common/util";
import { GraphView } from "./views/graph-view"; import { GraphView } from "./views/graph-view";
import { ScheduleView } from "./views/schedule-view"; import { ScheduleView } from "./views/schedule-view";
import { SequenceView } from "./views/sequence-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 { SelectionBroker } from "./selection/selection-broker";
import { PhaseView, View } from "./views/view"; import { PhaseView, View } from "./views/view";
import { GraphPhase } from "./phases/graph-phase/graph-phase"; import { GraphPhase } from "./phases/graph-phase/graph-phase";
import { PhaseType } from "./phases/phase"; import { PhaseType } from "./phases/phase";
import { TurboshaftGraphView } from "./views/turboshaft-graph-view"; import { TurboshaftGraphView } from "./views/turboshaft-graph-view";
import { SelectionStorage } from "./selection/selection-storage"; import { SelectionStorage } from "./selection/selection-storage";
import { TurboshaftGraphPhase } from "./phases/turboshaft-graph-phase/turboshaft-graph-phase";
const toolboxHTML = ` const toolboxHTML = `
<div class="graph-toolbox"> <div class="graph-toolbox">
@ -90,21 +91,23 @@ export class GraphMultiView extends View {
const lastPhaseIndex = storageGetItem("lastSelectedPhase"); const lastPhaseIndex = storageGetItem("lastSelectedPhase");
const initialPhaseIndex = this.sourceResolver.repairPhaseId(lastPhaseIndex); const initialPhaseIndex = this.sourceResolver.repairPhaseId(lastPhaseIndex);
this.selectMenu.selectedIndex = initialPhaseIndex; this.selectMenu.selectedIndex = initialPhaseIndex;
this.displayPhase(this.sourceResolver.getPhase(initialPhaseIndex)); this.displayPhase(this.sourceResolver.getDynamicPhase(initialPhaseIndex));
} }
public displayPhaseByName(phaseName: string, selection?: SelectionStorage): void { public displayPhaseByName(phaseName: string, selection?: SelectionStorage): void {
this.currentPhaseView.hide(); this.currentPhaseView.hide();
const phaseId = this.sourceResolver.getPhaseIdByName(phaseName); const phaseId = this.sourceResolver.getPhaseIdByName(phaseName);
this.selectMenu.selectedIndex = phaseId; this.selectMenu.selectedIndex = phaseId;
this.displayPhase(this.sourceResolver.getPhase(phaseId), selection); this.displayPhase(this.sourceResolver.getDynamicPhase(phaseId), selection);
} }
public onresize(): void { public onresize(): void {
this.currentPhaseView?.onresize(); 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) { if (phase.type == PhaseType.Graph) {
this.displayPhaseView(this.graph, phase, selection); this.displayPhaseView(this.graph, phase, selection);
} else if (phase.type == PhaseType.TurboshaftGraph) { } 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 { void {
const rememberedSelection = selection ? selection : this.hideCurrentPhase(); const rememberedSelection = selection ? selection : this.hideCurrentPhase();
view.initializeContent(data, rememberedSelection); view.initializeContent(data, rememberedSelection);
@ -126,8 +129,8 @@ export class GraphMultiView extends View {
private displayNextGraphPhase(): void { private displayNextGraphPhase(): void {
let nextPhaseIndex = this.selectMenu.selectedIndex + 1; let nextPhaseIndex = this.selectMenu.selectedIndex + 1;
while (nextPhaseIndex < this.sourceResolver.phases.length) { while (nextPhaseIndex < this.sourceResolver.phases.length) {
const nextPhase = this.sourceResolver.getPhase(nextPhaseIndex); const nextPhase = this.sourceResolver.getDynamicPhase(nextPhaseIndex);
if (nextPhase.isGraph()) { if (nextPhase && nextPhase.isGraph()) {
this.selectMenu.selectedIndex = nextPhaseIndex; this.selectMenu.selectedIndex = nextPhaseIndex;
storageSetItem("lastSelectedPhase", nextPhaseIndex); storageSetItem("lastSelectedPhase", nextPhaseIndex);
this.displayPhase(nextPhase); this.displayPhase(nextPhase);
@ -140,8 +143,8 @@ export class GraphMultiView extends View {
private displayPreviousGraphPhase(): void { private displayPreviousGraphPhase(): void {
let previousPhaseIndex = this.selectMenu.selectedIndex - 1; let previousPhaseIndex = this.selectMenu.selectedIndex - 1;
while (previousPhaseIndex >= 0) { while (previousPhaseIndex >= 0) {
const previousPhase = this.sourceResolver.getPhase(previousPhaseIndex); const previousPhase = this.sourceResolver.getDynamicPhase(previousPhaseIndex);
if (previousPhase.isGraph()) { if (previousPhase && previousPhase.isGraph()) {
this.selectMenu.selectedIndex = previousPhaseIndex; this.selectMenu.selectedIndex = previousPhaseIndex;
storageSetItem("lastSelectedPhase", previousPhaseIndex); storageSetItem("lastSelectedPhase", previousPhaseIndex);
this.displayPhase(previousPhase); this.displayPhase(previousPhase);
@ -157,7 +160,8 @@ export class GraphMultiView extends View {
for (const phase of view.sourceResolver.phases) { for (const phase of view.sourceResolver.phases) {
const optionElement = document.createElement("option"); const optionElement = document.createElement("option");
let maxNodeId = ""; let maxNodeId = "";
if (phase instanceof GraphPhase && phase.highestNodeId != 0) { if ((phase instanceof GraphPhase || phase instanceof TurboshaftGraphPhase)
&& phase.highestNodeId != 0) {
maxNodeId = ` ${phase.highestNodeId}`; maxNodeId = ` ${phase.highestNodeId}`;
} }
optionElement.text = `${phase.name}${maxNodeId}`; optionElement.text = `${phase.name}${maxNodeId}`;
@ -166,7 +170,7 @@ export class GraphMultiView extends View {
this.selectMenu.onchange = function (this: HTMLSelectElement) { this.selectMenu.onchange = function (this: HTMLSelectElement) {
const phaseIndex = this.selectedIndex; const phaseIndex = this.selectedIndex;
storageSetItem("lastSelectedPhase", phaseIndex); storageSetItem("lastSelectedPhase", phaseIndex);
view.displayPhase(view.sourceResolver.getPhase(phaseIndex)); view.displayPhase(view.sourceResolver.getDynamicPhase(phaseIndex));
}; };
} }

View File

@ -11,7 +11,8 @@ export class NodeLabel {
title: string; title: string;
live: boolean; live: boolean;
properties: string; properties: string;
sourcePosition: SourcePosition | BytecodePosition; sourcePosition: SourcePosition;
bytecodePosition: BytecodePosition;
origin: NodeOrigin | BytecodeOrigin; origin: NodeOrigin | BytecodeOrigin;
opcode: string; opcode: string;
control: boolean; control: boolean;
@ -20,15 +21,16 @@ export class NodeLabel {
inplaceUpdatePhase: string; inplaceUpdatePhase: string;
constructor(id: number, label: string, title: string, live: boolean, constructor(id: number, label: string, title: string, live: boolean,
properties: string, sourcePosition: SourcePosition | BytecodePosition, properties: string, sourcePosition: SourcePosition,
origin: NodeOrigin | BytecodeOrigin, opcode: string, control: boolean, bytecodePosition: BytecodePosition, origin: NodeOrigin | BytecodeOrigin,
opinfo: string, type: string) { opcode: string, control: boolean, opinfo: string, type: string) {
this.id = id; this.id = id;
this.label = label; this.label = label;
this.title = title; this.title = title;
this.live = live; this.live = live;
this.properties = properties; this.properties = properties;
this.sourcePosition = sourcePosition; this.sourcePosition = sourcePosition;
this.bytecodePosition = bytecodePosition;
this.origin = origin; this.origin = origin;
this.opcode = opcode; this.opcode = opcode;
this.control = control; this.control = control;

View File

@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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 { export abstract class Origin {
phase: string; phase: string;
reducer: string; reducer: string;
@ -14,10 +17,13 @@ export abstract class Origin {
export class NodeOrigin extends Origin { export class NodeOrigin extends Origin {
nodeId: number; 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); super(phase, reducer);
this.nodeId = nodeId; this.nodeId = nodeId;
this.node = node;
} }
public identifier(): string { public identifier(): string {

View File

@ -7,6 +7,7 @@ import { alignUp, measureText } from "../../common/util";
import { NodeLabel } from "../../node-label"; import { NodeLabel } from "../../node-label";
import { Node } from "../../node"; import { Node } from "../../node";
import { GraphEdge } from "./graph-edge"; import { GraphEdge } from "./graph-edge";
import { NodeOrigin } from "../../origin";
export class GraphNode extends Node<GraphEdge> { export class GraphNode extends Node<GraphEdge> {
nodeLabel: NodeLabel; nodeLabel: NodeLabel;
@ -79,6 +80,19 @@ export class GraphNode extends Node<GraphEdge> {
return this.nodeLabel.getTitle(); 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 { public getDisplayLabel(): string {
return this.nodeLabel.getDisplayLabel(); return this.nodeLabel.getDisplayLabel();
} }

View File

@ -5,81 +5,109 @@
import { Phase, PhaseType } from "../phase"; import { Phase, PhaseType } from "../phase";
import { NodeLabel } from "../../node-label"; import { NodeLabel } from "../../node-label";
import { BytecodeOrigin, NodeOrigin } from "../../origin"; import { BytecodeOrigin, NodeOrigin } from "../../origin";
import { SourcePosition } from "../../position";
import { GraphNode } from "./graph-node"; import { GraphNode } from "./graph-node";
import { GraphEdge } from "./graph-edge"; 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 { export class GraphPhase extends Phase {
highestNodeId: number;
data: GraphData; data: GraphData;
stateType: GraphStateType; stateType: GraphStateType;
nodeLabelMap: Array<NodeLabel>; instructionsPhase: InstructionsPhase;
nodeIdToNodeMap: Array<GraphNode>; nodeIdToNodeMap: Array<GraphNode>;
originIdToNodesMap: Map<string, Array<GraphNode>>; originIdToNodesMap: Map<string, Array<GraphNode>>;
positions: PositionsContainer;
highestNodeId: number;
rendered: boolean; rendered: boolean;
transform: { x: number, y: number, scale: number }; transform: { x: number, y: number, scale: number };
constructor(name: string, highestNodeId: number, dataJson, nodeLabelMap?: Array<NodeLabel>) { constructor(name: string, dataJson, nodeMap: Array<GraphNode>, sources: Array<Source>,
inlinings: Array<InliningPosition>) {
super(name, PhaseType.Graph); super(name, PhaseType.Graph);
this.highestNodeId = highestNodeId;
this.data = new GraphData(); this.data = new GraphData();
this.stateType = GraphStateType.NeedToFullRebuild; this.stateType = GraphStateType.NeedToFullRebuild;
this.instructionsPhase = new InstructionsPhase();
this.nodeIdToNodeMap = new Array<GraphNode>(); this.nodeIdToNodeMap = new Array<GraphNode>();
this.originIdToNodesMap = new Map<string, Array<GraphNode>>(); this.originIdToNodesMap = new Map<string, Array<GraphNode>>();
this.positions = new PositionsContainer();
this.highestNodeId = 0;
this.rendered = false; this.rendered = false;
this.parseDataFromJSON(dataJson, nodeLabelMap); this.parseDataFromJSON(dataJson, nodeMap, sources, inlinings);
this.nodeLabelMap = nodeLabelMap?.slice();
} }
private parseDataFromJSON(dataJson, nodeLabelMap: Array<NodeLabel>): void { private parseDataFromJSON(dataJson, nodeMap: Array<GraphNode>, sources: Array<Source>,
inlinings: Array<InliningPosition>): void {
this.data = new GraphData(); this.data = new GraphData();
this.nodeIdToNodeMap = this.parseNodesFromJSON(dataJson.nodes, nodeLabelMap); this.parseNodesFromJSON(dataJson.nodes, nodeMap, sources, inlinings);
this.parseEdgesFromJSON(dataJson.edges); this.parseEdgesFromJSON(dataJson.edges);
} }
private parseNodesFromJSON(nodesJSON, nodeLabelMap: Array<NodeLabel>): Array<GraphNode> { private parseNodesFromJSON(nodesJSON, nodeMap: Array<GraphNode>, sources: Array<Source>,
const nodeIdToNodeMap = new Array<GraphNode>(); inlinings: Array<InliningPosition>): void {
for (const node of nodesJSON) { 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; let sourcePosition: SourcePosition = null;
if (node.sourcePosition) { const sourcePositionJson = node.sourcePosition;
const scriptOffset = node.sourcePosition.scriptOffset; if (sourcePositionJson) {
const inliningId = node.sourcePosition.inliningId; const scriptOffset = sourcePositionJson.scriptOffset;
const inliningId = sourcePositionJson.inliningId;
sourcePosition = new SourcePosition(scriptOffset, inliningId); sourcePosition = new SourcePosition(scriptOffset, inliningId);
} }
const label = new NodeLabel(node.id, node.label, node.title, node.live, node.properties, let origin: NodeOrigin | BytecodeOrigin = null;
sourcePosition, origin, node.opcode, node.control, node.opinfo, node.type); let bytecodePosition: BytecodePosition = null;
const originJson = node.origin;
const previous = nodeLabelMap[label.id]; if (originJson) {
if (!label.equals(previous)) { const nodeId = originJson.nodeId;
if (previous !== undefined) { if (nodeId) {
label.setInplaceUpdatePhase(this.name); 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); const newNode = new GraphNode(label);
this.data.nodes.push(newNode); this.data.nodes.push(newNode);
nodeIdToNodeMap[newNode.identifier()] = newNode; this.highestNodeId = Math.max(this.highestNodeId, newNode.id);
if (origin) { 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(); const identifier = origin.identifier();
if (!this.originIdToNodesMap.has(identifier)) { if (!this.originIdToNodesMap.has(identifier)) {
this.originIdToNodesMap.set(identifier, new Array<GraphNode>()); this.originIdToNodesMap.set(identifier, new Array<GraphNode>());
} }
this.originIdToNodesMap.get(identifier).push(newNode); 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 { private parseEdgesFromJSON(edgesJSON): void {

View File

@ -37,6 +37,18 @@ export class InstructionsPhase extends Phase {
return -1; 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 { public instructionToPcOffsets(instruction: number): TurbolizerInstructionStartInfo {
return this.instructionToPCOffset[instruction]; return this.instructionToPCOffset[instruction];
} }

View File

@ -12,8 +12,11 @@ export abstract class Phase {
} }
public isGraph(): boolean { public isGraph(): boolean {
return this.type == PhaseType.Graph || return this.type == PhaseType.Graph || this.type == PhaseType.TurboshaftGraph;
this.type == PhaseType.TurboshaftGraph; }
public isDynamic(): boolean {
return this.isGraph() || this.type == PhaseType.Schedule || this.type == PhaseType.Sequence;
} }
} }

View File

@ -3,13 +3,19 @@
// found in the LICENSE file. // found in the LICENSE file.
import { Phase, PhaseType } from "./phase"; import { Phase, PhaseType } from "./phase";
import { PositionsContainer } from "../position";
import { InstructionsPhase } from "./instructions-phase";
export class SchedulePhase extends Phase { export class SchedulePhase extends Phase {
data: ScheduleData; data: ScheduleData;
instructionsPhase: InstructionsPhase;
positions: PositionsContainer;
constructor(name: string, dataJson) { constructor(name: string, dataJson) {
super(name, PhaseType.Schedule); super(name, PhaseType.Schedule);
this.data = new ScheduleData(); this.data = new ScheduleData();
this.instructionsPhase = new InstructionsPhase();
this.positions = new PositionsContainer();
this.parseScheduleFromJSON(dataJson); this.parseScheduleFromJSON(dataJson);
} }

View File

@ -4,13 +4,19 @@
import * as C from "../common/constants"; import * as C from "../common/constants";
import { Phase, PhaseType } from "./phase"; import { Phase, PhaseType } from "./phase";
import { PositionsContainer } from "../position";
import { InstructionsPhase } from "./instructions-phase";
export class SequencePhase extends Phase { export class SequencePhase extends Phase {
blocks: Array<SequenceBlock>; blocks: Array<SequenceBlock>;
instructionsPhase: InstructionsPhase;
positions: PositionsContainer;
registerAllocation: RegisterAllocation; registerAllocation: RegisterAllocation;
constructor(name: string, blocksJSON, registerAllocationJSON) { constructor(name: string, blocksJSON, registerAllocationJSON) {
super(name, PhaseType.Sequence); super(name, PhaseType.Sequence);
this.instructionsPhase = new InstructionsPhase();
this.positions = new PositionsContainer();
this.parseBlocksFromJSON(blocksJSON); this.parseBlocksFromJSON(blocksJSON);
this.parseRegisterAllocationFromJSON(registerAllocationJSON); this.parseRegisterAllocationFromJSON(registerAllocationJSON);
} }

View File

@ -7,17 +7,26 @@ import { measureText } from "../../common/util";
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge"; import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
import { TurboshaftGraphBlock } from "./turboshaft-graph-block"; import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
import { Node } from "../../node"; import { Node } from "../../node";
import { BytecodePosition, SourcePosition } from "../../position";
import { NodeOrigin } from "../../origin";
export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGraphNode>> { export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGraphNode>> {
title: string; title: string;
block: TurboshaftGraphBlock; block: TurboshaftGraphBlock;
sourcePosition: SourcePosition;
bytecodePosition: BytecodePosition;
origin: NodeOrigin;
opPropertiesType: OpPropertiesType; opPropertiesType: OpPropertiesType;
constructor(id: number, title: string, block: TurboshaftGraphBlock, constructor(id: number, title: string, block: TurboshaftGraphBlock,
opPropertiesType: OpPropertiesType) { sourcePosition: SourcePosition, bytecodePosition: BytecodePosition,
origin: NodeOrigin, opPropertiesType: OpPropertiesType) {
super(id); super(id);
this.title = title; this.title = title;
this.block = block; this.block = block;
this.sourcePosition = sourcePosition;
this.bytecodePosition = bytecodePosition;
this.origin = origin;
this.opPropertiesType = opPropertiesType; this.opPropertiesType = opPropertiesType;
this.visible = true; this.visible = true;
} }
@ -37,6 +46,9 @@ export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGrap
public getTitle(): string { public getTitle(): string {
let title = `${this.id} ${this.title} ${this.opPropertiesType}`; let title = `${this.id} ${this.title} ${this.opPropertiesType}`;
if (this.origin) {
title += `\nOrigin: ${this.origin.toString()}`;
}
if (this.inputs.length > 0) { if (this.inputs.length > 0) {
title += `\nInputs: ${this.inputs.map(i => i.source.id).join(", ")}`; title += `\nInputs: ${this.inputs.map(i => i.source.id).join(", ")}`;
} }
@ -46,10 +58,24 @@ export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGrap
return title; return title;
} }
public getHistoryLabel(): string {
return `${this.id} ${this.title}`;
}
public getNodeOrigin(): NodeOrigin {
return this.origin;
}
public getInlineLabel(): string { public getInlineLabel(): string {
if (this.inputs.length == 0) return `${this.id} ${this.title}`; if (this.inputs.length == 0) return `${this.id} ${this.title}`;
return `${this.id} ${this.title}(${this.inputs.map(i => i.source.id).join(",")})`; return `${this.id} ${this.title}(${this.inputs.map(i => 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 { export enum OpPropertiesType {

View File

@ -7,31 +7,51 @@ import { TurboshaftGraphNode } from "./turboshaft-graph-node";
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge"; import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
import { TurboshaftGraphBlock } from "./turboshaft-graph-block"; import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
import { DataTarget, TurboshaftCustomDataPhase } from "../turboshaft-custom-data-phase"; 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 { export class TurboshaftGraphPhase extends Phase {
data: TurboshaftGraphData; data: TurboshaftGraphData;
customData: TurboshaftCustomData; customData: TurboshaftCustomData;
stateType: GraphStateType; stateType: GraphStateType;
instructionsPhase: InstructionsPhase;
nodeIdToNodeMap: Array<TurboshaftGraphNode>; nodeIdToNodeMap: Array<TurboshaftGraphNode>;
blockIdToBlockMap: Array<TurboshaftGraphBlock>; blockIdToBlockMap: Array<TurboshaftGraphBlock>;
originIdToNodesMap: Map<string, Array<TurboshaftGraphNode>>;
positions: PositionsContainer;
highestNodeId: number;
rendered: boolean; rendered: boolean;
customDataShowed: boolean; customDataShowed: boolean;
transform: { x: number, y: number, scale: number }; transform: { x: number, y: number, scale: number };
constructor(name: string, dataJson) { constructor(name: string, dataJson, nodeMap: Array<GraphNode | TurboshaftGraphNode>,
sources: Array<Source>, inlinings: Array<InliningPosition>) {
super(name, PhaseType.TurboshaftGraph); super(name, PhaseType.TurboshaftGraph);
this.stateType = GraphStateType.NeedToFullRebuild; this.stateType = GraphStateType.NeedToFullRebuild;
this.instructionsPhase = new InstructionsPhase();
this.customData = new TurboshaftCustomData(); this.customData = new TurboshaftCustomData();
this.nodeIdToNodeMap = new Array<TurboshaftGraphNode>(); this.nodeIdToNodeMap = new Array<TurboshaftGraphNode>();
this.blockIdToBlockMap = new Array<TurboshaftGraphBlock>(); this.blockIdToBlockMap = new Array<TurboshaftGraphBlock>();
this.originIdToNodesMap = new Map<string, Array<TurboshaftGraphNode>>();
this.positions = new PositionsContainer();
this.highestNodeId = 0;
this.rendered = false; this.rendered = false;
this.parseDataFromJSON(dataJson); this.parseDataFromJSON(dataJson, nodeMap, sources, inlinings);
} }
private parseDataFromJSON(dataJson): void { private parseDataFromJSON(dataJson, nodeMap: Array<GraphNode | TurboshaftGraphNode>,
sources: Array<Source>, inlinings: Array<InliningPosition>): void {
this.data = new TurboshaftGraphData(); this.data = new TurboshaftGraphData();
this.parseBlocksFromJSON(dataJson.blocks); this.parseBlocksFromJSON(dataJson.blocks);
this.parseNodesFromJSON(dataJson.nodes); this.parseNodesFromJSON(dataJson.nodes, nodeMap, sources, inlinings);
this.parseEdgesFromJSON(dataJson.edges); this.parseEdgesFromJSON(dataJson.edges);
} }
@ -52,14 +72,62 @@ export class TurboshaftGraphPhase extends Phase {
} }
} }
private parseNodesFromJSON(nodesJson): void { private parseNodesFromJSON(nodesJson, nodeMap: Array<GraphNode | TurboshaftGraphNode>,
sources: Array<Source>, inlinings: Array<InliningPosition>): void {
for (const nodeJson of nodesJson) { for (const nodeJson of nodesJson) {
const block = this.blockIdToBlockMap[nodeJson.block_id]; 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); block.nodes.push(node);
this.data.nodes.push(node); this.data.nodes.push(node);
this.nodeIdToNodeMap[node.identifier()] = 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<TurboshaftGraphNode>());
}
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) { for (const block of this.blockIdToBlockMap) {
block.initCollapsedLabel(); block.initCollapsedLabel();
@ -127,7 +195,9 @@ export class TurboshaftCustomData {
private concatCustomData(key: number, items: Map<string, TurboshaftCustomDataPhase>): string { private concatCustomData(key: number, items: Map<string, TurboshaftCustomDataPhase>): string {
let customData = ""; let customData = "";
for (const [name, dataPhase] of items.entries()) { 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; return customData;
} }

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";
export class InliningPosition { export class InliningPosition {
sourceId: number; sourceId: number;
inliningPosition: SourcePosition; inliningPosition: SourcePosition;
@ -43,9 +45,11 @@ export class SourcePosition {
export class BytecodePosition { export class BytecodePosition {
bytecodePosition: number; bytecodePosition: number;
inliningId: number;
constructor(bytecodePosition: number) { constructor(bytecodePosition: number, inliningId: number) {
this.bytecodePosition = bytecodePosition; this.bytecodePosition = bytecodePosition;
this.inliningId = inliningId;
} }
public isValid(): boolean { public isValid(): boolean {
@ -53,6 +57,54 @@ export class BytecodePosition {
} }
public toString(): string { public toString(): string {
return `BCP:${this.bytecodePosition}`; return `BCP:${this.inliningId}:${this.bytecodePosition}`;
}
}
export class PositionsContainer {
nodeIdToSourcePositionMap: Array<SourcePosition>;
nodeIdToBytecodePositionMap: Array<BytecodePosition>;
sourcePositionToNodes: Map<string, Array<string>>;
bytecodePositionToNodes: Map<string, Array<string>>;
constructor() {
this.nodeIdToSourcePositionMap = new Array<SourcePosition>();
this.nodeIdToBytecodePositionMap = new Array<BytecodePosition>();
this.sourcePositionToNodes = new Map<string, Array<string>>();
this.bytecodePositionToNodes = new Map<string, Array<string>>();
}
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<string>());
}
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<string>());
}
const nodes = this.bytecodePositionToNodes.get(key);
if (!nodes.includes(nodeIdentifier)) nodes.push(nodeIdentifier);
}
public merge(nodes: Array<TurboshaftGraphNode>, replacements: Map<number, number>): 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);
}
}
} }
} }

View File

@ -4,6 +4,8 @@
import { GenericPosition, SourceResolver } from "../source-resolver"; import { GenericPosition, SourceResolver } from "../source-resolver";
import { GraphNode } from "../phases/graph-phase/graph-node"; import { GraphNode } from "../phases/graph-phase/graph-node";
import { BytecodePosition } from "../position";
import { TurboshaftGraphNode } from "../phases/turboshaft-graph-phase/turboshaft-graph-node";
import { import {
ClearableHandler, ClearableHandler,
SourcePositionSelectionHandler, SourcePositionSelectionHandler,
@ -11,7 +13,8 @@ import {
BlockSelectionHandler, BlockSelectionHandler,
InstructionSelectionHandler, InstructionSelectionHandler,
RegisterAllocationSelectionHandler, RegisterAllocationSelectionHandler,
HistoryHandler HistoryHandler,
BytecodeOffsetSelectionHandler
} from "./selection-handler"; } from "./selection-handler";
export class SelectionBroker { export class SelectionBroker {
@ -22,6 +25,7 @@ export class SelectionBroker {
blockHandlers: Array<BlockSelectionHandler>; blockHandlers: Array<BlockSelectionHandler>;
instructionHandlers: Array<InstructionSelectionHandler>; instructionHandlers: Array<InstructionSelectionHandler>;
sourcePositionHandlers: Array<SourcePositionSelectionHandler>; sourcePositionHandlers: Array<SourcePositionSelectionHandler>;
bytecodeOffsetHandlers: Array<BytecodeOffsetSelectionHandler>;
registerAllocationHandlers: Array<RegisterAllocationSelectionHandler>; registerAllocationHandlers: Array<RegisterAllocationSelectionHandler>;
constructor(sourceResolver: SourceResolver) { constructor(sourceResolver: SourceResolver) {
@ -32,6 +36,7 @@ export class SelectionBroker {
this.blockHandlers = new Array<BlockSelectionHandler>(); this.blockHandlers = new Array<BlockSelectionHandler>();
this.instructionHandlers = new Array<InstructionSelectionHandler>(); this.instructionHandlers = new Array<InstructionSelectionHandler>();
this.sourcePositionHandlers = new Array<SourcePositionSelectionHandler>(); this.sourcePositionHandlers = new Array<SourcePositionSelectionHandler>();
this.bytecodeOffsetHandlers = new Array<BytecodeOffsetSelectionHandler>();
this.registerAllocationHandlers = new Array<RegisterAllocationSelectionHandler>(); this.registerAllocationHandlers = new Array<RegisterAllocationSelectionHandler>();
} }
@ -74,15 +79,22 @@ export class SelectionBroker {
this.sourcePositionHandlers.push(handler); this.sourcePositionHandlers.push(handler);
} }
public addBytecodeOffsetHandler(handler: BytecodeOffsetSelectionHandler & ClearableHandler):
void {
this.allHandlers.push(handler);
this.bytecodeOffsetHandlers.push(handler);
}
public addRegisterAllocatorHandler(handler: RegisterAllocationSelectionHandler public addRegisterAllocatorHandler(handler: RegisterAllocationSelectionHandler
& ClearableHandler): void { & ClearableHandler): void {
this.allHandlers.push(handler); this.allHandlers.push(handler);
this.registerAllocationHandlers.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) { 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); 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 const pcOffsets = this.sourceResolver.instructionsPhase
.instructionsToKeyPcOffsets(instructionOffsets); .instructionsToKeyPcOffsets(instructionOffsets);
@ -103,12 +115,16 @@ export class SelectionBroker {
for (const handler of this.sourcePositionHandlers) { for (const handler of this.sourcePositionHandlers) {
if (handler != from) handler.brokeredSourcePositionSelect(sourcePositions, selected); 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. // The middle panel lines have already been selected so there's no need to reselect them.
} }
public broadcastSourcePositionSelect(from, sourcePositions: Array<GenericPosition>, public broadcastSourcePositionSelect(from, sourcePositions: Array<GenericPosition>,
selected: boolean): void { selected: boolean, selectedNodes?: Set<string>): void {
sourcePositions = sourcePositions.filter(sourcePosition => { sourcePositions = sourcePositions.filter(sourcePosition => {
if (!sourcePosition.isValid()) { if (!sourcePosition.isValid()) {
console.warn("Invalid source position"); console.warn("Invalid source position");
@ -128,6 +144,44 @@ export class SelectionBroker {
if (handler != from) handler.brokeredNodeSelect(nodes, selected); 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<BytecodePosition>,
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); this.selectInstructionsAndRegisterAllocations(from, nodes, selected);
} }
@ -143,6 +197,12 @@ export class SelectionBroker {
if (handler != from) handler.brokeredSourcePositionSelect(sourcePositions, selected); 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); this.selectInstructionsAndRegisterAllocations(from, nodes, selected);
} }

View File

@ -6,13 +6,14 @@ import { TurboshaftGraphNode } from "../phases/turboshaft-graph-phase/turboshaft
import { GraphNode } from "../phases/graph-phase/graph-node"; import { GraphNode } from "../phases/graph-phase/graph-node";
import { TurboshaftGraphBlock } from "../phases/turboshaft-graph-phase/turboshaft-graph-block"; import { TurboshaftGraphBlock } from "../phases/turboshaft-graph-phase/turboshaft-graph-block";
import { GenericPosition } from "../source-resolver"; import { GenericPosition } from "../source-resolver";
import { BytecodePosition } from "../position";
export interface ClearableHandler { export interface ClearableHandler {
brokeredClear(): void; brokeredClear(): void;
} }
export interface HistoryHandler { export interface HistoryHandler {
showTurbofanNodeHistory(node: GraphNode, phaseName: string): void; showNodeHistory(node: GraphNode | TurboshaftGraphNode, phaseName: string): void;
} }
export interface NodeSelectionHandler { export interface NodeSelectionHandler {
@ -41,6 +42,12 @@ export interface SourcePositionSelectionHandler {
brokeredSourcePositionSelect(sourcePositions: Array<GenericPosition>, selected: boolean): void; brokeredSourcePositionSelect(sourcePositions: Array<GenericPosition>, selected: boolean): void;
} }
export interface BytecodeOffsetSelectionHandler {
select(offsets: Array<number>, selected: boolean): void;
clear(): void;
brokeredBytecodeOffsetSelect(positions: Array<BytecodePosition>, selected: boolean): void;
}
export interface RegisterAllocationSelectionHandler { export interface RegisterAllocationSelectionHandler {
// These are called instructionIds since the class of the divs is "instruction-id" // These are called instructionIds since the class of the divs is "instruction-id"
select(instructionIds: Array<number>, selected: boolean): void; select(instructionIds: Array<number>, selected: boolean): void;

View File

@ -6,36 +6,36 @@ import { camelize, sortUnique } from "./common/util";
import { PhaseType } from "./phases/phase"; import { PhaseType } from "./phases/phase";
import { GraphPhase } from "./phases/graph-phase/graph-phase"; import { GraphPhase } from "./phases/graph-phase/graph-phase";
import { DisassemblyPhase } from "./phases/disassembly-phase"; import { DisassemblyPhase } from "./phases/disassembly-phase";
import { BytecodePosition, InliningPosition, SourcePosition } from "./position";
import { InstructionsPhase } from "./phases/instructions-phase"; import { InstructionsPhase } from "./phases/instructions-phase";
import { SchedulePhase } from "./phases/schedule-phase"; import { SchedulePhase } from "./phases/schedule-phase";
import { SequencePhase } from "./phases/sequence-phase"; import { SequencePhase } from "./phases/sequence-phase";
import { BytecodeOrigin } from "./origin";
import { BytecodeSource, BytecodeSourceData, Source } from "./source"; import { BytecodeSource, BytecodeSourceData, Source } from "./source";
import { NodeLabel } from "./node-label";
import { TurboshaftCustomDataPhase } from "./phases/turboshaft-custom-data-phase"; import { TurboshaftCustomDataPhase } from "./phases/turboshaft-custom-data-phase";
import { TurboshaftGraphPhase } from "./phases/turboshaft-graph-phase/turboshaft-graph-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 GenericPosition = SourcePosition | BytecodePosition;
export type DynamicPhase = GraphPhase | TurboshaftGraphPhase | SchedulePhase | SequencePhase;
export type GenericPhase = GraphPhase | TurboshaftGraphPhase | TurboshaftCustomDataPhase export type GenericPhase = GraphPhase | TurboshaftGraphPhase | TurboshaftCustomDataPhase
| DisassemblyPhase | InstructionsPhase | SchedulePhase | SequencePhase; | DisassemblyPhase | InstructionsPhase | SchedulePhase | SequencePhase;
export class SourceResolver { export class SourceResolver {
nodePositionMap: Array<GenericPosition>;
sources: Array<Source>; sources: Array<Source>;
bytecodeSources: Map<number, BytecodeSource>; bytecodeSources: Map<number, BytecodeSource>;
inlinings: Array<InliningPosition>; inlinings: Array<InliningPosition>;
inliningsMap: Map<string, InliningPosition>; inliningsMap: Map<string, InliningPosition>;
positionToNodes: Map<string, Array<string>>;
phases: Array<GenericPhase>; phases: Array<GenericPhase>;
phaseNames: Map<string, number>; phaseNames: Map<string, number>;
disassemblyPhase: DisassemblyPhase; disassemblyPhase: DisassemblyPhase;
instructionsPhase: InstructionsPhase;
linePositionMap: Map<string, Array<GenericPosition>>; linePositionMap: Map<string, Array<GenericPosition>>;
finalNodeOrigins: Array<NodeOrigin>;
instructionsPhase: InstructionsPhase;
positions: PositionsContainer;
constructor() { constructor() {
// Maps node ids to source positions.
this.nodePositionMap = new Array<GenericPosition>();
// Maps source ids to source objects. // Maps source ids to source objects.
this.sources = new Array<Source>(); this.sources = new Array<Source>();
// Maps bytecode source ids to bytecode source objects. // Maps bytecode source ids to bytecode source objects.
@ -44,15 +44,14 @@ export class SourceResolver {
this.inlinings = new Array<InliningPosition>(); this.inlinings = new Array<InliningPosition>();
// Maps source position keys to inlinings. // Maps source position keys to inlinings.
this.inliningsMap = new Map<string, InliningPosition>(); this.inliningsMap = new Map<string, InliningPosition>();
// Maps source position keys to node ids.
this.positionToNodes = new Map<string, Array<string>>();
// Maps phase ids to phases. // Maps phase ids to phases.
this.phases = new Array<GenericPhase>(); this.phases = new Array<GenericPhase>();
// Maps phase names to phaseIds. // Maps phase names to phaseIds.
this.phaseNames = new Map<string, number>(); this.phaseNames = new Map<string, number>();
this.instructionsPhase = new InstructionsPhase();
// Maps line numbers to source positions // Maps line numbers to source positions
this.linePositionMap = new Map<string, Array<GenericPosition>>(); this.linePositionMap = new Map<string, Array<GenericPosition>>();
// Maps node ids to node origin
this.finalNodeOrigins = new Array<NodeOrigin>();
} }
public getMainFunction(jsonObj): Source { public getMainFunction(jsonObj): Source {
@ -60,12 +59,10 @@ export class SourceResolver {
// Backwards compatibility. // Backwards compatibility.
if (typeof fncJson === "string") { if (typeof fncJson === "string") {
return new Source(null, null, jsonObj.source, -1, true, return new Source(null, null, jsonObj.source, -1, true,
new Array<SourcePosition>(), jsonObj.sourcePosition, jsonObj.sourcePosition, jsonObj.sourcePosition + jsonObj.source.length);
jsonObj.sourcePosition + jsonObj.source.length);
} }
return new Source(fncJson.sourceName, fncJson.functionName, fncJson.sourceText, return new Source(fncJson.sourceName, fncJson.functionName, fncJson.sourceText,
fncJson.sourceId, false, new Array<SourcePosition>(), fncJson.startPosition, fncJson.sourceId, false, fncJson.startPosition, fncJson.endPosition);
fncJson.endPosition);
} }
public setInlinings(inliningsJson): void { public setInlinings(inliningsJson): void {
@ -88,8 +85,7 @@ export class SourceResolver {
if (sourcesJson) { if (sourcesJson) {
for (const [sourceId, source] of Object.entries<Source>(sourcesJson)) { for (const [sourceId, source] of Object.entries<Source>(sourcesJson)) {
const src = new Source(source.sourceName, source.functionName, source.sourceText, const src = new Source(source.sourceName, source.functionName, source.sourceText,
source.sourceId, source.backwardsCompatibility, new Array<SourcePosition>(), source.sourceId, source.backwardsCompatibility, source.startPosition, source.endPosition);
source.startPosition, source.endPosition);
this.sources[sourceId] = src; this.sources[sourceId] = src;
} }
} }
@ -115,41 +111,20 @@ export class SourceResolver {
} }
} }
public setNodePositionMap(mapJson): void { public setFinalNodeOrigins(nodeOriginsJson): void {
if (!mapJson) return; if (!nodeOriginsJson) return;
if (typeof mapJson[0] !== "object") { for (const [nodeId, nodeOrigin] of Object.entries<NodeOrigin>(nodeOriginsJson)) {
const alternativeMap = new Map<string, SourcePosition>(); this.finalNodeOrigins[nodeId] = new NodeOrigin(nodeOrigin.nodeId, null, nodeOrigin.phase,
for (const [nodeId, scriptOffset] of Object.entries<number>(mapJson)) { nodeOrigin.reducer);
alternativeMap[nodeId] = new SourcePosition(scriptOffset, -1);
}
mapJson = alternativeMap;
}
for (const [nodeId, sourcePosition] of Object.entries<SourcePosition>(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<string>());
}
this.positionToNodes.get(key).push(nodeId);
}
for (const [, source] of Object.entries<Source>(this.sources)) {
source.sourcePositions = sortUnique(source.sourcePositions,
(a, b) => a.lessOrEquals(b),
(a, b) => a.equals(b));
} }
} }
public parsePhases(phasesJson): void { public parsePhases(phasesJson): void {
const nodeLabelMap = new Array<NodeLabel>(); const instructionsPhase = new InstructionsPhase();
const selectedDynamicPhases = new Array<DynamicPhase>();
const nodeMap = new Array<GraphNode | TurboshaftGraphNode>();
let lastTurboshaftGraphPhase: TurboshaftGraphPhase = null; let lastTurboshaftGraphPhase: TurboshaftGraphPhase = null;
let lastGraphPhase: GraphPhase | TurboshaftGraphPhase = null;
for (const [, genericPhase] of Object.entries<GenericPhase>(phasesJson)) { for (const [, genericPhase] of Object.entries<GenericPhase>(phasesJson)) {
switch (genericPhase.type) { switch (genericPhase.type) {
case PhaseType.Disassembly: case PhaseType.Disassembly:
@ -163,44 +138,58 @@ export class SourceResolver {
const schedulePhase = new SchedulePhase(castedSchedule.name, castedSchedule.data); const schedulePhase = new SchedulePhase(castedSchedule.name, castedSchedule.data);
this.phaseNames.set(schedulePhase.name, this.phases.length); this.phaseNames.set(schedulePhase.name, this.phases.length);
this.phases.push(schedulePhase); 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; break;
case PhaseType.Sequence: case PhaseType.Sequence:
const castedSequence = camelize(genericPhase) as SequencePhase; const castedSequence = camelize(genericPhase) as SequencePhase;
const sequencePhase = new SequencePhase(castedSequence.name, castedSequence.blocks, const sequencePhase = new SequencePhase(castedSequence.name, castedSequence.blocks,
castedSequence.registerAllocation); 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.phaseNames.set(sequencePhase.name, this.phases.length);
this.phases.push(sequencePhase); this.phases.push(sequencePhase);
break; break;
case PhaseType.Instructions: case PhaseType.Instructions:
const castedInstructions = genericPhase as InstructionsPhase; const castedInstructions = genericPhase as InstructionsPhase;
if (this.instructionsPhase.name === "") { if (instructionsPhase.name === "") {
this.instructionsPhase.name = castedInstructions.name; instructionsPhase.name = castedInstructions.name;
} else { } else {
this.instructionsPhase.name += `, ${castedInstructions.name}`; instructionsPhase.name += `, ${castedInstructions.name}`;
} }
this.instructionsPhase.parseNodeIdToInstructionRangeFromJSON(castedInstructions instructionsPhase.parseNodeIdToInstructionRangeFromJSON(castedInstructions
?.nodeIdToInstructionRange); ?.nodeIdToInstructionRange);
this.instructionsPhase.parseBlockIdToInstructionRangeFromJSON(castedInstructions instructionsPhase.parseBlockIdToInstructionRangeFromJSON(castedInstructions
?.blockIdToInstructionRange); ?.blockIdToInstructionRange);
this.instructionsPhase.parseInstructionOffsetToPCOffsetFromJSON(castedInstructions instructionsPhase.parseInstructionOffsetToPCOffsetFromJSON(castedInstructions
?.instructionOffsetToPCOffset); ?.instructionOffsetToPCOffset);
this.instructionsPhase.parseCodeOffsetsInfoFromJSON(castedInstructions instructionsPhase.parseCodeOffsetsInfoFromJSON(castedInstructions?.codeOffsetsInfo);
?.codeOffsetsInfo);
break; break;
case PhaseType.Graph: case PhaseType.Graph:
const castedGraph = genericPhase as GraphPhase; const castedGraph = genericPhase as GraphPhase;
const graphPhase = new GraphPhase(castedGraph.name, 0, castedGraph.data, nodeLabelMap); const graphPhase = new GraphPhase(castedGraph.name, castedGraph.data,
this.recordOrigins(graphPhase); nodeMap as Array<GraphNode>, this.sources, this.inlinings);
this.phaseNames.set(graphPhase.name, this.phases.length); this.phaseNames.set(graphPhase.name, this.phases.length);
this.phases.push(graphPhase); this.phases.push(graphPhase);
selectedDynamicPhases.push(graphPhase);
lastGraphPhase = graphPhase;
break; break;
case PhaseType.TurboshaftGraph: case PhaseType.TurboshaftGraph:
const castedTurboshaftGraph = genericPhase as TurboshaftGraphPhase; const castedTurboshaftGraph = genericPhase as TurboshaftGraphPhase;
const turboshaftGraphPhase = new TurboshaftGraphPhase(castedTurboshaftGraph.name, const turboshaftGraphPhase = new TurboshaftGraphPhase(castedTurboshaftGraph.name,
castedTurboshaftGraph.data); castedTurboshaftGraph.data, nodeMap, this.sources, this.inlinings);
this.phaseNames.set(turboshaftGraphPhase.name, this.phases.length); this.phaseNames.set(turboshaftGraphPhase.name, this.phases.length);
this.phases.push(turboshaftGraphPhase); this.phases.push(turboshaftGraphPhase);
selectedDynamicPhases.push(turboshaftGraphPhase);
lastTurboshaftGraphPhase = turboshaftGraphPhase; lastTurboshaftGraphPhase = turboshaftGraphPhase;
lastGraphPhase = turboshaftGraphPhase;
break; break;
case PhaseType.TurboshaftCustomData: case PhaseType.TurboshaftCustomData:
const castedCustomData = camelize(genericPhase) as TurboshaftCustomDataPhase; const castedCustomData = camelize(genericPhase) as TurboshaftCustomDataPhase;
@ -212,12 +201,43 @@ export class SourceResolver {
throw "Unsupported phase type"; 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<GenericPosition>): Set<string> { public sourcePositionsToNodeIds(sourcePositions: Array<GenericPosition>): Set<string> {
const nodeIds = new Set<string>(); const nodeIds = new Set<string>();
for (const sp of sourcePositions) { for (const position of sourcePositions) {
const nodeIdsForPosition = this.positionToNodes.get(sp.toString()); const key = position.toString();
let nodeIdsForPosition: Array<string> = 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<BytecodePosition>): Set<string> {
const nodeIds = new Set<string>();
for (const position of bytecodePositions) {
const nodeIdsForPosition = this.positions.bytecodePositionToNodes.get(position.toString());
if (!nodeIdsForPosition) continue; if (!nodeIdsForPosition) continue;
for (const nodeId of nodeIdsForPosition) { for (const nodeId of nodeIdsForPosition) {
nodeIds.add(nodeId); nodeIds.add(nodeId);
@ -229,9 +249,17 @@ export class SourceResolver {
public nodeIdsToSourcePositions(nodeIds: Iterable<string>): Array<GenericPosition> { public nodeIdsToSourcePositions(nodeIds: Iterable<string>): Array<GenericPosition> {
const sourcePositions = new Map<string, GenericPosition>(); const sourcePositions = new Map<string, GenericPosition>();
for (const nodeId of nodeIds) { for (const nodeId of nodeIds) {
const position = this.nodePositionMap[nodeId]; const sourcePosition = this.positions.nodeIdToSourcePositionMap[nodeId];
if (!position) continue; if (sourcePosition) {
sourcePositions.set(position.toString(), position); 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<GenericPosition>(); const sourcePositionArray = new Array<GenericPosition>();
for (const sourcePosition of sourcePositions.values()) { for (const sourcePosition of sourcePositions.values()) {
@ -240,6 +268,16 @@ export class SourceResolver {
return sourcePositionArray; return sourcePositionArray;
} }
public nodeIdsToBytecodePositions(nodeIds: Iterable<string>): Array<BytecodePosition> {
const bytecodePositions = new Map<string, BytecodePosition>();
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 { public translateToSourceId(sourceId: number, location?: SourcePosition): SourcePosition {
for (const position of this.getInlineStack(location)) { for (const position of this.getInlineStack(location)) {
const inlining = this.inlinings[position.inliningId]; const inlining = this.inlinings[position.inliningId];
@ -287,6 +325,16 @@ export class SourceResolver {
return this.phases[phaseId]; 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 { public getPhaseNameById(phaseId: number): string {
return this.getPhase(phaseId).name; return this.getPhase(phaseId).name;
} }
@ -320,7 +368,7 @@ export class SourceResolver {
public setSourceLineToBytecodePosition(sourceLineToBytecodePositionJson): void { public setSourceLineToBytecodePosition(sourceLineToBytecodePositionJson): void {
if (!sourceLineToBytecodePositionJson) return; if (!sourceLineToBytecodePositionJson) return;
sourceLineToBytecodePositionJson.forEach((position, idx) => { 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; return inliningStack;
} }
private recordOrigins(graphPhase: GraphPhase): void { private sortSourcePositions(): void {
if (graphPhase.type !== PhaseType.Graph) return; for (const source of Object.values<Source>(this.sources)) {
for (const node of graphPhase.data.nodes) { source.sourcePositions = sortUnique(source.sourcePositions,
graphPhase.highestNodeId = Math.max(graphPhase.highestNodeId, node.id); (a, b) => a.lessOrEquals(b),
const origin = node.nodeLabel.origin; (a, b) => a.equals(b));
if (origin instanceof BytecodeOrigin) { }
const position = new BytecodePosition(origin.bytecodePosition); }
this.nodePositionMap[node.id] = position;
const key = position.toString(); private adaptInstructionsPhases(dynamicPhases: Array<DynamicPhase>): void {
if (!this.positionToNodes.has(key)) { const seaOfNodesInstructions = new InstructionsPhase("sea of nodes");
this.positionToNodes.set(key, new Array<string>()); 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); } else if (phase.type == PhaseType.TurboshaftGraph &&
const identifier = node.identifier(); prevPhase.type == PhaseType.TurboshaftGraph) {
if (!nodes.includes(identifier)) nodes.push(identifier); 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<number, number> {
// This function works with final node origins (we can have overwriting for Turboshaft IR)
const oldIdToNewIdMap = new Map<number, number>();
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;
}
} }

View File

@ -15,16 +15,15 @@ export class Source {
endPosition?: number; endPosition?: number;
constructor(sourceName: string, functionName: string, sourceText: string, sourceId: number, constructor(sourceName: string, functionName: string, sourceText: string, sourceId: number,
backwardsCompatibility: boolean, sourcePositions?: Array<SourcePosition>, backwardsCompatibility: boolean, startPosition?: number, endPosition?: number) {
startPosition?: number, endPosition?: number) {
this.sourceName = sourceName; this.sourceName = sourceName;
this.functionName = functionName; this.functionName = functionName;
this.sourceText = sourceText; this.sourceText = sourceText;
this.sourceId = sourceId; this.sourceId = sourceId;
this.backwardsCompatibility = backwardsCompatibility; this.backwardsCompatibility = backwardsCompatibility;
this.sourcePositions = sourcePositions ?? new Array<SourcePosition>();
this.startPosition = startPosition; this.startPosition = startPosition;
this.endPosition = endPosition; this.endPosition = endPosition;
this.sourcePositions = new Array<SourcePosition>();
} }
public toString(): string { public toString(): string {

View File

@ -70,7 +70,7 @@ window.onload = function () {
sourceResolver.setSourceLineToBytecodePosition(jsonObj.sourceLineToBytecodePosition); sourceResolver.setSourceLineToBytecodePosition(jsonObj.sourceLineToBytecodePosition);
sourceResolver.setSources(jsonObj.sources, mainFunction); sourceResolver.setSources(jsonObj.sources, mainFunction);
sourceResolver.setBytecodeSources(jsonObj.bytecodeSources); sourceResolver.setBytecodeSources(jsonObj.bytecodeSources);
sourceResolver.setNodePositionMap(jsonObj.nodePositions); sourceResolver.setFinalNodeOrigins(jsonObj.nodeOrigins);
sourceResolver.parsePhases(jsonObj.phases); sourceResolver.parsePhases(jsonObj.phases);
const [sourceTab, sourceContainer] = sourceTabs.addTabAndContent("Source"); const [sourceTab, sourceContainer] = sourceTabs.addTabAndContent("Source");

View File

@ -7,6 +7,10 @@ import { CodeMode, View } from "./view";
import { SelectionBroker } from "../selection/selection-broker"; import { SelectionBroker } from "../selection/selection-broker";
import { BytecodeSource } from "../source"; import { BytecodeSource } from "../source";
import { SourceResolver } from "../source-resolver"; 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 { export class BytecodeSourceView extends View {
broker: SelectionBroker; broker: SelectionBroker;
@ -14,6 +18,8 @@ export class BytecodeSourceView extends View {
sourceResolver: SourceResolver; sourceResolver: SourceResolver;
codeMode: CodeMode; codeMode: CodeMode;
bytecodeOffsetToHtmlElement: Map<number, HTMLElement>; bytecodeOffsetToHtmlElement: Map<number, HTMLElement>;
bytecodeOffsetSelection: SelectionMap;
bytecodeOffsetSelectionHandler: BytecodeOffsetSelectionHandler & ClearableHandler;
constructor(parent: HTMLElement, broker: SelectionBroker, sourceFunction: BytecodeSource, constructor(parent: HTMLElement, broker: SelectionBroker, sourceFunction: BytecodeSource,
sourceResolver: SourceResolver, codeMode: CodeMode) { sourceResolver: SourceResolver, codeMode: CodeMode) {
@ -23,6 +29,9 @@ export class BytecodeSourceView extends View {
this.sourceResolver = sourceResolver; this.sourceResolver = sourceResolver;
this.codeMode = codeMode; this.codeMode = codeMode;
this.bytecodeOffsetToHtmlElement = new Map<number, HTMLElement>(); this.bytecodeOffsetToHtmlElement = new Map<number, HTMLElement>();
this.bytecodeOffsetSelection = new SelectionMap((offset: number) => String(offset));
this.bytecodeOffsetSelectionHandler = this.initializeBytecodeOffsetSelectionHandler();
this.broker.addBytecodeOffsetHandler(this.bytecodeOffsetSelectionHandler);
this.initializeCode(); this.initializeCode();
} }
@ -86,6 +95,44 @@ export class BytecodeSourceView extends View {
codePre.appendChild(constantList); codePre.appendChild(constantList);
} }
private initializeBytecodeOffsetSelectionHandler(): BytecodeOffsetSelectionHandler
& ClearableHandler {
const view = this;
const broker = this.broker;
return {
select: function (offsets: Array<number>, selected: boolean) {
const bytecodePositions = new Array<BytecodePosition>();
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<BytecodePosition>,
selected: boolean) {
const offsets = new Array<number>();
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 { private getBytecodeHeaderHtmlElementName(): string {
return `source-pre-${this.source.sourceId}-header`; 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"; 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 { private insertLineContent(lineElement: HTMLElement, content: string): void {
const lineContentElement = createElement("span", "", content); const lineContentElement = createElement("span", "", content);
lineElement.appendChild(lineContentElement); lineElement.appendChild(lineContentElement);
} }
private insertLineNumber(lineElement: HTMLElement, lineNumber: number): void { private insertLineNumber(lineElement: HTMLElement, lineNumber: number): void {
const view = this;
const lineNumberElement = createElement("div", "line-number", String(lineNumber)); 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); lineElement.insertBefore(lineNumberElement, lineElement.firstChild);
} }
} }

View File

@ -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(); const key = sourcePosition.toString();
if (!this.sourcePositionToHtmlElements.has(key)) { if (!this.sourcePositionToHtmlElements.has(key)) {
this.sourcePositionToHtmlElements.set(key, new Array<HTMLElement>()); this.sourcePositionToHtmlElements.set(key, new Array<HTMLElement>());

View File

@ -12,8 +12,7 @@ import { GraphNode } from "../phases/graph-phase/graph-node";
import { GraphEdge } from "../phases/graph-phase/graph-edge"; import { GraphEdge } from "../phases/graph-phase/graph-edge";
import { GraphLayout } from "../graph-layout"; import { GraphLayout } from "../graph-layout";
import { GraphPhase, GraphStateType } from "../phases/graph-phase/graph-phase"; import { GraphPhase, GraphStateType } from "../phases/graph-phase/graph-phase";
import { BytecodePosition } from "../position"; import { NodeOrigin } from "../origin";
import { BytecodeOrigin, NodeOrigin } from "../origin";
import { MovableView } from "./movable-view"; import { MovableView } from "./movable-view";
import { ClearableHandler, NodeSelectionHandler } from "../selection/selection-handler"; import { ClearableHandler, NodeSelectionHandler } from "../selection/selection-handler";
import { GenericPosition } from "../source-resolver"; import { GenericPosition } from "../source-resolver";
@ -32,8 +31,10 @@ export class GraphView extends MovableView<Graph> {
toolbox: HTMLElement) { toolbox: HTMLElement) {
super(idOrContainer, broker, showPhaseByName, toolbox); super(idOrContainer, broker, showPhaseByName, toolbox);
this.state.selection = new SelectionMap(node => node.identifier(), this.state.selection = new SelectionMap(node => node.identifier(), node => {
node => node.nodeLabel?.origin?.identifier()); if (node instanceof GraphNode) return node.nodeLabel?.origin?.identifier();
return node?.origin?.identifier();
});
this.nodeSelectionHandler = this.initializeNodeSelectionHandler(); this.nodeSelectionHandler = this.initializeNodeSelectionHandler();
this.svg.on("click", () => this.nodeSelectionHandler.clear()); this.svg.on("click", () => this.nodeSelectionHandler.clear());
@ -406,16 +407,19 @@ export class GraphView extends MovableView<Graph> {
return { return {
select: function (selectedNodes: Array<GraphNode>, selected: boolean) { select: function (selectedNodes: Array<GraphNode>, selected: boolean) {
const locations = new Array<GenericPosition>(); const locations = new Array<GenericPosition>();
const nodes = new Set<string>();
for (const node of selectedNodes) { for (const node of selectedNodes) {
if (node.nodeLabel.sourcePosition) { if (node.nodeLabel.sourcePosition) {
locations.push(node.nodeLabel.sourcePosition); locations.push(node.nodeLabel.sourcePosition);
nodes.add(node.identifier());
} }
if (node.nodeLabel.origin && node.nodeLabel.origin instanceof BytecodeOrigin) { if (node.nodeLabel.bytecodePosition) {
locations.push(new BytecodePosition(node.nodeLabel.origin.bytecodePosition)); locations.push(node.nodeLabel.bytecodePosition);
nodes.add(node.identifier());
} }
} }
view.state.selection.select(selectedNodes, selected); view.state.selection.select(selectedNodes, selected);
view.broker.broadcastSourcePositionSelect(this, locations, selected); view.broker.broadcastSourcePositionSelect(this, locations, selected, nodes);
view.updateGraphVisibility(); view.updateGraphVisibility();
}, },
clear: function () { clear: function () {
@ -434,10 +438,10 @@ export class GraphView extends MovableView<Graph> {
if (!node) continue; if (!node) continue;
node.visible = true; node.visible = true;
node.inputs.forEach(edge => { 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 => { node.outputs.forEach(edge => {
edge.visible = edge.visible || view.state.selection.isSelected(edge.target); edge.visible = edge.visible || edge.target.visible;
}); });
} }
view.updateGraphVisibility(); view.updateGraphVisibility();
@ -710,12 +714,6 @@ export class GraphView extends MovableView<Graph> {
this.updateGraphVisibility(); 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 { private selectOrigins(): void {
const selection = new SelectionStorage(); const selection = new SelectionStorage();
const origins = new Array<GraphNode>(); const origins = new Array<GraphNode>();

View File

@ -11,11 +11,15 @@ import { SourceResolver } from "../source-resolver";
import { GraphNode } from "../phases/graph-phase/graph-node"; import { GraphNode } from "../phases/graph-phase/graph-node";
import { HistoryHandler } from "../selection/selection-handler"; import { HistoryHandler } from "../selection/selection-handler";
import { GraphPhase } from "../phases/graph-phase/graph-phase"; import { GraphPhase } from "../phases/graph-phase/graph-phase";
import { NodeOrigin } from "../origin";
import { SelectionStorage } from "../selection/selection-storage"; 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 { export class HistoryView extends View {
node: GraphNode; node: GNode;
broker: SelectionBroker; broker: SelectionBroker;
sourceResolver: SourceResolver; sourceResolver: SourceResolver;
historyHandler: HistoryHandler; historyHandler: HistoryHandler;
@ -78,7 +82,7 @@ export class HistoryView extends View {
private initializeNodeSelectionHandler(): HistoryHandler { private initializeNodeSelectionHandler(): HistoryHandler {
const view = this; const view = this;
return { return {
showTurbofanNodeHistory: function (node: GraphNode, phaseName: string) { showNodeHistory: function (node: GNode, phaseName: string) {
view.clear(); view.clear();
view.node = node; view.node = node;
const phaseId = view.sourceResolver.getPhaseIdByName(phaseName); const phaseId = view.sourceResolver.getPhaseIdByName(phaseName);
@ -264,7 +268,7 @@ export class HistoryView extends View {
} }
private setLabel(): void { 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"); const coefficient = this.getCoefficient("history-tspan-font-size");
this.labelBox = measureText(this.label, coefficient); this.labelBox = measureText(this.label, coefficient);
} }
@ -275,14 +279,14 @@ export class HistoryView extends View {
return Math.min(tspanSize, varSize) / Math.max(tspanSize, varSize); return Math.min(tspanSize, varSize) / Math.max(tspanSize, varSize);
} }
private getPhaseHistory(historyChain: Map<number, GraphNode>): void { private getPhaseHistory(historyChain: Map<number, GNode>): void {
const uniqueAncestors = new Set<string>(); const uniqueAncestors = new Set<string>();
const coefficient = this.getCoefficient("history-item-tspan-font-size"); const coefficient = this.getCoefficient("history-item-tspan-font-size");
let prevNode = null; let prevNode = null;
let first = true; let first = true;
for (let i = 0; i < this.sourceResolver.phases.length; i++) { for (let i = 0; i < this.sourceResolver.phases.length; i++) {
const phase = this.sourceResolver.getPhase(i); const phase = this.sourceResolver.getGraphPhase(i);
if (!(phase instanceof GraphPhase)) continue; if (!phase) continue;
const phaseNameMeasure = measureText(phase.name, coefficient); const phaseNameMeasure = measureText(phase.name, coefficient);
this.maxPhaseNameWidth = Math.max(this.maxPhaseNameWidth, phaseNameMeasure.width); this.maxPhaseNameWidth = Math.max(this.maxPhaseNameWidth, phaseNameMeasure.width);
@ -298,10 +302,11 @@ export class HistoryView extends View {
if (prevNode && !prevNode.equals(node) && if (prevNode && !prevNode.equals(node) &&
phase.originIdToNodesMap.has(prevNode.identifier())) { phase.originIdToNodesMap.has(prevNode.identifier())) {
const prevNodeCurrentState = phase.nodeIdToNodeMap[prevNode.identifier()]; const prevNodeCurrentState = phase.nodeIdToNodeMap[prevNode.identifier()];
const inplaceUpdate = prevNodeCurrentState?.nodeLabel?.inplaceUpdatePhase;
if (!prevNodeCurrentState) { if (!prevNodeCurrentState) {
this.addToHistory(i, prevNode, HistoryChange.Removed); 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); this.addToHistory(i, prevNodeCurrentState, HistoryChange.InplaceUpdated);
} else if (node.identifier() != prevNode.identifier()) { } else if (node.identifier() != prevNode.identifier()) {
this.addToHistory(i, prevNodeCurrentState, HistoryChange.Survived); this.addToHistory(i, prevNodeCurrentState, HistoryChange.Survived);
@ -314,7 +319,7 @@ export class HistoryView extends View {
continue; continue;
} }
if (node.nodeLabel.inplaceUpdatePhase && node.nodeLabel.inplaceUpdatePhase == phase.name) { if (node instanceof GraphNode && node.getInplaceUpdatePhase() == phase.name) {
this.addToHistory(i, node, HistoryChange.InplaceUpdated); this.addToHistory(i, node, HistoryChange.InplaceUpdated);
} }
@ -328,7 +333,7 @@ export class HistoryView extends View {
} }
} }
private addHistoryAncestors(key: string, phase: GraphPhase, uniqueAncestors: Set<string>): private addHistoryAncestors(key: string, phase: GPhase, uniqueAncestors: Set<string>):
boolean { boolean {
let changed = false; let changed = false;
const phaseId = this.sourceResolver.getPhaseIdByName(phase.name); const phaseId = this.sourceResolver.getPhaseIdByName(phase.name);
@ -343,22 +348,22 @@ export class HistoryView extends View {
return changed; return changed;
} }
private getHistoryChain(phaseId: number, node: GraphNode): Map<number, GraphNode> { private getHistoryChain(phaseId: number, node: GNode): Map<number, GNode> {
const leftChain = this.getLeftHistoryChain(phaseId, node); const leftChain = this.getLeftHistoryChain(phaseId, node);
const rightChain = this.getRightHistoryChain(phaseId, node); const rightChain = this.getRightHistoryChain(phaseId, node);
return new Map([...leftChain, ...rightChain]); return new Map([...leftChain, ...rightChain]);
} }
private getLeftHistoryChain(phaseId: number, node: GraphNode): Map<number, GraphNode> { private getLeftHistoryChain(phaseId: number, node: GNode): Map<number, GNode> {
const leftChain = new Map<number, GraphNode>(); const leftChain = new Map<number, GNode>();
for (let i = phaseId; i >= 0; i--) { for (let i = phaseId; i >= 0; i--) {
const phase = this.sourceResolver.getPhase(i); const phase = this.sourceResolver.getGraphPhase(i);
if (!(phase instanceof GraphPhase)) continue; if (!phase) continue;
let currentNode = phase.nodeIdToNodeMap[node.identifier()]; let currentNode = phase.nodeIdToNodeMap[node.identifier()];
if (!currentNode) { if (!currentNode) {
const nodeOrigin = node.nodeLabel.origin; const nodeOrigin = node.getNodeOrigin();
if (nodeOrigin instanceof NodeOrigin) { if (nodeOrigin) {
currentNode = phase.nodeIdToNodeMap[nodeOrigin.identifier()]; currentNode = phase.nodeIdToNodeMap[nodeOrigin.identifier()];
} }
if (!currentNode) return leftChain; if (!currentNode) return leftChain;
@ -370,12 +375,12 @@ export class HistoryView extends View {
return leftChain; return leftChain;
} }
private getRightHistoryChain(phaseId: number, node: GraphNode): Map<number, GraphNode> { private getRightHistoryChain(phaseId: number, node: GNode): Map<number, GNode> {
const rightChain = new Map<number, GraphNode>(); const rightChain = new Map<number, GNode>();
for (let i = phaseId + 1; i < this.sourceResolver.phases.length; i++) { for (let i = phaseId + 1; i < this.sourceResolver.phases.length; i++) {
const phase = this.sourceResolver.getPhase(i); const phase = this.sourceResolver.getGraphPhase(i);
if (!(phase instanceof GraphPhase)) continue; if (!phase) continue;
const currentNode = phase.nodeIdToNodeMap[node.identifier()]; const currentNode = phase.nodeIdToNodeMap[node.identifier()];
if (!currentNode) return rightChain; if (!currentNode) return rightChain;
rightChain.set(i, currentNode); rightChain.set(i, currentNode);
@ -385,7 +390,7 @@ export class HistoryView extends View {
return rightChain; 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)) { if (!this.phaseIdToHistory.has(phaseId)) {
this.phaseIdToHistory.set(phaseId, new PhaseHistory(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 { private clear(): void {
this.phaseIdToHistory.clear(); this.phaseIdToHistory.clear();
this.maxNodeWidth = 0; this.maxNodeWidth = 0;
@ -458,7 +473,7 @@ export class PhaseHistory {
this.nodeIdToRecord = new Map<string, HistoryRecord>(); this.nodeIdToRecord = new Map<string, HistoryRecord>();
} }
public addChange(node: GraphNode, change: HistoryChange): void { public addChange(node: GNode, change: HistoryChange): void {
const key = node.identifier(); const key = node.identifier();
if (!this.nodeIdToRecord.has(key)) { if (!this.nodeIdToRecord.has(key)) {
this.nodeIdToRecord.set(key, new HistoryRecord(node)); this.nodeIdToRecord.set(key, new HistoryRecord(node));
@ -475,10 +490,10 @@ export class PhaseHistory {
} }
export class HistoryRecord { export class HistoryRecord {
node: GraphNode; node: GNode;
changes: Set<HistoryChange>; changes: Set<HistoryChange>;
constructor(node: GraphNode) { constructor(node: GNode) {
this.node = node; this.node = node;
this.changes = new Set<HistoryChange>(); this.changes = new Set<HistoryChange>();
} }

View File

@ -252,6 +252,12 @@ export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> 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): protected createImgToggleInput(id: string, title: string, initState: boolean, onClick):
HTMLElement { HTMLElement {
const input = this.createImgInput(id, title, onClick); const input = this.createImgInput(id, title, onClick);

View File

@ -21,6 +21,7 @@ import { TurboshaftGraphLayout } from "../turboshaft-graph-layout";
import { GraphStateType } from "../phases/graph-phase/graph-phase"; import { GraphStateType } from "../phases/graph-phase/graph-phase";
import { SelectionStorage } from "../selection/selection-storage"; import { SelectionStorage } from "../selection/selection-storage";
import { DataTarget } from "../phases/turboshaft-custom-data-phase"; import { DataTarget } from "../phases/turboshaft-custom-data-phase";
import { SourcePosition } from "../position";
import { import {
TurboshaftCustomData, TurboshaftCustomData,
TurboshaftGraphPhase TurboshaftGraphPhase
@ -99,8 +100,12 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
this.broker.addNodeHandler(this.nodeSelectionHandler); this.broker.addNodeHandler(this.nodeSelectionHandler);
this.broker.addBlockHandler(this.blockSelectionHandler); this.broker.addBlockHandler(this.blockSelectionHandler);
if (adaptedSelection.isAdapted()) { const countOfSelectedItems = adaptedSelection.isAdapted()
this.attachSelection(adaptedSelection); ? this.attachSelection(adaptedSelection)
: 0;
if (countOfSelectedItems > 0) {
this.updateGraphVisibility();
this.viewSelection(); this.viewSelection();
} else { } else {
if (this.state.cacheLayout && data.transform) { if (this.state.cacheLayout && data.transform) {
@ -142,6 +147,9 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
eventHandled = false; eventHandled = false;
} }
break; break;
case 72: // 'h'
this.showHoveredNodeHistory();
break;
case 73: // 'i' case 73: // 'i'
this.selectNodesOfSelectedBlocks(); this.selectNodesOfSelectedBlocks();
break; break;
@ -255,7 +263,16 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const view = this; const view = this;
return { return {
select: function (selectedNodes: Array<TurboshaftGraphNode>, selected: boolean) { select: function (selectedNodes: Array<TurboshaftGraphNode>, selected: boolean) {
const sourcePositions = new Array<SourcePosition>();
const nodes = new Set<string>();
for (const node of selectedNodes) {
if (node.sourcePosition) {
sourcePositions.push(node.sourcePosition);
nodes.add(node.identifier());
}
}
view.state.selection.select(selectedNodes, selected); view.state.selection.select(selectedNodes, selected);
view.broker.broadcastSourcePositionSelect(this, sourcePositions, selected, nodes);
view.updateGraphVisibility(); view.updateGraphVisibility();
}, },
clear: function () { clear: function () {
@ -367,6 +384,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const selectedCustomData = select.options[this.selectedIndex].text; const selectedCustomData = select.options[this.selectedIndex].text;
storageSetItem(storageKey, selectedCustomData); storageSetItem(storageKey, selectedCustomData);
view.updateGraphVisibility(); view.updateGraphVisibility();
view.updateInlineNodesCustomData();
}; };
this.toolbox.appendChild(select); this.toolbox.appendChild(select);
@ -603,13 +621,11 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
private updateInlineNodes(): void { private updateInlineNodes(): void {
const view = this; const view = this;
const state = this.state; const state = this.state;
const storageKey = this.customDataStorageKey(); const showCustomData = this.nodesCustomDataShowed();
const selectedCustomData = storageGetItem(storageKey, null, false);
let totalHeight = 0; let totalHeight = 0;
let blockId = 0; let blockId = 0;
view.visibleNodes.each(function (node: TurboshaftGraphNode) { view.visibleNodes.each(function (node: TurboshaftGraphNode) {
const nodeSvg = d3.select(this); const nodeSvg = d3.select(this);
const showCustomData = view.nodesCustomDataShowed();
if (blockId != node.block.id) { if (blockId != node.block.id) {
blockId = node.block.id; blockId = node.block.id;
totalHeight = 0; totalHeight = 0;
@ -623,18 +639,25 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
.attr("dy", nodeY) .attr("dy", nodeY)
.attr("visibility", !node.block.collapsed ? "visible" : "hidden"); .attr("visibility", !node.block.collapsed ? "visible" : "hidden");
const svgNodeCustomData = nodeSvg nodeSvg
.select(".inline-node-custom-data") .select(".inline-node-custom-data")
.attr("visibility", !node.block.collapsed && showCustomData ? "visible" : "hidden"); .attr("visibility", !node.block.collapsed && showCustomData ? "visible" : "hidden");
});
}
if (!node.block.collapsed && showCustomData) { private updateInlineNodesCustomData(): void {
const customData = view.graph.getCustomData(selectedCustomData, node.id, DataTarget.Nodes); const view = this;
svgNodeCustomData const storageKey = this.customDataStorageKey();
.select("tspan") const selectedCustomData = storageGetItem(storageKey, null, false);
.text(view.getReadableString(customData, node.block.width)) if (!this.nodesCustomDataShowed()) return;
.append("title") view.visibleNodes.each(function (node: TurboshaftGraphNode) {
.text(customData); 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<TurboshaftGraph> {
} }
} }
private attachSelection(selection: SelectionStorage): void { private attachSelection(selection: SelectionStorage): number {
if (!(selection instanceof SelectionStorage)) return; if (!(selection instanceof SelectionStorage)) return 0;
this.nodeSelectionHandler.clear(); this.nodeSelectionHandler.clear();
this.blockSelectionHandler.clear(); this.blockSelectionHandler.clear();
const selectedNodes = [ const selectedNodes = [
@ -719,6 +742,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
selection.adaptedBocks.has(this.state.blocksSelection.stringKey(block))) selection.adaptedBocks.has(this.state.blocksSelection.stringKey(block)))
]; ];
this.blockSelectionHandler.select(selectedBlocks, true); this.blockSelectionHandler.select(selectedBlocks, true);
return selectedNodes.length + selectedBlocks.length;
} }
private nodesCustomDataShowed(): boolean { private nodesCustomDataShowed(): boolean {
@ -813,6 +837,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const extent = view.graph.redetermineGraphBoundingBox(view.state.showCustomData); const extent = view.graph.redetermineGraphBoundingBox(view.state.showCustomData);
view.panZoom.translateExtent(extent); view.panZoom.translateExtent(extent);
view.adaptiveUpdateGraphVisibility(); view.adaptiveUpdateGraphVisibility();
view.updateInlineNodesCustomData();
} }
private toggleLayoutCachingAction(view: TurboshaftGraphView): void { private toggleLayoutCachingAction(view: TurboshaftGraphView): void {
@ -823,7 +848,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
// Hotkeys handlers // Hotkeys handlers
private selectAllNodes(): void { private selectAllNodes(): void {
this.state.selection.select(this.graph.nodeMap, true); this.nodeSelectionHandler.select(this.graph.nodeMap, true);
this.updateGraphVisibility(); this.updateGraphVisibility();
} }
@ -865,7 +890,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
block.collapsed = false; block.collapsed = false;
selectedNodes = selectedNodes.concat(block.nodes); selectedNodes = selectedNodes.concat(block.nodes);
} }
this.state.selection.select(selectedNodes, true); this.nodeSelectionHandler.select(selectedNodes, true);
this.updateGraphVisibility(); this.updateGraphVisibility();
} }
} }