| // 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; |