changeset 19003:0bcb0dae2c52

Merge.
author Thomas Wuerthinger <thomas.wuerthinger@oracle.com>
date Wed, 28 Jan 2015 11:28:03 +0100
parents 3be6793b4549 (current diff) d1c1cd2530d7 (diff)
children 986e2e87eedd a64863622854
files graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/InstrumentationTest.java graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/utilities/SourceTextTest.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/LineToProbesMap.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/LineToSourceSectionMap.java
diffstat 34 files changed, 3361 insertions(+), 1517 deletions(-) [+]
line wrap: on
line diff
--- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/InstrumentationTest.java	Wed Jan 28 11:27:35 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,752 +0,0 @@
-/*
- * Copyright (c) 2014, 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;
-
-import java.lang.reflect.*;
-import java.util.*;
-
-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.instrument.Probe.ProbeListener;
-import com.oracle.truffle.api.instrument.ProbeNode.Instrumentable;
-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.source.*;
-
-/**
- * <h3>AST Instrumentation</h3>
- *
- * Instrumentation allows the insertion into Truffle ASTs language-specific instances of
- * {@link WrapperNode} that propagate execution events through a {@link Probe} to any instances of
- * {@link Instrument} that might be attached to the particular probe by tools.
- * <ol>
- * <li>Creates a simple add AST</li>
- * <li>Verifies its structure</li>
- * <li>"Probes" the add node by adding a {@link WrapperNode} and associated {@link Probe}</li>
- * <li>Attaches a simple {@link Instrument} to the node via the Probe's {@link ProbeNode}</li>
- * <li>Verifies the structure of the probed AST</li>
- * <li>Verifies the execution of the probed AST</li>
- * <li>Verifies the results observed by the instrument.</li>
- * </ol>
- * To do these tests, several required classes have been implemented in their most basic form, only
- * implementing the methods necessary for the tests to pass, with stubs elsewhere.
- */
-public class InstrumentationTest {
-
-    private static final SyntaxTag ADD_TAG = new SyntaxTag() {
-
-        public String name() {
-            return "Addition";
-        }
-
-        public String getDescription() {
-            return "Test Language Addition Node";
-        }
-    };
-
-    private static final SyntaxTag VALUE_TAG = new SyntaxTag() {
-
-        public String name() {
-            return "Value";
-        }
-
-        public String getDescription() {
-            return "Test Language Value Node";
-        }
-    };
-
-    @Test
-    public void testBasicInstrumentation() {
-        // 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);
-
-        // Creating a call target sets the parent pointers in this tree and is necessary prior to
-        // checking any parent/child relationships
-        final CallTarget callTarget1 = runtime.createCallTarget(rootNode);
-
-        // Check the tree structure
-        Assert.assertEquals(addNode, leftValueNode.getParent());
-        Assert.assertEquals(addNode, rightValueNode.getParent());
-        Iterator<Node> iterator = addNode.getChildren().iterator();
-        Assert.assertEquals(leftValueNode, iterator.next());
-        Assert.assertEquals(rightValueNode, iterator.next());
-        Assert.assertFalse(iterator.hasNext());
-        Assert.assertEquals(rootNode, addNode.getParent());
-        iterator = rootNode.getChildren().iterator();
-        Assert.assertEquals(addNode, iterator.next());
-        Assert.assertFalse(iterator.hasNext());
-
-        // Ensure it executes correctly
-        Assert.assertEquals(13, callTarget1.call());
-
-        // Probe the addition node
-        final Probe probe = addNode.probe();
-
-        // Check the modified tree structure
-        Assert.assertEquals(addNode, leftValueNode.getParent());
-        Assert.assertEquals(addNode, rightValueNode.getParent());
-        iterator = addNode.getChildren().iterator();
-        Assert.assertEquals(leftValueNode, iterator.next());
-        Assert.assertEquals(rightValueNode, iterator.next());
-        Assert.assertFalse(iterator.hasNext());
-
-        // Ensure there's a WrapperNode correctly inserted into the AST
-        iterator = rootNode.getChildren().iterator();
-        Node wrapperNode = iterator.next();
-        Assert.assertTrue(wrapperNode instanceof TestLanguageWrapperNode);
-        Assert.assertFalse(iterator.hasNext());
-        Assert.assertEquals(rootNode, wrapperNode.getParent());
-
-        // Check that the WrapperNode has both the probe and the wrapped node as children
-        iterator = wrapperNode.getChildren().iterator();
-        Assert.assertEquals(addNode, iterator.next());
-        ProbeNode probeNode = (ProbeNode) iterator.next();
-        Assert.assertTrue(probeNode.getProbe() != null);
-        Assert.assertFalse(iterator.hasNext());
-
-        // Check that you can't probe the WrapperNodes
-        TestLanguageWrapperNode wrapper = (TestLanguageWrapperNode) wrapperNode;
-        try {
-            wrapper.probe();
-            Assert.fail();
-        } catch (IllegalStateException e) {
-        }
-
-        // Check that the "probed" AST still executes correctly
-        Assert.assertEquals(13, callTarget1.call());
-
-        // Attach a counting instrument to the probe
-        final TestCounter counterA = new TestCounter();
-        counterA.attach(probe);
-
-        // Attach a second counting instrument to the probe
-        final TestCounter counterB = new TestCounter();
-        counterB.attach(probe);
-
-        // Run it again and check that the two instruments are working
-        Assert.assertEquals(13, callTarget1.call());
-        Assert.assertEquals(counterA.enterCount, 1);
-        Assert.assertEquals(counterA.leaveCount, 1);
-        Assert.assertEquals(counterB.enterCount, 1);
-        Assert.assertEquals(counterB.leaveCount, 1);
-
-        // Remove counterA and check the "instrument chain"
-        counterA.dispose();
-        iterator = probeNode.getChildren().iterator();
-
-        // Run it again and check that instrument B is still working but not A
-        Assert.assertEquals(13, callTarget1.call());
-        Assert.assertEquals(counterA.enterCount, 1);
-        Assert.assertEquals(counterA.leaveCount, 1);
-        Assert.assertEquals(counterB.enterCount, 2);
-        Assert.assertEquals(counterB.leaveCount, 2);
-
-        // Simulate a split by cloning the AST
-        final CallTarget callTarget2 = runtime.createCallTarget((TestRootNode) rootNode.copy());
-        // Run the clone and check that instrument B is still working but not A
-        Assert.assertEquals(13, callTarget2.call());
-        Assert.assertEquals(counterA.enterCount, 1);
-        Assert.assertEquals(counterA.leaveCount, 1);
-        Assert.assertEquals(counterB.enterCount, 3);
-        Assert.assertEquals(counterB.leaveCount, 3);
-
-        // Run the original and check that instrument B is still working but not A
-        Assert.assertEquals(13, callTarget2.call());
-        Assert.assertEquals(counterA.enterCount, 1);
-        Assert.assertEquals(counterA.leaveCount, 1);
-        Assert.assertEquals(counterB.enterCount, 4);
-        Assert.assertEquals(counterB.leaveCount, 4);
-
-        // Attach a second instrument to the probe
-        final TestCounter counterC = new TestCounter();
-        counterC.attach(probe);
-
-        // Run the original and check that instruments B,C working but not A
-        Assert.assertEquals(13, callTarget1.call());
-        Assert.assertEquals(counterA.enterCount, 1);
-        Assert.assertEquals(counterA.leaveCount, 1);
-        Assert.assertEquals(counterB.enterCount, 5);
-        Assert.assertEquals(counterB.leaveCount, 5);
-        Assert.assertEquals(counterC.enterCount, 1);
-        Assert.assertEquals(counterC.leaveCount, 1);
-
-        // Run the clone and check that instruments B,C working but not A
-        Assert.assertEquals(13, callTarget2.call());
-        Assert.assertEquals(counterA.enterCount, 1);
-        Assert.assertEquals(counterA.leaveCount, 1);
-        Assert.assertEquals(counterB.enterCount, 6);
-        Assert.assertEquals(counterB.leaveCount, 6);
-        Assert.assertEquals(counterC.enterCount, 2);
-        Assert.assertEquals(counterC.leaveCount, 2);
-
-        // Remove instrumentC
-        counterC.dispose();
-
-        // Run the original and check that instrument B working but not A,C
-        Assert.assertEquals(13, callTarget1.call());
-        Assert.assertEquals(counterA.enterCount, 1);
-        Assert.assertEquals(counterA.leaveCount, 1);
-        Assert.assertEquals(counterB.enterCount, 7);
-        Assert.assertEquals(counterB.leaveCount, 7);
-        Assert.assertEquals(counterC.enterCount, 2);
-        Assert.assertEquals(counterC.leaveCount, 2);
-
-        // Run the clone and check that instrument B working but not A,C
-        Assert.assertEquals(13, callTarget2.call());
-        Assert.assertEquals(counterA.enterCount, 1);
-        Assert.assertEquals(counterA.leaveCount, 1);
-        Assert.assertEquals(counterB.enterCount, 8);
-        Assert.assertEquals(counterB.leaveCount, 8);
-        Assert.assertEquals(counterC.enterCount, 2);
-        Assert.assertEquals(counterC.leaveCount, 2);
-
-        // Remove instrumentB
-        counterB.dispose();
-
-        // Run both the original and clone, check that no instruments working
-        Assert.assertEquals(13, callTarget1.call());
-        Assert.assertEquals(13, callTarget2.call());
-        Assert.assertEquals(counterA.enterCount, 1);
-        Assert.assertEquals(counterA.leaveCount, 1);
-        Assert.assertEquals(counterB.enterCount, 8);
-        Assert.assertEquals(counterB.leaveCount, 8);
-        Assert.assertEquals(counterC.enterCount, 2);
-        Assert.assertEquals(counterC.leaveCount, 2);
-    }
-
-    @Test
-    public void testTagging() {
-
-        // Applies appropriate tags
-        final TestASTProber astProber = new TestASTProber();
-        Probe.registerASTProber(astProber);
-
-        // Listens for probes and tags being added
-        final TestProbeListener probeListener = new TestProbeListener();
-        Probe.addProbeListener(probeListener);
-
-        // Counts all entries to all instances of addition nodes
-        final TestMultiCounter additionCounter = new TestMultiCounter();
-
-        // Counts all entries to all instances of value nodes
-        final TestMultiCounter valueCounter = new TestMultiCounter();
-
-        // 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 callTarget = runtime.createCallTarget(rootNode);
-
-        // Check that the prober added probes to the tree
-        Assert.assertEquals(probeListener.probeCount, 3);
-        Assert.assertEquals(probeListener.tagCount, 3);
-
-        Assert.assertEquals(Probe.findProbesTaggedAs(ADD_TAG).size(), 1);
-        Assert.assertEquals(Probe.findProbesTaggedAs(VALUE_TAG).size(), 2);
-
-        // Check that it executes correctly
-        Assert.assertEquals(13, callTarget.call());
-
-        // Dynamically attach a counter for all executions of all Addition nodes
-        for (Probe probe : Probe.findProbesTaggedAs(ADD_TAG)) {
-            additionCounter.attachCounter(probe);
-        }
-        // Dynamically attach a counter for all executions of all Value nodes
-        for (Probe probe : Probe.findProbesTaggedAs(VALUE_TAG)) {
-            valueCounter.attachCounter(probe);
-        }
-
-        // Counters initialized at 0
-        Assert.assertEquals(additionCounter.count, 0);
-        Assert.assertEquals(valueCounter.count, 0);
-
-        // Execute again
-        Assert.assertEquals(13, callTarget.call());
-
-        // There are two value nodes in the AST, but only one addition node
-        Assert.assertEquals(additionCounter.count, 1);
-        Assert.assertEquals(valueCounter.count, 2);
-
-        Probe.unregisterASTProber(astProber);
-
-    }
-
-    @Test
-    public void testProbeLite() {
-
-        // Use the "lite-probing" option, limited to a single pass of
-        // probing and a single Instrument at each probed node. This
-        // particular test uses a shared event receiver at every
-        // lite-probed node.
-        final TestEventReceiver receiver = new TestEventReceiver();
-
-        TestASTLiteProber astLiteProber = new TestASTLiteProber(receiver);
-        Probe.registerASTProber(astLiteProber);
-
-        // 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);
-
-        // Creating a call target sets the parent pointers in this tree and is necessary prior to
-        // checking any parent/child relationships
-        final CallTarget callTarget = runtime.createCallTarget(rootNode);
-
-        // Check that the instrument is working as expected.
-        Assert.assertEquals(0, receiver.counter);
-        callTarget.call();
-        Assert.assertEquals(2, receiver.counter);
-
-        // Check that you can't probe a node that's already received a probeLite() call
-        try {
-            leftValueNode.probe();
-            Assert.fail();
-        } catch (IllegalStateException e) {
-        }
-
-        try {
-            rightValueNode.probe();
-            Assert.fail();
-        } catch (IllegalStateException e) {
-        }
-
-        // Check tree structure
-        Assert.assertTrue(leftValueNode.getParent() instanceof TestLanguageWrapperNode);
-        Assert.assertTrue(rightValueNode.getParent() instanceof TestLanguageWrapperNode);
-        TestLanguageWrapperNode leftWrapper = (TestLanguageWrapperNode) leftValueNode.getParent();
-        TestLanguageWrapperNode rightWrapper = (TestLanguageWrapperNode) rightValueNode.getParent();
-        Assert.assertEquals(addNode, leftWrapper.getParent());
-        Assert.assertEquals(addNode, rightWrapper.getParent());
-        Iterator<Node> iterator = addNode.getChildren().iterator();
-        Assert.assertEquals(leftWrapper, iterator.next());
-        Assert.assertEquals(rightWrapper, iterator.next());
-        Assert.assertFalse(iterator.hasNext());
-        Assert.assertEquals(rootNode, addNode.getParent());
-        iterator = rootNode.getChildren().iterator();
-        Assert.assertEquals(addNode, iterator.next());
-        Assert.assertFalse(iterator.hasNext());
-
-        // Check that you can't get a probe on the wrappers because they were "lite-probed"
-        try {
-            leftWrapper.getProbe();
-            Assert.fail();
-        } catch (IllegalStateException e) {
-        }
-        try {
-            rightWrapper.getProbe();
-            Assert.fail();
-        } catch (IllegalStateException e) {
-        }
-
-        // Check that you can't probe the wrappers
-        try {
-            leftWrapper.probe();
-            Assert.fail();
-        } catch (IllegalStateException e) {
-        }
-        try {
-            rightWrapper.probe();
-            Assert.fail();
-        } catch (IllegalStateException e) {
-        }
-        try {
-            leftWrapper.probeLite(null);
-            Assert.fail();
-        } catch (IllegalStateException e) {
-        }
-        try {
-            rightWrapper.probeLite(null);
-            Assert.fail();
-        } catch (IllegalStateException e) {
-        }
-
-        // Use reflection to check that each WrapperNode has a ProbeLiteNode with a
-        // SimpleEventReceiver
-        try {
-            Field probeNodeField = leftWrapper.getClass().getDeclaredField("probeNode");
-
-            // cheat: probeNode is private, so we change it's accessibility at runtime
-            probeNodeField.setAccessible(true);
-            ProbeNode probeNode = (ProbeNode) probeNodeField.get(leftWrapper);
-
-            // hack: Since ProbeLiteNode is not visible, we do a string compare here
-            Assert.assertTrue(probeNode.getClass().toString().endsWith("ProbeLiteNode"));
-
-            // Now we do the same to check the type of the eventReceiver in ProbeLiteNode
-            Field eventReceiverField = probeNode.getClass().getDeclaredField("eventReceiver");
-            eventReceiverField.setAccessible(true);
-            TruffleEventReceiver eventReceiver = (TruffleEventReceiver) eventReceiverField.get(probeNode);
-            Assert.assertTrue(eventReceiver instanceof SimpleEventReceiver);
-
-            // Reset accessibility
-            probeNodeField.setAccessible(false);
-            eventReceiverField.setAccessible(false);
-
-        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
-            Assert.fail();
-        }
-
-        Probe.unregisterASTProber(astLiteProber);
-
-    }
-
-    /**
-     * A guest language usually has a single language-specific subclass of {@link Node} from which
-     * all other nodes in the guest language subclass. By making this node {@link Instrumentable},
-     * we allow all nodes of the guest language to have {@link Probe}s attach to them.
-     *
-     */
-    private abstract class TestLanguageNode extends Node implements Instrumentable {
-        public abstract Object execute(VirtualFrame frame);
-
-        public Probe probe() {
-            Node parent = getParent();
-
-            if (parent == null) {
-                throw new IllegalStateException("Cannot call probe() on a node without a parent.");
-            }
-
-            if (parent instanceof TestLanguageWrapperNode) {
-                return ((TestLanguageWrapperNode) parent).getProbe();
-            }
-
-            // Create a new wrapper/probe with this node as its child.
-            final TestLanguageWrapperNode wrapper = new TestLanguageWrapperNode(this);
-
-            // Connect it to a Probe
-            final Probe probe = ProbeNode.insertProbe(wrapper);
-
-            // Replace this node in the AST with the wrapper
-            this.replace(wrapper);
-
-            return probe;
-        }
-
-        public void probeLite(TruffleEventReceiver eventReceiver) {
-            Node parent = getParent();
-
-            if (parent == null) {
-                throw new IllegalStateException("Cannot call probeLite() on a node without a parent");
-            }
-
-            if (parent instanceof TestLanguageWrapperNode) {
-                throw new IllegalStateException("Cannot call probeLite() on a node that already has a wrapper.");
-            }
-
-            final TestLanguageWrapperNode wrapper = new TestLanguageWrapperNode(this);
-            ProbeNode.insertProbeLite(wrapper, eventReceiver);
-
-            this.replace(wrapper);
-        }
-    }
-
-    /**
-     * The wrapper node class is usually language-specific and inherits from the language-specific
-     * subclass of {@link Node}, in this case, {@link TestLanguageNode}.
-     */
-    @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;
-        }
-
-        public String instrumentationInfo() {
-            return "Wrapper node for testing";
-        }
-
-        public void insertProbe(ProbeNode newProbeNode) {
-            this.probeNode = newProbeNode;
-        }
-
-        public Probe getProbe() {
-            try {
-                return probeNode.getProbe();
-            } catch (IllegalStateException e) {
-                throw new IllegalStateException("Cannot call getProbe() on a wrapper that has no probe");
-            }
-        }
-
-        @Override
-        public Node getChild() {
-            return child;
-        }
-
-        @Override
-        public Object execute(VirtualFrame frame) {
-            probeNode.enter(child, frame);
-            Object result;
-
-            try {
-                result = child.execute(frame);
-                probeNode.returnValue(child, frame, result);
-            } catch (KillException e) {
-                throw (e);
-            } catch (Exception e) {
-                probeNode.returnExceptional(child, frame, e);
-                throw (e);
-            }
-
-            return result;
-        }
-
-        @Override
-        public Probe probe() {
-            throw new IllegalStateException("Cannot call probe() on a wrapper.");
-        }
-
-        @Override
-        public void probeLite(TruffleEventReceiver eventReceiver) {
-            throw new IllegalStateException("Cannot call probeLite() on a wrapper.");
-        }
-    }
-
-    /**
-     * 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 frame) {
-            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 frame) {
-            return new Integer(((Integer) leftChild.execute(frame)).intValue() + ((Integer) rightChild.execute(frame)).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 frame) {
-            return body.execute(frame);
-        }
-
-        @Override
-        public boolean isCloningAllowed() {
-            return true;
-        }
-
-        @Override
-        public void applyInstrumentation() {
-            Probe.applyASTProbers(body);
-        }
-    }
-
-    /**
-     * A counter for the number of times execution enters and leaves a probed AST node.
-     */
-    private class TestCounter {
-
-        public int enterCount = 0;
-        public int leaveCount = 0;
-        public final Instrument instrument;
-
-        public TestCounter() {
-            instrument = Instrument.create(new SimpleEventReceiver() {
-
-                @Override
-                public void enter(Node node, VirtualFrame frame) {
-                    enterCount++;
-                }
-
-                @Override
-                public void returnAny(Node node, VirtualFrame frame) {
-                    leaveCount++;
-                }
-            }, "Instrumentation Test Counter");
-        }
-
-        public void attach(Probe probe) {
-            probe.attach(instrument);
-        }
-
-        public void dispose() {
-            instrument.dispose();
-        }
-
-    }
-
-    /**
-     * Tags selected nodes on newly constructed ASTs.
-     */
-    private static final class TestASTProber implements NodeVisitor, ASTProber {
-
-        public boolean visit(Node node) {
-            if (node instanceof TestLanguageNode) {
-
-                final TestLanguageNode testNode = (TestLanguageNode) node;
-
-                if (node instanceof TestValueNode) {
-                    testNode.probe().tagAs(VALUE_TAG, null);
-
-                } else if (node instanceof TestAdditionNode) {
-                    testNode.probe().tagAs(ADD_TAG, null);
-
-                }
-            }
-            return true;
-        }
-
-        public void probeAST(Node node) {
-            node.accept(this);
-        }
-    }
-
-    /**
-     * "lite-probes" every value node with a shared event receiver.
-     */
-    private static final class TestASTLiteProber implements NodeVisitor, ASTProber {
-        private final TruffleEventReceiver eventReceiver;
-
-        public TestASTLiteProber(SimpleEventReceiver simpleEventReceiver) {
-            this.eventReceiver = simpleEventReceiver;
-        }
-
-        public boolean visit(Node node) {
-            if (node instanceof TestValueNode) {
-                final TestLanguageNode testNode = (TestValueNode) node;
-                testNode.probeLite(eventReceiver);
-            }
-            return true;
-        }
-
-        public void probeAST(Node node) {
-            node.accept(this);
-        }
-    }
-
-    /**
-     * Counts the number of "enter" events at probed nodes.
-     *
-     */
-    static final class TestEventReceiver extends SimpleEventReceiver {
-
-        public int counter = 0;
-
-        @Override
-        public void enter(Node node, VirtualFrame frame) {
-            counter++;
-        }
-
-    }
-
-    /**
-     * A counter that can count executions at multiple nodes; it attaches a separate instrument at
-     * each Probe, but keeps a total count.
-     */
-    private static final class TestMultiCounter {
-
-        public int count = 0;
-
-        public void attachCounter(Probe probe) {
-
-            // Attach a new instrument for every Probe
-            // 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 SimpleEventReceiver() {
-
-                @Override
-                public void enter(Node node, VirtualFrame frame) {
-                    count++;
-                }
-            }, "Instrumentation Test MultiCounter"));
-        }
-    }
-
-    private static final class TestProbeListener implements ProbeListener {
-
-        public int probeCount = 0;
-        public int tagCount = 0;
-
-        public void newProbeInserted(Probe probe) {
-            probeCount++;
-        }
-
-        public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
-            tagCount++;
-        }
-
-        public void startASTProbing(Source source) {
-        }
-
-        public void endASTProbing(Source source) {
-        }
-
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/InstrumentationTest.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,721 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import java.util.*;
+
+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.instrument.ProbeFailure.Reason;
+import com.oracle.truffle.api.instrument.ProbeNode.WrapperNode;
+import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.nodes.*;
+
+/**
+ * <h3>AST Instrumentation</h3>
+ *
+ * Instrumentation allows the insertion into Truffle ASTs language-specific instances of
+ * {@link WrapperNode} that propagate execution events through a {@link Probe} to any instances of
+ * {@link Instrument} that might be attached to the particular probe by tools.
+ * <ol>
+ * <li>Creates a simple add AST</li>
+ * <li>Verifies its structure</li>
+ * <li>"Probes" the add node by adding a {@link WrapperNode} and associated {@link Probe}</li>
+ * <li>Attaches a simple {@link Instrument} to the node via the Probe's {@link ProbeNode}</li>
+ * <li>Verifies the structure of the probed AST</li>
+ * <li>Verifies the execution of the probed AST</li>
+ * <li>Verifies the results observed by the instrument.</li>
+ * </ol>
+ * To do these tests, several required classes have been implemented in their most basic form, only
+ * implementing the methods necessary for the tests to pass, with stubs elsewhere.
+ */
+public class InstrumentationTest {
+
+    private static final SyntaxTag ADD_TAG = new SyntaxTag() {
+
+        @Override
+        public String name() {
+            return "Addition";
+        }
+
+        @Override
+        public String getDescription() {
+            return "Test Language Addition Node";
+        }
+    };
+
+    private static final SyntaxTag VALUE_TAG = new SyntaxTag() {
+
+        @Override
+        public String name() {
+            return "Value";
+        }
+
+        @Override
+        public String getDescription() {
+            return "Test Language Value Node";
+        }
+    };
+
+    @Test
+    public void testBasicInstrumentation() {
+        // 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);
+
+        try {
+            addNode.probe();
+        } catch (ProbeException e) {
+            assertEquals(e.getFailure().getReason(), Reason.NO_PARENT);
+        }
+        final TestRootNode rootNode = new TestRootNode(addNode);
+
+        // Creating a call target sets the parent pointers in this tree and is necessary prior to
+        // checking any parent/child relationships
+        final CallTarget callTarget1 = runtime.createCallTarget(rootNode);
+
+        // Check the tree structure
+        assertEquals(addNode, leftValueNode.getParent());
+        assertEquals(addNode, rightValueNode.getParent());
+        Iterator<Node> iterator = addNode.getChildren().iterator();
+        assertEquals(leftValueNode, iterator.next());
+        assertEquals(rightValueNode, iterator.next());
+        assertFalse(iterator.hasNext());
+        assertEquals(rootNode, addNode.getParent());
+        iterator = rootNode.getChildren().iterator();
+        assertEquals(addNode, iterator.next());
+        assertFalse(iterator.hasNext());
+
+        // Ensure it executes correctly
+        assertEquals(13, callTarget1.call());
+
+        // Probe the addition node
+        final Probe probe = addNode.probe();
+
+        // Check the modified tree structure
+        assertEquals(addNode, leftValueNode.getParent());
+        assertEquals(addNode, rightValueNode.getParent());
+        iterator = addNode.getChildren().iterator();
+        assertEquals(leftValueNode, iterator.next());
+        assertEquals(rightValueNode, iterator.next());
+        assertFalse(iterator.hasNext());
+
+        // Ensure there's a WrapperNode correctly inserted into the AST
+        iterator = rootNode.getChildren().iterator();
+        Node wrapperNode = iterator.next();
+        assertTrue(wrapperNode instanceof TestLanguageWrapperNode);
+        assertFalse(iterator.hasNext());
+        assertEquals(rootNode, wrapperNode.getParent());
+
+        // Check that the WrapperNode has both the probe and the wrapped node as children
+        iterator = wrapperNode.getChildren().iterator();
+        assertEquals(addNode, iterator.next());
+        ProbeNode probeNode = (ProbeNode) iterator.next();
+        assertTrue(probeNode.getProbe() != null);
+        assertFalse(iterator.hasNext());
+
+        // Check that you can't probe the WrapperNodes
+        TestLanguageWrapperNode wrapper = (TestLanguageWrapperNode) wrapperNode;
+        try {
+            wrapper.probe();
+            fail();
+        } catch (ProbeException e) {
+            assertEquals(e.getFailure().getReason(), Reason.WRAPPER_NODE);
+        }
+
+        // Check that the "probed" AST still executes correctly
+        assertEquals(13, callTarget1.call());
+
+        // Attach a counting instrument to the probe
+        final TestCounter counterA = new TestCounter();
+        counterA.attach(probe);
+
+        // Attach a second counting instrument to the probe
+        final TestCounter counterB = new TestCounter();
+        counterB.attach(probe);
+
+        // Run it again and check that the two instruments are working
+        assertEquals(13, callTarget1.call());
+        assertEquals(counterA.enterCount, 1);
+        assertEquals(counterA.leaveCount, 1);
+        assertEquals(counterB.enterCount, 1);
+        assertEquals(counterB.leaveCount, 1);
+
+        // Remove counterA and check the "instrument chain"
+        counterA.dispose();
+        iterator = probeNode.getChildren().iterator();
+
+        // Run it again and check that instrument B is still working but not A
+        assertEquals(13, callTarget1.call());
+        assertEquals(counterA.enterCount, 1);
+        assertEquals(counterA.leaveCount, 1);
+        assertEquals(counterB.enterCount, 2);
+        assertEquals(counterB.leaveCount, 2);
+
+        // Simulate a split by cloning the AST
+        final CallTarget callTarget2 = runtime.createCallTarget((TestRootNode) rootNode.copy());
+        // Run the clone and check that instrument B is still working but not A
+        assertEquals(13, callTarget2.call());
+        assertEquals(counterA.enterCount, 1);
+        assertEquals(counterA.leaveCount, 1);
+        assertEquals(counterB.enterCount, 3);
+        assertEquals(counterB.leaveCount, 3);
+
+        // Run the original and check that instrument B is still working but not A
+        assertEquals(13, callTarget2.call());
+        assertEquals(counterA.enterCount, 1);
+        assertEquals(counterA.leaveCount, 1);
+        assertEquals(counterB.enterCount, 4);
+        assertEquals(counterB.leaveCount, 4);
+
+        // Attach a second instrument to the probe
+        final TestCounter counterC = new TestCounter();
+        counterC.attach(probe);
+
+        // Run the original and check that instruments B,C working but not A
+        assertEquals(13, callTarget1.call());
+        assertEquals(counterA.enterCount, 1);
+        assertEquals(counterA.leaveCount, 1);
+        assertEquals(counterB.enterCount, 5);
+        assertEquals(counterB.leaveCount, 5);
+        assertEquals(counterC.enterCount, 1);
+        assertEquals(counterC.leaveCount, 1);
+
+        // Run the clone and check that instruments B,C working but not A
+        assertEquals(13, callTarget2.call());
+        assertEquals(counterA.enterCount, 1);
+        assertEquals(counterA.leaveCount, 1);
+        assertEquals(counterB.enterCount, 6);
+        assertEquals(counterB.leaveCount, 6);
+        assertEquals(counterC.enterCount, 2);
+        assertEquals(counterC.leaveCount, 2);
+
+        // Remove instrumentC
+        counterC.dispose();
+
+        // Run the original and check that instrument B working but not A,C
+        assertEquals(13, callTarget1.call());
+        assertEquals(counterA.enterCount, 1);
+        assertEquals(counterA.leaveCount, 1);
+        assertEquals(counterB.enterCount, 7);
+        assertEquals(counterB.leaveCount, 7);
+        assertEquals(counterC.enterCount, 2);
+        assertEquals(counterC.leaveCount, 2);
+
+        // Run the clone and check that instrument B working but not A,C
+        assertEquals(13, callTarget2.call());
+        assertEquals(counterA.enterCount, 1);
+        assertEquals(counterA.leaveCount, 1);
+        assertEquals(counterB.enterCount, 8);
+        assertEquals(counterB.leaveCount, 8);
+        assertEquals(counterC.enterCount, 2);
+        assertEquals(counterC.leaveCount, 2);
+
+        // Remove instrumentB
+        counterB.dispose();
+
+        // Run both the original and clone, check that no instruments working
+        assertEquals(13, callTarget1.call());
+        assertEquals(13, callTarget2.call());
+        assertEquals(counterA.enterCount, 1);
+        assertEquals(counterA.leaveCount, 1);
+        assertEquals(counterB.enterCount, 8);
+        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);
+
+        // Listens for probes and tags being added
+        final TestProbeListener probeListener = new TestProbeListener();
+        Probe.addProbeListener(probeListener);
+
+        // Counts all entries to all instances of addition nodes
+        final TestMultiCounter additionCounter = new TestMultiCounter();
+
+        // Counts all entries to all instances of value nodes
+        final TestMultiCounter valueCounter = new TestMultiCounter();
+
+        // 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 callTarget = runtime.createCallTarget(rootNode);
+
+        // Check that the prober added probes to the tree
+        assertEquals(probeListener.probeCount, 3);
+        assertEquals(probeListener.tagCount, 3);
+
+        assertEquals(Probe.findProbesTaggedAs(ADD_TAG).size(), 1);
+        assertEquals(Probe.findProbesTaggedAs(VALUE_TAG).size(), 2);
+
+        // Check that it executes correctly
+        assertEquals(13, callTarget.call());
+
+        // Dynamically attach a counter for all executions of all Addition nodes
+        for (Probe probe : Probe.findProbesTaggedAs(ADD_TAG)) {
+            additionCounter.attachCounter(probe);
+        }
+        // Dynamically attach a counter for all executions of all Value nodes
+        for (Probe probe : Probe.findProbesTaggedAs(VALUE_TAG)) {
+            valueCounter.attachCounter(probe);
+        }
+
+        // Counters initialized at 0
+        assertEquals(additionCounter.count, 0);
+        assertEquals(valueCounter.count, 0);
+
+        // Execute again
+        assertEquals(13, callTarget.call());
+
+        // There are two value nodes in the AST, but only one addition node
+        assertEquals(additionCounter.count, 1);
+        assertEquals(valueCounter.count, 2);
+
+        Probe.unregisterASTProber(astProber);
+
+    }
+
+    @Test
+    public void testProbeLite() {
+
+        // Use the "lite-probing" option, limited to a single pass of
+        // probing and a single Instrument at each probed node. This
+        // particular test uses a shared event receiver at every
+        // lite-probed node.
+        final TestEventReceiver receiver = new TestEventReceiver();
+
+        TestASTLiteProber astLiteProber = new TestASTLiteProber(receiver);
+        Probe.registerASTProber(astLiteProber);
+
+        // 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);
+
+        // Creating a call target sets the parent pointers in this tree and is necessary prior to
+        // checking any parent/child relationships
+        final CallTarget callTarget = runtime.createCallTarget(rootNode);
+
+        // Check that the instrument is working as expected.
+        assertEquals(0, receiver.counter);
+        callTarget.call();
+        assertEquals(2, receiver.counter);
+
+        // Check that you can't probe a node that's already received a probeLite() call
+        try {
+            leftValueNode.probe();
+            fail();
+        } catch (IllegalStateException e) {
+        }
+
+        try {
+            rightValueNode.probe();
+            fail();
+        } catch (IllegalStateException e) {
+        }
+
+        // Check tree structure
+        assertTrue(leftValueNode.getParent() instanceof TestLanguageWrapperNode);
+        assertTrue(rightValueNode.getParent() instanceof TestLanguageWrapperNode);
+        TestLanguageWrapperNode leftWrapper = (TestLanguageWrapperNode) leftValueNode.getParent();
+        TestLanguageWrapperNode rightWrapper = (TestLanguageWrapperNode) rightValueNode.getParent();
+        assertEquals(addNode, leftWrapper.getParent());
+        assertEquals(addNode, rightWrapper.getParent());
+        Iterator<Node> iterator = addNode.getChildren().iterator();
+        assertEquals(leftWrapper, iterator.next());
+        assertEquals(rightWrapper, iterator.next());
+        assertFalse(iterator.hasNext());
+        assertEquals(rootNode, addNode.getParent());
+        iterator = rootNode.getChildren().iterator();
+        assertEquals(addNode, iterator.next());
+        assertFalse(iterator.hasNext());
+
+        // Check that you can't get a probe on the wrappers because they were "lite-probed"
+        try {
+            leftWrapper.getProbe();
+            fail();
+        } catch (IllegalStateException e) {
+        }
+        try {
+            rightWrapper.getProbe();
+            fail();
+        } catch (IllegalStateException e) {
+        }
+
+        // Check that you can't probe the wrappers
+        try {
+            leftWrapper.probe();
+            fail();
+        } catch (ProbeException e) {
+            assertEquals(e.getFailure().getReason(), ProbeFailure.Reason.WRAPPER_NODE);
+        }
+        try {
+            rightWrapper.probe();
+            fail();
+        } catch (ProbeException e) {
+            assertEquals(e.getFailure().getReason(), ProbeFailure.Reason.WRAPPER_NODE);
+        }
+        try {
+            leftWrapper.probeLite(null);
+            fail();
+        } catch (ProbeException e) {
+            assertEquals(e.getFailure().getReason(), ProbeFailure.Reason.WRAPPER_NODE);
+        }
+        try {
+            rightWrapper.probeLite(null);
+            fail();
+        } catch (ProbeException e) {
+            assertEquals(e.getFailure().getReason(), ProbeFailure.Reason.WRAPPER_NODE);
+        }
+
+        // Use reflection to check that each WrapperNode has a ProbeLiteNode with a
+        // SimpleEventReceiver
+        try {
+            java.lang.reflect.Field probeNodeField = leftWrapper.getClass().getDeclaredField("probeNode");
+
+            // cheat: probeNode is private, so we change it's accessibility at runtime
+            probeNodeField.setAccessible(true);
+            ProbeNode probeNode = (ProbeNode) probeNodeField.get(leftWrapper);
+
+            // hack: Since ProbeLiteNode is not visible, we do a string compare here
+            assertTrue(probeNode.getClass().toString().endsWith("ProbeLiteNode"));
+
+            // Now we do the same to check the type of the eventReceiver in ProbeLiteNode
+            java.lang.reflect.Field eventReceiverField = probeNode.getClass().getDeclaredField("eventReceiver");
+            eventReceiverField.setAccessible(true);
+            TruffleEventReceiver eventReceiver = (TruffleEventReceiver) eventReceiverField.get(probeNode);
+            assertTrue(eventReceiver instanceof SimpleEventReceiver);
+
+            // Reset accessibility
+            probeNodeField.setAccessible(false);
+            eventReceiverField.setAccessible(false);
+
+        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
+            fail();
+        }
+
+        Probe.unregisterASTProber(astLiteProber);
+
+    }
+
+    private abstract class TestLanguageNode extends Node {
+        public abstract Object execute(VirtualFrame frame);
+
+        @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() {
+            try {
+                return probeNode.getProbe();
+            } catch (IllegalStateException e) {
+                throw new IllegalStateException("Cannot call getProbe() on a wrapper that has no probe");
+            }
+        }
+
+        @Override
+        public Node getChild() {
+            return child;
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            probeNode.enter(child, frame);
+            Object result;
+
+            try {
+                result = child.execute(frame);
+                probeNode.returnValue(child, frame, result);
+            } catch (KillException e) {
+                throw (e);
+            } catch (Exception e) {
+                probeNode.returnExceptional(child, frame, 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 frame) {
+            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 frame) {
+            return new Integer(((Integer) leftChild.execute(frame)).intValue() + ((Integer) rightChild.execute(frame)).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 frame) {
+            return body.execute(frame);
+        }
+
+        @Override
+        public boolean isCloningAllowed() {
+            return true;
+        }
+
+        @Override
+        public void applyInstrumentation() {
+            Probe.applyASTProbers(body);
+        }
+    }
+
+    /**
+     * A counter for the number of times execution enters and leaves a probed AST node.
+     */
+    private class TestCounter {
+
+        public int enterCount = 0;
+        public int leaveCount = 0;
+        public final Instrument instrument;
+
+        public TestCounter() {
+            instrument = Instrument.create(new SimpleEventReceiver() {
+
+                @Override
+                public void enter(Node node, VirtualFrame frame) {
+                    enterCount++;
+                }
+
+                @Override
+                public void returnAny(Node node, VirtualFrame frame) {
+                    leaveCount++;
+                }
+            }, "Instrumentation Test Counter");
+        }
+
+        public void attach(Probe probe) {
+            probe.attach(instrument);
+        }
+
+        public void dispose() {
+            instrument.dispose();
+        }
+
+    }
+
+    /**
+     * Tags selected nodes on newly constructed ASTs.
+     */
+    private static final class TestASTProber implements NodeVisitor, ASTProber {
+
+        @Override
+        public boolean visit(Node node) {
+            if (node instanceof TestLanguageNode) {
+
+                final TestLanguageNode testNode = (TestLanguageNode) node;
+
+                if (node instanceof TestValueNode) {
+                    testNode.probe().tagAs(VALUE_TAG, null);
+
+                } else if (node instanceof TestAdditionNode) {
+                    testNode.probe().tagAs(ADD_TAG, null);
+
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public void probeAST(Node node) {
+            node.accept(this);
+        }
+    }
+
+    /**
+     * "lite-probes" every value node with a shared event receiver.
+     */
+    private static final class TestASTLiteProber implements NodeVisitor, ASTProber {
+        private final TruffleEventReceiver eventReceiver;
+
+        public TestASTLiteProber(SimpleEventReceiver simpleEventReceiver) {
+            this.eventReceiver = simpleEventReceiver;
+        }
+
+        public boolean visit(Node node) {
+            if (node instanceof TestValueNode) {
+                final TestLanguageNode testNode = (TestValueNode) node;
+                testNode.probeLite(eventReceiver);
+            }
+            return true;
+        }
+
+        public void probeAST(Node node) {
+            node.accept(this);
+        }
+    }
+
+    /**
+     * Counts the number of "enter" events at probed nodes.
+     *
+     */
+    static final class TestEventReceiver extends SimpleEventReceiver {
+
+        public int counter = 0;
+
+        @Override
+        public void enter(Node node, VirtualFrame frame) {
+            counter++;
+        }
+
+    }
+
+    /**
+     * A counter that can count executions at multiple nodes; it attaches a separate instrument at
+     * each Probe, but keeps a total count.
+     */
+    private static final class TestMultiCounter {
+
+        public int count = 0;
+
+        public void attachCounter(Probe probe) {
+
+            // Attach a new instrument for every Probe
+            // 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 SimpleEventReceiver() {
+
+                @Override
+                public void enter(Node node, VirtualFrame frame) {
+                    count++;
+                }
+            }, "Instrumentation Test MultiCounter"));
+        }
+    }
+
+    private static final class TestProbeListener extends DefaultProbeListener {
+
+        public int probeCount = 0;
+        public int tagCount = 0;
+
+        @Override
+        public void newProbeInserted(Probe probe) {
+            probeCount++;
+        }
+
+        @Override
+        public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
+            tagCount++;
+        }
+    }
+
+}
--- /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	Wed Jan 28 11:28:03 2015 +0100
@@ -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.test/src/com/oracle/truffle/api/test/package-info.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/package-info.java	Wed Jan 28 11:28:03 2015 +0100
@@ -44,7 +44,7 @@
  * <li>How to use frames and frame slots to store values local to an activation? {@link com.oracle.truffle.api.test.FrameTest}</li>
  * <li>How to use type specialization and speculation for frame slots? {@link com.oracle.truffle.api.test.FrameSlotTypeSpecializationTest}</li>
  * <li>How to use type specialization and speculation for node return values? {@link com.oracle.truffle.api.test.ReturnTypeSpecializationTest}</li>
- * <li>How to "instrument" an AST with nodes that can provide access to runtime state from external tools {@link com.oracle.truffle.api.test.InstrumentationTest}</li>
+ * <li>How to "instrument" an AST with nodes that can provide access to runtime state from external tools {@link com.oracle.truffle.api.test.instrument.InstrumentationTest}</li>
  * </ul>
  *
  *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/source/SourceTextTest.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,202 @@
+/*
+ * 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
+ * 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.source;
+
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.source.*;
+
+public class SourceTextTest {
+
+    private final Source emptySource = Source.fromText("", null);
+
+    private final Source emptyLineSource = Source.fromText("\n", null);
+
+    private final Source shortSource = Source.fromText("01", null);
+
+    private final Source longSource = Source.fromText("01234\n67\n9\n", null);
+
+    @Test
+    public void emptyTextTest0() {
+        assertEquals(emptySource.getLineCount(), 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyTextTest1() {
+        emptySource.getLineNumber(0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyTextTest2() {
+        emptySource.getColumnNumber(0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyTextTest3() {
+        emptySource.getLineNumber(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyTextTest4() {
+        emptySource.getLineStartOffset(0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyTextTest5() {
+        emptySource.getLineStartOffset(1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyTextTest6() {
+        emptySource.getLineLength(1);
+    }
+
+    @Test
+    public void emptyLineTest0() {
+        assertEquals(emptyLineSource.getLineCount(), 1);
+        assertEquals(emptyLineSource.getLineNumber(0), 1);
+        assertEquals(emptyLineSource.getLineStartOffset(1), 0);
+        assertEquals(emptyLineSource.getColumnNumber(0), 1);
+        assertEquals(emptyLineSource.getLineLength(1), 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyLineTest1() {
+        emptyLineSource.getLineNumber(1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyLineTest2() {
+        emptyLineSource.getLineStartOffset(2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyLineTest3() {
+        emptyLineSource.getColumnNumber(1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyLineTest4() {
+        emptyLineSource.getLineLength(2);
+    }
+
+    @Test
+    public void shortTextTest0() {
+
+        assertEquals(shortSource.getLineCount(), 1);
+
+        assertEquals(shortSource.getLineNumber(0), 1);
+        assertEquals(shortSource.getLineStartOffset(1), 0);
+        assertEquals(shortSource.getColumnNumber(0), 1);
+
+        assertEquals(shortSource.getLineNumber(1), 1);
+        assertEquals(shortSource.getColumnNumber(1), 2);
+
+        assertEquals(shortSource.getLineLength(1), 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shortTextTest1() {
+        shortSource.getLineNumber(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shortTextTest2() {
+        shortSource.getColumnNumber(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shortTextTest3() {
+        shortSource.getLineNumber(2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shortTextTest4() {
+        shortSource.getColumnNumber(2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shortTextTest5() {
+        shortSource.getLineLength(2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shortTextTest6() {
+        shortSource.getLineLength(2);
+    }
+
+    @Test
+    public void longTextTest0() {
+
+        assertEquals(longSource.getLineCount(), 3);
+
+        assertEquals(longSource.getLineNumber(0), 1);
+        assertEquals(longSource.getLineStartOffset(1), 0);
+        assertEquals(longSource.getColumnNumber(0), 1);
+
+        assertEquals(longSource.getLineNumber(4), 1);
+        assertEquals(longSource.getColumnNumber(4), 5);
+
+        assertEquals(longSource.getLineNumber(5), 1); // newline
+        assertEquals(longSource.getColumnNumber(5), 6); // newline
+        assertEquals(longSource.getLineLength(1), 5);
+
+        assertEquals(longSource.getLineNumber(6), 2);
+        assertEquals(longSource.getLineStartOffset(2), 6);
+        assertEquals(longSource.getColumnNumber(6), 1);
+
+        assertEquals(longSource.getLineNumber(7), 2);
+        assertEquals(longSource.getColumnNumber(7), 2);
+
+        assertEquals(longSource.getLineNumber(8), 2); // newline
+        assertEquals(longSource.getLineNumber(8), 2); // newline
+        assertEquals(longSource.getLineLength(2), 2);
+
+        assertEquals(longSource.getLineNumber(9), 3);
+        assertEquals(longSource.getLineStartOffset(3), 9);
+        assertEquals(longSource.getColumnNumber(9), 1);
+
+        assertEquals(longSource.getLineNumber(10), 3); // newline
+        assertEquals(longSource.getColumnNumber(10), 2); // newline
+        assertEquals(longSource.getLineLength(3), 1);
+
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void longTextTest1() {
+        longSource.getLineNumber(11);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void longTextTest2() {
+        longSource.getColumnNumber(11);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void longTextTest3() {
+        longSource.getLineStartOffset(4);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/CoverageTrackerTest.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,114 @@
+/*
+ * 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.tools;
+
+import static com.oracle.truffle.api.test.tools.TestNodes.*;
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.tools.*;
+
+public class CoverageTrackerTest {
+
+    @Test
+    public void testNoExecution() {
+        final CoverageTracker tool = new CoverageTracker();
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+        tool.install();
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+        tool.setEnabled(false);
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+        tool.setEnabled(true);
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+        tool.reset();
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+        tool.dispose();
+        assertEquals(tool.getCounts().entrySet().size(), 0);
+    }
+
+    @Test
+    public void testToolCreatedTooLate() {
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+        final CoverageTracker tool = new CoverageTracker();
+        tool.install();
+        assertEquals(13, expr13rootNode.execute(null));
+        assertTrue(tool.getCounts().isEmpty());
+        tool.dispose();
+    }
+
+    @Test
+    public void testToolInstalledcTooLate() {
+        final CoverageTracker tool = new CoverageTracker();
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+        tool.install();
+        assertEquals(13, expr13rootNode.execute(null));
+        assertTrue(tool.getCounts().isEmpty());
+        tool.dispose();
+    }
+
+    @Test
+    public void testCountingCoverage() {
+        final CoverageTracker tool = new CoverageTracker();
+        tool.install();
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+
+        // Not probed yet.
+        assertEquals(13, expr13rootNode.execute(null));
+        assertTrue(tool.getCounts().isEmpty());
+
+        final Node addNode = expr13rootNode.getChildren().iterator().next();
+        final Probe probe = addNode.probe();
+
+        // Probed but not tagged yet.
+        assertEquals(13, expr13rootNode.execute(null));
+        assertTrue(tool.getCounts().isEmpty());
+
+        probe.tagAs(StandardSyntaxTag.STATEMENT, "fake statement for testing");
+
+        // Counting now; execute once
+        assertEquals(13, expr13rootNode.execute(null));
+
+        final Long[] longs1 = tool.getCounts().get(addNode.getSourceSection().getSource());
+        assertNotNull(longs1);
+        assertEquals(longs1.length, 2);
+        assertNull(longs1[0]);  // Line 1 is empty (text lines are 1-based)
+        assertEquals(1L, longs1[1].longValue());  // Expression is on line 2
+
+        // Execute 99 more times
+        for (int i = 0; i < 99; i++) {
+            assertEquals(13, expr13rootNode.execute(null));
+        }
+
+        final Long[] longs100 = tool.getCounts().get(addNode.getSourceSection().getSource());
+        assertNotNull(longs100);
+        assertEquals(longs100.length, 2);
+        assertNull(longs100[0]);
+        assertEquals(100L, longs100[1].longValue());
+
+        tool.dispose();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/LineToProbesMapTest.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,86 @@
+/*
+ * 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.tools;
+
+import static com.oracle.truffle.api.test.tools.TestNodes.*;
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.api.tools.*;
+
+public class LineToProbesMapTest {
+
+    @Test
+    public void testToolCreatedTooLate() {
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+        final Node addNode = expr13rootNode.getChildren().iterator().next();
+        final Probe probe = addNode.probe();
+        final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation();
+        assertEquals(lineLocation, expr13Line2);
+
+        final LineToProbesMap tool = new LineToProbesMap();
+        tool.install();
+
+        assertNull(tool.findFirstProbe(expr13Line1));
+        assertNull(tool.findFirstProbe(expr13Line2));
+        tool.dispose();
+    }
+
+    @Test
+    public void testToolInstalledTooLate() {
+        final LineToProbesMap tool = new LineToProbesMap();
+
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+        final Node addNode = expr13rootNode.getChildren().iterator().next();
+        final Probe probe = addNode.probe();
+        final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation();
+        assertEquals(lineLocation, expr13Line2);
+
+        tool.install();
+
+        assertNull(tool.findFirstProbe(expr13Line1));
+        assertNull(tool.findFirstProbe(expr13Line2));
+        tool.dispose();
+    }
+
+    @Test
+    public void testMapping() {
+        final LineToProbesMap tool = new LineToProbesMap();
+        tool.install();
+
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+        final Node addNode = expr13rootNode.getChildren().iterator().next();
+        final Probe probe = addNode.probe();
+        final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation();
+        assertEquals(lineLocation, expr13Line2);
+
+        assertNull(tool.findFirstProbe(expr13Line1));
+        assertEquals(tool.findFirstProbe(expr13Line2), probe);
+        tool.dispose();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/NodeExecCounterTest.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,165 @@
+/*
+ * 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.tools;
+
+import static com.oracle.truffle.api.test.tools.TestNodes.*;
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.test.tools.TestNodes.TestAddNode;
+import com.oracle.truffle.api.test.tools.TestNodes.TestValueNode;
+import com.oracle.truffle.api.tools.*;
+import com.oracle.truffle.api.tools.NodeExecCounter.NodeExecutionCount;
+
+public class NodeExecCounterTest {
+
+    @Test
+    public void testNoExecution() {
+        final NodeExecCounter tool = new NodeExecCounter();
+        assertEquals(tool.getCounts().length, 0);
+        tool.install();
+        assertEquals(tool.getCounts().length, 0);
+        tool.setEnabled(false);
+        assertEquals(tool.getCounts().length, 0);
+        tool.setEnabled(true);
+        assertEquals(tool.getCounts().length, 0);
+        tool.reset();
+        assertEquals(tool.getCounts().length, 0);
+        tool.dispose();
+        assertEquals(tool.getCounts().length, 0);
+    }
+
+    @Test
+    public void testToolCreatedTooLate() {
+        final CallTarget expr13callTarget = createExpr13TestCallTarget();
+        final NodeExecCounter tool = new NodeExecCounter();
+        tool.install();
+        assertEquals(13, expr13callTarget.call());
+        assertEquals(tool.getCounts().length, 0);
+        tool.dispose();
+    }
+
+    @Test
+    public void testToolInstalledcTooLate() {
+        final NodeExecCounter tool = new NodeExecCounter();
+        final CallTarget expr13callTarget = createExpr13TestCallTarget();
+        tool.install();
+        assertEquals(13, expr13callTarget.call());
+        assertEquals(tool.getCounts().length, 0);
+        tool.dispose();
+    }
+
+    @Test
+    public void testCountingAll() {
+        final NodeExecCounter tool = new NodeExecCounter();
+        tool.install();
+        final CallTarget expr13callTarget = createExpr13TestCallTarget();
+
+        // execute once
+        assertEquals(13, expr13callTarget.call());
+        final NodeExecutionCount[] count1 = tool.getCounts();
+        assertNotNull(count1);
+        assertEquals(count1.length, 2);
+        for (NodeExecutionCount count : count1) {
+            final Class<?> class1 = count.nodeClass();
+            final long executionCount = count.executionCount();
+            if (class1 == TestAddNode.class) {
+                assertEquals(executionCount, 1);
+            } else if (class1 == TestValueNode.class) {
+                assertEquals(executionCount, 2);
+            } else {
+                fail();
+            }
+        }
+
+        // Execute 99 more times
+        for (int i = 0; i < 99; i++) {
+            assertEquals(13, expr13callTarget.call());
+        }
+        final NodeExecutionCount[] counts100 = tool.getCounts();
+        assertNotNull(counts100);
+        assertEquals(counts100.length, 2);
+        for (NodeExecutionCount count : counts100) {
+            final Class<?> class1 = count.nodeClass();
+            final long executionCount = count.executionCount();
+            if (class1 == TestAddNode.class) {
+                assertEquals(executionCount, 100);
+            } else if (class1 == TestValueNode.class) {
+                assertEquals(executionCount, 200);
+            } else {
+                fail();
+            }
+        }
+
+        tool.dispose();
+    }
+
+    @Test
+    public void testCountingTagged() {
+        final NodeExecCounter tool = new NodeExecCounter(StandardSyntaxTag.STATEMENT);
+        tool.install();
+        final RootNode expr13rootNode = createExpr13TestRootNode();
+
+        // Not probed yet.
+        assertEquals(13, expr13rootNode.execute(null));
+        assertEquals(tool.getCounts().length, 0);
+
+        final Node addNode = expr13rootNode.getChildren().iterator().next();
+        final Probe probe = addNode.probe();
+
+        // Probed but not tagged yet.
+        assertEquals(13, expr13rootNode.execute(null));
+        assertEquals(tool.getCounts().length, 0);
+
+        probe.tagAs(StandardSyntaxTag.STATEMENT, "fake statement for testing");
+
+        // Counting now; execute once
+        assertEquals(13, expr13rootNode.execute(null));
+        final NodeExecutionCount[] counts1 = tool.getCounts();
+        assertNotNull(counts1);
+        assertEquals(counts1.length, 1);
+        final NodeExecutionCount count1 = counts1[0];
+        assertNotNull(count1);
+        assertEquals(count1.nodeClass(), addNode.getClass());
+        assertEquals(count1.executionCount(), 1);
+
+        // Execute 99 more times
+        for (int i = 0; i < 99; i++) {
+            assertEquals(13, expr13rootNode.execute(null));
+        }
+
+        final NodeExecutionCount[] counts100 = tool.getCounts();
+        assertNotNull(counts100);
+        assertEquals(counts100.length, 1);
+        final NodeExecutionCount count100 = counts100[0];
+        assertNotNull(count100);
+        assertEquals(count100.nodeClass(), addNode.getClass());
+        assertEquals(count100.executionCount(), 100);
+
+        tool.dispose();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TestNodes.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,221 @@
+/*
+ * 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.tools;
+
+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.*;
+import com.oracle.truffle.api.source.*;
+
+/**
+ * Nodes and an {@linkplain CallTarget executable ASTs} for testing.
+ */
+public class TestNodes {
+
+    /**
+     * A fake source used for testing: empty line 1, expression on line 2.
+     */
+    public static final Source expr13Source = Source.fromText("\n6+7\n", "Test Source: expression on line 2 that evaluates to 13");
+    public static final LineLocation expr13Line1 = expr13Source.createLineLocation(1);
+    public static final LineLocation expr13Line2 = expr13Source.createLineLocation(2);
+
+    /**
+     * An executable addition expression that evaluates to 13.
+     */
+    public static CallTarget createExpr13TestCallTarget() {
+        final RootNode rootNode = createExpr13TestRootNode();
+        return Truffle.getRuntime().createCallTarget(rootNode);
+    }
+
+    /**
+     * Root holding an addition expression that evaluates to 13.
+     */
+    public static RootNode createExpr13TestRootNode() {
+        final TestLanguageNode ast = createExpr13AST();
+        final TestRootNode rootNode = new TestRootNode(ast);
+        rootNode.adoptChildren();
+        return rootNode;
+    }
+
+    /**
+     * Addition expression that evaluates to 13, with faked source attribution.
+     */
+    public 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);
+        final TestValueNode rightValueNode = new TestValueNode(7, rightSourceSection);
+        final SourceSection exprSourceSection = expr13Source.createSection("expr", 1, 3);
+        return new TestAddNode(leftValueNode, rightValueNode, exprSourceSection);
+    }
+
+    public abstract static class TestLanguageNode extends Node {
+        public abstract Object execute(VirtualFrame frame);
+
+        public TestLanguageNode() {
+        }
+
+        public TestLanguageNode(SourceSection srcSection) {
+            super(srcSection);
+        }
+
+        @Override
+        public boolean isInstrumentable() {
+            return true;
+        }
+
+        @Override
+        public WrapperNode createWrapperNode() {
+            return new TestWrapperNode(this);
+        }
+    }
+
+    @NodeInfo(cost = NodeCost.NONE)
+    public static class TestWrapperNode extends TestLanguageNode implements WrapperNode {
+        @Child private TestLanguageNode child;
+        @Child private ProbeNode probeNode;
+
+        public TestWrapperNode(TestLanguageNode child) {
+            assert !(child instanceof TestWrapperNode);
+            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() {
+            try {
+                return probeNode.getProbe();
+            } catch (IllegalStateException e) {
+                throw new IllegalStateException("Cannot call getProbe() on a wrapper that has no probe");
+            }
+        }
+
+        @Override
+        public Node getChild() {
+            return child;
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            probeNode.enter(child, frame);
+            Object result;
+
+            try {
+                result = child.execute(frame);
+                probeNode.returnValue(child, frame, result);
+            } catch (KillException e) {
+                throw (e);
+            } catch (Exception e) {
+                probeNode.returnExceptional(child, frame, e);
+                throw (e);
+            }
+
+            return result;
+        }
+    }
+
+    /**
+     * 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.
+     */
+    public 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 frame) {
+            return body.execute(frame);
+        }
+
+        @Override
+        public boolean isCloningAllowed() {
+            return true;
+        }
+
+        @Override
+        public void applyInstrumentation() {
+            Probe.applyASTProbers(body);
+        }
+    }
+
+    public static class TestValueNode extends TestLanguageNode {
+        private final int value;
+
+        public TestValueNode(int value) {
+            this.value = value;
+        }
+
+        public TestValueNode(int value, SourceSection srcSection) {
+            super(srcSection);
+            this.value = value;
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            return new Integer(this.value);
+        }
+    }
+
+    public static class TestAddNode extends TestLanguageNode {
+        @Child private TestLanguageNode leftChild;
+        @Child private TestLanguageNode rightChild;
+
+        public TestAddNode(TestValueNode leftChild, TestValueNode rightChild, SourceSection sourceSection) {
+            super(sourceSection);
+            this.leftChild = insert(leftChild);
+            this.rightChild = insert(rightChild);
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            return new Integer(((Integer) leftChild.execute(frame)).intValue() + ((Integer) rightChild.execute(frame)).intValue());
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TruffleToolTest.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,129 @@
+/*
+ * 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.tools;
+
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.instrument.*;
+
+/**
+ * Test the basic life cycle properties shared by all instances of {@link TruffleTool}.
+ */
+public class TruffleToolTest {
+
+    @Test
+    public void testEmptyLifeCycle() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        assertFalse(tool.isEnabled());
+        tool.install();
+        assertTrue(tool.isEnabled());
+        tool.reset();
+        assertTrue(tool.isEnabled());
+        tool.setEnabled(false);
+        assertFalse(tool.isEnabled());
+        tool.reset();
+        assertFalse(tool.isEnabled());
+        tool.setEnabled(true);
+        assertTrue(tool.isEnabled());
+        tool.reset();
+        assertTrue(tool.isEnabled());
+        tool.dispose();
+        assertFalse(tool.isEnabled());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNotYetInstalled1() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.reset();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNotYetInstalled2() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.setEnabled(true);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNotYetInstalled3() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.dispose();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAlreadyInstalled() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.install();
+        tool.install();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAlreadyDisposed1() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.install();
+        tool.dispose();
+        tool.install();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAlreadyDisposed2() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.install();
+        tool.dispose();
+        tool.reset();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAlreadyDisposed3() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.install();
+        tool.dispose();
+        tool.setEnabled(true);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAlreadyDisposed4() {
+        final DummyTruffleTool tool = new DummyTruffleTool();
+        tool.install();
+        tool.dispose();
+        tool.dispose();
+    }
+
+    private static final class DummyTruffleTool extends TruffleTool {
+
+        @Override
+        protected boolean internalInstall() {
+            return true;
+        }
+
+        @Override
+        protected void internalReset() {
+        }
+
+        @Override
+        protected void internalDispose() {
+        }
+
+    }
+}
--- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/utilities/SourceTextTest.java	Wed Jan 28 11:27:35 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,202 +0,0 @@
-/*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package com.oracle.truffle.api.test.utilities;
-
-import static org.junit.Assert.*;
-
-import org.junit.*;
-
-import com.oracle.truffle.api.source.*;
-
-public class SourceTextTest {
-
-    private final Source emptySource = Source.fromText("", null);
-
-    private final Source emptyLineSource = Source.fromText("\n", null);
-
-    private final Source shortSource = Source.fromText("01", null);
-
-    private final Source longSource = Source.fromText("01234\n67\n9\n", null);
-
-    @Test
-    public void emptyTextTest0() {
-        assertEquals(emptySource.getLineCount(), 0);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyTextTest1() {
-        emptySource.getLineNumber(0);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyTextTest2() {
-        emptySource.getColumnNumber(0);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyTextTest3() {
-        emptySource.getLineNumber(-1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyTextTest4() {
-        emptySource.getLineStartOffset(0);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyTextTest5() {
-        emptySource.getLineStartOffset(1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyTextTest6() {
-        emptySource.getLineLength(1);
-    }
-
-    @Test
-    public void emptyLineTest0() {
-        assertEquals(emptyLineSource.getLineCount(), 1);
-        assertEquals(emptyLineSource.getLineNumber(0), 1);
-        assertEquals(emptyLineSource.getLineStartOffset(1), 0);
-        assertEquals(emptyLineSource.getColumnNumber(0), 1);
-        assertEquals(emptyLineSource.getLineLength(1), 0);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyLineTest1() {
-        emptyLineSource.getLineNumber(1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyLineTest2() {
-        emptyLineSource.getLineStartOffset(2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyLineTest3() {
-        emptyLineSource.getColumnNumber(1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyLineTest4() {
-        emptyLineSource.getLineLength(2);
-    }
-
-    @Test
-    public void shortTextTest0() {
-
-        assertEquals(shortSource.getLineCount(), 1);
-
-        assertEquals(shortSource.getLineNumber(0), 1);
-        assertEquals(shortSource.getLineStartOffset(1), 0);
-        assertEquals(shortSource.getColumnNumber(0), 1);
-
-        assertEquals(shortSource.getLineNumber(1), 1);
-        assertEquals(shortSource.getColumnNumber(1), 2);
-
-        assertEquals(shortSource.getLineLength(1), 2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void shortTextTest1() {
-        shortSource.getLineNumber(-1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void shortTextTest2() {
-        shortSource.getColumnNumber(-1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void shortTextTest3() {
-        shortSource.getLineNumber(2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void shortTextTest4() {
-        shortSource.getColumnNumber(2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void shortTextTest5() {
-        shortSource.getLineLength(2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void shortTextTest6() {
-        shortSource.getLineLength(2);
-    }
-
-    @Test
-    public void longTextTest0() {
-
-        assertEquals(longSource.getLineCount(), 3);
-
-        assertEquals(longSource.getLineNumber(0), 1);
-        assertEquals(longSource.getLineStartOffset(1), 0);
-        assertEquals(longSource.getColumnNumber(0), 1);
-
-        assertEquals(longSource.getLineNumber(4), 1);
-        assertEquals(longSource.getColumnNumber(4), 5);
-
-        assertEquals(longSource.getLineNumber(5), 1); // newline
-        assertEquals(longSource.getColumnNumber(5), 6); // newline
-        assertEquals(longSource.getLineLength(1), 5);
-
-        assertEquals(longSource.getLineNumber(6), 2);
-        assertEquals(longSource.getLineStartOffset(2), 6);
-        assertEquals(longSource.getColumnNumber(6), 1);
-
-        assertEquals(longSource.getLineNumber(7), 2);
-        assertEquals(longSource.getColumnNumber(7), 2);
-
-        assertEquals(longSource.getLineNumber(8), 2); // newline
-        assertEquals(longSource.getLineNumber(8), 2); // newline
-        assertEquals(longSource.getLineLength(2), 2);
-
-        assertEquals(longSource.getLineNumber(9), 3);
-        assertEquals(longSource.getLineStartOffset(3), 9);
-        assertEquals(longSource.getColumnNumber(9), 1);
-
-        assertEquals(longSource.getLineNumber(10), 3); // newline
-        assertEquals(longSource.getColumnNumber(10), 2); // newline
-        assertEquals(longSource.getLineLength(3), 1);
-
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void longTextTest1() {
-        longSource.getLineNumber(11);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void longTextTest2() {
-        longSource.getColumnNumber(11);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void longTextTest3() {
-        longSource.getLineStartOffset(4);
-    }
-
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ASTProber.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ASTProber.java	Wed Jan 28 11:28:03 2015 +0100
@@ -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	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.java	Wed Jan 28 11:28:03 2015 +0100
@@ -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	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Probe.java	Wed Jan 28 11:28:03 2015 +0100
@@ -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	Wed Jan 28 11:28:03 2015 +0100
@@ -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	Wed Jan 28 11:28:03 2015 +0100
@@ -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	Wed Jan 28 11:28:03 2015 +0100
@@ -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	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/ProbeNode.java	Wed Jan 28 11:28:03 2015 +0100
@@ -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/TruffleTool.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,169 @@
+/*
+ * 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;
+
+/**
+ * {@linkplain Instrument Instrumentation}-based tools that gather data during Guest Language
+ * program execution.
+ * <p>
+ * Tools share a common <em>life cycle</em>:
+ * <ul>
+ * <li>A newly created tool is inert until {@linkplain #install() installed}.</li>
+ * <li>An installed tool becomes <em>enabled</em> and immediately begins installing
+ * {@linkplain Instrument instrumentation} on subsequently created ASTs and collecting data from
+ * those instruments</li>
+ * <li>A tool may only be installed once.</li>
+ * <li>It should be possible to install multiple instances of a tool, possibly (but not necessarily)
+ * configured differently with respect to what data is being collected.</li>
+ * <li>Once installed, a tool can be {@linkplain #setEnabled(boolean) enabled and disabled}
+ * arbitrarily.</li>
+ * <li>A disabled tool:
+ * <ul>
+ * <li>Collects no data;</li>
+ * <li>Retains existing AST instrumentation;</li>
+ * <li>Continues to instrument newly created ASTs; and</li>
+ * <li>Retains previously collected data.</li>
+ * </ul>
+ * </li>
+ * <li>An installed tool may be {@linkplain #reset() reset} at any time, which leaves the tool
+ * installed but with all previously collected data removed.</li>
+ * <li>A {@linkplain #dispose() disposed} tool removes all instrumentation (but not
+ * {@linkplain Probe probes}) and becomes permanently disabled; previously collected data persists.</li>
+ * </ul>
+ * <p>
+ * Tool-specific methods that access data collected by the tool should:
+ * <ul>
+ * <li>Return modification-safe representations of the data; and</li>
+ * <li>Not change the state of the data.</li>
+ * </ul>
+ * <b>Note:</b><br>
+ * Tool installation is currently <em>global</em> to the Truffle Execution environment. When
+ * language-agnostic management of individual execution environments is added to the platform,
+ * installation will be (optionally) specific to a single execution environment.
+ */
+public abstract class TruffleTool {
+
+    private enum ToolState {
+        UNINSTALLED,
+        ENABLED_INSTALLED,
+        DISABLED_INSTALLED,
+        DISPOSED;
+    }
+
+    private ToolState toolState = ToolState.UNINSTALLED;
+
+    protected TruffleTool() {
+
+    }
+
+    /**
+     * Connect the tool to some part of the Truffle runtime, and enable data collection to start.
+     * Instrumentation will only be added to subsequently created ASTs.
+     *
+     * @throws IllegalStateException if the tool has previously been installed.
+     */
+    public final void install() {
+        if (toolState != ToolState.UNINSTALLED) {
+            throw new IllegalStateException("Tool " + getClass().getSimpleName() + " has already been installed");
+        }
+        if (internalInstall()) {
+            toolState = ToolState.ENABLED_INSTALLED;
+        }
+    }
+
+    /**
+     * @return whether the tool is currently collecting data.
+     */
+    public final boolean isEnabled() {
+        return toolState == ToolState.ENABLED_INSTALLED;
+    }
+
+    /**
+     * Switches tool state between <em>enabled</em> (collecting data) and <em>disabled</em> (not
+     * collecting data, but keeping data already collected).
+     *
+     * @throws IllegalStateException if not yet installed or disposed.
+     */
+    public final void setEnabled(boolean isEnabled) {
+        if (toolState == ToolState.UNINSTALLED) {
+            throw new IllegalStateException("Tool " + getClass().getSimpleName() + " not yet installed");
+        }
+        if (toolState == ToolState.DISPOSED) {
+            throw new IllegalStateException("Tool " + getClass().getSimpleName() + " has been disposed");
+        }
+        internalSetEnabled(isEnabled);
+        toolState = isEnabled ? ToolState.ENABLED_INSTALLED : ToolState.DISABLED_INSTALLED;
+    }
+
+    /**
+     * Clears any data already collected, but otherwise does not change the state of the tool.
+     *
+     * @throws IllegalStateException if not yet installed or disposed.
+     */
+    public final void reset() {
+        if (toolState == ToolState.UNINSTALLED) {
+            throw new IllegalStateException("Tool " + getClass().getSimpleName() + " not yet installed");
+        }
+        if (toolState == ToolState.DISPOSED) {
+            throw new IllegalStateException("Tool " + getClass().getSimpleName() + " has been disposed");
+        }
+        internalReset();
+    }
+
+    /**
+     * Makes the tool permanently <em>disabled</em>, removes instrumentation, but keeps data already
+     * collected.
+     *
+     * @throws IllegalStateException if not yet installed or disposed.
+     */
+    public final void dispose() {
+        if (toolState == ToolState.UNINSTALLED) {
+            throw new IllegalStateException("Tool " + getClass().getSimpleName() + " not yet installed");
+        }
+        if (toolState == ToolState.DISPOSED) {
+            throw new IllegalStateException("Tool " + getClass().getSimpleName() + " has been disposed");
+        }
+        internalDispose();
+        toolState = ToolState.DISPOSED;
+    }
+
+    /**
+     * @return whether the installation succeeded.
+     */
+    protected abstract boolean internalInstall();
+
+    /**
+     * No subclass action required.
+     * 
+     * @param isEnabled
+     */
+    protected void internalSetEnabled(boolean isEnabled) {
+    }
+
+    protected abstract void internalReset();
+
+    protected abstract void internalDispose();
+
+}
--- /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	Wed Jan 28 11:28:03 2015 +0100
@@ -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/instrument/impl/LineToProbesMap.java	Wed Jan 28 11:27:35 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,176 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.io.*;
-import java.util.*;
-
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.instrument.Probe.ProbeListener;
-import com.oracle.truffle.api.source.*;
-
-/**
- * A mapping from {@link LineLocation} (a line number in a specific piece of {@link Source} code) to
- * a collection of {@link Probe}s whose associated {@link SourceSection} starts on that line.
- */
-public class LineToProbesMap implements ProbeListener {
-
-    private static final boolean TRACE = false;
-    private static final PrintStream OUT = System.out;
-
-    private static void trace(String msg) {
-        OUT.println("LineToProbesMap: " + msg);
-    }
-
-    /**
-     * Map: Source line ==> probes associated with source sections starting on the line.
-     */
-    private final Map<LineLocation, Collection<Probe>> lineToProbesMap = new HashMap<>();
-
-    public LineToProbesMap() {
-    }
-
-    public void startASTProbing(Source source) {
-    }
-
-    public void newProbeInserted(Probe probe) {
-        final SourceSection sourceSection = probe.getProbedSourceSection();
-        if (sourceSection != null && !(sourceSection instanceof NullSourceSection)) {
-            final LineLocation lineLocation = sourceSection.getLineLocation();
-            if (TRACE) {
-                trace("ADD " + lineLocation.getShortDescription() + " ==> " + probe.getShortDescription());
-            }
-            this.addProbeToLine(lineLocation, probe);
-        }
-    }
-
-    public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
-        // This map ignores tags
-    }
-
-    public void endASTProbing(Source source) {
-    }
-
-    /**
-     * Returns the {@link Probe}, if any, associated with source that starts on a specified line; if
-     * there are more than one, return the one with the first starting character location.
-     */
-    public Probe findLineProbe(LineLocation lineLocation) {
-        Probe probe = null;
-        final Collection<Probe> probes = getProbesAtLine(lineLocation);
-        for (Probe probesOnLine : probes) {
-            if (probe == null) {
-                probe = probesOnLine;
-            } else if (probesOnLine.getProbedSourceSection().getCharIndex() < probe.getProbedSourceSection().getCharIndex()) {
-                probe = probesOnLine;
-            }
-        }
-        return probe;
-    }
-
-    /**
-     * Records creation of a probe whose associated source starts on the given line.
-     * <p>
-     * If the line already exists in the internal {@link #lineToProbesMap}, this probe will be added
-     * to the existing collection. If no line already exists in the internal map, then a new key is
-     * added along with a new collection containing the probe.
-     * <p>
-     * This class requires that each added line/probe pair hasn't been previously added. However,
-     * attaching the same probe to a new line location is allowed.
-     *
-     * @param line The {@link LineLocation} to attach the probe to.
-     * @param probe The {@link Probe} to attach for that line location.
-     */
-    protected void addProbeToLine(LineLocation line, Probe probe) {
-
-        if (!lineToProbesMap.containsKey(line)) {
-            // Key does not exist, add new probe list
-            final ArrayList<Probe> newProbeList = new ArrayList<>(2);
-            newProbeList.add(probe);
-            lineToProbesMap.put(line, newProbeList);
-        } else {
-            // Probe list exists, add to existing
-            final Collection<Probe> existingProbeList = lineToProbesMap.get(line);
-            assert !existingProbeList.contains(probe);
-            existingProbeList.add(probe);
-        }
-    }
-
-    /**
-     *
-     * Returns a collection of {@link Probe}s whose associated source begins at the given
-     * {@link LineLocation}, an empty list if none.
-     *
-     * @param line The line to check.
-     * @return A collection of probes at the given line.
-     */
-    public Collection<Probe> getProbesAtLine(LineLocation line) {
-        Collection<Probe> probesList = lineToProbesMap.get(line);
-
-        if (probesList == null) {
-            return Collections.emptyList();
-        }
-        return probesList;
-    }
-
-    /**
-     * Convenience method to get probes according to a int line number. Returns a collection of
-     * {@link Probe}s at the given line number, an empty list if none.
-     *
-     * @param lineNumber The line number to check.
-     * @return A collection of probes at the given line.
-     */
-    public Collection<Probe> getProbesAtLineNumber(int lineNumber) {
-
-        final Set<LineLocation> keySet = lineToProbesMap.keySet();
-        if (keySet.size() == 0) {
-            return Collections.emptyList();
-        }
-
-        ArrayList<Probe> probes = new ArrayList<>();
-        for (LineLocation line : keySet) {
-            if (line.getLineNumber() == lineNumber) {
-                probes.addAll(lineToProbesMap.get(line));
-            }
-        }
-
-        return probes;
-    }
-
-    public void forget(Source source) {
-        final Set<LineLocation> mappedLines = lineToProbesMap.keySet();
-        if (mappedLines.size() > 0) {
-            List<LineLocation> forgetLines = new ArrayList<>();
-            for (LineLocation line : mappedLines) {
-                if (line.getSource().equals(source)) {
-                    forgetLines.add(line);
-                }
-            }
-            for (LineLocation line : forgetLines) {
-                lineToProbesMap.remove(line);
-            }
-        }
-    }
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/LineToSourceSectionMap.java	Wed Jan 28 11:27:35 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.io.*;
-import java.util.*;
-
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.instrument.Probe.*;
-import com.oracle.truffle.api.source.*;
-
-/**
- * A mapping from {@link LineLocation} (a line number in a specific piece of {@link Source} code) to
- * a collection of {@link SourceSection}s that exist on that line. This class assumes that all nodes
- * are instrumented as it uses the {@link ProbeListener} interface to determine the source sections
- * that exist in the file.
- */
-public class LineToSourceSectionMap implements ProbeListener {
-
-    private static final boolean TRACE = false;
-    private static final PrintStream OUT = System.out;
-
-    private static void trace(String msg) {
-        OUT.println("LineToSourceSectionMap: " + msg);
-    }
-
-    /**
-     * Map: Source line ==> source sections that exist on the line.
-     */
-    private final Map<LineLocation, Collection<SourceSection>> lineToSourceSectionsMap = new HashMap<>();
-
-    public LineToSourceSectionMap() {
-    }
-
-    public void startASTProbing(Source source) {
-    }
-
-    public void newProbeInserted(Probe probe) {
-        final SourceSection sourceSection = probe.getProbedSourceSection();
-        if (sourceSection != null && !(sourceSection instanceof NullSourceSection)) {
-            final LineLocation lineLocation = sourceSection.getLineLocation();
-            if (TRACE) {
-                trace("NEW " + lineLocation.getShortDescription() + " Probe=" + probe);
-            }
-            this.addSourceSectionToLine(lineLocation, sourceSection);
-        }
-    }
-
-    public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
-        // This map ignores tags, but this subclasses can override this method to operate on tags.
-    }
-
-    public void endASTProbing(Source source) {
-    }
-
-    /**
-     * Adds a source section to the given line.
-     * <p>
-     * If the line already exists in the internal {@link #lineToSourceSectionsMap}, this source
-     * section will be added to the existing collection. If no line already exists in the internal
-     * map, then a new key is added along with a new collection containing the source section.
-     * <p>
-     * This class does not check if a source section has already been added to a line.
-     *
-     * @param line The {@link LineLocation} to attach the source section to.
-     * @param sourceSection The {@link SourceSection} to attach for that line location.
-     */
-    protected void addSourceSectionToLine(LineLocation line, SourceSection sourceSection) {
-        if (!lineToSourceSectionsMap.containsKey(line)) {
-            // Key does not exist, add new source section list
-            final ArrayList<SourceSection> newSourceSectionList = new ArrayList<>(2);
-            newSourceSectionList.add(sourceSection);
-            lineToSourceSectionsMap.put(line, newSourceSectionList);
-        } else {
-            // Source section list exists, add to existing
-            final Collection<SourceSection> existingSourceSectionList = lineToSourceSectionsMap.get(line);
-            existingSourceSectionList.add(sourceSection);
-        }
-    }
-
-    /**
-     * Returns a collection of {@link SourceSection}s at the given {@link LineLocation}, an empty
-     * list if none.
-     *
-     * @param line The line to check.
-     * @return the source sections at the given line.
-     */
-    public Collection<SourceSection> getSourceSectionsAtLine(LineLocation line) {
-        Collection<SourceSection> sourceSectionList = lineToSourceSectionsMap.get(line);
-
-        if (sourceSectionList == null) {
-            return Collections.emptyList();
-        }
-        return sourceSectionList;
-    }
-
-    /**
-     * Convenience method to get source sections according to a int line number. Returns a
-     * collection of {@link SourceSection}s at the given line number. If there are no source
-     * sections at that line, an empty list is returned.
-     *
-     * @param lineNumber The line number to check.
-     * @return A collection of source sections at the given line.
-     */
-    public Collection<SourceSection> getSourceSectionsAtLineNumber(int lineNumber) {
-
-        final Set<LineLocation> keySet = lineToSourceSectionsMap.keySet();
-        if (keySet.size() == 0) {
-            return Collections.emptyList();
-        }
-
-        final ArrayList<SourceSection> sourceSections = new ArrayList<>();
-        for (LineLocation line : keySet) {
-            if (line.getLineNumber() == lineNumber) {
-                sourceSections.addAll(lineToSourceSectionsMap.get(line));
-            }
-        }
-
-        return sourceSections;
-    }
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java	Wed Jan 28 11:28:03 2015 +0100
@@ -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
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/NodeUtil.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/NodeUtil.java	Wed Jan 28 11:28:03 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2014, 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
@@ -362,6 +362,15 @@
             return childrenFields;
         }
 
+        public NodeField findField(long fieldOffset) {
+            for (NodeField field : getFields()) {
+                if (field.getOffset() == fieldOffset) {
+                    return field;
+                }
+            }
+            return null;
+        }
+
         @Override
         public int hashCode() {
             return clazz.hashCode();
@@ -646,6 +655,60 @@
         throw new IllegalArgumentException();
     }
 
+    /**
+     * Finds the field in a parent node, if any, that holds a specified child node.
+     *
+     * @return the field (possibly an array) holding the child, {@code null} if not found.
+     */
+    public static NodeField findChildField(Node parent, Node child) {
+        assert child != null;
+        NodeClass parentNodeClass = NodeClass.get(parent.getClass());
+
+        for (NodeField field : parentNodeClass.getChildFields()) {
+            final long fieldOffset = field.getOffset();
+            if (unsafe.getObject(parent, fieldOffset) == child) {
+                return parentNodeClass.findField(fieldOffset);
+            }
+        }
+
+        for (NodeField field : parentNodeClass.getChildrenFields()) {
+            final long fieldOffset = field.getOffset();
+            Object arrayObject = unsafe.getObject(parent, fieldOffset);
+            if (arrayObject != null) {
+                Object[] array = (Object[]) arrayObject;
+                for (int i = 0; i < array.length; i++) {
+                    if (array[i] == child) {
+                        return parentNodeClass.findField(fieldOffset);
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Determines whether a proposed child replacement would be type safe.
+     *
+     * @param parent non-null node
+     * @param oldChild non-null existing child of parent
+     * @param newChild non-null proposed replacement for existing child
+     */
+    public static boolean isReplacementSafe(Node parent, Node oldChild, Node newChild) {
+        assert newChild != null;
+        final NodeField field = findChildField(parent, oldChild);
+        if (field == null) {
+            throw new IllegalArgumentException();
+        }
+        switch (field.getKind()) {
+            case CHILD:
+                return field.getType().isAssignableFrom(newChild.getClass());
+            case CHILDREN:
+                return field.getType().getComponentType().isAssignableFrom(newChild.getClass());
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
+
     /** Returns all declared fields in the class hierarchy. */
     private static Field[] getAllFields(Class<? extends Object> clazz) {
         Field[] declaredFields = clazz.getDeclaredFields();
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/LineLocation.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/LineLocation.java	Wed Jan 28 11:28:03 2015 +0100
@@ -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
@@ -24,6 +24,8 @@
  */
 package com.oracle.truffle.api.source;
 
+import java.util.*;
+
 /**
  * A specification for a location in guest language source, expressed as a line number in a specific
  * instance of {@link Source}, suitable for hash table keys with equality defined in terms of
@@ -40,4 +42,19 @@
 
     String getShortDescription();
 
+    /**
+     * Default comparator by (1) textual path name, (2) line number.
+     */
+    Comparator<LineLocation> COMPARATOR = new Comparator<LineLocation>() {
+
+        public int compare(LineLocation l1, LineLocation l2) {
+            final int sourceResult = l1.getSource().getPath().compareTo(l2.getSource().getPath());
+            if (sourceResult != 0) {
+                return sourceResult;
+            }
+            return Integer.compare(l1.getLineNumber(), l2.getLineNumber());
+        }
+
+    };
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/CoverageTracker.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,317 @@
+/*
+ * 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.tools;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.atomic.*;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.nodes.Node.Child;
+import com.oracle.truffle.api.source.*;
+
+/**
+ * A {@link TruffleTool} that counts interpreter <em>execution calls</em> to AST nodes that hold a
+ * specified {@linkplain SyntaxTag tag}, tabulated by source and line number associated with each
+ * node. Tags are presumed to be applied external to the tool. If no tag is specified,
+ * {@linkplain StandardSyntaxTag#STATEMENT STATEMENT} is used, corresponding to conventional
+ * behavior for code coverage tools.
+ * <p>
+ * <b>Tool Life Cycle</b>
+ * <p>
+ * See {@link TruffleTool} for the life cycle common to all such tools.
+ * <p>
+ * <b>Execution Counts</b>
+ * <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 TruffleEventReceiver#enter(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;
+ * dynamically added nodes will not be instrumented.</li>
+ * </ul>
+ * </p>
+ * <b>Results</b>
+ * <p>
+ * A modification-safe copy of the {@linkplain #getCounts() counts} can be retrieved at any time,
+ * without effect on the state of the tool.
+ * </p>
+ * <p>
+ * A "default" {@linkplain #print(PrintStream) print()} method can summarizes the current counts at
+ * any time in a simple textual format, with no other effect on the state of the tool.
+ * </p>
+ *
+ * @see Instrument
+ * @see SyntaxTag
+ */
+public final class CoverageTracker extends TruffleTool {
+
+    /** Counting data. */
+    private final Map<LineLocation, CoverageCounter> counters = new HashMap<>();
+
+    /** For disposal. */
+    private final List<Instrument> instruments = new ArrayList<>();
+
+    /**
+     * Counting is restricted to nodes holding this tag.
+     */
+    private final SyntaxTag countingTag;
+
+    private final ProbeListener probeListener;
+
+    /**
+     * Create a per-line coverage tool for nodes tagged as {@linkplain StandardSyntaxTag#STATEMENT
+     * statements} in subsequently created ASTs.
+     */
+    public CoverageTracker() {
+        this(StandardSyntaxTag.STATEMENT);
+    }
+
+    /**
+     * Create a per-line coverage tool for nodes tagged as specified, presuming that tags applied
+     * outside this tool.
+     */
+    public CoverageTracker(SyntaxTag tag) {
+        this.probeListener = new CoverageProbeListener();
+        this.countingTag = tag;
+    }
+
+    @Override
+    protected boolean internalInstall() {
+        Probe.addProbeListener(probeListener);
+        return true;
+    }
+
+    @Override
+    protected void internalReset() {
+        counters.clear();
+    }
+
+    @Override
+    protected void internalDispose() {
+        Probe.removeProbeListener(probeListener);
+        for (Instrument instrument : instruments) {
+            instrument.dispose();
+        }
+    }
+
+    /**
+     * Gets a modification-safe summary of the current per-type node execution counts; does not
+     * affect the counts.
+     * <p>
+     * The map holds an array for each source, and elements of the a array corresponding to the
+     * textual lines of that source. An array entry contains null if the corresponding line of
+     * source is associated with no nodes of the relevant type, i.e. where no count was made. A
+     * numeric entry represents the execution count at the corresponding line of source.
+     * <p>
+     * <b>Note:</b> source line numbers are 1-based, so array index {@code i} corresponds to source
+     * line number {@code i + 1}
+     */
+    public Map<Source, Long[]> getCounts() {
+
+        /**
+         * Counters for every {Source, line number} for which a counter was installed, i.e. for
+         * every line associated with an appropriately tagged AST node; iterable in order of source
+         * name, then line number.
+         */
+        final TreeSet<Entry<LineLocation, CoverageCounter>> entries = new TreeSet<>(new LineLocationEntryComparator());
+
+        final Map<Source, Long[]> results = new HashMap<>();
+
+        for (Entry<LineLocation, CoverageCounter> entry : counters.entrySet()) {
+            entries.add(entry);
+        }
+        Source curSource = null;
+        Long[] curLineTable = null;
+        for (Entry<LineLocation, CoverageCounter> entry : entries) {
+            final LineLocation key = entry.getKey();
+            final Source source = key.getSource();
+            final int lineNo = key.getLineNumber();
+            if (source != curSource) {
+                if (curSource != null) {
+                    results.put(curSource, curLineTable);
+                }
+                curSource = source;
+                curLineTable = new Long[source.getLineCount()];
+            }
+            curLineTable[lineNo - 1] = entry.getValue().count.longValue();
+        }
+        if (curSource != null) {
+            results.put(curSource, curLineTable);
+        }
+        return results;
+    }
+
+    /**
+     * A default printer for the current line counts, producing lines of the form " (<count>) <line
+     * number> : <text of line>", grouped by source.
+     */
+    public void print(PrintStream out) {
+        out.println();
+        out.println(countingTag.name() + " coverage:");
+
+        /**
+         * Counters for every {Source, line number} for which a counter was installed, i.e. for
+         * every line associated with an appropriately tagged AST node; iterable in order of source
+         * name, then line number.
+         */
+        final TreeSet<Entry<LineLocation, CoverageCounter>> entries = new TreeSet<>(new LineLocationEntryComparator());
+
+        for (Entry<LineLocation, CoverageCounter> entry : counters.entrySet()) {
+            entries.add(entry);
+        }
+        Source curSource = null;
+        int curLineNo = 1;
+        for (Entry<LineLocation, CoverageCounter> entry : entries) {
+            final LineLocation key = entry.getKey();
+            final Source source = key.getSource();
+            final int lineNo = key.getLineNumber();
+            if (source != curSource) {
+                if (curSource != null) {
+                    while (curLineNo <= curSource.getLineCount()) {
+                        displayLine(out, null, curSource, curLineNo++);
+                    }
+                }
+                curSource = source;
+                curLineNo = 1;
+                out.println();
+                out.println(source.getPath());
+            }
+            while (curLineNo < lineNo) {
+                displayLine(out, null, curSource, curLineNo++);
+            }
+            displayLine(out, entry.getValue().count, curSource, curLineNo++);
+        }
+        if (curSource != null) {
+            while (curLineNo <= curSource.getLineCount()) {
+                displayLine(out, null, curSource, curLineNo++);
+            }
+        }
+    }
+
+    private static void displayLine(PrintStream out, AtomicLong value, Source source, int lineNo) {
+        if (value == null) {
+            out.format("%14s", " ");
+        } else {
+            out.format("(%12d)", value.longValue());
+        }
+        out.format(" %3d: ", lineNo);
+        out.println(source.getCode(lineNo));
+    }
+
+    /**
+     * A receiver for events at each instrumented AST location. This receiver counts
+     * "execution calls" to the instrumented node and is <em>stateful</em>. State in receivers must
+     * be considered carefully since ASTs, along with all instrumentation (including event receivers
+     * such as this) are routinely cloned by the Truffle runtime. AST cloning is <em>shallow</em>
+     * (for non- {@link Child} nodes), so in this case the actual count <em>is shared</em> among all
+     * the clones; the count is also held in a table indexed by source line.
+     * <p>
+     * In contrast, a primitive field would <em>not</em> be shared among clones and resulting counts
+     * would not be accurate.
+     */
+    private final class CoverageEventReceiver extends DefaultEventReceiver {
+
+        /**
+         * Shared by all clones of the associated instrument and by the table of counters for the
+         * line.
+         */
+        private final AtomicLong count;
+
+        CoverageEventReceiver(AtomicLong count) {
+            this.count = count;
+        }
+
+        @Override
+        @TruffleBoundary
+        public void enter(Node node, VirtualFrame frame) {
+            if (isEnabled()) {
+                count.getAndIncrement();
+            }
+        }
+    }
+
+    private static final class LineLocationEntryComparator implements Comparator<Entry<LineLocation, CoverageCounter>> {
+
+        public int compare(Entry<LineLocation, CoverageCounter> e1, Entry<LineLocation, CoverageCounter> e2) {
+            return LineLocation.COMPARATOR.compare(e1.getKey(), e2.getKey());
+        }
+    }
+
+    /**
+     * Attach a counting instrument to each node that is assigned a specified tag.
+     */
+    private class CoverageProbeListener extends DefaultProbeListener {
+
+        @Override
+        public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
+            if (countingTag == tag) {
+
+                final SourceSection srcSection = probe.getProbedSourceSection();
+                if (srcSection == null) {
+                    // TODO (mlvdv) report this?
+                } else {
+                    final LineLocation lineLocation = srcSection.getLineLocation();
+                    CoverageCounter counter = counters.get(lineLocation);
+                    if (counter != null) {
+                        // Another node starts on same line; count only the first (textually)
+                        if (srcSection.getCharIndex() > counter.srcSection.getCharIndex()) {
+                            // Counter already in place, corresponds to code earlier on line
+                            return;
+                        } else {
+                            // Counter already in place, corresponds to later code; replace it
+                            counter.instrument.dispose();
+                        }
+                    }
+                    final AtomicLong count = new AtomicLong();
+                    final CoverageEventReceiver eventReceiver = new CoverageEventReceiver(count);
+                    final Instrument instrument = Instrument.create(eventReceiver, CoverageTracker.class.getSimpleName());
+                    instruments.add(instrument);
+                    probe.attach(instrument);
+                    counters.put(lineLocation, new CoverageCounter(srcSection, instrument, count));
+                }
+            }
+        }
+    }
+
+    private class CoverageCounter {
+        final SourceSection srcSection;
+        final Instrument instrument;
+        final AtomicLong count;
+
+        CoverageCounter(SourceSection srcSection, Instrument instrument, AtomicLong count) {
+            this.srcSection = srcSection;
+            this.instrument = instrument;
+            this.count = count;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/LineToProbesMap.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,128 @@
+/*
+ * 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.tools;
+
+import java.io.*;
+import java.util.*;
+
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.source.*;
+
+/**
+ * A {@link TruffleTool} that builds a map of every {@Probe} attached to some AST, indexed
+ * by {@link Source} and line number.
+ */
+public final class LineToProbesMap extends TruffleTool {
+
+    private static final boolean TRACE = false;
+    private static final PrintStream OUT = System.out;
+
+    private static void trace(String msg) {
+        OUT.println("LineToProbesMap: " + msg);
+    }
+
+    /**
+     * Map: Source line ==> probes associated with source sections starting on the line.
+     */
+    private final Map<LineLocation, Collection<Probe>> lineToProbesMap = new HashMap<>();
+
+    private final ProbeListener probeListener;
+
+    /**
+     * Create a map of {@link Probe}s that collects information on all probes added to subsequently
+     * created ASTs (once installed).
+     */
+    public LineToProbesMap() {
+        this.probeListener = new LineToProbesListener();
+    }
+
+    @Override
+    protected boolean internalInstall() {
+        Probe.addProbeListener(probeListener);
+        return true;
+    }
+
+    @Override
+    protected void internalReset() {
+        lineToProbesMap.clear();
+    }
+
+    @Override
+    protected void internalDispose() {
+        Probe.removeProbeListener(probeListener);
+    }
+
+    /**
+     * Returns the {@link Probe}, if any, associated with a specific line of guest language code; if
+     * more than one, return the one with the first starting character location.
+     */
+    public Probe findFirstProbe(LineLocation lineLocation) {
+        Probe probe = null;
+        final Collection<Probe> probes = findProbes(lineLocation);
+        for (Probe probesOnLine : probes) {
+            if (probe == null) {
+                probe = probesOnLine;
+            } else if (probesOnLine.getProbedSourceSection().getCharIndex() < probe.getProbedSourceSection().getCharIndex()) {
+                probe = probesOnLine;
+            }
+        }
+        return probe;
+    }
+
+    /**
+     * Returns all {@link Probe}s whose associated source begins at the given {@link LineLocation},
+     * an empty list if none.
+     */
+    public Collection<Probe> findProbes(LineLocation line) {
+        final Collection<Probe> probes = lineToProbesMap.get(line);
+        if (probes == null) {
+            return Collections.emptyList();
+        }
+        return Collections.unmodifiableCollection(probes);
+    }
+
+    private class LineToProbesListener extends DefaultProbeListener {
+
+        @Override
+        public void newProbeInserted(Probe probe) {
+            final SourceSection sourceSection = probe.getProbedSourceSection();
+            if (sourceSection != null && !(sourceSection instanceof NullSourceSection)) {
+                final LineLocation lineLocation = sourceSection.getLineLocation();
+                if (TRACE) {
+                    trace("ADD " + lineLocation.getShortDescription() + " ==> " + probe.getShortDescription());
+                }
+                Collection<Probe> probes = lineToProbesMap.get(lineLocation);
+                if (probes == null) {
+                    probes = new ArrayList<>(2);
+                    lineToProbesMap.put(lineLocation, probes);
+                } else {
+                    assert !probes.contains(probe);
+                }
+                probes.add(probe);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/NodeExecCounter.java	Wed Jan 28 11:28:03 2015 +0100
@@ -0,0 +1,315 @@
+/*
+ * 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.tools;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.nodes.Node.Child;
+
+/**
+ * A {@link TruffleTool} that counts interpreter <em>execution calls</em> to AST nodes, tabulated by
+ * the type of called nodes; counting can be enabled <em>all</em> nodes or restricted to nodes with
+ * a specified {@linkplain SyntaxTag tag} that is presumed to be applied external to the tool.
+ * <p>
+ * <b>Tool Life Cycle</b>
+ * <p>
+ * See {@link TruffleTool} for the life cycle common to all such tools.
+ * </p>
+ * <b>Execution Counts</b>
+ * <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 TruffleEventReceiver#enter(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;
+ * dynamically added nodes will not be instrumented.</li>
+ * </ul>
+ * </p>
+ * <b>Failure Log</b>
+ * <p>
+ * For the benefit of language implementors, the tool maintains a log describing failed attempts to
+ * probe AST nodes. Most failures occur when the type of the wrapper created by
+ * {@link Node#createWrapperNode()} is not assignable to the relevant {@link Child} field in the
+ * node's parent.
+ * </p>
+ * <p>
+ * {@linkplain #reset() Resetting} the counts has no effect on the failure log.
+ * </p>
+ * <b>Results</b>
+ * <p>
+ * A modification-safe copy of the {@linkplain #getCounts() counts} can be retrieved at any time,
+ * without effect on the state of the tool.
+ * </p>
+ * <p>
+ * A "default" {@linkplain #print(PrintStream) print()} method can summarizes the current counts at
+ * any time in a simple textual format, without effect on the state of the tool.
+ * </p>
+ *
+ * @see Instrument
+ * @see SyntaxTag
+ * @see ProbeFailure
+ */
+public final class NodeExecCounter extends TruffleTool {
+
+    /**
+     * Execution count for AST nodes of a particular type.
+     */
+    public interface NodeExecutionCount {
+        Class<?> nodeClass();
+
+        long executionCount();
+    }
+
+    /**
+     * Receiver for events at instrumented nodes. Counts are maintained in a shared table, so the
+     * receiver is stateless and can be shared by every {@link Instrument}.
+     */
+    private final TruffleEventReceiver eventReceiver = new DefaultEventReceiver() {
+        @Override
+        @TruffleBoundary
+        public void enter(Node node, VirtualFrame frame) {
+            if (isEnabled()) {
+                final Class<?> nodeClass = node.getClass();
+                AtomicLong nodeCounter = counters.get(nodeClass);
+                if (nodeCounter == null) {
+                    nodeCounter = new AtomicLong();
+                    counters.put(nodeClass, nodeCounter);
+                }
+                nodeCounter.getAndIncrement();
+            }
+        }
+    };
+
+    /** Counting data. */
+    private final Map<Class<?>, AtomicLong> counters = new HashMap<>();
+
+    /** Failure log. */
+    private final List<ProbeFailure> failures = new ArrayList<>();
+
+    /** For disposal. */
+    private final List<Instrument> instruments = new ArrayList<>();
+
+    /**
+     * If non-null, counting is restricted to nodes holding this tag.
+     */
+    private final SyntaxTag countingTag;
+
+    /**
+     * Prober used only when instrumenting every node.
+     */
+    private ASTProber astProber;
+
+    /**
+     * Listener used only when restricting counting to a specific tag.
+     */
+    private ProbeListener probeListener;
+
+    /**
+     * Create a per node-type execution counting tool for all nodes in subsequently created ASTs.
+     */
+    public NodeExecCounter() {
+        this.countingTag = null;
+    }
+
+    /**
+     * Creates a per-type execution counting for nodes tagged as specified in subsequently created
+     * ASTs.
+     */
+    public NodeExecCounter(SyntaxTag tag) {
+        this.countingTag = tag;
+    }
+
+    @Override
+    protected boolean internalInstall() {
+        if (countingTag == null) {
+            astProber = new ExecCounterASTProber();
+            Probe.registerASTProber(astProber);
+        } else {
+            probeListener = new NodeExecCounterProbeListener();
+            Probe.addProbeListener(probeListener);
+        }
+        return true;
+    }
+
+    @Override
+    protected void internalReset() {
+        counters.clear();
+        failures.clear();
+    }
+
+    @Override
+    protected void internalDispose() {
+        if (astProber != null) {
+            Probe.unregisterASTProber(astProber);
+        }
+        if (probeListener != null) {
+            Probe.removeProbeListener(probeListener);
+        }
+        for (Instrument instrument : instruments) {
+            instrument.dispose();
+        }
+    }
+
+    /**
+     * Gets a modification-safe summary of the current per-type node execution counts; does not
+     * affect the counts.
+     */
+    public NodeExecutionCount[] getCounts() {
+        final Collection<Map.Entry<Class<?>, AtomicLong>> entrySet = counters.entrySet();
+        final NodeExecutionCount[] result = new NodeExecCountImpl[entrySet.size()];
+        int i = 0;
+        for (Map.Entry<Class<?>, AtomicLong> entry : entrySet) {
+            result[i++] = new NodeExecCountImpl(entry.getKey(), entry.getValue().longValue());
+        }
+        return result;
+    }
+
+    /**
+     * Gets a log containing a report of every failed attempt to instrument a node.
+     */
+    public ProbeFailure[] getFailures() {
+        return failures.toArray(new ProbeFailure[failures.size()]);
+    }
+
+    /**
+     * A default printer for the current counts, producing lines of the form
+     * " <count> : <node type>" in descending order of count.
+     */
+    public void print(PrintStream out) {
+        print(out, false);
+    }
+
+    /**
+     * A default printer for the current counts, producing lines of the form
+     * " <count> : <node type>" in descending order of count.
+     *
+     * @param out
+     * @param verbose whether to describe nodes on which instrumentation failed
+     */
+    public void print(PrintStream out, boolean verbose) {
+
+        final long missedNodes = failures.size();
+        out.println();
+        if (countingTag == null) {
+            out.println("Execution counts by node type:");
+        } else {
+            out.println("\"" + countingTag.name() + "\"-tagged execution counts by node type:");
+        }
+        final StringBuilder disclaim = new StringBuilder("(");
+        if (missedNodes > 0) {
+            disclaim.append(Long.toString(missedNodes) + " original AST nodes not instrumented, ");
+        }
+        disclaim.append("dynamically added nodes not instrumented)");
+        out.println(disclaim.toString());
+        NodeExecutionCount[] execCounts = getCounts();
+        // Sort in descending order
+        Arrays.sort(execCounts, new Comparator<NodeExecutionCount>() {
+
+            public int compare(NodeExecutionCount o1, NodeExecutionCount o2) {
+                return Long.compare(o2.executionCount(), o1.executionCount());
+            }
+
+        });
+        for (NodeExecutionCount nodeCount : execCounts) {
+            out.format("%12d", nodeCount.executionCount());
+            out.println(" : " + nodeCount.nodeClass().getName());
+        }
+
+        if (verbose && missedNodes > 0) {
+            out.println("Instrumentation failures for execution counts:");
+
+            for (ProbeFailure failure : failures) {
+                out.println("\t" + failure.getMessage());
+            }
+        }
+    }
+
+    /**
+     * A prober that attempts to probe and instrument every node.
+     */
+    private class ExecCounterASTProber implements ASTProber, NodeVisitor {
+
+        public boolean visit(Node node) {
+
+            if (node.isInstrumentable()) {
+                try {
+                    final Instrument instrument = Instrument.create(eventReceiver, "NodeExecCounter");
+                    instruments.add(instrument);
+                    node.probe().attach(instrument);
+                } catch (ProbeException ex) {
+                    failures.add(ex.getFailure());
+                }
+            }
+            return true;
+        }
+
+        public void probeAST(Node node) {
+            node.accept(this);
+        }
+    }
+
+    /**
+     * A listener that assumes ASTs have been tagged external to this tool, and which instruments
+     * nodes holding a specified tag.
+     */
+    private class NodeExecCounterProbeListener extends DefaultProbeListener {
+
+        @Override
+        public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
+            if (countingTag == tag) {
+                final Instrument instrument = Instrument.create(eventReceiver, NodeExecCounter.class.getSimpleName());
+                instruments.add(instrument);
+                probe.attach(instrument);
+            }
+        }
+    }
+
+    private static class NodeExecCountImpl implements NodeExecutionCount {
+
+        private final Class<?> nodeClass;
+        private final long count;
+
+        public NodeExecCountImpl(Class<?> nodeClass, long count) {
+            this.nodeClass = nodeClass;
+            this.count = count;
+        }
+
+        public Class<?> nodeClass() {
+            return nodeClass;
+        }
+
+        public long executionCount() {
+            return count;
+        }
+    }
+}
--- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLMain.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLMain.java	Wed Jan 28 11:28:03 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2014, 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
@@ -24,17 +24,21 @@
 
 import java.io.*;
 import java.math.*;
+import java.util.Scanner;
 
 import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.api.instrument.*;
 import com.oracle.truffle.api.nodes.*;
 import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.api.tools.*;
 import com.oracle.truffle.sl.builtins.*;
 import com.oracle.truffle.sl.factory.*;
 import com.oracle.truffle.sl.nodes.*;
 import com.oracle.truffle.sl.nodes.call.*;
 import com.oracle.truffle.sl.nodes.controlflow.*;
 import com.oracle.truffle.sl.nodes.expression.*;
+import com.oracle.truffle.sl.nodes.instrument.*;
 import com.oracle.truffle.sl.nodes.local.*;
 import com.oracle.truffle.sl.parser.*;
 import com.oracle.truffle.sl.runtime.*;
@@ -111,9 +115,29 @@
  * argument and adds them to the function registry. Functions that are already defined are replaced
  * with the new version.
  * </ul>
+ *
+ * <p>
+ * <b>Tools:</b><br>
+ * The use of some of Truffle's support for developer tools (based on the Truffle Instrumentation
+ * Framework) are demonstrated in this file, for example:
+ * <ul>
+ * <li>a {@linkplain NodeExecCounter counter for node executions}, tabulated by node type; and</li>
+ * <li>a simple {@linkplain CoverageTracker code coverage engine}.</li>
+ * </ul>
+ * In each case, the tool is enabled if a corresponding local boolean variable in this file is set
+ * to {@code true}. Results are printed at the end of the execution using each tool's
+ * <em>default printer</em>.
+ *
  */
 public class SLMain {
 
+    /* Demonstrate per-type tabulation of node execution counts */
+    private static boolean nodeExecCounts = false;
+    /* Demonstrate per-line tabulation of STATEMENT node execution counts */
+    private static boolean statementCounts = false;
+    /* Demonstrate per-line tabulation of STATEMENT coverage */
+    private static boolean coverage = false;
+
     /**
      * The main entry point. Use the mx command "mx sl" to run it with the correct class path setup.
      */
@@ -146,6 +170,28 @@
             // logOutput.println("Source = " + source.getCode());
         }
 
+        if (statementCounts || coverage) {
+            Probe.registerASTProber(new SLStandardASTProber());
+        }
+
+        NodeExecCounter nodeExecCounter = null;
+        if (nodeExecCounts) {
+            nodeExecCounter = new NodeExecCounter();
+            nodeExecCounter.install();
+        }
+
+        NodeExecCounter statementExecCounter = null;
+        if (statementCounts) {
+            statementExecCounter = new NodeExecCounter(StandardSyntaxTag.STATEMENT);
+            statementExecCounter.install();
+        }
+
+        CoverageTracker coverageTracker = null;
+        if (coverage) {
+            coverageTracker = new CoverageTracker();
+            coverageTracker.install();
+        }
+
         /* Parse the SL source file. */
         Parser.parseSL(context, source);
 
@@ -187,6 +233,18 @@
         } finally {
             printScript("after execution", context, logOutput, printASTToLog, printSourceAttributionToLog, dumpASTToIGV);
         }
+        if (nodeExecCounter != null) {
+            nodeExecCounter.print(System.out);
+            nodeExecCounter.dispose();
+        }
+        if (statementExecCounter != null) {
+            statementExecCounter.print(System.out);
+            statementExecCounter.dispose();
+        }
+        if (coverageTracker != null) {
+            coverageTracker.print(System.out);
+            coverageTracker.dispose();
+        }
         return totalRuntime;
     }
 
@@ -282,4 +340,5 @@
         }
         return result.toString();
     }
+
 }
--- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/SLExpressionNode.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/SLExpressionNode.java	Wed Jan 28 11:28:03 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2014, 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
@@ -26,7 +26,7 @@
 
 import com.oracle.truffle.api.dsl.*;
 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.*;
 import com.oracle.truffle.api.source.*;
 import com.oracle.truffle.sl.nodes.instrument.*;
@@ -91,43 +91,13 @@
     }
 
     @Override
-    public Probe probe() {
-        Node parent = getParent();
-
-        if (parent == null) {
-            throw new IllegalStateException("Cannot call probe() a node without a parent.");
-        }
-
-        if (parent instanceof SLExpressionWrapperNode) {
-            return ((SLExpressionWrapperNode) parent).getProbe();
-        }
-
-        // Create a new wrapper/probe with this node as its child.
-        final SLExpressionWrapperNode wrapper = new SLExpressionWrapperNode(this);
-
-        // Connect it to a Probe
-        final Probe probe = ProbeNode.insertProbe(wrapper);
-
-        // Replace this node in the AST with the wrapper
-        this.replace(wrapper);
-
-        return probe;
+    public boolean isInstrumentable() {
+        return true;
     }
 
     @Override
-    public void probeLite(TruffleEventReceiver eventReceiver) {
-        Node parent = getParent();
-
-        if (parent == null) {
-            throw new IllegalStateException("Cannot call probeLite() on a node without a parent.");
-        }
+    public WrapperNode createWrapperNode() {
+        return new SLExpressionWrapperNode(this);
+    }
 
-        if (parent instanceof SLExpressionWrapperNode) {
-            throw new IllegalStateException("Cannot call probeLite() on a node that already has a wrapper.");
-        }
-        final SLExpressionWrapperNode wrapper = new SLExpressionWrapperNode(this);
-        ProbeNode.insertProbeLite(wrapper, eventReceiver);
-
-        this.replace(wrapper);
-    }
 }
--- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/SLStatementNode.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/SLStatementNode.java	Wed Jan 28 11:28:03 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2014, 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
@@ -25,8 +25,7 @@
 import java.io.*;
 
 import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.instrument.*;
-import com.oracle.truffle.api.instrument.ProbeNode.*;
+import com.oracle.truffle.api.instrument.ProbeNode.WrapperNode;
 import com.oracle.truffle.api.nodes.*;
 import com.oracle.truffle.api.source.*;
 import com.oracle.truffle.sl.nodes.instrument.*;
@@ -37,7 +36,7 @@
  * local variables.
  */
 @NodeInfo(language = "Simple Language", description = "The abstract base node for all statements")
-public abstract class SLStatementNode extends Node implements Instrumentable {
+public abstract class SLStatementNode extends Node {
 
     public SLStatementNode(SourceSection src) {
         super(src);
@@ -86,44 +85,13 @@
     }
 
     @Override
-    public Probe probe() {
-        Node parent = getParent();
-
-        if (parent == null) {
-            throw new IllegalStateException("Cannot call probe() on a node without a parent.");
-        }
-
-        if (parent instanceof SLStatementWrapperNode) {
-            return ((SLStatementWrapperNode) parent).getProbe();
-        }
-
-        // Create a new wrapper/probe with this node as its child.
-        final SLStatementWrapperNode wrapper = new SLStatementWrapperNode(this);
-
-        // Connect it to a Probe
-        final Probe probe = ProbeNode.insertProbe(wrapper);
-
-        // Replace this node in the AST with the wrapper
-        this.replace(wrapper);
-
-        return probe;
+    public boolean isInstrumentable() {
+        return true;
     }
 
     @Override
-    public void probeLite(TruffleEventReceiver eventReceiver) {
-        Node parent = getParent();
-
-        if (parent == null) {
-            throw new IllegalStateException("Cannot call probeLite() on a node without a parent");
-        }
+    public WrapperNode createWrapperNode() {
+        return new SLStatementWrapperNode(this);
+    }
 
-        if (parent instanceof SLStatementWrapperNode) {
-            throw new IllegalStateException("Cannot call probeLite() on a node that already has a wrapper.");
-        }
-
-        final SLStatementWrapperNode wrapper = new SLStatementWrapperNode(this);
-        ProbeNode.insertProbeLite(wrapper, eventReceiver);
-
-        this.replace(wrapper);
-    }
 }
--- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLExpressionWrapperNode.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLExpressionWrapperNode.java	Wed Jan 28 11:28:03 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2014, 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
@@ -58,6 +58,11 @@
     }
 
     @Override
+    public boolean isInstrumentable() {
+        return false;
+    }
+
+    @Override
     public SLExpressionNode getNonWrapperNode() {
         return child;
     }
@@ -133,14 +138,4 @@
     public SLNull executeNull(VirtualFrame frame) throws UnexpectedResultException {
         return SLTypesGen.expectSLNull(executeGeneric(frame));
     }
-
-    @Override
-    public Probe probe() {
-        throw new IllegalStateException("Cannot call probe() on a wrapper.");
-    }
-
-    @Override
-    public void probeLite(TruffleEventReceiver eventReceiver) {
-        throw new IllegalStateException("Cannot call probeLite() on a wrapper.");
-    }
 }
--- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLStandardASTProber.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLStandardASTProber.java	Wed Jan 28 11:28:03 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -44,31 +44,35 @@
      */
     public boolean visit(Node node) {
 
-        if (node instanceof SLStatementNode) {
+        if (node.isInstrumentable()) {
+
+            if (node instanceof SLStatementNode) {
 
-            // We have to distinguish between SLExpressionNode and SLStatementNode since some of the
-            // generated factories have methods that require SLExpressionNodes as parameters. Since
-            // SLExpressionNodes are a subclass of SLStatementNode, we check if something is an
-            // SLExpressionNode first.
-            if (node instanceof SLExpressionNode && node.getParent() != null) {
-                SLExpressionNode expressionNode = (SLExpressionNode) node;
-                if (expressionNode.getSourceSection() != null) {
-                    Probe probe = expressionNode.probe();
+                // Distinguish between SLExpressionNode and SLStatementNode since some of the
+                // generated factory methods that require SLExpressionNodes as parameters.
+                // Since
+                // SLExpressionNodes are a subclass of SLStatementNode, check if something is an
+                // SLExpressionNode first.
+                if (node instanceof SLExpressionNode && node.getParent() != null) {
+                    SLExpressionNode expressionNode = (SLExpressionNode) node;
+                    if (expressionNode.getSourceSection() != null) {
+                        Probe probe = expressionNode.probe();
 
-                    if (node instanceof SLWriteLocalVariableNode) {
+                        if (node instanceof SLWriteLocalVariableNode) {
+                            probe.tagAs(STATEMENT, null);
+                            probe.tagAs(ASSIGNMENT, null);
+                        }
+                    }
+                } else if (node instanceof SLStatementNode && node.getParent() != null) {
+
+                    SLStatementNode statementNode = (SLStatementNode) node;
+                    if (statementNode.getSourceSection() != null) {
+                        Probe probe = statementNode.probe();
                         probe.tagAs(STATEMENT, null);
-                        probe.tagAs(ASSIGNMENT, null);
-                    }
-                }
-            } else if (node instanceof SLStatementNode && node.getParent() != null) {
 
-                SLStatementNode statementNode = (SLStatementNode) node;
-                if (statementNode.getSourceSection() != null) {
-                    Probe probe = statementNode.probe();
-                    probe.tagAs(STATEMENT, null);
-
-                    if (node instanceof SLWhileNode) {
-                        probe.tagAs(START_LOOP, null);
+                        if (node instanceof SLWhileNode) {
+                            probe.tagAs(START_LOOP, null);
+                        }
                     }
                 }
             }
--- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLStatementWrapperNode.java	Wed Jan 28 11:27:35 2015 +0100
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/instrument/SLStatementWrapperNode.java	Wed Jan 28 11:28:03 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2014, 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
@@ -51,6 +51,11 @@
     }
 
     @Override
+    public boolean isInstrumentable() {
+        return false;
+    }
+
+    @Override
     public SLStatementNode getNonWrapperNode() {
         return child;
     }
@@ -87,13 +92,4 @@
         }
     }
 
-    @Override
-    public Probe probe() {
-        throw new IllegalStateException("Cannot call probe() on a wrapper.");
-    }
-
-    @Override
-    public void probeLite(TruffleEventReceiver eventReceiver) {
-        throw new IllegalStateException("Cannot call probeLite() on a wrapper.");
-    }
 }