blob: 8e99606de1d094a03b30173b80d312c9a59e04d1 [file] [log] [blame]
/*
* 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<Boolean> VerifyGraalGraphs = new OptionValue<>(true);
@Option(help = "Perform expensive verification of graph inputs, usages, successors and predecessors", type = OptionType.Debug)//
public static final OptionValue<Boolean> 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<Integer> 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<Boolean> 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<Node> iterableNodesFirst;
private final ArrayList<Node> 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<CacheEntry, Node> 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<Map<Node, Node>> 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<Map<Node, Node>> duplicationMapCallback) {
Graph copy = new Graph(newName);
Map<Node, Node> duplicates = copy.addDuplicates(getNodes(), this, this.getNodeCount(), (Map<Node, Node>) 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 extends Node> 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 extends Node> T addWithoutUnique(T node) {
return addHelper(node);
}
public <T extends Node> T addOrUnique(T node) {
if (node.getNodeClass().valueNumberable()) {
return uniqueHelper(node, true);
}
return add(node);
}
public <T extends Node> T addWithoutUniqueWithInputs(T node) {
addInputs(node);
return addHelper(node);
}
public <T extends Node> 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 <T extends Node> void addInputs(T node) {
node.applyInputs(addInputsFilter);
}
private <T extends Node> 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:
*
* <pre>
* try (NodeEventScope nes = graph.trackNodeEvents(listener)) {
* // make changes to the graph
* }
* </pre>
*/
public NodeEventScope trackNodeEvents(NodeEventListener listener) {
return new NodeEventScope(listener);
}
/**
* Looks for a node <i>similar</i> 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 extends Node & ValueNumberable> T unique(T node) {
return uniqueHelper(node, true);
}
<T extends Node> 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 extends Node> 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<Node> getNewNodes(Mark mark) {
final int index = mark == null ? 0 : mark.getValue();
return new NodeIterable<Node>() {
@Override
public Iterator<Node> 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<Node> getNodes() {
return new NodeIterable<Node>() {
@Override
public Iterator<Node> 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<PlaceHolderNode> 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 <T extends Node & IterableNodeType> NodeIterable<T> getNodes(final NodeClass<T> nodeClass) {
return new NodeIterable<T>() {
@Override
public Iterator<T> 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 <T extends Node & IterableNodeType> boolean hasNode(final NodeClass<T> 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 <T> NodeMap<T> 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<Node, Node> addDuplicates(Iterable<? extends Node> newNodes, final Graph oldGraph, int estimatedNodeCount, Map<Node, Node> 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<Node, Node> map;
MapReplacement(Map<Node, Node> 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<Node, Node> addDuplicates(Iterable<? extends Node> 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;
}
}