1// Copyright 2014 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import { Edge } from "../src/edge"; 6import { NodeLabel } from "./node-label"; 7import { MAX_RANK_SENTINEL } from "./constants"; 8import { alignUp, measureText } from "./util"; 9 10export const DEFAULT_NODE_BUBBLE_RADIUS = 12; 11export const NODE_INPUT_WIDTH = 50; 12export const MINIMUM_NODE_OUTPUT_APPROACH = 15; 13export const MINIMUM_EDGE_SEPARATION = 20; 14const MINIMUM_NODE_INPUT_APPROACH = 15 + 2 * DEFAULT_NODE_BUBBLE_RADIUS; 15 16export class GNode { 17 id: number; 18 nodeLabel: NodeLabel; 19 displayLabel: string; 20 inputs: Array<Edge>; 21 outputs: Array<Edge>; 22 visible: boolean; 23 x: number; 24 y: number; 25 rank: number; 26 outputApproach: number; 27 cfg: boolean; 28 labelbbox: { width: number, height: number }; 29 width: number; 30 normalheight: number; 31 visitOrderWithinRank: number; 32 33 constructor(nodeLabel: NodeLabel) { 34 this.id = nodeLabel.id; 35 this.nodeLabel = nodeLabel; 36 this.displayLabel = nodeLabel.getDisplayLabel(); 37 this.inputs = []; 38 this.outputs = []; 39 this.visible = false; 40 this.x = 0; 41 this.y = 0; 42 this.rank = MAX_RANK_SENTINEL; 43 this.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH; 44 // Every control node is a CFG node. 45 this.cfg = nodeLabel.control; 46 this.labelbbox = measureText(this.displayLabel); 47 const typebbox = measureText(this.getDisplayType()); 48 const innerwidth = Math.max(this.labelbbox.width, typebbox.width); 49 this.width = alignUp(innerwidth + NODE_INPUT_WIDTH * 2, 50 NODE_INPUT_WIDTH); 51 const innerheight = Math.max(this.labelbbox.height, typebbox.height); 52 this.normalheight = innerheight + 20; 53 this.visitOrderWithinRank = 0; 54 } 55 56 isControl() { 57 return this.nodeLabel.control; 58 } 59 isInput() { 60 return this.nodeLabel.opcode == 'Parameter' || this.nodeLabel.opcode.endsWith('Constant'); 61 } 62 isLive() { 63 return this.nodeLabel.live !== false; 64 } 65 isJavaScript() { 66 return this.nodeLabel.opcode.startsWith('JS'); 67 } 68 isSimplified() { 69 if (this.isJavaScript()) return false; 70 const opcode = this.nodeLabel.opcode; 71 return opcode.endsWith('Phi') || 72 opcode.startsWith('Boolean') || 73 opcode.startsWith('Number') || 74 opcode.startsWith('String') || 75 opcode.startsWith('Change') || 76 opcode.startsWith('Object') || 77 opcode.startsWith('Reference') || 78 opcode.startsWith('Any') || 79 opcode.endsWith('ToNumber') || 80 (opcode == 'AnyToBoolean') || 81 (opcode.startsWith('Load') && opcode.length > 4) || 82 (opcode.startsWith('Store') && opcode.length > 5); 83 } 84 isMachine() { 85 return !(this.isControl() || this.isInput() || 86 this.isJavaScript() || this.isSimplified()); 87 } 88 getTotalNodeWidth() { 89 const inputWidth = this.inputs.length * NODE_INPUT_WIDTH; 90 return Math.max(inputWidth, this.width); 91 } 92 getTitle() { 93 return this.nodeLabel.getTitle(); 94 } 95 getDisplayLabel() { 96 return this.nodeLabel.getDisplayLabel(); 97 } 98 getType() { 99 return this.nodeLabel.type; 100 } 101 getDisplayType() { 102 let typeString = this.nodeLabel.type; 103 if (typeString == undefined) return ""; 104 if (typeString.length > 24) { 105 typeString = typeString.substr(0, 25) + "..."; 106 } 107 return typeString; 108 } 109 deepestInputRank() { 110 let deepestRank = 0; 111 this.inputs.forEach(function (e) { 112 if (e.isVisible() && !e.isBackEdge()) { 113 if (e.source.rank > deepestRank) { 114 deepestRank = e.source.rank; 115 } 116 } 117 }); 118 return deepestRank; 119 } 120 areAnyOutputsVisible() { 121 let visibleCount = 0; 122 this.outputs.forEach(function (e) { if (e.isVisible())++visibleCount; }); 123 if (this.outputs.length == visibleCount) return 2; 124 if (visibleCount != 0) return 1; 125 return 0; 126 } 127 setOutputVisibility(v) { 128 let result = false; 129 this.outputs.forEach(function (e) { 130 e.visible = v; 131 if (v) { 132 if (!e.target.visible) { 133 e.target.visible = true; 134 result = true; 135 } 136 } 137 }); 138 return result; 139 } 140 setInputVisibility(i, v) { 141 const edge = this.inputs[i]; 142 edge.visible = v; 143 if (v) { 144 if (!edge.source.visible) { 145 edge.source.visible = true; 146 return true; 147 } 148 } 149 return false; 150 } 151 getInputApproach(index) { 152 return this.y - MINIMUM_NODE_INPUT_APPROACH - 153 (index % 4) * MINIMUM_EDGE_SEPARATION - DEFAULT_NODE_BUBBLE_RADIUS; 154 } 155 getNodeHeight(showTypes: boolean): number { 156 if (showTypes) { 157 return this.normalheight + this.labelbbox.height; 158 } else { 159 return this.normalheight; 160 } 161 } 162 getOutputApproach(showTypes: boolean) { 163 return this.y + this.outputApproach + this.getNodeHeight(showTypes) + 164 + DEFAULT_NODE_BUBBLE_RADIUS; 165 } 166 getInputX(index) { 167 const result = this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2) + 168 (index - this.inputs.length + 1) * NODE_INPUT_WIDTH; 169 return result; 170 } 171 getOutputX() { 172 return this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2); 173 } 174 hasBackEdges() { 175 return (this.nodeLabel.opcode == "Loop") || 176 ((this.nodeLabel.opcode == "Phi" || this.nodeLabel.opcode == "EffectPhi" || this.nodeLabel.opcode == "InductionVariablePhi") && 177 this.inputs[this.inputs.length - 1].source.nodeLabel.opcode == "Loop"); 178 } 179} 180 181export const nodeToStr = (n: GNode) => "N" + n.id; 182