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

com.oracle.truffle.api.nodes.Node Maven / Gradle / Ivy

Go to download

Truffle is a multi-language framework for executing dynamic languages that achieves high performance when combined with Graal.

There is a newer version: 1.0.0-rc7
Show newest version
/*
 * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.truffle.api.nodes;

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 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.ReplaceObserver;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.TruffleRuntime;
import com.oracle.truffle.api.impl.Accessor;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.utilities.JSONHelper;

/**
 * Abstract base class for all Truffle nodes.
 *
 * @since 0.8 or earlier
 */
public abstract class Node implements NodeInterface, Cloneable {
    private final NodeClass nodeClass;
    @CompilationFinal private Node parent;

    /**
     * Marks array fields that are children of this node.
     *
     * @since 0.8 or earlier
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD})
    public @interface Children {
    }

    /**
     * Marks fields that represent child nodes of this node.
     *
     * @since 0.8 or earlier
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD})
    public @interface Child {
    }

    /** @since 0.8 or earlier */
    protected Node() {
        CompilerAsserts.neverPartOfCompilation("do not create a Node from compiled code");
        this.nodeClass = NodeClass.get(getClass());
        if (TruffleOptions.TraceASTJSON) {
            JSONHelper.dumpNewNode(this);
        }
    }

    NodeClass getNodeClass() {
        return nodeClass;
    }

    void setParent(Node parent) {
        this.parent = parent;
    }

    /**
     * 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 final or
     * {@link CompilationFinal} field in your node to the caller.
     *
     * To define node with fixed {@link SourceSection} that doesn't change after node
     * construction use:
     *
     * {@link com.oracle.truffle.api.nodes.NodeSnippets.NodeWithFixedSourceSection#section}
     *
     * To create a node which can associate and change the {@link SourceSection} later at any point
     * of time use:
     *
     * {@link com.oracle.truffle.api.nodes.NodeSnippets.MutableSourceSectionNode#section}
     *
     * @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.
     *
     * @return an approximation of the source code represented by this Node
     * @since 0.8 or earlier
     */
    @ExplodeLoop
    public SourceSection getEncapsulatingSourceSection() {
        Node current = this;
        while (current != null) {
            final SourceSection currentSection = current.getSourceSection();
            if (currentSection != null) {
                return currentSection;
            }
            current = current.parent;
        }
        return null;
    }

    /**
     * Method that updates the link to the parent in the array of specified new child nodes to this
     * node.
     *
     * @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[] insert(final T[] newChildren) {
        CompilerDirectives.transferToInterpreterAndInvalidate();
        assert newChildren != null;
        for (Node newChild : newChildren) {
            adoptHelper(newChild);
        }
        return newChildren;
    }

    /**
     * Method that updates the link to the parent in the specified new child node to this node.
     *
     * @param newChild the new child whose parent should be updated
     * @return the new child
     * @since 0.8 or earlier
     */
    protected final  T insert(final T newChild) {
        CompilerDirectives.transferToInterpreterAndInvalidate();
        assert newChild != null;
        adoptHelper(newChild);
        return newChild;
    }

