/*
 * Copyright (c) 2012, 2020, 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.nodes;

import static com.oracle.truffle.api.nodes.NodeAccessor.ENGINE;
import static com.oracle.truffle.api.nodes.NodeAccessor.INSTRUMENT;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.ReplaceObserver;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.ContextReference;
import com.oracle.truffle.api.TruffleLanguage.LanguageReference;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExecutableNode.ReferenceCache;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;

Abstract base class for all Truffle nodes.
Since:0.8 or earlier
/** * Abstract base class for all Truffle nodes. * * @since 0.8 or earlier */
public abstract class Node implements NodeInterface, Cloneable { @CompilationFinal private volatile Node parent;
Marks array fields that are children of this node. This annotation implies the semantics of @CompilationFinal(dimensions = 1).
Since:0.8 or earlier
/** * Marks array fields that are children of this node. * * This annotation implies the semantics of @{@link CompilationFinal}(dimensions = 1). * * @since 0.8 or earlier */
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface Children { }
Marks fields that represent child nodes of this node. This annotation implies the semantics of CompilationFinal.
Since:0.8 or earlier
/** * Marks fields that represent child nodes of this node. * * This annotation implies the semantics of {@link CompilationFinal}. * * @since 0.8 or earlier */
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface Child { }
Since:0.8 or earlier
/** @since 0.8 or earlier */
protected Node() { CompilerAsserts.neverPartOfCompilation("do not create a Node from compiled code"); assert NodeClass.get(getClass()) != null; // ensure NodeClass constructor does not throw } NodeClass getNodeClass() { return NodeClass.get(getClass()); } void setParent(Node parent) { this.parent = parent; }
Returns a rough estimate for the cost of this Node. This estimate can be used by runtime systems or guest languages to implement heuristics based on Truffle ASTs. This method is intended to be overridden by subclasses. The default implementation returns the value of NodeInfo.cost() of the NodeInfo annotation declared at the subclass. If no NodeInfo annotation is declared the method returns NodeCost.MONOMORPHIC as a default value.
Since:0.8 or earlier
/** * Returns a rough estimate for the cost of this {@link Node}. This estimate can be used by * runtime systems or guest languages to implement heuristics based on Truffle ASTs. This method * is intended to be overridden by subclasses. The default implementation returns the value of * {@link NodeInfo#cost()} of the {@link NodeInfo} annotation declared at the subclass. If no * {@link NodeInfo} annotation is declared the method returns {@link NodeCost#MONOMORPHIC} as a * default value. * * @since 0.8 or earlier */
public NodeCost getCost() { NodeInfo info = getClass().getAnnotation(NodeInfo.class); if (info != null) { return info.cost(); } return NodeCost.MONOMORPHIC; }
Retrieves the segment of guest language source code that is represented by this Node. The default implementation of this method returns null. If your node represents a segment of the source code, override this method and return a eagerly or lazily computed source section value. This method is not designed to be invoked on compiled code paths. May be called on any thread and without a language context being active.

Simple example implementation using a simple implementation using a field: SimpleNode

Recommended implementation computing the source section lazily from primitive fields: RecommendedNode

Returns:the source code represented by this Node
Since:0.8 or earlier
/** * Retrieves the segment of guest language source code that is represented by this Node. The * default implementation of this method returns <code>null</code>. If your node represents a * segment of the source code, override this method and return a eagerly or lazily computed * source section value. This method is not designed to be invoked on compiled code paths. May * be called on any thread and without a language context being active. * <p> * Simple example implementation using a simple implementation using a field: * {@link com.oracle.truffle.api.nodes.NodeSnippets.SimpleNode} * <p> * Recommended implementation computing the source section lazily from primitive fields: * {@link com.oracle.truffle.api.nodes.NodeSnippets.RecommendedNode} * * @return the source code represented by this Node * @since 0.8 or earlier */
public SourceSection getSourceSection() { return null; }
Retrieves the segment of guest language source code that is represented by this Node, if present; otherwise retrieves the segment represented by the nearest AST ancestor that has this information. Can be called on any thread and without a language context.
Returns:an approximation of the source code represented by this Node
Since:0.8 or earlier
/** * Retrieves the segment of guest language source code that is represented by this Node, if * present; otherwise retrieves the segment represented by the nearest AST ancestor that has * this information. Can be called on any thread and without a language context. * * @return an approximation of the source code represented by this Node * @since 0.8 or earlier */
@TruffleBoundary public SourceSection getEncapsulatingSourceSection() { Node current = this; while (current != null) { final SourceSection currentSection = current.getSourceSection(); if (currentSection != null) { return currentSection; } current = current.getParent(); } return null; }
Returns true if this node can be adopted by a parent. This method is intended to be overriden by subclasses. If nodes need to be statically shared that they must not be adoptable, because otherwise the parent reference might cause a memory leak. If a node is not adoptable then then it is guaranteed that the parent pointer remains null at all times, even if the node is tried to be adopted by a parent.

Implementations of isAdoptable() are required to fold to a constant result when compiled with a constant receiver.

Since:19.0
/** * Returns <code>true</code> if this node can be adopted by a parent. This method is intended to * be overriden by subclasses. If nodes need to be statically shared that they must not be * adoptable, because otherwise the parent reference might cause a memory leak. If a node is not * adoptable then then it is guaranteed that the {@link #getParent() parent} pointer remains * <code>null</code> at all times, even if the node is tried to be adopted by a parent. * <p> * Implementations of {@link #isAdoptable()} are required to fold to a constant result when * compiled with a constant receiver. * * @since 19.0 */
public boolean isAdoptable() { return true; }
Inserts new node children into an AST that was already adopted by a parent. The new children need to be assigned to its children field after insert was called.
Params:
  • newChildren – the array of new children whose parent should be updated
Returns:the array of new children
Since:0.8 or earlier
/** * Inserts new node children into an AST that was already {@link #adoptChildren() adopted} by a * {@link #getParent() parent}. The new children need to be assigned to its {@link Children * children} field after insert was called. * * @param newChildren the array of new children whose parent should be updated * @return the array of new children * @since 0.8 or earlier */
protected final <T extends Node> T[] insert(final T[] newChildren) { CompilerDirectives.transferToInterpreterAndInvalidate(); if (newChildren != null) { for (Node newChild : newChildren) { adoptHelper(newChild); } } return newChildren; }
Inserts an new node into an AST that was already adopted by a parent. The new child needs to be assigned to its child field after insert was called.
Params:
  • newChild – the new child whose parent should be updated
Returns:the new child
Since:0.8 or earlier
/** * Inserts an new node into an AST that was already {@link #adoptChildren() adopted} by a * {@link #getParent() parent}. The new child needs to be assigned to its {@link Child child} * field after insert was called. * * @param newChild the new child whose parent should be updated * @return the new child * @since 0.8 or earlier */
protected final <T extends Node> T insert(final T newChild) { CompilerDirectives.transferToInterpreterAndInvalidate(); if (newChild != null) { adoptHelper(newChild); } return newChild; }
Notifies the framework about the insertion of one or more nodes during execution. Otherwise, the framework assumes that instrumentable nodes remain unchanged after their root node is first executed. Insertions don't need to be notified if it is known that none of the inserted nodes are instrumentable.

The provided Node and its children must be adopted in the AST before invoking this method. The caller must ensure that this method is invoked only once for a given node and its children.

Example usage: NodeSnippets.notifyInserted

Params:
  • node – the node tree that got inserted.
Since:0.27
/** * Notifies the framework about the insertion of one or more nodes during execution. Otherwise, * the framework assumes that {@link com.oracle.truffle.api.instrumentation.InstrumentableNode * instrumentable} nodes remain unchanged after their root node is first * {@link RootNode#execute(com.oracle.truffle.api.frame.VirtualFrame) executed}. Insertions * don't need to be notified if it is known that none of the inserted nodes are * {@link com.oracle.truffle.api.instrumentation.InstrumentableNode instrumentable}. * <p> * The provided {@link Node} and its children must be {@link #adoptChildren() adopted} in the * AST before invoking this method. The caller must ensure that this method is invoked only once * for a given node and its children. * <p> * Example usage: {@link com.oracle.truffle.api.nodes.NodeSnippets#notifyInserted} * * @param node the node tree that got inserted. * @since 0.27 */
@SuppressWarnings("static-method") protected final void notifyInserted(Node node) { RootNode rootNode = node.getRootNode(); if (rootNode == null) { throw new IllegalStateException("Node is not yet adopted and cannot be updated."); } INSTRUMENT.onNodeInserted(rootNode, node); }
Since:0.8 or earlier
/** @since 0.8 or earlier */
public final void adoptChildren() { CompilerDirectives.transferToInterpreterAndInvalidate(); NodeUtil.adoptChildrenHelper(this); } final void adoptHelper(final Node newChild) { assert newChild != null; if (newChild == this) { throw new IllegalStateException("The parent of a node can never be the node itself."); } if (newChild.isAdoptable()) { assert checkSameLanguages(newChild); newChild.parent = this; NodeUtil.adoptChildrenHelper(newChild); } } int adoptChildrenAndCount() { CompilerAsserts.neverPartOfCompilation(); return 1 + NodeUtil.adoptChildrenAndCountHelper(this); } int adoptAndCountHelper(Node newChild) { assert newChild != null; if (newChild == this) { throw new IllegalStateException("The parent of a node can never be the node itself."); } int count = 1; if (newChild.isAdoptable()) { assert checkSameLanguages(newChild); newChild.parent = this; count += NodeUtil.adoptChildrenAndCountHelper(newChild); } return count; } private boolean checkSameLanguages(final Node newChild) { if (newChild instanceof ExecutableNode && !(newChild instanceof RootNode)) { RootNode root = getRootNode(); if (root == null) { throw new IllegalStateException("Cannot adopt ExecutableNode " + newChild + " as a child of node without a root."); } LanguageInfo pl = root.getLanguageInfo(); LanguageInfo cl = ((ExecutableNode) newChild).getLanguageInfo(); if (cl != pl) { throw new IllegalArgumentException("Can not adopt ExecutableNode under a different language." + " Parent " + this + " is of " + langId(pl) + ", child " + newChild + " is of " + langId(cl)); } } return true; } private static String langId(LanguageInfo languageInfo) { if (languageInfo == null) { return null; } else { return languageInfo.getId(); } } private void adoptUnadoptedHelper(final Node newChild) { assert isAdoptable(); assert newChild != null; if (newChild == this) { throw new IllegalStateException("The parent of a node can never be the node itself."); } newChild.parent = this; NodeUtil.forEachChild(newChild, new NodeVisitor() { public boolean visit(Node child) { if (child != null && child.getParent() == null) { newChild.adoptUnadoptedHelper(child); } return true; } }); }
Returns properties of this node interesting for debugging and can be overwritten by subclasses to add their own custom properties.
Returns:the properties as a key/value hash map
Since:0.8 or earlier
/** * Returns properties of this node interesting for debugging and can be overwritten by * subclasses to add their own custom properties. * * @return the properties as a key/value hash map * @since 0.8 or earlier */
public Map<String, Object> getDebugProperties() { Map<String, Object> properties = new HashMap<>(); return properties; }
The current parent node of this node.
Returns:the parent node
Since:0.8 or earlier
/** * The current parent node of this node. * * @return the parent node * @since 0.8 or earlier */
public final Node getParent() { return parent; }
Replaces this node with another node. If there is a source section (see getSourceSection()) associated with this node, it is transferred to the new node.
Params:
  • newNode – the new node that is the replacement
  • reason – a description of the reason for the replacement
Returns:the new node
Since:0.8 or earlier
/** * Replaces this node with another node. If there is a source section (see * {@link #getSourceSection()}) associated with this node, it is transferred to the new node. * * @param newNode the new node that is the replacement * @param reason a description of the reason for the replacement * @return the new node * @since 0.8 or earlier */
public final <T extends Node> T replace(final T newNode, final CharSequence reason) { CompilerDirectives.transferToInterpreterAndInvalidate(); atomic(new Runnable() { public void run() { replaceHelper(newNode, reason); } }); return newNode; }
Replaces this node with another node. If there is a source section (see getSourceSection()) associated with this node, it is transferred to the new node.
Params:
  • newNode – the new node that is the replacement
Returns:the new node
Since:0.8 or earlier
/** * Replaces this node with another node. If there is a source section (see * {@link #getSourceSection()}) associated with this node, it is transferred to the new node. * * @param newNode the new node that is the replacement * @return the new node * @since 0.8 or earlier */
public final <T extends Node> T replace(T newNode) { return replace(newNode, ""); } final void replaceHelper(Node newNode, CharSequence reason) { CompilerAsserts.neverPartOfCompilation("do not call Node.replaceHelper from compiled code"); assert inAtomicBlock(); if (this.getParent() == null) { throw new IllegalStateException("This node cannot be replaced, because it does not yet have a parent."); } // (aw) need to set parent *before* replace, so that (unsynchronized) getRootNode() // will always find the root node if (newNode.isAdoptable()) { newNode.parent = this.parent; } if (!NodeUtil.replaceChild(this.parent, this, newNode, true)) { this.parent.adoptUnadoptedHelper(newNode); } reportReplace(this, newNode, reason); onReplace(newNode, reason); }
Checks if this node can be replaced by another node: tree structure & type.
Since:0.8 or earlier
/** * Checks if this node can be replaced by another node: tree structure & type. * * @since 0.8 or earlier */
public final boolean isSafelyReplaceableBy(Node newNode) { return NodeUtil.isReplacementSafe(getParent(), this, newNode); } private void reportReplace(Node oldNode, Node newNode, CharSequence reason) { Node node = this; while (node != null) { boolean consumed = false; if (node instanceof ReplaceObserver) { consumed = ((ReplaceObserver) node).nodeReplaced(oldNode, newNode, reason); } else if (node instanceof RootNode) { CallTarget target = ((RootNode) node).getCallTarget(); if (target instanceof ReplaceObserver) { consumed = ((ReplaceObserver) target).nodeReplaced(oldNode, newNode, reason); } } if (consumed) { break; } node = node.getParent(); } if (TruffleOptions.TraceRewrites) { NodeUtil.traceRewrite(this, newNode, reason); } }
Intended to be implemented by subclasses of Node to receive a notification when the node is rewritten. This method is invoked before the actual replace has happened.
Params:
  • newNode – the replacement node
  • reason – the reason the replace supplied
Since:0.8 or earlier
/** * Intended to be implemented by subclasses of {@link Node} to receive a notification when the * node is rewritten. This method is invoked before the actual replace has happened. * * @param newNode the replacement node * @param reason the reason the replace supplied * @since 0.8 or earlier */
protected void onReplace(Node newNode, CharSequence reason) { // empty default }
Invokes the NodeVisitor.visit(Node) method for this node and recursively also for all child nodes.
Params:
  • nodeVisitor – the visitor
Since:0.8 or earlier
/** * Invokes the {@link NodeVisitor#visit(Node)} method for this node and recursively also for all * child nodes. * * @param nodeVisitor the visitor * @since 0.8 or earlier */
public final void accept(NodeVisitor nodeVisitor) { if (nodeVisitor.visit(this)) { NodeUtil.forEachChildRecursive(this, nodeVisitor); } }
Iterator over the children of this node.
Returns:the iterator
Since:0.8 or earlier
/** * Iterator over the children of this node. * * @return the iterator * @since 0.8 or earlier */
public final Iterable<Node> getChildren() { return new Iterable<Node>() { public Iterator<Node> iterator() { return getNodeClass().makeIterator(Node.this); } }; }
Creates a shallow copy of this node.
Returns:the new copy
Since:0.8 or earlier
/** * Creates a shallow copy of this node. * * @return the new copy * @since 0.8 or earlier */
public Node copy() { CompilerAsserts.neverPartOfCompilation("do not call Node.copy from compiled code"); try { return (Node) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(e); } }
Creates a deep copy of this node.
Returns:the new deep copy
Since:0.8 or earlier
/** * Creates a deep copy of this node. * * @return the new deep copy * @since 0.8 or earlier */
public Node deepCopy() { return NodeUtil.deepCopyImpl(this); }
Get the root node of the tree a node belongs to.
Returns:the RootNode or null if there is none.
Since:0.8 or earlier
/** * Get the root node of the tree a node belongs to. * * @return the {@link RootNode} or {@code null} if there is none. * @since 0.8 or earlier */
public final RootNode getRootNode() { if (CompilerDirectives.isPartialEvaluationConstant(this)) { return getRootNodeImpl(); } else { return getRootBoundary(); } } @TruffleBoundary private RootNode getRootBoundary() { return getRootNodeImpl(); } @ExplodeLoop private RootNode getRootNodeImpl() { Node node = this; Node prev; do { prev = node; node = node.getParent(); } while (node != null); if (prev instanceof RootNode) { return (RootNode) prev; } else { return null; } }
Notifies the runtime that this node specialized to a polymorphic state. This includes specializations that increase "level" of polymorphism (e.g. Adding another element to an existing inline cache). The runtime can use this information to, if allowed, create a deep copy of the RootNode hosting this node and gather context sensitive profiling feedback.
Since:0.33
/** * Notifies the runtime that this node specialized to a polymorphic state. This includes * specializations that increase "level" of polymorphism (e.g. Adding another element to an * existing inline cache). The runtime can use this information to, if * {@link RootNode#isCloningAllowed() allowed}, create a deep copy of the {@link RootNode} * hosting this node and gather context sensitive profiling feedback. * * @since 0.33 */
protected final void reportPolymorphicSpecialize() { CompilerAsserts.neverPartOfCompilation(); NodeAccessor.RUNTIME.reportPolymorphicSpecialize(this); }
Converts this node to a textual representation useful for debugging.
Since:0.8 or earlier
/** * Converts this node to a textual representation useful for debugging. * * @since 0.8 or earlier */
@Override public String toString() { StringBuilder sb = new StringBuilder(getClass().getSimpleName()); Map<String, Object> properties = getDebugProperties(); boolean hasProperties = false; for (Map.Entry<String, Object> entry : properties.entrySet()) { sb.append(hasProperties ? "," : "<"); hasProperties = true; sb.append(entry.getKey()).append("=").append(entry.getValue()); } if (hasProperties) { sb.append(">"); } sb.append("@").append(Integer.toHexString(hashCode())); return sb.toString(); }
Since:0.8 or earlier
/** @since 0.8 or earlier */
public final void atomic(Runnable closure) { Lock lock = getLock(); try { lock.lock(); closure.run(); } finally { lock.unlock(); } }
Since:0.8 or earlier
/** @since 0.8 or earlier */
public final <T> T atomic(Callable<T> closure) { Lock lock = getLock(); try { lock.lock(); return closure.call(); } catch (RuntimeException | Error e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } finally { lock.unlock(); } }
Returns a lock object that can be used to synchronize modifications to the AST. Don't lock if you call into foreign code with potential recursions to avoid deadlocks. Use responsibly.
Since:0.19
/** * Returns a lock object that can be used to synchronize modifications to the AST. Don't lock if * you call into foreign code with potential recursions to avoid deadlocks. Use responsibly. * * @since 0.19 */
protected final Lock getLock() { // Major Assumption: parent is never null after a node got adopted // it is never reset to null, and thus, rootNode is always reachable. // GIL: used for nodes that are replace in ASTs that are not yet adopted RootNode root = getRootNode(); if (root == null) { return GIL_LOCK; } else { return root.getLazyLock(); } }
Returns a user-readable description of the purpose of the Node, or "" if no description is available. Can be called on any thread and without a language context.
Since:0.8 or earlier
/** * Returns a user-readable description of the purpose of the Node, or "" if no description is * available. Can be called on any thread and without a language context. * * @since 0.8 or earlier */
public String getDescription() { NodeInfo info = getClass().getAnnotation(NodeInfo.class); if (info != null) { return info.description(); } return ""; } private static final Map<Class<?>, LanguageReference<?>> UNCACHED_LANGUAGE_REFERENCES = new ConcurrentHashMap<>();
Returns a reference that returns the current language instance. The returned language reference is intended to be cached in the currently adopted AST. If this node is adoptable then the method must be invoked after the AST was adopted otherwise an IllegalStateException is thrown. The reference lookup decides which lookup method is the best given the parent ExecutableNode or RootNode and the provided languageClass. It is recommended to use @CachedLanguage instead whenever possible. The given language class must not be null. If the given language class is not known to the current engine then an IllegalArgumentException is thrown.

Usage example:

class ExampleNode extends Node {
    @CompilationFinal private LanguageReference reference;
    void execute() {
        if (reference == null) {
            CompilerDirectives.transferToInterpreterAndInvalidate();
            this.reference = lookupLanguageReference(MyLanguage.class);
        }
        MyLanguage language = this.reference.get();
        // use language
    }
}

The current language might vary between executions if resources or code was shared between multiple contexts and the node was inserted into an AST that was not associated with this language. It is not recommended to cache the language in the AST directly.

This method is designed for partial evaluation and will reliably return a constant when called with a class literal and the number of accessed languages does not exceed the limit of 5 per root executable node. If possible the reference should be cached in the AST in order to avoid the repeated lookup of the parent executable or root node.

See Also:
  • @CachedContext to use the context reference in specializations or exported messages.
Since:19.0
/** * Returns a reference that returns the current language instance. The returned language * reference is intended to be cached in the currently adopted AST. If this node is * {@link #isAdoptable() adoptable} then the method must be invoked after the AST was adopted * otherwise an {@link IllegalStateException} is thrown. The reference lookup decides which * lookup method is the best given the parent {@link ExecutableNode} or {@link RootNode} and the * provided languageClass. It is recommended to use * {@link com.oracle.truffle.api.dsl.CachedLanguage @CachedLanguage} instead whenever possible. * The given language class must not be <code>null</code>. If the given language class is not * known to the current engine then an {@link IllegalArgumentException} is thrown. * <p> * Usage example: * * <pre> * class ExampleNode extends Node { * * &#64;CompilationFinal private LanguageReference<MyLanguage> reference; * * void execute() { * if (reference == null) { * CompilerDirectives.transferToInterpreterAndInvalidate(); * this.reference = lookupLanguageReference(MyLanguage.class); * } * MyLanguage language = this.reference.get(); * // use language * } * } * * </pre> * <p> * The current language might vary between {@link ExecutableNode#execute(VirtualFrame) * executions} if resources or code was shared between multiple contexts and the node was * inserted into an AST that was not associated with this language. It is not recommended to * cache the language in the AST directly. * <p> * This method is designed for partial evaluation and will reliably return a constant when * called with a class literal and the number of accessed languages does not exceed the limit of * 5 per root executable node. If possible the reference should be cached in the AST in order to * avoid the repeated lookup of the parent executable or root node. * * @see com.oracle.truffle.api.dsl.CachedContext @CachedContext to use the context reference in * specializations or exported messages. * @since 19.0 */
@SuppressWarnings({"unchecked", "rawtypes"}) protected final <T extends TruffleLanguage> LanguageReference<T> lookupLanguageReference(Class<T> languageClass) { try { if (languageClass == null) { CompilerDirectives.transferToInterpreter(); throw new NullPointerException(); } ExecutableNode executableNode = getExecutableNode(); if (executableNode != null) { TruffleLanguage<?> language = executableNode.getLanguage(); Object engine = executableNode.getEngine(); if (language != null && language.getClass() == languageClass) { return ENGINE.getDirectLanguageReference(engine, language, languageClass); } else { ReferenceCache cache = executableNode.lookupReferenceCache(languageClass); if (cache != null) { return (LanguageReference<T>) cache.languageReference; } else { return ENGINE.lookupLanguageReference(engine, language, languageClass); } } } return lookupUncachedLanguageReference(languageClass); } catch (Throwable t) { throw ENGINE.engineToLanguageException(t); } } @SuppressWarnings("unchecked") @TruffleBoundary private static <T extends TruffleLanguage<?>> LanguageReference<T> lookupUncachedLanguageReference(Class<T> languageClass) { LanguageReference<?> result = UNCACHED_LANGUAGE_REFERENCES.get(languageClass); if (result == null) { result = new LanguageReference<TruffleLanguage<?>>() { @Override @TruffleBoundary public TruffleLanguage<?> get() { return ENGINE.getCurrentLanguage(languageClass); } }; UNCACHED_LANGUAGE_REFERENCES.put(languageClass, result); } return (LanguageReference<T>) result; } @ExplodeLoop private ExecutableNode getExecutableNode() { Node node = this; while (node != null) { if (node instanceof ExecutableNode) { return (ExecutableNode) node; } node = node.getParent(); } if (node == null) { checkAdoptable(); } return null; } /* * Better to call this on a boundary to not pull in more methods. */ @TruffleBoundary private void checkAdoptable() { if (isAdoptable()) { throw new IllegalStateException("Node must be adopted before a reference can be looked up."); } }
Returns a reference that returns the current execution context associated with the given language. The returned context reference is intended to be cached in the currently adopted AST. If this node is adoptable then the method must be invoked after the AST was adopted otherwise an IllegalStateException is thrown. The reference lookup decides which lookup method is the best given the parent ExecutableNode or RootNode and the provided languageClass. It is recommended to use @CachedContext instead whenever possible. The given language class must not be null. If the given language class is not known to the current engine then an IllegalArgumentException is thrown.

Usage example:

class ExampleNode extends Node {
    @CompilationFinal private ContextReference reference;
    void execute() {
        if (reference == null) {
            CompilerDirectives.transferToInterpreterAndInvalidate();
            this.reference = lookupContextReference(MyLanguage.class);
        }
        MyContext context = this.reference.get();
        // use context
    }
}

The current context might vary between executions if resources or code is shared between multiple contexts. It is not recommended to cache the context in the AST directly.

This method is designed for partial evaluation and will reliably return a constant when called with a class literal and the number of accessed languages does not exceed the limit of 5 per root executable node. If possible the reference should be cached in the AST in order to avoid the repeated lookup of the parent executable or root node.

See Also:
  • @CachedContext to use the context reference in specializations or exported messages.
Since:19.0
/** * Returns a reference that returns the current execution context associated with the given * language. The returned context reference is intended to be cached in the currently adopted * AST. If this node is {@link #isAdoptable() adoptable} then the method must be invoked after * the AST was adopted otherwise an {@link IllegalStateException} is thrown. The reference * lookup decides which lookup method is the best given the parent {@link ExecutableNode} or * {@link RootNode} and the provided languageClass. It is recommended to use * {@link com.oracle.truffle.api.dsl.CachedContext @CachedContext} instead whenever possible. * The given language class must not be null. If the given language class is not known to the * current engine then an {@link IllegalArgumentException} is thrown. * <p> * Usage example: * * <pre> * class ExampleNode extends Node { * * &#64;CompilationFinal private ContextReference<MyContext> reference; * * void execute() { * if (reference == null) { * CompilerDirectives.transferToInterpreterAndInvalidate(); * this.reference = lookupContextReference(MyLanguage.class); * } * MyContext context = this.reference.get(); * // use context * } * } * * </pre> * <p> * The current context might vary between {@link ExecutableNode#execute(VirtualFrame) * executions} if resources or code is shared between multiple contexts. It is not recommended * to cache the context in the AST directly. * <p> * This method is designed for partial evaluation and will reliably return a constant when * called with a class literal and the number of accessed languages does not exceed the limit of * 5 per root executable node. If possible the reference should be cached in the AST in order to * avoid the repeated lookup of the parent executable or root node. * <p> * * @see com.oracle.truffle.api.dsl.CachedContext @CachedContext to use the context reference in * specializations or exported messages. * @since 19.0 */
@SuppressWarnings("unchecked") protected final <C, T extends TruffleLanguage<C>> ContextReference<C> lookupContextReference(Class<T> languageClass) { try { if (languageClass == null) { CompilerDirectives.transferToInterpreter(); throw new NullPointerException(); } ExecutableNode executableNode = getExecutableNode(); if (executableNode != null) { TruffleLanguage<?> language = executableNode.getLanguage(); Object engine = executableNode.getEngine(); if (language != null && language.getClass() == languageClass) { return ENGINE.getDirectContextReference(engine, language, languageClass); } else { ReferenceCache cache = executableNode.lookupReferenceCache(languageClass); if (cache != null) { return (ContextReference<C>) cache.contextReference; } else { return ENGINE.lookupContextReference(engine, language, languageClass); } } } return lookupUncachedContextReference(languageClass); } catch (Throwable t) { throw ENGINE.engineToLanguageException(t); } } private static final Map<Class<?>, ContextReference<?>> UNCACHED_CONTEXT_REFERENCES = new ConcurrentHashMap<>();
Resets the state for native image generation. NOTE: this method is called reflectively by downstream projects.
/** * Resets the state for native image generation. * * NOTE: this method is called reflectively by downstream projects. */
@SuppressWarnings("unused") private static void resetNativeImageState() { assert TruffleOptions.AOT : "Only supported during image generation"; UNCACHED_CONTEXT_REFERENCES.clear(); UNCACHED_LANGUAGE_REFERENCES.clear(); } @SuppressWarnings("unchecked") @TruffleBoundary private static <T extends TruffleLanguage<C>, C> ContextReference<C> lookupUncachedContextReference(Class<T> language) { ContextReference<?> result = UNCACHED_CONTEXT_REFERENCES.get(language); if (result == null) { result = new ContextReference<Object>() { @Override @TruffleBoundary public Object get() { try { return ENGINE.getCurrentContext(language); } catch (Throwable t) { throw ENGINE.engineToLanguageException(t); } } }; UNCACHED_CONTEXT_REFERENCES.put(language, result); } return (ContextReference<C>) result; } private static final ReentrantLock GIL_LOCK = new ReentrantLock(false); private boolean inAtomicBlock() { return ((ReentrantLock) getLock()).isHeldByCurrentThread(); } } @SuppressWarnings("unused") class NodeSnippets { // BEGIN: com.oracle.truffle.api.nodes.NodeSnippets.SimpleNode abstract class SimpleNode extends Node { private SourceSection sourceSection; void setSourceSection(SourceSection sourceSection) { this.sourceSection = sourceSection; } @Override public SourceSection getSourceSection() { return sourceSection; } } // END: com.oracle.truffle.api.nodes.NodeSnippets.SimpleNode // BEGIN:com.oracle.truffle.api.nodes.NodeSnippets.RecommendedNode abstract class RecommendedNode extends Node { 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; } @Override 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); } } // END: com.oracle.truffle.api.nodes.NodeSnippets.RecommendedNode public static void notifyInserted() { class InstrumentableLanguageNode extends Node { Object execute(VirtualFrame frame) { return null; } } class MyLanguage extends TruffleLanguage<Object> { @Override protected Object createContext(com.oracle.truffle.api.TruffleLanguage.Env env) { return null; } } // @formatter:off // BEGIN: com.oracle.truffle.api.nodes.NodeSnippets#notifyInserted class MyRootNode extends RootNode { protected MyRootNode(MyLanguage language) { super(language); } @Child InstrumentableLanguageNode child; @Override public Object execute(VirtualFrame frame) { if (child == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); child = insert(new InstrumentableLanguageNode()); notifyInserted(child); } return child.execute(frame); } } // END: com.oracle.truffle.api.nodes.NodeSnippets#notifyInserted // @formatter:on } }