Mercurial > hg > graal-compiler
view 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 source
/* * 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 * 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.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 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> * 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> * 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> * 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 Instrumentable * @see ASTProber */ 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); } /** * Returns all {@link Probe}s holding a particular {@link SyntaxTag}, or the whole collection 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; } /** * 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(); } } } /** * Clears the current {@link SyntaxTagTrap}. * * @throws IllegalStateException if no trap is currently set. */ 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 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 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); } } 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(); } /** * 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 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); } } 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(); } }