Mercurial > hg > truffle
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 } |