comparison 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
comparison
equal deleted inserted replaced
18484:e97e1f07a3d6 18485:e3c95cbbb50c
1 /* 1 /*
2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 2 * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 * 4 *
5 * This code is free software; you can redistribute it and/or modify it 5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as 6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this 7 * published by the Free Software Foundation. Oracle designates this
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 java.lang.ref.*;
28 import java.util.*;
29
30 import com.oracle.truffle.api.*;
31 import com.oracle.truffle.api.instrument.ProbeNode.Instrumentable;
27 import com.oracle.truffle.api.nodes.*; 32 import com.oracle.truffle.api.nodes.*;
28 import com.oracle.truffle.api.source.*; 33 import com.oracle.truffle.api.source.*;
29 34 import com.oracle.truffle.api.utilities.*;
35
36 //TODO (mlvdv) migrate some of this to external documentation.
30 /** 37 /**
31 * A collector of {@link ExecutionEvents} at a specific site (node) in a Truffle AST (generated by a 38 * A binding between a particular location in the Truffle AST representation of a running Guest
32 * {@link Wrapper} inserted into the AST) for the purpose of <em>instrumentation</em>. For probes 39 * Language (GL) program (i.e. a {@link Node}) and a dynamically managed collection of "attached"
33 * associated with programmer-facing tools, there should be no more than one probe associated with a 40 * {@linkplain Instrument instrumentation} for use by external tools.
34 * particular piece of source code syntax (i.e. a {@link SourceSection}).
35 * <p> 41 * <p>
36 * Any {@linkplain SyntaxTag tags} associated with a particular piece of source code syntax are 42 * The effect of a binding is to intercept {@linkplain TruffleEventReceiver execution events} at the
37 * managed by the probe. 43 * node and notify each attached {@link Instrument} before execution is allowed to resume.
38 * <p> 44 * <p>
39 * When ASTs are copied, it is presumed that the probe for a site is shared by all AST nodes 45 * A Probe is "inserted" into a GL node via a call to {@link Instrumentable#probe()}; a GL node must
40 * representing that site. 46 * implement {@link Instrumentable} in order to support instrumentation. No more than one Probe can
47 * be inserted at a node.
41 * <p> 48 * <p>
42 * A probe holds zero or more {@link Instrument}s, which can be added and removed dynamically. 49 * The "probing" of a Truffle AST must be done after it is complete (i.e. with parent pointers
43 * Events reported to a probe are propagated to every attached instrument; the order is undefined. 50 * correctly assigned), but before any executions. This is done by creating an instance of
51 * {@link ASTProber} and registering it via {@link #registerASTProber(ASTProber)}, after which it
52 * will be automatically applied to newly created ASTs.
44 * <p> 53 * <p>
45 * Probe methods must be amenable to Truffle/Graal inlining on the assumption that the collection of 54 * Each Probe may also have assigned to it one or more {@link SyntaxTag}s, for example identifying a
46 * attached instruments seldom changes. The assumption is invalidated when instruments are added or 55 * node as a {@linkplain StandardSyntaxTag#STATEMENT STATEMENT}. Tags can be queried by tools to
47 * removed, but some instruments may change their internal state in such a way that the assumption 56 * configure behavior relevant to each probed node.
48 * should also be invalidated. 57 * <p>
58 * Instrumentation is implemented by modifying ASTs, both by inserting nodes into each AST at probed
59 * locations and by attaching additional nodes that implement dynamically attached instruments.
60 * Attached instrumentation code become, in effect, part of the GL program, and is subject to the
61 * same levels of optimization as other GL code. This implementation accounts properly for the fact
62 * that Truffle frequently <em>clones</em> ASTs, along with any attached instrumentation nodes. A
63 * Probe, along with attached Instruments, represents a <em>logical</em> binding with a source code
64 * location, producing event notifications that are (mostly) independent of which AST clone is
65 * executing.
49 * 66 *
50 * @see Instrument 67 * @see Instrument
51 * @see Wrapper 68 * @see Instrumentable
69 * @see ASTProber
52 */ 70 */
53 public interface Probe extends ExecutionEvents, SyntaxTagged, NodeInterface { 71 public final class Probe implements SyntaxTagged {
54 72
55 /** 73 /**
56 * Get the {@link SourceSection} in some Truffle AST associated with this probe. 74 * An observer of events related to {@link Probe}s: creating and tagging.
75 */
76 public interface ProbeListener {
77
78 /**
79 * Notifies that all registered {@link ASTProber}s are about to be applied to a newly
80 * constructed AST.
81 *
82 * @param source source code from which the AST was constructed
83 */
84 void startASTProbing(Source source);
85
86 /**
87 * Notifies that a {@link Probe} has been newly attached to an AST via
88 * {@link Instrumentable#probe()}.
89 * <p>
90 * There can be no more than one {@link Probe} at a node; this notification will only be
91 * delivered the first time {@linkplain Instrumentable#probe() probe()} is called at a
92 * particular AST node. There will also be no notification when the AST to which the Probe
93 * is attached is cloned.
94 */
95 void newProbeInserted(Probe probe);
96
97 /**
98 * Notifies that a {@link SyntaxTag} has been newly added to the set of tags associated with
99 * a {@link Probe} via {@link Probe#tagAs(SyntaxTag, Object)}.
100 * <p>
101 * The {@linkplain SyntaxTag tags} at a {@link Probe} are a <em>set</em>; this notification
102 * will only be delivered the first time a particular {@linkplain SyntaxTag tag} is added at
103 * a {@link Probe}.
104 * <p>
105 * An optional value supplied with {@linkplain Probe#tagAs(SyntaxTag, Object)
106 * tagAs(SyntaxTag, Object)} is reported to all listeners, but not stored. As a consequence,
107 * the optional value will have no effect at all if the tag had already been added.
108 *
109 * @param probe where a tag has been added
110 * @param tag the tag that has been newly added (subsequent additions of the tag are
111 * unreported).
112 * @param tagValue an optional value associated with the tag for the purposes of reporting.
113 */
114 void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue);
115
116 /**
117 * Notifies that the application of all registered {@link ASTProber}s to a newly constructed
118 * AST has completed.
119 *
120 * @param source source code from which the AST was constructed
121 */
122 void endASTProbing(Source source);
123
124 }
125
126 private static final List<ASTProber> astProbers = new ArrayList<>();
127
128 private static final List<ProbeListener> probeListeners = new ArrayList<>();
129
130 /**
131 * All Probes that have been created.
132 */
133 private static final List<WeakReference<Probe>> probes = new ArrayList<>();
134
135 private static final class FindSourceVisitor implements NodeVisitor {
136
137 Source source = null;
138
139 public boolean visit(Node node) {
140 final SourceSection sourceSection = node.getSourceSection();
141 if (sourceSection != null) {
142 source = sourceSection.getSource();
143 return false;
144 }
145 return true;
146 }
147 }
148
149 /**
150 * Walks an AST, looking for the first node with an assigned {@link SourceSection} and returning
151 * the {@link Source}.
152 */
153 private static Source findSource(Node node) {
154 final FindSourceVisitor visitor = new FindSourceVisitor();
155 node.accept(visitor);
156 return visitor.source;
157 }
158
159 /**
160 * The tag trap is a global setting; it only affects {@linkplain Probe probes} with the
161 * {@linkplain SyntaxTag tag} specified .
162 */
163 private static SyntaxTagTrap globalTagTrap = null;
164
165 /**
166 * Enables instrumentation at selected nodes in all subsequently constructed ASTs.
167 */
168 public static void registerASTProber(ASTProber prober) {
169 astProbers.add(prober);
170 }
171
172 public static void unregisterASTProber(ASTProber prober) {
173 astProbers.remove(prober);
174 }
175
176 /**
177 * Enables instrumentation in a newly created AST by applying all registered instances of
178 * {@link ASTProber}.
179 */
180 public static void applyASTProbers(Node node) {
181
182 final Source source = findSource(node);
183
184 for (ProbeListener listener : probeListeners) {
185 listener.startASTProbing(source);
186 }
187 for (ASTProber prober : astProbers) {
188 prober.probeAST(node);
189 }
190 for (ProbeListener listener : probeListeners) {
191 listener.endASTProbing(source);
192 }
193 }
194
195 /**
196 * Adds a {@link ProbeListener} to receive events.
197 */
198 public static void addProbeListener(ProbeListener listener) {
199 assert listener != null;
200 probeListeners.add(listener);
201 }
202
203 /**
204 * Removes a {@link ProbeListener}. Ignored if listener not found.
205 */
206 public static void removeProbeListener(ProbeListener listener) {
207 probeListeners.remove(listener);
208 }
209
210 /**
211 * Returns all {@link Probe}s holding a particular {@link SyntaxTag}, or the whole collection if
212 * the specified tag is {@code null}.
57 * 213 *
58 * @return The source associated with this probe. 214 * @return A collection of probes containing the given tag.
59 */ 215 */
60 SourceSection getSourceLocation(); 216 public static Collection<Probe> findProbesTaggedAs(SyntaxTag tag) {
61 217 final List<Probe> taggedProbes = new ArrayList<>();
62 /** 218 for (WeakReference<Probe> ref : probes) {
63 * Mark this probe as belonging to some tool-related category that can be used to guide tool 219 Probe probe = ref.get();
64 * behavior at an associated AST node. For example, a debugger might add the tag 220 if (probe != null) {
65 * {@link StandardSyntaxTag#STATEMENT} as a way of configuring where execution should stop when 221 if (tag == null || probe.isTaggedAs(tag)) {
66 * stepping. 222 taggedProbes.add(ref.get());
67 */ 223 }
68 void tagAs(SyntaxTag tag); 224 }
69 225 }
70 /** 226 return taggedProbes;
71 * Adds an {@link Instrument} to this probe's collection. No check is made to see if the same 227 }
72 * instrument has already been added. 228
229 /**
230 * Sets the current "tag trap". This causes a callback to be triggered whenever execution
231 * reaches a {@link Probe} (either existing or subsequently created) with the specified tag.
232 * There can only be one tag trap set at a time.
73 * 233 *
74 * @param newInstrument The instrument to add to this probe. 234 * @param newTagTrap The {@link SyntaxTagTrap} to set.
75 */ 235 * @throws IllegalStateException if a trap is currently set.
76 void addInstrument(Instrument newInstrument); 236 */
77 237 public static void setTagTrap(SyntaxTagTrap newTagTrap) throws IllegalStateException {
78 /** 238 assert newTagTrap != null;
79 * Removes the given instrument from the probe's collection. 239 if (globalTagTrap != null) {
240 throw new IllegalStateException("trap already set");
241 }
242 globalTagTrap = newTagTrap;
243
244 final SyntaxTag newTag = newTagTrap.getTag();
245 for (WeakReference<Probe> ref : probes) {
246 final Probe probe = ref.get();
247 if (probe != null && probe.tags.contains(newTag)) {
248 probe.trapActive = true;
249 probe.probeStateUnchanged.invalidate();
250 }
251 }
252 }
253
254 /**
255 * Clears the current {@link SyntaxTagTrap}.
80 * 256 *
81 * @param oldInstrument The instrument to remove from this probe. 257 * @throws IllegalStateException if no trap is currently set.
82 * @throws RuntimeException if no matching instrument has been attached. 258 */
83 */ 259 public static void clearTagTrap() {
84 void removeInstrument(Instrument oldInstrument) throws RuntimeException; 260 if (globalTagTrap == null) {
85 261 throw new IllegalStateException("no trap set");
262 }
263 globalTagTrap = null;
264
265 for (WeakReference<Probe> ref : probes) {
266 final Probe probe = ref.get();
267 if (probe != null && probe.trapActive) {
268 probe.trapActive = false;
269 probe.probeStateUnchanged.invalidate();
270 }
271 }
272 }
273
274 private final SourceSection sourceSection;
275 private final ArrayList<SyntaxTag> tags = new ArrayList<>();
276 private final List<WeakReference<ProbeNode>> probeNodeClones = new ArrayList<>();
277 private final CyclicAssumption probeStateUnchanged = new CyclicAssumption("Probe state unchanged");
278
279 /**
280 * {@code true} iff the global trap is set and this probe has the matching tag.
281 */
282 private boolean trapActive = false;
283
284 /**
285 * @see Instrumentable#probe()
286 */
287 Probe(ProbeNode probeNode, SourceSection sourceSection) {
288 this.sourceSection = sourceSection;
289 probes.add(new WeakReference<>(this));
290 registerProbeNodeClone(probeNode);
291 for (ProbeListener listener : probeListeners) {
292 listener.newProbeInserted(this);
293 }
294 }
295
296 public boolean isTaggedAs(SyntaxTag tag) {
297 assert tag != null;
298 return tags.contains(tag);
299 }
300
301 public Collection<SyntaxTag> getSyntaxTags() {
302 return Collections.unmodifiableCollection(tags);
303 }
304
305 /**
306 * Adds a {@linkplain SyntaxTag tag} to the set of tags associated with this {@link Probe};
307 * {@code no-op} if already in the set.
308 */
309 public void tagAs(SyntaxTag tag, Object tagValue) {
310 assert tag != null;
311 if (!tags.contains(tag)) {
312 tags.add(tag);
313 for (ProbeListener listener : probeListeners) {
314 listener.probeTaggedAs(this, tag, tagValue);
315 }
316 if (globalTagTrap != null && tag == globalTagTrap.getTag()) {
317 this.trapActive = true;
318 }
319 probeStateUnchanged.invalidate();
320 }
321 }
322
323 /**
324 * Adds instrumentation at this Probe.
325 *
326 * @param instrument an instrument not yet attached to a probe
327 * @throws IllegalStateException if the instrument has ever been attached before
328 */
329 public void attach(Instrument instrument) throws IllegalStateException {
330 if (instrument.isDisposed()) {
331 throw new IllegalStateException("Attempt to attach disposed instrument");
332 }
333 if (instrument.getProbe() != null) {
334 throw new IllegalStateException("Attampt to attach an already attached instrument");
335 }
336 instrument.setAttachedTo(this);
337 for (WeakReference<ProbeNode> ref : probeNodeClones) {
338 final ProbeNode probeNode = ref.get();
339 if (probeNode != null) {
340 probeNode.addInstrument(instrument);
341 }
342 }
343 probeStateUnchanged.invalidate();
344 }
345
346 /**
347 * Gets the {@link SourceSection} associated with the Guest Language AST node being
348 * instrumented, possibly {@code null}.
349 */
350 public SourceSection getProbedSourceSection() {
351 return sourceSection;
352 }
353
354 public String getShortDescription() {
355 final String location = sourceSection == null ? "<unknown>" : sourceSection.getShortDescription();
356 return "Probe@" + location + getTagsDescription();
357 }
358
359 /**
360 * Receives notification that a new clone of the instrument chain associated with this
361 * {@link Probe} has been created as a side-effect of AST cloning.
362 */
363 void registerProbeNodeClone(ProbeNode probeNode) {
364 probeNodeClones.add(new WeakReference<>(probeNode));
365 }
366
367 /**
368 * Gets the currently active {@linkplain SyntaxTagTrap tagTrap}; {@code null} if not set.
369 */
370 SyntaxTagTrap getTrap() {
371 return trapActive ? globalTagTrap : null;
372 }
373
374 /**
375 * Gets the {@link Assumption} that the instrumentation-related state of this {@link Probe} has
376 * not changed since this method was last called.
377 */
378 Assumption getUnchangedAssumption() {
379 return probeStateUnchanged.getAssumption();
380 }
381
382 /**
383 * Internal method for removing and rendering inert a specific instrument previously attached at
384 * this Probe.
385 *
386 * @param instrument an instrument already attached
387 * @throws IllegalStateException if instrument not attached at this Probe
388 * @see Instrument#dispose()
389 */
390 void disposeInstrument(Instrument instrument) throws IllegalStateException {
391 for (WeakReference<ProbeNode> ref : probeNodeClones) {
392 final ProbeNode probeNode = ref.get();
393 if (probeNode != null) {
394 probeNode.removeInstrument(instrument);
395 }
396 }
397 probeStateUnchanged.invalidate();
398 }
399
400 private String getTagsDescription() {
401 final StringBuilder sb = new StringBuilder();
402 sb.append("[");
403 String prefix = "";
404 for (SyntaxTag tag : tags) {
405 sb.append(prefix);
406 prefix = ",";
407 sb.append(tag.toString());
408 }
409 sb.append("]");
410 return sb.toString();
411 }
86 } 412 }