// Copyright 2014 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import { MINIMUM_EDGE_SEPARATION, Edge } from "../src/edge"; import { NodeLabel } from "./node-label"; import { MAX_RANK_SENTINEL } from "./constants"; import { alignUp, measureText } from "./util"; export const DEFAULT_NODE_BUBBLE_RADIUS = 12; export const NODE_INPUT_WIDTH = 50; export const MINIMUM_NODE_OUTPUT_APPROACH = 15; const MINIMUM_NODE_INPUT_APPROACH = 15 + 2 * DEFAULT_NODE_BUBBLE_RADIUS; export class GNode { id: number; nodeLabel: NodeLabel; displayLabel: string; inputs: Array<Edge>; outputs: Array<Edge>; visible: boolean; x: number; y: number; rank: number; outputApproach: number; cfg: boolean; labelbbox: { width: number, height: number }; width: number; normalheight: number; visitOrderWithinRank: number; constructor(nodeLabel: NodeLabel) { this.id = nodeLabel.id; this.nodeLabel = nodeLabel; this.displayLabel = nodeLabel.getDisplayLabel(); this.inputs = []; this.outputs = []; this.visible = false; this.x = 0; this.y = 0; this.rank = MAX_RANK_SENTINEL; this.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH; // Every control node is a CFG node. this.cfg = nodeLabel.control; this.labelbbox = measureText(this.displayLabel); const typebbox = measureText(this.getDisplayType()); const innerwidth = Math.max(this.labelbbox.width, typebbox.width); this.width = alignUp(innerwidth + NODE_INPUT_WIDTH * 2, NODE_INPUT_WIDTH); const innerheight = Math.max(this.labelbbox.height, typebbox.height); this.normalheight = innerheight + 20; this.visitOrderWithinRank = 0; } isControl() { return this.nodeLabel.control; } isInput() { return this.nodeLabel.opcode == 'Parameter' || this.nodeLabel.opcode.endsWith('Constant'); } isLive() { return this.nodeLabel.live !== false; } isJavaScript() { return this.nodeLabel.opcode.startsWith('JS'); } isSimplified() { if (this.isJavaScript()) return false; const opcode = this.nodeLabel.opcode; return opcode.endsWith('Phi') || opcode.startsWith('Boolean') || opcode.startsWith('Number') || opcode.startsWith('String') || opcode.startsWith('Change') || opcode.startsWith('Object') || opcode.startsWith('Reference') || opcode.startsWith('Any') || opcode.endsWith('ToNumber') || (opcode == 'AnyToBoolean') || (opcode.startsWith('Load') && opcode.length > 4) || (opcode.startsWith('Store') && opcode.length > 5); } isMachine() { return !(this.isControl() || this.isInput() || this.isJavaScript() || this.isSimplified()); } getTotalNodeWidth() { const inputWidth = this.inputs.length * NODE_INPUT_WIDTH; return Math.max(inputWidth, this.width); } getTitle() { return this.nodeLabel.getTitle(); } getDisplayLabel() { return this.nodeLabel.getDisplayLabel(); } getType() { return this.nodeLabel.type; } getDisplayType() { let typeString = this.nodeLabel.type; if (typeString == undefined) return ""; if (typeString.length > 24) { typeString = typeString.substr(0, 25) + "..."; } return typeString; } deepestInputRank() { let deepestRank = 0; this.inputs.forEach(function (e) { if (e.isVisible() && !e.isBackEdge()) { if (e.source.rank > deepestRank) { deepestRank = e.source.rank; } } }); return deepestRank; } areAnyOutputsVisible() { let visibleCount = 0; this.outputs.forEach(function (e) { if (e.isVisible())++visibleCount; }); if (this.outputs.length == visibleCount) return 2; if (visibleCount != 0) return 1; return 0; } setOutputVisibility(v) { let result = false; this.outputs.forEach(function (e) { e.visible = v; if (v) { if (!e.target.visible) { e.target.visible = true; result = true; } } }); return result; } setInputVisibility(i, v) { const edge = this.inputs[i]; edge.visible = v; if (v) { if (!edge.source.visible) { edge.source.visible = true; return true; } } return false; } getInputApproach(index) { return this.y - MINIMUM_NODE_INPUT_APPROACH - (index % 4) * MINIMUM_EDGE_SEPARATION - DEFAULT_NODE_BUBBLE_RADIUS; } getNodeHeight(showTypes: boolean): number { if (showTypes) { return this.normalheight + this.labelbbox.height; } else { return this.normalheight; } } getOutputApproach(showTypes: boolean) { return this.y + this.outputApproach + this.getNodeHeight(showTypes) + + DEFAULT_NODE_BUBBLE_RADIUS; } getInputX(index) { const result = this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2) + (index - this.inputs.length + 1) * NODE_INPUT_WIDTH; return result; } getOutputX() { return this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2); } hasBackEdges() { return (this.nodeLabel.opcode == "Loop") || ((this.nodeLabel.opcode == "Phi" || this.nodeLabel.opcode == "EffectPhi" || this.nodeLabel.opcode == "InductionVariablePhi") && this.inputs[this.inputs.length - 1].source.nodeLabel.opcode == "Loop"); } } export const nodeToStr = (n: GNode) => "N" + n.id;