com.oracle.truffle.api.instrumentation.InstrumentableNode Maven / Gradle / Ivy
Show all versions of truffle-api Show documentation
/*
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.api.instrumentation;
import java.util.Set;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.InstrumentableNode.WrapperNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.Node.Child;
import com.oracle.truffle.api.nodes.NodeInterface;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
/**
* Interface implemented by AST {@link Node nodes} that may be instrumentable: an AST
* location where {@linkplain com.oracle.truffle.api.instrumentation.TruffleInstrument 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 {@link #isInstrumentable()}. All
* instrumentable nodes must also extend {@link Node node}. All other member methods of this
* interface are only allowed to be invoked if {@link #isInstrumentable()} returns true
* .
*
* Every instrumentable node is required to create a wrapper for this instrumentable node in
* {@link #createWrapper(ProbeNode)}. The instrumentation framework will, when needed during
* execution, {@link Node#replace(Node) replace} the instrumentable node with a {@link WrapperNode
* 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 @{@link 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 {@link Tag
* tag}. Tags are used by guest languages to indicate that a {@link Node node} is a member of a
* certain category of nodes. For example a debugger
* {@link com.oracle.truffle.api.instrumentation.TruffleInstrument instrument} might require a guest
* language to tag all nodes as {@link StandardTags.StatementTag statements} that should be
* considered as such. See {@link #hasTag(Class)} for further details on how to use tags.
*
* Example minimal implementation of an instrumentable node:
*
* {@link com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.SimpleNode}
*
*
* Example for a typical implementation of an instrumentable node with support for source
* sections:
* {@link com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.RecommendedNode}
*
*
* @see #isInstrumentable() to decide whether node is instrumentable.
* @see #hasTag(Class) Implement hasTag to decide whether an instrumentable node is tagged with a
* tag.
* @see GenerateWrapper Use an annotation processor to generate the wrapper class.
* @see Instrumenter#attachExecutionEventListener(SourceSectionFilter, ExecutionEventListener)
* @since 0.33
*/
public interface InstrumentableNode extends NodeInterface {
/**
* Returns true
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
* {@link RootNode root node} was wrapped in a {@link CallTarget} using
* {@link RootNode#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
*/
boolean isInstrumentable();
/**
* Returns a new, never adopted, unshared {@link WrapperNode wrapper} node implementation for
* this {@link InstrumentableNode instrumentable} node. The returned wrapper implementation must
* extend the same type that implements {@link InstrumentableNode}.
*
* The instrumentation framework will, when needed during execution, {@link Node#replace(Node)
* replace} the instrumentable node with a {@link WrapperNode 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 @{@link GenerateWrapper}. Please note that
* if an instrumetnable node subclass has additional execute methods then a new wrapper must be
* generated or implemented. Otherwise the {@link Node#replace(Node) replacement} of the
* instrumentable node with the wrapper will fail if the subtype is used as static type in nodes
* {@link Child children}.
*
* A wrapper forwards the following events concerning the delegate to the given {@link ProbeNode
* probe} for propagation through the instrumentation framework, e.g. to
* {@linkplain ExecutionEventListener event listeners} bound to this guest language program
* location:
*
* - {@linkplain ProbeNode#onEnter(com.oracle.truffle.api.frame.VirtualFrame) onEnter(Frame)}:
* an execute method on the delegate is ready to be called;
* - {@linkplain ProbeNode#onReturnValue(com.oracle.truffle.api.frame.VirtualFrame, Object)
* onReturnValue(Frame,Object)}: an execute method on the delegate has just returned a
* (possibly
null
) value;
* - {@linkplain ProbeNode#onReturnExceptionalOrUnwind(VirtualFrame, Throwable, boolean)
* 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.
*
* @param probe the {@link ProbeNode probe node} to be adopted and sent execution events by the
* wrapper
* @return a {@link WrapperNode wrapper} implementation
* @since 0.33
*/
WrapperNode createWrapper(ProbeNode probe);
/**
* Returns true
if this node should be considered tagged by a given tag else
* false
. In order for a Truffle language to support a particular tag, the tag must
* also be marked as {@link ProvidedTags provided} by the language.
*
* Tags are used by guest languages to indicate that a {@link Node node} is a member of a
* certain category of nodes. For example a debugger {@link TruffleInstrument instrument} 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 {@link #hasTag(Class)} method. This
* example shows how to tag a node subclass and all its subclasses as statement:
*
* {@link com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.StatementNode}
*
*
* 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.
*
* {@link com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.HaltNode}
*
*
* The implementation of hasTag method must ensure that its result is stable after the parent
* {@link RootNode root node} was wrapped in a {@link CallTarget} using
* {@link RootNode#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.
*
* @param tag the class {@link com.oracle.truffle.api.instrumentation.ProvidedTags provided} by
* the {@link TruffleLanguage language}
* @return true
if the node should be considered tagged by a tag else
* false
.
* @since 0.33
*/
default boolean hasTag(Class extends Tag> tag) {
return false;
}
/**
* Returns an interop capable object that contains all keys and values of attributes associated
* with this node. The returned object must return true
in response to the
* {@link com.oracle.truffle.api.interop.InteropLibrary#hasMembers(Object) has members} message.
* If null
is returned then an empty tag object without any readable keys will be
* assumed. Multiple calls to {@link #getNodeObject()} 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 {@link #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 AST {@link Node node}. 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 {@link 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
* {@link Node#getLock() AST lock} is held while {@link #getNodeObject()} object is invoked.
* There is no lock held when the object is read.
*
* @return the node object as TruffleObject or null
if no node object properties
* are available for this instrumented node
* @since 0.33
*/
default Object getNodeObject() {
return null;
}
/**
* 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 {@link 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.
*
* In the example below, we show how the IncrementNode
with a
* ConstantNode
child is optimized into a ConstantIncrementNode
and
* how it can implement materializeSyntaxNodes
to restore the syntactic structure
* of the AST:
*
* {@link com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.ExpressionNode}
*
* @param materializedTags a set of tags that requested to be materialized
* @since 0.33
*/
default InstrumentableNode materializeInstrumentableNodes(Set> materializedTags) {
return this;
}
/**
* Find the nearest {@link Node node} 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 Truffle
* {@link Node} 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 as isTagged
) 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.
*
*
*
*
* @param sourceCharIndex the 0-based character index in this node's source, to find the nearest
* tagged node from
* @param tags a set of tags, the nearest node needs to be tagged with at least one tag from
* this set
* @return 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
*/
default Node findNearestNodeAt(int sourceCharIndex, Set> tags) {
return DefaultNearestNodeSearch.findNearestNodeAt(sourceCharIndex, (Node) this, tags);
}
/**
* Find the first {@link #isInstrumentable() instrumentable} 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.
*
* @param node a Node
* @return the first instrumentable node, or null
when no instrumentable parent
* exists.
* @since 20.3
*/
static Node findInstrumentableParent(Node node) {
Node inode = node;
while (inode != null && (inode instanceof WrapperNode || !(inode instanceof InstrumentableNode && ((InstrumentableNode) inode).isInstrumentable()))) {
inode = inode.getParent();
}
assert inode == null || inode instanceof InstrumentableNode && ((InstrumentableNode) inode).isInstrumentable() : inode;
assert !(inode instanceof WrapperNode) : inode;
return inode;
}
/**
* Nodes that the instrumentation framework inserts into guest language ASTs (between
* {@link InstrumentableNode instrumentable} guest language nodes and their parents) for the
* purpose of interposing on execution events and reporting them via the instrumentation
* framework.
*
* @see #createWrapper(ProbeNode)
* @since 0.33
*/
public interface WrapperNode extends NodeInterface {
/**
* The {@link InstrumentableNode instrumentable} guest language node, adopted as a child,
* whose execution events the wrapper reports to the instrumentation framework.
*
* This method might be called in parallel from multiple threads. The method may be invoked
* without a language context currently being active.
*
* @since 0.33
*/
Node getDelegateNode();
/**
* A child of the wrapper, through which the wrapper reports execution events related to the
* guest language delegate node.
*
* This method might be called in parallel from multiple threads. The method may be invoked
* without a language context currently being active.
*
* @since 0.33
*/
ProbeNode getProbeNode();
}
}
class InstrumentableNodeSnippets {
static class SimpleNodeWrapper implements WrapperNode {
@SuppressWarnings("unused")
SimpleNodeWrapper(SimpleNode delegate, ProbeNode probe) {
}
public Node getDelegateNode() {
return null;
}
public ProbeNode getProbeNode() {
return null;
}
}
// BEGIN: com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.SimpleNode
@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);
}
}
// END: com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.SimpleNode
static class RecommendedNodeWrapper implements WrapperNode {
@SuppressWarnings("unused")
RecommendedNodeWrapper(RecommendedNode delegate, ProbeNode probe) {
}
public Node getDelegateNode() {
return null;
}
public ProbeNode getProbeNode() {
return null;
}
}
// BEGIN: com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.RecommendedNode
@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);
}
}
// END: com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.RecommendedNode
abstract static class StatementNodeWrapper implements WrapperNode {
@SuppressWarnings("unused")
static StatementNodeWrapper create(StatementNode statementNode,
ProbeNode probe) {
return null;
}
}
// BEGIN: com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.StatementNode
@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;
}
}
// END: com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.StatementNode
private static final class Debugger {
static class HaltTag extends Tag {
}
}
@SuppressWarnings("unused")
static class HaltNodeWrapper implements WrapperNode {
HaltNodeWrapper(Node node, ProbeNode probe) {
}
public Node getDelegateNode() {
return null;
}
public ProbeNode getProbeNode() {
return null;
}
}
@SuppressWarnings("unused")
// BEGIN: com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.HaltNode
@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);
}
}
// END: com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.HaltNode
@SuppressWarnings("unused")
static class ExpressionNodeWrapper implements WrapperNode {
ExpressionNodeWrapper(Node node, ProbeNode probe) {
}
public Node getDelegateNode() {
return null;
}
public ProbeNode getProbeNode() {
return null;
}
}
// BEGIN: com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.ExpressionNode
@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> 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;
}
}
// END: com.oracle.truffle.api.instrumentation.InstrumentableNodeSnippets.ExpressionNode
}