    /** @since 0.8 or earlier */
    public final void adoptChildren() {
        CompilerDirectives.transferToInterpreterAndInvalidate();
        adoptHelper();
    }

    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.");
        }
        newChild.parent = this;
        if (TruffleOptions.TraceASTJSON) {
            JSONHelper.dumpNewChild(this, newChild);
        }
        newChild.adoptHelper();
    }

    private void adoptHelper() {
        NodeUtil.forEachChild(this, new NodeVisitor() {
            public boolean visit(Node child) {
                if (child != null && child.getParent() != Node.this) {
                    Node.this.adoptHelper(child);
                }
                return true;
            }
        });
    }

    private void adoptUnadoptedHelper(final Node newChild) {
        assert newChild != null;
        if (newChild == this) {
            throw new IllegalStateException("The parent of a node can never be the node itself.");
        }
        newChild.parent = this;
        newChild.adoptUnadoptedHelper();
    }

    private void adoptUnadoptedHelper() {
        NodeUtil.forEachChild(this, new NodeVisitor() {
            public boolean visit(Node child) {
                if (child != null && child.getParent() == null) {
                    Node.this.adoptUnadoptedHelper(child);
                }
                return true;
            }
        });
    }

    /**
     * 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 getDebugProperties() {
        Map properties = new HashMap<>();
        return properties;
    }

    /**
     * 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
     * {@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 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
     * {@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 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
        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
     */
    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);
        }
        if (TruffleOptions.TraceASTJSON) {
            JSONHelper.dumpReplaceChild(this, newNode, reason);
        }
    }

    /**
     * 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 {@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.
     *
     * @return the iterator
     * @since 0.8 or earlier
     */
    public final Iterable getChildren() {
        return new Iterable() {
            public Iterator iterator() {
                return getNodeClass().makeIterator(Node.this);
            }
        };
    }

    /**
     * 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.
     *
     * @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.
     *
     * @return the {@link RootNode} or {@code null} if there is none.
     * @since 0.8 or earlier
     */
    public final RootNode getRootNode() {
        Node rootNode = this;
        while (rootNode.getParent() != null) {
            assert !(rootNode instanceof RootNode) : "root node must not have a parent";
            rootNode = rootNode.getParent();
        }
        if (rootNode instanceof RootNode) {
            return (RootNode) rootNode;
        }
        return null;
    }

    /**
     * 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 properties = getDebugProperties();
        boolean hasProperties = false;
        for (Map.Entry 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 */
    public final void atomic(Runnable closure) {
        RootNode rootNode = getRootNode();
        // 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
        synchronized (rootNode != null ? rootNode : GIL) {
            assert enterAtomic();
            try {
                closure.run();
            } finally {
                assert exitAtomic();
            }
        }
    }

    /** @since 0.8 or earlier */
    public final  T atomic(Callable closure) {
        try {
            RootNode rootNode = getRootNode();
            // 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
            synchronized (rootNode != null ? rootNode : GIL) {
                assert enterAtomic();
                try {
                    return closure.call();
                } finally {
                    assert exitAtomic();
                }
            }
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Returns true if this node should be considered tagged by a given tag else
     * false. The method is only invoked for tags which are explicitly declared as
     * {@link com.oracle.truffle.api.instrumentation.ProvidedTags provided} by the
     * {@link TruffleLanguage language}. If the {@link #getSourceSection() source section} of the
     * node returns null then this node is considered to be not tagged by any 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 halt locations 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 #isTaggedWith(Class)} method. * This example shows how to tag a node subclass and all its subclasses as expression and * statement: * * {@link com.oracle.truffle.api.nodes.NodeSnippets.ExpressionNode} * *

* 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.nodes.NodeSnippets.StatementNode#isDebuggerHalt} * *

* The implementation of isTaggedWith method must ensure that its result is stable after the * parent {@link RootNode root node} was wrapped in a {@link CallTarget} using * {@link TruffleRuntime#createCallTarget(RootNode)}. The result is stable if the result of * calling this method for a particular tag remains always the same. * * @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.12 */ protected boolean isTaggedWith(Class tag) { return false; } /** * Returns a user-readable description of the purpose of the Node, or "" if no description is * available. * * @since 0.8 or earlier */ public String getDescription() { NodeInfo info = getClass().getAnnotation(NodeInfo.class); if (info != null) { return info.description(); } return ""; } /** * Returns a string representing the language this node has been implemented for. If the * language is unknown, returns "". * * @since 0.8 or earlier */ public String getLanguage() { NodeInfo info = getClass().getAnnotation(NodeInfo.class); if (info != null && info.language() != null && info.language().length() > 0) { return info.language(); } if (parent != null) { return parent.getLanguage(); } return ""; } private static final Object GIL = new Object(); private static final ThreadLocal IN_ATOMIC_BLOCK = new ThreadLocal() { @Override protected Integer initialValue() { return 0; } }; private static boolean inAtomicBlock() { return IN_ATOMIC_BLOCK.get() > 0; } private static boolean enterAtomic() { IN_ATOMIC_BLOCK.set(IN_ATOMIC_BLOCK.get() + 1); return true; } private static boolean exitAtomic() { IN_ATOMIC_BLOCK.set(IN_ATOMIC_BLOCK.get() - 1); return true; } static final class AccessorNodes extends Accessor { void probeAST(RootNode rootNode) { OldInstrumentSupport instrument = oldInstrumentSupport(); if (instrument != null) { instrument.probeAST(rootNode); } } @Override protected void onLoopCount(Node source, int iterations) { super.onLoopCount(source, iterations); } @Override protected Accessor.Nodes nodes() { return new AccessNodes(); } static final class AccessNodes extends Accessor.Nodes { @SuppressWarnings("rawtypes") @Override public Class findLanguage(RootNode n) { return n.language; } @Override public boolean isInstrumentable(RootNode rootNode) { return rootNode.isInstrumentable(); } @Override public boolean isTaggedWith(Node node, Class tag) { return node.isTaggedWith(tag); } } } // registers into Accessor.NODES static final AccessorNodes ACCESSOR = new AccessorNodes(); } class NodeSnippets { static class NodeWithFixedSourceSection extends Node { // BEGIN: com.oracle.truffle.api.nodes.NodeSnippets.NodeWithFixedSourceSection#section private final SourceSection section; NodeWithFixedSourceSection(SourceSection section) { this.section = section; } @Override public SourceSection getSourceSection() { return section; } // END: com.oracle.truffle.api.nodes.NodeSnippets.NodeWithFixedSourceSection#section } static class MutableSourceSectionNode extends Node { // BEGIN: com.oracle.truffle.api.nodes.NodeSnippets.MutableSourceSectionNode#section @CompilerDirectives.CompilationFinal private SourceSection section; final void changeSourceSection(SourceSection sourceSection) { CompilerDirectives.transferToInterpreterAndInvalidate(); this.section = sourceSection; } @Override public SourceSection getSourceSection() { return section; } // END: com.oracle.truffle.api.nodes.NodeSnippets.MutableSourceSectionNode#section } private static final class Debugger { static class HaltTag { } } // BEGIN: com.oracle.truffle.api.nodes.NodeSnippets.StatementNode#isDebuggerHalt class StatementNode extends Node { private boolean isDebuggerHalt; public void setDebuggerHalt(boolean isDebuggerHalt) { this.isDebuggerHalt = isDebuggerHalt; } @Override protected boolean isTaggedWith(Class tag) { if (tag == Debugger.HaltTag.class) { return isDebuggerHalt; } return super.isTaggedWith(tag); } } // END: com.oracle.truffle.api.nodes.NodeSnippets.StatementNode#isDebuggerHalt static class ExpressionTag { } // BEGIN: com.oracle.truffle.api.nodes.NodeSnippets.ExpressionNode class ExpressionNode extends Node { @Override protected boolean isTaggedWith(Class tag) { if (tag == ExpressionTag.class) { return true; } return super.isTaggedWith(tag); } } // END: com.oracle.truffle.api.nodes.NodeSnippets.ExpressionNode }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy