diff graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Probe.java @ 18485:e3c95cbbb50c

Truffle Instrumentation: major API revision, based around the Probe and Instrument classes; add Instrumentable API for language implementors, with most details automated; reimplemented to handle AST splitting automatically; more JUnit tests.
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Sun, 23 Nov 2014 16:07:23 -0800
parents c4f374adce13
children 867058575979
line wrap: on
line diff
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Probe.java	Fri Nov 21 13:16:02 2014 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Probe.java	Sun Nov 23 16:07:23 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 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
@@ -24,63 +24,389 @@
  */
 package com.oracle.truffle.api.instrument;
 
+import java.lang.ref.*;
+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 collector of {@link ExecutionEvents} at a specific site (node) in a Truffle AST (generated by a
- * {@link Wrapper} inserted into the AST) for the purpose of <em>instrumentation</em>. For probes
- * associated with programmer-facing tools, there should be no more than one probe associated with a
- * particular piece of source code syntax (i.e. a {@link SourceSection}).
+ * 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.
  * <p>
- * Any {@linkplain SyntaxTag tags} associated with a particular piece of source code syntax are
- * managed by the probe.
+ * 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.
+ * <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.
  * <p>
- * When ASTs are copied, it is presumed that the probe for a site is shared by all AST nodes
- * representing that site.
+ * 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
+ * configure behavior relevant to each probed node.
  * <p>
- * A probe holds zero or more {@link Instrument}s, which can be added and removed dynamically.
- * Events reported to a probe are propagated to every attached instrument; the order is undefined.
- * <p>
- * Probe methods must be amenable to Truffle/Graal inlining on the assumption that the collection of
- * attached instruments seldom changes. The assumption is invalidated when instruments are added or
- * removed, but some instruments may change their internal state in such a way that the assumption
- * should also be invalidated.
+ * Instrumentation is implemented by modifying ASTs, both by inserting nodes into each AST at probed
+ * locations and by attaching additional nodes that implement dynamically attached instruments.
+ * 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.
  *
  * @see Instrument
- * @see Wrapper
+ * @see Instrumentable
+ * @see ASTProber
  */
