changeset 20106:2e3cc2a27711

Truffle/Instrumentation: a new flavor of Instrument that lazily provides an AST fragment to be attached/adopted directly into a running AST, and to which execution event notifications will be routed. Important use cases so far include conditional breakpoints (with optimizeable conditions) and Ruby set_trace_func.
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Tue, 31 Mar 2015 19:01:07 -0700
parents e73096245a4c
children 2c65cac3d940
files graal/com.oracle.graal.truffle.test/src/com/oracle/graal/truffle/test/InstrumentationPartialEvaluationTest.java graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/InstrumentationTest.java graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/InstrumentationTestNodes.java graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/ToolNodeInstrumentationTest.java graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TestNodes.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ToolNode.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ToolNodeInstrumentListener.java
diffstat 8 files changed, 582 insertions(+), 180 deletions(-) [+]
line wrap: on
line diff
--- a/graal/com.oracle.graal.truffle.test/src/com/oracle/graal/truffle/test/InstrumentationPartialEvaluationTest.java	Tue Mar 31 18:58:36 2015 -0700
+++ b/graal/com.oracle.graal.truffle.test/src/com/oracle/graal/truffle/test/InstrumentationPartialEvaluationTest.java	Tue Mar 31 19:01:07 2015 -0700
@@ -205,6 +205,47 @@
     }
 
     @Test
