changeset 18985:867058575979

Truffle: Improved support for "probing" AST nodes: - Node.isSafelyReplacaeableBy(Node) checks in advance if Node.replace(Node) would be unsafe (crash the VM). - Hoist Probe() from language imlementations into Node; now completely language agnostic. - Language implementations support probing by implementing Node.isInstrumentable() and Node.createWrapperNode() - Node.Probe() throws ProbeException (without side effects) if the probe fails. -- ProbeException contains an instance of ProbeFailure that diagnoses the failure in detail - Additional measures to prevent instrumentation from being applied to internal InstrumentationNodes. - Promote ProbeListener to top level interface and add a default implementation
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Tue, 27 Jan 2015 20:24:54 -0800
parents 0f462015296f
children 50b22daf6d53
files graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/nodes/SafeReplaceTest.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ASTProber.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/Probe.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeException.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeFailure.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeListener.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeNode.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultProbeListener.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java
diffstat 10 files changed, 598 insertions(+), 120 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/nodes/SafeReplaceTest.java	Tue Jan 27 20:24:54 2015 -0800
@@ -0,0 +1,95 @@
+/*
+ * 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.nodes;
+
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+
+/**
+ * Tests optional method for ensuring that a node replacement is type safe. Ordinary node
+ * replacement is performed by unsafe assignment of a parent node's child field.
+ */
+public class SafeReplaceTest {
+
+    @Test
+    public void testCorrectReplacement() {
+        TestRootNode root = new TestRootNode();
+        final TestNode oldChild = new TestNode();
+        root.child = oldChild;
+        assertFalse(oldChild.isReplaceable());  // No parent node
+        root.adoptChildren();
+        assertTrue(oldChild.isReplaceable());   // Now adopted by parent
+        final TestNode newChild = new TestNode();
+        assertTrue(oldChild.isSafelyReplaceableBy(newChild));  // Parent field type is assignable by
+        // new node
+        oldChild.replace(newChild);
+        root.execute(null);
+        assertEquals(root.executed, 1);
+        assertEquals(oldChild.executed, 0);
+        assertEquals(newChild.executed, 1);
+    }
+
+    @Test
+    public void testIncorrectReplacement() {
+        TestRootNode root = new TestRootNode();
+        final TestNode oldChild = new TestNode();
+        root.child = oldChild;
+        root.adoptChildren();
+        final WrongTestNode newChild = new WrongTestNode();
+        assertFalse(oldChild.isSafelyReplaceableBy(newChild));
+        // Can't test: oldChild.replace(newChild);
+        // Fails if assertions checked; else unsafe assignment will eventually crash the VM
+    }
+
+    private static class TestNode extends Node {
+
+        private int executed;
+
+        public Object execute() {
+            executed++;
+            return null;
+        }
+    }
+
+    private static class TestRootNode extends RootNode {
+
+        @Child TestNode child;
+
+        private int executed;
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            executed++;
+            child.execute();
+            return null;
+        }
+    }
+
+    private static class WrongTestNode extends Node {
+    }
+
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ASTProber.java	Tue Jan 27 20:23:13 2015 -0800
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ASTProber.java	Tue Jan 27 20:24:54 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 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
@@ -31,13 +31,14 @@
  * not yet executed) AST.
  *
  * @see Probe
- * @see Probe#addProbeListener(com.oracle.truffle.api.instrument.Probe.ProbeListener)
+ * @see Probe#addProbeListener(ProbeListener)
  */
 public interface ASTProber {
 
     /**
      * Walk the AST starting at a node and enable instrumentation at selected nodes by attaching
-     * {@linkplain Probe Probes} to them.
+     * {@linkplain Probe Probes} to them. Ignore {@linkplain Node#isInstrumentable()
+     * non-instrumentable} nodes.
      */
     void probeAST(Node node);
 
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.java	Tue Jan 27 20:23:13 2015 -0800
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.java	Tue Jan 27 20:24:54 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 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
@@ -248,6 +248,11 @@
             this.nextInstrument = nextNode;
         }
 
