Mercurial > hg > graal-jvmci-8
comparison graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/InstrumentationTest.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 | 5d16da2ca0c8 |
children |
comparison
equal
deleted
inserted
replaced
18484:e97e1f07a3d6 | 18485:e3c95cbbb50c |
---|---|
20 * or visit www.oracle.com if you need additional information or have any | 20 * or visit www.oracle.com if you need additional information or have any |
21 * questions. | 21 * questions. |
22 */ | 22 */ |
23 package com.oracle.truffle.api.test; | 23 package com.oracle.truffle.api.test; |
24 | 24 |
25 import java.lang.reflect.*; | |
25 import java.util.*; | 26 import java.util.*; |
26 | 27 |
27 import org.junit.*; | 28 import org.junit.*; |
28 | 29 |
29 import com.oracle.truffle.api.*; | 30 import com.oracle.truffle.api.*; |
30 import com.oracle.truffle.api.frame.*; | 31 import com.oracle.truffle.api.frame.*; |
31 import com.oracle.truffle.api.instrument.*; | 32 import com.oracle.truffle.api.instrument.*; |
33 import com.oracle.truffle.api.instrument.Probe.ProbeListener; | |
34 import com.oracle.truffle.api.instrument.ProbeNode.Instrumentable; | |
35 import com.oracle.truffle.api.instrument.ProbeNode.WrapperNode; | |
36 import com.oracle.truffle.api.instrument.impl.*; | |
32 import com.oracle.truffle.api.nodes.*; | 37 import com.oracle.truffle.api.nodes.*; |
33 import com.oracle.truffle.api.source.*; | 38 import com.oracle.truffle.api.source.*; |
34 | 39 |
35 /** | 40 /** |
36 * <h3>AST Instrumentation</h3> | 41 * <h3>AST Instrumentation</h3> |
37 * | 42 * |
38 * Instrumentation allows the insertion into Truffle ASTs language-specific instances of | 43 * Instrumentation allows the insertion into Truffle ASTs language-specific instances of |
39 * {@link Wrapper} that propagate {@link ExecutionEvents} through a {@link Probe} to any instances | 44 * {@link WrapperNode} that propagate execution events through a {@link Probe} to any instances of |
40 * of {@link Instrument} that might be attached to the particular probe by tools. | 45 * {@link Instrument} that might be attached to the particular probe by tools. |
41 * <ol> | 46 * <ol> |
42 * <li>Creates a simple add AST</li> | 47 * <li>Creates a simple add AST</li> |
43 * <li>Verifies its structure</li> | 48 * <li>Verifies its structure</li> |
44 * <li>"Probes" the add node by adding a {@link Wrapper} and associated {@link Probe}</li> | 49 * <li>"Probes" the add node by adding a {@link WrapperNode} and associated {@link Probe}</li> |
45 * <li>Attaches a simple {@link Instrument} to the node via its {@link Probe}</li> | 50 * <li>Attaches a simple {@link Instrument} to the node via the Probe's {@link ProbeNode}</li> |
46 * <li>Verifies the structure of the probed AST</li> | 51 * <li>Verifies the structure of the probed AST</li> |
47 * <li>Verifies the execution of the probed AST</li> | 52 * <li>Verifies the execution of the probed AST</li> |
48 * <li>Verifies the results observed by the instrument.</li> | 53 * <li>Verifies the results observed by the instrument.</li> |
49 * </ol> | 54 * </ol> |
50 * To do these tests, several required classes have been implemented in their most basic form, only | 55 * To do these tests, several required classes have been implemented in their most basic form, only |
51 * implementing the methods necessary for the tests to pass, with stubs elsewhere. | 56 * implementing the methods necessary for the tests to pass, with stubs elsewhere. |
52 */ | 57 */ |
53 public class InstrumentationTest { | 58 public class InstrumentationTest { |
54 | 59 |
60 private static final SyntaxTag ADD_TAG = new SyntaxTag() { | |
61 | |
62 public String name() { | |
63 return "Addition"; | |
64 } | |
65 | |
66 public String getDescription() { | |
67 return "Test Language Addition Node"; | |
68 } | |
69 }; | |
70 | |
71 private static final SyntaxTag VALUE_TAG = new SyntaxTag() { | |
72 | |
73 public String name() { | |
74 return "Value"; | |
75 } | |
76 | |
77 public String getDescription() { | |
78 return "Test Language Value Node"; | |
79 } | |
80 }; | |
81 | |
55 @Test | 82 @Test |
56 public void test() { | 83 public void testBasicInstrumentation() { |
57 // Build a tree | 84 // Create a simple addition AST |
58 TruffleRuntime runtime = Truffle.getRuntime(); | 85 final TruffleRuntime runtime = Truffle.getRuntime(); |
59 TestChildNode leftChild = new TestChildNode(); | 86 final TestValueNode leftValueNode = new TestValueNode(6); |
60 TestChildNode rightChild = new TestChildNode(); | 87 final TestValueNode rightValueNode = new TestValueNode(7); |
61 TestSourceSection sourceSection = new TestSourceSection(); | 88 final TestAdditionNode addNode = new TestAdditionNode(leftValueNode, rightValueNode); |
62 TestAddNode addNode = new TestAddNode(leftChild, rightChild, sourceSection); | 89 final TestRootNode rootNode = new TestRootNode(addNode); |
63 TestRootNode rootNode = new TestRootNode(addNode); | 90 |
64 | 91 // Creating a call target sets the parent pointers in this tree and is necessary prior to |
65 // Have to create a call target before checking parent/child relationships | 92 // checking any parent/child relationships |
66 CallTarget target = runtime.createCallTarget(rootNode); | 93 final CallTarget callTarget1 = runtime.createCallTarget(rootNode); |
67 | 94 |
68 // Check tree structure | 95 // Check the tree structure |
69 Assert.assertEquals(addNode, leftChild.getParent()); | 96 Assert.assertEquals(addNode, leftValueNode.getParent()); |
70 Assert.assertEquals(addNode, rightChild.getParent()); | 97 Assert.assertEquals(addNode, rightValueNode.getParent()); |
71 Iterator<Node> iterator = addNode.getChildren().iterator(); | 98 Iterator<Node> iterator = addNode.getChildren().iterator(); |
72 Assert.assertEquals(leftChild, iterator.next()); | 99 Assert.assertEquals(leftValueNode, iterator.next()); |
73 Assert.assertEquals(rightChild, iterator.next()); | 100 Assert.assertEquals(rightValueNode, iterator.next()); |
74 Assert.assertFalse(iterator.hasNext()); | 101 Assert.assertFalse(iterator.hasNext()); |
75 Assert.assertEquals(rootNode, addNode.getParent()); | 102 Assert.assertEquals(rootNode, addNode.getParent()); |
76 iterator = rootNode.getChildren().iterator(); | 103 iterator = rootNode.getChildren().iterator(); |
77 Assert.assertEquals(addNode, iterator.next()); | 104 Assert.assertEquals(addNode, iterator.next()); |
78 Assert.assertFalse(iterator.hasNext()); | 105 Assert.assertFalse(iterator.hasNext()); |
79 Object result = target.call(); | 106 |
80 Assert.assertEquals(42, result); | 107 // Ensure it executes correctly |
81 | 108 Assert.assertEquals(13, callTarget1.call()); |
82 // Create another call target, this time with the "probed" add node | 109 |
83 TestExecutionContext context = new TestExecutionContext(); | 110 // Probe the addition node |
84 TestWrapper wrapper = new TestWrapper(addNode, context); | 111 final Probe probe = addNode.probe(); |
85 rootNode = new TestRootNode(wrapper); | 112 |
86 target = runtime.createCallTarget(rootNode); | 113 // Check the modified tree structure |
87 | 114 Assert.assertEquals(addNode, leftValueNode.getParent()); |
88 // Check the new tree structure | 115 Assert.assertEquals(addNode, rightValueNode.getParent()); |
89 Assert.assertEquals(addNode, leftChild.getParent()); | |
90 Assert.assertEquals(addNode, rightChild.getParent()); | |
91 iterator = addNode.getChildren().iterator(); | 116 iterator = addNode.getChildren().iterator(); |
92 Assert.assertEquals(leftChild, iterator.next()); | 117 Assert.assertEquals(leftValueNode, iterator.next()); |
93 Assert.assertEquals(rightChild, iterator.next()); | 118 Assert.assertEquals(rightValueNode, iterator.next()); |
94 Assert.assertFalse(iterator.hasNext()); | 119 Assert.assertFalse(iterator.hasNext()); |
95 Assert.assertEquals(wrapper, addNode.getParent()); | 120 |
96 iterator = wrapper.getChildren().iterator(); | 121 // Ensure there's a WrapperNode correctly inserted into the AST |
122 iterator = rootNode.getChildren().iterator(); | |
123 Node wrapperNode = iterator.next(); | |
124 Assert.assertTrue(wrapperNode instanceof TestLanguageWrapperNode); | |
125 Assert.assertFalse(iterator.hasNext()); | |
126 Assert.assertEquals(rootNode, wrapperNode.getParent()); | |
127 | |
128 // Check that the WrapperNode has both the probe and the wrapped node as children | |
129 iterator = wrapperNode.getChildren().iterator(); | |
130 Assert.assertEquals(addNode, iterator.next()); | |
131 ProbeNode probeNode = (ProbeNode) iterator.next(); | |
132 Assert.assertTrue(probeNode.getProbe() != null); | |
133 Assert.assertFalse(iterator.hasNext()); | |
134 | |
135 // Check that you can't probe the WrapperNodes | |
136 TestLanguageWrapperNode wrapper = (TestLanguageWrapperNode) wrapperNode; | |
137 try { | |
138 wrapper.probe(); | |
139 Assert.fail(); | |
140 } catch (IllegalStateException e) { | |
141 } | |
142 | |
143 // Check that the "probed" AST still executes correctly | |
144 Assert.assertEquals(13, callTarget1.call()); | |
145 | |
146 // Attach a counting instrument to the probe | |
147 final TestCounter counterA = new TestCounter(); | |
148 counterA.attach(probe); | |
149 | |
150 // Attach a second counting instrument to the probe | |
151 final TestCounter counterB = new TestCounter(); | |
152 counterB.attach(probe); | |
153 | |
154 // Run it again and check that the two instruments are working | |
155 Assert.assertEquals(13, callTarget1.call()); | |
156 Assert.assertEquals(counterA.enterCount, 1); | |
157 Assert.assertEquals(counterA.leaveCount, 1); | |
158 Assert.assertEquals(counterB.enterCount, 1); | |
159 Assert.assertEquals(counterB.leaveCount, 1); | |
160 | |
161 // Remove counterA and check the "instrument chain" | |
162 counterA.dispose(); | |
163 iterator = probeNode.getChildren().iterator(); | |
164 | |
165 // Run it again and check that instrument B is still working but not A | |
166 Assert.assertEquals(13, callTarget1.call()); | |
167 Assert.assertEquals(counterA.enterCount, 1); | |
168 Assert.assertEquals(counterA.leaveCount, 1); | |
169 Assert.assertEquals(counterB.enterCount, 2); | |
170 Assert.assertEquals(counterB.leaveCount, 2); | |
171 | |
172 // Simulate a split by cloning the AST | |
173 final CallTarget callTarget2 = runtime.createCallTarget((TestRootNode) rootNode.copy()); | |
174 // Run the clone and check that instrument B is still working but not A | |
175 Assert.assertEquals(13, callTarget2.call()); | |
176 Assert.assertEquals(counterA.enterCount, 1); | |
177 Assert.assertEquals(counterA.leaveCount, 1); | |
178 Assert.assertEquals(counterB.enterCount, 3); | |
179 Assert.assertEquals(counterB.leaveCount, 3); | |
180 | |
181 // Run the original and check that instrument B is still working but not A | |
182 Assert.assertEquals(13, callTarget2.call()); | |
183 Assert.assertEquals(counterA.enterCount, 1); | |
184 Assert.assertEquals(counterA.leaveCount, 1); | |
185 Assert.assertEquals(counterB.enterCount, 4); | |
186 Assert.assertEquals(counterB.leaveCount, 4); | |
187 | |
188 // Attach a second instrument to the probe | |
189 final TestCounter counterC = new TestCounter(); | |
190 counterC.attach(probe); | |
191 | |
192 // Run the original and check that instruments B,C working but not A | |
193 Assert.assertEquals(13, callTarget1.call()); | |
194 Assert.assertEquals(counterA.enterCount, 1); | |
195 Assert.assertEquals(counterA.leaveCount, 1); | |
196 Assert.assertEquals(counterB.enterCount, 5); | |
197 Assert.assertEquals(counterB.leaveCount, 5); | |
198 Assert.assertEquals(counterC.enterCount, 1); | |
199 Assert.assertEquals(counterC.leaveCount, 1); | |
200 | |
201 // Run the clone and check that instruments B,C working but not A | |
202 Assert.assertEquals(13, callTarget2.call()); | |
203 Assert.assertEquals(counterA.enterCount, 1); | |
204 Assert.assertEquals(counterA.leaveCount, 1); | |
205 Assert.assertEquals(counterB.enterCount, 6); | |
206 Assert.assertEquals(counterB.leaveCount, 6); | |
207 Assert.assertEquals(counterC.enterCount, 2); | |
208 Assert.assertEquals(counterC.leaveCount, 2); | |
209 | |
210 // Remove instrumentC | |
211 counterC.dispose(); | |
212 | |
213 // Run the original and check that instrument B working but not A,C | |
214 Assert.assertEquals(13, callTarget1.call()); | |
215 Assert.assertEquals(counterA.enterCount, 1); | |
216 Assert.assertEquals(counterA.leaveCount, 1); | |
217 Assert.assertEquals(counterB.enterCount, 7); | |
218 Assert.assertEquals(counterB.leaveCount, 7); | |
219 Assert.assertEquals(counterC.enterCount, 2); | |
220 Assert.assertEquals(counterC.leaveCount, 2); | |
221 | |
222 // Run the clone and check that instrument B working but not A,C | |
223 Assert.assertEquals(13, callTarget2.call()); | |
224 Assert.assertEquals(counterA.enterCount, 1); | |
225 Assert.assertEquals(counterA.leaveCount, 1); | |
226 Assert.assertEquals(counterB.enterCount, 8); | |
227 Assert.assertEquals(counterB.leaveCount, 8); | |
228 Assert.assertEquals(counterC.enterCount, 2); | |
229 Assert.assertEquals(counterC.leaveCount, 2); | |
230 | |
231 // Remove instrumentB | |
232 counterB.dispose(); | |
233 | |
234 // Run both the original and clone, check that no instruments working | |
235 Assert.assertEquals(13, callTarget1.call()); | |
236 Assert.assertEquals(13, callTarget2.call()); | |
237 Assert.assertEquals(counterA.enterCount, 1); | |
238 Assert.assertEquals(counterA.leaveCount, 1); | |
239 Assert.assertEquals(counterB.enterCount, 8); | |
240 Assert.assertEquals(counterB.leaveCount, 8); | |
241 Assert.assertEquals(counterC.enterCount, 2); | |
242 Assert.assertEquals(counterC.leaveCount, 2); | |
243 } | |
244 | |
245 @Test | |
246 public void testTagging() { | |
247 | |
248 // Applies appropriate tags | |
249 final TestASTProber astProber = new TestASTProber(); | |
250 Probe.registerASTProber(astProber); | |
251 | |
252 // Listens for probes and tags being added | |
253 final TestProbeListener probeListener = new TestProbeListener(); | |
254 Probe.addProbeListener(probeListener); | |
255 | |
256 // Counts all entries to all instances of addition nodes | |
257 final TestMultiCounter additionCounter = new TestMultiCounter(); | |
258 | |
259 // Counts all entries to all instances of value nodes | |
260 final TestMultiCounter valueCounter = new TestMultiCounter(); | |
261 | |
262 // Create a simple addition AST | |
263 final TruffleRuntime runtime = Truffle.getRuntime(); | |
264 final TestValueNode leftValueNode = new TestValueNode(6); | |
265 final TestValueNode rightValueNode = new TestValueNode(7); | |
266 final TestAdditionNode addNode = new TestAdditionNode(leftValueNode, rightValueNode); | |
267 | |
268 final TestRootNode rootNode = new TestRootNode(addNode); | |
269 | |
270 final CallTarget callTarget = runtime.createCallTarget(rootNode); | |
271 | |
272 // Check that the prober added probes to the tree | |
273 Assert.assertEquals(probeListener.probeCount, 3); | |
274 Assert.assertEquals(probeListener.tagCount, 3); | |
275 | |
276 Assert.assertEquals(Probe.findProbesTaggedAs(ADD_TAG).size(), 1); | |
277 Assert.assertEquals(Probe.findProbesTaggedAs(VALUE_TAG).size(), 2); | |
278 | |
279 // Check that it executes correctly | |
280 Assert.assertEquals(13, callTarget.call()); | |
281 | |
282 // Dynamically attach a counter for all executions of all Addition nodes | |
283 for (Probe probe : Probe.findProbesTaggedAs(ADD_TAG)) { | |
284 additionCounter.attachCounter(probe); | |
285 } | |
286 // Dynamically attach a counter for all executions of all Value nodes | |
287 for (Probe probe : Probe.findProbesTaggedAs(VALUE_TAG)) { | |
288 valueCounter.attachCounter(probe); | |
289 } | |
290 | |
291 // Counters initialized at 0 | |
292 Assert.assertEquals(additionCounter.count, 0); | |
293 Assert.assertEquals(valueCounter.count, 0); | |
294 | |
295 // Execute again | |
296 Assert.assertEquals(13, callTarget.call()); | |
297 | |
298 // There are two value nodes in the AST, but only one addition node | |
299 Assert.assertEquals(additionCounter.count, 1); | |
300 Assert.assertEquals(valueCounter.count, 2); | |
301 | |
302 Probe.unregisterASTProber(astProber); | |
303 | |
304 } | |
305 | |
306 @Test | |
307 public void testProbeLite() { | |
308 | |
309 // Use the "lite-probing" option, limited to a single pass of | |
310 // probing and a single Instrument at each probed node. This | |
311 // particular test uses a shared event receiver at every | |
312 // lite-probed node. | |
313 final TestEventReceiver receiver = new TestEventReceiver(); | |
314 | |
315 TestASTLiteProber astLiteProber = new TestASTLiteProber(receiver); | |
316 Probe.registerASTProber(astLiteProber); | |
317 | |
318 // Create a simple addition AST | |
319 final TruffleRuntime runtime = Truffle.getRuntime(); | |
320 final TestValueNode leftValueNode = new TestValueNode(6); | |
321 final TestValueNode rightValueNode = new TestValueNode(7); | |
322 final TestAdditionNode addNode = new TestAdditionNode(leftValueNode, rightValueNode); | |
323 final TestRootNode rootNode = new TestRootNode(addNode); | |
324 | |
325 // Creating a call target sets the parent pointers in this tree and is necessary prior to | |
326 // checking any parent/child relationships | |
327 final CallTarget callTarget = runtime.createCallTarget(rootNode); | |
328 | |
329 // Check that the instrument is working as expected. | |
330 Assert.assertEquals(0, receiver.counter); | |
331 callTarget.call(); | |
332 Assert.assertEquals(2, receiver.counter); | |
333 | |
334 // Check that you can't probe a node that's already received a probeLite() call | |
335 try { | |
336 leftValueNode.probe(); | |
337 Assert.fail(); | |
338 } catch (IllegalStateException e) { | |
339 } | |
340 | |
341 try { | |
342 rightValueNode.probe(); | |
343 Assert.fail(); | |
344 } catch (IllegalStateException e) { | |
345 } | |
346 | |
347 // Check tree structure | |
348 Assert.assertTrue(leftValueNode.getParent() instanceof TestLanguageWrapperNode); | |
349 Assert.assertTrue(rightValueNode.getParent() instanceof TestLanguageWrapperNode); | |
350 TestLanguageWrapperNode leftWrapper = (TestLanguageWrapperNode) leftValueNode.getParent(); | |
351 TestLanguageWrapperNode rightWrapper = (TestLanguageWrapperNode) rightValueNode.getParent(); | |
352 Assert.assertEquals(addNode, leftWrapper.getParent()); | |
353 Assert.assertEquals(addNode, rightWrapper.getParent()); | |
354 Iterator<Node> iterator = addNode.getChildren().iterator(); | |
355 Assert.assertEquals(leftWrapper, iterator.next()); | |
356 Assert.assertEquals(rightWrapper, iterator.next()); | |
357 Assert.assertFalse(iterator.hasNext()); | |
358 Assert.assertEquals(rootNode, addNode.getParent()); | |
359 iterator = rootNode.getChildren().iterator(); | |
97 Assert.assertEquals(addNode, iterator.next()); | 360 Assert.assertEquals(addNode, iterator.next()); |
98 Assert.assertFalse(iterator.hasNext()); | 361 Assert.assertFalse(iterator.hasNext()); |
99 Assert.assertEquals(rootNode, wrapper.getParent()); | 362 |
100 iterator = rootNode.getChildren().iterator(); | 363 // Check that you can't get a probe on the wrappers because they were "lite-probed" |
101 Assert.assertEquals(wrapper, iterator.next()); | 364 try { |
102 Assert.assertFalse(iterator.hasNext()); | 365 leftWrapper.getProbe(); |
103 result = target.call(); | 366 Assert.fail(); |
104 Assert.assertEquals(42, result); | 367 } catch (IllegalStateException e) { |
105 | 368 } |
106 // Create some instruments | 369 try { |
107 final TestInstrument instrumentA = new TestInstrument(); | 370 rightWrapper.getProbe(); |
108 final TestInstrument instrumentB = new TestInstrument(); | 371 Assert.fail(); |
109 | 372 } catch (IllegalStateException e) { |
110 wrapper.getProbe().addInstrument(instrumentA); | 373 } |
111 | 374 |
112 result = target.call(); | 375 // Check that you can't probe the wrappers |
113 Assert.assertEquals(instrumentA.numInstrumentEnter, 1); | 376 try { |
114 Assert.assertEquals(instrumentA.numInstrumentLeave, 1); | 377 leftWrapper.probe(); |
115 Assert.assertEquals(instrumentB.numInstrumentEnter, 0); | 378 Assert.fail(); |
116 Assert.assertEquals(instrumentB.numInstrumentLeave, 0); | 379 } catch (IllegalStateException e) { |
117 Assert.assertEquals(42, result); | 380 } |
118 | 381 try { |
119 wrapper.getProbe().addInstrument(instrumentB); | 382 rightWrapper.probe(); |
120 | 383 Assert.fail(); |
121 result = target.call(); | 384 } catch (IllegalStateException e) { |
122 Assert.assertEquals(instrumentA.numInstrumentEnter, 2); | 385 } |
123 Assert.assertEquals(instrumentA.numInstrumentLeave, 2); | 386 try { |
124 Assert.assertEquals(instrumentB.numInstrumentEnter, 1); | 387 leftWrapper.probeLite(null); |
125 Assert.assertEquals(instrumentB.numInstrumentLeave, 1); | 388 Assert.fail(); |
126 Assert.assertEquals(42, result); | 389 } catch (IllegalStateException e) { |
127 | 390 } |
128 wrapper.getProbe().removeInstrument(instrumentA); | 391 try { |
129 | 392 rightWrapper.probeLite(null); |
130 result = target.call(); | 393 Assert.fail(); |
131 Assert.assertEquals(instrumentA.numInstrumentEnter, 2); | 394 } catch (IllegalStateException e) { |
132 Assert.assertEquals(instrumentA.numInstrumentLeave, 2); | 395 } |
133 Assert.assertEquals(instrumentB.numInstrumentEnter, 2); | 396 |
134 Assert.assertEquals(instrumentB.numInstrumentLeave, 2); | 397 // Use reflection to check that each WrapperNode has a ProbeLiteNode with a |
135 Assert.assertEquals(42, result); | 398 // SimpleEventReceiver |
136 | 399 try { |
137 } | 400 Field probeNodeField = leftWrapper.getClass().getDeclaredField("probeNode"); |
138 | 401 |
139 private class TestRootNode extends RootNode { | 402 // cheat: probeNode is private, so we change it's accessibility at runtime |
140 @Child private RootNode child; | 403 probeNodeField.setAccessible(true); |
141 | 404 ProbeNode probeNode = (ProbeNode) probeNodeField.get(leftWrapper); |
142 public TestRootNode(RootNode child) { | 405 |
143 super(null); | 406 // hack: Since ProbeLiteNode is not visible, we do a string compare here |
407 Assert.assertTrue(probeNode.getClass().toString().endsWith("ProbeLiteNode")); | |
408 | |
409 // Now we do the same to check the type of the eventReceiver in ProbeLiteNode | |
410 Field eventReceiverField = probeNode.getClass().getDeclaredField("eventReceiver"); | |
411 eventReceiverField.setAccessible(true); | |
412 TruffleEventReceiver eventReceiver = (TruffleEventReceiver) eventReceiverField.get(probeNode); | |
413 Assert.assertTrue(eventReceiver instanceof SimpleEventReceiver); | |
414 | |
415 // Reset accessibility | |
416 probeNodeField.setAccessible(false); | |
417 eventReceiverField.setAccessible(false); | |
418 | |
419 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { | |
420 Assert.fail(); | |
421 } | |
422 | |
423 Probe.unregisterASTProber(astLiteProber); | |
424 | |
425 } | |
426 | |
427 /** | |
428 * A guest language usually has a single language-specific subclass of {@link Node} from which | |
429 * all other nodes in the guest language subclass. By making this node {@link Instrumentable}, | |
430 * we allow all nodes of the guest language to have {@link Probe}s attach to them. | |
431 * | |
432 */ | |
433 private abstract class TestLanguageNode extends Node implements Instrumentable { | |
434 public abstract Object execute(VirtualFrame frame); | |
435 | |
436 public Probe probe() { | |
437 Node parent = getParent(); | |
438 | |
439 if (parent == null) { | |
440 throw new IllegalStateException("Cannot call probe() on a node without a parent."); | |
441 } | |
442 | |
443 if (parent instanceof TestLanguageWrapperNode) { | |
444 return ((TestLanguageWrapperNode) parent).getProbe(); | |
445 } | |
446 | |
447 // Create a new wrapper/probe with this node as its child. | |
448 final TestLanguageWrapperNode wrapper = new TestLanguageWrapperNode(this); | |
449 | |
450 // Connect it to a Probe | |
451 final Probe probe = ProbeNode.insertProbe(wrapper); | |
452 | |
453 // Replace this node in the AST with the wrapper | |
454 this.replace(wrapper); | |
455 | |
456 return probe; | |
457 } | |
458 | |
459 public void probeLite(TruffleEventReceiver eventReceiver) { | |
460 Node parent = getParent(); | |
461 | |
462 if (parent == null) { | |
463 throw new IllegalStateException("Cannot call probeLite() on a node without a parent"); | |
464 } | |
465 | |
466 if (parent instanceof TestLanguageWrapperNode) { | |
467 throw new IllegalStateException("Cannot call probeLite() on a node that already has a wrapper."); | |
468 } | |
469 | |
470 final TestLanguageWrapperNode wrapper = new TestLanguageWrapperNode(this); | |
471 ProbeNode.insertProbeLite(wrapper, eventReceiver); | |
472 | |
473 this.replace(wrapper); | |
474 } | |
475 } | |
476 | |
477 /** | |
478 * The wrapper node class is usually language-specific and inherits from the language-specific | |
479 * subclass of {@link Node}, in this case, {@link TestLanguageNode}. | |
480 */ | |
481 @NodeInfo(cost = NodeCost.NONE) | |
482 private class TestLanguageWrapperNode extends TestLanguageNode implements WrapperNode { | |
483 @Child private TestLanguageNode child; | |
484 @Child private ProbeNode probeNode; | |
485 | |
486 public TestLanguageWrapperNode(TestLanguageNode child) { | |
487 assert !(child instanceof TestLanguageWrapperNode); | |
144 this.child = child; | 488 this.child = child; |
145 } | 489 } |
146 | 490 |
147 @Override | 491 public String instrumentationInfo() { |
148 public Object execute(VirtualFrame frame) { | 492 return "Wrapper node for testing"; |
149 return child.execute(frame); | 493 } |
150 } | 494 |
151 } | 495 public void insertProbe(ProbeNode newProbeNode) { |
152 | 496 this.probeNode = newProbeNode; |
153 private class TestAddNode extends RootNode { | 497 } |
154 | 498 |
155 @Child private TestChildNode left; | 499 public Probe getProbe() { |
156 @Child private TestChildNode right; | 500 try { |
157 | 501 return probeNode.getProbe(); |
158 public TestAddNode(TestChildNode left, TestChildNode right, TestSourceSection sourceSection) { | 502 } catch (IllegalStateException e) { |
159 super(sourceSection); | 503 throw new IllegalStateException("Cannot call getProbe() on a wrapper that has no probe"); |
160 this.left = left; | 504 } |
161 this.right = right; | 505 } |
162 } | 506 |
163 | 507 @Override |
164 @Override | |
165 public Object execute(VirtualFrame frame) { | |
166 return left.execute() + right.execute(); | |
167 } | |
168 } | |
169 | |
170 private class TestChildNode extends Node { | |
171 | |
172 public TestChildNode() { | |
173 super(null); | |
174 } | |
175 | |
176 public int execute() { | |
177 return 21; | |
178 } | |
179 } | |
180 | |
181 /** | |
182 * The wrapper node class is usually language-specific and inherits from the language-specific | |
183 * subclass of {@link Node}, not {@RootNode}. | |
184 */ | |
185 private class TestWrapper extends RootNode implements Wrapper { | |
186 @Child private RootNode child; | |
187 private Probe probe; | |
188 | |
189 public TestWrapper(RootNode child, ExecutionContext context) { | |
190 this.child = insert(child); | |
191 this.probe = context.createProbe(child.getSourceSection()); | |
192 } | |
193 | |
194 public Node getChild() { | 508 public Node getChild() { |
195 return child; | 509 return child; |
196 } | 510 } |
197 | 511 |
198 public Probe getProbe() { | |
199 return probe; | |
200 } | |
201 | |
202 @Override | 512 @Override |
203 public Object execute(VirtualFrame frame) { | 513 public Object execute(VirtualFrame frame) { |
204 probe.enter(child, frame); | 514 probeNode.enter(child, frame); |
205 Object result; | 515 Object result; |
206 | 516 |
207 try { | 517 try { |
208 result = child.execute(frame); | 518 result = child.execute(frame); |
209 probe.leave(child, frame, result); | 519 probeNode.returnValue(child, frame, result); |
520 } catch (KillException e) { | |
521 throw (e); | |
210 } catch (Exception e) { | 522 } catch (Exception e) { |
211 probe.leaveExceptional(child, frame, e); | 523 probeNode.returnExceptional(child, frame, e); |
212 throw (e); | 524 throw (e); |
213 } | 525 } |
526 | |
214 return result; | 527 return result; |
215 } | 528 } |
216 } | 529 |
217 | 530 @Override |
218 /** | 531 public Probe probe() { |
219 * An "empty" description of the source code that might correspond to a particular AST node. The | 532 throw new IllegalStateException("Cannot call probe() on a wrapper."); |
220 * instrumentation framework tracks probes that have been inserted by their source location, | 533 } |
221 * using this as a key. | 534 |
222 */ | 535 @Override |
223 private class TestSourceSection implements SourceSection { | 536 public void probeLite(TruffleEventReceiver eventReceiver) { |
224 | 537 throw new IllegalStateException("Cannot call probeLite() on a wrapper."); |
225 public Source getSource() { | 538 } |
226 return null; | 539 } |
227 } | 540 |
228 | 541 /** |
229 public int getStartLine() { | 542 * A simple node for our test language to store a value. |
230 return 0; | 543 */ |
231 } | 544 private class TestValueNode extends TestLanguageNode { |
232 | 545 private final int value; |
233 public LineLocation getLineLocation() { | 546 |
234 return null; | 547 public TestValueNode(int value) { |
235 } | 548 this.value = value; |
236 | 549 } |
237 public int getStartColumn() { | 550 |
238 return 0; | 551 @Override |
239 } | 552 public Object execute(VirtualFrame frame) { |
240 | 553 return new Integer(this.value); |
241 public int getCharIndex() { | 554 } |
242 return 0; | 555 } |
243 } | 556 |
244 | 557 /** |
245 public int getCharLength() { | 558 * A node for our test language that adds up two {@link TestValueNode}s. |
246 return 0; | 559 */ |
247 } | 560 private class TestAdditionNode extends TestLanguageNode { |
248 | 561 @Child private TestLanguageNode leftChild; |
249 public int getCharEndIndex() { | 562 @Child private TestLanguageNode rightChild; |
250 return 0; | 563 |
251 } | 564 public TestAdditionNode(TestValueNode leftChild, TestValueNode rightChild) { |
252 | 565 this.leftChild = insert(leftChild); |
253 public String getIdentifier() { | 566 this.rightChild = insert(rightChild); |
254 return null; | 567 } |
255 } | 568 |
256 | 569 @Override |
257 public String getCode() { | 570 public Object execute(VirtualFrame frame) { |
258 return null; | 571 return new Integer(((Integer) leftChild.execute(frame)).intValue() + ((Integer) rightChild.execute(frame)).intValue()); |
259 } | 572 } |
260 | 573 } |
261 public String getShortDescription() { | 574 |
262 return null; | 575 /** |
263 } | 576 * Truffle requires that all guest languages to have a {@link RootNode} which sits atop any AST |
264 | 577 * of the guest language. This is necessary since creating a {@link CallTarget} is how Truffle |
265 } | 578 * completes an AST. The root nodes serves as our entry point into a program. |
266 | 579 */ |
267 private class TestExecutionContext extends ExecutionContext { | 580 private class TestRootNode extends RootNode { |
268 | 581 @Child private TestLanguageNode body; |
269 @Override | 582 |
270 public String getLanguageShortName() { | 583 /** |
271 return "test"; | 584 * This constructor emulates the global machinery that applies registered probers to every |
272 } | 585 * newly created AST. Global registry is not used, since that would interfere with other |
273 | 586 * tests run in the same environment. |
274 @Override | 587 */ |
275 protected void setSourceCallback(SourceCallback sourceCallback) { | 588 public TestRootNode(TestLanguageNode body) { |
276 } | 589 super(null); |
277 | 590 this.body = body; |
278 } | 591 } |
279 | 592 |
280 private class TestInstrument extends Instrument { | 593 @Override |
281 | 594 public Object execute(VirtualFrame frame) { |
282 public int numInstrumentEnter = 0; | 595 return body.execute(frame); |
283 public int numInstrumentLeave = 0; | 596 } |
284 | 597 |
285 @Override | 598 @Override |
286 public void enter(Node astNode, VirtualFrame frame) { | 599 public boolean isCloningAllowed() { |
287 numInstrumentEnter++; | 600 return true; |
288 } | 601 } |
289 | 602 |
290 @Override | 603 @Override |
291 public void leave(Node astNode, VirtualFrame frame, Object result) { | 604 public void applyInstrumentation() { |
292 numInstrumentLeave++; | 605 Probe.applyASTProbers(body); |
293 } | 606 } |
607 } | |
608 | |
609 /** | |
610 * A counter for the number of times execution enters and leaves a probed AST node. | |
611 */ | |
612 private class TestCounter { | |
613 | |
614 public int enterCount = 0; | |
615 public int leaveCount = 0; | |
616 public final Instrument instrument; | |
617 | |
618 public TestCounter() { | |
619 instrument = Instrument.create(new SimpleEventReceiver() { | |
620 | |
621 @Override | |
622 public void enter(Node node, VirtualFrame frame) { | |
623 enterCount++; | |
624 } | |
625 | |
626 @Override | |
627 public void returnAny(Node node, VirtualFrame frame) { | |
628 leaveCount++; | |
629 } | |
630 }, "Instrumentation Test Counter"); | |
631 } | |
632 | |
633 public void attach(Probe probe) { | |
634 probe.attach(instrument); | |
635 } | |
636 | |
637 public void dispose() { | |
638 instrument.dispose(); | |
639 } | |
640 | |
641 } | |
642 | |
643 /** | |
644 * Tags selected nodes on newly constructed ASTs. | |
645 */ | |
646 private static final class TestASTProber implements NodeVisitor, ASTProber { | |
647 | |
648 public boolean visit(Node node) { | |
649 if (node instanceof TestLanguageNode) { | |
650 | |
651 final TestLanguageNode testNode = (TestLanguageNode) node; | |
652 | |
653 if (node instanceof TestValueNode) { | |
654 testNode.probe().tagAs(VALUE_TAG, null); | |
655 | |
656 } else if (node instanceof TestAdditionNode) { | |
657 testNode.probe().tagAs(ADD_TAG, null); | |
658 | |
659 } | |
660 } | |
661 return true; | |
662 } | |
663 | |
664 public void probeAST(Node node) { | |
665 node.accept(this); | |
666 } | |
667 } | |
668 | |
669 /** | |
670 * "lite-probes" every value node with a shared event receiver. | |
671 */ | |
672 private static final class TestASTLiteProber implements NodeVisitor, ASTProber { | |
673 private final TruffleEventReceiver eventReceiver; | |
674 | |
675 public TestASTLiteProber(SimpleEventReceiver simpleEventReceiver) { | |
676 this.eventReceiver = simpleEventReceiver; | |
677 } | |
678 | |
679 public boolean visit(Node node) { | |
680 if (node instanceof TestValueNode) { | |
681 final TestLanguageNode testNode = (TestValueNode) node; | |
682 testNode.probeLite(eventReceiver); | |
683 } | |
684 return true; | |
685 } | |
686 | |
687 public void probeAST(Node node) { | |
688 node.accept(this); | |
689 } | |
690 } | |
691 | |
692 /** | |
693 * Counts the number of "enter" events at probed nodes. | |
694 * | |
695 */ | |
696 static final class TestEventReceiver extends SimpleEventReceiver { | |
697 | |
698 public int counter = 0; | |
699 | |
700 @Override | |
701 public void enter(Node node, VirtualFrame frame) { | |
702 counter++; | |
703 } | |
704 | |
705 } | |
706 | |
707 /** | |
708 * A counter that can count executions at multiple nodes; it attaches a separate instrument at | |
709 * each Probe, but keeps a total count. | |
710 */ | |
711 private static final class TestMultiCounter { | |
712 | |
713 public int count = 0; | |
714 | |
715 public void attachCounter(Probe probe) { | |
716 | |
717 // Attach a new instrument for every Probe | |
718 // where we want to count executions. | |
719 // it will get copied when ASTs cloned, so | |
720 // keep the count in this outer class. | |
721 probe.attach(Instrument.create(new SimpleEventReceiver() { | |
722 | |
723 @Override | |
724 public void enter(Node node, VirtualFrame frame) { | |
725 count++; | |
726 } | |
727 }, "Instrumentation Test MultiCounter")); | |
728 } | |
729 } | |
730 | |
731 private static final class TestProbeListener implements ProbeListener { | |
732 | |
733 public int probeCount = 0; | |
734 public int tagCount = 0; | |
735 | |
736 public void newProbeInserted(Probe probe) { | |
737 probeCount++; | |
738 } | |
739 | |
740 public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) { | |
741 tagCount++; | |
742 } | |
743 | |
744 public void startASTProbing(Source source) { | |
745 } | |
746 | |
747 public void endASTProbing(Source source) { | |
748 } | |
749 | |
294 } | 750 } |
295 | 751 |
296 } | 752 } |