comparison 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
comparison
equal deleted inserted replaced
18484:e97e1f07a3d6 18485:e3c95cbbb50c
22 * or visit www.oracle.com if you need additional information or have any 22 * or visit www.oracle.com if you need additional information or have any
23 * questions. 23 * questions.
24 */ 24 */
25 package com.oracle.truffle.api.instrument; 25 package com.oracle.truffle.api.instrument;
26 26
27 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
28 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; 27 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
28 import com.oracle.truffle.api.*;
29 import com.oracle.truffle.api.frame.*; 29 import com.oracle.truffle.api.frame.*;
30 import com.oracle.truffle.api.instrument.impl.*; 30 import com.oracle.truffle.api.instrument.impl.*;
31 import com.oracle.truffle.api.nodes.*; 31 import com.oracle.truffle.api.nodes.*;
32 32
33 // TODO (mlvdv) migrate some of this to external documentation.
33 /** 34 /**
34 * A receiver of Truffle AST runtime {@link ExecutionEvents}, propagated from the {@link Probe} to 35 * A dynamically added/removed binding between a {@link Probe}, which provides notification of
35 * which the instrument is attached, for the benefit of associated <em>tools</em>. 36 * {@linkplain TruffleEventReceiver execution events} taking place at a {@link Node} in a Guest
36 * <p> 37 * Language (GL) Truffle AST, and a {@linkplain TruffleEventReceiver receiver}, which consumes
37 * Guidelines for implementing {@link Instrument}s, with particular attention to minimize runtime 38 * notifications on behalf of an external tool.
38 * performance overhead: 39 * <p>
40 * <h4>Summary: How to "instrument" an AST location:</h4>
39 * <ol> 41 * <ol>
40 * <li>Extend this abstract class and override only the {@linkplain ExecutionEvents event handling 42 * <li>Create an implementation of {@link TruffleEventReceiver} that responds to events on behalf of
41 * methods} for which intervention is needed.</li> 43 * a tool.</li>
42 * <li>Instruments are Truffle {@link Node}s and should be coded as much as possible in the desired 44 * <li>Create an Instrument via factory method {@link Instrument#create(TruffleEventReceiver)}.</li>
43 * <em>Truffle style</em>, documented more thoroughly elsewhere.</li> 45 * <li>"Attach" the Instrument to a Probe via {@link Probe#attach(Instrument)}, at which point event
44 * <li>Maintain as little state as possible.</li> 46 * notifications begin to arrive at the receiver.</li>
45 * <li>If state is necessary, make object fields {@code final} if at all possible.</li> 47 * <li>When no longer needed, "detach" the Instrument via {@link Instrument#dispose()}, at which
46 * <li>If non-final object-valued state is necessary, annotate it as {@link CompilationFinal} and 48 * point event notifications to the receiver cease, and the Instrument becomes unusable.</li>
47 * call {@linkplain InstrumentationNode#notifyProbeChanged(Instrument)} whenever it is modified.</li>
48 * <li>Never store a {@link Frame} value in a field.</li>
49 * <li>Minimize computation in standard execution paths.</li>
50 * <li>If runtime calls must be made back to a tool, construct the instrument with a callback stored
51 * in a {@code final} field.</li>
52 * <li>Tool methods called by the instrument should be annotated as {@link TruffleBoundary} to
53 * prevent them from being inlined into fast execution paths.</li>
54 * <li>If computation in the execution path is needed, and if performance is important, then the
55 * computation is best expressed as a guest language AST and evaluated using standard Truffle
56 * mechanisms so that standard Truffle optimizations can be applied.</li>
57 * </ol> 49 * </ol>
58 * <p> 50 * <p>
59 * Guidelines for attachment to a {@link Probe}: 51 * <h4>Options for creating receivers:</h4>
52 * <p>
60 * <ol> 53 * <ol>
61 * <li>An Instrument instance must only attached to a single {@link Probe}, each of which is 54 * <li>Implement the interface {@link TruffleEventReceiver}. The event handling methods account for
62 * associated uniquely with a specific syntactic unit of a guest language program, and thus 55 * both the entry into an AST node (about to call) and several possible kinds of exit from an AST
63 * (initially) to a specific {@linkplain Node Truffle AST node}.</li> 56 * node (just returned).</li>
64 * <li>When the AST containing such a node is copied at runtime, the {@link Probe} will be shared by 57 * <li>Extend {@link DefaultEventReceiver}, which provides a no-op implementation of every
65 * every copy, and so the Instrument will receive events corresponding to the intended syntactic 58 * {@link TruffleEventReceiver} method; override the methods of interest.</li>
66 * unit of code, independent of which AST copy is being executed.</li> 59 * <li>Extend {@link SimpleEventReceiver}, where return values are ignored so only two methods (for
60 * "enter" and "return") will notify all events.</li>
67 * </ol> 61 * </ol>
68 * <p> 62 * <p>
69 * Guidelines for handling {@link ExecutionEvents}: 63 * <h4>General guidelines for receiver implementation:</h4>
70 * <ol> 64 * <p>
71 * <li>Separate event methods are defined for each kind of possible return: object-valued, 65 * When an Instrument is attached to a Probe, the receiver effectively becomes part of the executing
72 * primitive-valued, void-valued, and exceptional.</li> 66 * GL program; performance can be affected by the receiver's implementation.
73 * <li>Override "leave*" primitive methods if the language implementation returns primitives and the 67 * <ul>
74 * instrument should avoid boxing them.</li> 68 * <li>Do not store {@link Frame} or {@link Node} references in fields.</li>
75 * <li>On the other hand, if boxing all primitives for instrumentation is desired, it is only 69 * <li>Prefer {@code final} fields and (where performance is important) short methods.</li>
76 * necessary to override the object-valued return methods, since the default implementation of each 70 * <li>If needed, pass along the {@link VirtualFrame} reference from an event notification as far as
77 * primitive-valued return method is to box the value and forward it to the object-valued return 71 * possible through code that is expected to be inlined, since this incurs no runtime overhead. When
78 * method.</li> 72 * access to frame data is needed, substitute a more expensive {@linkplain Frame#materialize()
79 * </ol> 73 * materialized} representation of the frame.</li>
80 * 74 * <li>If a receiver calls back to its tool during event handling, and if performance is an issue,
75 * then this should be through a final "callback" field in the instrument, and the called methods
76 * should be minimal.</li>
77 * <li>On the other hand, implementations should prevent Truffle from inlining beyond a reasonable
78 * point with the method annotation {@link TruffleBoundary}.</li>
79 * <li>The implicit "outer" pointer in a non-static inner class is a useful way to implement
80 * callbacks to owner tools.</li>
81 * <li>Primitive-valued return events are boxed for notification, but Truffle will eliminate the
82 * boxing if they are cast back to their primitive values quickly (in particular before crossing any
83 * {@link TruffleBoundary} annotations).
84 * </ul>
85 * <p>
86 * <h4>Allowing for AST cloning:</h4>
87 * <p>
88 * Truffle routinely <em>clones</em> ASTs, which has consequences for receiver implementation.
89 * <ul>
90 * <li>Even though a {@link Probe} is uniquely associated with a particular location in the
91 * executing Guest Language program, execution events at that location will in general be
92 * implemented by different {@link Node} instances, i.e. <em>clones</em> of the originally probed
93 * node.</li>
94 * <li>Because of <em>cloning</em> the {@link Node} supplied with notifications to a particular
95 * receiver will vary, but because they all represent the same GL program location the events should
96 * be treated as equivalent for most purposes.</li>
97 * </ul>
98 * <p>
99 * <h4>Access to execution state:</h4>
100 * <p>
101 * <ul>
102 * <li>Event notification arguments provide primary access to the GL program's execution states:
103 * <ul>
104 * <li>{@link Node}: the concrete node (in one of the AST's clones) from which the event originated.
105 * </li>
106 * <li>{@link VirtualFrame}: the current execution frame.
107 * </ul>
108 * <li>Some global information is available, for example the execution
109 * {@linkplain TruffleRuntime#iterateFrames(FrameInstanceVisitor) stack}.</li>
110 * <li>Additional information needed by a receiver could be stored when created, preferably
111 * {@code final} of course. For example, a reference to the {@link Probe} to which the receiver's
112 * Instrument has been attached would give access to its corresponding
113 * {@linkplain Probe#getProbedSourceSection() source location} or to the collection of
114 * {@linkplain SyntaxTag tags} currently applied to the Probe.</li>
115 * </ul>
116 * <p>
117 * <h4>Activating and deactivating Instruments:</h4>
118 * <p>
119 * Instruments are <em>single-use</em>:
120 * <ul>
121 * <li>An instrument becomes active only when <em>attached</em> to a Probe via
122 * {@link Probe#attach(Instrument)}, and it may only be attached to a single Probe. It is a runtime
123 * error to attempt attaching a previously attached instrument.</li>
124 * <li>Attaching an instrument modifies every existing clone of the AST to which it is being
125 * attached, which can trigger deoptimization.</li>
126 * <li>The method {@link Instrument#dispose()} makes an instrument inactive by removing it from the
127 * Probe to which it was attached and rendering it permanently inert.</li>
128 * <li>Disposal removes the implementation of an instrument from all ASTs to which it was attached,
129 * which can trigger deoptimization.</li>
130 * </ul>
131 * <p>
132 * <h4>Sharing receivers:</h4>
133 * <p>
134 * Although an Instrument may only be attached to a single Probe, a receiver can be shared among
135 * multiple Instruments. This can be useful for observing events that might happen at different
136 * locations in a single AST, for example all assignments to a particular variable. In this case a
137 * new Instrument would be created and attached at each assignment node, but all the Instruments
138 * would be created with the same receiver.
81 * <p> 139 * <p>
82 * <strong>Disclaimer:</strong> experimental; under development. 140 * <strong>Disclaimer:</strong> experimental; under development.
83 * 141 *
84 * @see Probe 142 * @see Probe
85 * @see ASTNodeProber 143 * @see TruffleEventReceiver
86 */ 144 */
87 public abstract class Instrument extends InstrumentationNode { 145 public final class Instrument {
88 146
89 protected Instrument() { 147 /**
90 } 148 * Creates an instrument that will route execution events to a receiver.
91 149 *
92 public void enter(Node astNode, VirtualFrame frame) { 150 * @param receiver a receiver for event generated by the instrument
93 } 151 * @param instrumentInfo optional description of the instrument's role
94 152 * @return a new instrument, ready for attachment at a probe
95 public void leave(Node astNode, VirtualFrame frame) { 153 */
96 } 154 public static Instrument create(TruffleEventReceiver receiver, String instrumentInfo) {
97 155 return new Instrument(receiver, instrumentInfo);
98 public void leave(Node astNode, VirtualFrame frame, boolean result) { 156 }
99 leave(astNode, frame, (Object) result); 157
100 } 158 /**
101 159 * Creates an instrument that will route execution events to a receiver.
102 public void leave(Node astNode, VirtualFrame frame, byte result) { 160 */
103 leave(astNode, frame, (Object) result); 161 public static Instrument create(TruffleEventReceiver receiver) {
104 } 162 return new Instrument(receiver, null);
105 163 }
106 public void leave(Node astNode, VirtualFrame frame, short result) { 164
107 leave(astNode, frame, (Object) result); 165 /**
108 } 166 * Tool-supplied receiver of events.
109 167 */
110 public void leave(Node astNode, VirtualFrame frame, int result) { 168 private final TruffleEventReceiver toolEventreceiver;
111 leave(astNode, frame, (Object) result); 169
112 } 170 /**
113 171 * Optional documentation, mainly for debugging.
114 public void leave(Node astNode, VirtualFrame frame, long result) { 172 */
115 leave(astNode, frame, (Object) result); 173 private final String instrumentInfo;
116 } 174
117 175 /**
118 public void leave(Node astNode, VirtualFrame frame, char result) { 176 * Has this instrument been disposed? stays true once set.
119 leave(astNode, frame, (Object) result); 177 */
120 } 178 private boolean isDisposed = false;
121 179
122 public void leave(Node astNode, VirtualFrame frame, float result) { 180 private Probe probe = null;
123 leave(astNode, frame, (Object) result); 181
124 } 182 private Instrument(TruffleEventReceiver receiver, String instrumentInfo) {
125 183 this.toolEventreceiver = receiver;
126 public void leave(Node astNode, VirtualFrame frame, double result) { 184 this.instrumentInfo = instrumentInfo;
127 leave(astNode, frame, (Object) result); 185 }
128 } 186
129 187 /**
130 public void leave(Node astNode, VirtualFrame frame, Object result) { 188 * Removes this instrument (and any clones) from the probe to which it attached and renders the
131 } 189 * instrument inert.
132 190 *
133 public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) { 191 * @throws IllegalStateException if this instrument has already been disposed
192 */
193 public void dispose() throws IllegalStateException {
194 if (isDisposed) {
195 throw new IllegalStateException("Attempt to dispose an already disposed Instrumennt");
196 }
197 if (probe != null) {
198 // It's attached
199 probe.disposeInstrument(this);
200 }
201 this.isDisposed = true;
202 }
203
204 Probe getProbe() {
205 return probe;
206 }
207
208 void setAttachedTo(Probe probe) {
209 this.probe = probe;
210 }
211
212 /**
213 * Has this instrument been disposed and rendered unusable?
214 */
215 boolean isDisposed() {
216 return isDisposed;
217 }
218
219 InstrumentNode addToChain(InstrumentNode nextNode) {
220 return new InstrumentNode(nextNode);
221 }
222
223 /**
224 * Removes this instrument from an instrument chain.
225 */
226 InstrumentNode removeFromChain(InstrumentNode instrumentNode) {
227 boolean found = false;
228 if (instrumentNode != null) {
229 if (instrumentNode.getInstrument() == this) {
230 // Found the match at the head of the chain
231 return instrumentNode.nextInstrument;
232 }
233 // Match not at the head of the chain; remove it.
234 found = instrumentNode.removeFromChain(Instrument.this);
235 }
236 if (!found) {
237 throw new IllegalStateException("Couldn't find instrument node to remove: " + this);
238 }
239 return instrumentNode;
240 }
241
242 @NodeInfo(cost = NodeCost.NONE)
243 final class InstrumentNode extends Node implements TruffleEventReceiver, InstrumentationNode {
244
245 @Child private InstrumentNode nextInstrument;
246
247 private InstrumentNode(InstrumentNode nextNode) {
248 this.nextInstrument = nextNode;
249 }
250
251 /**
252 * Gets the instrument that created this node.
253 */
254 private Instrument getInstrument() {
255 return Instrument.this;
256 }
257
258 /**
259 * Removes the node from this chain that was added by a particular instrument, assuming that
260 * the head of the chain is not the one to be replaced. This is awkward, but is required
261 * because {@link Node#replace(Node)} won't take a {@code null} argument. This doesn't work
262 * for the tail of the list, which would be replacing itself with null. So the replacement
263 * must be directed the parent of the node being removed.
264 */
265 private boolean removeFromChain(Instrument instrument) {
266 assert getInstrument() != instrument;
267 if (nextInstrument == null) {
268 return false;
269 }
270 if (nextInstrument.getInstrument() == instrument) {
271 // Next is the one to remove
272 if (nextInstrument.nextInstrument == null) {
273 // Next is at the tail; just forget
274 nextInstrument = null;
275 } else {
276 // Replace next with its successor
277 nextInstrument.replace(nextInstrument.nextInstrument);
278 }
279 return true;
280 }
281 return nextInstrument.removeFromChain(instrument);
282 }
283
284 public void enter(Node node, VirtualFrame frame) {
285 Instrument.this.toolEventreceiver.enter(node, frame);
286 if (nextInstrument != null) {
287 nextInstrument.enter(node, frame);
288 }
289 }
290
291 public void returnVoid(Node node, VirtualFrame frame) {
292 Instrument.this.toolEventreceiver.returnVoid(node, frame);
293 if (nextInstrument != null) {
294 nextInstrument.returnVoid(node, frame);
295 }
296 }
297
298 public void returnValue(Node node, VirtualFrame frame, Object result) {
299 Instrument.this.toolEventreceiver.returnValue(node, frame, result);
300 if (nextInstrument != null) {
301 nextInstrument.returnValue(node, frame, result);
302 }
303 }
304
305 public void returnExceptional(Node node, VirtualFrame frame, Exception exception) {
306 Instrument.this.toolEventreceiver.returnExceptional(node, frame, exception);
307 if (nextInstrument != null) {
308 nextInstrument.returnExceptional(node, frame, exception);
309 }
310 }
311
312 public String instrumentationInfo() {
313 if (Instrument.this.instrumentInfo != null) {
314 return Instrument.this.instrumentInfo;
315 }
316 return toolEventreceiver.getClass().getSimpleName();
317 }
134 } 318 }
135 319
136 } 320 }