Interface InstrumentableNode
- All Superinterfaces:
NodeInterface
nodes
that may be instrumentable: an AST
location where Truffle
instruments are permitted to listen to before and after using execution event listeners.
If a node is instrumentable depends on the return value of isInstrumentable()
. All
instrumentable nodes must also extend node
. All other member methods of this
interface are only allowed to be invoked if isInstrumentable()
returns true
.
Every instrumentable node is required to create a wrapper for this instrumentable node in
createWrapper(ProbeNode)
. The instrumentation framework will, when needed during
execution, replace
the instrumentable node with a wrapper
and delegate to the original node. After the replacement of an instrumentable node with
a wrapper we refer to the original node as an instrumented node.
Wrappers can be generated automatically using an annotation processor by annotating the class
with @GenerateWrapper
. If an instrumentable node subclass has additional declared methods
than its instrumentable base class that are used by other nodes, then a new wrapper should be
generated or implemented for the subclass, otherwise the replacement of the wrapper will fail.
Instrumentable nodes may return true
to indicate that they were tagged by tag
. Tags are used by guest languages to indicate that a node
is a member of a
certain category of nodes. For example a debugger
instrument
might require a guest
language to tag all nodes as statements
that should be
considered as such. See hasTag(Class)
for further details on how to use tags.
Example minimal implementation of an instrumentable node:
@GenerateWrapper
abstract static class SimpleNode extends Node
implements InstrumentableNode {
public abstract Object execute(VirtualFrame frame);
public boolean isInstrumentable() {
return true;
}
public WrapperNode createWrapper(ProbeNode probe) {
// ASTNodeWrapper is generated by @GenerateWrapper
return new SimpleNodeWrapper(this, probe);
}
}
Example for a typical implementation of an instrumentable node with support for source sections:
@GenerateWrapper
abstract static class RecommendedNode extends Node
implements InstrumentableNode {
private static final int NO_SOURCE = -1;
private int sourceCharIndex = NO_SOURCE;
private int sourceLength;
public abstract Object execute(VirtualFrame frame);
// invoked by the parser to set the source
void setSourceSection(int charIndex, int length) {
assert sourceCharIndex == NO_SOURCE : "source should only be set once";
this.sourceCharIndex = charIndex;
this.sourceLength = length;
}
public final boolean isInstrumentable() {
// all AST nodes with source are instrumentable
return sourceCharIndex != NO_SOURCE;
}
@Override
@TruffleBoundary
public final SourceSection getSourceSection() {
if (sourceCharIndex == NO_SOURCE) {
// AST node without source
return null;
}
RootNode rootNode = getRootNode();
if (rootNode == null) {
// not yet adopted yet
return null;
}
Source source = rootNode.getSourceSection().getSource();
return source.createSection(sourceCharIndex, sourceLength);
}
public WrapperNode createWrapper(ProbeNode probe) {
// ASTNodeWrapper is generated by @GenerateWrapper
return new RecommendedNodeWrapper(this, probe);
}
}
- Since:
- 0.33
- See Also:
-
Nested Class Summary
Modifier and TypeInterfaceDescriptionstatic interface
Nodes that the instrumentation framework inserts into guest language ASTs (betweeninstrumentable
guest language nodes and their parents) for the purpose of interposing on execution events and reporting them via the instrumentation framework. -
Method Summary
Modifier and TypeMethodDescriptioncreateWrapper
(ProbeNode probe) Returns a new, never adopted, unsharedwrapper
node implementation for thisinstrumentable
node.static Node
findInstrumentableParent
(Node node) Find the firstinstrumentable
node on it's parent chain.default Node
findNearestNodeAt
(int line, int column, Set<Class<? extends Tag>> tags) Find the nearestnode
to the given source line and column position, according to the guest language control flow, that is tagged with some of the given tags.default Node
findNearestNodeAt
(int sourceCharIndex, Set<Class<? extends Tag>> tags) Find the nearestnode
to the given source character index according to the guest language control flow, that is tagged with some of the given tags.default Object
Returns an interop capable object that contains all keys and values of attributes associated with this node.default boolean
Returnstrue
if this node should be considered tagged by a given tag elsefalse
.boolean
Returnstrue
if this node is instrumentable.default InstrumentableNode
materializeInstrumentableNodes
(Set<Class<? extends Tag>> materializedTags) Removes optimizations performed in this AST node to restore the syntactic AST structure.
-
Method Details
-
isInstrumentable
boolean isInstrumentable()Returnstrue
if this node is instrumentable. Instrumentable nodes are points where instrumenters can attach execution events. The return values of instrumentable nodes must always be interop capable values.The implementation of this method must ensure that its result is stable after the parent
root node
was wrapped in aCallTarget
usingRootNode.getCallTarget()
. The result is stable if the result of calling this method remains always the same.This method might be called in parallel from multiple threads even if the language is single threaded. The method may be invoked without a language context currently being active.
- Since:
- 0.33
-
createWrapper
Returns a new, never adopted, unsharedwrapper
node implementation for thisinstrumentable
node. The returned wrapper implementation must extend the same type that implementsInstrumentableNode
.The instrumentation framework will, when needed during execution,
replace
the instrumentable node with awrapper
and delegate to the original node. After the replacement of an instrumentable node with a wrapper we refer to the original node as an instrumented node. Wrappers can be generated automatically using an annotation processor by annotating the class with @GenerateWrapper
. Please note that if an instrumetnable node subclass has additional execute methods then a new wrapper must be generated or implemented. Otherwise thereplacement
of the instrumentable node with the wrapper will fail if the subtype is used as static type in nodeschildren
.A wrapper forwards the following events concerning the delegate to the given
probe
for propagation through the instrumentation framework, e.g. to event listeners bound to this guest language program location:- onEnter(Frame): an execute method on the delegate is ready to be called;
- onReturnValue(Frame,Object): an execute method on the delegate has just returned a
(possibly
null
) value; - onReturnExceptionalOrUnwind(Frame,Throwable, boolean): an execute method on the delegate has just thrown an exception.
This method is always invoked on an interpreter thread. The method may be invoked without a language context currently being active.
- Parameters:
probe
- theprobe node
to be adopted and sent execution events by the wrapper- Returns:
- a
wrapper
implementation - Since:
- 0.33
-
hasTag
Returnstrue
if this node should be considered tagged by a given tag elsefalse
. In order for a Truffle language to support a particular tag, the tag must also be marked asprovided
by the language.Tags are used by guest languages to indicate that a
node
is a member of a certain category of nodes. For example a debuggerinstrument
might require a guest language to tag all nodes as statements that should be considered as such.The node implementor may decide how to implement tagging for nodes. The simplest way to implement tagging using Java types is by overriding the
hasTag(Class)
method. This example shows how to tag a node subclass and all its subclasses as statement:@GenerateWrapper abstract static class StatementNode extends SimpleNode implements InstrumentableNode { @Override public final Object execute(VirtualFrame frame) { executeVoid(frame); return null; } public abstract void executeVoid(VirtualFrame frame); @Override public final WrapperNode createWrapper(ProbeNode probe) { return StatementNodeWrapper.create(this, probe); } public boolean hasTag(Class<? extends Tag> tag) { if (tag == StandardTags.StatementTag.class) { return true; } return false; } }
Often it is impossible to just rely on the node's Java type to implement tagging. This example shows how to use local state to implement tagging for a node.
@GenerateWrapper static class HaltNode extends Node implements InstrumentableNode { private boolean isDebuggerHalt; public void setDebuggerHalt(boolean isDebuggerHalt) { this.isDebuggerHalt = isDebuggerHalt; } public Object execute(VirtualFrame frame) { // does nothing; return null; } public boolean isInstrumentable() { return true; } public boolean hasTag(Class<? extends Tag> tag) { if (tag == Debugger.HaltTag.class) { return isDebuggerHalt; } return false; } public WrapperNode createWrapper(ProbeNode probe) { return new HaltNodeWrapper(this, probe); } }
The implementation of hasTag method must ensure that its result is stable after the parent
root node
was wrapped in aCallTarget
usingRootNode.getCallTarget()
. The result is stable if the result of calling this method for a particular tag remains always the same.This method might be called in parallel from multiple threads even if the language is single threaded. The method may be invoked without a language context currently being active.
-
getNodeObject
Returns an interop capable object that contains all keys and values of attributes associated with this node. The returned object must returntrue
in response to thehas members
message. Ifnull
is returned then an empty tag object without any readable keys will be assumed. Multiple calls togetNodeObject()
for a particular node may return the same or objects with different identity. The returned object must not support any write operation. The returned object must not support execution or instantiation and must not have a size.For performance reasons it is not recommended to eagerly collect all properties of the node object when
getNodeObject()
is invoked. Instead, the language should lazily compute them when they are read. If the node object contains dynamic properties, that change during the execution of the AST, then the node must return an updated value for each key when it is read repeatedly. In other words the node object must always represent the current state of this ASTnode
. The implementer should not cache the node instance in the AST. The instrumentation framework will take care of caching node object instances when they are requested by tools.Compatibility: In addition to the expected keys by the tag specification, the language implementation may provide any set of additional keys and values. Tools might depend on these language specific tags and might break if keys or values are changed without notice.
For a memory efficient implementation the language might make the instrumentable
Node
a TruffleObject and return this instance.This method might be called in parallel from multiple threads even if the language is single threaded. The method may be invoked without a language context currently being active. The
AST lock
is held whilegetNodeObject()
object is invoked. There is no lock held when the object is read.- Returns:
- the node object as TruffleObject or
null
if no node object properties are available for this instrumented node - Since:
- 0.33
-
materializeInstrumentableNodes
default InstrumentableNode materializeInstrumentableNodes(Set<Class<? extends Tag>> materializedTags) Removes optimizations performed in this AST node to restore the syntactic AST structure. Guest languages may decide to group multiple nodes together into a single node. This is useful to reduce the memory consumed by the AST representation and it can also improve the execution performance when interpreting the AST. Performing such optimizations often modify the syntactic AST structure, leading to invalid execution events reported to the instrumentation framework. Implementing this method allows the instrumented node to restore the syntactic AST structure when needed. It provides a list of tags that were requested by all current execution event bindings to allow the language to do the materialization selectively for instrumentable nodes with certain tags only.The returned instrumentable nodes must return themselves when this method is called on them with the same tags. Materialized nodes should not be re-materialized again. Instrumentation relies on the stability of materialized nodes. Use
Node.notifyInserted(Node)
when you need to change the structure of instrumentable nodes.Node must return itself from this method when it has already seen all the materializedTags specified as an argument, i.e., not only if the set of tags is exactly the same as before, but also if the current set of tags is completely contained in the union of all the sets of tags specified in all the calls of this method that led to creation of this materialized node.
If the node returns a new node from this method, the subtree rooted at the new node must be completely unadopted, i.e., all nodes it contains must not have existed in the original AST. Also, the new subtree must be completely materialized, so that no new materializations occur when the instrumentation framework instruments the new subtree during the current traversal.
The AST lock is acquired while this method is invoked. Therefore it is not allowed to run guest language code while this method is invoked. This method might be called in parallel from multiple threads even if the language is single threaded. The method may be invoked without a language context currently being active. Language reference is always available.
In the example below, we show how the
IncrementNode
with aConstantNode
child is optimized into aConstantIncrementNode
and how it can implementmaterializeSyntaxNodes
to restore the syntactic structure of the AST:@GenerateWrapper abstract static class ExpressionNode extends Node implements InstrumentableNode { abstract int execute(VirtualFrame frame); public boolean isInstrumentable() { return true; } public boolean hasTag(Class<? extends Tag> tag) { return tag == StandardTags.ExpressionTag.class; } public WrapperNode createWrapper(ProbeNode probe) { return new ExpressionNodeWrapper(this, probe); } } class ConstantNode extends ExpressionNode { private final int constant; ConstantNode(int constant) { this.constant = constant; } @Override int execute(VirtualFrame frame) { return constant; } } // node with constant folded operation class ConstantIncrementNode extends ExpressionNode { final int constantIncremented; ConstantIncrementNode(int constant) { this.constantIncremented = constant + 1; } // desguar to restore syntactic structure of the AST public InstrumentableNode materializeInstrumentableNodes( Set<Class<? extends Tag>> tags) { if (tags.contains(StandardTags.ExpressionTag.class)) { return new IncrementNode( new ConstantNode(constantIncremented - 1)); } return this; } @Override int execute(VirtualFrame frame) { return constantIncremented; } } // node with full semantics of the node. class IncrementNode extends ExpressionNode { @Child ExpressionNode child; IncrementNode(ExpressionNode child) { this.child = child; } @Override int execute(VirtualFrame frame) { return child.execute(frame) + 1; } }
- Parameters:
materializedTags
- a set of tags that requested to be materialized- Since:
- 0.33
-
findNearestNodeAt
Find the nearestnode
to the given source character index according to the guest language control flow, that is tagged with some of the given tags. The source character index is in this node's source. The nearest node will preferably be in the same block/function as the character index. This node acts as a context node - either a node containing the character index if such node exists, or node following the character index if exists, or node preceding the character index otherwise.Return an instrumentable node that is tagged with some of the tags and containing the character index, if such exists and there is not a more suitable sibling node inside the container source section. Return the next sibling tagged node otherwise, or the previous one when the next one does not exist.
Use Case
The current use-case of this method is a relocation of breakpoint position, for instance. When a user submits a breakpoint at the source character index, a nearest logical instrumentable node that has suitable tags needs to be found to move the breakpoint accordingly.Default Implementation
This method has a default implementation, which assumes that the materialized TruffleNode
hierarchy corresponds with the logical guest language AST structure. If this is not the case for a particular guest language, this method needs to be implemented, possibly with the help of language specific AST node classes.The default algorithm is following:
- If the character index is smaller than the start index of this node's source section, return the first tagged child of this node.
- If the character index is larger than the end index of this node's source section, return the last tagged child of this node.
- Otherwise, this node's source section contains the character index. Use following steps
to find the nearest tagged node in this node's hierarchy:
- Traverse the node children in declaration order (AST breadth-first order). For every
child do:
- When the child is not instrumentable, include its children into the traversal.
- When the child does not have a source section assigned, ignore it.
- When the
sourceCharIndex
is inside the child's source section, find if it's tagged with one of the tags (store asisTagged
) and repeat recursively from 3.a. using this child as the node. - When the child is above the character index, remember a sorted list of such children up
to the lowest tagged child (store in
higherNodes
list). - When the child is below the character index, remember a sorted list of such children down
to the highest tagged child (store in
lowerNodes
list).
- If a tagged child node was found in 3.a with source section matching the
sourceCharIndex
, return it. - Otherwise, we check the list of lower/higher nodes:
- Prefer the node after the character index.
- Traverse
higherNodes
in ascending order. When the node is tagged, return it, when not, repeat with that node from 3.a. - If no tagged node was found, traverse
lowerNodes
in descending order. When the node is tagged, return it, when not, repeat with that node from 3.a. - When nothing was found in the steps above, return
null
.
- If c. didn't provide a tagged node, apply this algorithm recursively to a parent of this node, if exists. If you encounter the nearest tagged parent node found in 3.a, return it. Otherwise, return a tagged child found in the steps above, if any.
- Traverse the node children in declaration order (AST breadth-first order). For every
child do:
- Parameters:
sourceCharIndex
- the 0-based character index in this node's source, to find the nearest tagged node fromtags
- a set of tags, the nearest node needs to be tagged with at least one tag from this set- Returns:
- the nearest instrumentable node according to the execution flow and tagged with some
of the tags, or
null
when none was found - Since:
- 0.33
- See Also:
-
findNearestNodeAt
Find the nearestnode
to the given source line and column position, according to the guest language control flow, that is tagged with some of the given tags.Behaves in the same way as
findNearestNodeAt(int, Set)
but uses line/column as the position specification instead of a character index.- Parameters:
line
- 1-based line numbercolumn
- 1-based column number, or less than one when the column is unknowntags
- a set of tags, the nearest node needs to be tagged with at least one tag from this set- Returns:
- the nearest instrumentable node according to the execution flow and tagged with some
of the tags, or
null
when none was found - Since:
- 23.0
- See Also:
-
findInstrumentableParent
Find the firstinstrumentable
node on it's parent chain. If the provided node is instrumentable itself, it is returned. If not, the first parent node that is instrumentable is returned, if any.- Parameters:
node
- a Node- Returns:
- the first instrumentable node, or
null
when no instrumentable parent exists. - Since:
- 20.3
-