All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pkl.thirdparty.truffle.api.nodes.RootNode Maven / Gradle / Ivy

Go to download

Fat Jar containing pkl-cli, pkl-codegen-java, pkl-codegen-kotlin, pkl-config-java, pkl-core, pkl-doc, and their shaded third-party dependencies.

There is a newer version: 0.27.1
Show newest version
/*
 * Copyright (c) 2012, 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 org.pkl.thirdparty.truffle.api.nodes;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.ReentrantLock;

import org.pkl.thirdparty.truffle.api.CallTarget;
import org.pkl.thirdparty.truffle.api.CompilerAsserts;
import org.pkl.thirdparty.truffle.api.CompilerDirectives;
import org.pkl.thirdparty.truffle.api.CompilerDirectives.CompilationFinal;
import org.pkl.thirdparty.truffle.api.CompilerDirectives.TruffleBoundary;
import org.pkl.thirdparty.truffle.api.RootCallTarget;
import org.pkl.thirdparty.truffle.api.TruffleContext;
import org.pkl.thirdparty.truffle.api.TruffleLanguage;
import org.pkl.thirdparty.truffle.api.TruffleLanguage.ContextReference;
import org.pkl.thirdparty.truffle.api.TruffleLanguage.Env;
import org.pkl.thirdparty.truffle.api.TruffleLanguage.ParsingRequest;
import org.pkl.thirdparty.truffle.api.TruffleStackTraceElement;
import org.pkl.thirdparty.truffle.api.frame.Frame;
import org.pkl.thirdparty.truffle.api.frame.FrameDescriptor;
import org.pkl.thirdparty.truffle.api.frame.VirtualFrame;
import org.pkl.thirdparty.truffle.api.source.Source;
import org.pkl.thirdparty.truffle.api.source.SourceSection;

/**
 * Represents the root node in a Truffle AST. The root node is a {@link Node node} that allows to be
 * {@link #execute(VirtualFrame) executed} using a {@link VirtualFrame frame} instance created by
 * the framework. Please note that the {@link RootNode} should not be executed directly but using
 * {@link CallTarget#call(Object...)}. The structure of the frame is provided by the
 * {@link FrameDescriptor frame descriptor} passed in the constructor. A root node has always a
 * null {@link #getParent() parent} and cannot be {@link #replace(Node) replaced}.
 *
 * 

Construction

* * The root node can be constructed with a {@link TruffleLanguage language implementation} if it is * available. The language implementation instance is obtainable while * {@link TruffleLanguage#createContext(Env)} or {@link TruffleLanguage#parse(ParsingRequest)} is * executed. If no language environment is available, then null can be passed. Please * note that root nodes with null language are considered not instrumentable and don't * have access to its public {@link #getLanguageInfo() language information}. * *

Execution

* * In order to execute a root node, its call target is lazily created and can be accessed via * {@link RootNode#getCallTarget()}. This allows the runtime system to optimize the execution of the * AST. The {@link CallTarget} can either be {@link CallTarget#call(Object...) called} directly from * runtime code or {@link DirectCallNode direct} and {@link IndirectCallNode indirect} call nodes * can be created, inserted in a child field and {@link DirectCallNode#call(Object[]) called}. The * use of direct call nodes allows the framework to automatically inline and further optimize call * sites based on heuristics. *

* After several calls to a call target or call node, the root node might get compiled using partial * evaluation. The details of the compilation heuristic are unspecified, therefore the Truffle * runtime system might decide to not compile at all. * *

Cardinalities

* * One root node instance refers to other classes using the following cardinalities: *
    *
  • one {@link TruffleLanguage language} *
  • one {@link CallTarget call target} *
  • many {@link TruffleLanguage#createContext(Env) created} language contexts *
* *

Instrumentation

* * A root node can be {@linkplain org.pkl.thirdparty.truffle.api.instrumentation instrumented} if the * following conditions apply: *
    *
  • A non-null {@link TruffleLanguage language} is passed in the root node constructor. *
  • {@link #isInstrumentable()} is overridden and returns true. *
  • {@link #getSourceSection()} is overridden and returns a non-null value. *
  • The AST contains at least one node that implements * {@link org.pkl.thirdparty.truffle.api.instrumentation.InstrumentableNode}. *
  • It is recommended that children of instrumentable root nodes are tagged with * StandardTags. *
*

* Note: It is recommended to override {@link #getSourceSection()} and provide a * source section if available. This allows for better testing/tracing/tooling. If no concrete * source section is available please consider using {@link Source#createUnavailableSection()}. * * @since 0.8 or earlier */ public abstract class RootNode extends ExecutableNode { private static final AtomicReferenceFieldUpdater LOCK_UPDATER = AtomicReferenceFieldUpdater.newUpdater(RootNode.class, ReentrantLock.class, "lock"); @CompilationFinal private volatile RootCallTarget callTarget; @CompilationFinal private FrameDescriptor frameDescriptor; private volatile ReentrantLock lock; volatile byte instrumentationBits; /** * Creates new root node with a given language instance. The language instance is obtainable * while {@link TruffleLanguage#createContext(Env)} or * {@link TruffleLanguage#parse(ParsingRequest)} is executed. If no language environment is * available, then null can be passed. Please note that root nodes with * null language are considered not instrumentable and don't have access to its * public {@link #getLanguageInfo() language information}. * * @param language the language this root node is associated with * @since 0.25 */ protected RootNode(TruffleLanguage language) { this(language, null); } /** * Creates new root node given an language environment and frame descriptor. The language * instance is obtainable while {@link TruffleLanguage#createContext(Env)} or * {@link TruffleLanguage#parse(ParsingRequest)} is executed. If no language environment is * available, then null can be passed. Please note that root nodes with * null language are considered not instrumentable and don't have access to its * public {@link #getLanguageInfo() language information}. * * @param language the language this root node is associated with * @since 0.25 */ protected RootNode(TruffleLanguage language, FrameDescriptor frameDescriptor) { super(language); CompilerAsserts.neverPartOfCompilation(); this.frameDescriptor = frameDescriptor == null ? new FrameDescriptor() : frameDescriptor; } /** @since 0.8 or earlier */ @Override public Node copy() { RootNode root = (RootNode) super.copy(); root.frameDescriptor = frameDescriptor; resetFieldsAfterCopy(root); return root; } private static void resetFieldsAfterCopy(RootNode root) { root.callTarget = null; root.instrumentationBits = 0; root.lock = null; } /** * Returns a qualified name of the AST that in the best case uniquely identifiers the method. If * the qualified name is not specified by the root, then the {@link #getName() name} is used by * default. A root node that represents a Java method could consist of the package name, the * class name and the method name. E.g. mypackage.MyClass.myMethod * * @since 20.0.0 beta 1 */ public String getQualifiedName() { return getName(); } /** * Returns a simple name of the AST (expected to be a method or procedure name in most * languages) that identifies the AST for the benefit of guest language programmers using tools; * it might appear, for example in the context of a stack dump or trace and is not expected to * be called often. Can be called on any thread and without a language context. The name of a * root node that represents a Java method could consist of the method name. E.g. * myMethod *

* In some languages AST "compilation units" may have no intrinsic names. When no information is * available, language implementations might simply use the first few characters of the code, * followed by "{@code ...}". Language implementations should assign a more helpful name * whenever it becomes possible, for example when a functional value is assigned. This means * that the name might not be stable over time. *

* Language execution semantics should not depend on either this name or the way that it is * formatted. The name should be presented in the way expected to be most useful for * programmers. * * @return a name that helps guest language programmers identify code corresponding to the AST, * possibly {@code null} if the language implementation is unable to provide any useful * information. * @since 0.15 */ public String getName() { return null; } /** * Returns true if this root node should be considered internal and not be shown to * a guest language programmer. This method has effect on tools and guest language stack traces. * By default a {@link RootNode} is internal if no language was passed in the constructor or if * the {@link #getSourceSection() root source section} is set and points to an internal source. * This method is intended to be overwritten by guest languages, when the node's source is * internal, the implementation should respect that. Can be called on any thread and without a * language context. *

* This method may be invoked on compiled code paths. It is recommended to implement this method * such that it returns a compilation final constant. * * @since 0.27 */ public boolean isInternal() { if (getLanguageInfo() == null) { return true; } SourceSection sc = materializeSourceSection(); if (sc != null) { return sc.getSource().isInternal(); } return false; } /** * Returns true if this root node should count towards * {@link org.pkl.thirdparty.truffle.api.exception.AbstractTruffleException#getStackTraceElementLimit}. *

* By default, returns the negation of {@link #isInternal()}. *

* This method may be invoked on compiled code paths. It is recommended to implement this method * or #isInternal() such that it returns a partial evaluation constant. * * @since 21.2.0 */ protected boolean countsTowardsStackTraceLimit() { return !isInternal(); } @TruffleBoundary private SourceSection materializeSourceSection() { return getSourceSection(); } /** * Returns true if an AbstractTruffleException leaving this node should capture * {@link Frame} objects in its stack trace in addition to the default information. This is * false by default to avoid the attached overhead. The captured frames are then * accessible through {@link TruffleStackTraceElement#getFrame()} * * @since 0.31 */ public boolean isCaptureFramesForTrace() { return false; } /** * Returns true if this {@link RootNode} is allowed to be cloned. The runtime * system might decide to create deep copies of the {@link RootNode} in order to gather context * sensitive profiling feedback. The default implementation returns false. Guest * language specific implementations may want to return true here to indicate that * gathering call site specific profiling information might make sense for this {@link RootNode} * . * * @return true if cloning is allowed else false. * @since 0.8 or earlier */ public boolean isCloningAllowed() { return false; } /** * Returns true if {@link #cloneUninitialized()} can be used to create * uninitialized copies of an already initialized / executed root node. By default, or if this * method returns false, an optimizing Truffle runtime might need to copy the AST * before it is executed for the first time to ensure it is able to create new uninitialized * copies when needed. By returning true and therefore supporting uninitialized * copies an optimizing runtime does not need to keep a reference to an uninitialized copy on * its own and might therefore be able to save memory. The returned boolean needs to be * immutable for a {@link RootNode} instance. * * @return true if calls to {@link #cloneUninitialized() uninitialized copies} are * supported. * @see #cloneUninitialized() * @since 0.24 */ protected boolean isCloneUninitializedSupported() { return false; } /** * Creates an uninitialized copy of an already initialized/executed root node if it is * {@link #isCloneUninitializedSupported() supported}. Throws an * {@link UnsupportedOperationException} exception by default. By default, or if * {@link #isCloneUninitializedSupported()} returns false, an optimizing Truffle * runtime might need to copy the root node before it is executed for the first time to ensure * it is able to create new uninitialized copies when needed. By supporting uninitialized copies * an optimizing runtime does not need to keep a reference to an uninitialized copy on its own * and might therefore be able to save memory. * *

* Two common strategies to implement {@link #cloneUninitialized()} are: *

    *
  • Reparsing: Support it by keeping a reference to the original source code including * the lexical scope and create the uninitialized copy of the root node by reparsing the source. *
  • Resetting: Support it by traversing the {@link Node} tree and derive an * uninitialized copy from each initialized node. *
* * @return an uninitialized copy of this root node if supported. * @throws UnsupportedOperationException if not supported * @see #isCloneUninitializedSupported() * @since 0.24 */ protected RootNode cloneUninitialized() { throw new UnsupportedOperationException(); } final RootNode cloneUninitializedImpl(CallTarget sourceCallTarget, RootNode uninitializedRootNode) { RootNode clonedRoot; if (isCloneUninitializedSupported()) { assert uninitializedRootNode == null : "uninitializedRootNode should not have been created"; clonedRoot = cloneUninitialized(); // if the language copied we cannot be sure // that the call target is not reset (with their own means of copying) // so better make sure they are reset. resetFieldsAfterCopy(clonedRoot); } else { clonedRoot = NodeUtil.cloneNode(uninitializedRootNode); // regular cloning guarantees that call target, instrumentation bits, // and lock are null. See #copy(). assert clonedRoot.callTarget == null; assert clonedRoot.instrumentationBits == 0; assert clonedRoot.lock == null; } RootCallTarget clonedTarget = NodeAccessor.RUNTIME.newCallTarget(sourceCallTarget, clonedRoot); ReentrantLock l = clonedRoot.getLazyLock(); l.lock(); try { clonedRoot.setupCallTarget(clonedTarget, "callTarget not null. Was getCallTarget on the result of RootNode.cloneUninitialized called?"); } finally { l.unlock(); } return clonedRoot; } /** * Executes this function using the specified frame and returns the result value. * * @param frame the frame of the currently executing guest language method * @return the value of the execution * @since 0.8 or earlier */ @Override public abstract Object execute(VirtualFrame frame); /** @since 0.8 or earlier */ public final RootCallTarget getCallTarget() { RootCallTarget target = this.callTarget; // Check isLoaded to avoid returning a CallTarget before notifyOnLoad() is done if (target == null || !NodeAccessor.RUNTIME.isLoaded(target)) { CompilerDirectives.transferToInterpreterAndInvalidate(); target = initializeTarget(); } return target; } private RootCallTarget initializeTarget() { RootCallTarget target; ReentrantLock l = getLazyLock(); l.lock(); try { target = this.callTarget; if (target == null) { target = NodeAccessor.RUNTIME.newCallTarget(null, this); this.setupCallTarget(target, "callTarget was set by newCallTarget but should not"); } } finally { l.unlock(); } return target; } private void setupCallTarget(RootCallTarget callTarget, String message) { assert getLazyLock().isHeldByCurrentThread(); if (this.callTarget != null) { throw CompilerDirectives.shouldNotReachHere(message); } this.callTarget = callTarget; // Call notifyOnLoad() after the callTarget field is set, so the invariant that if a // CallTarget exists for a RootNode then that rootNode.callTarget points to the CallTarget // always holds, and no matter what notifyOnLoad() does the 1-1 relation between the // RootNode and CallTarget is already there. NodeAccessor.RUNTIME.notifyOnLoad(callTarget); } final RootCallTarget getCallTargetWithoutInitialization() { return callTarget; } /** @since 0.8 or earlier */ public final FrameDescriptor getFrameDescriptor() { return frameDescriptor; } /** * Does this contain AST content that it is possible to instrument. Can be called on any thread * and without a language context. * * @since 0.8 or earlier */ protected boolean isInstrumentable() { return true; } /** * Is this root node to be considered trivial by the runtime. * * A trivial root node is defined as a root node that: *
    *
  1. Never increases code size when inlined, i.e. is always less complex then the call.
  2. *
  3. Never performs guest language calls.
  4. *
  5. Never contains loops.
  6. *
  7. Is small (for a language-specific definition of small).
  8. *
* * An good example of trivial root nodes would be getters and setters in java. * * @since 20.3.0 * @return true if this root node should be considered trivial by the runtime. * false otherwise. */ protected boolean isTrivial() { return false; } /** * Provide a list of stack frames that led to a schedule of asynchronous execution of this root * node on the provided frame. The asynchronous frames are expected to be found here when * {@link Env#getAsynchronousStackDepth()} is positive. The language is free to provide * asynchronous frames or longer list of frames when it's of no performance penalty, or if * requested by other options. This method is invoked on slow-paths only and with a context * entered. * * @param frame A frame, never null * @return a list of {@link TruffleStackTraceElement}, or null when no asynchronous * stack is available. * @see Env#getAsynchronousStackDepth() * @since 20.1.0 */ protected List findAsynchronousFrames(Frame frame) { return null; } /** * Translates the {@link TruffleStackTraceElement} into an interop object supporting the * {@code hasExecutableName} and potentially {@code hasDeclaringMetaObject} and * {@code hasSourceLocation} messages. An executable name must be provided, whereas the * declaring meta object and source location is optional. Guest languages may typically return * their function objects that typically already implement the required contracts. *

* The intention of this method is to provide a guest language object for other languages that * they can inspect using interop. An implementation of this method is expected to not fail with * a guest error. Implementations are allowed to do {@link ContextReference#get(Node) context * reference lookups} in the implementation of the method. This may be useful to access the * function objects needed to resolve the stack trace element. * * @see TruffleStackTraceElement#getGuestObject() to access the guest object of a stack trace * element. * @since 20.3 */ protected Object translateStackTraceElement(TruffleStackTraceElement element) { Node location = element.getLocation(); return NodeAccessor.EXCEPTION.createDefaultStackTraceElementObject(element.getTarget().getRootNode(), location != null ? location.getEncapsulatingSourceSection() : null); } /** * Allows languages to perform actions before a root node is attempted to be compiled without * prior call to {@link #execute(VirtualFrame)}. By default this method returns * null to indicate that AOT compilation is not supported. Any non-null value * indicates that compilation without execution is supported for this root node. This method is * guaranteed to not be invoked prior to any calls to {@link #execute(VirtualFrame) execute}. *

* Common tasks that need to be performed by this method: *

    *
  • Initialize local variable types in the {@link FrameDescriptor} of the root node. Without * that any access to the frame will invalidate the code on first execute. *
  • Initialize specializing nodes with profiles that do not invalidate on first execution. * For initialization of Truffle DSL nodes see {@link org.pkl.thirdparty.truffle.api.dsl.AOTSupport}. *
  • Compute the expected execution signature of a root node and return it. *
*

* If possible an {@link ExecutionSignature execution signature} should be returned for better * call efficiency. If the argument and return profile is not available or cannot be derived the * {@link ExecutionSignature#GENERIC} can be used to indicate that any value needs to be * expected for as argument from or as return value of the method. To indicate that a type is * unknown a null return or argument type should be used. The type * Object type should not be used in that case. *

* This method is invoked when no context is currently {@link TruffleContext#enter(Node) * entered} therefore no guest application code must be executed. The execution might happen on * any thread, even threads unknown to the guest language implementation. It is allowed to * create new {@link CallTarget call targets} during preparation of the root node or perform * modifications to the {@link TruffleLanguage language} of this root node. * * @since 20.3 */ protected ExecutionSignature prepareForAOT() { return null; } /** * Helper method to create a root node that always returns the same value. Certain operations * (especially {@link org.pkl.thirdparty.truffle.api.interop inter-operability} API) require return of * stable {@link RootNode root nodes}. To simplify creation of such nodes, here is a factory * method that can create {@link RootNode} that returns always the same value. * * @param constant the constant to return * @return root node returning the constant * @since 0.8 or earlier */ public static RootNode createConstantNode(Object constant) { return new Constant(constant); } /** * If this root node has a lexical scope parent, this method returns its frame descriptor. * * As an example, consider the following pseudocode: * *

     * def m {
     *   # For the "m" root node:
     *   # getFrameDescriptor       returns FrameDescriptor(m)
     *   # getParentFrameDescriptor returns null
     *   var_method = 0
     *   a = () -> {
     *     # For the "a lambda" root node:
     *     # getFrameDescriptor       returns FrameDescriptor(a)
     *     # getParentFrameDescriptor returns FrameDescriptor(m)
     *     var_lambda1 = 1
     *     b = () -> {
     *       # For the "b lambda" root node:
     *       # getFrameDescriptor       returns FrameDescriptor(b)
     *       # getParentFrameDescriptor returns FrameDescriptor(a)
     *       var_method + var_lambda1
     *     }
     *     b.call
     *   }
     *   a.call
     * }
     * 
* * This info is used by the runtime to optimize compilation order by giving more priority to * lexical parents which are likely to inline the child thus resulting in better performance * sooner rather than waiting for the lexical parent to get hot on its own. * * @return The frame descriptor of the lexical parent scope if it exists. null * otherwise. * @since 22.3.0 */ protected FrameDescriptor getParentFrameDescriptor() { return null; } final ReentrantLock getLazyLock() { ReentrantLock l = this.lock; if (l == null) { l = initializeLock(); } return l; } private ReentrantLock initializeLock() { ReentrantLock l = new ReentrantLock(); if (!RootNode.LOCK_UPDATER.compareAndSet(this, null, l)) { // if CAS failed, lock is already initialized; cannot be null after that. l = Objects.requireNonNull(this.lock); } return l; } /** * Computes a size estimate of this root node. This is intended to be overwritten if the size of * the root node cannot be estimated from the natural AST size. For example, in case the root * node is represented by a bytecode interpreter. If -1 is returned, the regular * AST size estimate is going to be used. By default this method returns -1. *

* The size estimate is guaranteed to be invoked only once when the {@link CallTarget} is * created. This corresponds to calls to {@link #getCallTarget()} for the first time. This * method will never be invoked after the root node was already executed. * * @since 23.0 */ protected int computeSize() { return -1; } private static final class Constant extends RootNode { private final Object value; Constant(Object value) { super(null); this.value = value; } @Override public Object execute(VirtualFrame frame) { return value; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy