Mercurial > hg > truffle
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(); + } }