--- /dev/null 2017-01-22 10:16:57.869617664 -0800 +++ new/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.graph/src/org/graalvm/compiler/graph/Graph.java 2017-02-15 16:58:34.169515193 -0800 @@ -0,0 +1,1113 @@ +/* + * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.graalvm.compiler.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.function.Consumer; + +import org.graalvm.compiler.core.common.CollectionsFactory; +import org.graalvm.compiler.debug.Debug; +import org.graalvm.compiler.debug.DebugCloseable; +import org.graalvm.compiler.debug.DebugCounter; +import org.graalvm.compiler.debug.DebugTimer; +import org.graalvm.compiler.debug.Fingerprint; +import org.graalvm.compiler.debug.GraalError; +import org.graalvm.compiler.graph.Node.ValueNumberable; +import org.graalvm.compiler.graph.iterators.NodeIterable; +import org.graalvm.compiler.options.Option; +import org.graalvm.compiler.options.OptionType; +import org.graalvm.compiler.options.OptionValue; + +/** + * This class is a graph container, it contains the set of nodes that belong to this graph. + */ +public class Graph { + + public static class Options { + @Option(help = "Verify graphs often during compilation when assertions are turned on", type = OptionType.Debug)// + public static final OptionValue VerifyGraalGraphs = new OptionValue<>(true); + @Option(help = "Perform expensive verification of graph inputs, usages, successors and predecessors", type = OptionType.Debug)// + public static final OptionValue VerifyGraalGraphEdges = new OptionValue<>(false); + @Option(help = "Graal graph compression is performed when percent of live nodes falls below this value", type = OptionType.Debug)// + public static final OptionValue GraphCompressionThreshold = new OptionValue<>(70); + @Option(help = "Use Unsafe to clone graph nodes thus avoiding copying fields that will be re-initialized anyway", type = OptionType.Debug)// + public static final OptionValue CloneNodesWithUnsafe = new OptionValue<>(true); + } + + public final String name; + + /** + * The set of nodes in the graph, ordered by {@linkplain #register(Node) registration} time. + */ + Node[] nodes; + + /** + * Source information to associate with newly created nodes. + */ + NodeSourcePosition currentNodeSourcePosition; + + /** + * Records if updating of node source information is required when performing inlining. + */ + boolean seenNodeSourcePosition; + + /** + * The number of valid entries in {@link #nodes}. + */ + int nodesSize; + + /** + * Records the modification count for nodes. This is only used in assertions. + */ + private int[] nodeModCounts; + + /** + * Records the modification count for nodes' usage lists. This is only used in assertions. + */ + private int[] nodeUsageModCounts; + + // these two arrays contain one entry for each NodeClass, indexed by NodeClass.iterableId. + // they contain the first and last pointer to a linked list of all nodes with this type. + private final ArrayList iterableNodesFirst; + private final ArrayList iterableNodesLast; + + private int nodesDeletedSinceLastCompression; + private int nodesDeletedBeforeLastCompression; + + /** + * The number of times this graph has been compressed. + */ + int compressions; + + NodeEventListener nodeEventListener; + + /** + * Used to global value number {@link ValueNumberable} {@linkplain NodeClass#isLeafNode() leaf} + * nodes. + */ + private final HashMap cachedLeafNodes = CollectionsFactory.newMap(); + + /* + * Indicates that the graph should no longer be modified. Frozen graphs can be used my multiple + * threads so it's only safe to read them. + */ + private boolean isFrozen = false; + + /** + * Entry in {@link Graph#cachedLeafNodes}. + */ + private static final class CacheEntry { + + private final Node node; + + CacheEntry(Node node) { + assert node.getNodeClass().valueNumberable(); + assert node.getNodeClass().isLeafNode(); + this.node = node; + } + + @Override + public int hashCode() { + return node.getNodeClass().valueNumber(node); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof CacheEntry) { + CacheEntry other = (CacheEntry) obj; + if (other.node.getClass() == node.getClass()) { + return node.valueEquals(other.node); + } + } + return false; + } + + @Override + public String toString() { + return node.toString(); + } + } + + private class NodeSourcePositionScope implements DebugCloseable { + private final NodeSourcePosition previous; + + NodeSourcePositionScope(NodeSourcePosition sourcePosition) { + previous = currentNodeSourcePosition; + currentNodeSourcePosition = sourcePosition; + } + + @Override + public void close() { + currentNodeSourcePosition = previous; + } + } + + public NodeSourcePosition currentNodeSourcePosition() { + return currentNodeSourcePosition; + } + + /** + * Opens a scope in which the source information from {@code node} is copied into nodes created + * within the scope. If {@code node} has no source information information, no scope is opened + * and {@code null} is returned. + * + * @return a {@link DebugCloseable} for managing the opened scope or {@code null} if no scope + * was opened + */ + public DebugCloseable withNodeSourcePosition(Node node) { + return withNodeSourcePosition(node.sourcePosition); + } + + /** + * Opens a scope in which {@code sourcePosition} is copied into nodes created within the scope. + * If {@code sourcePosition == null}, no scope is opened and {@code null} is returned. + * + * @return a {@link DebugCloseable} for managing the opened scope or {@code null} if no scope + * was opened + */ + public DebugCloseable withNodeSourcePosition(NodeSourcePosition sourcePosition) { + return sourcePosition != null ? new NodeSourcePositionScope(sourcePosition) : null; + } + + /** + * Opens a scope in which newly created nodes do not get any source information added. + * + * @return a {@link DebugCloseable} for managing the opened scope + */ + public DebugCloseable withoutNodeSourcePosition() { + return new NodeSourcePositionScope(null); + } + + /** + * Determines if this graph might contain nodes with source information. This is mainly useful + * to short circuit logic for updating those positions after inlining since that requires + * visiting every node in the graph. + */ + public boolean mayHaveNodeSourcePosition() { + assert seenNodeSourcePosition || verifyHasNoSourcePosition(); + return seenNodeSourcePosition; + } + + private boolean verifyHasNoSourcePosition() { + for (Node node : getNodes()) { + assert node.getNodeSourcePosition() == null; + } + return true; + } + + /** + * Creates an empty Graph with no name. + */ + public Graph() { + this(null); + } + + /** + * We only want the expensive modification count tracking when assertions are enabled for the + * {@link Graph} class. + */ + @SuppressWarnings("all") + public static boolean isModificationCountsEnabled() { + boolean enabled = false; + assert enabled = true; + return enabled; + } + + private static final int INITIAL_NODES_SIZE = 32; + + /** + * Creates an empty Graph with a given name. + * + * @param name the name of the graph, used for debugging purposes + */ + public Graph(String name) { + nodes = new Node[INITIAL_NODES_SIZE]; + iterableNodesFirst = new ArrayList<>(NodeClass.allocatedNodeIterabledIds()); + iterableNodesLast = new ArrayList<>(NodeClass.allocatedNodeIterabledIds()); + this.name = name; + if (isModificationCountsEnabled()) { + nodeModCounts = new int[INITIAL_NODES_SIZE]; + nodeUsageModCounts = new int[INITIAL_NODES_SIZE]; + } + } + + int extractOriginalNodeId(Node node) { + int id = node.id; + if (id <= Node.DELETED_ID_START) { + id = Node.DELETED_ID_START - id; + } + return id; + } + + int modCount(Node node) { + int id = extractOriginalNodeId(node); + if (id >= 0 && id < nodeModCounts.length) { + return nodeModCounts[id]; + } + return 0; + } + + void incModCount(Node node) { + int id = extractOriginalNodeId(node); + if (id >= 0) { + if (id >= nodeModCounts.length) { + nodeModCounts = Arrays.copyOf(nodeModCounts, id * 2 + 30); + } + nodeModCounts[id]++; + } else { + assert false; + } + } + + int usageModCount(Node node) { + int id = extractOriginalNodeId(node); + if (id >= 0 && id < nodeUsageModCounts.length) { + return nodeUsageModCounts[id]; + } + return 0; + } + + void incUsageModCount(Node node) { + int id = extractOriginalNodeId(node); + if (id >= 0) { + if (id >= nodeUsageModCounts.length) { + nodeUsageModCounts = Arrays.copyOf(nodeUsageModCounts, id * 2 + 30); + } + nodeUsageModCounts[id]++; + } else { + assert false; + } + } + + /** + * Creates a copy of this graph. + */ + public final Graph copy() { + return copy(name, null); + } + + /** + * Creates a copy of this graph. + * + * @param duplicationMapCallback consumer of the duplication map created during the copying + */ + public final Graph copy(Consumer> duplicationMapCallback) { + return copy(name, duplicationMapCallback); + } + + /** + * Creates a copy of this graph. + * + * @param newName the name of the copy, used for debugging purposes (can be null) + */ + public final Graph copy(String newName) { + return copy(newName, null); + } + + /** + * Creates a copy of this graph. + * + * @param newName the name of the copy, used for debugging purposes (can be null) + * @param duplicationMapCallback consumer of the duplication map created during the copying + */ + protected Graph copy(String newName, Consumer> duplicationMapCallback) { + Graph copy = new Graph(newName); + Map duplicates = copy.addDuplicates(getNodes(), this, this.getNodeCount(), (Map) null); + if (duplicationMapCallback != null) { + duplicationMapCallback.accept(duplicates); + } + return copy; + } + + @Override + public String toString() { + return name == null ? super.toString() : "Graph " + name; + } + + /** + * Gets the number of live nodes in this graph. That is the number of nodes which have been + * added to the graph minus the number of deleted nodes. + * + * @return the number of live nodes in this graph + */ + public int getNodeCount() { + return nodesSize - getNodesDeletedSinceLastCompression(); + } + + /** + * Gets the number of times this graph has been {@linkplain #maybeCompress() compressed}. Node + * identifiers are only stable between compressions. To ensure this constraint is observed, any + * entity relying upon stable node identifiers should use {@link NodeIdAccessor}. + */ + public int getCompressions() { + return compressions; + } + + /** + * Gets the number of nodes which have been deleted from this graph since it was last + * {@linkplain #maybeCompress() compressed}. + */ + public int getNodesDeletedSinceLastCompression() { + return nodesDeletedSinceLastCompression; + } + + /** + * Gets the total number of nodes which have been deleted from this graph. + */ + public int getTotalNodesDeleted() { + return nodesDeletedSinceLastCompression + nodesDeletedBeforeLastCompression; + } + + /** + * Adds a new node to the graph. + * + * @param node the node to be added + * @return the node which was added to the graph + */ + public T add(T node) { + if (node.getNodeClass().valueNumberable()) { + throw new IllegalStateException("Using add for value numberable node. Consider using either unique or addWithoutUnique."); + } + return addHelper(node); + } + + public T addWithoutUnique(T node) { + return addHelper(node); + } + + public T addOrUnique(T node) { + if (node.getNodeClass().valueNumberable()) { + return uniqueHelper(node, true); + } + return add(node); + } + + public T addWithoutUniqueWithInputs(T node) { + addInputs(node); + return addHelper(node); + } + + public T addOrUniqueWithInputs(T node) { + addInputs(node); + if (node.getNodeClass().valueNumberable()) { + return uniqueHelper(node, true); + } + return add(node); + } + + private final class AddInputsFilter extends Node.EdgeVisitor { + + @Override + public Node apply(Node self, Node input) { + if (!input.isAlive()) { + assert !input.isDeleted(); + return addOrUniqueWithInputs(input); + } else { + return input; + } + } + + } + + private AddInputsFilter addInputsFilter = new AddInputsFilter(); + + private void addInputs(T node) { + node.applyInputs(addInputsFilter); + } + + private T addHelper(T node) { + node.initialize(this); + return node; + } + + /** + * The type of events sent to a {@link NodeEventListener}. + */ + public enum NodeEvent { + /** + * A node's input is changed. + */ + INPUT_CHANGED, + + /** + * A node's {@linkplain Node#usages() usages} count dropped to zero. + */ + ZERO_USAGES, + + /** + * A node was added to a graph. + */ + NODE_ADDED; + } + + /** + * Client interested in one or more node related events. + */ + public interface NodeEventListener { + + /** + * Default handler for events. + * + * @param e an event + * @param node the node related to {@code e} + */ + default void event(NodeEvent e, Node node) { + } + + /** + * Notifies this listener of a change in a node's inputs. + * + * @param node a node who has had one of its inputs changed + */ + default void inputChanged(Node node) { + event(NodeEvent.INPUT_CHANGED, node); + } + + /** + * Notifies this listener of a node becoming unused. + * + * @param node a node whose {@link Node#usages()} just became empty + */ + default void usagesDroppedToZero(Node node) { + event(NodeEvent.ZERO_USAGES, node); + } + + /** + * Notifies this listener of an added node. + * + * @param node a node that was just added to the graph + */ + default void nodeAdded(Node node) { + event(NodeEvent.NODE_ADDED, node); + } + } + + /** + * Registers a given {@link NodeEventListener} with the enclosing graph until this object is + * {@linkplain #close() closed}. + */ + public final class NodeEventScope implements AutoCloseable { + NodeEventScope(NodeEventListener listener) { + if (nodeEventListener == null) { + nodeEventListener = listener; + } else { + nodeEventListener = new ChainedNodeEventListener(listener, nodeEventListener); + } + } + + @Override + public void close() { + assert nodeEventListener != null; + if (nodeEventListener instanceof ChainedNodeEventListener) { + nodeEventListener = ((ChainedNodeEventListener) nodeEventListener).next; + } else { + nodeEventListener = null; + } + } + } + + private static class ChainedNodeEventListener implements NodeEventListener { + + NodeEventListener head; + NodeEventListener next; + + ChainedNodeEventListener(NodeEventListener head, NodeEventListener next) { + this.head = head; + this.next = next; + } + + @Override + public void nodeAdded(Node node) { + head.nodeAdded(node); + next.nodeAdded(node); + } + + @Override + public void inputChanged(Node node) { + head.inputChanged(node); + next.inputChanged(node); + } + + @Override + public void usagesDroppedToZero(Node node) { + head.usagesDroppedToZero(node); + next.usagesDroppedToZero(node); + } + } + + /** + * Registers a given {@link NodeEventListener} with this graph. This should be used in + * conjunction with try-with-resources statement as follows: + * + *
+     * try (NodeEventScope nes = graph.trackNodeEvents(listener)) {
+     *     // make changes to the graph
+     * }
+     * 
+ */ + public NodeEventScope trackNodeEvents(NodeEventListener listener) { + return new NodeEventScope(listener); + } + + /** + * Looks for a node similar to {@code node} and returns it if found. Otherwise + * {@code node} is added to this graph and returned. + * + * @return a node similar to {@code node} if one exists, otherwise {@code node} + */ + public T unique(T node) { + return uniqueHelper(node, true); + } + + T uniqueHelper(T node, boolean addIfMissing) { + assert node.getNodeClass().valueNumberable(); + T other = this.findDuplicate(node); + if (other != null) { + return other; + } else { + T result = addIfMissing ? addHelper(node) : node; + if (node.getNodeClass().isLeafNode()) { + putNodeIntoCache(result); + } + return result; + } + } + + void putNodeIntoCache(Node node) { + assert node.graph() == this || node.graph() == null; + assert node.getNodeClass().valueNumberable(); + assert node.getNodeClass().isLeafNode() : node.getClass(); + CacheEntry entry = new CacheEntry(node); + cachedLeafNodes.put(entry, node); + } + + Node findNodeInCache(Node node) { + CacheEntry key = new CacheEntry(node); + Node result = cachedLeafNodes.get(key); + if (result != null && result.isDeleted()) { + cachedLeafNodes.remove(key); + return null; + } + return result; + } + + /** + * Returns a possible duplicate for the given node in the graph or {@code null} if no such + * duplicate exists. + */ + @SuppressWarnings("unchecked") + public T findDuplicate(T node) { + NodeClass nodeClass = node.getNodeClass(); + assert nodeClass.valueNumberable(); + if (nodeClass.isLeafNode()) { + // Leaf node: look up in cache + Node cachedNode = findNodeInCache(node); + if (cachedNode != null && cachedNode != node) { + return (T) cachedNode; + } else { + return null; + } + } else { + /* + * Non-leaf node: look for another usage of the node's inputs that has the same data, + * inputs and successors as the node. To reduce the cost of this computation, only the + * input with lowest usage count is considered. If this node is the only user of any + * input then the search can terminate early. The usage count is only incremented once + * the Node is in the Graph, so account for that in the test. + */ + final int earlyExitUsageCount = node.graph() != null ? 1 : 0; + int minCount = Integer.MAX_VALUE; + Node minCountNode = null; + for (Node input : node.inputs()) { + int usageCount = input.getUsageCount(); + if (usageCount == earlyExitUsageCount) { + return null; + } else if (usageCount < minCount) { + minCount = usageCount; + minCountNode = input; + } + } + if (minCountNode != null) { + for (Node usage : minCountNode.usages()) { + if (usage != node && nodeClass == usage.getNodeClass() && node.valueEquals(usage) && nodeClass.equalInputs(node, usage) && + nodeClass.equalSuccessors(node, usage)) { + return (T) usage; + } + } + return null; + } + return null; + } + } + + public boolean isNew(Mark mark, Node node) { + return node.id >= mark.getValue(); + } + + /** + * A snapshot of the {@linkplain Graph#getNodeCount() live node count} in a graph. + */ + public static class Mark extends NodeIdAccessor { + private final int value; + + Mark(Graph graph) { + super(graph); + this.value = graph.nodeIdCount(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Mark) { + Mark other = (Mark) obj; + return other.getValue() == getValue() && other.getGraph() == getGraph(); + } + return false; + } + + @Override + public int hashCode() { + return value ^ (epoch + 11); + } + + /** + * Determines if this mark is positioned at the first live node in the graph. + */ + public boolean isStart() { + return value == 0; + } + + /** + * Gets the {@linkplain Graph#getNodeCount() live node count} of the associated graph when + * this object was created. + */ + int getValue() { + return value; + } + + /** + * Determines if this mark still represents the {@linkplain Graph#getNodeCount() live node + * count} of the graph. + */ + public boolean isCurrent() { + return value == graph.nodeIdCount(); + } + } + + /** + * Gets a mark that can be used with {@link #getNewNodes}. + */ + public Mark getMark() { + return new Mark(this); + } + + /** + * Returns an {@link Iterable} providing all nodes added since the last {@link Graph#getMark() + * mark}. + */ + public NodeIterable getNewNodes(Mark mark) { + final int index = mark == null ? 0 : mark.getValue(); + return new NodeIterable() { + + @Override + public Iterator iterator() { + return new GraphNodeIterator(Graph.this, index); + } + }; + } + + /** + * Returns an {@link Iterable} providing all the live nodes. + * + * @return an {@link Iterable} providing all the live nodes. + */ + public NodeIterable getNodes() { + return new NodeIterable() { + + @Override + public Iterator iterator() { + return new GraphNodeIterator(Graph.this); + } + + @Override + public int count() { + return getNodeCount(); + } + }; + } + + // Fully qualified annotation name is required to satisfy javac + @org.graalvm.compiler.nodeinfo.NodeInfo + static final class PlaceHolderNode extends Node { + + public static final NodeClass TYPE = NodeClass.create(PlaceHolderNode.class); + + protected PlaceHolderNode() { + super(TYPE); + } + + } + + static final Node PLACE_HOLDER = new PlaceHolderNode(); + + public static final int COMPRESSION_THRESHOLD = Options.GraphCompressionThreshold.getValue(); + + private static final DebugCounter GraphCompressions = Debug.counter("GraphCompressions"); + + /** + * If the {@linkplain #COMPRESSION_THRESHOLD compression threshold} is met, the list of nodes is + * compressed such that all non-null entries precede all null entries while preserving the + * ordering between the nodes within the list. + */ + public boolean maybeCompress() { + if (Debug.isDumpEnabledForMethod() || Debug.isLogEnabledForMethod()) { + return false; + } + int liveNodeCount = getNodeCount(); + int liveNodePercent = liveNodeCount * 100 / nodesSize; + if (COMPRESSION_THRESHOLD == 0 || liveNodePercent >= COMPRESSION_THRESHOLD) { + return false; + } + GraphCompressions.increment(); + int nextId = 0; + for (int i = 0; nextId < liveNodeCount; i++) { + Node n = nodes[i]; + if (n != null) { + assert n.id == i; + if (i != nextId) { + assert n.id > nextId; + n.id = nextId; + nodes[nextId] = n; + nodes[i] = null; + } + nextId++; + } + } + if (isModificationCountsEnabled()) { + // This will cause any current iteration to fail with an assertion + Arrays.fill(nodeModCounts, 0); + Arrays.fill(nodeUsageModCounts, 0); + } + nodesSize = nextId; + compressions++; + nodesDeletedBeforeLastCompression += nodesDeletedSinceLastCompression; + nodesDeletedSinceLastCompression = 0; + return true; + } + + /** + * Returns an {@link Iterable} providing all the live nodes whose type is compatible with + * {@code type}. + * + * @param nodeClass the type of node to return + * @return an {@link Iterable} providing all the matching nodes + */ + public NodeIterable getNodes(final NodeClass nodeClass) { + return new NodeIterable() { + + @Override + public Iterator iterator() { + return new TypedGraphNodeIterator<>(nodeClass, Graph.this); + } + }; + } + + /** + * Returns whether the graph contains at least one node of the given type. + * + * @param type the type of node that is checked for occurrence + * @return whether there is at least one such node + */ + public boolean hasNode(final NodeClass type) { + return getNodes(type).iterator().hasNext(); + } + + /** + * @param iterableId + * @return the first live Node with a matching iterableId + */ + Node getIterableNodeStart(int iterableId) { + if (iterableNodesFirst.size() <= iterableId) { + return null; + } + Node start = iterableNodesFirst.get(iterableId); + if (start == null || !start.isDeleted()) { + return start; + } + return findFirstLiveIterable(iterableId, start); + } + + private Node findFirstLiveIterable(int iterableId, Node node) { + Node start = node; + while (start != null && start.isDeleted()) { + start = start.typeCacheNext; + } + /* + * Multiple threads iterating nodes can update this cache simultaneously. This is a benign + * race, since all threads update it to the same value. + */ + iterableNodesFirst.set(iterableId, start); + if (start == null) { + iterableNodesLast.set(iterableId, start); + } + return start; + } + + /** + * @param node + * @return return the first live Node with a matching iterableId starting from {@code node} + */ + Node getIterableNodeNext(Node node) { + if (node == null) { + return null; + } + Node n = node; + if (n == null || !n.isDeleted()) { + return n; + } + + return findNextLiveiterable(node); + } + + private Node findNextLiveiterable(Node start) { + Node n = start; + while (n != null && n.isDeleted()) { + n = n.typeCacheNext; + } + if (n == null) { + // Only dead nodes after this one + start.typeCacheNext = null; + int nodeClassId = start.getNodeClass().iterableId(); + assert nodeClassId != Node.NOT_ITERABLE; + iterableNodesLast.set(nodeClassId, start); + } else { + // Everything in between is dead + start.typeCacheNext = n; + } + return n; + } + + public NodeBitMap createNodeBitMap() { + return new NodeBitMap(this); + } + + public NodeMap createNodeMap() { + return new NodeMap<>(this); + } + + public NodeFlood createNodeFlood() { + return new NodeFlood(this); + } + + public NodeWorkList createNodeWorkList() { + return new NodeWorkList.SingletonNodeWorkList(this); + } + + public NodeWorkList createIterativeNodeWorkList(boolean fill, int iterationLimitPerNode) { + return new NodeWorkList.IterativeNodeWorkList(this, fill, iterationLimitPerNode); + } + + void register(Node node) { + assert !isFrozen(); + assert node.id() == Node.INITIAL_ID; + if (nodes.length == nodesSize) { + Node[] newNodes = new Node[(nodesSize * 2) + 1]; + System.arraycopy(nodes, 0, newNodes, 0, nodesSize); + nodes = newNodes; + } + int id = nodesSize; + nodes[id] = node; + if (currentNodeSourcePosition != null) { + node.setNodeSourcePosition(currentNodeSourcePosition); + } else if (!seenNodeSourcePosition && node.getNodeSourcePosition() != null) { + seenNodeSourcePosition = true; + } + nodesSize++; + + updateNodeCaches(node); + + node.id = id; + if (nodeEventListener != null) { + nodeEventListener.nodeAdded(node); + } + if (!seenNodeSourcePosition && node.sourcePosition != null) { + seenNodeSourcePosition = true; + } + if (Fingerprint.ENABLED) { + Fingerprint.submit("%s: %s", NodeEvent.NODE_ADDED, node); + } + } + + @SuppressWarnings("unused") + private void postDeserialization() { + recomputeIterableNodeLists(); + } + + /** + * Rebuilds the lists used to support {@link #getNodes(NodeClass)}. This is useful for + * serialization where the underlying {@linkplain NodeClass#iterableId() iterable ids} may have + * changed. + */ + private void recomputeIterableNodeLists() { + iterableNodesFirst.clear(); + iterableNodesLast.clear(); + for (Node node : nodes) { + if (node != null && node.isAlive()) { + updateNodeCaches(node); + } + } + } + + private void updateNodeCaches(Node node) { + int nodeClassId = node.getNodeClass().iterableId(); + if (nodeClassId != Node.NOT_ITERABLE) { + while (iterableNodesFirst.size() <= nodeClassId) { + iterableNodesFirst.add(null); + iterableNodesLast.add(null); + } + Node prev = iterableNodesLast.get(nodeClassId); + if (prev != null) { + prev.typeCacheNext = node; + } else { + iterableNodesFirst.set(nodeClassId, node); + } + iterableNodesLast.set(nodeClassId, node); + } + } + + void unregister(Node node) { + assert !isFrozen(); + assert !node.isDeleted() : "cannot delete a node twice! node=" + node; + nodes[node.id] = null; + nodesDeletedSinceLastCompression++; + + // nodes aren't removed from the type cache here - they will be removed during iteration + } + + public boolean verify() { + if (Options.VerifyGraalGraphs.getValue()) { + for (Node node : getNodes()) { + try { + try { + assert node.verify(); + } catch (AssertionError t) { + throw new GraalError(t); + } catch (RuntimeException t) { + throw new GraalError(t); + } + } catch (GraalError e) { + throw GraalGraphError.transformAndAddContext(e, node).addContext(this); + } + } + } + return true; + } + + public Node getNode(int id) { + return nodes[id]; + } + + /** + * Returns the number of node ids generated so far. + * + * @return the number of node ids generated so far + */ + int nodeIdCount() { + return nodesSize; + } + + /** + * Adds duplicates of the nodes in {@code nodes} to this graph. This will recreate any edges + * between the duplicate nodes. The {@code replacement} map can be used to replace a node from + * the source graph by a given node (which must already be in this graph). Edges between + * duplicate and replacement nodes will also be recreated so care should be taken regarding the + * matching of node types in the replacement map. + * + * @param newNodes the nodes to be duplicated + * @param replacementsMap the replacement map (can be null if no replacement is to be performed) + * @return a map which associates the original nodes from {@code nodes} to their duplicates + */ + public Map addDuplicates(Iterable newNodes, final Graph oldGraph, int estimatedNodeCount, Map replacementsMap) { + DuplicationReplacement replacements; + if (replacementsMap == null) { + replacements = null; + } else { + replacements = new MapReplacement(replacementsMap); + } + return addDuplicates(newNodes, oldGraph, estimatedNodeCount, replacements); + } + + public interface DuplicationReplacement { + + Node replacement(Node original); + } + + private static final class MapReplacement implements DuplicationReplacement { + + private final Map map; + + MapReplacement(Map map) { + this.map = map; + } + + @Override + public Node replacement(Node original) { + Node replacement = map.get(original); + return replacement != null ? replacement : original; + } + + } + + private static final DebugTimer DuplicateGraph = Debug.timer("DuplicateGraph"); + + @SuppressWarnings({"all", "try"}) + public Map addDuplicates(Iterable newNodes, final Graph oldGraph, int estimatedNodeCount, DuplicationReplacement replacements) { + try (DebugCloseable s = DuplicateGraph.start()) { + return NodeClass.addGraphDuplicate(this, oldGraph, estimatedNodeCount, newNodes, replacements); + } + } + + /** + * Reverses the usage orders of all nodes. This is used for debugging to make sure an unorthodox + * usage order does not trigger bugs in the compiler. + */ + public void reverseUsageOrder() { + for (Node n : getNodes()) { + n.reverseUsageOrder(); + } + } + + public boolean isFrozen() { + return isFrozen; + } + + public void freeze() { + this.isFrozen = true; + } +}