Mercurial > hg > truffle
diff graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.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 | 85cec9cab17b |
children | 867058575979 |
line wrap: on
line diff
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.java Fri Nov 21 13:16:02 2014 +0100 +++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/Instrument.java Sun Nov 23 16:07:23 2014 -0800 @@ -24,113 +24,297 @@ */ package com.oracle.truffle.api.instrument; -import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.*; import com.oracle.truffle.api.frame.*; import com.oracle.truffle.api.instrument.impl.*; import com.oracle.truffle.api.nodes.*; +// TODO (mlvdv) migrate some of this to external documentation. /** - * A receiver of Truffle AST runtime {@link ExecutionEvents}, propagated from the {@link Probe} to - * which the instrument is attached, for the benefit of associated <em>tools</em>. + * A dynamically added/removed binding between a {@link Probe}, which provides notification of + * {@linkplain TruffleEventReceiver execution events} taking place at a {@link Node} in a Guest + * Language (GL) Truffle AST, and a {@linkplain TruffleEventReceiver receiver}, which consumes + * notifications on behalf of an external tool. * <p> - * Guidelines for implementing {@link Instrument}s, with particular attention to minimize runtime - * performance overhead: + * <h4>Summary: How to "instrument" an AST location:</h4> * <ol> - * <li>Extend this abstract class and override only the {@linkplain ExecutionEvents event handling - * methods} for which intervention is needed.</li> - * <li>Instruments are Truffle {@link Node}s and should be coded as much as possible in the desired - * <em>Truffle style</em>, documented more thoroughly elsewhere.</li> - * <li>Maintain as little state as possible.</li> - * <li>If state is necessary, make object fields {@code final} if at all possible.</li> - * <li>If non-final object-valued state is necessary, annotate it as {@link CompilationFinal} and - * call {@linkplain InstrumentationNode#notifyProbeChanged(Instrument)} whenever it is modified.</li> - * <li>Never store a {@link Frame} value in a field.</li> - * <li>Minimize computation in standard execution paths.</li> - * <li>If runtime calls must be made back to a tool, construct the instrument with a callback stored - * in a {@code final} field.</li> - * <li>Tool methods called by the instrument should be annotated as {@link TruffleBoundary} to - * prevent them from being inlined into fast execution paths.</li> - * <li>If computation in the execution path is needed, and if performance is important, then the - * computation is best expressed as a guest language AST and evaluated using standard Truffle - * mechanisms so that standard Truffle optimizations can be applied.</li> + * <li>Create an implementation of {@link TruffleEventReceiver} that responds to events on behalf of + * a tool.</li> + * <li>Create an Instrument via factory method {@link Instrument#create(TruffleEventReceiver)}.</li> + * <li>"Attach" the Instrument to a Probe via {@link Probe#attach(Instrument)}, at which point event + * notifications begin to arrive at the receiver.</li> + * <li>When no longer needed, "detach" the Instrument via {@link Instrument#dispose()}, at which + * point event notifications to the receiver cease, and the Instrument becomes unusable.</li> + * </ol> + * <p> + * <h4>Options for creating receivers:</h4> + * <p> + * <ol> + * <li>Implement the interface {@link TruffleEventReceiver}. The event handling methods account for + * both the entry into an AST node (about to call) and several possible kinds of exit from an AST + * node (just returned).</li> + * <li>Extend {@link DefaultEventReceiver}, which provides a no-op implementation of every + * {@link TruffleEventReceiver} method; override the methods of interest.</li> + * <li>Extend {@link SimpleEventReceiver}, where return values are ignored so only two methods (for + * "enter" and "return") will notify all events.</li> * </ol> * <p> - * Guidelines for attachment to a {@link Probe}: - * <ol> - * <li>An Instrument instance must only attached to a single {@link Probe}, each of which is - * associated uniquely with a specific syntactic unit of a guest language program, and thus - * (initially) to a specific {@linkplain Node Truffle AST node}.</li> - * <li>When the AST containing such a node is copied at runtime, the {@link Probe} will be shared by - * every copy, and so the Instrument will receive events corresponding to the intended syntactic - * unit of code, independent of which AST copy is being executed.</li> - * </ol> + * <h4>General guidelines for receiver implementation:</h4> + * <p> + * When an Instrument is attached to a Probe, the receiver effectively becomes part of the executing + * GL program; performance can be affected by the receiver's implementation. + * <ul> + * <li>Do not store {@link Frame} or {@link Node} references in fields.</li> + * <li>Prefer {@code final} fields and (where performance is important) short methods.</li> + * <li>If needed, pass along the {@link VirtualFrame} reference from an event notification as far as + * possible through code that is expected to be inlined, since this incurs no runtime overhead. When + * access to frame data is needed, substitute a more expensive {@linkplain Frame#materialize() + * materialized} representation of the frame.</li> + * <li>If a receiver calls back to its tool during event handling, and if performance is an issue, + * then this should be through a final "callback" field in the instrument, and the called methods + * should be minimal.</li> + * <li>On the other hand, implementations should prevent Truffle from inlining beyond a reasonable + * point with the method annotation {@link TruffleBoundary}.</li> + * <li>The implicit "outer" pointer in a non-static inner class is a useful way to implement + * callbacks to owner tools.</li> + * <li>Primitive-valued return events are boxed for notification, but Truffle will eliminate the + * boxing if they are cast back to their primitive values quickly (in particular before crossing any + * {@link TruffleBoundary} annotations). + * </ul> + * <p> + * <h4>Allowing for AST cloning:</h4> + * <p> + * Truffle routinely <em>clones</em> ASTs, which has consequences for receiver implementation. + * <ul> + * <li>Even though a {@link Probe} is uniquely associated with a particular location in the + * executing Guest Language program, execution events at that location will in general be + * implemented by different {@link Node} instances, i.e. <em>clones</em> of the originally probed + * node.</li> + * <li>Because of <em>cloning</em> the {@link Node} supplied with notifications to a particular + * receiver will vary, but because they all represent the same GL program location the events should + * be treated as equivalent for most purposes.</li> + * </ul> + * <p> + * <h4>Access to execution state:</h4> * <p> - * Guidelines for handling {@link ExecutionEvents}: - * <ol> - * <li>Separate event methods are defined for each kind of possible return: object-valued, - * primitive-valued, void-valued, and exceptional.</li> - * <li>Override "leave*" primitive methods if the language implementation returns primitives and the - * instrument should avoid boxing them.</li> - * <li>On the other hand, if boxing all primitives for instrumentation is desired, it is only - * necessary to override the object-valued return methods, since the default implementation of each - * primitive-valued return method is to box the value and forward it to the object-valued return - * method.</li> - * </ol> - * + * <ul> + * <li>Event notification arguments provide primary access to the GL program's execution states: + * <ul> + * <li>{@link Node}: the concrete node (in one of the AST's clones) from which the event originated. + * </li> + * <li>{@link VirtualFrame}: the current execution frame. + * </ul> + * <li>Some global information is available, for example the execution + * {@linkplain TruffleRuntime#iterateFrames(FrameInstanceVisitor) stack}.</li> + * <li>Additional information needed by a receiver could be stored when created, preferably + * {@code final} of course. For example, a reference to the {@link Probe} to which the receiver's + * Instrument has been attached would give access to its corresponding + * {@linkplain Probe#getProbedSourceSection() source location} or to the collection of + * {@linkplain SyntaxTag tags} currently applied to the Probe.</li> + * </ul> + * <p> + * <h4>Activating and deactivating Instruments:</h4> + * <p> + * Instruments are <em>single-use</em>: + * <ul> + * <li>An instrument becomes active only when <em>attached</em> to a Probe via + * {@link Probe#attach(Instrument)}, and it may only be attached to a single Probe. It is a runtime + * error to attempt attaching a previously attached instrument.</li> + * <li>Attaching an instrument modifies every existing clone of the AST to which it is being + * attached, which can trigger deoptimization.</li> + * <li>The method {@link Instrument#dispose()} makes an instrument inactive by removing it from the + * Probe to which it was attached and rendering it permanently inert.</li> + * <li>Disposal removes the implementation of an instrument from all ASTs to which it was attached, + * which can trigger deoptimization.</li> + * </ul> + * <p> + * <h4>Sharing receivers:</h4> + * <p> + * Although an Instrument may only be attached to a single Probe, a receiver can be shared among + * multiple Instruments. This can be useful for observing events that might happen at different + * locations in a single AST, for example all assignments to a particular variable. In this case a + * new Instrument would be created and attached at each assignment node, but all the Instruments + * would be created with the same receiver. * <p> * <strong>Disclaimer:</strong> experimental; under development. * * @see Probe - * @see ASTNodeProber + * @see TruffleEventReceiver */ -public abstract class Instrument extends InstrumentationNode { +public final class Instrument { - protected Instrument() { + /** + * Creates an instrument that will route execution events to a receiver. + * + * @param receiver a receiver for event generated by the instrument + * @param instrumentInfo optional description of the instrument's role + * @return a new instrument, ready for attachment at a probe + */ + public static Instrument create(TruffleEventReceiver receiver, String instrumentInfo) { + return new Instrument(receiver, instrumentInfo); } - public void enter(Node astNode, VirtualFrame frame) { + /** + * Creates an instrument that will route execution events to a receiver. + */ + public static Instrument create(TruffleEventReceiver receiver) { + return new Instrument(receiver, null); } - public void leave(Node astNode, VirtualFrame frame) { - } + /** + * Tool-supplied receiver of events. + */ + private final TruffleEventReceiver toolEventreceiver; + + /** + * Optional documentation, mainly for debugging. + */ + private final String instrumentInfo; - public void leave(Node astNode, VirtualFrame frame, boolean result) { - leave(astNode, frame, (Object) result); + /** + * Has this instrument been disposed? stays true once set. + */ + private boolean isDisposed = false; + + private Probe probe = null; + + private Instrument(TruffleEventReceiver receiver, String instrumentInfo) { + this.toolEventreceiver = receiver; + this.instrumentInfo = instrumentInfo; } - public void leave(Node astNode, VirtualFrame frame, byte result) { - leave(astNode, frame, (Object) result); + /** + * Removes this instrument (and any clones) from the probe to which it attached and renders the + * instrument inert. + * + * @throws IllegalStateException if this instrument has already been disposed + */ + public void dispose() throws IllegalStateException { + if (isDisposed) { + throw new IllegalStateException("Attempt to dispose an already disposed Instrumennt"); + } + if (probe != null) { + // It's attached + probe.disposeInstrument(this); + } + this.isDisposed = true; } - public void leave(Node astNode, VirtualFrame frame, short result) { - leave(astNode, frame, (Object) result); + Probe getProbe() { + return probe; + } + + void setAttachedTo(Probe probe) { + this.probe = probe; + } + + /** + * Has this instrument been disposed and rendered unusable? + */ + boolean isDisposed() { + return isDisposed; + } + + InstrumentNode addToChain(InstrumentNode nextNode) { + return new InstrumentNode(nextNode); } - public void leave(Node astNode, VirtualFrame frame, int result) { - leave(astNode, frame, (Object) result); - } - - public void leave(Node astNode, VirtualFrame frame, long result) { - leave(astNode, frame, (Object) result); - } - - public void leave(Node astNode, VirtualFrame frame, char result) { - leave(astNode, frame, (Object) result); + /** + * Removes this instrument from an instrument chain. + */ + InstrumentNode removeFromChain(InstrumentNode instrumentNode) { + boolean found = false; + if (instrumentNode != null) { + if (instrumentNode.getInstrument() == this) { + // Found the match at the head of the chain + return instrumentNode.nextInstrument; + } + // Match not at the head of the chain; remove it. + found = instrumentNode.removeFromChain(Instrument.this); + } + if (!found) { + throw new IllegalStateException("Couldn't find instrument node to remove: " + this); + } + return instrumentNode; } - public void leave(Node astNode, VirtualFrame frame, float result) { - leave(astNode, frame, (Object) result); - } + @NodeInfo(cost = NodeCost.NONE) + final class InstrumentNode extends Node implements TruffleEventReceiver, InstrumentationNode { + + @Child private InstrumentNode nextInstrument; + + private InstrumentNode(InstrumentNode nextNode) { + this.nextInstrument = nextNode; + } + + /** + * Gets the instrument that created this node. + */ + private Instrument getInstrument() { + return Instrument.this; + } - public void leave(Node astNode, VirtualFrame frame, double result) { - leave(astNode, frame, (Object) result); - } + /** + * Removes the node from this chain that was added by a particular instrument, assuming that + * the head of the chain is not the one to be replaced. This is awkward, but is required + * because {@link Node#replace(Node)} won't take a {@code null} argument. This doesn't work + * for the tail of the list, which would be replacing itself with null. So the replacement + * must be directed the parent of the node being removed. + */ + private boolean removeFromChain(Instrument instrument) { + assert getInstrument() != instrument; + if (nextInstrument == null) { + return false; + } + if (nextInstrument.getInstrument() == instrument) { + // Next is the one to remove + if (nextInstrument.nextInstrument == null) { + // Next is at the tail; just forget + nextInstrument = null; + } else { + // Replace next with its successor + nextInstrument.replace(nextInstrument.nextInstrument); + } + return true; + } + return nextInstrument.removeFromChain(instrument); + } - public void leave(Node astNode, VirtualFrame frame, Object result) { - } + public void enter(Node node, VirtualFrame frame) { + Instrument.this.toolEventreceiver.enter(node, frame); + if (nextInstrument != null) { + nextInstrument.enter(node, frame); + } + } + + public void returnVoid(Node node, VirtualFrame frame) { + Instrument.this.toolEventreceiver.returnVoid(node, frame); + if (nextInstrument != null) { + nextInstrument.returnVoid(node, frame); + } + } - public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) { + public void returnValue(Node node, VirtualFrame frame, Object result) { + Instrument.this.toolEventreceiver.returnValue(node, frame, result); + if (nextInstrument != null) { + nextInstrument.returnValue(node, frame, result); + } + } + + public void returnExceptional(Node node, VirtualFrame frame, Exception exception) { + Instrument.this.toolEventreceiver.returnExceptional(node, frame, exception); + if (nextInstrument != null) { + nextInstrument.returnExceptional(node, frame, exception); + } + } + + public String instrumentationInfo() { + if (Instrument.this.instrumentInfo != null) { + return Instrument.this.instrumentInfo; + } + return toolEventreceiver.getClass().getSimpleName(); + } } }