+        @Override
+        public boolean isInstrumentable() {
+            return false;
+        }
+
         /**
          * Gets the instrument that created this node.
          */
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Probe.java	Tue Jan 27 20:23:13 2015 -0800
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Probe.java	Tue Jan 27 20:24:54 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 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
@@ -28,31 +28,32 @@
 import java.util.*;
 
 import com.oracle.truffle.api.*;
-import com.oracle.truffle.api.instrument.ProbeNode.Instrumentable;
 import com.oracle.truffle.api.nodes.*;
 import com.oracle.truffle.api.source.*;
 import com.oracle.truffle.api.utilities.*;
 
 //TODO (mlvdv) migrate some of this to external documentation.
 /**
- * A binding between a particular location in the Truffle AST representation of a running Guest
- * Language (GL) program (i.e. a {@link Node}) and a dynamically managed collection of "attached"
- * {@linkplain Instrument instrumentation} for use by external tools.
+ * A binding between a particular <em>location</em> in the Truffle AST representation of a running
+ * Guest Language (GL) program (i.e. a {@link Node}) and a dynamically managed collection of
+ * "attached" {@linkplain Instrument instrumentation} for use by external tools. The instrumentation
+ * is intended to persist at the location, even if the specific node instance is
+ * {@linkplain Node#replace(Node) replaced}.
  * <p>
- * The effect of a binding is to intercept {@linkplain TruffleEventReceiver execution events} at the
- * node and notify each attached {@link Instrument} before execution is allowed to resume.
+ * The effect of a binding is to intercept {@linkplain TruffleEventReceiver execution events}
+ * arriving at the node and notify each attached {@link Instrument} before execution is allowed to
+ * proceed to the child.
  * <p>
- * A Probe is "inserted" into a GL node via a call to {@link Instrumentable#probe()}; a GL node must
- * implement {@link Instrumentable} in order to support instrumentation. No more than one Probe can
- * be inserted at a node.
+ * A Probe is "inserted" into a GL node via a call to {@link Node#probe()}. No more than one Probe
+ * can be inserted at a node.
  * <p>
  * The "probing" of a Truffle AST must be done after it is complete (i.e. with parent pointers
  * correctly assigned), but before any executions. This is done by creating an instance of
  * {@link ASTProber} and registering it via {@link #registerASTProber(ASTProber)}, after which it
  * will be automatically applied to newly created ASTs.
  * <p>
- * Each Probe may also have assigned to it one or more {@link SyntaxTag}s, for example identifying a
- * node as a {@linkplain StandardSyntaxTag#STATEMENT STATEMENT}. Tags can be queried by tools to
+ * Each Probe may also have assigned to it any number of {@link SyntaxTag}s, for example identifying
+ * a node as a {@linkplain StandardSyntaxTag#STATEMENT STATEMENT}. Tags can be queried by tools to
  * configure behavior relevant to each probed node.
  * <p>
  * Instrumentation is implemented by modifying ASTs, both by inserting nodes into each AST at probed
@@ -60,69 +61,16 @@
  * Attached instrumentation code become, in effect, part of the GL program, and is subject to the
  * same levels of optimization as other GL code. This implementation accounts properly for the fact
  * that Truffle frequently <em>clones</em> ASTs, along with any attached instrumentation nodes. A
- * Probe, along with attached Instruments, represents a <em>logical</em> binding with a source code
- * location, producing event notifications that are (mostly) independent of which AST clone is
- * executing.
+ * {@link Probe}, along with attached {@link Instrument}s, represents a <em>logical</em> binding
+ * with a source code location, producing event notifications that are (mostly) independent of which
+ * AST clone is executing.
  *
  * @see Instrument
- * @see Instrumentable
  * @see ASTProber
+ * @see ProbeListener
  */
 public final class Probe implements SyntaxTagged {
 
-    /**
-     * An observer of events related to {@link Probe}s: creating and tagging.
-     */
-    public interface ProbeListener {
-
-        /**
-         * Notifies that all registered {@link ASTProber}s are about to be applied to a newly
-         * constructed AST.
-         *
-         * @param source source code from which the AST was constructed
-         */
-        void startASTProbing(Source source);
-
-        /**
-         * Notifies that a {@link Probe} has been newly attached to an AST via
-         * {@link Instrumentable#probe()}.
-         * <p>
-         * There can be no more than one {@link Probe} at a node; this notification will only be
-         * delivered the first time {@linkplain Instrumentable#probe() probe()} is called at a
-         * particular AST node. There will also be no notification when the AST to which the Probe
-         * is attached is cloned.
-         */
-        void newProbeInserted(Probe probe);
-
-        /**
-         * Notifies that a {@link SyntaxTag} has been newly added to the set of tags associated with
-         * a {@link Probe} via {@link Probe#tagAs(SyntaxTag, Object)}.
-         * <p>
-         * The {@linkplain SyntaxTag tags} at a {@link Probe} are a <em>set</em>; this notification
-         * will only be delivered the first time a particular {@linkplain SyntaxTag tag} is added at
-         * a {@link Probe}.
-         * <p>
-         * An optional value supplied with {@linkplain Probe#tagAs(SyntaxTag, Object)
-         * tagAs(SyntaxTag, Object)} is reported to all listeners, but not stored. As a consequence,
-         * the optional value will have no effect at all if the tag had already been added.
-         *
-         * @param probe where a tag has been added
-         * @param tag the tag that has been newly added (subsequent additions of the tag are
-         *            unreported).
-         * @param tagValue an optional value associated with the tag for the purposes of reporting.
-         */
-        void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue);
-
-        /**
-         * Notifies that the application of all registered {@link ASTProber}s to a newly constructed
-         * AST has completed.
-         *
-         * @param source source code from which the AST was constructed
-         */
-        void endASTProbing(Source source);
-
-    }
-
     private static final List<ASTProber> astProbers = new ArrayList<>();
 
     private static final List<ProbeListener> probeListeners = new ArrayList<>();
