1 /*
   2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 package org.graalvm.compiler.nodes;
  26 
  27 import java.util.ArrayDeque;
  28 import java.util.Deque;
  29 import java.util.Iterator;
  30 import java.util.Objects;
  31 
  32 import jdk.internal.vm.compiler.collections.Pair;
  33 import jdk.internal.vm.compiler.collections.UnmodifiableMapCursor;
  34 import org.graalvm.compiler.core.common.Fields;
  35 import org.graalvm.compiler.core.common.util.FrequencyEncoder;
  36 import org.graalvm.compiler.core.common.util.TypeConversion;
  37 import org.graalvm.compiler.core.common.util.TypeReader;
  38 import org.graalvm.compiler.core.common.util.TypeWriter;
  39 import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter;
  40 import org.graalvm.compiler.debug.DebugContext;
  41 import org.graalvm.compiler.graph.Edges;
  42 import org.graalvm.compiler.graph.Node;
  43 import org.graalvm.compiler.graph.NodeClass;
  44 import org.graalvm.compiler.graph.NodeList;
  45 import org.graalvm.compiler.graph.NodeMap;
  46 import org.graalvm.compiler.graph.iterators.NodeIterable;
  47 import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
  48 import org.graalvm.compiler.nodes.java.ExceptionObjectNode;
  49 
  50 import jdk.vm.ci.code.Architecture;
  51 
  52 /**
  53  * Encodes a {@link StructuredGraph} to a compact byte[] array. All nodes of the graph and edges
  54  * between the nodes are encoded. Primitive data fields of nodes are stored in the byte[] array.
  55  * Object data fields of nodes are stored in a separate Object[] array.
  56  *
  57  * One encoder instance can be used to encode multiple graphs. This requires that {@link #prepare}
  58  * is called for all graphs first, followed by one call to {@link #finishPrepare}. Then
  59  * {@link #encode} can be called for all graphs. The {@link #getObjects() objects} and
  60  * {@link #getNodeClasses() node classes} arrays do not change anymore after preparation.
  61  *
  62  * Multiple encoded graphs share the Object[] array, and elements of the Object[] array are
  63  * de-duplicated using {@link Object#equals Object equality}. This uses the assumption and good
  64  * coding practice that data objects are immutable if {@link Object#equals} is implemented.
  65  * Unfortunately, this cannot be enforced.
  66  *
  67  * The Graal {@link NodeClass} does not have a unique id that allows class lookup from an id.
  68  * Therefore, the encoded graph contains a {@link NodeClass}[] array for lookup, and type ids are
  69  * encoding-local.
  70  *
  71  * The encoded graph has the following structure: First, all nodes and their edges are serialized.
  72  * The start offset of every node is then known. The raw node data is followed by metadata, i.e.,
  73  * the maximum fixed node order id and a "table of contents" that lists the start offset for every
  74  * node.
  75  *
  76  * The beginning of this metadata is the return value of {@link #encode} and stored in
  77  * {@link EncodedGraph#getStartOffset()}. The order of nodes in the table of contents is the
  78  * {@link NodeOrder#orderIds orderId} of a node. Note that the orderId is not the regular node id
  79  * that every Graal graph node gets assigned. The orderId is computed and used just for encoding and
  80  * decoding. The orderId of fixed nodes is assigned in reverse postorder. The decoder processes
  81  * nodes using that order, which ensures that all predecessors of a node (including all
  82  * {@link EndNode predecessors} of a {@link AbstractBeginNode block}) are decoded before the node.
  83  * The order id of floating node does not matter during decoding, so floating nodes get order ids
  84  * after all fixed nodes. The order id is used to encode edges between nodes
  85  *
  86  * Structure of an encoded node:
  87  *
  88  * <pre>
  89  * struct Node {
  90  *   unsigned typeId
  91  *   unsigned[] inputOrderIds
  92  *   signed[] properties
  93  *   unsigned[] successorOrderIds
  94  * }
  95  * </pre>
  96  *
  97  * All numbers (unsigned and signed) are stored using a variable-length encoding as defined in
  98  * {@link TypeReader} and {@link TypeWriter}. Especially orderIds are small, so the variable-length
  99  * encoding is important to keep the encoding compact.
 100  *
 101  * The properties, successors, and inputs are written in the order as defined in
 102  * {@link NodeClass#getData}, {@link NodeClass#getSuccessorEdges()}, and
 103  * {@link NodeClass#getInputEdges()}. For variable-length successors and input lists, first the
 104  * length is written and then the orderIds. There is a distinction between null lists (encoded as
 105  * length -1) and empty lists (encoded as length 0). No reverse edges are written (predecessors,
 106  * usages) since that information can be easily restored during decoding.
 107  *
 108  * Some nodes have additional information written after the properties, successors, and inputs:
 109  * <li><item>{@link AbstractEndNode}: the orderId of the merge node and then all {@link PhiNode phi
 110  * mappings} from this end to the merge node are written. <item>{@link LoopExitNode}: the orderId of
 111  * all {@link ProxyNode proxy nodes} of the loop exit is written.</li>
 112  */
 113 public class GraphEncoder {
 114 
 115     /** The orderId that always represents {@code null}. */
 116     public static final int NULL_ORDER_ID = 0;
 117     /** The orderId of the {@link StructuredGraph#start() start node} of the encoded graph. */
 118     public static final int START_NODE_ORDER_ID = 1;
 119     /**
 120      * The orderId of the first actual node after the {@link StructuredGraph#start() start node}.
 121      */
 122     public static final int FIRST_NODE_ORDER_ID = 2;
 123 
 124     /**
 125      * The known offset between the orderId of a {@link AbstractBeginNode} and its
 126      * {@link AbstractBeginNode#next() successor}.
 127      */
 128     protected static final int BEGIN_NEXT_ORDER_ID_OFFSET = 1;
 129 
 130     protected final Architecture architecture;
 131 
 132     /**
 133      * Collects all non-primitive data referenced from nodes. The encoding uses an index into an
 134      * array for decoding. Because of the variable-length encoding, it is beneficial that frequently
 135      * used objects have the small indices.
 136      */
 137     protected final FrequencyEncoder<Object> objects;
 138     /**
 139      * Collects all node classes referenced in graphs. This is necessary because {@link NodeClass}
 140      * currently does not have a unique id.
 141      */
 142     protected final FrequencyEncoder<NodeClass<?>> nodeClasses;
 143     /** The writer for the encoded graphs. */
 144     protected final UnsafeArrayTypeWriter writer;
 145 
 146     /** The last snapshot of {@link #objects} that was retrieved. */
 147     protected Object[] objectsArray;
 148     /** The last snapshot of {@link #nodeClasses} that was retrieved. */
 149     protected NodeClass<?>[] nodeClassesArray;
 150 
 151     /**
 152      * Utility method that does everything necessary to encode a single graph.
 153      */
 154     public static EncodedGraph encodeSingleGraph(StructuredGraph graph, Architecture architecture) {
 155         GraphEncoder encoder = new GraphEncoder(architecture);
 156         encoder.prepare(graph);
 157         encoder.finishPrepare();
 158         int startOffset = encoder.encode(graph);
 159         return new EncodedGraph(encoder.getEncoding(), startOffset, encoder.getObjects(), encoder.getNodeClasses(), graph);
 160     }
 161 
 162     public GraphEncoder(Architecture architecture) {
 163         this.architecture = architecture;
 164         objects = FrequencyEncoder.createEqualityEncoder();
 165         nodeClasses = FrequencyEncoder.createIdentityEncoder();
 166         writer = UnsafeArrayTypeWriter.create(architecture.supportsUnalignedMemoryAccess());
 167     }
 168 
 169     /**
 170      * Must be invoked before {@link #finishPrepare()} and {@link #encode}.
 171      */
 172     public void prepare(StructuredGraph graph) {
 173         for (Node node : graph.getNodes()) {
 174             NodeClass<? extends Node> nodeClass = node.getNodeClass();
 175             nodeClasses.addObject(nodeClass);
 176             objects.addObject(node.getNodeSourcePosition());
 177             for (int i = 0; i < nodeClass.getData().getCount(); i++) {
 178                 if (!nodeClass.getData().getType(i).isPrimitive()) {
 179                     objects.addObject(nodeClass.getData().get(node, i));
 180                 }
 181             }
 182             if (node instanceof Invoke) {
 183                 objects.addObject(((Invoke) node).getContextType());
 184             }
 185         }
 186     }
 187 
 188     public void finishPrepare() {
 189         objectsArray = objects.encodeAll(new Object[objects.getLength()]);
 190         nodeClassesArray = nodeClasses.encodeAll(new NodeClass<?>[nodeClasses.getLength()]);
 191     }
 192 
 193     public Object[] getObjects() {
 194         return objectsArray;
 195     }
 196 
 197     public NodeClass<?>[] getNodeClasses() {
 198         return nodeClassesArray;
 199     }
 200 
 201     /**
 202      * Compresses a graph to a byte array. Multiple graphs can be compressed with the same
 203      * {@link GraphEncoder}.
 204      *
 205      * @param graph The graph to encode
 206      */
 207     public int encode(StructuredGraph graph) {
 208         assert objectsArray != null && nodeClassesArray != null : "finishPrepare() must be called before encode()";
 209 
 210         NodeOrder nodeOrder = new NodeOrder(graph);
 211         int nodeCount = nodeOrder.nextOrderId;
 212         assert nodeOrder.orderIds.get(graph.start()) == START_NODE_ORDER_ID;
 213         assert nodeOrder.orderIds.get(graph.start().next()) == FIRST_NODE_ORDER_ID;
 214 
 215         long[] nodeStartOffsets = new long[nodeCount];
 216         UnmodifiableMapCursor<Node, Integer> cursor = nodeOrder.orderIds.getEntries();
 217         while (cursor.advance()) {
 218             Node node = cursor.getKey();
 219             Integer orderId = cursor.getValue();
 220 
 221             assert !(node instanceof AbstractBeginNode) || nodeOrder.orderIds.get(((AbstractBeginNode) node).next()) == orderId + BEGIN_NEXT_ORDER_ID_OFFSET;
 222             assert nodeStartOffsets[orderId] == 0;
 223             nodeStartOffsets[orderId] = writer.getBytesWritten();
 224 
 225             /* Write out the type, properties, and edges. */
 226             NodeClass<?> nodeClass = node.getNodeClass();
 227             writer.putUV(nodeClasses.getIndex(nodeClass));
 228             writeEdges(node, nodeClass.getEdges(Edges.Type.Inputs), nodeOrder);
 229             writeProperties(node, nodeClass.getData());
 230             writeEdges(node, nodeClass.getEdges(Edges.Type.Successors), nodeOrder);
 231 
 232             /* Special handling for some nodes that require additional information for decoding. */
 233             if (node instanceof AbstractEndNode) {
 234                 AbstractEndNode end = (AbstractEndNode) node;
 235                 AbstractMergeNode merge = end.merge();
 236                 /*
 237                  * Write the orderId of the merge. The merge is not a successor in the Graal graph
 238                  * (only the merge has an input edge to the EndNode).
 239                  */
 240                 writeOrderId(merge, nodeOrder);
 241 
 242                 /*
 243                  * Write all phi mappings (the oderId of the phi input for this EndNode, and the
 244                  * orderId of the phi node.
 245                  */
 246                 writer.putUV(merge.phis().count());
 247                 for (PhiNode phi : merge.phis()) {
 248                     writeOrderId(phi.valueAt(end), nodeOrder);
 249                     writeOrderId(phi, nodeOrder);
 250                 }
 251 
 252             } else if (node instanceof LoopExitNode) {
 253                 LoopExitNode exit = (LoopExitNode) node;
 254                 writeOrderId(exit.stateAfter(), nodeOrder);
 255                 /* Write all proxy nodes of the LoopExitNode. */
 256                 writer.putUV(exit.proxies().count());
 257                 for (ProxyNode proxy : exit.proxies()) {
 258                     writeOrderId(proxy, nodeOrder);
 259                 }
 260 
 261             } else if (node instanceof Invoke) {
 262                 Invoke invoke = (Invoke) node;
 263                 assert invoke.stateDuring() == null : "stateDuring is not used in high-level graphs";
 264 
 265                 writeObjectId(invoke.getContextType());
 266                 writeOrderId(invoke.callTarget(), nodeOrder);
 267                 writeOrderId(invoke.stateAfter(), nodeOrder);
 268                 writeOrderId(invoke.next(), nodeOrder);
 269                 if (invoke instanceof InvokeWithExceptionNode) {
 270                     InvokeWithExceptionNode invokeWithExcpetion = (InvokeWithExceptionNode) invoke;
 271                     ExceptionObjectNode exceptionEdge = (ExceptionObjectNode) invokeWithExcpetion.exceptionEdge();
 272 
 273                     writeOrderId(invokeWithExcpetion.next().next(), nodeOrder);
 274                     writeOrderId(invokeWithExcpetion.exceptionEdge(), nodeOrder);
 275                     writeOrderId(exceptionEdge.stateAfter(), nodeOrder);
 276                     writeOrderId(exceptionEdge.next(), nodeOrder);
 277                 }
 278             }
 279         }
 280 
 281         /*
 282          * Write out the metadata (maximum fixed node order id and the table of contents with the
 283          * start offset for all nodes).
 284          */
 285         int metadataStart = TypeConversion.asS4(writer.getBytesWritten());
 286         writer.putUV(nodeOrder.maxFixedNodeOrderId);
 287         writer.putUV(nodeCount);
 288         for (int i = 0; i < nodeCount; i++) {
 289             writer.putUV(metadataStart - nodeStartOffsets[i]);
 290         }
 291 
 292         /* Check that the decoding of the encode graph is the same as the input. */
 293         assert verifyEncoding(graph, new EncodedGraph(getEncoding(), metadataStart, getObjects(), getNodeClasses(), graph), architecture);
 294 
 295         return metadataStart;
 296     }
 297 
 298     public byte[] getEncoding() {
 299         return writer.toArray(new byte[TypeConversion.asS4(writer.getBytesWritten())]);
 300     }
 301 
 302     static class NodeOrder {
 303         protected final NodeMap<Integer> orderIds;
 304         protected int nextOrderId;
 305         protected int maxFixedNodeOrderId;
 306 
 307         NodeOrder(StructuredGraph graph) {
 308             this.orderIds = new NodeMap<>(graph);
 309             this.nextOrderId = START_NODE_ORDER_ID;
 310 
 311             /* Order the fixed nodes of the graph in reverse postorder. */
 312             Deque<AbstractBeginNode> nodeQueue = new ArrayDeque<>();
 313             FixedNode current = graph.start();
 314             do {
 315                 add(current);
 316                 if (current instanceof AbstractBeginNode) {
 317                     add(((AbstractBeginNode) current).next());
 318                 }
 319 
 320                 if (current instanceof FixedWithNextNode) {
 321                     current = ((FixedWithNextNode) current).next;
 322                 } else {
 323                     if (current instanceof ControlSplitNode) {
 324                         for (Node successor : current.successors()) {
 325                             if (successor != null) {
 326                                 nodeQueue.addFirst((AbstractBeginNode) successor);
 327                             }
 328                         }
 329                     } else if (current instanceof EndNode) {
 330                         AbstractMergeNode merge = ((AbstractEndNode) current).merge();
 331                         boolean allForwardEndsVisited = true;
 332                         for (int i = 0; i < merge.forwardEndCount(); i++) {
 333                             if (orderIds.get(merge.forwardEndAt(i)) == null) {
 334                                 allForwardEndsVisited = false;
 335                                 break;
 336                             }
 337                         }
 338                         if (allForwardEndsVisited) {
 339                             nodeQueue.add(merge);
 340                         }
 341                     }
 342                     current = nodeQueue.pollFirst();
 343                 }
 344             } while (current != null);
 345 
 346             maxFixedNodeOrderId = nextOrderId - 1;
 347 
 348             /*
 349              * Emit all parameters consecutively at a known location (after all fixed nodes). This
 350              * allows substituting parameters when inlining during decoding by pre-initializing the
 351              * decoded node list.
 352              *
 353              * Note that not all parameters must be present (unused parameters are deleted after
 354              * parsing). This leads to holes in the orderId, i.e., unused orderIds.
 355              */
 356             int parameterCount = graph.method().getSignature().getParameterCount(!graph.method().isStatic());
 357             for (ParameterNode node : graph.getNodes(ParameterNode.TYPE)) {
 358                 assert orderIds.get(node) == null : "Parameter node must not be ordered yet";
 359                 assert node.index() < parameterCount : "Parameter index out of range";
 360                 orderIds.set(node, nextOrderId + node.index());
 361             }
 362             nextOrderId += parameterCount;
 363 
 364             for (Node node : graph.getNodes()) {
 365                 assert (node instanceof FixedNode || node instanceof ParameterNode) == (orderIds.get(node) != null) : "all fixed nodes and ParameterNodes must be ordered: " + node;
 366                 add(node);
 367             }
 368         }
 369 
 370         private void add(Node node) {
 371             if (orderIds.get(node) == null) {
 372                 orderIds.set(node, nextOrderId);
 373                 nextOrderId++;
 374             }
 375         }
 376     }
 377 
 378     protected void writeProperties(Node node, Fields fields) {
 379         writeObjectId(node.getNodeSourcePosition());
 380         for (int idx = 0; idx < fields.getCount(); idx++) {
 381             if (fields.getType(idx).isPrimitive()) {
 382                 long primitive = fields.getRawPrimitive(node, idx);
 383                 writer.putSV(primitive);
 384             } else {
 385                 Object property = fields.get(node, idx);
 386                 writeObjectId(property);
 387             }
 388         }
 389     }
 390 
 391     protected void writeEdges(Node node, Edges edges, NodeOrder nodeOrder) {
 392         if (node instanceof PhiNode) {
 393             /* Edges are not needed for decoding, so we must not write it. */
 394             return;
 395         }
 396 
 397         for (int idx = 0; idx < edges.getDirectCount(); idx++) {
 398             if (GraphDecoder.skipDirectEdge(node, edges, idx)) {
 399                 /* Edge is not needed for decoding, so we must not write it. */
 400                 continue;
 401             }
 402             Node edge = Edges.getNode(node, edges.getOffsets(), idx);
 403             writeOrderId(edge, nodeOrder);
 404         }
 405 
 406         if (node instanceof AbstractMergeNode && edges.type() == Edges.Type.Inputs) {
 407             /* The ends of merge nodes are decoded manually when the ends are processed. */
 408         } else {
 409             for (int idx = edges.getDirectCount(); idx < edges.getCount(); idx++) {
 410                 NodeList<Node> edgeList = Edges.getNodeList(node, edges.getOffsets(), idx);
 411                 if (edgeList == null) {
 412                     writer.putSV(-1);
 413                 } else {
 414                     writer.putSV(edgeList.size());
 415                     for (Node edge : edgeList) {
 416                         writeOrderId(edge, nodeOrder);
 417                     }
 418                 }
 419             }
 420         }
 421 
 422     }
 423 
 424     protected void writeOrderId(Node node, NodeOrder nodeOrder) {
 425         writer.putUV(node == null ? NULL_ORDER_ID : nodeOrder.orderIds.get(node));
 426     }
 427 
 428     protected void writeObjectId(Object object) {
 429         writer.putUV(objects.getIndex(object));
 430     }
 431 
 432     /**
 433      * Verification code that checks that the decoding of an encode graph is the same as the
 434      * original graph.
 435      */
 436     @SuppressWarnings("try")
 437     public static boolean verifyEncoding(StructuredGraph originalGraph, EncodedGraph encodedGraph, Architecture architecture) {
 438         DebugContext debug = originalGraph.getDebug();
 439         // @formatter:off
 440         StructuredGraph decodedGraph = new StructuredGraph.Builder(originalGraph.getOptions(), debug, AllowAssumptions.YES).
 441                         method(originalGraph.method()).
 442                         setIsSubstitution(originalGraph.isSubstitution()).
 443                         trackNodeSourcePosition(originalGraph.trackNodeSourcePosition()).
 444                         build();
 445         // @formatter:off
 446         GraphDecoder decoder = new GraphDecoder(architecture, decodedGraph);
 447         decoder.decode(encodedGraph);
 448 
 449         decodedGraph.verify();
 450         try {
 451             GraphComparison.verifyGraphsEqual(originalGraph, decodedGraph);
 452         } catch (Throwable ex) {
 453             originalGraph.getDebug();
 454             try (DebugContext.Scope scope = debug.scope("GraphEncoder")) {
 455                 debug.dump(DebugContext.VERBOSE_LEVEL, originalGraph, "Original Graph");
 456                 debug.dump(DebugContext.VERBOSE_LEVEL, decodedGraph, "Decoded Graph");
 457             }
 458             throw ex;
 459         }
 460         return true;
 461     }
 462 }
 463 
 464 class GraphComparison {
 465     public static boolean verifyGraphsEqual(StructuredGraph expectedGraph, StructuredGraph actualGraph) {
 466         NodeMap<Node> nodeMapping = new NodeMap<>(expectedGraph);
 467         Deque<Pair<Node, Node>> workList = new ArrayDeque<>();
 468 
 469         pushToWorklist(expectedGraph.start(), actualGraph.start(), nodeMapping, workList);
 470         while (!workList.isEmpty()) {
 471             Pair<Node, Node> pair = workList.removeFirst();
 472             Node expectedNode = pair.getLeft();
 473             Node actualNode = pair.getRight();
 474             assert expectedNode.getClass() == actualNode.getClass();
 475 
 476             NodeClass<?> nodeClass = expectedNode.getNodeClass();
 477             assert nodeClass == actualNode.getNodeClass();
 478 
 479             if (expectedNode instanceof MergeNode) {
 480                 /* The order of the ends can be different, so ignore them. */
 481                 verifyNodesEqual(expectedNode.inputs(), actualNode.inputs(), nodeMapping, workList, true);
 482             } else if (expectedNode instanceof PhiNode) {
 483                 verifyPhi((PhiNode) expectedNode, (PhiNode) actualNode, nodeMapping, workList);
 484             } else {
 485                 verifyNodesEqual(expectedNode.inputs(), actualNode.inputs(), nodeMapping, workList, false);
 486             }
 487             verifyNodesEqual(expectedNode.successors(), actualNode.successors(), nodeMapping, workList, false);
 488 
 489             if (expectedNode instanceof LoopEndNode) {
 490                 LoopEndNode actualLoopEnd = (LoopEndNode) actualNode;
 491                 assert actualLoopEnd.loopBegin().loopEnds().snapshot().indexOf(actualLoopEnd) == actualLoopEnd.endIndex();
 492             } else {
 493                 for (int i = 0; i < nodeClass.getData().getCount(); i++) {
 494                     Object expectedProperty = nodeClass.getData().get(expectedNode, i);
 495                     Object actualProperty = nodeClass.getData().get(actualNode, i);
 496                     assert Objects.equals(expectedProperty, actualProperty);
 497                 }
 498             }
 499 
 500             if (expectedNode instanceof EndNode) {
 501                 /* Visit the merge node, which is the one and only usage of the EndNode. */
 502                 assert expectedNode.usages().count() == 1;
 503                 assert actualNode.usages().count() == 1;
 504                 verifyNodesEqual(expectedNode.usages(), actualNode.usages(), nodeMapping, workList, false);
 505             }
 506 
 507             if (expectedNode instanceof AbstractEndNode) {
 508                 /* Visit the input values of the merge phi functions for this EndNode. */
 509                 verifyPhis((AbstractEndNode) expectedNode, (AbstractEndNode) actualNode, nodeMapping, workList);
 510             }
 511         }
 512 
 513         return true;
 514     }
 515 
 516     protected static void verifyPhi(PhiNode expectedPhi, PhiNode actualPhi, NodeMap<Node> nodeMapping, Deque<Pair<Node, Node>> workList) {
 517         AbstractMergeNode expectedMergeNode = expectedPhi.merge();
 518         AbstractMergeNode actualMergeNode = actualPhi.merge();
 519         assert actualMergeNode == nodeMapping.get(expectedMergeNode);
 520 
 521         for (EndNode expectedEndNode : expectedMergeNode.ends) {
 522             EndNode actualEndNode = (EndNode) nodeMapping.get(expectedEndNode);
 523             if (actualEndNode != null) {
 524                 ValueNode expectedPhiInput = expectedPhi.valueAt(expectedEndNode);
 525                 ValueNode actualPhiInput = actualPhi.valueAt(actualEndNode);
 526                 verifyNodeEqual(expectedPhiInput, actualPhiInput, nodeMapping, workList, false);
 527             }
 528         }
 529     }
 530 
 531     protected static void verifyPhis(AbstractEndNode expectedEndNode, AbstractEndNode actualEndNode, NodeMap<Node> nodeMapping, Deque<Pair<Node, Node>> workList) {
 532         AbstractMergeNode expectedMergeNode = expectedEndNode.merge();
 533         AbstractMergeNode actualMergeNode = (AbstractMergeNode) nodeMapping.get(expectedMergeNode);
 534         assert actualMergeNode != null;
 535 
 536         for (PhiNode expectedPhi : expectedMergeNode.phis()) {
 537             PhiNode actualPhi = (PhiNode) nodeMapping.get(expectedPhi);
 538             if (actualPhi != null) {
 539                 ValueNode expectedPhiInput = expectedPhi.valueAt(expectedEndNode);
 540                 ValueNode actualPhiInput = actualPhi.valueAt(actualEndNode);
 541                 verifyNodeEqual(expectedPhiInput, actualPhiInput, nodeMapping, workList, false);
 542             }
 543         }
 544     }
 545 
 546     private static void verifyNodesEqual(NodeIterable<Node> expectedNodes, NodeIterable<Node> actualNodes, NodeMap<Node> nodeMapping, Deque<Pair<Node, Node>> workList, boolean ignoreEndNode) {
 547         Iterator<Node> actualIter = actualNodes.iterator();
 548         for (Node expectedNode : expectedNodes) {
 549             verifyNodeEqual(expectedNode, actualIter.next(), nodeMapping, workList, ignoreEndNode);
 550         }
 551         assert !actualIter.hasNext();
 552     }
 553 
 554     protected static void verifyNodeEqual(Node expectedNode, Node actualNode, NodeMap<Node> nodeMapping, Deque<Pair<Node, Node>> workList, boolean ignoreEndNode) {
 555         assert expectedNode.getClass() == actualNode.getClass();
 556         if (ignoreEndNode && expectedNode instanceof EndNode) {
 557             return;
 558         }
 559 
 560         Node existing = nodeMapping.get(expectedNode);
 561         if (existing != null) {
 562             assert existing == actualNode;
 563         } else {
 564             pushToWorklist(expectedNode, actualNode, nodeMapping, workList);
 565         }
 566     }
 567 
 568     protected static void pushToWorklist(Node expectedNode, Node actualNode, NodeMap<Node> nodeMapping, Deque<Pair<Node, Node>> workList) {
 569         nodeMapping.set(expectedNode, actualNode);
 570         if (expectedNode instanceof AbstractEndNode) {
 571             /* To ensure phi nodes have been added, we handle everything before block ends. */
 572             workList.addLast(Pair.create(expectedNode, actualNode));
 573         } else {
 574             workList.addFirst(Pair.create(expectedNode, actualNode));
 575         }
 576     }
 577 }