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

View File

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

View File

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

View File

@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { GraphNode } from "./phases/graph-phase/graph-node";
import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";
export abstract class Origin {
phase: string;
reducer: string;
@ -14,10 +17,13 @@ export abstract class Origin {
export class NodeOrigin extends Origin {
nodeId: number;
node: GraphNode | TurboshaftGraphNode;
constructor(nodeId: number, phase: string, reducer: string) {
constructor(nodeId: number, node: GraphNode | TurboshaftGraphNode, phase: string,
reducer: string) {
super(phase, reducer);
this.nodeId = nodeId;
this.node = node;
}
public identifier(): string {

View File

@ -7,6 +7,7 @@ import { alignUp, measureText } from "../../common/util";
import { NodeLabel } from "../../node-label";
import { Node } from "../../node";
import { GraphEdge } from "./graph-edge";
import { NodeOrigin } from "../../origin";
export class GraphNode extends Node<GraphEdge> {
nodeLabel: NodeLabel;
@ -79,6 +80,19 @@ export class GraphNode extends Node<GraphEdge> {
return this.nodeLabel.getTitle();
}
public getHistoryLabel(): string {
return `${this.id} ${this.nodeLabel.opcode}`;
}
public getNodeOrigin(): NodeOrigin {
const origin = this.nodeLabel.origin;
return origin instanceof NodeOrigin ? origin : null;
}
public getInplaceUpdatePhase(): string {
return this?.nodeLabel?.inplaceUpdatePhase;
}
public getDisplayLabel(): string {
return this.nodeLabel.getDisplayLabel();
}

View File

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

View File

@ -37,6 +37,18 @@ export class InstructionsPhase extends Phase {
return -1;
}
public merge(other: InstructionsPhase): void {
if (!other) return;
if (this.name == "") this.name = "merged data";
this.nodeIdToInstructionRange = new Array<[number, number]>();
this.blockIdToInstructionRange = other.blockIdToInstructionRange;
this.instructionOffsetToPCOffset = other.instructionOffsetToPCOffset;
this.codeOffsetsInfo = other.codeOffsetsInfo;
this.instructionToPCOffset = other.instructionToPCOffset;
this.pcOffsetToInstructions = other.pcOffsetToInstructions;
this.pcOffsets = other.pcOffsets;
}
public instructionToPcOffsets(instruction: number): TurbolizerInstructionStartInfo {
return this.instructionToPCOffset[instruction];
}

View File

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

View File

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

View File

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

View File

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

View File

@ -7,31 +7,51 @@ import { TurboshaftGraphNode } from "./turboshaft-graph-node";
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
import { DataTarget, TurboshaftCustomDataPhase } from "../turboshaft-custom-data-phase";
import { GraphNode } from "../graph-phase/graph-node";
import { NodeOrigin } from "../../origin";
import { Source } from "../../source";
import { InstructionsPhase } from "../instructions-phase";
import {
BytecodePosition,
InliningPosition,
PositionsContainer,
SourcePosition
} from "../../position";
export class TurboshaftGraphPhase extends Phase {
data: TurboshaftGraphData;
customData: TurboshaftCustomData;
stateType: GraphStateType;
instructionsPhase: InstructionsPhase;
nodeIdToNodeMap: Array<TurboshaftGraphNode>;
blockIdToBlockMap: Array<TurboshaftGraphBlock>;
originIdToNodesMap: Map<string, Array<TurboshaftGraphNode>>;
positions: PositionsContainer;
highestNodeId: number;
rendered: boolean;
customDataShowed: boolean;
transform: { x: number, y: number, scale: number };
constructor(name: string, dataJson) {
constructor(name: string, dataJson, nodeMap: Array<GraphNode | TurboshaftGraphNode>,
sources: Array<Source>, inlinings: Array<InliningPosition>) {
super(name, PhaseType.TurboshaftGraph);
this.stateType = GraphStateType.NeedToFullRebuild;
this.instructionsPhase = new InstructionsPhase();
this.customData = new TurboshaftCustomData();
this.nodeIdToNodeMap = new Array<TurboshaftGraphNode>();
this.blockIdToBlockMap = new Array<TurboshaftGraphBlock>();
this.originIdToNodesMap = new Map<string, Array<TurboshaftGraphNode>>();
this.positions = new PositionsContainer();
this.highestNodeId = 0;
this.rendered = false;
this.parseDataFromJSON(dataJson);
this.parseDataFromJSON(dataJson, nodeMap, sources, inlinings);
}
private parseDataFromJSON(dataJson): void {
private parseDataFromJSON(dataJson, nodeMap: Array<GraphNode | TurboshaftGraphNode>,
sources: Array<Source>, inlinings: Array<InliningPosition>): void {
this.data = new TurboshaftGraphData();
this.parseBlocksFromJSON(dataJson.blocks);
this.parseNodesFromJSON(dataJson.nodes);
this.parseNodesFromJSON(dataJson.nodes, nodeMap, sources, inlinings);
this.parseEdgesFromJSON(dataJson.edges);
}
@ -52,14 +72,62 @@ export class TurboshaftGraphPhase extends Phase {
}
}
private parseNodesFromJSON(nodesJson): void {
private parseNodesFromJSON(nodesJson, nodeMap: Array<GraphNode | TurboshaftGraphNode>,
sources: Array<Source>, inlinings: Array<InliningPosition>): void {
for (const nodeJson of nodesJson) {
const block = this.blockIdToBlockMap[nodeJson.block_id];
const node = new TurboshaftGraphNode(nodeJson.id, nodeJson.title,
block, nodeJson.op_properties_type);
let sourcePosition: SourcePosition = null;
const sourcePositionJson = nodeJson.sourcePosition;
if (sourcePositionJson) {
const scriptOffset = sourcePositionJson.scriptOffset;
const inliningId = sourcePositionJson.inliningId;
sourcePosition = new SourcePosition(scriptOffset, inliningId);
}
let origin: NodeOrigin = null;
let bytecodePosition: BytecodePosition = null;
const originJson = nodeJson.origin;
if (originJson) {
const nodeId = originJson.nodeId;
const originNode = nodeMap[nodeId];
origin = new NodeOrigin(nodeId, originNode, originJson.phase, originJson.reducer);
if (originNode) {
if (originNode instanceof GraphNode) {
bytecodePosition = originNode.nodeLabel.bytecodePosition;
} else {
bytecodePosition = originNode.bytecodePosition;
}
}
}
const node = new TurboshaftGraphNode(nodeJson.id, nodeJson.title, block, sourcePosition,
bytecodePosition, origin, nodeJson.op_properties_type);
block.nodes.push(node);
this.data.nodes.push(node);
this.nodeIdToNodeMap[node.identifier()] = node;
this.highestNodeId = Math.max(this.highestNodeId, node.id);
if (origin) {
const identifier = origin.identifier();
if (!this.originIdToNodesMap.has(identifier)) {
this.originIdToNodesMap.set(identifier, new Array<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) {
block.initCollapsedLabel();
@ -127,7 +195,9 @@ export class TurboshaftCustomData {
private concatCustomData(key: number, items: Map<string, TurboshaftCustomDataPhase>): string {
let customData = "";
for (const [name, dataPhase] of items.entries()) {
customData += `\n${name}: ${dataPhase.data[key] ?? ""}`;
if (dataPhase.data[key] && dataPhase.data[key].length > 0) {
customData += `\n${name}: ${dataPhase.data[key]}`;
}
}
return customData;
}

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";
export class InliningPosition {
sourceId: number;
inliningPosition: SourcePosition;
@ -43,9 +45,11 @@ export class SourcePosition {
export class BytecodePosition {
bytecodePosition: number;
inliningId: number;
constructor(bytecodePosition: number) {
constructor(bytecodePosition: number, inliningId: number) {
this.bytecodePosition = bytecodePosition;
this.inliningId = inliningId;
}
public isValid(): boolean {
@ -53,6 +57,54 @@ export class BytecodePosition {
}
public toString(): string {
return `BCP:${this.bytecodePosition}`;
return `BCP:${this.inliningId}:${this.bytecodePosition}`;
}
}
export class PositionsContainer {
nodeIdToSourcePositionMap: Array<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 { GraphNode } from "../phases/graph-phase/graph-node";
import { BytecodePosition } from "../position";
import { TurboshaftGraphNode } from "../phases/turboshaft-graph-phase/turboshaft-graph-node";
import {
ClearableHandler,
SourcePositionSelectionHandler,
@ -11,7 +13,8 @@ import {
BlockSelectionHandler,
InstructionSelectionHandler,
RegisterAllocationSelectionHandler,
HistoryHandler
HistoryHandler,
BytecodeOffsetSelectionHandler
} from "./selection-handler";
export class SelectionBroker {
@ -22,6 +25,7 @@ export class SelectionBroker {
blockHandlers: Array<BlockSelectionHandler>;
instructionHandlers: Array<InstructionSelectionHandler>;
sourcePositionHandlers: Array<SourcePositionSelectionHandler>;
bytecodeOffsetHandlers: Array<BytecodeOffsetSelectionHandler>;
registerAllocationHandlers: Array<RegisterAllocationSelectionHandler>;
constructor(sourceResolver: SourceResolver) {
@ -32,6 +36,7 @@ export class SelectionBroker {
this.blockHandlers = new Array<BlockSelectionHandler>();
this.instructionHandlers = new Array<InstructionSelectionHandler>();
this.sourcePositionHandlers = new Array<SourcePositionSelectionHandler>();
this.bytecodeOffsetHandlers = new Array<BytecodeOffsetSelectionHandler>();
this.registerAllocationHandlers = new Array<RegisterAllocationSelectionHandler>();
}
@ -74,15 +79,22 @@ export class SelectionBroker {
this.sourcePositionHandlers.push(handler);
}
public addBytecodeOffsetHandler(handler: BytecodeOffsetSelectionHandler & ClearableHandler):
void {
this.allHandlers.push(handler);
this.bytecodeOffsetHandlers.push(handler);
}
public addRegisterAllocatorHandler(handler: RegisterAllocationSelectionHandler
& ClearableHandler): void {
this.allHandlers.push(handler);
this.registerAllocationHandlers.push(handler);
}
public broadcastHistoryShow(from, node: GraphNode, phaseName: string): void {
public broadcastHistoryShow(from, node: GraphNode | TurboshaftGraphNode, phaseName: string):
void {
for (const handler of this.historyHandlers) {
if (handler != from) handler.showTurbofanNodeHistory(node, phaseName);
if (handler != from) handler.showNodeHistory(node, phaseName);
}
}
@ -93,7 +105,7 @@ export class SelectionBroker {
if (handler != from) handler.brokeredInstructionSelect([instructionOffsets], selected);
}
// Select the lines from the source panel (left panel)
// Select the lines from the source and bytecode panels (left panels)
const pcOffsets = this.sourceResolver.instructionsPhase
.instructionsToKeyPcOffsets(instructionOffsets);
@ -103,12 +115,16 @@ export class SelectionBroker {
for (const handler of this.sourcePositionHandlers) {
if (handler != from) handler.brokeredSourcePositionSelect(sourcePositions, selected);
}
const bytecodePositions = this.sourceResolver.nodeIdsToBytecodePositions(nodes);
for (const handler of this.bytecodeOffsetHandlers) {
if (handler != from) handler.brokeredBytecodeOffsetSelect(bytecodePositions, selected);
}
}
// The middle panel lines have already been selected so there's no need to reselect them.
}
public broadcastSourcePositionSelect(from, sourcePositions: Array<GenericPosition>,
selected: boolean): void {
selected: boolean, selectedNodes?: Set<string>): void {
sourcePositions = sourcePositions.filter(sourcePosition => {
if (!sourcePosition.isValid()) {
console.warn("Invalid source position");
@ -128,6 +144,44 @@ export class SelectionBroker {
if (handler != from) handler.brokeredNodeSelect(nodes, selected);
}
// Select bytecode source panel (left panel)
const bytecodePositions = selectedNodes
? this.sourceResolver.nodeIdsToBytecodePositions(selectedNodes)
: this.sourceResolver.nodeIdsToBytecodePositions(nodes);
for (const handler of this.bytecodeOffsetHandlers) {
if (handler != from) handler.brokeredBytecodeOffsetSelect(bytecodePositions, selected);
}
this.selectInstructionsAndRegisterAllocations(from, nodes, selected);
}
public broadcastBytecodePositionsSelect(from, bytecodePositions: Array<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);
}
@ -143,6 +197,12 @@ export class SelectionBroker {
if (handler != from) handler.brokeredSourcePositionSelect(sourcePositions, selected);
}
// Select bytecode source panel (left panel)
const bytecodePositions = this.sourceResolver.nodeIdsToBytecodePositions(nodes);
for (const handler of this.bytecodeOffsetHandlers) {
if (handler != from) handler.brokeredBytecodeOffsetSelect(bytecodePositions, selected);
}
this.selectInstructionsAndRegisterAllocations(from, nodes, selected);
}

View File

@ -6,13 +6,14 @@ import { TurboshaftGraphNode } from "../phases/turboshaft-graph-phase/turboshaft
import { GraphNode } from "../phases/graph-phase/graph-node";
import { TurboshaftGraphBlock } from "../phases/turboshaft-graph-phase/turboshaft-graph-block";
import { GenericPosition } from "../source-resolver";
import { BytecodePosition } from "../position";
export interface ClearableHandler {
brokeredClear(): void;
}
export interface HistoryHandler {
showTurbofanNodeHistory(node: GraphNode, phaseName: string): void;
showNodeHistory(node: GraphNode | TurboshaftGraphNode, phaseName: string): void;
}
export interface NodeSelectionHandler {
@ -41,6 +42,12 @@ export interface SourcePositionSelectionHandler {
brokeredSourcePositionSelect(sourcePositions: Array<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 {
// These are called instructionIds since the class of the divs is "instruction-id"
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 { GraphPhase } from "./phases/graph-phase/graph-phase";
import { DisassemblyPhase } from "./phases/disassembly-phase";
import { BytecodePosition, InliningPosition, SourcePosition } from "./position";
import { InstructionsPhase } from "./phases/instructions-phase";
import { SchedulePhase } from "./phases/schedule-phase";
import { SequencePhase } from "./phases/sequence-phase";
import { BytecodeOrigin } from "./origin";
import { BytecodeSource, BytecodeSourceData, Source } from "./source";
import { NodeLabel } from "./node-label";
import { TurboshaftCustomDataPhase } from "./phases/turboshaft-custom-data-phase";
import { TurboshaftGraphPhase } from "./phases/turboshaft-graph-phase/turboshaft-graph-phase";
import { GraphNode } from "./phases/graph-phase/graph-node";
import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";
import { BytecodePosition, InliningPosition, PositionsContainer, SourcePosition } from "./position";
import { NodeOrigin } from "./origin";
export type GenericPosition = SourcePosition | BytecodePosition;
export type DynamicPhase = GraphPhase | TurboshaftGraphPhase | SchedulePhase | SequencePhase;
export type GenericPhase = GraphPhase | TurboshaftGraphPhase | TurboshaftCustomDataPhase
| DisassemblyPhase | InstructionsPhase | SchedulePhase | SequencePhase;
export class SourceResolver {
nodePositionMap: Array<GenericPosition>;
sources: Array<Source>;
bytecodeSources: Map<number, BytecodeSource>;
inlinings: Array<InliningPosition>;
inliningsMap: Map<string, InliningPosition>;
positionToNodes: Map<string, Array<string>>;
phases: Array<GenericPhase>;
phaseNames: Map<string, number>;
disassemblyPhase: DisassemblyPhase;
instructionsPhase: InstructionsPhase;
linePositionMap: Map<string, Array<GenericPosition>>;
finalNodeOrigins: Array<NodeOrigin>;
instructionsPhase: InstructionsPhase;
positions: PositionsContainer;
constructor() {
// Maps node ids to source positions.
this.nodePositionMap = new Array<GenericPosition>();
// Maps source ids to source objects.
this.sources = new Array<Source>();
// Maps bytecode source ids to bytecode source objects.
@ -44,15 +44,14 @@ export class SourceResolver {
this.inlinings = new Array<InliningPosition>();
// Maps source position keys to inlinings.
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.
this.phases = new Array<GenericPhase>();
// Maps phase names to phaseIds.
this.phaseNames = new Map<string, number>();
this.instructionsPhase = new InstructionsPhase();
// Maps line numbers to source positions
this.linePositionMap = new Map<string, Array<GenericPosition>>();
// Maps node ids to node origin
this.finalNodeOrigins = new Array<NodeOrigin>();
}
public getMainFunction(jsonObj): Source {
@ -60,12 +59,10 @@ export class SourceResolver {
// Backwards compatibility.
if (typeof fncJson === "string") {
return new Source(null, null, jsonObj.source, -1, true,
new Array<SourcePosition>(), jsonObj.sourcePosition,
jsonObj.sourcePosition + jsonObj.source.length);
jsonObj.sourcePosition, jsonObj.sourcePosition + jsonObj.source.length);
}
return new Source(fncJson.sourceName, fncJson.functionName, fncJson.sourceText,
fncJson.sourceId, false, new Array<SourcePosition>(), fncJson.startPosition,
fncJson.endPosition);
fncJson.sourceId, false, fncJson.startPosition, fncJson.endPosition);
}
public setInlinings(inliningsJson): void {
@ -88,8 +85,7 @@ export class SourceResolver {
if (sourcesJson) {
for (const [sourceId, source] of Object.entries<Source>(sourcesJson)) {
const src = new Source(source.sourceName, source.functionName, source.sourceText,
source.sourceId, source.backwardsCompatibility, new Array<SourcePosition>(),
source.startPosition, source.endPosition);
source.sourceId, source.backwardsCompatibility, source.startPosition, source.endPosition);
this.sources[sourceId] = src;
}
}
@ -115,41 +111,20 @@ export class SourceResolver {
}
}
public setNodePositionMap(mapJson): void {
if (!mapJson) return;
if (typeof mapJson[0] !== "object") {
const alternativeMap = new Map<string, SourcePosition>();
for (const [nodeId, scriptOffset] of Object.entries<number>(mapJson)) {
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 setFinalNodeOrigins(nodeOriginsJson): void {
if (!nodeOriginsJson) return;
for (const [nodeId, nodeOrigin] of Object.entries<NodeOrigin>(nodeOriginsJson)) {
this.finalNodeOrigins[nodeId] = new NodeOrigin(nodeOrigin.nodeId, null, nodeOrigin.phase,
nodeOrigin.reducer);
}
}
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 lastGraphPhase: GraphPhase | TurboshaftGraphPhase = null;
for (const [, genericPhase] of Object.entries<GenericPhase>(phasesJson)) {
switch (genericPhase.type) {
case PhaseType.Disassembly:
@ -163,44 +138,58 @@ export class SourceResolver {
const schedulePhase = new SchedulePhase(castedSchedule.name, castedSchedule.data);
this.phaseNames.set(schedulePhase.name, this.phases.length);
this.phases.push(schedulePhase);
selectedDynamicPhases.push(schedulePhase);
if (lastGraphPhase instanceof GraphPhase) {
schedulePhase.positions = lastGraphPhase.positions;
} else {
const oldIdToNewIdMap = this.getOldIdToNewIdMap(this.phases.length - 1);
schedulePhase.positions.merge(lastGraphPhase.data.nodes, oldIdToNewIdMap);
}
schedulePhase.instructionsPhase = instructionsPhase;
break;
case PhaseType.Sequence:
const castedSequence = camelize(genericPhase) as SequencePhase;
const sequencePhase = new SequencePhase(castedSequence.name, castedSequence.blocks,
castedSequence.registerAllocation);
const prevPhase = this.getDynamicPhase(this.phases.length - 1);
sequencePhase.positions = prevPhase.positions;
sequencePhase.instructionsPhase = prevPhase.instructionsPhase;
this.phaseNames.set(sequencePhase.name, this.phases.length);
this.phases.push(sequencePhase);
break;
case PhaseType.Instructions:
const castedInstructions = genericPhase as InstructionsPhase;
if (this.instructionsPhase.name === "") {
this.instructionsPhase.name = castedInstructions.name;
if (instructionsPhase.name === "") {
instructionsPhase.name = castedInstructions.name;
} else {
this.instructionsPhase.name += `, ${castedInstructions.name}`;
instructionsPhase.name += `, ${castedInstructions.name}`;
}
this.instructionsPhase.parseNodeIdToInstructionRangeFromJSON(castedInstructions
instructionsPhase.parseNodeIdToInstructionRangeFromJSON(castedInstructions
?.nodeIdToInstructionRange);
this.instructionsPhase.parseBlockIdToInstructionRangeFromJSON(castedInstructions
instructionsPhase.parseBlockIdToInstructionRangeFromJSON(castedInstructions
?.blockIdToInstructionRange);
this.instructionsPhase.parseInstructionOffsetToPCOffsetFromJSON(castedInstructions
instructionsPhase.parseInstructionOffsetToPCOffsetFromJSON(castedInstructions
?.instructionOffsetToPCOffset);
this.instructionsPhase.parseCodeOffsetsInfoFromJSON(castedInstructions
?.codeOffsetsInfo);
instructionsPhase.parseCodeOffsetsInfoFromJSON(castedInstructions?.codeOffsetsInfo);
break;
case PhaseType.Graph:
const castedGraph = genericPhase as GraphPhase;
const graphPhase = new GraphPhase(castedGraph.name, 0, castedGraph.data, nodeLabelMap);
this.recordOrigins(graphPhase);
const graphPhase = new GraphPhase(castedGraph.name, castedGraph.data,
nodeMap as Array<GraphNode>, this.sources, this.inlinings);
this.phaseNames.set(graphPhase.name, this.phases.length);
this.phases.push(graphPhase);
selectedDynamicPhases.push(graphPhase);
lastGraphPhase = graphPhase;
break;
case PhaseType.TurboshaftGraph:
const castedTurboshaftGraph = genericPhase as TurboshaftGraphPhase;
const turboshaftGraphPhase = new TurboshaftGraphPhase(castedTurboshaftGraph.name,
castedTurboshaftGraph.data);
castedTurboshaftGraph.data, nodeMap, this.sources, this.inlinings);
this.phaseNames.set(turboshaftGraphPhase.name, this.phases.length);
this.phases.push(turboshaftGraphPhase);
selectedDynamicPhases.push(turboshaftGraphPhase);
lastTurboshaftGraphPhase = turboshaftGraphPhase;
lastGraphPhase = turboshaftGraphPhase;
break;
case PhaseType.TurboshaftCustomData:
const castedCustomData = camelize(genericPhase) as TurboshaftCustomDataPhase;
@ -212,12 +201,43 @@ export class SourceResolver {
throw "Unsupported phase type";
}
}
this.sortSourcePositions();
this.instructionsPhase = instructionsPhase;
if (!lastTurboshaftGraphPhase) {
for (const phase of selectedDynamicPhases) {
if (!phase.isDynamic()) continue;
phase.instructionsPhase = instructionsPhase;
}
return;
}
if (instructionsPhase.name == "") return;
// Adapting 'nodeIdToInstructionRange' array to fix Turboshaft's nodes recreation
this.adaptInstructionsPhases(selectedDynamicPhases);
}
public sourcePositionsToNodeIds(sourcePositions: Array<GenericPosition>): Set<string> {
const nodeIds = new Set<string>();
for (const sp of sourcePositions) {
const nodeIdsForPosition = this.positionToNodes.get(sp.toString());
for (const position of sourcePositions) {
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;
for (const nodeId of nodeIdsForPosition) {
nodeIds.add(nodeId);
@ -229,9 +249,17 @@ export class SourceResolver {
public nodeIdsToSourcePositions(nodeIds: Iterable<string>): Array<GenericPosition> {
const sourcePositions = new Map<string, GenericPosition>();
for (const nodeId of nodeIds) {
const position = this.nodePositionMap[nodeId];
if (!position) continue;
sourcePositions.set(position.toString(), position);
const sourcePosition = this.positions.nodeIdToSourcePositionMap[nodeId];
if (sourcePosition) {
sourcePositions.set(sourcePosition.toString(), sourcePosition);
}
// Wasm support
if (this.bytecodeSources.size == 0) {
const bytecodePosition = this.positions.nodeIdToBytecodePositionMap[nodeId];
if (bytecodePosition) {
sourcePositions.set(bytecodePosition.toString(), bytecodePosition);
}
}
}
const sourcePositionArray = new Array<GenericPosition>();
for (const sourcePosition of sourcePositions.values()) {
@ -240,6 +268,16 @@ export class SourceResolver {
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 {
for (const position of this.getInlineStack(location)) {
const inlining = this.inlinings[position.inliningId];
@ -287,6 +325,16 @@ export class SourceResolver {
return this.phases[phaseId];
}
public getGraphPhase(phaseId: number): GraphPhase | TurboshaftGraphPhase {
const phase = this.phases[phaseId];
return phase.isGraph() ? phase as GraphPhase | TurboshaftGraphPhase : null;
}
public getDynamicPhase(phaseId: number): DynamicPhase {
const phase = this.phases[phaseId];
return phase.isDynamic() ? phase as DynamicPhase : null;
}
public getPhaseNameById(phaseId: number): string {
return this.getPhase(phaseId).name;
}
@ -320,7 +368,7 @@ export class SourceResolver {
public setSourceLineToBytecodePosition(sourceLineToBytecodePositionJson): void {
if (!sourceLineToBytecodePositionJson) return;
sourceLineToBytecodePositionJson.forEach((position, idx) => {
this.addAnyPositionToLine(idx, new BytecodePosition(position));
this.addAnyPositionToLine(idx, new BytecodePosition(position, -1));
});
}
@ -340,22 +388,78 @@ export class SourceResolver {
return inliningStack;
}
private recordOrigins(graphPhase: GraphPhase): void {
if (graphPhase.type !== PhaseType.Graph) return;
for (const node of graphPhase.data.nodes) {
graphPhase.highestNodeId = Math.max(graphPhase.highestNodeId, node.id);
const origin = node.nodeLabel.origin;
if (origin instanceof BytecodeOrigin) {
const position = new BytecodePosition(origin.bytecodePosition);
this.nodePositionMap[node.id] = position;
const key = position.toString();
if (!this.positionToNodes.has(key)) {
this.positionToNodes.set(key, new Array<string>());
private sortSourcePositions(): void {
for (const source of Object.values<Source>(this.sources)) {
source.sourcePositions = sortUnique(source.sourcePositions,
(a, b) => a.lessOrEquals(b),
(a, b) => a.equals(b));
}
}
private adaptInstructionsPhases(dynamicPhases: Array<DynamicPhase>): void {
const seaOfNodesInstructions = new InstructionsPhase("sea of nodes");
for (let phaseId = dynamicPhases.length - 2; phaseId >= 0; phaseId--) {
const phase = dynamicPhases[phaseId];
const prevPhase = dynamicPhases[phaseId + 1];
if (phase.type == PhaseType.TurboshaftGraph && prevPhase.type == PhaseType.Schedule) {
phase.instructionsPhase.merge(prevPhase.instructionsPhase);
const oldIdToNewIdMap = this.getOldIdToNewIdMap(phaseId + 1);
const maxNodeId = (phase as TurboshaftGraphPhase).highestNodeId;
for (let nodeId = 0; nodeId <= maxNodeId; nodeId++) {
const prevNodeId = oldIdToNewIdMap.has(nodeId) ? oldIdToNewIdMap.get(nodeId) : nodeId;
phase.instructionsPhase.nodeIdToInstructionRange[nodeId] =
prevPhase.instructionsPhase.getInstruction(prevNodeId);
}
const nodes = this.positionToNodes.get(key);
const identifier = node.identifier();
if (!nodes.includes(identifier)) nodes.push(identifier);
} else if (phase.type == PhaseType.TurboshaftGraph &&
prevPhase.type == PhaseType.TurboshaftGraph) {
phase.instructionsPhase.merge(prevPhase.instructionsPhase);
const maxNodeId = (phase as TurboshaftGraphPhase).highestNodeId;
const originIdToNodesMap = (prevPhase as TurboshaftGraphPhase).originIdToNodesMap;
for (let nodeId = 0; nodeId <= maxNodeId; nodeId++) {
const nodes = originIdToNodesMap.get(String(nodeId));
const prevNodeId = nodes?.length > 0 ? nodes[0]?.id : nodeId;
phase.instructionsPhase.nodeIdToInstructionRange[nodeId] =
prevPhase.instructionsPhase.getInstruction(prevNodeId);
}
} else if (phase.type == PhaseType.Schedule && prevPhase.type == PhaseType.TurboshaftGraph) {
seaOfNodesInstructions.merge(prevPhase.instructionsPhase);
phase.instructionsPhase = seaOfNodesInstructions;
const originIdToNodesMap = (prevPhase as TurboshaftGraphPhase).originIdToNodesMap;
for (const [originId, nodes] of originIdToNodesMap.entries()) {
if (!originId || nodes.length == 0) continue;
phase.instructionsPhase.nodeIdToInstructionRange[originId] =
prevPhase.instructionsPhase.getInstruction(nodes[0].id);
}
} else if (phase.type == PhaseType.Graph && prevPhase.type == PhaseType.Graph) {
phase.instructionsPhase = seaOfNodesInstructions;
const prevGraphPhase = prevPhase as GraphPhase;
for (const [originId, nodes] of prevGraphPhase.originIdToNodesMap.entries()) {
if (!originId || nodes.length == 0) continue;
for (const node of nodes) {
if (!phase.instructionsPhase.nodeIdToInstructionRange[originId]) {
if (!prevPhase.instructionsPhase.nodeIdToInstructionRange[node.id]) continue;
phase.instructionsPhase.nodeIdToInstructionRange[originId] =
prevPhase.instructionsPhase.getInstruction(node.id);
} else {
break;
}
}
}
} else {
phase.instructionsPhase = seaOfNodesInstructions;
}
}
}
private getOldIdToNewIdMap(phaseId: number): Map<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;
constructor(sourceName: string, functionName: string, sourceText: string, sourceId: number,
backwardsCompatibility: boolean, sourcePositions?: Array<SourcePosition>,
startPosition?: number, endPosition?: number) {
backwardsCompatibility: boolean, startPosition?: number, endPosition?: number) {
this.sourceName = sourceName;
this.functionName = functionName;
this.sourceText = sourceText;
this.sourceId = sourceId;
this.backwardsCompatibility = backwardsCompatibility;
this.sourcePositions = sourcePositions ?? new Array<SourcePosition>();
this.startPosition = startPosition;
this.endPosition = endPosition;
this.sourcePositions = new Array<SourcePosition>();
}
public toString(): string {

View File

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

View File

@ -7,6 +7,10 @@ import { CodeMode, View } from "./view";
import { SelectionBroker } from "../selection/selection-broker";
import { BytecodeSource } from "../source";
import { SourceResolver } from "../source-resolver";
import { SelectionMap } from "../selection/selection-map";
import { ViewElements } from "../common/view-elements";
import { BytecodeOffsetSelectionHandler, ClearableHandler } from "../selection/selection-handler";
import { BytecodePosition } from "../position";
export class BytecodeSourceView extends View {
broker: SelectionBroker;
@ -14,6 +18,8 @@ export class BytecodeSourceView extends View {
sourceResolver: SourceResolver;
codeMode: CodeMode;
bytecodeOffsetToHtmlElement: Map<number, HTMLElement>;
bytecodeOffsetSelection: SelectionMap;
bytecodeOffsetSelectionHandler: BytecodeOffsetSelectionHandler & ClearableHandler;
constructor(parent: HTMLElement, broker: SelectionBroker, sourceFunction: BytecodeSource,
sourceResolver: SourceResolver, codeMode: CodeMode) {
@ -23,6 +29,9 @@ export class BytecodeSourceView extends View {
this.sourceResolver = sourceResolver;
this.codeMode = codeMode;
this.bytecodeOffsetToHtmlElement = new Map<number, HTMLElement>();
this.bytecodeOffsetSelection = new SelectionMap((offset: number) => String(offset));
this.bytecodeOffsetSelectionHandler = this.initializeBytecodeOffsetSelectionHandler();
this.broker.addBytecodeOffsetHandler(this.bytecodeOffsetSelectionHandler);
this.initializeCode();
}
@ -86,6 +95,44 @@ export class BytecodeSourceView extends View {
codePre.appendChild(constantList);
}
private initializeBytecodeOffsetSelectionHandler(): BytecodeOffsetSelectionHandler
& ClearableHandler {
const view = this;
const broker = this.broker;
return {
select: function (offsets: Array<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 {
return `source-pre-${this.source.sourceId}-header`;
}
@ -98,13 +145,36 @@ export class BytecodeSourceView extends View {
return this.codeMode == CodeMode.MainSource ? "main-source" : "inlined-source";
}
private updateSelection(scrollIntoView: boolean = false): void {
const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement);
for (const [offset, element] of this.bytecodeOffsetToHtmlElement.entries()) {
const key = this.bytecodeOffsetSelection.stringKey(offset);
const isSelected = this.bytecodeOffsetSelection.isKeySelected(key);
mkVisible.consider(element, isSelected);
element.classList.toggle("selected", isSelected);
}
mkVisible.apply(scrollIntoView);
}
private onSelectBytecodeOffset(offset: number, doClear: boolean) {
if (doClear) {
this.bytecodeOffsetSelectionHandler.clear();
}
this.bytecodeOffsetSelectionHandler.select([offset], undefined);
}
private insertLineContent(lineElement: HTMLElement, content: string): void {
const lineContentElement = createElement("span", "", content);
lineElement.appendChild(lineContentElement);
}
private insertLineNumber(lineElement: HTMLElement, lineNumber: number): void {
const view = this;
const lineNumberElement = createElement("div", "line-number", String(lineNumber));
lineNumberElement.onclick = function (e: MouseEvent) {
e.stopPropagation();
view.onSelectBytecodeOffset(lineNumber, !e.shiftKey);
};
lineElement.insertBefore(lineNumberElement, lineElement.firstChild);
}
}

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

View File

@ -11,11 +11,15 @@ import { SourceResolver } from "../source-resolver";
import { GraphNode } from "../phases/graph-phase/graph-node";
import { HistoryHandler } from "../selection/selection-handler";
import { GraphPhase } from "../phases/graph-phase/graph-phase";
import { NodeOrigin } from "../origin";
import { SelectionStorage } from "../selection/selection-storage";
import { TurboshaftGraphNode } from "../phases/turboshaft-graph-phase/turboshaft-graph-node";
import { TurboshaftGraphPhase } from "../phases/turboshaft-graph-phase/turboshaft-graph-phase";
type GNode = GraphNode | TurboshaftGraphNode;
type GPhase = GraphPhase | TurboshaftGraphPhase;
export class HistoryView extends View {
node: GraphNode;
node: GNode;
broker: SelectionBroker;
sourceResolver: SourceResolver;
historyHandler: HistoryHandler;
@ -78,7 +82,7 @@ export class HistoryView extends View {
private initializeNodeSelectionHandler(): HistoryHandler {
const view = this;
return {
showTurbofanNodeHistory: function (node: GraphNode, phaseName: string) {
showNodeHistory: function (node: GNode, phaseName: string) {
view.clear();
view.node = node;
const phaseId = view.sourceResolver.getPhaseIdByName(phaseName);
@ -264,7 +268,7 @@ export class HistoryView extends View {
}
private setLabel(): void {
this.label = `${this.node.id} ${this.node.nodeLabel.opcode}`;
this.label = this.node.getHistoryLabel();
const coefficient = this.getCoefficient("history-tspan-font-size");
this.labelBox = measureText(this.label, coefficient);
}
@ -275,14 +279,14 @@ export class HistoryView extends View {
return Math.min(tspanSize, varSize) / Math.max(tspanSize, varSize);
}
private getPhaseHistory(historyChain: Map<number, GraphNode>): void {
private getPhaseHistory(historyChain: Map<number, GNode>): void {
const uniqueAncestors = new Set<string>();
const coefficient = this.getCoefficient("history-item-tspan-font-size");
let prevNode = null;
let first = true;
for (let i = 0; i < this.sourceResolver.phases.length; i++) {
const phase = this.sourceResolver.getPhase(i);
if (!(phase instanceof GraphPhase)) continue;
const phase = this.sourceResolver.getGraphPhase(i);
if (!phase) continue;
const phaseNameMeasure = measureText(phase.name, coefficient);
this.maxPhaseNameWidth = Math.max(this.maxPhaseNameWidth, phaseNameMeasure.width);
@ -298,10 +302,11 @@ export class HistoryView extends View {
if (prevNode && !prevNode.equals(node) &&
phase.originIdToNodesMap.has(prevNode.identifier())) {
const prevNodeCurrentState = phase.nodeIdToNodeMap[prevNode.identifier()];
const inplaceUpdate = prevNodeCurrentState?.nodeLabel?.inplaceUpdatePhase;
if (!prevNodeCurrentState) {
this.addToHistory(i, prevNode, HistoryChange.Removed);
} else if (!prevNodeCurrentState?.equals(node) && inplaceUpdate == phase.name) {
} else if (!this.nodeEquals(prevNodeCurrentState, node) &&
prevNodeCurrentState instanceof GraphNode &&
prevNodeCurrentState.getInplaceUpdatePhase() == phase.name) {
this.addToHistory(i, prevNodeCurrentState, HistoryChange.InplaceUpdated);
} else if (node.identifier() != prevNode.identifier()) {
this.addToHistory(i, prevNodeCurrentState, HistoryChange.Survived);
@ -314,7 +319,7 @@ export class HistoryView extends View {
continue;
}
if (node.nodeLabel.inplaceUpdatePhase && node.nodeLabel.inplaceUpdatePhase == phase.name) {
if (node instanceof GraphNode && node.getInplaceUpdatePhase() == phase.name) {
this.addToHistory(i, node, HistoryChange.InplaceUpdated);
}
@ -328,7 +333,7 @@ export class HistoryView extends View {
}
}
private addHistoryAncestors(key: string, phase: GraphPhase, uniqueAncestors: Set<string>):
private addHistoryAncestors(key: string, phase: GPhase, uniqueAncestors: Set<string>):
boolean {
let changed = false;
const phaseId = this.sourceResolver.getPhaseIdByName(phase.name);
@ -343,22 +348,22 @@ export class HistoryView extends View {
return changed;
}
private getHistoryChain(phaseId: number, node: GraphNode): Map<number, GraphNode> {
private getHistoryChain(phaseId: number, node: GNode): Map<number, GNode> {
const leftChain = this.getLeftHistoryChain(phaseId, node);
const rightChain = this.getRightHistoryChain(phaseId, node);
return new Map([...leftChain, ...rightChain]);
}
private getLeftHistoryChain(phaseId: number, node: GraphNode): Map<number, GraphNode> {
const leftChain = new Map<number, GraphNode>();
private getLeftHistoryChain(phaseId: number, node: GNode): Map<number, GNode> {
const leftChain = new Map<number, GNode>();
for (let i = phaseId; i >= 0; i--) {
const phase = this.sourceResolver.getPhase(i);
if (!(phase instanceof GraphPhase)) continue;
const phase = this.sourceResolver.getGraphPhase(i);
if (!phase) continue;
let currentNode = phase.nodeIdToNodeMap[node.identifier()];
if (!currentNode) {
const nodeOrigin = node.nodeLabel.origin;
if (nodeOrigin instanceof NodeOrigin) {
const nodeOrigin = node.getNodeOrigin();
if (nodeOrigin) {
currentNode = phase.nodeIdToNodeMap[nodeOrigin.identifier()];
}
if (!currentNode) return leftChain;
@ -370,12 +375,12 @@ export class HistoryView extends View {
return leftChain;
}
private getRightHistoryChain(phaseId: number, node: GraphNode): Map<number, GraphNode> {
const rightChain = new Map<number, GraphNode>();
private getRightHistoryChain(phaseId: number, node: GNode): Map<number, GNode> {
const rightChain = new Map<number, GNode>();
for (let i = phaseId + 1; i < this.sourceResolver.phases.length; i++) {
const phase = this.sourceResolver.getPhase(i);
if (!(phase instanceof GraphPhase)) continue;
const phase = this.sourceResolver.getGraphPhase(i);
if (!phase) continue;
const currentNode = phase.nodeIdToNodeMap[node.identifier()];
if (!currentNode) return rightChain;
rightChain.set(i, currentNode);
@ -385,7 +390,7 @@ export class HistoryView extends View {
return rightChain;
}
private addToHistory(phaseId: number, node: GraphNode, change: HistoryChange): void {
private addToHistory(phaseId: number, node: GNode, change: HistoryChange): void {
if (!this.phaseIdToHistory.has(phaseId)) {
this.phaseIdToHistory.set(phaseId, new PhaseHistory(phaseId));
}
@ -397,6 +402,16 @@ export class HistoryView extends View {
}
}
private nodeEquals(first: GNode, second: GNode): boolean {
if (!first || !second) return false;
if ((first instanceof GraphNode && second instanceof GraphNode)) {
return first.equals(second);
} else if (first instanceof TurboshaftGraphNode && second instanceof TurboshaftGraphNode) {
return first.equals(second);
}
return first.getHistoryLabel() == second.getHistoryLabel();
}
private clear(): void {
this.phaseIdToHistory.clear();
this.maxNodeWidth = 0;
@ -458,7 +473,7 @@ export class PhaseHistory {
this.nodeIdToRecord = new Map<string, HistoryRecord>();
}
public addChange(node: GraphNode, change: HistoryChange): void {
public addChange(node: GNode, change: HistoryChange): void {
const key = node.identifier();
if (!this.nodeIdToRecord.has(key)) {
this.nodeIdToRecord.set(key, new HistoryRecord(node));
@ -475,10 +490,10 @@ export class PhaseHistory {
}
export class HistoryRecord {
node: GraphNode;
node: GNode;
changes: Set<HistoryChange>;
constructor(node: GraphNode) {
constructor(node: GNode) {
this.node = node;
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):
HTMLElement {
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 { SelectionStorage } from "../selection/selection-storage";
import { DataTarget } from "../phases/turboshaft-custom-data-phase";
import { SourcePosition } from "../position";
import {
TurboshaftCustomData,
TurboshaftGraphPhase
@ -99,8 +100,12 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
this.broker.addNodeHandler(this.nodeSelectionHandler);
this.broker.addBlockHandler(this.blockSelectionHandler);
if (adaptedSelection.isAdapted()) {
this.attachSelection(adaptedSelection);
const countOfSelectedItems = adaptedSelection.isAdapted()
? this.attachSelection(adaptedSelection)
: 0;
if (countOfSelectedItems > 0) {
this.updateGraphVisibility();
this.viewSelection();
} else {
if (this.state.cacheLayout && data.transform) {
@ -142,6 +147,9 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
eventHandled = false;
}
break;
case 72: // 'h'
this.showHoveredNodeHistory();
break;
case 73: // 'i'
this.selectNodesOfSelectedBlocks();
break;
@ -255,7 +263,16 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const view = this;
return {
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.broker.broadcastSourcePositionSelect(this, sourcePositions, selected, nodes);
view.updateGraphVisibility();
},
clear: function () {
@ -367,6 +384,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const selectedCustomData = select.options[this.selectedIndex].text;
storageSetItem(storageKey, selectedCustomData);
view.updateGraphVisibility();
view.updateInlineNodesCustomData();
};
this.toolbox.appendChild(select);
@ -603,13 +621,11 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
private updateInlineNodes(): void {
const view = this;
const state = this.state;
const storageKey = this.customDataStorageKey();
const selectedCustomData = storageGetItem(storageKey, null, false);
const showCustomData = this.nodesCustomDataShowed();
let totalHeight = 0;
let blockId = 0;
view.visibleNodes.each(function (node: TurboshaftGraphNode) {
const nodeSvg = d3.select(this);
const showCustomData = view.nodesCustomDataShowed();
if (blockId != node.block.id) {
blockId = node.block.id;
totalHeight = 0;
@ -623,18 +639,25 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
.attr("dy", nodeY)
.attr("visibility", !node.block.collapsed ? "visible" : "hidden");
const svgNodeCustomData = nodeSvg
nodeSvg
.select(".inline-node-custom-data")
.attr("visibility", !node.block.collapsed && showCustomData ? "visible" : "hidden");
});
}
if (!node.block.collapsed && showCustomData) {
const customData = view.graph.getCustomData(selectedCustomData, node.id, DataTarget.Nodes);
svgNodeCustomData
.select("tspan")
.text(view.getReadableString(customData, node.block.width))
.append("title")
.text(customData);
}
private updateInlineNodesCustomData(): void {
const view = this;
const storageKey = this.customDataStorageKey();
const selectedCustomData = storageGetItem(storageKey, null, false);
if (!this.nodesCustomDataShowed()) return;
view.visibleNodes.each(function (node: TurboshaftGraphNode) {
const customData = view.graph.getCustomData(selectedCustomData, node.id, DataTarget.Nodes);
d3.select(this)
.select(".inline-node-custom-data")
.select("tspan")
.text(view.getReadableString(customData, node.block.width))
.append("title")
.text(customData);
});
}
@ -705,8 +728,8 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
}
}
private attachSelection(selection: SelectionStorage): void {
if (!(selection instanceof SelectionStorage)) return;
private attachSelection(selection: SelectionStorage): number {
if (!(selection instanceof SelectionStorage)) return 0;
this.nodeSelectionHandler.clear();
this.blockSelectionHandler.clear();
const selectedNodes = [
@ -719,6 +742,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
selection.adaptedBocks.has(this.state.blocksSelection.stringKey(block)))
];
this.blockSelectionHandler.select(selectedBlocks, true);
return selectedNodes.length + selectedBlocks.length;
}
private nodesCustomDataShowed(): boolean {
@ -813,6 +837,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const extent = view.graph.redetermineGraphBoundingBox(view.state.showCustomData);
view.panZoom.translateExtent(extent);
view.adaptiveUpdateGraphVisibility();
view.updateInlineNodesCustomData();
}
private toggleLayoutCachingAction(view: TurboshaftGraphView): void {
@ -823,7 +848,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
// Hotkeys handlers
private selectAllNodes(): void {
this.state.selection.select(this.graph.nodeMap, true);
this.nodeSelectionHandler.select(this.graph.nodeMap, true);
this.updateGraphVisibility();
}
@ -865,7 +890,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
block.collapsed = false;
selectedNodes = selectedNodes.concat(block.nodes);
}
this.state.selection.select(selectedNodes, true);
this.nodeSelectionHandler.select(selectedNodes, true);
this.updateGraphVisibility();
}
}