+    public void constantValueInertToolNodeInstrumentListener() {
+        FrameDescriptor fd = new FrameDescriptor();
+        AbstractTestNode result = new ConstantTestNode(42);
+        RootTestNode root = new RootTestNode(fd, "constantValue", result);
+        root.adoptChildren();
+        Probe probe = result.probe();
+        // A listener that could insert a "tool node" into the AST, but which never does.
+        Instrument instrument = Instrument.create(new ToolNodeInstrumentListener() {
+
+            public ToolNode getToolNode(Probe p) {
+                return null;
+            }
+
+        }, null);
+        probe.attach(instrument);
+
+        assertPartialEvalEquals("constant42", root);
+    }
+
+    @Test
+    public void constantValueInertToolNode() {
+        FrameDescriptor fd = new FrameDescriptor();
+        AbstractTestNode result = new ConstantTestNode(42);
+        RootTestNode root = new RootTestNode(fd, "constantValue", result);
+        root.adoptChildren();
+        Probe probe = result.probe();
+        // A listener that inserts a "tool node" with empty methods into the AST.
+        Instrument instrument = Instrument.create(new ToolNodeInstrumentListener() {
+
+            public ToolNode getToolNode(Probe p) {
+                return new ToolNode() {
+                };
+            }
+
+        }, null);
+        probe.attach(instrument);
+
+        assertPartialEvalEquals("constant42", root);
+    }
+
+    @Test
     public void instrumentDeopt() {
         final FrameDescriptor fd = new FrameDescriptor();
         final AbstractTestNode result = new ConstantTestNode(42);
--- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/InstrumentationTest.java	Tue Mar 31 18:58:36 2015 -0700
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/InstrumentationTest.java	Tue Mar 31 19:01:07 2015 -0700
@@ -35,6 +35,11 @@
 import com.oracle.truffle.api.instrument.ProbeNode.WrapperNode;
 import com.oracle.truffle.api.instrument.impl.*;
 import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.test.instrument.InstrumentationTestNodes.TestAdditionNode;
+import com.oracle.truffle.api.test.instrument.InstrumentationTestNodes.TestLanguageNode;
+import com.oracle.truffle.api.test.instrument.InstrumentationTestNodes.TestLanguageWrapperNode;
+import com.oracle.truffle.api.test.instrument.InstrumentationTestNodes.TestRootNode;
+import com.oracle.truffle.api.test.instrument.InstrumentationTestNodes.TestValueNode;
 
 /**
  * <h3>AST Instrumentation</h3>
@@ -339,142 +344,6 @@
 
     }
 
-    private abstract class TestLanguageNode extends Node {
-        public abstract Object execute(VirtualFrame vFrame);
-
-        @Override
-        public boolean isInstrumentable() {
-            return true;
-        }
-
-        @Override
-        public WrapperNode createWrapperNode() {
-            return new TestLanguageWrapperNode(this);
-        }
-    }
-
-    @NodeInfo(cost = NodeCost.NONE)
-    private class TestLanguageWrapperNode extends TestLanguageNode implements WrapperNode {
-        @Child private TestLanguageNode child;
-        @Child private ProbeNode probeNode;
-
-        public TestLanguageWrapperNode(TestLanguageNode child) {
-            assert !(child instanceof TestLanguageWrapperNode);
-            this.child = child;
-        }
-
-        @Override
-        public String instrumentationInfo() {
-            return "Wrapper node for testing";
-        }
-
-        @Override
-        public boolean isInstrumentable() {
-            return false;
-        }
-
-        @Override
-        public void insertProbe(ProbeNode newProbeNode) {
-            this.probeNode = newProbeNode;
-        }
-
-        @Override
-        public Probe getProbe() {
-            return probeNode.getProbe();
-        }
-
-        @Override
-        public Node getChild() {
-            return child;
-        }
-
-        @Override
-        public Object execute(VirtualFrame vFrame) {
-            probeNode.enter(child, vFrame);
-            Object result;
-
-            try {
-                result = child.execute(vFrame);
-                probeNode.returnValue(child, vFrame, result);
-            } catch (KillException e) {
-                throw (e);
-            } catch (Exception e) {
-                probeNode.returnExceptional(child, vFrame, e);
-                throw (e);
-            }
-
-            return result;
-        }
-    }
-
-    /**
-     * A simple node for our test language to store a value.
-     */
-    private class TestValueNode extends TestLanguageNode {
-        private final int value;
-
-        public TestValueNode(int value) {
-            this.value = value;
-        }
-
-        @Override
-        public Object execute(VirtualFrame vFrame) {
-            return new Integer(this.value);
-        }
-    }
-
-    /**
-     * A node for our test language that adds up two {@link TestValueNode}s.
-     */
-    private class TestAdditionNode extends TestLanguageNode {
-        @Child private TestLanguageNode leftChild;
-        @Child private TestLanguageNode rightChild;
-
-        public TestAdditionNode(TestValueNode leftChild, TestValueNode rightChild) {
-            this.leftChild = insert(leftChild);
-            this.rightChild = insert(rightChild);
-        }
-
-        @Override
-        public Object execute(VirtualFrame vFrame) {
-            return new Integer(((Integer) leftChild.execute(vFrame)).intValue() + ((Integer) rightChild.execute(vFrame)).intValue());
-        }
-    }
-
-    /**
-     * Truffle requires that all guest languages to have a {@link RootNode} which sits atop any AST
-     * of the guest language. This is necessary since creating a {@link CallTarget} is how Truffle
-     * completes an AST. The root nodes serves as our entry point into a program.
-     */
-    private class TestRootNode extends RootNode {
-        @Child private TestLanguageNode body;
-
-        /**
-         * This constructor emulates the global machinery that applies registered probers to every
-         * newly created AST. Global registry is not used, since that would interfere with other
-         * tests run in the same environment.
-         */
-        public TestRootNode(TestLanguageNode body) {
-            super(null);
-            this.body = body;
-        }
-
-        @Override
-        public Object execute(VirtualFrame vFrame) {
-            return body.execute(vFrame);
-        }
-
-        @Override
-        public boolean isCloningAllowed() {
-            return true;
-        }
-
-        @Override
-        public void applyInstrumentation() {
-            Probe.applyASTProbers(body);
-        }
-    }
-
     private interface TestCounter {
 
         int enterCount();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/InstrumentationTestNodes.java	Tue Mar 31 19:01:07 2015 -0700
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ *
+ * 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.test.instrument;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.ProbeNode.WrapperNode;
+import com.oracle.truffle.api.nodes.*;
+
+/**
+ * Tests instrumentation where a client can attach a node that gets attached into the AST.
+ */
+class InstrumentationTestNodes {
+
+    abstract static class TestLanguageNode extends Node {
+        public abstract Object execute(VirtualFrame vFrame);
+
+        @Override
+        public boolean isInstrumentable() {
+            return true;
+        }
+
+        @Override
+        public WrapperNode createWrapperNode() {
+            return new TestLanguageWrapperNode(this);
+        }
+    }
+
+    @NodeInfo(cost = NodeCost.NONE)
+    static class TestLanguageWrapperNode extends TestLanguageNode implements WrapperNode {
+        @Child private TestLanguageNode child;
+        @Child private ProbeNode probeNode;
+
+        public TestLanguageWrapperNode(TestLanguageNode child) {
+            assert !(child instanceof TestLanguageWrapperNode);
+            this.child = child;
+        }
+
+        @Override
+        public String instrumentationInfo() {
+            return "Wrapper node for testing";
+        }
+
+        @Override
+        public boolean isInstrumentable() {
+            return false;
+        }
+
+        @Override
+        public void insertProbe(ProbeNode newProbeNode) {
+            this.probeNode = newProbeNode;
+        }
+
+        @Override
+        public Probe getProbe() {
+            return probeNode.getProbe();
+        }
+
+        @Override
+        public Node getChild() {
+            return child;
+        }
+
+        @Override
+        public Object execute(VirtualFrame vFrame) {
+            probeNode.enter(child, vFrame);
+            Object result;
+
+            try {
+                result = child.execute(vFrame);
+                probeNode.returnValue(child, vFrame, result);
+            } catch (KillException e) {
+                throw (e);
+            } catch (Exception e) {
+                probeNode.returnExceptional(child, vFrame, e);
+                throw (e);
+            }
+
+            return result;
+        }
+    }
+
+    /**
+     * A simple node for our test language to store a value.
+     */
+    static class TestValueNode extends TestLanguageNode {
+        private final int value;
+
+        public TestValueNode(int value) {
+            this.value = value;
+        }
+
+        @Override
+        public Object execute(VirtualFrame vFrame) {
+            return new Integer(this.value);
+        }
+    }
+
+    /**
+     * A node for our test language that adds up two {@link TestValueNode}s.
+     */
+    static class TestAdditionNode extends TestLanguageNode {
+        @Child private TestLanguageNode leftChild;
+        @Child private TestLanguageNode rightChild;
+
+        public TestAdditionNode(TestValueNode leftChild, TestValueNode rightChild) {
+            this.leftChild = insert(leftChild);
+            this.rightChild = insert(rightChild);
+        }
+
+        @Override
+        public Object execute(VirtualFrame vFrame) {
+            return new Integer(((Integer) leftChild.execute(vFrame)).intValue() + ((Integer) rightChild.execute(vFrame)).intValue());
+        }
+    }
+
+    /**
+     * Truffle requires that all guest languages to have a {@link RootNode} which sits atop any AST
+     * of the guest language. This is necessary since creating a {@link CallTarget} is how Truffle
+     * completes an AST. The root nodes serves as our entry point into a program.
+     */
+    static class TestRootNode extends RootNode {
+        @Child private TestLanguageNode body;
+
+        /**
+         * This constructor emulates the global machinery that applies registered probers to every
+         * newly created AST. Global registry is not used, since that would interfere with other
+         * tests run in the same environment.
+         */
+        public TestRootNode(TestLanguageNode body) {
+            super(null);
+            this.body = body;
+        }
+
+        @Override
+        public Object execute(VirtualFrame vFrame) {
+            return body.execute(vFrame);
+        }
+
+        @Override
+        public boolean isCloningAllowed() {
+            return true;
+        }
+
+        @Override
+        public void applyInstrumentation() {
+            Probe.applyASTProbers(body);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/ToolNodeInstrumentationTest.java	Tue Mar 31 19:01:07 2015 -0700
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ * 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.test.instrument;
+
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.test.instrument.InstrumentationTestNodes.TestAdditionNode;
+import com.oracle.truffle.api.test.instrument.InstrumentationTestNodes.TestRootNode;
+import com.oracle.truffle.api.test.instrument.InstrumentationTestNodes.TestValueNode;
+
+/**
+ * Tests instrumentation where a client can attach a node that gets attached into the AST.
+ */
+public class ToolNodeInstrumentationTest {
+
+    @Test
+    public void testToolNodeListener() {
+        // Create a simple addition AST
+        final TruffleRuntime runtime = Truffle.getRuntime();
+        final TestValueNode leftValueNode = new TestValueNode(6);
+        final TestValueNode rightValueNode = new TestValueNode(7);
+        final TestAdditionNode addNode = new TestAdditionNode(leftValueNode, rightValueNode);
+        final TestRootNode rootNode = new TestRootNode(addNode);
+        final CallTarget callTarget1 = runtime.createCallTarget(rootNode);
+
+        // Ensure it executes correctly
+        assertEquals(13, callTarget1.call());
+
+        // Probe the addition node
+        final Probe probe = addNode.probe();
+
+        assertEquals(13, callTarget1.call());
+
+        // Attach a listener that never actually attaches a node.
+        final Instrument instrument = Instrument.create(new ToolNodeInstrumentListener() {
+
+            public ToolNode getToolNode(Probe p) {
+                return null;
+            }
+
+        }, null);
+        probe.attach(instrument);
+
+        assertEquals(13, callTarget1.call());
+
+        final int[] count = new int[1];
+
+        // Attach a listener that never actually attaches a node.
+        probe.attach(Instrument.create(new ToolNodeInstrumentListener() {
+
+            public ToolNode getToolNode(Probe p) {
+                return new ToolNode() {
+
+                    @Override
+                    public void enter(Node node, VirtualFrame vFrame) {
+                        count[0] = count[0] + 1;
+                    }
+                };
+            }
+
+        }, null));
+        assertEquals(0, count[0]);
+
+        assertEquals(13, callTarget1.call());
+
+        assertEquals(1, count[0]);
+
+    }
+
+}
--- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TestNodes.java	Tue Mar 31 18:58:36 2015 -0700
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TestNodes.java	Tue Mar 31 19:01:07 2015 -0700
@@ -32,7 +32,7 @@
 /**
  * Nodes and an {@linkplain CallTarget executable ASTs} for testing.
  */
-public class TestNodes {
+class TestNodes {
 
     /**
      * A fake source used for testing: empty line 1, expression on line 2.
@@ -44,7 +44,7 @@
     /**
      * An executable addition expression that evaluates to 13.
      */
-    public static CallTarget createExpr13TestCallTarget() {
+    static CallTarget createExpr13TestCallTarget() {
         final RootNode rootNode = createExpr13TestRootNode();
         return Truffle.getRuntime().createCallTarget(rootNode);
     }
@@ -52,7 +52,7 @@
     /**
      * Root holding an addition expression that evaluates to 13.
      */
-    public static RootNode createExpr13TestRootNode() {
+    static RootNode createExpr13TestRootNode() {
         final TestLanguageNode ast = createExpr13AST();
         final TestRootNode rootNode = new TestRootNode(ast);
         rootNode.adoptChildren();
@@ -62,7 +62,7 @@
     /**
      * Addition expression that evaluates to 13, with faked source attribution.
      */
-    public static TestLanguageNode createExpr13AST() {
+    static TestLanguageNode createExpr13AST() {
         final SourceSection leftSourceSection = expr13Source.createSection("left", 1, 1);
         final TestValueNode leftValueNode = new TestValueNode(6, leftSourceSection);
         final SourceSection rightSourceSection = expr13Source.createSection("right", 3, 1);
@@ -71,7 +71,7 @@
         return new TestAddNode(leftValueNode, rightValueNode, exprSourceSection);
     }
 
-    public abstract static class TestLanguageNode extends Node {
+    abstract static class TestLanguageNode extends Node {
         public abstract Object execute(VirtualFrame frame);
 
         public TestLanguageNode() {
@@ -93,7 +93,7 @@
     }
 
     @NodeInfo(cost = NodeCost.NONE)
-    public static class TestWrapperNode extends TestLanguageNode implements WrapperNode {
+    static class TestWrapperNode extends TestLanguageNode implements WrapperNode {
         @Child private TestLanguageNode child;
         @Child private ProbeNode probeNode;
 
@@ -151,7 +151,7 @@
      * of the guest language. This is necessary since creating a {@link CallTarget} is how Truffle
      * completes an AST. The root nodes serves as our entry point into a program.
      */
-    public static class TestRootNode extends RootNode {
+    static class TestRootNode extends RootNode {
         @Child private TestLanguageNode body;
 
         /**
@@ -180,7 +180,7 @@
         }
     }
 
-    public static class TestValueNode extends TestLanguageNode {
+    static class TestValueNode extends TestLanguageNode {
         private final int value;
 
         public TestValueNode(int value) {
@@ -198,7 +198,7 @@
         }
     }
 
-    public static class TestAddNode extends TestLanguageNode {
+    static class TestAddNode extends TestLanguageNode {
         @Child private TestLanguageNode leftChild;
         @Child private TestLanguageNode rightChild;
 
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.java	Tue Mar 31 18:58:36 2015 -0700
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.java	Tue Mar 31 19:01:07 2015 -0700
@@ -194,6 +194,19 @@
         return new ASTInstrument(astListener, instrumentInfo);
     }
 
+    /**
+     * Creates an instrument that, when executed the first time in any particular AST location,
+     * invites the tool to provide an AST fragment for attachment/adoption into the running AST.
+     *
+     * @param toolNodeListener a listener for the tool that can request an AST fragment
+     * @param instrumentInfo 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(ToolNodeInstrumentListener toolNodeListener, String instrumentInfo) {
+        return new ToolNodeInstrument(toolNodeListener, instrumentInfo);
+    }
+
     // TODO (mlvdv) experimental
     /**
      * For implementation testing.
@@ -278,7 +291,7 @@
             if (instrumentNode != null) {
                 if (instrumentNode.getInstrument() == this) {
                     // Found the match at the head of the chain
-                    return instrumentNode.nextInstrument;
+                    return instrumentNode.nextInstrumentNode;
                 }
                 // Match not at the head of the chain; remove it.
                 found = instrumentNode.removeFromChain(BasicInstrument.this);
@@ -298,29 +311,29 @@
 
             public void enter(Node node, VirtualFrame vFrame) {
                 BasicInstrument.this.instrumentListener.enter(BasicInstrument.this.probe);
-                if (nextInstrument != null) {
-                    nextInstrument.enter(node, vFrame);
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.enter(node, vFrame);
                 }
             }
 
             public void returnVoid(Node node, VirtualFrame vFrame) {
                 BasicInstrument.this.instrumentListener.returnVoid(BasicInstrument.this.probe);
-                if (nextInstrument != null) {
-                    nextInstrument.returnVoid(node, vFrame);
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.returnVoid(node, vFrame);
                 }
             }
 
             public void returnValue(Node node, VirtualFrame vFrame, Object result) {
                 BasicInstrument.this.instrumentListener.returnValue(BasicInstrument.this.probe, result);
-                if (nextInstrument != null) {
-                    nextInstrument.returnValue(node, vFrame, 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);
-                if (nextInstrument != null) {
-                    nextInstrument.returnExceptional(node, vFrame, exception);
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.returnExceptional(node, vFrame, exception);
                 }
             }
 
@@ -362,7 +375,7 @@
             if (instrumentNode != null) {
                 if (instrumentNode.getInstrument() == this) {
                     // Found the match at the head of the chain
-                    return instrumentNode.nextInstrument;
+                    return instrumentNode.nextInstrumentNode;
                 }
                 // Match not at the head of the chain; remove it.
                 found = instrumentNode.removeFromChain(ASTInstrument.this);
@@ -382,29 +395,29 @@
 
             public void enter(Node node, VirtualFrame vFrame) {
                 ASTInstrument.this.astListener.enter(ASTInstrument.this.probe, node, vFrame);
-                if (nextInstrument != null) {
-                    nextInstrument.enter(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);
-                if (nextInstrument != null) {
-                    nextInstrument.returnVoid(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);
-                if (nextInstrument != null) {
-                    nextInstrument.returnValue(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);
-                if (nextInstrument != null) {
-                    nextInstrument.returnExceptional(node, vFrame, exception);
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.returnExceptional(node, vFrame, exception);
                 }
             }
 
@@ -416,6 +429,104 @@
 
     }
 
+    /**
+     * An instrument that propagates events to an instance of {@link ASTInstrumentListener}.
+     */
+    private static final class ToolNodeInstrument extends Instrument {
+
+        /**
+         * Tool-supplied listener for AST events.
+         */
+        private final ToolNodeInstrumentListener toolNodeListener;
+
+        private ToolNodeInstrument(ToolNodeInstrumentListener toolNodeListener, String instrumentInfo) {
+            super(instrumentInfo);
+            this.toolNodeListener = toolNodeListener;
+        }
+
+        @Override
+        AbstractInstrumentNode addToChain(AbstractInstrumentNode nextNode) {
+            return new ToolInstrumentNode(nextNode);
+        }
+
+        @Override
+        AbstractInstrumentNode removeFromChain(AbstractInstrumentNode instrumentNode) {
+            boolean found = false;
+            if (instrumentNode != null) {
+                if (instrumentNode.getInstrument() == this) {
+                    // Found the match at the head of the chain
+                    return instrumentNode.nextInstrumentNode;
+                }
+                // Match not at the head of the chain; remove it.
+                found = instrumentNode.removeFromChain(ToolNodeInstrument.this);
+            }
+            if (!found) {
+                throw new IllegalStateException("Couldn't find instrument node to remove: " + this);
+            }
+            return instrumentNode;
+        }
+
+        @NodeInfo(cost = NodeCost.NONE)
+        private final class ToolInstrumentNode extends AbstractInstrumentNode {
+
+            @Child ToolNode toolNode;
+
+            private ToolInstrumentNode(AbstractInstrumentNode nextNode) {
+                super(nextNode);
+            }
+
+            public void enter(Node node, VirtualFrame vFrame) {
+                if (toolNode == null) {
+                    final ToolNode newToolNode = ToolNodeInstrument.this.toolNodeListener.getToolNode(ToolNodeInstrument.this.probe);
+                    if (newToolNode != null) {
+                        toolNode = newToolNode;
+                        adoptChildren();
+                        ToolNodeInstrument.this.probe.invalidateProbeUnchanged();
+                    }
+                }
+                if (toolNode != null) {
+                    toolNode.enter(node, vFrame);
+                }
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.enter(node, vFrame);
+                }
+            }
+
+            public void returnVoid(Node node, VirtualFrame vFrame) {
+                if (toolNode != null) {
+                    toolNode.returnVoid(node, vFrame);
+                }
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.returnVoid(node, vFrame);
+                }
+            }
+
+            public void returnValue(Node node, VirtualFrame vFrame, Object result) {
+                if (toolNode != null) {
+                    toolNode.returnValue(node, vFrame, result);
+                }
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.returnValue(node, vFrame, result);
+                }
+            }
+
+            public void returnExceptional(Node node, VirtualFrame vFrame, Exception exception) {
+                if (toolNode != null) {
+                    toolNode.returnExceptional(node, vFrame, exception);
+                }
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.returnExceptional(node, vFrame, exception);
+                }
+            }
+
+            public String instrumentationInfo() {
+                final String info = getInstrumentInfo();
+                return info != null ? info : toolNodeListener.getClass().getSimpleName();
+            }
+        }
+
+    }
+
     public interface TruffleOptListener {
         void notifyIsCompiled(boolean isCompiled);
     }
@@ -440,7 +551,7 @@
             if (instrumentNode != null) {
                 if (instrumentNode.getInstrument() == this) {
                     // Found the match at the head of the chain
-                    return instrumentNode.nextInstrument;
+                    return instrumentNode.nextInstrumentNode;
                 }
                 // Match not at the head of the chain; remove it.
                 found = instrumentNode.removeFromChain(TruffleOptInstrument.this);
@@ -466,26 +577,26 @@
                     this.isCompiled = CompilerDirectives.inCompiledCode();
                     TruffleOptInstrument.this.toolOptListener.notifyIsCompiled(this.isCompiled);
                 }
-                if (nextInstrument != null) {
-                    nextInstrument.enter(node, vFrame);
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.enter(node, vFrame);
                 }
             }
 
             public void returnVoid(Node node, VirtualFrame vFrame) {
-                if (nextInstrument != null) {
-                    nextInstrument.returnVoid(node, vFrame);
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.returnVoid(node, vFrame);
                 }
             }
 
             public void returnValue(Node node, VirtualFrame vFrame, Object result) {
-                if (nextInstrument != null) {
-                    nextInstrument.returnValue(node, vFrame, result);
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.returnValue(node, vFrame, result);
                 }
             }
 
             public void returnExceptional(Node node, VirtualFrame vFrame, Exception exception) {
-                if (nextInstrument != null) {
-                    nextInstrument.returnExceptional(node, vFrame, exception);
+                if (nextInstrumentNode != null) {
+                    nextInstrumentNode.returnExceptional(node, vFrame, exception);
                 }
             }
 
@@ -500,10 +611,10 @@
     @NodeInfo(cost = NodeCost.NONE)
     abstract class AbstractInstrumentNode extends Node implements TruffleEvents, InstrumentationNode {
 
-        @Child protected AbstractInstrumentNode nextInstrument;
+        @Child protected AbstractInstrumentNode nextInstrumentNode;
 
         protected AbstractInstrumentNode(AbstractInstrumentNode nextNode) {
-            this.nextInstrument = nextNode;
+            this.nextInstrumentNode = nextNode;
         }
 
         @Override
@@ -527,21 +638,21 @@
          */
         private boolean removeFromChain(Instrument instrument) {
             assert getInstrument() != instrument;
-            if (nextInstrument == null) {
+            if (nextInstrumentNode == null) {
                 return false;
             }
-            if (nextInstrument.getInstrument() == instrument) {
+            if (nextInstrumentNode.getInstrument() == instrument) {
                 // Next is the one to remove
-                if (nextInstrument.nextInstrument == null) {
+                if (nextInstrumentNode.nextInstrumentNode == null) {
                     // Next is at the tail; just forget
-                    nextInstrument = null;
+                    nextInstrumentNode = null;
                 } else {
                     // Replace next with its successor
-                    nextInstrument.replace(nextInstrument.nextInstrument);
+                    nextInstrumentNode.replace(nextInstrumentNode.nextInstrumentNode);
                 }
                 return true;
             }
-            return nextInstrument.removeFromChain(instrument);
+            return nextInstrumentNode.removeFromChain(instrument);
         }
 
         protected String getInstrumentInfo() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ToolNode.java	Tue Mar 31 19:01:07 2015 -0700
@@ -0,0 +1,55 @@
+/*
+ * 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.*;
+
+/**
+ * Root of a tool-provided AST fragment that can be attached directly into an executing AST via
+ * {@link Instrument#create(ToolNodeInstrumentListener, String)}.
+ * <p>
+ * <strong>Note:</strong> Instances of this class will in some situations be cloned by the
+ * instrumentation platform for attachment at equivalent locations in cloned parent ASTs.
+ */
+public abstract class ToolNode extends Node implements InstrumentationNode.TruffleEvents, InstrumentationNode {
+
+    public void enter(Node node, VirtualFrame vFrame) {
+    }
+
+    public void returnVoid(Node node, VirtualFrame vFrame) {
+    }
+
+    public void returnValue(Node node, VirtualFrame vFrame, Object result) {
+    }
+
+    public void returnExceptional(Node node, VirtualFrame vFrame, Exception exception) {
+    }
+
+    public String instrumentationInfo() {
+        return null;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ToolNodeInstrumentListener.java	Tue Mar 31 19:01:07 2015 -0700
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+/**
+ * Instrument listener for a tool that works by providing an AST to be attached/adopted directly
+ * into the AST.
+ */
+public interface ToolNodeInstrumentListener {
+
+    /**
+     * Receive notification that a probed AST node to which the {@link Instrument} is attached is
+     * about to be executed for the first time. This is a lazy opportunity for the tool to
+     * optionally add the root of a newly created AST fragment that will be attached/adopted
+     * directly into the executing AST. The new AST fragment will immediately begin receiving
+     * {@link InstrumentationNode.TruffleEvents}, beginning with the current execution event.
+     * <p>
+     * AST fragments must be written to Truffle conventions. Some of these conventions are
+     * especially important if the fragment is to be fully optimized along with it's new parent AST.
+     * <p>
+     * If this method returns {@code null} then it will be called again the next time the probed
+     * node is about to be executed.
+     * <p>
+     * In some situations, this method will be called more than once for a particular Probe, and a
+     * new instance must be supplied each time. Each instance will be attached at the equivalent
+     * location in clones of the AST, and so should be behave as if equivalent for most purposes.
+     * <p>
+     * In some situations the AST fragment supplied by this method maybe cloned for attachment to
+     * equivalent locations in cloned AST, so care should be taken about any state local to each
+     * instance of the AST fragment.
+     *
+     * @see Instrument
+     */
+    ToolNode getToolNode(Probe probe);
+
+}