changeset 20904:44d8f20d4f55

Merge.
author Doug Simon <doug.simon@oracle.com>
date Mon, 13 Apr 2015 21:51:19 +0200
parents dc58f4ca21c9 (current diff) c893d4112f30 (diff)
children f0d8a33aebd1
files graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ASTInstrumentListener.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/InstrumentListener.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultASTInstrumentListener.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultInstrumentListener.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/SimpleASTInstrumentListener.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/SimpleInstrumentListener.java
diffstat 44 files changed, 1070 insertions(+), 922 deletions(-) [+]
line wrap: on
line diff
--- a/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/AbstractObjectStamp.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/AbstractObjectStamp.java	Mon Apr 13 21:51:19 2015 +0200
@@ -98,9 +98,6 @@
         if (this == otherStamp) {
             return this;
         }
-        if (!isCompatible(otherStamp)) {
-            return StampFactory.illegal(Kind.Illegal);
-        }
         AbstractObjectStamp other = (AbstractObjectStamp) otherStamp;
         if (isIllegal()) {
             return other;
@@ -170,9 +167,6 @@
         if (this == otherStamp) {
             return this;
         }
-        if (!isCompatible(otherStamp)) {
-            return StampFactory.illegal(Kind.Illegal);
-        }
         AbstractObjectStamp other = (AbstractObjectStamp) otherStamp;
         if (isIllegal()) {
             return this;
--- a/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/FloatStamp.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/FloatStamp.java	Mon Apr 13 21:51:19 2015 +0200
@@ -176,9 +176,6 @@
         if (otherStamp == this) {
             return this;
         }
-        if (!(otherStamp instanceof FloatStamp)) {
-            return StampFactory.illegal(Kind.Illegal);
-        }
         FloatStamp other = (FloatStamp) otherStamp;
         assert getBits() == other.getBits();
         double meetUpperBound = meetBounds(upperBound, other.upperBound, Math::max);
@@ -198,9 +195,6 @@
         if (otherStamp == this) {
             return this;
         }
-        if (!(otherStamp instanceof FloatStamp)) {
-            return StampFactory.illegal(Kind.Illegal);
-        }
         FloatStamp other = (FloatStamp) otherStamp;
         assert getBits() == other.getBits();
         double joinUpperBound = Math.min(upperBound, other.upperBound);
--- a/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/IllegalStamp.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/IllegalStamp.java	Mon Apr 13 21:51:19 2015 +0200
@@ -27,7 +27,9 @@
 import com.oracle.graal.compiler.common.spi.*;
 
 /**
- * This stamp represents the illegal type. Values with this type can not exist at run time.
+ * This stamp represents the type of the {@link Kind#Illegal} value in the second slot of
+ * {@link Kind#Long} and {@link Kind#Double} values. It can only appear in framestates or virtual
+ * objects.
  */
 public final class IllegalStamp extends Stamp {
 
@@ -56,7 +58,8 @@
 
     @Override
     public Stamp constant(Constant c, MetaAccessProvider meta) {
-        throw GraalInternalError.shouldNotReachHere("illegal stamp has no value");
+        assert ((PrimitiveConstant) c).getKind() == Kind.Illegal;
+        return this;
     }
 
     @Override
@@ -66,17 +69,19 @@
 
     @Override
     public Stamp meet(Stamp other) {
+        assert other instanceof IllegalStamp;
         return this;
     }
 
     @Override
     public Stamp join(Stamp other) {
+        assert other instanceof IllegalStamp;
         return this;
     }
 
     @Override
     public boolean isCompatible(Stamp stamp) {
-        return false;
+        return stamp instanceof IllegalStamp;
     }
 
     @Override
@@ -91,6 +96,7 @@
 
     @Override
     public Stamp improveWith(Stamp other) {
+        assert other instanceof IllegalStamp;
         return this;
     }
 
--- a/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/IntegerStamp.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/IntegerStamp.java	Mon Apr 13 21:51:19 2015 +0200
@@ -253,9 +253,6 @@
         if (otherStamp == this) {
             return this;
         }
-        if (!(otherStamp instanceof IntegerStamp)) {
-            return StampFactory.illegal(Kind.Illegal);
-        }
         IntegerStamp other = (IntegerStamp) otherStamp;
         return createStamp(other, Math.max(upperBound, other.upperBound), Math.min(lowerBound, other.lowerBound), downMask & other.downMask, upMask | other.upMask);
     }
@@ -265,9 +262,6 @@
         if (otherStamp == this) {
             return this;
         }
-        if (!(otherStamp instanceof IntegerStamp)) {
-            return StampFactory.illegal(Kind.Illegal);
-        }
         IntegerStamp other = (IntegerStamp) otherStamp;
         long newDownMask = downMask | other.downMask;
         long newLowerBound = Math.max(lowerBound, other.lowerBound) | newDownMask;
--- a/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/StampFactory.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/StampFactory.java	Mon Apr 13 21:51:19 2015 +0200
@@ -76,10 +76,9 @@
         for (Kind k : Kind.values()) {
             if (stampCache[k.ordinal()] != null) {
                 illegalStampCache[k.ordinal()] = stampCache[k.ordinal()].illegal();
-            } else {
-                illegalStampCache[k.ordinal()] = IllegalStamp.getInstance();
             }
         }
+        illegalStampCache[Kind.Illegal.ordinal()] = IllegalStamp.getInstance();
     }
 
     public static Stamp tautology() {
@@ -122,10 +121,6 @@
         return positiveInt;
     }
 
-    public static Stamp illegal() {
-        return illegal(Kind.Illegal);
-    }
-
     public static Stamp illegal(Kind kind) {
         return illegalStampCache[kind.ordinal()];
     }
--- a/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/VoidStamp.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/type/VoidStamp.java	Mon Apr 13 21:51:19 2015 +0200
@@ -46,10 +46,8 @@
 
     @Override
     public Stamp improveWith(Stamp other) {
-        if (other instanceof VoidStamp) {
-            return this;
-        }
-        return StampFactory.illegal(Kind.Illegal);
+        assert other instanceof VoidStamp;
+        return this;
     }
 
     @Override
@@ -74,29 +72,19 @@
 
     @Override
     public Stamp meet(Stamp other) {
-        if (other instanceof IllegalStamp) {
-            return other.join(this);
-        }
-        if (this == other) {
-            return this;
-        }
-        return StampFactory.illegal(Kind.Illegal);
+        assert other instanceof VoidStamp;
+        return this;
     }
 
     @Override
     public Stamp join(Stamp other) {
-        if (other instanceof IllegalStamp) {
-            return other.join(this);
-        }
-        if (this == other) {
-            return this;
-        }
-        return StampFactory.illegal(Kind.Illegal);
+        assert other instanceof VoidStamp;
+        return this;
     }
 
     @Override
     public boolean isCompatible(Stamp stamp) {
-        return this == stamp;
+        return stamp instanceof VoidStamp;
     }
 
     @Override
--- a/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/util/UnsafeArrayTypeWriter.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.compiler.common/src/com/oracle/graal/compiler/common/util/UnsafeArrayTypeWriter.java	Mon Apr 13 21:51:19 2015 +0200
@@ -37,12 +37,17 @@
  */
 public class UnsafeArrayTypeWriter implements TypeWriter {
 
-    private static final int CHUNK_SIZE = 4000;
+    private static final int MIN_CHUNK_LENGTH = 200;
+    private static final int MAX_CHUNK_LENGTH = 16000;
 
     static class Chunk {
-        protected final byte[] data = new byte[CHUNK_SIZE];
+        protected final byte[] data;
         protected int size;
         protected Chunk next;
+
+        protected Chunk(int arrayLength) {
+            data = new byte[arrayLength];
+        }
     }
 
     private Chunk firstChunk;
@@ -50,7 +55,7 @@
     private int totalSize;
 
     public UnsafeArrayTypeWriter() {
-        firstChunk = new Chunk();
+        firstChunk = new Chunk(MIN_CHUNK_LENGTH);
         writeChunk = firstChunk;
     }
 
@@ -153,7 +158,7 @@
 
     private long writeOffset(int writeBytes) {
         if (writeChunk.size + writeBytes >= writeChunk.data.length) {
-            Chunk newChunk = new Chunk();
+            Chunk newChunk = new Chunk(Math.min(writeChunk.data.length * 2, MAX_CHUNK_LENGTH));
             writeChunk.next = newChunk;
             writeChunk = newChunk;
         }
--- a/graal/com.oracle.graal.compiler/src/com/oracle/graal/compiler/gen/NodeLIRBuilder.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.compiler/src/com/oracle/graal/compiler/gen/NodeLIRBuilder.java	Mon Apr 13 21:51:19 2015 +0200
@@ -304,7 +304,7 @@
     }
 
     protected void emitNode(ValueNode node) {
-        if (Debug.isLogEnabled() && node.stamp() instanceof IllegalStamp) {
+        if (Debug.isLogEnabled() && node.stamp().isIllegal()) {
             Debug.log("This node has invalid type, we are emitting dead code(?): %s", node);
         }
         if (node instanceof LIRLowerable) {
--- a/graal/com.oracle.graal.graph/src/com/oracle/graal/graph/Node.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.graph/src/com/oracle/graal/graph/Node.java	Mon Apr 13 21:51:19 2015 +0200
@@ -433,7 +433,7 @@
             this.movUsageFromEndTo(1);
             return true;
         }
-        for (int i = 0; i < this.extraUsagesCount; ++i) {
+        for (int i = this.extraUsagesCount - 1; i >= 0; i--) {
             if (extraUsages[i] == node) {
                 this.movUsageFromEndTo(i + INLINE_USAGE_COUNT);
                 return true;
@@ -683,12 +683,12 @@
     }
 
     private void unregisterInputs() {
-        for (Node input : inputs()) {
-            removeThisFromUsages(input);
+        acceptInputs((node, input) -> {
+            node.removeThisFromUsages(input);
             if (input.hasNoUsages()) {
-                maybeNotifyZeroUsages(input);
+                node.maybeNotifyZeroUsages(input);
             }
-        }
+        });
     }
 
     public void clearInputs() {
--- a/graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/nodes/type/MetaspacePointerStamp.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/nodes/type/MetaspacePointerStamp.java	Mon Apr 13 21:51:19 2015 +0200
@@ -40,9 +40,7 @@
 
     @Override
     public Stamp meet(Stamp other) {
-        if (!isCompatible(other)) {
-            return StampFactory.illegal();
-        }
+        assert isCompatible(other);
         return this;
     }
 
@@ -53,9 +51,7 @@
 
     @Override
     public Stamp join(Stamp other) {
-        if (!isCompatible(other)) {
-            return StampFactory.illegal();
-        }
+        assert isCompatible(other);
         return this;
     }
 
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/EncodedGraph.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/EncodedGraph.java	Mon Apr 13 21:51:19 2015 +0200
@@ -41,6 +41,12 @@
     private final Assumptions assumptions;
     private final Set<ResolvedJavaMethod> inlinedMethods;
 
+    /**
+     * The "table of contents" of the encoded graph, i.e., the mapping from orderId numbers to the
+     * offset in the encoded byte[] array. Used as a cache during decoding.
+     */
+    protected long[] nodeStartOffsets;
+
     public EncodedGraph(byte[] encoding, long startOffset, Object[] objects, NodeClass<?>[] types, Assumptions assumptions, Set<ResolvedJavaMethod> inlinedMethods) {
         this.encoding = encoding;
         this.startOffset = startOffset;
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/FrameState.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/FrameState.java	Mon Apr 13 21:51:19 2015 +0200
@@ -227,11 +227,11 @@
      * the stack.
      */
     public FrameState duplicateModifiedDuringCall(int newBci, Kind popKind) {
-        return duplicateModified(newBci, rethrowException, true, popKind);
+        return duplicateModified(graph(), newBci, rethrowException, true, popKind);
     }
 
     public FrameState duplicateModifiedBeforeCall(int newBci, Kind popKind, ValueNode... pushedValues) {
-        return duplicateModified(newBci, rethrowException, false, popKind, pushedValues);
+        return duplicateModified(graph(), newBci, rethrowException, false, popKind, pushedValues);
     }
 
     /**
@@ -241,7 +241,7 @@
      * followed by a null slot.
      */
     public FrameState duplicateModified(int newBci, boolean newRethrowException, Kind popKind, ValueNode... pushedValues) {
-        return duplicateModified(newBci, newRethrowException, duringCall, popKind, pushedValues);
+        return duplicateModified(graph(), newBci, newRethrowException, duringCall, popKind, pushedValues);
     }
 
     /**
@@ -250,7 +250,7 @@
      */
     public FrameState duplicateModified(Kind popKind, ValueNode pushedValue) {
         assert pushedValue != null && pushedValue.getKind() == popKind;
-        return duplicateModified(bci, rethrowException, duringCall, popKind, pushedValue);
+        return duplicateModified(graph(), bci, rethrowException, duringCall, popKind, pushedValue);
     }
 
     /**
@@ -259,7 +259,7 @@
      * correctly in slot encoding: a long or double will be followed by a null slot. The bci will be
      * changed to newBci.
      */
-    private FrameState duplicateModified(int newBci, boolean newRethrowException, boolean newDuringCall, Kind popKind, ValueNode... pushedValues) {
+    public FrameState duplicateModified(StructuredGraph graph, int newBci, boolean newRethrowException, boolean newDuringCall, Kind popKind, ValueNode... pushedValues) {
         ArrayList<ValueNode> copy;
         if (newRethrowException && !rethrowException && popKind == Kind.Void) {
             assert popKind == Kind.Void;
@@ -285,7 +285,7 @@
         copy.addAll(values.subList(localsSize + stackSize, values.size()));
 
         assert checkStackDepth(bci, stackSize, duringCall, rethrowException, newBci, newStackSize, newDuringCall, newRethrowException);
-        return graph().add(new FrameState(outerFrameState(), method, newBci, copy, localsSize, newStackSize, newRethrowException, newDuringCall, monitorIds, virtualObjectMappings));
+        return graph.add(new FrameState(outerFrameState(), method, newBci, copy, localsSize, newStackSize, newRethrowException, newDuringCall, monitorIds, virtualObjectMappings));
     }
 
     /**
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/GraphDecoder.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/GraphDecoder.java	Mon Apr 13 21:51:19 2015 +0200
@@ -26,6 +26,7 @@
 
 import java.util.*;
 
+import com.oracle.graal.api.meta.*;
 import com.oracle.graal.compiler.common.*;
 import com.oracle.graal.compiler.common.util.*;
 import com.oracle.graal.debug.*;
@@ -70,48 +71,38 @@
     protected static class MethodScope {
         /** The target graph where decoded nodes are added to. */
         public final StructuredGraph graph;
-        /** The state of the caller method. Only non-null during method inlining. */
-        public final MethodScope caller;
         /** The encode graph that is decoded. */
         public final EncodedGraph encodedGraph;
         /** Access to the encoded graph. */
         public final TypeReader reader;
-        /**
-         * The "table of contents" of the encoded graph, i.e., the mapping from orderId numbers to
-         * the offset in the encoded byte[] array.
-         */
-        public final long[] nodeStartOffsets;
         /** The kind of loop explosion to be performed during decoding. */
         public final LoopExplosionKind loopExplosion;
 
-        /**
-         * The start node of the decoded graph. This is a temporary node for inlined graphs that
-         * needs to be deleted after inlining.
-         */
-        public StartNode startNode;
         /** All return nodes encountered during decoding. */
         public final List<ReturnNode> returnNodes;
         /** The exception unwind node encountered during decoding, or null. */
         public UnwindNode unwindNode;
 
-        protected MethodScope(StructuredGraph graph, MethodScope caller, EncodedGraph encodedGraph, LoopExplosionKind loopExplosion) {
+        protected MethodScope(StructuredGraph graph, EncodedGraph encodedGraph, LoopExplosionKind loopExplosion) {
             this.graph = graph;
-            this.caller = caller;
             this.encodedGraph = encodedGraph;
             this.loopExplosion = loopExplosion;
             this.returnNodes = new ArrayList<>();
 
-            reader = new UnsafeArrayTypeReader(encodedGraph.getEncoding(), encodedGraph.getStartOffset());
-            int nodeCount = reader.getUVInt();
-            nodeStartOffsets = new long[nodeCount];
-            for (int i = 0; i < nodeCount; i++) {
-                nodeStartOffsets[i] = encodedGraph.getStartOffset() - reader.getUV();
+            if (encodedGraph != null) {
+                reader = new UnsafeArrayTypeReader(encodedGraph.getEncoding(), encodedGraph.getStartOffset());
+                if (encodedGraph.nodeStartOffsets == null) {
+                    int nodeCount = reader.getUVInt();
+                    long[] nodeStartOffsets = new long[nodeCount];
+                    for (int i = 0; i < nodeCount; i++) {
+                        nodeStartOffsets[i] = encodedGraph.getStartOffset() - reader.getUV();
+                    }
+                    encodedGraph.nodeStartOffsets = nodeStartOffsets;
+                }
+            } else {
+                reader = null;
             }
         }
-
-        public boolean isInlinedMethod() {
-            return caller != null;
-        }
     }
 
     /** Decoding state maintained for each loop in the encoded graph. */
@@ -153,7 +144,7 @@
             this.iterationStates = null;
             this.loopBeginOrderId = -1;
 
-            int nodeCount = methodScope.nodeStartOffsets.length;
+            int nodeCount = methodScope.encodedGraph.nodeStartOffsets.length;
             this.nodesToProcess = new BitSet(nodeCount);
             this.initialCreatedNodes = new Node[nodeCount];
             this.createdNodes = new Node[nodeCount];
@@ -226,22 +217,55 @@
         }
     }
 
+    /**
+     * Additional information encoded for {@link Invoke} nodes to allow method inlining without
+     * decoding the frame state and successors beforehand.
+     */
+    protected static class InvokeData {
+        public final Invoke invoke;
+        public final ResolvedJavaType contextType;
+        public final int invokeOrderId;
+        public final int callTargetOrderId;
+        public final int stateAfterOrderId;
+        public final int nextOrderId;
+
+        public final int nextNextOrderId;
+        public final int exceptionOrderId;
+        public final int exceptionStateOrderId;
+        public final int exceptionNextOrderId;
+
+        protected InvokeData(Invoke invoke, ResolvedJavaType contextType, int invokeOrderId, int callTargetOrderId, int stateAfterOrderId, int nextOrderId, int nextNextOrderId, int exceptionOrderId,
+                        int exceptionStateOrderId, int exceptionNextOrderId) {
+            this.invoke = invoke;
+            this.contextType = contextType;
+            this.invokeOrderId = invokeOrderId;
+            this.callTargetOrderId = callTargetOrderId;
+            this.stateAfterOrderId = stateAfterOrderId;
+            this.nextOrderId = nextOrderId;
+            this.nextNextOrderId = nextNextOrderId;
+            this.exceptionOrderId = exceptionOrderId;
+            this.exceptionStateOrderId = exceptionStateOrderId;
+            this.exceptionNextOrderId = exceptionNextOrderId;
+        }
+    }
+
     public final void decode(StructuredGraph graph, EncodedGraph encodedGraph) {
-        MethodScope methodScope = new MethodScope(graph, null, encodedGraph, LoopExplosionKind.NONE);
-        decode(methodScope);
+        MethodScope methodScope = new MethodScope(graph, encodedGraph, LoopExplosionKind.NONE);
+        decode(methodScope, null);
         cleanupGraph(methodScope);
         methodScope.graph.verify();
     }
 
-    protected final void decode(MethodScope methodScope) {
+    protected final void decode(MethodScope methodScope, FixedWithNextNode startNode) {
         LoopScope loopScope = new LoopScope(methodScope);
-        if (methodScope.isInlinedMethod()) {
-            methodScope.startNode = methodScope.graph.add(new StartNode());
-            methodScope.startNode.setNext(makeStubNode(methodScope, loopScope, GraphEncoder.FIRST_NODE_ORDER_ID));
+        FixedNode firstNode;
+        if (startNode != null) {
+            firstNode = makeStubNode(methodScope, loopScope, GraphEncoder.FIRST_NODE_ORDER_ID);
+            startNode.setNext(firstNode);
             loopScope.nodesToProcess.set(GraphEncoder.FIRST_NODE_ORDER_ID);
         } else {
-            methodScope.startNode = methodScope.graph.start();
-            registerNode(loopScope, GraphEncoder.START_NODE_ORDER_ID, methodScope.startNode, false, false);
+            firstNode = methodScope.graph.start();
+            registerNode(loopScope, GraphEncoder.START_NODE_ORDER_ID, firstNode, false, false);
             loopScope.nodesToProcess.set(GraphEncoder.START_NODE_ORDER_ID);
         }
 
@@ -262,7 +286,7 @@
         if (methodScope.loopExplosion == LoopExplosionKind.MERGE_EXPLODE) {
             cleanupGraph(methodScope);
             Debug.dump(methodScope.graph, "Before loop detection");
-            detectLoops(methodScope.graph, methodScope.startNode);
+            detectLoops(methodScope.graph, firstNode);
         }
     }
 
@@ -275,6 +299,18 @@
             return loopScope;
         }
 
+        if ((node instanceof MergeNode || (node instanceof LoopBeginNode && (methodScope.loopExplosion == LoopExplosionKind.FULL_UNROLL || methodScope.loopExplosion == LoopExplosionKind.FULL_EXPLODE))) &&
+                        ((AbstractMergeNode) node).forwardEndCount() == 1) {
+            AbstractMergeNode merge = (AbstractMergeNode) node;
+            EndNode singleEnd = merge.forwardEndAt(0);
+            FixedNode next = makeStubNode(methodScope, loopScope, nodeOrderId + GraphEncoder.BEGIN_NEXT_ORDER_ID_OFFSET);
+            singleEnd.replaceAtPredecessor(next);
+
+            merge.safeDelete();
+            singleEnd.safeDelete();
+            return loopScope;
+        }
+
         LoopScope successorAddScope = loopScope;
         boolean updatePredecessors = true;
         if (node instanceof LoopExitNode) {
@@ -282,7 +318,7 @@
             updatePredecessors = methodScope.loopExplosion == LoopExplosionKind.NONE;
         }
 
-        methodScope.reader.setByteIndex(methodScope.nodeStartOffsets[nodeOrderId]);
+        methodScope.reader.setByteIndex(methodScope.encodedGraph.nodeStartOffsets[nodeOrderId]);
         int typeId = methodScope.reader.getUVInt();
         assert node.getNodeClass() == methodScope.encodedGraph.getNodeClasses()[typeId];
         readProperties(methodScope, node);
@@ -299,7 +335,7 @@
             if (methodScope.loopExplosion != LoopExplosionKind.NONE) {
                 handleLoopExplosionProxyNodes(methodScope, loopScope, (LoopExitNode) node, nodeOrderId);
             } else {
-                handleProxyNodes(methodScope, loopScope);
+                handleProxyNodes(methodScope, loopScope, (LoopExitNode) node);
             }
 
         } else if (node instanceof AbstractEndNode) {
@@ -311,7 +347,7 @@
                 phiNodeScope = loopScope.nextIterations.getLast();
             }
 
-            int mergeOrderId = methodScope.reader.getUVInt();
+            int mergeOrderId = readOrderId(methodScope);
             AbstractMergeNode merge = (AbstractMergeNode) lookupNode(phiNodeScope, mergeOrderId);
             if (merge == null) {
                 merge = (AbstractMergeNode) makeStubNode(methodScope, phiNodeScope, mergeOrderId);
@@ -332,7 +368,8 @@
             handlePhiFunctions(methodScope, phiInputScope, phiNodeScope, (AbstractEndNode) node, merge);
 
         } else if (node instanceof Invoke) {
-            simplifyInvoke(methodScope, loopScope, nodeOrderId, (Invoke) node);
+            InvokeData invokeData = readInvokeData(methodScope, nodeOrderId, (Invoke) node);
+            handleInvoke(methodScope, loopScope, invokeData);
 
         } else if (node instanceof ReturnNode) {
             methodScope.returnNodes.add((ReturnNode) node);
@@ -347,15 +384,45 @@
         return resultScope;
     }
 
+    private InvokeData readInvokeData(MethodScope methodScope, int invokeOrderId, Invoke invoke) {
+        ResolvedJavaType contextType = (ResolvedJavaType) readObject(methodScope);
+        int callTargetOrderId = readOrderId(methodScope);
+        int stateAfterOrderId = readOrderId(methodScope);
+        int nextOrderId = readOrderId(methodScope);
+
+        if (invoke instanceof InvokeWithExceptionNode) {
+            int nextNextOrderId = readOrderId(methodScope);
+            int exceptionOrderId = readOrderId(methodScope);
+            int exceptionStateOrderId = readOrderId(methodScope);
+            int exceptionNextOrderId = readOrderId(methodScope);
+            return new InvokeData(invoke, contextType, invokeOrderId, callTargetOrderId, stateAfterOrderId, nextOrderId, nextNextOrderId, exceptionOrderId, exceptionStateOrderId, exceptionNextOrderId);
+        } else {
+            return new InvokeData(invoke, contextType, invokeOrderId, callTargetOrderId, stateAfterOrderId, nextOrderId, -1, -1, -1, -1);
+        }
+    }
+
     /**
-     * Hook for subclasses.
-     *
-     * @param methodScope The current method.
-     * @param loopScope The current loop.
-     * @param invokeOrderId The orderId of the method invocation node.
-     * @param invoke The invocation node.
+     * {@link Invoke} nodes do not have the {@link CallTargetNode}, {@link FrameState}, and
+     * successors encoded. Instead, this information is provided separately to allow method inlining
+     * without decoding and adding them to the graph upfront. For non-inlined methods, this method
+     * restores the normal state. Subclasses can override it to perform method inlining.
      */
-    protected void simplifyInvoke(MethodScope methodScope, LoopScope loopScope, int invokeOrderId, Invoke invoke) {
+    protected void handleInvoke(MethodScope methodScope, LoopScope loopScope, InvokeData invokeData) {
+        assert invokeData.invoke.callTarget() == null : "callTarget edge is ignored during decoding of Invoke";
+        CallTargetNode callTarget = (CallTargetNode) ensureNodeCreated(methodScope, loopScope, invokeData.callTargetOrderId);
+        if (invokeData.invoke instanceof InvokeWithExceptionNode) {
+            ((InvokeWithExceptionNode) invokeData.invoke).setCallTarget(callTarget);
+        } else {
+            ((InvokeNode) invokeData.invoke).setCallTarget(callTarget);
+        }
+
+        assert invokeData.invoke.stateAfter() == null && invokeData.invoke.stateDuring() == null : "FrameState edges are ignored during decoding of Invoke";
+        invokeData.invoke.setStateAfter((FrameState) ensureNodeCreated(methodScope, loopScope, invokeData.stateAfterOrderId));
+
+        invokeData.invoke.setNext(makeStubNode(methodScope, loopScope, invokeData.nextOrderId));
+        if (invokeData.invoke instanceof InvokeWithExceptionNode) {
+            ((InvokeWithExceptionNode) invokeData.invoke).setExceptionEdge((AbstractBeginNode) makeStubNode(methodScope, loopScope, invokeData.exceptionOrderId));
+        }
     }
 
     protected void handleLoopExplosionBegin(MethodScope methodScope, LoopScope loopScope, LoopBeginNode loopBegin) {
@@ -432,10 +499,14 @@
     protected void simplifyFixedNode(MethodScope methodScope, LoopScope loopScope, int nodeOrderId, FixedNode node) {
     }
 
-    protected void handleProxyNodes(MethodScope methodScope, LoopScope loopScope) {
+    protected void handleProxyNodes(MethodScope methodScope, LoopScope loopScope, LoopExitNode loopExit) {
+        assert loopExit.stateAfter() == null;
+        int stateAfterOrderId = readOrderId(methodScope);
+        loopExit.setStateAfter((FrameState) ensureNodeCreated(methodScope, loopScope, stateAfterOrderId));
+
         int numProxies = methodScope.reader.getUVInt();
         for (int i = 0; i < numProxies; i++) {
-            int proxyOrderId = methodScope.reader.getUVInt();
+            int proxyOrderId = readOrderId(methodScope);
             ProxyNode proxy = (ProxyNode) ensureNodeCreated(methodScope, loopScope, proxyOrderId);
             /*
              * The ProxyNode transports a value from the loop to the outer scope. We therefore
@@ -446,9 +517,11 @@
     }
 
     protected void handleLoopExplosionProxyNodes(MethodScope methodScope, LoopScope loopScope, LoopExitNode loopExit, int loopExitOrderId) {
+        assert loopExit.stateAfter() == null;
+        int stateAfterOrderId = readOrderId(methodScope);
+
         BeginNode begin = methodScope.graph.add(new BeginNode());
 
-        FrameState loopExitState = loopExit.stateAfter();
         FixedNode loopExitSuccessor = loopExit.next();
         loopExit.replaceAtPredecessor(begin);
 
@@ -457,33 +530,43 @@
          * iterations now take the same loop exit, so we have to introduce a new merge node to
          * handle the merge.
          */
-        MergeNode merge;
-        if (lookupNode(loopScope.outer, loopExitOrderId) == null) {
+        MergeNode merge = null;
+        Node existingExit = lookupNode(loopScope.outer, loopExitOrderId);
+        if (existingExit == null) {
+            /* First loop iteration that exits. No merge necessary yet. */
+            registerNode(loopScope.outer, loopExitOrderId, begin, false, false);
+            begin.setNext(loopExitSuccessor);
+
+        } else if (existingExit instanceof BeginNode) {
+            /* Second loop iteration that exits. Create the merge. */
             merge = methodScope.graph.add(new MergeNode());
-            registerNode(loopScope.outer, loopExitOrderId, merge, false, false);
+            registerNode(loopScope.outer, loopExitOrderId, merge, true, false);
+            /* Add the first iteration. */
+            EndNode firstEnd = methodScope.graph.add(new EndNode());
+            ((BeginNode) existingExit).setNext(firstEnd);
+            merge.addForwardEnd(firstEnd);
             merge.setNext(loopExitSuccessor);
+
         } else {
-            merge = (MergeNode) loopScope.outer.createdNodes[loopExitOrderId];
+            /* Subsequent loop iteration. Merge already created. */
+            merge = (MergeNode) existingExit;
         }
 
-        FrameState oldStateAfter = merge.stateAfter();
-        merge.setStateAfter(loopExitState);
-        if (oldStateAfter != null) {
-            oldStateAfter.safeDelete();
+        if (merge != null) {
+            EndNode end = methodScope.graph.add(new EndNode());
+            begin.setNext(end);
+            merge.addForwardEnd(end);
         }
 
-        EndNode end = methodScope.graph.add(new EndNode());
-        begin.setNext(end);
-        merge.addForwardEnd(end);
-
         /*
          * Possibly create phi nodes for the original proxy nodes that flow out of the loop. Note
          * that we definitely do not need a proxy node itself anymore, since the loop was exploded
          * and is no longer present.
          */
         int numProxies = methodScope.reader.getUVInt();
+        boolean phiCreated = false;
         for (int i = 0; i < numProxies; i++) {
-            int proxyOrderId = methodScope.reader.getUVInt();
+            int proxyOrderId = readOrderId(methodScope);
             ProxyNode proxy = (ProxyNode) ensureNodeCreated(methodScope, loopScope, proxyOrderId);
             ValueNode phiInput = proxy.value();
             ValueNode replacement;
@@ -508,6 +591,7 @@
                 phi.addInput(phiInput);
                 registerNode(loopScope.outer, proxyOrderId, phi, true, false);
                 replacement = phi;
+                phiCreated = true;
 
             } else {
                 /* Phi node has been created before, so just add the new input. */
@@ -519,9 +603,22 @@
             methodScope.graph.replaceFloating(proxy, replacement);
         }
 
+        if (merge != null && (merge.stateAfter() == null || phiCreated)) {
+            FrameState oldStateAfter = merge.stateAfter();
+            registerNode(loopScope.outer, stateAfterOrderId, null, true, true);
+            merge.setStateAfter((FrameState) ensureNodeCreated(methodScope, loopScope.outer, stateAfterOrderId));
+            if (oldStateAfter != null) {
+                oldStateAfter.safeDelete();
+            }
+        }
+
         loopExit.safeDelete();
         assert loopExitSuccessor.predecessor() == null;
-        merge.getNodeClass().getSuccessorEdges().update(merge, null, loopExitSuccessor);
+        if (merge != null) {
+            merge.getNodeClass().getSuccessorEdges().update(merge, null, loopExitSuccessor);
+        } else {
+            begin.getNodeClass().getSuccessorEdges().update(begin, null, loopExitSuccessor);
+        }
     }
 
     protected void handlePhiFunctions(MethodScope methodScope, LoopScope phiInputScope, LoopScope phiNodeScope, AbstractEndNode end, AbstractMergeNode merge) {
@@ -555,8 +652,8 @@
         boolean lazyPhi = !(merge instanceof LoopBeginNode) || methodScope.loopExplosion != LoopExplosionKind.NONE;
         int numPhis = methodScope.reader.getUVInt();
         for (int i = 0; i < numPhis; i++) {
-            int phiInputOrderId = methodScope.reader.getUVInt();
-            int phiNodeOrderId = methodScope.reader.getUVInt();
+            int phiInputOrderId = readOrderId(methodScope);
+            int phiNodeOrderId = readOrderId(methodScope);
 
             ValueNode phiInput = (ValueNode) ensureNodeCreated(methodScope, phiInputScope, phiInputOrderId);
 
@@ -588,7 +685,7 @@
     }
 
     protected Node instantiateNode(MethodScope methodScope, int nodeOrderId) {
-        methodScope.reader.setByteIndex(methodScope.nodeStartOffsets[nodeOrderId]);
+        methodScope.reader.setByteIndex(methodScope.encodedGraph.nodeStartOffsets[nodeOrderId]);
         NodeClass<?> nodeClass = methodScope.encodedGraph.getNodeClasses()[methodScope.reader.getUVInt()];
         return nodeClass.allocateInstance();
     }
@@ -600,8 +697,7 @@
                 long primitive = methodScope.reader.getSV();
                 fields.setRawPrimitive(node, pos, primitive);
             } else {
-                int objectId = methodScope.reader.getUVInt();
-                Object value = methodScope.encodedGraph.getObjects()[objectId];
+                Object value = readObject(methodScope);
                 fields.set(node, pos, value);
             }
         }
@@ -619,7 +715,7 @@
             if (skipEdge(node, edges, index, true, true)) {
                 continue;
             }
-            int orderId = methodScope.reader.getUVInt();
+            int orderId = readOrderId(methodScope);
             Node value = ensureNodeCreated(methodScope, loopScope, orderId);
             Edges.initializeNode(node, edges.getOffsets(), index, value);
             if (updateUsages && value != null && !value.isDeleted()) {
@@ -636,7 +732,7 @@
                 NodeList<Node> nodeList = new NodeInputList<>(node, size);
                 Edges.initializeList(node, edges.getOffsets(), index, nodeList);
                 for (int idx = 0; idx < size; idx++) {
-                    int orderId = methodScope.reader.getUVInt();
+                    int orderId = readOrderId(methodScope);
                     Node value = ensureNodeCreated(methodScope, loopScope, orderId);
                     nodeList.initialize(idx, value);
                     if (updateUsages && value != null && !value.isDeleted()) {
@@ -656,17 +752,7 @@
             return node;
         }
 
-        long readerByteIndex = methodScope.reader.getByteIndex();
-        node = instantiateNode(methodScope, nodeOrderId);
-        assert !(node instanceof FixedNode);
-
-        /* Read the properties of the node. */
-        readProperties(methodScope, node);
-        /* There must not be any successors to read, since it is a non-fixed node. */
-        assert node.getNodeClass().getEdges(Edges.Type.Successors).getCount() == 0;
-        /* Read the inputs of the node, possibly creating them recursively. */
-        makeInputNodes(methodScope, loopScope, node, false);
-        methodScope.reader.setByteIndex(readerByteIndex);
+        node = decodeFloatingNode(methodScope, loopScope, nodeOrderId);
 
         if (node instanceof ProxyNode || node instanceof PhiNode) {
             /*
@@ -688,6 +774,24 @@
     }
 
     /**
+     * Decodes a non-fixed node, but does not do any post-processing and does not register it.
+     */
+    protected Node decodeFloatingNode(MethodScope methodScope, LoopScope loopScope, int nodeOrderId) {
+        long readerByteIndex = methodScope.reader.getByteIndex();
+        Node node = instantiateNode(methodScope, nodeOrderId);
+        assert !(node instanceof FixedNode);
+
+        /* Read the properties of the node. */
+        readProperties(methodScope, node);
+        /* There must not be any successors to read, since it is a non-fixed node. */
+        assert node.getNodeClass().getEdges(Edges.Type.Successors).getCount() == 0;
+        /* Read the inputs of the node, possibly creating them recursively. */
+        makeInputNodes(methodScope, loopScope, node, false);
+        methodScope.reader.setByteIndex(readerByteIndex);
+        return node;
+    }
+
+    /**
      * Hook for subclasses to process a non-fixed node before it is added to the graph.
      *
      * @param methodScope The current method.
@@ -722,7 +826,7 @@
             if (skipEdge(node, edges, index, true, true)) {
                 continue;
             }
-            int orderId = methodScope.reader.getUVInt();
+            int orderId = readOrderId(methodScope);
             Node value = makeStubNode(methodScope, loopScope, orderId);
             Edges.initializeNode(node, edges.getOffsets(), index, value);
             if (updatePredecessors && value != null) {
@@ -738,7 +842,7 @@
                 NodeList<Node> nodeList = new NodeSuccessorList<>(node, size);
                 Edges.initializeList(node, edges.getOffsets(), index, nodeList);
                 for (int idx = 0; idx < size; idx++) {
-                    int orderId = methodScope.reader.getUVInt();
+                    int orderId = readOrderId(methodScope);
                     Node value = makeStubNode(methodScope, loopScope, orderId);
                     nodeList.initialize(idx, value);
                     if (updatePredecessors && value != null) {
@@ -792,6 +896,26 @@
             assert index == edges.getCount() - 1 : "MergeNode has one variable size input (the ends)";
             assert Edges.getNodeList(node, edges.getOffsets(), index) != null : "Input list must have been already created";
             return true;
+
+        } else if (node instanceof LoopExitNode && edges.type() == Edges.Type.Inputs && edges.getType(index) == FrameState.class) {
+            /* The stateAfter of the loop exit is filled manually. */
+            return true;
+
+        } else if (node instanceof Invoke) {
+            assert node instanceof InvokeNode || node instanceof InvokeWithExceptionNode : "The only two Invoke node classes";
+            assert direct : "Invoke and InvokeWithException only have direct successor and input edges";
+            if (edges.type() == Edges.Type.Successors) {
+                assert edges.getCount() == (node instanceof InvokeWithExceptionNode ? 2 : 1) : "InvokeNode has one successor (next); InvokeWithExceptionNode has two successors (next, exceptionEdge)";
+                return true;
+            } else {
+                assert edges.type() == Edges.Type.Inputs;
+                if (edges.getType(index) == CallTargetNode.class) {
+                    return true;
+                } else if (edges.getType(index) == FrameState.class) {
+                    assert edges.get(node, index) == null || edges.get(node, index) == ((Invoke) node).stateAfter() : "Only stateAfter can be a FrameState during encoding";
+                    return true;
+                }
+            }
         }
         return false;
     }
@@ -807,6 +931,14 @@
         loopScope.createdNodes[nodeOrderId] = node;
     }
 
+    protected int readOrderId(MethodScope methodScope) {
+        return methodScope.reader.getUVInt();
+    }
+
+    protected Object readObject(MethodScope methodScope) {
+        return methodScope.encodedGraph.getObjects()[methodScope.reader.getUVInt()];
+    }
+
     /*
      * The following methods are a literal copy from GraphBuilderPhase.
      */
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/GraphEncoder.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/GraphEncoder.java	Mon Apr 13 21:51:19 2015 +0200
@@ -29,6 +29,7 @@
 import com.oracle.graal.graph.*;
 import com.oracle.graal.graph.iterators.*;
 import com.oracle.graal.nodes.StructuredGraph.AllowAssumptions;
+import com.oracle.graal.nodes.java.*;
 
 /**
  * Encodes a {@link StructuredGraph} to a compact byte[] array. All nodes of the graph and edges
@@ -100,6 +101,12 @@
     public static final int FIRST_NODE_ORDER_ID = 2;
 
     /**
+     * The known offset between the orderId of a {@link AbstractBeginNode} and its
+     * {@link AbstractBeginNode#next() successor}.
+     */
+    protected static final int BEGIN_NEXT_ORDER_ID_OFFSET = 1;
+
+    /**
      * Collects all non-primitive data referenced from nodes. The encoding uses an index into an
      * array for decoding. Because of the variable-length encoding, it is beneficial that frequently
      * used objects have the small indices.
@@ -148,6 +155,9 @@
                     objects.addObject(nodeClass.getData().get(node, i));
                 }
             }
+            if (node instanceof Invoke) {
+                objects.addObject(((Invoke) node).getContextType());
+            }
         }
     }
 
@@ -182,7 +192,10 @@
         long[] nodeStartOffsets = new long[nodeCount];
         for (Map.Entry<Node, Integer> entry : nodeOrder.orderIds.entries()) {
             Node node = entry.getKey();
-            nodeStartOffsets[entry.getValue()] = writer.getBytesWritten();
+            Integer orderId = entry.getValue();
+
+            assert !(node instanceof AbstractBeginNode) || nodeOrder.orderIds.get(((AbstractBeginNode) node).next()) == orderId + BEGIN_NEXT_ORDER_ID_OFFSET;
+            nodeStartOffsets[orderId] = writer.getBytesWritten();
 
             /* Write out the type, properties, and edges. */
             NodeClass<?> nodeClass = node.getNodeClass();
@@ -213,11 +226,30 @@
 
             } else if (node instanceof LoopExitNode) {
                 LoopExitNode exit = (LoopExitNode) node;
+                writeOrderId(exit.stateAfter(), nodeOrder);
                 /* Write all proxy nodes of the LoopExitNode. */
                 writer.putUV(exit.proxies().count());
                 for (ProxyNode proxy : exit.proxies()) {
                     writeOrderId(proxy, nodeOrder);
                 }
+
+            } else if (node instanceof Invoke) {
+                Invoke invoke = (Invoke) node;
+                assert invoke.stateDuring() == null : "stateDuring is not used in high-level graphs";
+
+                writeObjectId(invoke.getContextType());
+                writeOrderId(invoke.callTarget(), nodeOrder);
+                writeOrderId(invoke.stateAfter(), nodeOrder);
+                writeOrderId(invoke.next(), nodeOrder);
+                if (invoke instanceof InvokeWithExceptionNode) {
+                    InvokeWithExceptionNode invokeWithExcpetion = (InvokeWithExceptionNode) invoke;
+                    ExceptionObjectNode exceptionEdge = (ExceptionObjectNode) invokeWithExcpetion.exceptionEdge();
+
+                    writeOrderId(invokeWithExcpetion.next().next(), nodeOrder);
+                    writeOrderId(invokeWithExcpetion.exceptionEdge(), nodeOrder);
+                    writeOrderId(exceptionEdge.stateAfter(), nodeOrder);
+                    writeOrderId(exceptionEdge.next(), nodeOrder);
+                }
             }
         }
 
@@ -252,13 +284,8 @@
             FixedNode current = graph.start();
             do {
                 add(current);
-                if (current instanceof InvokeWithExceptionNode) {
-                    /*
-                     * Special handling for invokes: the orderID of the invocation, the regular
-                     * successor, and the exception edge must be consecutive.
-                     */
-                    add(((InvokeWithExceptionNode) current).next());
-                    add(((InvokeWithExceptionNode) current).exceptionEdge());
+                if (current instanceof AbstractBeginNode) {
+                    add(((AbstractBeginNode) current).next());
                 }
 
                 if (current instanceof FixedWithNextNode) {
@@ -308,7 +335,7 @@
                 writer.putSV(primitive);
             } else {
                 Object property = fields.get(node, idx);
-                writer.putUV(objects.getIndex(property));
+                writeObjectId(property);
             }
         }
     }
@@ -343,6 +370,10 @@
         writer.putUV(node == null ? NULL_ORDER_ID : nodeOrder.orderIds.get(node));
     }
 
+    protected void writeObjectId(Object object) {
+        writer.putUV(objects.getIndex(object));
+    }
+
     /**
      * Verification code that checks that the decoding of an encode graph is the same as the
      * original graph.
@@ -365,7 +396,7 @@
 
         pushToWorklist(expectedGraph.start(), actualGraph.start(), nodeMapping, workList);
         while (!workList.isEmpty()) {
-            Pair<Node, Node> pair = workList.pop();
+            Pair<Node, Node> pair = workList.removeFirst();
             Node expectedNode = pair.first;
             Node actualNode = pair.second;
             assert expectedNode.getClass() == actualNode.getClass();
@@ -377,7 +408,7 @@
                 /* The order of the ends can be different, so ignore them. */
                 verifyNodesEqual(expectedNode.inputs(), actualNode.inputs(), nodeMapping, workList, true);
             } else if (expectedNode instanceof PhiNode) {
-                /* The order of phi inputs can be different, so they are checked manually below. */
+                verifyPhi((PhiNode) expectedNode, (PhiNode) actualNode, nodeMapping, workList);
             } else {
                 verifyNodesEqual(expectedNode.inputs(), actualNode.inputs(), nodeMapping, workList, false);
             }
@@ -413,20 +444,34 @@
         return true;
     }
 
+    protected static void verifyPhi(PhiNode expectedPhi, PhiNode actualPhi, NodeMap<Node> nodeMapping, Deque<Pair<Node, Node>> workList) {
+        AbstractMergeNode expectedMergeNode = expectedPhi.merge();
+        AbstractMergeNode actualMergeNode = actualPhi.merge();
+        assert actualMergeNode == nodeMapping.get(expectedMergeNode);
+
+        for (EndNode expectedEndNode : expectedMergeNode.ends) {
+            EndNode actualEndNode = (EndNode) nodeMapping.get(expectedEndNode);
+            if (actualEndNode != null) {
+                ValueNode expectedPhiInput = expectedPhi.valueAt(expectedEndNode);
+                ValueNode actualPhiInput = actualPhi.valueAt(actualEndNode);
+                verifyNodeEqual(expectedPhiInput, actualPhiInput, nodeMapping, workList, false);
+            }
+        }
+    }
+
     protected static void verifyPhis(AbstractEndNode expectedEndNode, AbstractEndNode actualEndNode, NodeMap<Node> nodeMapping, Deque<Pair<Node, Node>> workList) {
         AbstractMergeNode expectedMergeNode = expectedEndNode.merge();
         AbstractMergeNode actualMergeNode = (AbstractMergeNode) nodeMapping.get(expectedMergeNode);
-
-        Iterator<PhiNode> actualPhis = actualMergeNode.phis().iterator();
-        for (PhiNode expectedPhi : expectedMergeNode.phis()) {
-            PhiNode actualPhi = actualPhis.next();
-            verifyNodeEqual(expectedPhi, actualPhi, nodeMapping, workList, false);
+        assert actualMergeNode != null;
 
-            ValueNode expectedPhiInput = expectedPhi.valueAt(expectedEndNode);
-            ValueNode actualPhiInput = actualPhi.valueAt(actualEndNode);
-            verifyNodeEqual(expectedPhiInput, actualPhiInput, nodeMapping, workList, false);
+        for (PhiNode expectedPhi : expectedMergeNode.phis()) {
+            PhiNode actualPhi = (PhiNode) nodeMapping.get(expectedPhi);
+            if (actualPhi != null) {
+                ValueNode expectedPhiInput = expectedPhi.valueAt(expectedEndNode);
+                ValueNode actualPhiInput = actualPhi.valueAt(actualEndNode);
+                verifyNodeEqual(expectedPhiInput, actualPhiInput, nodeMapping, workList, false);
+            }
         }
-        assert !actualPhis.hasNext();
     }
 
     private static void verifyNodesEqual(NodeIterable<Node> expectedNodes, NodeIterable<Node> actualNodes, NodeMap<Node> nodeMapping, Deque<Pair<Node, Node>> workList, boolean ignoreEndNode) {
@@ -453,7 +498,12 @@
 
     protected static void pushToWorklist(Node expectedNode, Node actualNode, NodeMap<Node> nodeMapping, Deque<Pair<Node, Node>> workList) {
         nodeMapping.set(expectedNode, actualNode);
-        workList.push(new Pair<>(expectedNode, actualNode));
+        if (expectedNode instanceof AbstractEndNode) {
+            /* To ensure phi nodes have been added, we handle everything before block ends. */
+            workList.addLast(new Pair<>(expectedNode, actualNode));
+        } else {
+            workList.addFirst(new Pair<>(expectedNode, actualNode));
+        }
     }
 }
 
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/GuardingPiNode.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/GuardingPiNode.java	Mon Apr 13 21:51:19 2015 +0200
@@ -107,7 +107,7 @@
 
     @Override
     public Node canonical(CanonicalizerTool tool) {
-        if (stamp() == StampFactory.illegal(object.getKind())) {
+        if (stamp().isIllegal()) {
             // The guard always fails
             return new DeoptimizeNode(action, reason);
         }
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/IfNode.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/IfNode.java	Mon Apr 13 21:51:19 2015 +0200
@@ -140,6 +140,15 @@
         return super.verify();
     }
 
+    public void eliminateNegation() {
+        AbstractBeginNode oldTrueSuccessor = trueSuccessor;
+        AbstractBeginNode oldFalseSuccessor = falseSuccessor;
+        trueSuccessor = oldFalseSuccessor;
+        falseSuccessor = oldTrueSuccessor;
+        trueSuccessorProbability = 1 - trueSuccessorProbability;
+        setCondition(((LogicNegationNode) condition).getValue());
+    }
+
     @Override
     public void simplify(SimplifierTool tool) {
         if (trueSuccessor().next() instanceof DeoptimizeNode) {
@@ -155,15 +164,7 @@
         }
 
         if (condition() instanceof LogicNegationNode) {
-            AbstractBeginNode trueSucc = trueSuccessor();
-            AbstractBeginNode falseSucc = falseSuccessor();
-            setTrueSuccessor(null);
-            setFalseSuccessor(null);
-            LogicNegationNode negation = (LogicNegationNode) condition();
-            IfNode newIfNode = graph().add(new IfNode(negation.getValue(), falseSucc, trueSucc, 1 - trueSuccessorProbability));
-            predecessor().replaceFirstSuccessor(this, newIfNode);
-            GraphUtil.killWithUnusedFloatingInputs(this);
-            return;
+            eliminateNegation();
         }
         if (condition() instanceof LogicConstantNode) {
             LogicConstantNode c = (LogicConstantNode) condition();
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/InvokeNode.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/InvokeNode.java	Mon Apr 13 21:51:19 2015 +0200
@@ -64,6 +64,11 @@
         return callTarget;
     }
 
+    void setCallTarget(CallTargetNode callTarget) {
+        updateUsages(this.callTarget, callTarget);
+        this.callTarget = callTarget;
+    }
+
     @Override
     public boolean isPolymorphic() {
         return polymorphic;
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/InvokeWithExceptionNode.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/InvokeWithExceptionNode.java	Mon Apr 13 21:51:19 2015 +0200
@@ -81,6 +81,11 @@
         return callTarget;
     }
 
+    void setCallTarget(CallTargetNode callTarget) {
+        updateUsages(this.callTarget, callTarget);
+        this.callTarget = callTarget;
+    }
+
     public MethodCallTargetNode methodCallTarget() {
         return (MethodCallTargetNode) callTarget;
     }
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/calc/FloatConvertNode.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/calc/FloatConvertNode.java	Mon Apr 13 21:51:19 2015 +0200
@@ -22,6 +22,8 @@
  */
 package com.oracle.graal.nodes.calc;
 
+import java.util.*;
+
 import com.oracle.graal.api.meta.*;
 import com.oracle.graal.compiler.common.calc.*;
 import com.oracle.graal.compiler.common.type.*;
@@ -43,8 +45,16 @@
 
     protected final FloatConvert op;
 
+    private static final EnumMap<FloatConvert, SerializableUnaryFunction<FloatConvertOp>> getOps;
+    static {
+        getOps = new EnumMap<>(FloatConvert.class);
+        for (FloatConvert op : FloatConvert.values()) {
+            getOps.put(op, table -> table.getFloatConvert(op));
+        }
+    }
+
     public FloatConvertNode(FloatConvert op, ValueNode input) {
-        super(TYPE, table -> table.getFloatConvert(op), input);
+        super(TYPE, getOps.get(op), input);
         this.op = op;
     }
 
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/java/CheckCastNode.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/java/CheckCastNode.java	Mon Apr 13 21:51:19 2015 +0200
@@ -116,7 +116,7 @@
         Stamp newStamp = StampFactory.declaredTrusted(type).improveWith(object().stamp());
         ValueNode condition;
         ValueNode theValue = object;
-        if (newStamp instanceof IllegalStamp) {
+        if (newStamp.isIllegal()) {
             // This is a check cast that will always fail
             condition = LogicConstantNode.contradiction(graph());
             newStamp = StampFactory.declaredTrusted(type);
--- a/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/util/GraphUtil.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/util/GraphUtil.java	Mon Apr 13 21:51:19 2015 +0200
@@ -150,11 +150,11 @@
 
     public static void killWithUnusedFloatingInputs(Node node) {
         node.safeDelete();
-        for (Node in : node.inputs()) {
+        node.acceptInputs((n, in) -> {
             if (in.isAlive() && in.hasNoUsages() && !(in instanceof FixedNode)) {
                 killWithUnusedFloatingInputs(in);
             }
-        }
+        });
     }
 
     public static void removeFixedWithUnusedInputs(FixedWithNextNode fixed) {
--- a/graal/com.oracle.graal.phases.common/src/com/oracle/graal/phases/common/inlining/InliningUtil.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.phases.common/src/com/oracle/graal/phases/common/inlining/InliningUtil.java	Mon Apr 13 21:51:19 2015 +0200
@@ -306,7 +306,7 @@
             if (callerLockDepth != 0) {
                 for (MonitorIdNode original : inlineGraph.getNodes(MonitorIdNode.TYPE)) {
                     MonitorIdNode monitor = (MonitorIdNode) duplicates.get(original);
-                    processMonitorId(invoke, monitor);
+                    processMonitorId(invoke.stateAfter(), monitor);
                 }
             }
         } else {
@@ -432,8 +432,7 @@
         return pos;
     }
 
-    public static void processMonitorId(Invoke invoke, MonitorIdNode monitorIdNode) {
-        FrameState stateAfter = invoke.stateAfter();
+    public static void processMonitorId(FrameState stateAfter, MonitorIdNode monitorIdNode) {
         if (stateAfter != null) {
             int callerLockDepth = stateAfter.nestedLockDepth();
             monitorIdNode.setLockDepth(monitorIdNode.getLockDepth() + callerLockDepth);
@@ -579,26 +578,43 @@
     }
 
     public static ValueNode mergeReturns(AbstractMergeNode merge, List<? extends ReturnNode> returnNodes, List<Node> canonicalizedNodes) {
+        ValueNode singleReturnValue = null;
         PhiNode returnValuePhi = null;
-
         for (ReturnNode returnNode : returnNodes) {
-            // create and wire up a new EndNode
-            EndNode endNode = merge.graph().add(new EndNode());
-            merge.addForwardEnd(endNode);
+            if (returnNode.result() != null) {
+                if (returnValuePhi == null && (singleReturnValue == null || singleReturnValue == returnNode.result())) {
+                    /* Only one return value, so no need yet for a phi node. */
+                    singleReturnValue = returnNode.result();
 
-            if (returnNode.result() != null) {
-                if (returnValuePhi == null) {
+                } else if (returnValuePhi == null) {
+                    /* Found a second return value, so create phi node. */
                     returnValuePhi = merge.graph().addWithoutUnique(new ValuePhiNode(returnNode.result().stamp().unrestricted(), merge));
                     if (canonicalizedNodes != null) {
                         canonicalizedNodes.add(returnValuePhi);
                     }
+                    for (int i = 0; i < merge.forwardEndCount(); i++) {
+                        returnValuePhi.addInput(singleReturnValue);
+                    }
+                    returnValuePhi.addInput(returnNode.result());
+
+                } else {
+                    /* Multiple return values, just add to existing phi node. */
+                    returnValuePhi.addInput(returnNode.result());
                 }
-                returnValuePhi.addInput(returnNode.result());
             }
+
+            // create and wire up a new EndNode
+            EndNode endNode = merge.graph().add(new EndNode());
+            merge.addForwardEnd(endNode);
             returnNode.replaceAndDelete(endNode);
-
         }
-        return returnValuePhi;
+
+        if (returnValuePhi != null) {
+            assert returnValuePhi.verify();
+            return returnValuePhi;
+        } else {
+            return singleReturnValue;
+        }
     }
 
     private static boolean checkContainsOnlyInvalidOrAfterFrameState(Map<Node, Node> duplicates) {
--- a/graal/com.oracle.graal.phases/src/com/oracle/graal/phases/graph/InferStamps.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.phases/src/com/oracle/graal/phases/graph/InferStamps.java	Mon Apr 13 21:51:19 2015 +0200
@@ -88,7 +88,7 @@
         for (Node n : graph.getNodes()) {
             if (n instanceof ValuePhiNode) {
                 ValueNode node = (ValueNode) n;
-                assert !(node.stamp() instanceof IllegalStamp) : "Stamp is illegal after analysis. This is not necessarily an error, but a condition that we want to investigate (and then maybe relax or remove the assertion).";
+                assert node.stamp().isLegal() : "Stamp is illegal after analysis. This is not necessarily an error, but a condition that we want to investigate (and then maybe relax or remove the assertion).";
             }
         }
         return true;
--- a/graal/com.oracle.graal.truffle.test/src/com/oracle/graal/truffle/test/InstrumentationPartialEvaluationTest.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.truffle.test/src/com/oracle/graal/truffle/test/InstrumentationPartialEvaluationTest.java	Mon Apr 13 21:51:19 2015 +0200
@@ -67,7 +67,7 @@
         RootTestNode root = new RootTestNode(fd, "constantValue", result);
         root.adoptChildren();
         Probe probe = result.probe();
-        Instrument instrument = Instrument.create(new DefaultInstrumentListener(), "Null test Instrument");
+        Instrument instrument = Instrument.create(new DefaultSimpleInstrumentListener(), "Null test Instrument");
         probe.attach(instrument);
         assertPartialEvalEquals("constant42", root);
     }
@@ -79,7 +79,7 @@
         RootTestNode root = new RootTestNode(fd, "constantValue", result);
         root.adoptChildren();
         Probe probe = result.probe();
-        Instrument instrument = Instrument.create(new DefaultASTInstrumentListener(), "Null test Instrument");
+        Instrument instrument = Instrument.create(new DefaultStandardInstrumentListener(), "Null test Instrument");
         probe.attach(instrument);
         assertPartialEvalEquals("constant42", root);
     }
@@ -91,7 +91,7 @@
         RootTestNode root = new RootTestNode(fd, "constantValue", result);
         root.adoptChildren();
         Probe probe = result.probe();
-        Instrument instrument = Instrument.create(new DefaultInstrumentListener(), "Null test Instrument");
+        Instrument instrument = Instrument.create(new DefaultSimpleInstrumentListener(), "Null test Instrument");
         probe.attach(instrument);
         instrument.dispose();
         assertPartialEvalEquals("constant42", root);
@@ -104,7 +104,7 @@
         RootTestNode root = new RootTestNode(fd, "constantValue", result);
         root.adoptChildren();
         Probe probe = result.probe();
-        Instrument instrument = Instrument.create(new DefaultASTInstrumentListener(), "Null test Instrument");
+        Instrument instrument = Instrument.create(new DefaultStandardInstrumentListener(), "Null test Instrument");
         probe.attach(instrument);
         instrument.dispose();
         assertPartialEvalEquals("constant42", root);
@@ -117,9 +117,9 @@
         RootTestNode root = new RootTestNode(fd, "constantValue", result);
         root.adoptChildren();
         Probe probe = result.probe();
-        Instrument instrument1 = Instrument.create(new DefaultInstrumentListener(), "Null test Instrument 1");
+        Instrument instrument1 = Instrument.create(new DefaultSimpleInstrumentListener(), "Null test Instrument 1");
         probe.attach(instrument1);
-        Instrument instrument2 = Instrument.create(new DefaultInstrumentListener(), "Null test Instrument 2");
+        Instrument instrument2 = Instrument.create(new DefaultSimpleInstrumentListener(), "Null test Instrument 2");
         probe.attach(instrument2);
         assertPartialEvalEquals("constant42", root);
     }
@@ -131,9 +131,9 @@
         RootTestNode root = new RootTestNode(fd, "constantValue", result);
         root.adoptChildren();
         Probe probe = result.probe();
-        Instrument instrument1 = Instrument.create(new DefaultASTInstrumentListener(), "Null test Instrument 1");
+        Instrument instrument1 = Instrument.create(new DefaultStandardInstrumentListener(), "Null test Instrument 1");
         probe.attach(instrument1);
-        Instrument instrument2 = Instrument.create(new DefaultASTInstrumentListener(), "Null test Instrument 2");
+        Instrument instrument2 = Instrument.create(new DefaultStandardInstrumentListener(), "Null test Instrument 2");
         probe.attach(instrument2);
         assertPartialEvalEquals("constant42", root);
     }
@@ -145,11 +145,11 @@
         RootTestNode root = new RootTestNode(fd, "constantValue", result);
         root.adoptChildren();
         Probe probe = result.probe();
-        Instrument instrument1 = Instrument.create(new DefaultInstrumentListener(), "Null test Instrument 1");
+        Instrument instrument1 = Instrument.create(new DefaultSimpleInstrumentListener(), "Null test Instrument 1");
         probe.attach(instrument1);
-        Instrument instrument2 = Instrument.create(new DefaultInstrumentListener(), "Null test Instrument 2");
+        Instrument instrument2 = Instrument.create(new DefaultSimpleInstrumentListener(), "Null test Instrument 2");
         probe.attach(instrument2);
-        Instrument instrument3 = Instrument.create(new DefaultInstrumentListener(), "Null test Instrument 3");
+        Instrument instrument3 = Instrument.create(new DefaultSimpleInstrumentListener(), "Null test Instrument 3");
         probe.attach(instrument3);
         assertPartialEvalEquals("constant42", root);
     }
@@ -161,11 +161,11 @@
         RootTestNode root = new RootTestNode(fd, "constantValue", result);
         root.adoptChildren();
         Probe probe = result.probe();
-        Instrument instrument1 = Instrument.create(new DefaultASTInstrumentListener(), "Null test Instrument 1");
+        Instrument instrument1 = Instrument.create(new DefaultStandardInstrumentListener(), "Null test Instrument 1");
         probe.attach(instrument1);
-        Instrument instrument2 = Instrument.create(new DefaultASTInstrumentListener(), "Null test Instrument 2");
+        Instrument instrument2 = Instrument.create(new DefaultStandardInstrumentListener(), "Null test Instrument 2");
         probe.attach(instrument2);
-        Instrument instrument3 = Instrument.create(new DefaultASTInstrumentListener(), "Null test Instrument 3");
+        Instrument instrument3 = Instrument.create(new DefaultStandardInstrumentListener(), "Null test Instrument 3");
         probe.attach(instrument3);
         assertPartialEvalEquals("constant42", root);
     }
@@ -177,11 +177,11 @@
         RootTestNode root = new RootTestNode(fd, "constantValue", result);
         root.adoptChildren();
         Probe probe = result.probe();
-        Instrument instrument1 = Instrument.create(new DefaultInstrumentListener(), "Null test Instrument 1");
+        Instrument instrument1 = Instrument.create(new DefaultSimpleInstrumentListener(), "Null test Instrument 1");
         probe.attach(instrument1);
-        Instrument instrument2 = Instrument.create(new DefaultInstrumentListener(), "Null test Instrument 2");
+        Instrument instrument2 = Instrument.create(new DefaultSimpleInstrumentListener(), "Null test Instrument 2");
         probe.attach(instrument2);
-        Instrument instrument3 = Instrument.create(new DefaultInstrumentListener(), "Null test Instrument 3");
+        Instrument instrument3 = Instrument.create(new DefaultSimpleInstrumentListener(), "Null test Instrument 3");
         probe.attach(instrument3);
         instrument2.dispose();
         assertPartialEvalEquals("constant42", root);
@@ -194,11 +194,11 @@
         RootTestNode root = new RootTestNode(fd, "constantValue", result);
         root.adoptChildren();
         Probe probe = result.probe();
-        Instrument instrument1 = Instrument.create(new DefaultASTInstrumentListener(), "Null test Instrument 1");
+        Instrument instrument1 = Instrument.create(new DefaultStandardInstrumentListener(), "Null test Instrument 1");
         probe.attach(instrument1);
-        Instrument instrument2 = Instrument.create(new DefaultASTInstrumentListener(), "Null test Instrument 2");
+        Instrument instrument2 = Instrument.create(new DefaultStandardInstrumentListener(), "Null test Instrument 2");
         probe.attach(instrument2);
-        Instrument instrument3 = Instrument.create(new DefaultASTInstrumentListener(), "Null test Instrument 3");
+        Instrument instrument3 = Instrument.create(new DefaultStandardInstrumentListener(), "Null test Instrument 3");
         probe.attach(instrument3);
         instrument2.dispose();
         assertPartialEvalEquals("constant42", root);
@@ -280,7 +280,7 @@
             Assert.assertEquals(0, count[0]);           // Didn't count anything
 
             // Add a counting instrument; this changes the "Probe state" and should cause a deopt
-            final Instrument countingInstrument = Instrument.create(new DefaultInstrumentListener() {
+            final Instrument countingInstrument = Instrument.create(new DefaultSimpleInstrumentListener() {
 
                 @Override
                 public void enter(Probe p) {
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/PEGraphDecoder.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/PEGraphDecoder.java	Mon Apr 13 21:51:19 2015 +0200
@@ -29,13 +29,14 @@
 
 import com.oracle.graal.api.code.*;
 import com.oracle.graal.api.meta.*;
-import com.oracle.graal.debug.*;
+import com.oracle.graal.compiler.common.type.*;
 import com.oracle.graal.graph.*;
 import com.oracle.graal.graph.spi.*;
 import com.oracle.graal.graphbuilderconf.*;
 import com.oracle.graal.graphbuilderconf.InlineInvokePlugin.InlineInfo;
 import com.oracle.graal.graphbuilderconf.InvocationPlugins.InvocationPluginReceiver;
 import com.oracle.graal.java.*;
+import com.oracle.graal.nodeinfo.*;
 import com.oracle.graal.nodes.*;
 import com.oracle.graal.nodes.CallTargetNode.InvokeKind;
 import com.oracle.graal.nodes.extended.*;
@@ -63,8 +64,11 @@
     protected final StampProvider stampProvider;
 
     protected class PEMethodScope extends MethodScope {
+        /** The state of the caller method. Only non-null during method inlining. */
+        protected final PEMethodScope caller;
+        protected final LoopScope callerLoopScope;
         protected final ResolvedJavaMethod method;
-        protected final Invoke invoke;
+        protected final InvokeData invokeData;
         protected final int inliningDepth;
 
         protected final LoopExplosionPlugin loopExplosionPlugin;
@@ -73,15 +77,20 @@
         protected final ParameterPlugin parameterPlugin;
         protected final ValueNode[] arguments;
 
-        protected FrameState outerFrameState;
+        protected FrameState outerState;
+        protected FrameState exceptionState;
+        protected ExceptionPlaceholderNode exceptionPlaceholderNode;
         protected BytecodePosition bytecodePosition;
 
-        protected PEMethodScope(StructuredGraph targetGraph, MethodScope caller, EncodedGraph encodedGraph, ResolvedJavaMethod method, Invoke invoke, int inliningDepth,
-                        LoopExplosionPlugin loopExplosionPlugin, InvocationPlugins invocationPlugins, InlineInvokePlugin inlineInvokePlugin, ParameterPlugin parameterPlugin, ValueNode[] arguments) {
-            super(targetGraph, caller, encodedGraph, loopExplosionKind(method, loopExplosionPlugin));
+        protected PEMethodScope(StructuredGraph targetGraph, PEMethodScope caller, LoopScope callerLoopScope, EncodedGraph encodedGraph, ResolvedJavaMethod method, InvokeData invokeData,
+                        int inliningDepth, LoopExplosionPlugin loopExplosionPlugin, InvocationPlugins invocationPlugins, InlineInvokePlugin inlineInvokePlugin, ParameterPlugin parameterPlugin,
+                        ValueNode[] arguments) {
+            super(targetGraph, encodedGraph, loopExplosionKind(method, loopExplosionPlugin));
 
+            this.caller = caller;
+            this.callerLoopScope = callerLoopScope;
             this.method = method;
-            this.invoke = invoke;
+            this.invokeData = invokeData;
             this.inliningDepth = inliningDepth;
             this.loopExplosionPlugin = loopExplosionPlugin;
             this.invocationPlugins = invocationPlugins;
@@ -89,6 +98,10 @@
             this.parameterPlugin = parameterPlugin;
             this.arguments = arguments;
         }
+
+        public boolean isInlinedMethod() {
+            return caller != null;
+        }
     }
 
     protected class PECanonicalizerTool implements CanonicalizerTool {
@@ -114,7 +127,7 @@
     }
 
     protected class PENonAppendGraphBuilderContext implements GraphBuilderContext {
-        private final PEMethodScope methodScope;
+        protected final PEMethodScope methodScope;
 
         public PENonAppendGraphBuilderContext(PEMethodScope methodScope) {
             this.methodScope = methodScope;
@@ -212,13 +225,11 @@
     }
 
     protected class PEAppendGraphBuilderContext extends PENonAppendGraphBuilderContext {
-        protected final Invoke invoke;
         protected FixedWithNextNode lastInstr;
         protected ValueNode pushedNode;
 
-        public PEAppendGraphBuilderContext(PEMethodScope methodScope, Invoke invoke, FixedWithNextNode lastInstr) {
+        public PEAppendGraphBuilderContext(PEMethodScope methodScope, FixedWithNextNode lastInstr) {
             super(methodScope);
-            this.invoke = invoke;
             this.lastInstr = lastInstr;
         }
 
@@ -232,7 +243,9 @@
 
         @Override
         public FrameState createStateAfter() {
-            return invoke.stateAfter().duplicate();
+            Node stateAfter = decodeFloatingNode(methodScope.caller, methodScope.callerLoopScope, methodScope.invokeData.stateAfterOrderId);
+            getGraph().add(stateAfter);
+            return (FrameState) handleFloatingNodeAfterAdd(methodScope.caller, methodScope.callerLoopScope, stateAfter);
         }
 
         @Override
@@ -274,6 +287,15 @@
         }
     }
 
+    @NodeInfo
+    static class ExceptionPlaceholderNode extends ValueNode {
+        public static final NodeClass<ExceptionPlaceholderNode> TYPE = NodeClass.create(ExceptionPlaceholderNode.class);
+
+        public ExceptionPlaceholderNode() {
+            super(TYPE, StampFactory.object());
+        }
+    }
+
     public PEGraphDecoder(MetaAccessProvider metaAccess, ConstantReflectionProvider constantReflection, StampProvider stampProvider) {
         this.metaAccess = metaAccess;
         this.constantReflection = constantReflection;
@@ -294,8 +316,9 @@
 
     public void decode(StructuredGraph targetGraph, ResolvedJavaMethod method, LoopExplosionPlugin loopExplosionPlugin, InvocationPlugins invocationPlugins, InlineInvokePlugin inlineInvokePlugin,
                     ParameterPlugin parameterPlugin) {
-        PEMethodScope methodScope = new PEMethodScope(targetGraph, null, lookupEncodedGraph(method), method, null, 0, loopExplosionPlugin, invocationPlugins, inlineInvokePlugin, parameterPlugin, null);
-        decode(methodScope);
+        PEMethodScope methodScope = new PEMethodScope(targetGraph, null, null, lookupEncodedGraph(method), method, null, 0, loopExplosionPlugin, invocationPlugins, inlineInvokePlugin,
+                        parameterPlugin, null);
+        decode(methodScope, null);
         cleanupGraph(methodScope);
         methodScope.graph.verify();
     }
@@ -310,8 +333,6 @@
     protected void checkLoopExplosionIteration(MethodScope s, LoopScope loopScope) {
         PEMethodScope methodScope = (PEMethodScope) s;
 
-        Debug.dump(methodScope.graph, "Loop iteration " + loopScope.loopIteration);
-
         if (loopScope.loopIteration > MaximumLoopExplosionCount.getValue()) {
             String message = "too many loop explosion iterations - does the explosion not terminate for method " + methodScope.method + "?";
             if (FailedLoopExplosionIsFatal.getValue()) {
@@ -323,39 +344,52 @@
     }
 
     @Override
-    protected void simplifyInvoke(MethodScope s, LoopScope loopScope, int invokeOrderId, Invoke invoke) {
-        if (!(invoke.callTarget() instanceof MethodCallTargetNode)) {
-            return;
-        }
+    protected void handleInvoke(MethodScope s, LoopScope loopScope, InvokeData invokeData) {
         PEMethodScope methodScope = (PEMethodScope) s;
-        MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget();
+
+        /*
+         * Decode the call target, but do not add it to the graph yet. This avoids adding usages for
+         * all the arguments, which are expensive to remove again when we can inline the method.
+         */
+        assert invokeData.invoke.callTarget() == null : "callTarget edge is ignored during decoding of Invoke";
+        CallTargetNode callTarget = (CallTargetNode) decodeFloatingNode(methodScope, loopScope, invokeData.callTargetOrderId);
+        if (!(callTarget instanceof MethodCallTargetNode) || !trySimplifyInvoke(methodScope, loopScope, invokeData, (MethodCallTargetNode) callTarget)) {
 
+            /* We know that we need an invoke, so now we can add the call target to the graph. */
+            methodScope.graph.add(callTarget);
+            registerNode(loopScope, invokeData.callTargetOrderId, callTarget, false, false);
+            super.handleInvoke(methodScope, loopScope, invokeData);
+        }
+    }
+
+    protected boolean trySimplifyInvoke(PEMethodScope methodScope, LoopScope loopScope, InvokeData invokeData, MethodCallTargetNode callTarget) {
         // attempt to devirtualize the call
-        ResolvedJavaType contextType = (invoke.stateAfter() == null && invoke.stateDuring() == null) ? null : invoke.getContextType();
-        ResolvedJavaMethod specialCallTarget = MethodCallTargetNode.findSpecialCallTarget(callTarget.invokeKind(), callTarget.receiver(), callTarget.targetMethod(), contextType);
+        ResolvedJavaMethod specialCallTarget = MethodCallTargetNode.findSpecialCallTarget(callTarget.invokeKind(), callTarget.receiver(), callTarget.targetMethod(), invokeData.contextType);
         if (specialCallTarget != null) {
             callTarget.setTargetMethod(specialCallTarget);
             callTarget.setInvokeKind(InvokeKind.Special);
         }
 
-        if (tryInvocationPlugin(methodScope, loopScope, invokeOrderId, invoke)) {
-            return;
+        if (tryInvocationPlugin(methodScope, loopScope, invokeData, callTarget)) {
+            return true;
         }
-        if (tryInline(methodScope, loopScope, invokeOrderId, invoke)) {
-            return;
+        if (tryInline(methodScope, loopScope, invokeData, callTarget)) {
+            return true;
         }
 
         if (methodScope.inlineInvokePlugin != null) {
-            methodScope.inlineInvokePlugin.notifyOfNoninlinedInvoke(new PENonAppendGraphBuilderContext(methodScope), callTarget.targetMethod(), invoke);
+            methodScope.inlineInvokePlugin.notifyOfNoninlinedInvoke(new PENonAppendGraphBuilderContext(methodScope), callTarget.targetMethod(), invokeData.invoke);
         }
+        return false;
     }
 
-    protected boolean tryInvocationPlugin(PEMethodScope methodScope, LoopScope loopScope, int invokeOrderId, Invoke invoke) {
+    protected boolean tryInvocationPlugin(PEMethodScope methodScope, LoopScope loopScope, InvokeData invokeData, MethodCallTargetNode callTarget) {
         if (methodScope.invocationPlugins == null) {
             return false;
         }
 
-        MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget();
+        Invoke invoke = invokeData.invoke;
+
         ResolvedJavaMethod targetMethod = callTarget.targetMethod();
         InvocationPlugin invocationPlugin = methodScope.invocationPlugins.lookupInvocation(targetMethod);
         if (invocationPlugin == null) {
@@ -364,52 +398,41 @@
 
         ValueNode[] arguments = callTarget.arguments().toArray(new ValueNode[0]);
         FixedWithNextNode invokePredecessor = (FixedWithNextNode) invoke.asNode().predecessor();
-        FixedNode invokeNext = invoke.next();
-        AbstractBeginNode invokeException = null;
-        if (invoke instanceof InvokeWithExceptionNode) {
-            invokeException = ((InvokeWithExceptionNode) invoke).exceptionEdge();
-        }
 
+        /* Remove invoke from graph so that invocation plugin can append nodes to the predecessor. */
         invoke.asNode().replaceAtPredecessor(null);
 
-        PEAppendGraphBuilderContext graphBuilderContext = new PEAppendGraphBuilderContext(methodScope, invoke, invokePredecessor);
+        PEMethodScope inlineScope = new PEMethodScope(methodScope.graph, methodScope, loopScope, null, targetMethod, invokeData, methodScope.inliningDepth + 1, methodScope.loopExplosionPlugin,
+                        methodScope.invocationPlugins, methodScope.inlineInvokePlugin, null, arguments);
+        PEAppendGraphBuilderContext graphBuilderContext = new PEAppendGraphBuilderContext(inlineScope, invokePredecessor);
         InvocationPluginReceiver invocationPluginReceiver = new InvocationPluginReceiver(graphBuilderContext);
+
         if (invocationPlugin.execute(graphBuilderContext, targetMethod, invocationPluginReceiver.init(targetMethod, arguments), arguments)) {
 
             if (graphBuilderContext.lastInstr != null) {
-                registerNode(loopScope, invokeOrderId, graphBuilderContext.pushedNode, true, true);
+                registerNode(loopScope, invokeData.invokeOrderId, graphBuilderContext.pushedNode, true, true);
                 invoke.asNode().replaceAtUsages(graphBuilderContext.pushedNode);
+                graphBuilderContext.lastInstr.setNext(nodeAfterInvoke(methodScope, loopScope, invokeData));
             } else {
                 assert graphBuilderContext.pushedNode == null : "Why push a node when the invoke does not return anyway?";
                 invoke.asNode().replaceAtUsages(null);
             }
 
             deleteInvoke(invoke);
-            if (invokeException != null) {
-                invokeException.safeDelete();
-            }
-
-            if (graphBuilderContext.lastInstr != null) {
-                graphBuilderContext.lastInstr.setNext(invokeNext);
-            } else {
-                invokeNext.replaceAtPredecessor(null);
-                invokeNext.safeDelete();
-            }
             return true;
 
         } else {
-            /* Restore original state: invoke is in Graph. */
+            /* Intrinsification failed, restore original state: invoke is in Graph. */
             invokePredecessor.setNext(invoke.asNode());
             return false;
         }
     }
 
-    protected boolean tryInline(PEMethodScope methodScope, LoopScope loopScope, int invokeOrderId, Invoke invoke) {
-        if (methodScope.inlineInvokePlugin == null || !invoke.getInvokeKind().isDirect()) {
+    protected boolean tryInline(PEMethodScope methodScope, LoopScope loopScope, InvokeData invokeData, MethodCallTargetNode callTarget) {
+        if (methodScope.inlineInvokePlugin == null || !callTarget.invokeKind().isDirect()) {
             return false;
         }
 
-        MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget();
         ResolvedJavaMethod targetMethod = callTarget.targetMethod();
         if (!targetMethod.canBeInlined()) {
             return false;
@@ -429,50 +452,72 @@
             return false;
         }
 
-        int exceptionObjectOrderId = -1;
-        if (invoke instanceof InvokeWithExceptionNode) {
-            /*
-             * We need to have the regular next node (usually a KillingBeginNode) and the exception
-             * edge node (always an ExceptionObjectNode) fully decoded, because both can be changed
-             * or replaced as part of the inlining process. The GraphEncoder adds these two
-             * successors in a known order (first the regular next node, then the exception edge)
-             * that we can rely on here.
-             */
-            assert ((InvokeWithExceptionNode) invoke).next().next() == null;
-            processNextNode(methodScope, loopScope);
-            assert ((InvokeWithExceptionNode) invoke).next().next() != null;
+        Invoke invoke = invokeData.invoke;
+        FixedNode invokeNode = invoke.asNode();
+        FixedWithNextNode predecessor = (FixedWithNextNode) invokeNode.predecessor();
+        invokeNode.replaceAtPredecessor(null);
 
-            assert ((InvokeWithExceptionNode) invoke).exceptionEdge().next() == null;
-            exceptionObjectOrderId = loopScope.nodesToProcess.nextSetBit(0);
-            processNextNode(methodScope, loopScope);
-            assert ((InvokeWithExceptionNode) invoke).exceptionEdge().next() != null;
-        }
-
-        PEMethodScope inlineScope = new PEMethodScope(methodScope.graph, methodScope, graphToInline, inlineMethod, invoke, methodScope.inliningDepth + 1, methodScope.loopExplosionPlugin,
-                        methodScope.invocationPlugins, methodScope.inlineInvokePlugin, null, arguments);
+        PEMethodScope inlineScope = new PEMethodScope(methodScope.graph, methodScope, loopScope, graphToInline, inlineMethod, invokeData, methodScope.inliningDepth + 1,
+                        methodScope.loopExplosionPlugin, methodScope.invocationPlugins, methodScope.inlineInvokePlugin, null, arguments);
         /* Do the actual inlining by decoding the inlineMethod */
-        decode(inlineScope);
+        decode(inlineScope, predecessor);
 
         ValueNode exceptionValue = null;
         if (inlineScope.unwindNode != null) {
             exceptionValue = inlineScope.unwindNode.exception();
         }
+        UnwindNode unwindNode = inlineScope.unwindNode;
 
-        FixedNode firstInlinedNode = inlineScope.startNode.next();
-        /* The StartNode was only necessary as a placeholder during decoding. */
-        inlineScope.startNode.safeDelete();
+        if (invoke instanceof InvokeWithExceptionNode) {
+            InvokeWithExceptionNode invokeWithException = ((InvokeWithExceptionNode) invoke);
+            assert invokeWithException.next() == null;
+            assert invokeWithException.exceptionEdge() == null;
+
+            if (unwindNode != null) {
+                assert unwindNode.predecessor() != null;
+                Node n = makeStubNode(methodScope, loopScope, invokeData.exceptionNextOrderId);
+                unwindNode.replaceAndDelete(n);
+            }
+
+        } else {
+            if (unwindNode != null && !unwindNode.isDeleted()) {
+                DeoptimizeNode deoptimizeNode = methodScope.graph.add(new DeoptimizeNode(DeoptimizationAction.InvalidateRecompile, DeoptimizationReason.NotCompiledExceptionHandler));
+                unwindNode.replaceAndDelete(deoptimizeNode);
+            }
+        }
+
+        assert invoke.next() == null;
 
-        assert inlineScope.startNode.stateAfter() == null;
-        ValueNode returnValue = InliningUtil.finishInlining(invoke, methodScope.graph, firstInlinedNode, inlineScope.returnNodes, inlineScope.unwindNode, inlineScope.encodedGraph.getAssumptions(),
-                        inlineScope.encodedGraph.getInlinedMethods(), null);
+        ValueNode returnValue;
+        List<ReturnNode> returnNodes = inlineScope.returnNodes;
+        if (!returnNodes.isEmpty()) {
+            FixedNode n;
+            n = nodeAfterInvoke(methodScope, loopScope, invokeData);
+            if (returnNodes.size() == 1) {
+                ReturnNode returnNode = returnNodes.get(0);
+                returnValue = returnNode.result();
+                returnNode.replaceAndDelete(n);
+            } else {
+                AbstractMergeNode merge = methodScope.graph.add(new MergeNode());
+                merge.setStateAfter((FrameState) ensureNodeCreated(methodScope, loopScope, invokeData.stateAfterOrderId));
+                returnValue = InliningUtil.mergeReturns(merge, returnNodes, null);
+                merge.setNext(n);
+            }
+        } else {
+            returnValue = null;
+        }
+        invokeNode.replaceAtUsages(returnValue);
 
         /*
          * Usage the handles that we have on the return value and the exception to update the
          * orderId->Node table.
          */
-        registerNode(loopScope, invokeOrderId, returnValue, true, true);
+        registerNode(loopScope, invokeData.invokeOrderId, returnValue, true, true);
         if (invoke instanceof InvokeWithExceptionNode) {
-            registerNode(loopScope, exceptionObjectOrderId, exceptionValue, true, true);
+            registerNode(loopScope, invokeData.exceptionOrderId, exceptionValue, true, true);
+        }
+        if (inlineScope.exceptionPlaceholderNode != null) {
+            inlineScope.exceptionPlaceholderNode.replaceAndDelete(exceptionValue);
         }
         deleteInvoke(invoke);
 
@@ -480,6 +525,16 @@
         return true;
     }
 
+    public FixedNode nodeAfterInvoke(PEMethodScope methodScope, LoopScope loopScope, InvokeData invokeData) {
+        FixedNode n;
+        if (invokeData.invoke instanceof InvokeWithExceptionNode) {
+            n = makeStubNode(methodScope, loopScope, invokeData.nextNextOrderId);
+        } else {
+            n = makeStubNode(methodScope, loopScope, invokeData.nextOrderId);
+        }
+        return n;
+    }
+
     private static void deleteInvoke(Invoke invoke) {
         /*
          * Clean up unused nodes. We cannot just call killCFG on the invoke node because that can
@@ -487,7 +542,7 @@
          */
         FrameState frameState = invoke.stateAfter();
         invoke.asNode().safeDelete();
-        invoke.callTarget().safeDelete();
+        assert invoke.callTarget() == null : "must not have been added to the graph yet";
         if (frameState != null && frameState.hasNoUsages()) {
             frameState.safeDelete();
         }
@@ -499,15 +554,20 @@
     protected void simplifyFixedNode(MethodScope s, LoopScope loopScope, int nodeOrderId, FixedNode node) {
         PEMethodScope methodScope = (PEMethodScope) s;
 
-        if (node instanceof IfNode && ((IfNode) node).condition() instanceof LogicConstantNode) {
+        if (node instanceof IfNode) {
             IfNode ifNode = (IfNode) node;
-            boolean condition = ((LogicConstantNode) ifNode.condition()).getValue();
-            AbstractBeginNode survivingSuccessor = ifNode.getSuccessor(condition);
-            AbstractBeginNode deadSuccessor = ifNode.getSuccessor(!condition);
+            if (ifNode.condition() instanceof LogicNegationNode) {
+                ifNode.eliminateNegation();
+            }
+            if (ifNode.condition() instanceof LogicConstantNode) {
+                boolean condition = ((LogicConstantNode) ifNode.condition()).getValue();
+                AbstractBeginNode survivingSuccessor = ifNode.getSuccessor(condition);
+                AbstractBeginNode deadSuccessor = ifNode.getSuccessor(!condition);
 
-            methodScope.graph.removeSplit(ifNode, survivingSuccessor);
-            assert deadSuccessor.next() == null : "must not be parsed yet";
-            deadSuccessor.safeDelete();
+                methodScope.graph.removeSplit(ifNode, survivingSuccessor);
+                assert deadSuccessor.next() == null : "must not be parsed yet";
+                deadSuccessor.safeDelete();
+            }
 
         } else if (node instanceof IntegerSwitchNode && ((IntegerSwitchNode) node).value().isConstant()) {
             IntegerSwitchNode switchNode = (IntegerSwitchNode) node;
@@ -587,31 +647,68 @@
         return node;
     }
 
+    protected void ensureOuterStateDecoded(PEMethodScope methodScope) {
+        if (methodScope.outerState == null && methodScope.caller != null) {
+            FrameState stateAtReturn = methodScope.invokeData.invoke.stateAfter();
+            if (stateAtReturn == null) {
+                stateAtReturn = (FrameState) decodeFloatingNode(methodScope.caller, methodScope.callerLoopScope, methodScope.invokeData.stateAfterOrderId);
+            }
+
+            Kind invokeReturnKind = methodScope.invokeData.invoke.asNode().getKind();
+            FrameState outerState = stateAtReturn.duplicateModified(methodScope.graph, methodScope.invokeData.invoke.bci(), stateAtReturn.rethrowException(), true, invokeReturnKind);
+
+            if (methodScope.caller != null) {
+                ensureOuterStateDecoded(methodScope.caller);
+                outerState.setOuterFrameState(methodScope.caller.outerState);
+            }
+            methodScope.outerState = outerState;
+        }
+    }
+
+    protected void ensureStateAfterDecoded(PEMethodScope methodScope) {
+        if (methodScope.invokeData.invoke.stateAfter() == null) {
+            methodScope.invokeData.invoke.setStateAfter((FrameState) ensureNodeCreated(methodScope.caller, methodScope.callerLoopScope, methodScope.invokeData.stateAfterOrderId));
+        }
+    }
+
+    protected void ensureExceptionStateDecoded(PEMethodScope methodScope) {
+        if (methodScope.exceptionState == null && methodScope.caller != null && methodScope.invokeData.invoke instanceof InvokeWithExceptionNode) {
+            ensureStateAfterDecoded(methodScope);
+
+            assert methodScope.exceptionPlaceholderNode == null;
+            methodScope.exceptionPlaceholderNode = methodScope.graph.add(new ExceptionPlaceholderNode());
+            registerNode(methodScope.callerLoopScope, methodScope.invokeData.exceptionOrderId, methodScope.exceptionPlaceholderNode, false, false);
+            FrameState exceptionState = (FrameState) ensureNodeCreated(methodScope.caller, methodScope.callerLoopScope, methodScope.invokeData.exceptionStateOrderId);
+
+            if (methodScope.caller != null) {
+                ensureOuterStateDecoded(methodScope.caller);
+                exceptionState.setOuterFrameState(methodScope.caller.outerState);
+            }
+            methodScope.exceptionState = exceptionState;
+        }
+    }
+
     @Override
     protected Node handleFloatingNodeAfterAdd(MethodScope s, LoopScope loopScope, Node node) {
         PEMethodScope methodScope = (PEMethodScope) s;
 
         if (methodScope.isInlinedMethod()) {
             if (node instanceof SimpleInfopointNode) {
-                methodScope.bytecodePosition = InliningUtil.processSimpleInfopoint(methodScope.invoke, (SimpleInfopointNode) node, methodScope.bytecodePosition);
+                methodScope.bytecodePosition = InliningUtil.processSimpleInfopoint(methodScope.invokeData.invoke, (SimpleInfopointNode) node, methodScope.bytecodePosition);
                 return node;
 
             } else if (node instanceof FrameState) {
-                FrameState stateAtExceptionEdge = null;
-                if (methodScope.invoke instanceof InvokeWithExceptionNode) {
-                    InvokeWithExceptionNode invokeWithException = ((InvokeWithExceptionNode) methodScope.invoke);
-                    ExceptionObjectNode obj = (ExceptionObjectNode) invokeWithException.exceptionEdge();
-                    stateAtExceptionEdge = obj.stateAfter();
+                FrameState frameState = (FrameState) node;
+
+                ensureOuterStateDecoded(methodScope);
+                if (frameState.bci < 0) {
+                    ensureExceptionStateDecoded(methodScope);
                 }
-                if (methodScope.outerFrameState == null) {
-                    FrameState stateAtReturn = methodScope.invoke.stateAfter();
-                    Kind invokeReturnKind = methodScope.invoke.asNode().getKind();
-                    methodScope.outerFrameState = stateAtReturn.duplicateModifiedDuringCall(methodScope.invoke.bci(), invokeReturnKind);
-                }
-                return InliningUtil.processFrameState((FrameState) node, methodScope.invoke, methodScope.method, stateAtExceptionEdge, methodScope.outerFrameState, true);
+                return InliningUtil.processFrameState(frameState, methodScope.invokeData.invoke, methodScope.method, methodScope.exceptionState, methodScope.outerState, true);
 
             } else if (node instanceof MonitorIdNode) {
-                InliningUtil.processMonitorId(methodScope.invoke, (MonitorIdNode) node);
+                ensureOuterStateDecoded(methodScope);
+                InliningUtil.processMonitorId(methodScope.outerState, (MonitorIdNode) node);
                 return node;
             }
         }
--- a/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/PartialEvaluator.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.graal.truffle/src/com/oracle/graal/truffle/PartialEvaluator.java	Mon Apr 13 21:51:19 2015 +0200
@@ -164,28 +164,74 @@
 
     private class PEInlineInvokePlugin implements InlineInvokePlugin {
 
-        private final boolean duringParsing;
         private Deque<TruffleInlining> inlining;
         private OptimizedDirectCallNode lastDirectCallNode;
         private final Replacements replacements;
 
-        private final InvocationPlugins invocationPlugins;
-        private final LoopExplosionPlugin loopExplosionPlugin;
-
-        public PEInlineInvokePlugin(TruffleInlining inlining, Replacements replacements, boolean duringParsing, InvocationPlugins invocationPlugins, LoopExplosionPlugin loopExplosionPlugin) {
+        public PEInlineInvokePlugin(TruffleInlining inlining, Replacements replacements) {
             this.inlining = new ArrayDeque<>();
             this.inlining.push(inlining);
             this.replacements = replacements;
-            this.duringParsing = duringParsing;
+        }
+
+        @Override
+        public InlineInfo getInlineInfo(GraphBuilderContext builder, ResolvedJavaMethod original, ValueNode[] arguments, JavaType returnType) {
+            if (original.getAnnotation(TruffleBoundary.class) != null) {
+                return null;
+            }
+            if (replacements != null && replacements.hasSubstitution(original)) {
+                return null;
+            }
+            assert !builder.parsingReplacement();
+            if (TruffleCompilerOptions.TruffleFunctionInlining.getValue()) {
+                if (original.equals(callSiteProxyMethod)) {
+                    ValueNode arg1 = arguments[0];
+                    if (!arg1.isConstant()) {
+                        GraalInternalError.shouldNotReachHere("The direct call node does not resolve to a constant!");
+                    }
+
+                    Object callNode = snippetReflection.asObject(Object.class, (JavaConstant) arg1.asConstant());
+                    if (callNode instanceof OptimizedDirectCallNode) {
+                        OptimizedDirectCallNode directCallNode = (OptimizedDirectCallNode) callNode;
+                        lastDirectCallNode = directCallNode;
+                    }
+                } else if (original.equals(callDirectMethod)) {
+                    TruffleInliningDecision decision = getDecision(inlining.peek(), lastDirectCallNode);
+                    lastDirectCallNode = null;
+                    if (decision != null && decision.isInline()) {
+                        inlining.push(decision);
+                        builder.getAssumptions().record(new AssumptionValidAssumption((OptimizedAssumption) decision.getTarget().getNodeRewritingAssumption()));
+                        return new InlineInfo(callInlinedMethod, false, false);
+                    }
+                }
+            }
+
+            return new InlineInfo(original, false, false);
+        }
+
+        @Override
+        public void postInline(ResolvedJavaMethod inlinedTargetMethod) {
+            if (inlinedTargetMethod.equals(callInlinedMethod)) {
+                inlining.pop();
+            }
+        }
+    }
+
+    private class ParsingInlineInvokePlugin implements InlineInvokePlugin {
+
+        private final Replacements replacements;
+        private final InvocationPlugins invocationPlugins;
+        private final LoopExplosionPlugin loopExplosionPlugin;
+        private final boolean inlineDuringParsing;
+
+        public ParsingInlineInvokePlugin(Replacements replacements, InvocationPlugins invocationPlugins, LoopExplosionPlugin loopExplosionPlugin, boolean inlineDuringParsing) {
+            this.replacements = replacements;
             this.invocationPlugins = invocationPlugins;
             this.loopExplosionPlugin = loopExplosionPlugin;
+            this.inlineDuringParsing = inlineDuringParsing;
         }
 
         private boolean hasMethodHandleArgument(ValueNode[] arguments) {
-            /*
-             * We want to process invokes that have a constant MethodHandle parameter. And the
-             * method must be statically bound, otherwise we do not have a single target method.
-             */
             for (ValueNode argument : arguments) {
                 if (argument.isConstant()) {
                     JavaConstant constant = argument.asJavaConstant();
@@ -197,58 +243,31 @@
             return false;
         }
 
+        @Override
         public InlineInfo getInlineInfo(GraphBuilderContext builder, ResolvedJavaMethod original, ValueNode[] arguments, JavaType returnType) {
-            if (duringParsing && (invocationPlugins.lookupInvocation(original) != null || loopExplosionPlugin.shouldExplodeLoops(original))) {
+            if (invocationPlugins.lookupInvocation(original) != null || loopExplosionPlugin.shouldExplodeLoops(original)) {
                 return null;
             }
-
             if (original.getAnnotation(TruffleBoundary.class) != null) {
                 return null;
             }
             if (replacements != null && replacements.hasSubstitution(original)) {
                 return null;
             }
-            assert !builder.parsingReplacement();
-            if (TruffleCompilerOptions.TruffleFunctionInlining.getValue()) {
-                if (original.equals(callSiteProxyMethod)) {
-                    if (duringParsing) {
-                        return null;
-                    }
-                    ValueNode arg1 = arguments[0];
-                    if (!arg1.isConstant()) {
-                        GraalInternalError.shouldNotReachHere("The direct call node does not resolve to a constant!");
-                    }
-
-                    Object callNode = snippetReflection.asObject(Object.class, (JavaConstant) arg1.asConstant());
-                    if (callNode instanceof OptimizedDirectCallNode) {
-                        OptimizedDirectCallNode directCallNode = (OptimizedDirectCallNode) callNode;
-                        lastDirectCallNode = directCallNode;
-                    }
-                } else if (original.equals(callDirectMethod)) {
-                    if (duringParsing) {
-                        return null;
-                    }
-                    TruffleInliningDecision decision = getDecision(inlining.peek(), lastDirectCallNode);
-                    lastDirectCallNode = null;
-                    if (decision != null && decision.isInline()) {
-                        inlining.push(decision);
-                        builder.getAssumptions().record(new AssumptionValidAssumption((OptimizedAssumption) decision.getTarget().getNodeRewritingAssumption()));
-                        return new InlineInfo(callInlinedMethod, false, false);
-                    }
-                }
-            }
-
-            if (duringParsing && (!original.hasBytecodes() || original.getCode().length >= TrivialInliningSize.getValue() || builder.getDepth() >= InlineDuringParsingMaxDepth.getValue()) &&
-                            !hasMethodHandleArgument(arguments)) {
+            if (original.equals(callSiteProxyMethod) || original.equals(callDirectMethod)) {
                 return null;
             }
-            return new InlineInfo(original, false, false);
-        }
-
-        public void postInline(ResolvedJavaMethod inlinedTargetMethod) {
-            if (inlinedTargetMethod.equals(callInlinedMethod)) {
-                inlining.pop();
+            if (hasMethodHandleArgument(arguments)) {
+                /*
+                 * We want to inline invokes that have a constant MethodHandle parameter to remove
+                 * invokedynamic related calls as early as possible.
+                 */
+                return new InlineInfo(original, false, false);
             }
+            if (inlineDuringParsing && original.hasBytecodes() && original.getCode().length < TrivialInliningSize.getValue() && builder.getDepth() < InlineDuringParsingMaxDepth.getValue()) {
+                return new InlineInfo(original, false, false);
+            }
+            return null;
         }
     }
 
@@ -279,7 +298,7 @@
         plugins.setParameterPlugin(new InterceptReceiverPlugin(callTarget));
         callTarget.setInlining(new TruffleInlining(callTarget, new DefaultInliningPolicy()));
 
-        InlineInvokePlugin inlinePlugin = new PEInlineInvokePlugin(callTarget.getInlining(), providers.getReplacements(), false, null, null);
+        InlineInvokePlugin inlinePlugin = new PEInlineInvokePlugin(callTarget.getInlining(), providers.getReplacements());
         if (PrintTruffleExpansionHistogram.getValue()) {
             inlinePlugin = new HistogramInlineInvokePlugin(graph, inlinePlugin);
         }
@@ -303,7 +322,7 @@
         newConfig.setUseProfiling(false);
         Plugins plugins = newConfig.getPlugins();
         plugins.setLoadFieldPlugin(new InterceptLoadFieldPlugin());
-        plugins.setInlineInvokePlugin(new PEInlineInvokePlugin(callTarget.getInlining(), providers.getReplacements(), true, parsingInvocationPlugins, loopExplosionPlugin));
+        plugins.setInlineInvokePlugin(new ParsingInlineInvokePlugin(providers.getReplacements(), parsingInvocationPlugins, loopExplosionPlugin, !PrintTruffleExpansionHistogram.getValue()));
 
         CachingPEGraphDecoder decoder = new CachingPEGraphDecoder(providers, newConfig, AllowAssumptions.from(graph.getAssumptions() != null));
 
@@ -311,7 +330,7 @@
 
         InvocationPlugins decodingInvocationPlugins = new InvocationPlugins(providers.getMetaAccess());
         TruffleGraphBuilderPlugins.registerInvocationPlugins(providers.getMetaAccess(), decodingInvocationPlugins, false, snippetReflection);
-        InlineInvokePlugin decodingInlinePlugin = new PEInlineInvokePlugin(callTarget.getInlining(), providers.getReplacements(), false, decodingInvocationPlugins, loopExplosionPlugin);
+        InlineInvokePlugin decodingInlinePlugin = new PEInlineInvokePlugin(callTarget.getInlining(), providers.getReplacements());
         if (PrintTruffleExpansionHistogram.getValue()) {
             decodingInlinePlugin = new HistogramInlineInvokePlugin(graph, decodingInlinePlugin);
         }
@@ -431,7 +450,9 @@
         if (!TruffleCompilerOptions.TruffleInlineAcrossTruffleBoundary.getValue()) {
             // Do not inline across Truffle boundaries.
             for (MethodCallTargetNode mct : graph.getNodes(MethodCallTargetNode.TYPE)) {
-                mct.invoke().setUseForInlining(false);
+                if (mct.targetMethod().getAnnotation(TruffleBoundary.class) != null) {
+                    mct.invoke().setUseForInlining(false);
+                }
             }
         }
     }
--- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/InstrumentationTest.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/InstrumentationTest.java	Mon Apr 13 21:51:19 2015 +0200
@@ -157,7 +157,6 @@
 
         // Check that the "probed" AST still executes correctly
         assertEquals(13, callTarget1.call());
-
     }
 
     @Test
@@ -178,11 +177,10 @@
 
         // Check instrumentation with the simplest kind of counters.
         // They should all be removed when the check is finished.
-        checkCounters(probe, callTarget, rootNode, new TestInstrumentCounter(), new TestInstrumentCounter(), new TestInstrumentCounter());
+        checkCounters(probe, callTarget, rootNode, new TestSimpleInstrumentCounter(), new TestSimpleInstrumentCounter(), new TestSimpleInstrumentCounter());
 
         // Now try with the more complex flavor of listener
-        checkCounters(probe, callTarget, rootNode, new TestASTInstrumentCounter(), new TestASTInstrumentCounter(), new TestASTInstrumentCounter());
-
+        checkCounters(probe, callTarget, rootNode, new TestStandardInstrumentCounter(), new TestStandardInstrumentCounter(), new TestStandardInstrumentCounter());
     }
 
     private static void checkCounters(Probe probe, CallTarget callTarget, RootNode rootNode, TestCounter counterA, TestCounter counterB, TestCounter counterC) {
@@ -280,12 +278,10 @@
         assertEquals(counterB.leaveCount(), 8);
         assertEquals(counterC.enterCount(), 2);
         assertEquals(counterC.leaveCount(), 2);
-
     }
 
     @Test
     public void testTagging() {
-
         // Applies appropriate tags
         final TestASTProber astProber = new TestASTProber();
         Probe.registerASTProber(astProber);
@@ -341,7 +337,6 @@
         assertEquals(valueCounter.count, 2);
 
         Probe.unregisterASTProber(astProber);
-
     }
 
     private interface TestCounter {
@@ -353,92 +348,107 @@
         void attach(Probe probe);
 
         void dispose();
-
     }
 
     /**
-     * A counter for the number of times execution enters and leaves a probed AST node, using the
-     * simplest kind of listener.
+     * A counter for the number of times execution enters and leaves a probed AST node.
      */
-    private class TestInstrumentCounter implements TestCounter {
+    private class TestSimpleInstrumentCounter implements TestCounter {
 
         public int enterCount = 0;
         public int leaveCount = 0;
         public final Instrument instrument;
 
-        public TestInstrumentCounter() {
+        public TestSimpleInstrumentCounter() {
             this.instrument = Instrument.create(new SimpleInstrumentListener() {
 
-                @Override
                 public void enter(Probe probe) {
                     enterCount++;
                 }
 
-                @Override
-                public void returnAny(Probe probe) {
+                public void returnVoid(Probe probe) {
+                    leaveCount++;
+                }
+
+                public void returnValue(Probe probe, Object result) {
+                    leaveCount++;
+                }
+
+                public void returnExceptional(Probe probe, Exception exception) {
                     leaveCount++;
                 }
 
             }, "Instrumentation Test Counter");
-
         }
 
+        @Override
         public int enterCount() {
             return enterCount;
         }
 
+        @Override
         public int leaveCount() {
             return leaveCount;
         }
 
+        @Override
         public void attach(Probe probe) {
             probe.attach(instrument);
         }
 
+        @Override
         public void dispose() {
             instrument.dispose();
         }
     }
 
     /**
-     * A counter for the number of times execution enters and leaves a probed AST node, using the
-     * simplest kind of listener.
+     * A counter for the number of times execution enters and leaves a probed AST node.
      */
-    private class TestASTInstrumentCounter implements TestCounter {
+    private class TestStandardInstrumentCounter implements TestCounter {
 
         public int enterCount = 0;
         public int leaveCount = 0;
         public final Instrument instrument;
 
-        public TestASTInstrumentCounter() {
-            this.instrument = Instrument.create(new SimpleASTInstrumentListener() {
+        public TestStandardInstrumentCounter() {
+            this.instrument = Instrument.create(new StandardInstrumentListener() {
 
-                @Override
                 public void enter(Probe probe, Node node, VirtualFrame vFrame) {
                     enterCount++;
                 }
 
-                @Override
-                public void returnAny(Probe probe, Node node, VirtualFrame vFrame) {
+                public void returnVoid(Probe probe, Node node, VirtualFrame vFrame) {
+                    leaveCount++;
+                }
+
+                public void returnValue(Probe probe, Node node, VirtualFrame vFrame, Object result) {
+                    leaveCount++;
+                }
+
+                public void returnExceptional(Probe probe, Node node, VirtualFrame vFrame, Exception exception) {
                     leaveCount++;
                 }
 
             }, "Instrumentation Test Counter");
-
         }
 
+        @Override
         public int enterCount() {
             return enterCount;
         }
 
+        @Override
         public int leaveCount() {
             return leaveCount;
         }
 
+        @Override
         public void attach(Probe probe) {
             probe.attach(instrument);
         }
 
+        @Override
         public void dispose() {
             instrument.dispose();
         }
@@ -475,7 +485,7 @@
     /**
      * Counts the number of "enter" events at probed nodes using the simplest AST listener.
      */
-    static final class TestInstrumentListener extends DefaultInstrumentListener {
+    static final class TestSimpleInstrumentListener extends DefaultSimpleInstrumentListener {
 
         public int counter = 0;
 
@@ -483,13 +493,12 @@
         public void enter(Probe probe) {
             counter++;
         }
-
     }
 
     /**
      * Counts the number of "enter" events at probed nodes using the AST listener.
      */
-    static final class TestASTInstrumentListener extends DefaultASTInstrumentListener {
+    static final class TestASTInstrumentListener extends DefaultStandardInstrumentListener {
 
         public int counter = 0;
 
@@ -497,7 +506,6 @@
         public void enter(Probe probe, Node node, VirtualFrame vFrame) {
             counter++;
         }
-
     }
 
     /**
@@ -514,7 +522,7 @@
             // where we want to count executions.
             // it will get copied when ASTs cloned, so
             // keep the count in this outer class.
-            probe.attach(Instrument.create(new DefaultInstrumentListener() {
+            probe.attach(Instrument.create(new DefaultSimpleInstrumentListener() {
 
                 @Override
                 public void enter(Probe p) {
@@ -539,5 +547,4 @@
             tagCount++;
         }
     }
-
 }
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ASTInstrumentListener.java	Mon Apr 13 21:50:37 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 com.oracle.truffle.api.instrument;
-
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-
-/**
- * A listener of Truffle AST runtime execution events that can collect information, examine the
- * execution state at a particular node, and possibly intervene on behalf of an external tool.
- */
-public interface ASTInstrumentListener {
-
-    /**
-     * Receive notification that an AST node's execute method is about to be called.
-     */
-    void enter(Probe probe, Node node, VirtualFrame vFrame);
-
-    /**
-     * Receive notification that an AST Node's {@code void}-valued execute method has just returned.
-     */
-    void returnVoid(Probe probe, Node node, VirtualFrame vFrame);
-
-    /**
-     * Receive notification that an AST Node's execute method has just returned a value (boxed if
-     * primitive).
-     */
-    void returnValue(Probe probe, Node node, VirtualFrame vFrame, Object result);
-
-    /**
-     * Receive notification that an AST Node's execute method has just thrown an exception.
-     */
-    void returnExceptional(Probe probe, Node node, VirtualFrame vFrame, Exception exception);
-
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.java	Mon Apr 13 21:51:19 2015 +0200
@@ -25,145 +25,67 @@
 package com.oracle.truffle.api.instrument;
 
 import com.oracle.truffle.api.*;
-import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.frame.*;
 import com.oracle.truffle.api.instrument.InstrumentationNode.TruffleEvents;
-import com.oracle.truffle.api.instrument.impl.*;
 import com.oracle.truffle.api.nodes.*;
 import com.oracle.truffle.api.source.*;
 
-// TODO (mlvdv) migrate some of this to external documentation.
 // TODO (mlvdv) move all this to a factory implemented in .impl (together with Probe),
 // then break out some of the nested classes into package privates.
 /**
- * A dynamically added/removed binding between a {@link Probe}, which provides notification of
- * <em>execution events</em> taking place at a {@link Node} in a Guest Language (GL) Truffle AST,
- * and a <em>listener</em>, which consumes notifications on behalf of an external tool. There are at
- * present two kinds of listeners that be used:
- * <ol>
- * <li>{@link InstrumentListener} is the simplest and is intended for tools that require no access
- * to the <em>internal execution state</em> of the Truffle execution, only that execution has passed
- * through a particular location in the program. Information about that location is made available
- * via the {@link Probe} argument in notification methods, including the {@linkplain SourceSection
- * source location} of the node and any {@linkplain SyntaxTag tags} that have been applied to the
- * node.</li>
- * <li>{@link ASTInstrumentListener} reports the same events and {@link Probe} argument, but
- * additionally provides access to the execution state via the explicit {@link Node} and
- * {@link Frame} at the current execution site.</li>
- * </ol>
- * <p>
- * <h4>Summary: How to "instrument" an AST location:</h4>
- * <p>
+ * A <em>binding</em> between:
  * <ol>
- * <li>Create an implementation of a <em>listener</em> interface.</li>
- * <li>Create an Instrument via factory methods
- * {@link Instrument#create(InstrumentListener, String)} or
- * {@link Instrument#create(ASTInstrumentListener, String)}.</li>
- * <li>"Attach" the Instrument to a Probe via {@link Probe#attach(Instrument)}, at which point event
- * notifications begin to arrive at the listener.</li>
- * <li>When no longer needed, "detach" the Instrument via {@link ASTInstrument#dispose()}, at which
- * point event notifications to the listener cease, and the Instrument becomes unusable.</li>
- * </ol>
- * <p>
- * <h4>Options for creating listeners:</h4>
- * <p>
- * <ol>
- * <li>Implement one of the <em>listener interfaces</em>: {@link InstrumentListener} or
- * {@link ASTInstrumentListener} . Their event handling methods account for both the entry into an
- * AST node (about to call) and three possible kinds of <em>execution return</em> from an AST node.</li>
- * <li>Extend one of the <em>helper implementations</em>: {@link DefaultInstrumentListener} or
- * {@link DefaultASTInstrumentListener}. These provide no-op implementation of every listener
- * method, so only the methods of interest need to be overridden.</li>
- * <li>Extend one of the <em>helper implementations</em>: {@link SimpleInstrumentListener} or
- * {@link SimpleASTInstrumentListener}. These re-route all <em>execution returns</em> to a single
- * method, ignoring return values, so only two methods (for "enter" and "return") will notify all
- * events.</li>
+ * <li>A {@link Probe}: a source of <em>execution events</em> taking place at a program location in
+ * an executing Truffle AST, and</li>
+ * <li>A <em>listener</em>: a consumer of execution events on behalf of an external client.
  * </ol>
  * <p>
- * <h4>General guidelines for {@link ASTInstrumentListener} implementation:</h4>
+ * Client-oriented documentation for the use of Instruments is available online at <a
+ * HREF="https://wiki.openjdk.java.net/display/Graal/Listening+for+Execution+Events">Listening for
+ * Execution Events</a>
  * <p>
- * Unlike the listener interface {@link InstrumentListener}, which isolates implementations
- * completely from Truffle internals (and is thus <em>Truffle-safe</em>), implementations of
- * {@link ASTInstrumentListener} can interact directly with (and potentially affect) Truffle
- * execution in general and Truffle optimization in particular. For example, it is possible to
- * implement a debugger with this interface.
- * </p>
+ * The implementation of Instruments is complicated by the requirement that Truffle be able to clone
+ * ASTs at any time. In particular, any instrumentation-supporting Nodes that have been attached to
+ * an AST must be cloned along with the AST: AST clones are not permitted to share Nodes.
  * <p>
- * As a consequence, implementations of {@link ASTInstrumentListener} effectively become part of the
- * Truffle execution and must be coded according to general guidelines for Truffle implementations.
- * For example:
- * <ul>
- * <li>Do not store {@link Frame} or {@link Node} references in fields.</li>
- * <li>Prefer {@code final} fields and (where performance is important) short methods.</li>
- * <li>If needed, pass along the {@link VirtualFrame} reference from an event notification as far as
- * possible through code that is expected to be inlined, since this incurs no runtime overhead. When
- * access to frame data is needed, substitute a more expensive {@linkplain Frame#materialize()
- * materialized} representation of the frame.</li>
- * <li>If a listener calls back to its tool during event handling, and if performance is an issue,
- * then this should be through a final "callback" field in the instrument, and the called methods
- * should be minimal.</li>
- * <li>On the other hand, implementations should prevent Truffle from inlining beyond a reasonable
- * point with the method annotation {@link TruffleBoundary}.</li>
- * <li>The implicit "outer" pointer in a non-static inner class is a useful (and
- * Truffle-optimizable) way to implement callbacks to owner tools.</li>
- * <li>Primitive-valued return events are boxed for event notification, but Truffle will eliminate
- * the boxing if they are cast back to their primitive values quickly (in particular before crossing
- * any {@link TruffleBoundary} annotations).
- * </ul>
+ * AST cloning is intended to be as <em>transparent</em> as possible to clients. This is encouraged
+ * by providing the {@link SimpleInstrumentListener} for clients that need know nothing more than
+ * the properties associated with a Probe: it's {@link SourceSection} and any associated instances
+ * of {@link SyntaxTag}.
  * <p>
- * <h4>Allowing for AST cloning:</h4>
+ * AST cloning is <em>not transparent</em> to clients that use the
+ * {@link StandardInstrumentListener}, since those event methods identify the concrete Node instance
+ * (and thus the AST instance) where the event takes place.
  * <p>
- * Truffle routinely <em>clones</em> ASTs, which has consequences for implementations of
- * {@link ASTInstrumentListener} (but not for implementations of {@link InstrumentListener}, from
- * which cloning is hidden).
- * <ul>
- * <li>Even though a {@link Probe} is uniquely associated with a particular location in the
- * executing Guest Language program, execution events at that location will in general be
- * implemented by different {@link Node} instances, i.e. <em>clones</em> of the originally probed
- * node.</li>
- * <li>Because of <em>cloning</em> the {@link Node} supplied with notifications to a particular
- * listener will vary, but because they all represent the same GL program location the events should
- * be treated as equivalent for most purposes.</li>
- * </ul>
- * <p>
- * <h4>Access to execution state via {@link ASTInstrumentListener}:</h4>
+ * <h4>Implementation Notes: the Life Cycle of an {@link Instrument} at a {@link Probe}</h4>
  * <p>
  * <ul>
- * <li>Notification arguments provide primary access to the GL program's execution states:
- * <ul>
- * <li>{@link Node}: the concrete node (in one of the AST's clones) from which the event originated.
+ * <li>A new Instrument is created in permanent association with a client-provided
+ * <em>listener.</em></li>
+ * <li>Multiple Instruments may share a single listener.</li>
+ * <li>An Instrument does nothing until it is {@linkplain Probe#attach(Instrument) attached} to a
+ * Probe, at which time the Instrument begins routing execution events from the Probe's AST location
+ * to the Instrument's listener.</li>
+ * <li>Neither Instruments nor Probes are {@link Node}s.</li>
+ * <li>A Probe has a single source-based location in an AST, but manages a separate
+ * <em>instrumentation chain</em> of Nodes at the equivalent location in each clone of the AST.</li>
+ * <li>When a probed AST is cloned, the instrumentation chain associated with each Probe is cloned
+ * along with the rest of the AST.</li>
+ * <li>When a new Instrument (for example an instance of {@link SimpleInstrument} is attached to a
+ * Probe, the Instrument inserts a new instance of its private Node type,
+ * {@link SimpleInstrument.SimpleInstrumentNode}, into <em>each of the instrument chains</em>
+ * managed by the Probe, i.e. one node instance per existing clone of the AST.</li>
+ * <li>If an Instrument is attached to a Probe in an AST that subsequently gets cloned, then the
+ * Instrument's private Node type will be cloned along with the rest of the the AST.</li>
+ * <li>Each Instrument's private Node type is a dynamic inner class whose only state is in the
+ * shared (outer) Instrument instance; that state includes a reference to the Instrument's listener.
  * </li>
- * <li>{@link VirtualFrame}: the current execution frame.
- * </ul>
- * <li>Truffle global information is available, for example the execution
- * {@linkplain TruffleRuntime#iterateFrames(FrameInstanceVisitor) stack}.</li>
- * <li>Additional API access to execution state may be added in the future.</li>
+ * <li>When an Instrument that has been attached to a Probe is {@linkplain #dispose() disposed}, the
+ * Instrument searches every instrument chain associated with the Probe and removes the instance of
+ * its private Node type.</li>
+ * <li>Attaching and disposing an Instrument at a Probe <em>deoptimizes</em> any compilations of the
+ * AST.</li>
  * </ul>
- * <p>
- * <h4>Activating and deactivating Instruments:</h4>
- * <p>
- * Instruments are <em>single-use</em>:
- * <ul>
- * <li>An instrument becomes active only when <em>attached</em> to a Probe via
- * {@link Probe#attach(Instrument)}, and it may only be attached to a single Probe. It is a runtime
- * error to attempt attaching a previously attached instrument.</li>
- * <li>Attaching an instrument modifies every existing clone of the AST to which it is being
- * attached, which can trigger deoptimization.</li>
- * <li>The method {@link Instrument#dispose()} makes an instrument inactive by removing it from the
- * Probe to which it was attached and rendering it permanently inert.</li>
- * <li>Disposal removes the implementation of an instrument from all ASTs to which it was attached,
- * which can trigger deoptimization.</li>
- * </ul>
- * <p>
- * <h4>Sharing listeners:</h4>
- * <p>
- * Although an Instrument may only be attached to a single Probe, a listener can be shared among
- * multiple Instruments. This can be useful for observing events that might happen at different
- * locations in a single AST, for example all assignments to a particular variable. In this case a
- * new Instrument would be created and attached at each assignment node, but all the Instruments
- * would be created with the same listener.
- * <p>
- * <strong>Disclaimer:</strong> experimental; under development.
  *
  * @see Probe
  * @see TruffleEvents
@@ -177,21 +99,21 @@
      * @param instrumentInfo optional description of the instrument's role, useful for debugging.
      * @return a new instrument, ready for attachment at a probe
      */
-    public static Instrument create(InstrumentListener listener, String instrumentInfo) {
-        return new BasicInstrument(listener, instrumentInfo);
+    public static Instrument create(SimpleInstrumentListener listener, String instrumentInfo) {
+        return new SimpleInstrument(listener, instrumentInfo);
     }
 
     /**
      * Creates an instrument that will route execution events to a listener, along with access to
      * internal execution state.
      *
-     * @param astListener a listener for event generated by the instrument that provides access to
-     *            internal execution state
+     * @param standardListener a listener for event generated by the instrument that provides access
+     *            to internal execution state
      * @param instrumentInfo optional description of the instrument's role, useful for debugging.
      * @return a new instrument, ready for attachment at a probe
      */
-    public static Instrument create(ASTInstrumentListener astListener, String instrumentInfo) {
-        return new ASTInstrument(astListener, instrumentInfo);
+    public static Instrument create(StandardInstrumentListener standardListener, String instrumentInfo) {
+        return new StandardInstrument(standardListener, instrumentInfo);
     }
 
     /**
@@ -232,8 +154,7 @@
     }
 
     /**
-     * Removes this instrument (and any clones) from the probe to which it attached and renders the
-     * instrument inert.
+     * Removes this instrument from the probe to which it attached and renders the instrument inert.
      *
      * @throws IllegalStateException if this instrument has already been disposed
      */
@@ -266,23 +187,23 @@
     abstract AbstractInstrumentNode addToChain(AbstractInstrumentNode nextNode);
 
     /**
-     * An instrument that propagates events to an instance of {@link InstrumentListener}.
+     * An instrument that propagates events to an instance of {@link SimpleInstrumentListener}.
      */
-    private static final class BasicInstrument extends Instrument {
+    private static final class SimpleInstrument extends Instrument {
 
         /**
          * Tool-supplied listener for events.
          */
-        private final InstrumentListener instrumentListener;
+        private final SimpleInstrumentListener simpleListener;
 
-        private BasicInstrument(InstrumentListener basicListener, String instrumentInfo) {
+        private SimpleInstrument(SimpleInstrumentListener simpleListener, String instrumentInfo) {
             super(instrumentInfo);
-            this.instrumentListener = basicListener;
+            this.simpleListener = simpleListener;
         }
 
         @Override
         AbstractInstrumentNode addToChain(AbstractInstrumentNode nextNode) {
-            return new BasicInstrumentNode(nextNode);
+            return new SimpleInstrumentNode(nextNode);
         }
 
         @Override
@@ -294,7 +215,7 @@
                     return instrumentNode.nextInstrumentNode;
                 }
                 // Match not at the head of the chain; remove it.
-                found = instrumentNode.removeFromChain(BasicInstrument.this);
+                found = instrumentNode.removeFromChain(SimpleInstrument.this);
             }
             if (!found) {
                 throw new IllegalStateException("Couldn't find instrument node to remove: " + this);
@@ -303,35 +224,35 @@
         }
 
         @NodeInfo(cost = NodeCost.NONE)
-        private final class BasicInstrumentNode extends AbstractInstrumentNode {
+        private final class SimpleInstrumentNode extends AbstractInstrumentNode {
 
-            private BasicInstrumentNode(AbstractInstrumentNode nextNode) {
+            private SimpleInstrumentNode(AbstractInstrumentNode nextNode) {
                 super(nextNode);
             }
 
             public void enter(Node node, VirtualFrame vFrame) {
-                BasicInstrument.this.instrumentListener.enter(BasicInstrument.this.probe);
+                SimpleInstrument.this.simpleListener.enter(SimpleInstrument.this.probe);
                 if (nextInstrumentNode != null) {
                     nextInstrumentNode.enter(node, vFrame);
                 }
             }
 
             public void returnVoid(Node node, VirtualFrame vFrame) {
-                BasicInstrument.this.instrumentListener.returnVoid(BasicInstrument.this.probe);
+                SimpleInstrument.this.simpleListener.returnVoid(SimpleInstrument.this.probe);
                 if (nextInstrumentNode != null) {
                     nextInstrumentNode.returnVoid(node, vFrame);
                 }
             }
 
             public void returnValue(Node node, VirtualFrame vFrame, Object result) {
-                BasicInstrument.this.instrumentListener.returnValue(BasicInstrument.this.probe, result);
+                SimpleInstrument.this.simpleListener.returnValue(SimpleInstrument.this.probe, result);
                 if (nextInstrumentNode != null) {
                     nextInstrumentNode.returnValue(node, vFrame, result);
                 }
             }
 
             public void returnExceptional(Node node, VirtualFrame vFrame, Exception exception) {
-                BasicInstrument.this.instrumentListener.returnExceptional(BasicInstrument.this.probe, exception);
+                SimpleInstrument.this.simpleListener.returnExceptional(SimpleInstrument.this.probe, exception);
                 if (nextInstrumentNode != null) {
                     nextInstrumentNode.returnExceptional(node, vFrame, exception);
                 }
@@ -339,7 +260,7 @@
 
             public String instrumentationInfo() {
                 final String info = getInstrumentInfo();
-                return info != null ? info : instrumentListener.getClass().getSimpleName();
+                return info != null ? info : simpleListener.getClass().getSimpleName();
             }
         }
     }
@@ -350,23 +271,23 @@
     abstract AbstractInstrumentNode removeFromChain(AbstractInstrumentNode instrumentNode);
 
     /**
-     * An instrument that propagates events to an instance of {@link ASTInstrumentListener}.
+     * An instrument that propagates events to an instance of {@link StandardInstrumentListener}.
      */
-    private static final class ASTInstrument extends Instrument {
+    private static final class StandardInstrument extends Instrument {
 
         /**
          * Tool-supplied listener for AST events.
          */
-        private final ASTInstrumentListener astListener;
+        private final StandardInstrumentListener standardListener;
 
-        private ASTInstrument(ASTInstrumentListener astListener, String instrumentInfo) {
+        private StandardInstrument(StandardInstrumentListener standardListener, String instrumentInfo) {
             super(instrumentInfo);
-            this.astListener = astListener;
+            this.standardListener = standardListener;
         }
 
         @Override
         AbstractInstrumentNode addToChain(AbstractInstrumentNode nextNode) {
-            return new ASTInstrumentNode(nextNode);
+            return new StandardInstrumentNode(nextNode);
         }
 
         @Override
@@ -378,7 +299,7 @@
                     return instrumentNode.nextInstrumentNode;
                 }
                 // Match not at the head of the chain; remove it.
-                found = instrumentNode.removeFromChain(ASTInstrument.this);
+                found = instrumentNode.removeFromChain(StandardInstrument.this);
             }
             if (!found) {
                 throw new IllegalStateException("Couldn't find instrument node to remove: " + this);
@@ -387,35 +308,35 @@
         }
 
         @NodeInfo(cost = NodeCost.NONE)
-        private final class ASTInstrumentNode extends AbstractInstrumentNode {
+        private final class StandardInstrumentNode extends AbstractInstrumentNode {
 
-            private ASTInstrumentNode(AbstractInstrumentNode nextNode) {
+            private StandardInstrumentNode(AbstractInstrumentNode nextNode) {
                 super(nextNode);
             }
 
             public void enter(Node node, VirtualFrame vFrame) {
-                ASTInstrument.this.astListener.enter(ASTInstrument.this.probe, node, vFrame);
+                standardListener.enter(StandardInstrument.this.probe, node, vFrame);
                 if (nextInstrumentNode != null) {
                     nextInstrumentNode.enter(node, vFrame);
                 }
             }
 
             public void returnVoid(Node node, VirtualFrame vFrame) {
-                ASTInstrument.this.astListener.returnVoid(ASTInstrument.this.probe, node, vFrame);
+                standardListener.returnVoid(StandardInstrument.this.probe, node, vFrame);
                 if (nextInstrumentNode != null) {
                     nextInstrumentNode.returnVoid(node, vFrame);
                 }
             }
 
             public void returnValue(Node node, VirtualFrame vFrame, Object result) {
-                ASTInstrument.this.astListener.returnValue(ASTInstrument.this.probe, node, vFrame, result);
+                standardListener.returnValue(StandardInstrument.this.probe, node, vFrame, result);
                 if (nextInstrumentNode != null) {
                     nextInstrumentNode.returnValue(node, vFrame, result);
                 }
             }
 
             public void returnExceptional(Node node, VirtualFrame vFrame, Exception exception) {
-                ASTInstrument.this.astListener.returnExceptional(ASTInstrument.this.probe, node, vFrame, exception);
+                standardListener.returnExceptional(StandardInstrument.this.probe, node, vFrame, exception);
                 if (nextInstrumentNode != null) {
                     nextInstrumentNode.returnExceptional(node, vFrame, exception);
                 }
@@ -423,14 +344,14 @@
 
             public String instrumentationInfo() {
                 final String info = getInstrumentInfo();
-                return info != null ? info : astListener.getClass().getSimpleName();
+                return info != null ? info : standardListener.getClass().getSimpleName();
             }
         }
-
     }
 
+    // TODO (mlvdv) EXPERIMENTAL- UNDER DEVELOPMENT
     /**
-     * An instrument that propagates events to an instance of {@link ASTInstrumentListener}.
+     * An instrument that propagates events to an instance of {@link StandardInstrumentListener}.
      */
     private static final class ToolNodeInstrument extends Instrument {
 
@@ -524,7 +445,6 @@
                 return info != null ? info : toolNodeListener.getClass().getSimpleName();
             }
         }
-
     }
 
     public interface TruffleOptListener {
@@ -605,7 +525,6 @@
                 return info != null ? info : toolOptListener.getClass().getSimpleName();
             }
         }
-
     }
 
     @NodeInfo(cost = NodeCost.NONE)
@@ -658,7 +577,5 @@
         protected String getInstrumentInfo() {
             return Instrument.this.instrumentInfo;
         }
-
     }
-
 }
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/InstrumentListener.java	Mon Apr 13 21:50:37 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 com.oracle.truffle.api.instrument;
-
-/**
- * A listener of Truffle execution events that can collect information on behalf of an external
- * tool. Contextual information about the source of the event, if not stored in the implementation
- * of the listener, can be obtained via access to the {@link Probe} that generates the event.
- */
-public interface InstrumentListener {
-
-    /**
-     * Receive notification that an AST node's execute method is about to be called.
-     */
-    void enter(Probe probe);
-
-    /**
-     * Receive notification that an AST Node's {@code void}-valued execute method has just returned.
-     */
-    void returnVoid(Probe probe);
-
-    /**
-     * Receive notification that an AST Node's execute method has just returned a value (boxed if
-     * primitive).
-     */
-    void returnValue(Probe probe, Object result);
-
-    /**
-     * Receive notification that an AST Node's execute method has just thrown an exception.
-     */
-    void returnExceptional(Probe probe, Exception exception);
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/InstrumentationNode.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/InstrumentationNode.java	Mon Apr 13 21:51:19 2015 +0200
@@ -28,8 +28,9 @@
 import com.oracle.truffle.api.nodes.*;
 
 /**
- * A marker interface for Truffle {@linkplain Node nodes} that support <em>Instrumentation</em> and
- * are should not be part of any Guest Language execution semantics.
+ * A marker interface for Truffle {@linkplain Node nodes} that internally implement the
+ * <em>Instrumentation Framework</em>. Such nodes should not be part of any Guest Language execution
+ * semantics, and should in general not be visible to ordinary Instrumentation clients.
  */
 public interface InstrumentationNode {
 
@@ -39,7 +40,8 @@
     String instrumentationInfo();
 
     /**
-     * Events at a Truffle node that get propagated through the Instrumentation Framework.
+     * Events that propagate through the {@linkplain InstrumentationNode implementation nodes} of
+     * the Instrumentation Framework, not visible in this form to Instrumentation clients.
      */
     interface TruffleEvents {
 
@@ -62,6 +64,5 @@
          * An AST Node's execute method has just thrown an exception.
          */
         void returnExceptional(Node node, VirtualFrame vFrame, Exception exception);
-
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/SimpleInstrumentListener.java	Mon Apr 13 21:51:19 2015 +0200
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2015, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 com.oracle.truffle.api.instrument;
+
+import com.oracle.truffle.api.source.*;
+
+/**
+ * A receiver of Truffle execution events that can act on behalf of an external client.
+ * <p>
+ * The {@link Probe} instance provides access to the {@link SourceSection} associated with the
+ * event, as well as any {@link SyntaxTag}s that have been applied at that program's location.
+ * <p>
+ * This is the simplest kind of listener, suitable for clients that need no other information about
+ * the program's execution state at the time of the event. Clients that require access to the AST
+ * execution state should use {@link StandardInstrumentListener}.
+ * <p>
+ * Clients are free, of course, to record additional information in the listener implementation that
+ * carries additional information about the context and reason for the particular {@link Instrument}
+ * that is to be created from the listener.
+ */
+public interface SimpleInstrumentListener {
+
+    /**
+     * Receive notification that a program location is about to be executed.
+     * <p>
+     * <strong>Synchronous</strong>: Truffle execution waits until the call returns.
+     */
+    void enter(Probe probe);
+
+    /**
+     * Receive notification that a program location's {@code void}-valued execution has just
+     * completed.
+     * <p>
+     * <strong>Synchronous</strong>: Truffle execution waits until the call returns.
+     */
+    void returnVoid(Probe probe);
+
+    /**
+     * Receive notification that a program location's execution has just completed and returned a
+     * value (boxed if primitive).
+     * <p>
+     * <strong>Synchronous</strong>: Truffle execution waits until the call returns.
+     */
+    void returnValue(Probe probe, Object result);
+
+    /**
+     * Receive notification that a program location's execution has just thrown an exception.
+     * <p>
+     * <strong>Synchronous</strong>: Truffle execution waits until the call returns.
+     */
+    void returnExceptional(Probe probe, Exception exception);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/StandardInstrumentListener.java	Mon Apr 13 21:51:19 2015 +0200
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 com.oracle.truffle.api.instrument;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+
+/**
+ * A receiver of Truffle execution events that can act on behalf of an external client.
+ * <p>
+ * The {@link Probe} argument provides access to the {@link SourceSection} associated with the
+ * event, as well as any {@link SyntaxTag}s that have been applied at that AST node.
+ * <p>
+ * This listener is designed for clients that also require access to the AST execution state at the
+ * time of the event. Clients that do not require access to the AST execution state should use the
+ * {@link SimpleInstrumentListener}.
+ * <p>
+ * Clients are free, of course, to record additional information in the listener implementation that
+ * carries additional information about the context and reason for the particular {@link Instrument}
+ * that is to be created from the listener.
+ */
+public interface StandardInstrumentListener {
+
+    /**
+     * Receive notification that an AST node's execute method is about to be called.
+     * <p>
+     * <strong>Synchronous</strong>: Truffle execution waits until the call returns.
+     */
+    void enter(Probe probe, Node node, VirtualFrame vFrame);
+
+    /**
+     * Receive notification that an AST Node's {@code void}-valued execute method has just returned.
+     * <p>
+     * <strong>Synchronous</strong>: Truffle execution waits until the call returns.
+     */
+    void returnVoid(Probe probe, Node node, VirtualFrame vFrame);
+
+    /**
+     * Receive notification that an AST Node's execute method has just returned a value (boxed if
+     * primitive).
+     * <p>
+     * <strong>Synchronous</strong>: Truffle execution waits until the call returns.
+     */
+    void returnValue(Probe probe, Node node, VirtualFrame vFrame, Object result);
+
+    /**
+     * Receive notification that an AST Node's execute method has just thrown an exception.
+     * <p>
+     * <strong>Synchronous</strong>: Truffle execution waits until the call returns.
+     */
+    void returnExceptional(Probe probe, Node node, VirtualFrame vFrame, Exception exception);
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultASTInstrumentListener.java	Mon Apr 13 21:50:37 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 com.oracle.truffle.api.instrument.impl;
-
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.nodes.*;
-
-/**
- * A listener for AST {@linkplain ASTInstrumentListener execution events} that provides a no-op
- * implementation of every event.
- */
-public class DefaultASTInstrumentListener implements ASTInstrumentListener {
-
-    public void enter(Probe probe, Node node, VirtualFrame vFrame) {
-    }
-
-    public void returnVoid(Probe probe, Node node, VirtualFrame vFrame) {
-    }
-
-    public void returnValue(Probe probe, Node node, VirtualFrame vFrame, Object result) {
-    }
-
-    public void returnExceptional(Probe probe, Node node, VirtualFrame vFrame, Exception exception) {
-    }
-
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultInstrumentListener.java	Mon Apr 13 21:50:37 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 com.oracle.truffle.api.instrument.impl;
-
-import com.oracle.truffle.api.instrument.*;
-
-/**
- * A listener for Truffle execution events that provides a no-op implementation of every event.
- */
-public class DefaultInstrumentListener implements InstrumentListener {
-
-    public void enter(Probe probe) {
-    }
-
-    public void returnVoid(Probe probe) {
-    }
-
-    public void returnValue(Probe probe, Object result) {
-    }
-
-    public void returnExceptional(Probe probe, Exception exception) {
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultSimpleInstrumentListener.java	Mon Apr 13 21:51:19 2015 +0200
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 com.oracle.truffle.api.instrument.impl;
+
+import com.oracle.truffle.api.instrument.*;
+
+/**
+ * A listener for Truffle execution events that provides a no-op implementation of every event.
+ */
+public class DefaultSimpleInstrumentListener implements SimpleInstrumentListener {
+
+    public void enter(Probe probe) {
+    }
+
+    public void returnVoid(Probe probe) {
+    }
+
+    public void returnValue(Probe probe, Object result) {
+    }
+
+    public void returnExceptional(Probe probe, Exception exception) {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultStandardInstrumentListener.java	Mon Apr 13 21:51:19 2015 +0200
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 com.oracle.truffle.api.instrument.impl;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.nodes.*;
+
+/**
+ * A listener for AST {@linkplain StandardInstrumentListener execution events} that provides a no-op
+ * implementation of every event.
+ */
+public class DefaultStandardInstrumentListener implements StandardInstrumentListener {
+
+    public void enter(Probe probe, Node node, VirtualFrame vFrame) {
+    }
+
+    public void returnVoid(Probe probe, Node node, VirtualFrame vFrame) {
+    }
+
+    public void returnValue(Probe probe, Node node, VirtualFrame vFrame, Object result) {
+    }
+
+    public void returnExceptional(Probe probe, Node node, VirtualFrame vFrame, Exception exception) {
+    }
+
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/SimpleASTInstrumentListener.java	Mon Apr 13 21:50:37 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/*
- * Copyright (c) 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 com.oracle.truffle.api.instrument.impl;
-
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.nodes.*;
-
-/**
- * An abstract listener for AST {@linkplain ASTInstrumentListener execution events} that ignores
- * return values and supports handling all events by overriding only two methods:
- * <ul>
- * <li>{@link #enter(Probe, Node, VirtualFrame)}, and</li>
- * <li>{@link #returnAny(Probe, Node, VirtualFrame)}.</li>
- * </ul>
- */
-public abstract class SimpleASTInstrumentListener implements ASTInstrumentListener {
-
-    public void enter(Probe probe, Node node, VirtualFrame vFrame) {
-    }
-
-    /**
-     * Receive notification that one of an AST Node's execute methods has just returned by any
-     * means: with or without a return value (ignored) or via exception (ignored).
-     *
-     * @param probe where the event originated
-     * @param node specific node of the event
-     * @param vFrame
-     */
-    protected void returnAny(Probe probe, Node node, VirtualFrame vFrame) {
-    }
-
-    public final void returnVoid(Probe probe, Node node, VirtualFrame vFrame) {
-        returnAny(probe, node, vFrame);
-    }
-
-    public final void returnValue(Probe probe, Node node, VirtualFrame vFrame, Object result) {
-        returnAny(probe, node, vFrame);
-    }
-
-    public final void returnExceptional(Probe probe, Node node, VirtualFrame vFrame, Exception e) {
-        returnAny(probe, node, vFrame);
-    }
-
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/SimpleInstrumentListener.java	Mon Apr 13 21:50:37 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 com.oracle.truffle.api.instrument.impl;
-
-import com.oracle.truffle.api.instrument.*;
-
-/**
- * An abstract listener for Truffle {@linkplain InstrumentListener execution events} that ignores
- * return values and supports handling all events by overriding only two methods:
- * <ul>
- * <li>{@link #enter(Probe)}, and</li>
- * <li>{@link #returnAny(Probe)}.</li>
- * </ul>
- */
-public abstract class SimpleInstrumentListener implements InstrumentListener {
-
-    public void enter(Probe probe) {
-    }
-
-    /**
-     * Receive notification that an execute method has just returned by any means: with or without a
-     * return value (ignored) or via exception (ignored).
-     *
-     * @param probe
-     */
-    protected void returnAny(Probe probe) {
-    }
-
-    public final void returnVoid(Probe probe) {
-        returnAny(probe);
-    }
-
-    public final void returnValue(Probe probe, Object result) {
-        returnAny(probe);
-    }
-
-    public final void returnExceptional(Probe probe, Exception e) {
-        returnAny(probe);
-    }
-
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/CoverageTracker.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/CoverageTracker.java	Mon Apr 13 21:51:19 2015 +0200
@@ -48,7 +48,7 @@
  * <p>
  * <ul>
  * <li>"Execution call" on a node is is defined as invocation of a node method that is instrumented
- * to produce the event {@link InstrumentListener#enter(Probe)};</li>
+ * to produce the event {@link SimpleInstrumentListener#enter(Probe)};</li>
  * <li>Execution calls are tabulated only at <em>instrumented</em> nodes, i.e. those for which
  * {@linkplain Node#isInstrumentable() isInstrumentable() == true};</li>
  * <li>Execution calls are tabulated only at nodes present in the AST when originally created;
@@ -226,7 +226,7 @@
      * A listener for events at each instrumented AST location. This listener counts
      * "execution calls" to the instrumented node.
      */
-    private final class CoverageRecord extends DefaultInstrumentListener {
+    private final class CoverageRecord extends DefaultSimpleInstrumentListener {
 
         private final SourceSection srcSection; // The text of the code being counted
         private Instrument instrument;  // The attached Instrument, in case need to remove.
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/NodeExecCounter.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/NodeExecCounter.java	Mon Apr 13 21:51:19 2015 +0200
@@ -49,7 +49,7 @@
  * <p>
  * <ul>
  * <li>"Execution call" on a node is is defined as invocation of a node method that is instrumented
- * to produce the event {@link ASTInstrumentListener#enter(Probe, Node, VirtualFrame)};</li>
+ * to produce the event {@link StandardInstrumentListener#enter(Probe, Node, VirtualFrame)};</li>
  * <li>Execution calls are tabulated only at <em>instrumented</em> nodes, i.e. those for which
  * {@linkplain Node#isInstrumentable() isInstrumentable() == true};</li>
  * <li>Execution calls are tabulated only at nodes present in the AST when originally created;
@@ -95,7 +95,7 @@
      * Listener for events at instrumented nodes. Counts are maintained in a shared table, so the
      * listener is stateless and can be shared by every {@link Instrument}.
      */
-    private final ASTInstrumentListener instrumentListener = new DefaultASTInstrumentListener() {
+    private final StandardInstrumentListener instrumentListener = new DefaultStandardInstrumentListener() {
         @Override
         public void enter(Probe probe, Node node, VirtualFrame vFrame) {
             if (isEnabled()) {
--- a/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestRunner.java	Mon Apr 13 21:50:37 2015 +0200
+++ b/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestRunner.java	Mon Apr 13 21:51:19 2015 +0200
@@ -274,7 +274,7 @@
      * attached at {@link SLWriteLocalVariableNode}, but provides no guards to protect it from being
      * attached elsewhere.
      */
-    public final class SLPrintAssigmentValueListener extends DefaultInstrumentListener {
+    public final class SLPrintAssigmentValueListener extends DefaultSimpleInstrumentListener {
 
         private PrintStream output;
 
--- a/src/share/vm/graal/graalCompilerToVM.cpp	Mon Apr 13 21:50:37 2015 +0200
+++ b/src/share/vm/graal/graalCompilerToVM.cpp	Mon Apr 13 21:51:19 2015 +0200
@@ -154,7 +154,7 @@
     }
   }
 
-  return (jbyteArray) JNIHandles::make_local(reconstituted_code);
+  return (jbyteArray) JNIHandles::make_local(THREAD, reconstituted_code);
 C2V_END
 
 C2V_VMENTRY(jint, exceptionTableLength, (JNIEnv *, jobject, jlong metaspace_method))
@@ -576,7 +576,7 @@
   }
 
   Handle result = java_lang_String::create_from_platform_dependent_str(st.as_string(), CHECK_NULL);
-  return JNIHandles::make_local(result());
+  return JNIHandles::make_local(THREAD, result());
 C2V_END
 
 C2V_VMENTRY(jobject, getStackTraceElement, (JNIEnv*, jobject, jlong metaspace_method, int bci))
@@ -585,7 +585,7 @@
 
   methodHandle method = asMethod(metaspace_method);
   oop element = java_lang_StackTraceElement::create(method, bci, CHECK_NULL);
-  return JNIHandles::make_local(element);
+  return JNIHandles::make_local(THREAD, element);
 C2V_END
 
 C2V_VMENTRY(jobject, executeCompiledMethodVarargs, (JNIEnv*, jobject, jobject args, jobject hotspotInstalledCode))
@@ -609,7 +609,7 @@
   if (jap.get_ret_type() == T_VOID) {
     return NULL;
   } else if (jap.get_ret_type() == T_OBJECT || jap.get_ret_type() == T_ARRAY) {
-    return JNIHandles::make_local((oop) result.get_jobject());
+    return JNIHandles::make_local(THREAD, (oop) result.get_jobject());
   } else {
     jvalue *value = (jvalue *) result.get_value_addr();
     // Narrow the value down if required (Important on big endian machines)
@@ -628,7 +628,7 @@
        break;
      }
     oop o = java_lang_boxing_object::create(jap.get_ret_type(), value, CHECK_NULL);
-    return JNIHandles::make_local(o);
+    return JNIHandles::make_local(THREAD, o);
   }
 C2V_END
 
@@ -656,7 +656,7 @@
     i += 2;
   }
 
-  return (jlongArray) JNIHandles::make_local(result);
+  return (jlongArray) JNIHandles::make_local(THREAD, result);
 C2V_END
 
 C2V_VMENTRY(jlong, getLocalVariableTableStart, (JNIEnv *, jobject, jlong metaspace_method))
@@ -709,9 +709,9 @@
   InstalledCode::set_address(hotspotInstalledCode, 0);
 C2V_END
 
-C2V_VMENTRY(jobject, getJavaMirror, (JNIEnv*, jobject, jlong metaspace_klass))
+C2V_VMENTRY(jobject, getJavaMirror, (JNIEnv* env, jobject, jlong metaspace_klass))
   Klass* klass = asKlass(metaspace_klass);
-  return JNIHandles::make_local(klass->java_mirror());
+  return JNIHandles::make_local(THREAD, klass->java_mirror());
 C2V_END
 
 C2V_VMENTRY(jlong, readUnsafeKlassPointer, (JNIEnv*, jobject, jobject o))
@@ -722,13 +722,13 @@
 
 C2V_VMENTRY(jobject, readUncompressedOop, (JNIEnv*, jobject, jlong addr))
   oop ret = oopDesc::load_decode_heap_oop((oop*)(address)addr);
-  return JNIHandles::make_local(ret);
+  return JNIHandles::make_local(THREAD, ret);
 C2V_END
 
 C2V_VMENTRY(jlongArray, collectCounters, (JNIEnv*, jobject))
   typeArrayOop arrayOop = oopFactory::new_longArray(GraalCounterSize, CHECK_NULL);
   JavaThread::collect_counters(arrayOop);
-  return (jlongArray) JNIHandles::make_local(arrayOop);
+  return (jlongArray) JNIHandles::make_local(THREAD, arrayOop);
 C2V_END
 
 C2V_VMENTRY(int, allocateCompileId, (JNIEnv*, jobject, jlong metaspace_method, int entry_bci))
--- a/src/share/vm/graal/graalRuntime.cpp	Mon Apr 13 21:50:37 2015 +0200
+++ b/src/share/vm/graal/graalRuntime.cpp	Mon Apr 13 21:51:19 2015 +0200
@@ -647,7 +647,7 @@
   TempNewSymbol sig = SymbolTable::new_symbol("()Lcom/oracle/truffle/api/TruffleRuntime;", CHECK_NULL);
   JavaValue result(T_OBJECT);
   JavaCalls::call_static(&result, klass, makeInstance, sig, CHECK_NULL);
-  return JNIHandles::make_local((oop) result.get_jobject());
+  return JNIHandles::make_local(THREAD, (oop) result.get_jobject());
 JVM_END
 
 // private static NativeFunctionInterfaceRuntime.createInterface()
@@ -660,7 +660,7 @@
   TempNewSymbol sig = SymbolTable::new_symbol("()Lcom/oracle/nfi/api/NativeFunctionInterface;", CHECK_NULL);
   JavaValue result(T_OBJECT);
   JavaCalls::call_static(&result, klass, makeInstance, sig, CHECK_NULL);
-  return JNIHandles::make_local((oop) result.get_jobject());
+  return JNIHandles::make_local(THREAD, (oop) result.get_jobject());
 JVM_END
 
 void GraalRuntime::check_generated_sources_sha1(TRAPS) {