diff truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Probe.java @ 21951:9c8c0937da41

Moving all sources into truffle subdirectory
author Jaroslav Tulach <jaroslav.tulach@oracle.com>
date Wed, 17 Jun 2015 10:58:08 +0200
parents graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Probe.java@fd8a92655fbd
children 5bc7f7b867ab
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Probe.java	Wed Jun 17 10:58:08 2015 +0200
@@ -0,0 +1,486 @@
+/*
+ * 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.  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 java.io.*;
+import java.lang.ref.*;
+import java.util.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.instrument.InstrumentationNode.TruffleEvents;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.api.utilities.*;
+
+//TODO (mlvdv) these statics should not be global.  Move them to some kind of context.
+
+/**
+ * A <em>binding</em> between:
+ * <ol>
+ * <li>A program location in an executing Truffle AST (corresponding to a {@link SourceSection}),
+ * and</li>
+ * <li>A dynamically managed collection of "attached" {@linkplain Instrument Instruments} that
+ * receive event notifications on behalf of external clients.</li>
+ * </ol>
+ * <p>
+ * Client-oriented documentation for the use of Probes is available online at <a
+ * HREF="https://wiki.openjdk.java.net/display/Graal/Finding+Probes"
+ * >https://wiki.openjdk.java.net/display/Graal/Finding+Probes</a>
+ * <p>
+ * <h4>Implementation notes:</h4>
+ * <p>
+ * <ul>
+ * <li>A Probe must be permanently associated with a <em>program location</em>, defined by a
+ * particular {@link SourceSection}, even though:
+ * <ul>
+ * <li>that location is represented in an AST as a {@link Node}, which might be replaced through
+ * optimizations such as specialization, and</li>
+ * <li>Truffle may <em>clone</em> the AST so that the location is actually represented by multiple
+ * Nodes in multiple ASTs.</li>
+ * </ul>
+ * </li>
+ *
+ * <li>The effect of the binding is to intercept {@linkplain TruffleEvents execution events}
+ * arriving at the "probed" AST Node and notify each attached {@link Instrument} before execution is
+ * allowed to proceed to the child and again after execution completes.</li>
+ *
+ * <li>The method {@link Node#probe()} creates a Probe on an AST Node; redundant calls return the
+ * same Probe.</li>
+ *
+ * <li>The "probing" of a Truffle AST must be done after the AST is complete (i.e. parent pointers
+ * correctly assigned), but before any cloning or executions. This is done by creating an instance
+ * of {@link ASTProber} and registering it via {@link #registerASTProber(ASTProber)}. Once
+ * registered, it will be applied automatically to every newly created AST.</li>
+ *
+ * <li>The "probing" of an AST Node is implemented by insertion of a {@link ProbeNode.WrapperNode}
+ * into the AST (as new parent of the Node being probed), together with an associated
+ * {@link ProbeNode} that routes execution events at the probed Node to all the
+ * {@linkplain Instrument Instruments} attached to the Probe's <em>instrument chain</em>.</li>
+ *
+ * <li>When Truffle clones an AST, any attached WrapperNodes and ProbeNodes are cloned as well,
+ * together with their attached instrument chains. Each Probe instance intercepts cloning events and
+ * keeps track of all AST copies.</li>
+ *
+ * <li>All attached {@link InstrumentationNode}s effectively become part of the running program:
+ * <ul>
+ * <li>Good News: instrumentation code implicitly benefits from every kind of Truffle optimization.</li>
+ * <li>Bad News: instrumentation code must be implemented carefully to avoid interfering with any
+ * Truffle optimizations.</li>
+ * </ul>
+ * </li>
+ *
+ * </ul>
+ *
+ * @see Instrument
+ * @see ASTProber
+ * @see ProbeListener
+ * @see SyntaxTag
+ */
+public final class Probe {
+
+    private static final boolean TRACE = false;
+    private static final String TRACE_PREFIX = "PROBE: ";
+    private static final PrintStream OUT = System.out;
+
+    private static void trace(String format, Object... args) {
+        if (TRACE) {
+            OUT.println(TRACE_PREFIX + String.format(format, args));
+        }
+    }
+
+    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<>();
+
+    /**
+     * A global trap that triggers notification just before executing any Node that is Probed with a
+     * matching tag.
+     */
+    @CompilationFinal private static SyntaxTagTrap beforeTagTrap = null;
+
+    /**
+     * A global trap that triggers notification just after executing any Node that is Probed with a
+     * matching tag.
+     */
+    @CompilationFinal private static SyntaxTagTrap afterTagTrap = null;
+
+    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;
+    }
+
+    /**
+     * 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) {
+
+        String name = "<?>";
+        final Source source = findSource(node);
+        if (source != null) {
+            name = source.getShortName();
+        } else {
+            final SourceSection sourceSection = node.getEncapsulatingSourceSection();
+            if (sourceSection != null) {
+                name = sourceSection.getShortDescription();
+            }
+        }
+        trace("START %s", name);
+        for (ProbeListener listener : probeListeners) {
+            listener.startASTProbing(source);
+        }
+        for (ASTProber prober : astProbers) {
+            prober.probeAST(node);
+        }
+        for (ProbeListener listener : probeListeners) {
+            listener.endASTProbing(source);
+        }
+        trace("FINISHED %s", name);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Returns all {@link Probe}s holding a particular {@link SyntaxTag}, or the whole collection of
+     * probes if the specified tag is {@code null}.
+     *
+     * @return A collection of probes containing the given tag.
+     */
+    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;
+    }
+
+    // TODO (mlvdv) generalize to permit multiple "before traps" without a performance hit?
+    /**
+     * Sets the current "<em>before</em> tag trap"; there can be no more than one in effect.
+     * <ul>
+     * <li>The before-trap triggers a callback just <strong><em>before</em></strong> execution
+     * reaches <strong><em>any</em></strong> {@link Probe} (either existing or subsequently created)
+     * with the specified {@link SyntaxTag}.</li>
+     * <li>Setting the before-trap to {@code null} clears an existing before-trap.</li>
+     * <li>Setting a non{@code -null} before-trap when one is already set clears the previously set
+     * before-trap.</li>
+     * </ul>
+     *
+     * @param newBeforeTagTrap The new "before" {@link SyntaxTagTrap} to set.
+     */
+    public static void setBeforeTagTrap(SyntaxTagTrap newBeforeTagTrap) {
+        beforeTagTrap = newBeforeTagTrap;
+        for (WeakReference<Probe> ref : probes) {
+            final Probe probe = ref.get();
+            if (probe != null) {
+                probe.notifyTrapsChanged();
+            }
+        }
+    }
+
+    // TODO (mlvdv) generalize to permit multiple "after traps" without a performance hit?
+    /**
+     * Sets the current "<em>after</em> tag trap"; there can be no more than one in effect.
+     * <ul>
+     * <li>The after-trap triggers a callback just <strong><em>after</em></strong> execution leaves
+     * <strong><em>any</em></strong> {@link Probe} (either existing or subsequently created) with
+     * the specified {@link SyntaxTag}.</li>
+     * <li>Setting the after-trap to {@code null} clears an existing after-trap.</li>
+     * <li>Setting a non{@code -null} after-trap when one is already set clears the previously set
+     * after-trap.</li>
+     * </ul>
+     *
+     * @param newAfterTagTrap The new "after" {@link SyntaxTagTrap} to set.
+     */
+    public static void setAfterTagTrap(SyntaxTagTrap newAfterTagTrap) {
+        afterTagTrap = newAfterTagTrap;
+        for (WeakReference<Probe> ref : probes) {
+            final Probe probe = ref.get();
+            if (probe != null) {
+                probe.notifyTrapsChanged();
+            }
+        }
+    }
+
+    private final SourceSection sourceSection;
+    private final ArrayList<SyntaxTag> tags = new ArrayList<>();
+    private final List<WeakReference<ProbeNode>> probeNodeClones = new ArrayList<>();
+
+    /*
+     * Invalidated whenever something changes in the Probe and its Instrument chain, so need deopt
+     */
+    private final CyclicAssumption probeStateUnchangedCyclic = new CyclicAssumption("Probe state unchanged");
+
+    /*
+     * The assumption that nothing had changed in this probe, the last time anybody checked (when
+     * there may have been a deopt). Every time a check fails, gets replaced by a new unchanged
+     * assumption.
+     */
+    @CompilationFinal private Assumption probeStateUnchangedAssumption = probeStateUnchangedCyclic.getAssumption();
+
+    // Must invalidate whenever changed
+    @CompilationFinal private boolean isBeforeTrapActive = false;
+
+    // Must invalidate whenever changed
+    @CompilationFinal private boolean isAfterTrapActive = false;
+
+    /**
+     * Intended for use only by {@link ProbeNode}.
+     */
+    Probe(ProbeNode probeNode, SourceSection sourceSection) {
+        this.sourceSection = sourceSection;
+        probes.add(new WeakReference<>(this));
+        registerProbeNodeClone(probeNode);
+        if (TRACE) {
+            final String location = this.sourceSection == null ? "<unknown>" : sourceSection.getShortDescription();
+            trace("ADDED %s %s %s", "Probe@", location, getTagsDescription());
+        }
+        for (ProbeListener listener : probeListeners) {
+            listener.newProbeInserted(this);
+        }
+    }
+
+    /**
+     * Is this node tagged as belonging to a particular human-sensible category of language
+     * constructs?
+     */
+    public boolean isTaggedAs(SyntaxTag tag) {
+        assert tag != null;
+        return tags.contains(tag);
+    }
+
+    /**
+     * In which user-sensible categories has this node been tagged (<em>empty set</em> if none).
+     */
+    public Collection<SyntaxTag> getSyntaxTags() {
+        return Collections.unmodifiableCollection(tags);
+    }
+
+    /**
+     * 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);
+            }
+
+            // Update the status of this Probe with respect to global tag traps
+            boolean tagTrapsChanged = false;
+            if (beforeTagTrap != null && tag == beforeTagTrap.getTag()) {
+                this.isBeforeTrapActive = true;
+                tagTrapsChanged = true;
+            }
+            if (afterTagTrap != null && tag == afterTagTrap.getTag()) {
+                this.isAfterTrapActive = true;
+                tagTrapsChanged = true;
+            }
+            if (tagTrapsChanged) {
+                invalidateProbeUnchanged();
+            }
+            if (TRACE) {
+                trace("TAGGED as %s: %s", tag, getShortDescription());
+            }
+        }
+    }
+
+    /**
+     * Adds instrumentation at this Probe.
+     *
+     * @param instrument an instrument not yet attached to a probe
+     * @throws IllegalStateException if the instrument has ever been attached before
+     */
+    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);
+            }
+        }
+        invalidateProbeUnchanged();
+    }
+
+    /**
+     * 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();
+    }
+
+    /**
+     * Internal method for removing and rendering inert a specific instrument previously attached at
+     * this Probe.
+     *
+     * @param instrument an instrument already attached
+     * @throws IllegalStateException if instrument not attached at this Probe
+     * @see Instrument#dispose()
+     */
+    void disposeInstrument(Instrument instrument) throws IllegalStateException {
+        for (WeakReference<ProbeNode> ref : probeNodeClones) {
+            final ProbeNode probeNode = ref.get();
+            if (probeNode != null) {
+                probeNode.removeInstrument(instrument);
+            }
+        }
+        invalidateProbeUnchanged();
+    }
+
+    /**
+     * 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 <strong><em>before</em></strong> {@linkplain SyntaxTagTrap Tag
+     * Trap} at this Probe. Non{@code -null} if the global
+     * {@linkplain Probe#setBeforeTagTrap(SyntaxTagTrap) Before Tag Trap} is set and if this Probe
+     * holds the {@link SyntaxTag} specified in the trap.
+     */
+    SyntaxTagTrap getBeforeTrap() {
+        checkProbeUnchanged();
+        return isBeforeTrapActive ? beforeTagTrap : null;
+    }
+
+    /**
+     * Gets the currently active <strong><em>after</em></strong> {@linkplain SyntaxTagTrap Tag Trap}
+     * at this Probe. Non{@code -null} if the global
+     * {@linkplain Probe#setAfterTagTrap(SyntaxTagTrap) After Tag Trap} is set and if this Probe
+     * holds the {@link SyntaxTag} specified in the trap.
+     */
+    SyntaxTagTrap getAfterTrap() {
+        checkProbeUnchanged();
+        return isAfterTrapActive ? afterTagTrap : null;
+    }
+
+    /**
+     * To be called wherever in the Probe/Instrument chain there are dependencies on the probe
+     * state's @CompilatonFinal fields.
+     */
+    void checkProbeUnchanged() {
+        try {
+            probeStateUnchangedAssumption.check();
+        } catch (InvalidAssumptionException ex) {
+            // Failure creates an implicit deoptimization
+            // Get the assumption associated with the new probe state
+            this.probeStateUnchangedAssumption = probeStateUnchangedCyclic.getAssumption();
+        }
+    }
+
+    void invalidateProbeUnchanged() {
+        probeStateUnchangedCyclic.invalidate();
+    }
+
+    private void notifyTrapsChanged() {
+        this.isBeforeTrapActive = beforeTagTrap != null && this.isTaggedAs(beforeTagTrap.getTag());
+        this.isAfterTrapActive = afterTagTrap != null && this.isTaggedAs(afterTagTrap.getTag());
+        invalidateProbeUnchanged();
+    }
+
+    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();
+    }
+}