Skip to content

Commit d897c57

Browse files
committed
Refactor and document the coverage node and factory.
1 parent 7b71c1a commit d897c57

File tree

1 file changed

+87
-17
lines changed

1 file changed

+87
-17
lines changed

src/main/java/com/oracle/simpletool/SimpleCodeCoverageInstrument.java

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -245,27 +245,97 @@ public void onLoad(LoadSourceSectionEvent event) {
245245
}
246246
}
247247

248+
/**
249+
* A factory for nodes that track coverage
250+
*
251+
* Because we
252+
* {@link #enable(com.oracle.truffle.api.instrumentation.TruffleInstrument.Env) attached}
253+
* an instance of this factory, each time a AST node of interest is created,
254+
* it is instrumented with a node created by this factory.
255+
*/
248256
private class CoverageEventFactory implements ExecutionEventNodeFactory {
249-
257+
258+
/**
259+
* @param ec context of the event, used in our case to lookup the
260+
* {@link SourceSection} that our node is instrumenting.
261+
* @return An {@link ExecutionEventNode}
262+
*/
250263
public ExecutionEventNode create(final EventContext ec) {
251-
return new ExecutionEventNode() {
252-
@CompilationFinal
253-
private boolean visited;
264+
return new CoverageNode(ec.getInstrumentedSourceSection());
265+
}
266+
}
267+
268+
/**
269+
* Node that "wraps" AST nodes of interest (Nodes that correspond to
270+
* expressions in our case as defined by the filter given to the
271+
* {@link Instrumenter} in {@link #onCreate(com.oracle.truffle.api.instrumentation.TruffleInstrument.Env)
272+
* }), and removes the "wrapped" {@link SourceSection} from the set
273+
* {@link #sourceToUncoveredSections uncovered} {@link SourceSection}.
274+
*/
275+
class CoverageNode extends ExecutionEventNode {
254276

255-
@Override
256-
public void onReturnValue(VirtualFrame vFrame, Object result) {
257-
if (!visited) {
258-
CompilerDirectives.transferToInterpreterAndInvalidate();
259-
visited = true;
260-
final SourceSection sourceSection = ec.getInstrumentedSourceSection();
261-
final Source source = sourceSection.getSource();
262-
// TODO: This should not be necesery becuase of the filter. Bug!
263-
if (!source.isInternal()) {
264-
sourceToUncoveredSections.get(source).remove(sourceSection);
265-
}
266-
}
277+
@CompilationFinal
278+
private boolean covered;
279+
280+
/**
281+
* Each node knows which {@link SourceSection} it instruments.
282+
*/
283+
private final SourceSection instrumentedSourceSection;
284+
285+
private CoverageNode(SourceSection instrumentedSourceSection) {
286+
this.instrumentedSourceSection = instrumentedSourceSection;
287+
}
288+
289+
/**
290+
* The {@link ExecutionEventNode} class let's us define several events
291+
* that we can intercept. The one of interest to us is {@link ExecutionEventNode#onReturnValue(com.oracle.truffle.api.frame.VirtualFrame, java.lang.Object)
292+
* } as we wish to remove this nodes {@link #instrumentedSourceSection}
293+
* from the {@link #sourceToUncoveredSections set of uncovered nodes}
294+
* only once the node is successfully executed (as oppose to, for
295+
* example, {@link ExecutionEventNode#onReturnExceptional(com.oracle.truffle.api.frame.VirtualFrame, java.lang.Throwable)
296+
* }).
297+
*
298+
* Each node keeps a {@link #covered} flag so that the removal only
299+
* happens once. The fact that the flag is annotated with
300+
* {@link CompilationFinal} means that this flag will be treated as
301+
* {@code final} during compilation of instrumented source code (i.e.
302+
* the {@code false} branch of the if statement can be optimized away).
303+
*
304+
* The way it's used in this method is a pattern when writing Truffle
305+
* nodes:
306+
* <ul>
307+
* <li> If we are compiling a covered node, the if condition will
308+
* evaluate to false and the if-guarded code will be optimized away.
309+
* This means that once this {@link SourceSection} is confirmed to be
310+
* covered, there is no further instrumentation overhead on performance.
311+
* <li> If we are compiling a not-yet-covered node, the if condition
312+
* will evaluate to true, and the if-guarded code will be included for
313+
* compilation. The first statement in this block is a
314+
* {@link CompilerDirectives#transferToInterpreterAndInvalidate() directive to the compiler}
315+
* to make sure that if this point in the execution is reached, the
316+
* execution should return to the interpreter and the existing compiled
317+
* code is no longer valid (since once the covered flag is set to true,
318+
* the check is unnecessary). The code following the directive is thus
319+
* always executed in the interpreter: We set the {@link #covered} flag
320+
* to true, ensuring that the next compilation will have no
321+
* instrumentation overhead on performance.
322+
* </li>
323+
* </ul>
324+
*
325+
* @param vFrame unused
326+
* @param result unused
327+
*/
328+
@Override
329+
public void onReturnValue(VirtualFrame vFrame, Object result) {
330+
if (!covered) {
331+
CompilerDirectives.transferToInterpreterAndInvalidate();
332+
covered = true;
333+
final Source source = instrumentedSourceSection.getSource();
334+
// TODO: This should not be necesery becuase of the filter. Bug!
335+
if (!source.isInternal()) {
336+
sourceToUncoveredSections.get(source).remove(instrumentedSourceSection);
267337
}
268-
};
338+
}
269339
}
270340
}
271341

0 commit comments

Comments
 (0)