@@ -282,7 +230,7 @@
     private boolean trapActive = false;
 
     /**
-     * @see Instrumentable#probe()
+     * Intended for use only by {@link ProbeNode}.
      */
     Probe(ProbeNode probeNode, SourceSection sourceSection) {
         this.sourceSection = sourceSection;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeException.java	Tue Jan 27 20:24:54 2015 -0800
@@ -0,0 +1,52 @@
+/*
+ * 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.instrument.ProbeFailure.Reason;
+import com.oracle.truffle.api.nodes.*;
+
+/**
+ * An exception thrown when {@link Node#probe()} fails because of an implementation failure.
+ * <p>
+ * Language and tool implementations should ensure that clients of tools never see this exception.
+ */
+public class ProbeException extends RuntimeException {
+    static final long serialVersionUID = 1L;
+    private final ProbeFailure failure;
+
+    public ProbeException(Reason reason, Node parent, Node child, Object wrapper) {
+        this.failure = new ProbeFailure(reason, parent, child, wrapper);
+    }
+
+    public ProbeFailure getFailure() {
+        return failure;
+    }
+
+    @Override
+    public String toString() {
+        return failure.getMessage();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeFailure.java	Tue Jan 27 20:24:54 2015 -0800
@@ -0,0 +1,145 @@
+/*
+ * 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.instrument.ProbeNode.WrapperNode;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.nodes.NodeUtil.NodeField;
+
+/**
+ * Description of a failed attempt to instrument an AST node.
+ */
+public final class ProbeFailure {
+
+    public enum Reason {
+
+        /**
+         * Node to be probed has no parent.
+         */
+        NO_PARENT("Node to be probed has no parent"),
+
+        /**
+         * The node to be probed is a wrapper.
+         */
+        WRAPPER_NODE("The node to be probed is a wrapper"),
+
+        /**
+         * The node to be probed returned {@link Node#isInstrumentable()}{@code == false}.
+         */
+        NOT_INSTRUMENTABLE("The node to be project is \"not instrumentable\""),
+
+        /**
+         * No wrapper could be created that is also a {@link Node}.
+         */
+        NO_WRAPPER("No wrapper could be created"),
+
+        /**
+         * Wrapper not assignable to the parent's child field.
+         */
+        WRAPPER_TYPE("Wrapper not assignable to parent's child field"),
+
+        /**
+         * Attempt to \"probe lite\" an already probed node.
+         */
+        LITE_VIOLATION("Attempt to \"probe lite\" an already probed node");
+
+        final String message;
+
+        private Reason(String message) {
+            this.message = message;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+    }
+
+    private final Reason reason;
+    private final Node parent;
+    private final Node child;
+    private final Object wrapper;
+
+    /**
+     * Description of an internal failure of {@link Node#probe()}.
+     *
+     * @param reason what caused the failure
+     * @param parent the parent, if known, of the child being probed
+     * @param child this child being probed
+     * @param wrapper the {@link WrapperNode} created to implement the probe
+     */
+    public ProbeFailure(Reason reason, Node parent, Node child, Object wrapper) {
+        this.reason = reason;
+        this.parent = parent;
+        this.child = child;
+        this.wrapper = wrapper;
+    }
+
+    /**
+     * @return a short explanation of the failure
+     */
+    public Reason getReason() {
+        return reason;
+    }
+
+    /**
+     * @return the parent, if any, of the node being probed
+     */
+    public Node getParent() {
+        return parent;
+    }
+
+    /**
+     * @return the node being probed
+     */
+    public Node getChild() {
+        return child;
+    }
+
+    /**
+     * @return the {@link WrapperNode} created for the probe attempt
+     */
+    public Object getWrapper() {
+        return wrapper;
+    }
+
+    public String getMessage() {
+        final StringBuilder sb = new StringBuilder(reason.message + ": ");
+        if (parent != null) {
+            sb.append("parent=" + parent.getClass().getSimpleName() + " ");
+            if (child != null) {
+                sb.append("child=" + child.getClass().getSimpleName() + " ");
+                final NodeField field = NodeUtil.findChildField(parent, child);
+                if (field != null) {
+                    sb.append("field=" + field.getName() + " ");
+                }
+            }
+        }
+        if (wrapper != null) {
+            sb.append("wrapper=" + wrapper.getClass().getSimpleName());
+        }
+        return sb.toString();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeListener.java	Tue Jan 27 20:24:54 2015 -0800
@@ -0,0 +1,80 @@
+/*
+ * 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.nodes.*;
+import com.oracle.truffle.api.source.*;
+
+/**
+ * An observer of events related to {@link Probe}s: creating and tagging.
+ */
+public interface ProbeListener {
+
+    /**
+     * Notifies that all registered {@link ASTProber}s are about to be applied to a newly
+     * constructed AST.
+     *
+     * @param source source code from which the AST was constructed
+     */
+    void startASTProbing(Source source);
+
+    /**
+     * Notifies that a {@link Probe} has been newly attached to an AST via {@link Node#probe()}.
+     * <p>
+     * There can be no more than one {@link Probe} at a node; this notification will only be
+     * delivered the first time {@linkplain Node#probe() probe()} is called at a particular AST
+     * node. There will also be no notification when the AST to which the Probe is attached is
+     * cloned.
+     */
+    void newProbeInserted(Probe probe);
+
+    /**
+     * Notifies that a {@link SyntaxTag} has been newly added to the set of tags associated with a
+     * {@link Probe} via {@link Probe#tagAs(SyntaxTag, Object)}.
+     * <p>
+     * The {@linkplain SyntaxTag tags} at a {@link Probe} are a <em>set</em>; this notification will
+     * only be delivered the first time a particular {@linkplain SyntaxTag tag} is added at a
+     * {@link Probe}.
+     * <p>
+     * An optional value supplied with {@linkplain Probe#tagAs(SyntaxTag, Object) tagAs(SyntaxTag,
+     * Object)} is reported to all listeners, but not stored. As a consequence, the optional value
+     * will have no effect at all if the tag had already been added.
+     *
+     * @param probe where a tag has been added
+     * @param tag the tag that has been newly added (subsequent additions of the tag are
+     *            unreported).
+     * @param tagValue an optional value associated with the tag for the purposes of reporting.
+     */
+    void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue);
+
+    /**
+     * Notifies that the application of all registered {@link ASTProber}s to a newly constructed AST
+     * has completed.
+     *
+     * @param source source code from which the AST was constructed
+     */
+    void endASTProbing(Source source);
+
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeNode.java	Tue Jan 27 20:23:13 2015 -0800
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeNode.java	Tue Jan 27 20:24:54 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 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
@@ -38,46 +38,9 @@
 public abstract class ProbeNode extends Node implements TruffleEventReceiver, InstrumentationNode {
 
     /**
-     * Any Truffle node implementing this interface can be "instrumented" by installing a
-     * {@link Probe} that intercepts execution events at the node and routes them to any
-     * {@link Instrument}s that have been attached to the {@link Probe}. Only one {@link Probe} may
-     * be installed at each node; subsequent calls return the one already installed.
-     *
-     * @see Instrument
-     */
-    public interface Instrumentable {
-
-        /**
-         * Enables "instrumentation" of a Guest Language Truffle node, where the node is presumed to
-         * be part of a well-formed Truffle AST that is not being executed. The AST may be modified
-         * as a side effect.
-         * <p>
-         * This interface is not intended to be visible as part of the API for tools
-         * (instrumentation clients).
-         *
-         * @return a (possibly newly created) {@link Probe} associated with this node.
-         */
-        Probe probe();
-
-        /**
-         * Enables a one-time, unchangeable "instrumentation" of a Guest Language Truffle node,
-         * where the node is presumed to be part of a well-formed Truffle AST that is not being
-         * executed. The AST may be modified as a side-effect. Unlike {@link #probe()}, once
-         * {@link #probeLite(TruffleEventReceiver)} is called at a node, no additional probing can
-         * be added and no additional instrumentation can be attached.
-         * <p>
-         * This interface is not intended to be visible as part of the API for tools
-         * (instrumentation clients).
-         *
-         * @param eventReceiver The {@link TruffleEventReceiver} for the single "instrument" being
-         *            attached to this node.
-         */
-        void probeLite(TruffleEventReceiver eventReceiver);
-    }
-
-    /**
-     * A node that can be inserted into a Truffle AST, and which enables <em>instrumentation</em> at
-     * a particular Guest Language (GL) node.
+     * A node that can be inserted into a Truffle AST, and which enables {@linkplain Instrument
+     * instrumentation} at a particular Guest Language (GL) node. Implementations must extend
+     * {@link Node} and should override {@link Node#isInstrumentable()} to return {@code false}.
      * <p>
      * The implementation must be GL-specific. A wrapper <em>decorates</em> a GL AST node (the
      * wrapper's <em>child</em>) by acting as a transparent <em>proxy</em> with respect to the GL's
@@ -115,7 +78,6 @@
      * their runtime overhead to zero when there are no attached {@link Instrument}s.</li>
      * </ol>
      * <p>
-     * <strong>Disclaimer:</strong> experimental interface under development.
      *
      * @see Instrument
      */
@@ -130,7 +92,7 @@
 
         /**
          * Gets the {@link Probe} responsible for installing this wrapper; none if the wrapper
-         * installed via {@linkplain Instrumentable#probeLite(TruffleEventReceiver) "lite-Probing"}.
+         * installed via {@linkplain Node#probeLite(TruffleEventReceiver) "lite-Probing"}.
          */
         Probe getProbe();
 
@@ -163,6 +125,11 @@
         wrapper.insertProbe(probeLiteNode);
     }
 
+    @Override
+    public boolean isInstrumentable() {
+        return false;
+    }
+
     /**
      * @return the {@link Probe} permanently associated with this {@link ProbeNode}.
      *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultProbeListener.java	Tue Jan 27 20:24:54 2015 -0800
@@ -0,0 +1,44 @@
+/*
+ * 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.*;
+import com.oracle.truffle.api.source.*;
+
+public abstract class DefaultProbeListener implements ProbeListener {
+
+    public void startASTProbing(Source source) {
+    }
+
+    public void newProbeInserted(Probe probe) {
+    }
+
+    public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
+    }
+
+    public void endASTProbing(Source source) {
+    }
+
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java	Tue Jan 27 20:23:13 2015 -0800
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java	Tue Jan 27 20:24:54 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 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
@@ -30,6 +30,8 @@
 
 import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.ProbeNode.WrapperNode;
 import com.oracle.truffle.api.source.*;
 import com.oracle.truffle.api.utilities.*;
 
@@ -288,9 +290,9 @@
     }
 
     /**
-     * Checks if this node is properly adopted by a parent and can be replaced.
+     * Checks if this node is properly adopted by its parent.
      *
-     * @return {@code true} if it is safe to replace this node.
+     * @return {@code true} if it is structurally safe to replace this node.
      */
     public final boolean isReplaceable() {
         if (getParent() != null) {
@@ -303,6 +305,13 @@
         return false;
     }
 
+    /**
+     * Checks if this node can be replaced by another node, both structurally and with type safety.
+     */
+    public final boolean isSafelyReplaceableBy(Node newNode) {
+        return isReplaceable() && NodeUtil.isReplacementSafe(getParent(), this, newNode);
+    }
+
     private void reportReplace(Node oldNode, Node newNode, CharSequence reason) {
         Node node = this;
         while (node != null) {
@@ -424,6 +433,138 @@
     }
 
     /**
+     * Any node for which this is {@code true} can be "instrumented" by installing a {@link Probe}
+     * that intercepts execution events at the node and routes them to any {@link Instrument}s that
+     * have been attached to the {@link Probe}. Only one {@link Probe} may be installed at each
+     * node; subsequent calls return the one already installed.
+     *
+     * @see Instrument
+     */
+    public boolean isInstrumentable() {
+        return false;
+    }
+
+    /**
+     * For any node that {@link #isInstrumentable()}, this method must return a {@link Node} that:
+     * <ol>
+     * <li>implements {@link WrapperNode}</li>
+     * <li>has {@code this} as it's child, and</li>
+     * <li>whose type is suitable for (unsafe) replacement of {@code this} in the parent.</li>
+     * </ol>
+     *
+     * @return an appropriately typed {@link WrapperNode} if {@link #isInstrumentable()}.
+     */
+    public WrapperNode createWrapperNode() {
+        return null;
+    }
+
+    /**
+     * Enables {@linkplain Instrument instrumentation} of a node, where the node is presumed to be
+     * part of a well-formed Truffle AST that is not being executed. If this node has not already
+     * been probed, modifies the AST by inserting a {@linkplain WrapperNode wrapper node} between
+     * the node and its parent; the wrapper node must be provided by implementations of
+     * {@link #createWrapperNode()}. No more than one {@link Probe} may be associated with a node,
+     * so a {@linkplain WrapperNode wrapper} may not wrap another {@linkplain WrapperNode wrapper}.
+     *
+     * @return a (possibly newly created) {@link Probe} associated with this node.
+     * @throws ProbeException (unchecked) when a probe cannot be created, leaving the AST unchanged
+     */
+    public final Probe probe() {
+
+        if (this instanceof WrapperNode) {
+            throw new ProbeException(ProbeFailure.Reason.WRAPPER_NODE, null, this, null);
+        }
+
+        if (parent == null) {
+            throw new ProbeException(ProbeFailure.Reason.NO_PARENT, null, this, null);
+        }
+
+        if (parent instanceof WrapperNode) {
+            return ((WrapperNode) parent).getProbe();
+        }
+
+        if (!isInstrumentable()) {
+            throw new ProbeException(ProbeFailure.Reason.NOT_INSTRUMENTABLE, parent, this, null);
+        }
+
+        // Create a new wrapper/probe with this node as its child.
+        final WrapperNode wrapper = createWrapperNode();
+
+        if (wrapper == null || !(wrapper instanceof Node)) {
+            throw new ProbeException(ProbeFailure.Reason.NO_WRAPPER, parent, this, wrapper);
+        }
+
+        final Node wrapperNode = (Node) wrapper;
+
+        if (!this.isSafelyReplaceableBy(wrapperNode)) {
+            throw new ProbeException(ProbeFailure.Reason.WRAPPER_TYPE, parent, this, wrapper);
+        }
+
+        // Connect it to a Probe
+        final Probe probe = ProbeNode.insertProbe(wrapper);
+
+        // Replace this node in the AST with the wrapper
+        this.replace(wrapperNode);
+
+        return probe;
+    }
+
+    /**
+     * Enables "one-shot", unmodifiable {@linkplain Instrument instrumentation} of a node, where the
+     * node is presumed to be part of a well-formed Truffle AST that is not being executed.
+     * <p>
+     * Modifies the AST by inserting a {@linkplain WrapperNode wrapper node} between the node and
+     * its parent; the wrapper node must be provided by implementations of
+     * {@link #createWrapperNode()}.
+     * <p>
+     * Unlike {@link #probe()}, once {@link #probeLite(TruffleEventReceiver)} is called at a node,
+     * no additional probing can be added and no additional instrumentation can be attached.
+     * <p>
+     * This restricted form of instrumentation is intended for special cases where only one kind of
+     * instrumentation is desired, and for which performance is a concern
+     *
+     * @param eventReceiver
+     * @throws ProbeException (unchecked) when a probe cannot be created, leaving the AST unchanged
+     */
+    public final void probeLite(TruffleEventReceiver eventReceiver) {
+
+        if (this instanceof WrapperNode) {
+            throw new ProbeException(ProbeFailure.Reason.WRAPPER_NODE, null, this, null);
+        }
+
+        if (parent == null) {
+            throw new ProbeException(ProbeFailure.Reason.NO_PARENT, null, this, null);
+        }
+
+        if (parent instanceof WrapperNode) {
+            throw new ProbeException(ProbeFailure.Reason.LITE_VIOLATION, null, this, null);
+        }
+
+        if (!isInstrumentable()) {
+            throw new ProbeException(ProbeFailure.Reason.NOT_INSTRUMENTABLE, parent, this, null);
+        }
+
+        // Create a new wrapper/probe with this node as its child.
+        final WrapperNode wrapper = createWrapperNode();
+
+        if (wrapper == null || !(wrapper instanceof Node)) {
+            throw new ProbeException(ProbeFailure.Reason.NO_WRAPPER, parent, this, wrapper);
+        }
+
+        final Node wrapperNode = (Node) wrapper;
+
+        if (!this.isSafelyReplaceableBy(wrapperNode)) {
+            throw new ProbeException(ProbeFailure.Reason.WRAPPER_TYPE, parent, this, wrapper);
+        }
+
+        // Connect it to a Probe
+        ProbeNode.insertProbeLite(wrapper, eventReceiver);
+
+        // Replace this node in the AST with the wrapper
+        this.replace(wrapperNode);
+    }
+
+    /**
      * Converts this node to a textual representation useful for debugging.
      */
     @Override