-public interface Probe extends ExecutionEvents, SyntaxTagged, NodeInterface {
+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<>();
+
+    /**
+     * All Probes that have been created.
+     */
+    private static final List<WeakReference<Probe>> probes = new ArrayList<>();
+
+    private static final class FindSourceVisitor implements NodeVisitor {
+
+        Source source = null;
+
+        public boolean visit(Node node) {
+            final SourceSection sourceSection = node.getSourceSection();
+            if (sourceSection != null) {
+                source = sourceSection.getSource();
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Walks an AST, looking for the first node with an assigned {@link SourceSection} and returning
+     * the {@link Source}.
+     */
+    private static Source findSource(Node node) {
+        final FindSourceVisitor visitor = new FindSourceVisitor();
+        node.accept(visitor);
+        return visitor.source;
+    }
+
+    /**
+     * The tag trap is a global setting; it only affects {@linkplain Probe probes} with the
+     * {@linkplain SyntaxTag tag} specified .
+     */
+    private static SyntaxTagTrap globalTagTrap = null;
+
+    /**
+     * Enables instrumentation at selected nodes in all subsequently constructed ASTs.
+     */
+    public static void registerASTProber(ASTProber prober) {
+        astProbers.add(prober);
+    }
+
+    public static void unregisterASTProber(ASTProber prober) {
+        astProbers.remove(prober);
+    }
+
+    /**
+     * Enables instrumentation in a newly created AST by applying all registered instances of
+     * {@link ASTProber}.
+     */
+    public static void applyASTProbers(Node node) {
+
+        final Source source = findSource(node);
+
+        for (ProbeListener listener : probeListeners) {
+            listener.startASTProbing(source);
+        }
+        for (ASTProber prober : astProbers) {
+            prober.probeAST(node);
+        }
+        for (ProbeListener listener : probeListeners) {
+            listener.endASTProbing(source);
+        }
+    }
+
+    /**
+     * Adds a {@link ProbeListener} to receive events.
+     */
+    public static void addProbeListener(ProbeListener listener) {
+        assert listener != null;
+        probeListeners.add(listener);
+    }
+
+    /**
+     * Removes a {@link ProbeListener}. Ignored if listener not found.
+     */
+    public static void removeProbeListener(ProbeListener listener) {
+        probeListeners.remove(listener);
+    }
 
     /**
-     * Get the {@link SourceSection} in some Truffle AST associated with this probe.
+     * Returns all {@link Probe}s holding a particular {@link SyntaxTag}, or the whole collection if
+     * the specified tag is {@code null}.
      *
-     * @return The source associated with this probe.
+     * @return A collection of probes containing the given tag.
      */
-    SourceSection getSourceLocation();
+    public static Collection<Probe> findProbesTaggedAs(SyntaxTag tag) {
+        final List<Probe> taggedProbes = new ArrayList<>();
+        for (WeakReference<Probe> ref : probes) {
+            Probe probe = ref.get();
+            if (probe != null) {
+                if (tag == null || probe.isTaggedAs(tag)) {
+                    taggedProbes.add(ref.get());
+                }
+            }
+        }
+        return taggedProbes;
+    }
+
+    /**
+     * Sets the current "tag trap". This causes a callback to be triggered whenever execution
+     * reaches a {@link Probe} (either existing or subsequently created) with the specified tag.
+     * There can only be one tag trap set at a time.
+     *
+     * @param newTagTrap The {@link SyntaxTagTrap} to set.
+     * @throws IllegalStateException if a trap is currently set.
+     */
+    public static void setTagTrap(SyntaxTagTrap newTagTrap) throws IllegalStateException {
+        assert newTagTrap != null;
+        if (globalTagTrap != null) {
+            throw new IllegalStateException("trap already set");
+        }
+        globalTagTrap = newTagTrap;
+
+        final SyntaxTag newTag = newTagTrap.getTag();
+        for (WeakReference<Probe> ref : probes) {
+            final Probe probe = ref.get();
+            if (probe != null && probe.tags.contains(newTag)) {
+                probe.trapActive = true;
+                probe.probeStateUnchanged.invalidate();
+            }
+        }
+    }
 
     /**
-     * Mark this probe as belonging to some tool-related category that can be used to guide tool
-     * behavior at an associated AST node. For example, a debugger might add the tag
-     * {@link StandardSyntaxTag#STATEMENT} as a way of configuring where execution should stop when
-     * stepping.
+     * Clears the current {@link SyntaxTagTrap}.
+     *
+     * @throws IllegalStateException if no trap is currently set.
      */
-    void tagAs(SyntaxTag tag);
+    public static void clearTagTrap() {
+        if (globalTagTrap == null) {
+            throw new IllegalStateException("no trap set");
+        }
+        globalTagTrap = null;
+
+        for (WeakReference<Probe> ref : probes) {
+            final Probe probe = ref.get();
+            if (probe != null && probe.trapActive) {
+                probe.trapActive = false;
+                probe.probeStateUnchanged.invalidate();
+            }
+        }
+    }
+
+    private final SourceSection sourceSection;
+    private final ArrayList<SyntaxTag> tags = new ArrayList<>();
+    private final List<WeakReference<ProbeNode>> probeNodeClones = new ArrayList<>();
+    private final CyclicAssumption probeStateUnchanged = new CyclicAssumption("Probe state unchanged");
+
+    /**
+     * {@code true} iff the global trap is set and this probe has the matching tag.
+     */
+    private boolean trapActive = false;
+
+    /**
+     * @see Instrumentable#probe()
+     */
+    Probe(ProbeNode probeNode, SourceSection sourceSection) {
+        this.sourceSection = sourceSection;
+        probes.add(new WeakReference<>(this));
+        registerProbeNodeClone(probeNode);
+        for (ProbeListener listener : probeListeners) {
+            listener.newProbeInserted(this);
+        }
+    }
+
+    public boolean isTaggedAs(SyntaxTag tag) {
+        assert tag != null;
+        return tags.contains(tag);
+    }
+
+    public Collection<SyntaxTag> getSyntaxTags() {
+        return Collections.unmodifiableCollection(tags);
+    }
 
     /**
-     * Adds an {@link Instrument} to this probe's collection. No check is made to see if the same
-     * instrument has already been added.
+     * Adds a {@linkplain SyntaxTag tag} to the set of tags associated with this {@link Probe};
+     * {@code no-op} if already in the set.
+     */
+    public void tagAs(SyntaxTag tag, Object tagValue) {
+        assert tag != null;
+        if (!tags.contains(tag)) {
+            tags.add(tag);
+            for (ProbeListener listener : probeListeners) {
+                listener.probeTaggedAs(this, tag, tagValue);
+            }
+            if (globalTagTrap != null && tag == globalTagTrap.getTag()) {
+                this.trapActive = true;
+            }
+            probeStateUnchanged.invalidate();
+        }
+    }
+
+    /**
+     * Adds instrumentation at this Probe.
      *
-     * @param newInstrument The instrument to add to this probe.
+     * @param instrument an instrument not yet attached to a probe
+     * @throws IllegalStateException if the instrument has ever been attached before
      */
-    void addInstrument(Instrument newInstrument);
+    public void attach(Instrument instrument) throws IllegalStateException {
+        if (instrument.isDisposed()) {
+            throw new IllegalStateException("Attempt to attach disposed instrument");
+        }
+        if (instrument.getProbe() != null) {
+            throw new IllegalStateException("Attampt to attach an already attached instrument");
+        }
+        instrument.setAttachedTo(this);
+        for (WeakReference<ProbeNode> ref : probeNodeClones) {
+            final ProbeNode probeNode = ref.get();
+            if (probeNode != null) {
+                probeNode.addInstrument(instrument);
+            }
+        }
+        probeStateUnchanged.invalidate();
+    }
+
+    /**
+     * Gets the {@link SourceSection} associated with the Guest Language AST node being
+     * instrumented, possibly {@code null}.
+     */
+    public SourceSection getProbedSourceSection() {
+        return sourceSection;
+    }
+
+    public String getShortDescription() {
+        final String location = sourceSection == null ? "<unknown>" : sourceSection.getShortDescription();
+        return "Probe@" + location + getTagsDescription();
+    }
 
     /**
-     * Removes the given instrument from the probe's collection.
+     * Receives notification that a new clone of the instrument chain associated with this
+     * {@link Probe} has been created as a side-effect of AST cloning.
+     */
+    void registerProbeNodeClone(ProbeNode probeNode) {
+        probeNodeClones.add(new WeakReference<>(probeNode));
+    }
+
+    /**
+     * Gets the currently active {@linkplain SyntaxTagTrap tagTrap}; {@code null} if not set.
+     */
+    SyntaxTagTrap getTrap() {
+        return trapActive ? globalTagTrap : null;
+    }
+
+    /**
+     * Gets the {@link Assumption} that the instrumentation-related state of this {@link Probe} has
+     * not changed since this method was last called.
+     */
+    Assumption getUnchangedAssumption() {
+        return probeStateUnchanged.getAssumption();
+    }
+
+    /**
+     * Internal method for removing and rendering inert a specific instrument previously attached at
+     * this Probe.
      *
-     * @param oldInstrument The instrument to remove from this probe.
-     * @throws RuntimeException if no matching instrument has been attached.
+     * @param instrument an instrument already attached
+     * @throws IllegalStateException if instrument not attached at this Probe
+     * @see Instrument#dispose()
      */
-    void removeInstrument(Instrument oldInstrument) throws RuntimeException;
+    void disposeInstrument(Instrument instrument) throws IllegalStateException {
+        for (WeakReference<ProbeNode> ref : probeNodeClones) {
+            final ProbeNode probeNode = ref.get();
+            if (probeNode != null) {
+                probeNode.removeInstrument(instrument);
+            }
+        }
+        probeStateUnchanged.invalidate();
+    }
 
+    private String getTagsDescription() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        String prefix = "";
+        for (SyntaxTag tag : tags) {
+            sb.append(prefix);
+            prefix = ",";
+            sb.append(tag.toString());
+        }
+        sb.append("]");
+        return sb.toString();
+    }
 }