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

com.oracle.truffle.js.nodes.JavaScriptNode Maven / Gradle / Ivy

/*
 * Copyright (c) 2018, 2024, 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.js.nodes;

import java.util.Set;

import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.GenerateWrapper;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.interop.NodeLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.js.nodes.function.BlockScopeNode;
import com.oracle.truffle.js.nodes.function.JSBuiltinNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.JavaScriptRootNode;
import com.oracle.truffle.js.runtime.interop.ScopeVariables;
import com.oracle.truffle.js.runtime.objects.JSModuleRecord;

@GenerateWrapper
@ExportLibrary(NodeLibrary.class)
public abstract class JavaScriptNode extends JavaScriptBaseNode implements InstrumentableNode {
    /** Source or SourceSection. */
    private Object source;
    private int charIndex;
    private int charLength;

    private static final int STATEMENT_TAG_BIT = 1 << 31;
    private static final int CALL_TAG_BIT = 1 << 30;
    private static final int CHAR_LENGTH_MASK = ~(STATEMENT_TAG_BIT | CALL_TAG_BIT);

    private static final int ROOT_BODY_TAG_BIT = 1 << 31;
    private static final int EXPRESSION_TAG_BIT = 1 << 30;
    private static final int CHAR_INDEX_MASK = ~(ROOT_BODY_TAG_BIT | EXPRESSION_TAG_BIT);

    protected static final String INTERMEDIATE_VALUE = "(intermediate value)";

    protected JavaScriptNode() {
    }

    protected JavaScriptNode(SourceSection sourceSection) {
        setSourceSection(sourceSection);
    }

    @Override
    public boolean isInstrumentable() {
        return hasSourceSection();
    }

    @Override
    public WrapperNode createWrapper(ProbeNode probe) {
        return new JavaScriptNodeWrapper(this, probe);
    }

    /**
     * Executes this node using the specified context and frame and returns the result value.
     *
     * @param frame the frame of the currently executing guest language method
     * @return the value of the execution
     */
    public abstract Object execute(VirtualFrame frame);

    /**
     * Like {@link #execute(VirtualFrame)} except that it tries to convert the result value to an
     * int. A node can override this method if it has a better way to producing a value of type int.
     *
     * @param frame the frame of the currently executing guest language method
     * @return the value of the execution as an int
     * @throws UnexpectedResultException if a loss-free conversion of the result to int is not
     *             possible
     */
    public int executeInt(VirtualFrame frame) throws UnexpectedResultException {
        Object o = execute(frame);
        if (o instanceof Integer) {
            return (int) o;
        } else {
            CompilerDirectives.transferToInterpreterAndInvalidate();
            throw new UnexpectedResultException(o);
        }
    }

    /**
     * Like {@link #execute(VirtualFrame)} except that it tries to convert the result value to a
     * double. A node can override this method if it has a better way to producing a value of type
     * double.
     *
     * @param frame the frame of the currently executing guest language method
     * @return the value of the execution as a double
     * @throws UnexpectedResultException if a loss-free conversion of the result to double is not
     *             possible
     */
    public double executeDouble(VirtualFrame frame) throws UnexpectedResultException {
        Object o = execute(frame);
        if (o instanceof Double) {
            return (double) o;
        } else {
            CompilerDirectives.transferToInterpreterAndInvalidate();
            throw new UnexpectedResultException(o);
        }
    }

    /**
     * Like {@link #execute(VirtualFrame)} except that it tries to convert the result value to a
     * boolean. A node can override this method if it has a better way to producing a value of type
     * boolean.
     *
     * @param frame the frame of the currently executing guest language method
     * @return the value of the execution as a boolean
     * @throws UnexpectedResultException if a loss-free conversion of the result to double is not
     *             possible
     */
    public boolean executeBoolean(VirtualFrame frame) throws UnexpectedResultException {
        Object o = execute(frame);
        if (o instanceof Boolean) {
            return (boolean) o;
        } else {
            CompilerDirectives.transferToInterpreterAndInvalidate();
            throw new UnexpectedResultException(o);
        }
    }

    /**
     * Like {@link #execute(VirtualFrame)} except that it throws away the result. A node can
     * override this method if it has a better way to execute without producing a value.
     *
     * @param frame the frame of the currently executing guest language method
     */
    public void executeVoid(VirtualFrame frame) {
        execute(frame);
    }

    @Override
    public JavaScriptNode copy() {
        CompilerAsserts.neverPartOfCompilation("cannot call JavaScriptNode.copy() in compiled code");
        return (JavaScriptNode) super.copy();
    }

    @Override
    public String toString() {
        CompilerAsserts.neverPartOfCompilation("cannot call JavaScriptNode.toString() in compiled code");
        String simpleName = getClass().getName().substring(getClass().getName().lastIndexOf('.') + 1);
        StringBuilder sb = new StringBuilder(simpleName);
        sb.append('@').append(Integer.toHexString(System.identityHashCode(this)));
        sb.append(" ").append(JSNodeUtil.formatSourceSection(this));
        String tagsString = JSNodeUtil.formatTags(this);
        if (!tagsString.isEmpty()) {
            sb.append("[").append(tagsString).append("]");
        }
        RootNode rootNode = getRootNode();
        if (rootNode != null) {
            sb.append(" '").append(JSNodeUtil.resolveName(rootNode)).append("'");
        }
        String expressionString = expressionToString();
        if (expressionString != null) {
            sb.append(" (").append(expressionString).append(")");
        }
        return sb.toString();
    }

    @Override
    protected void onReplace(Node newNode, CharSequence reason) {
        super.onReplace(newNode, reason);
        transferSourceSectionAndTags(this, (JavaScriptNode) newNode);
    }

    public static void transferSourceSectionAndTags(JavaScriptNode fromNode, JavaScriptNode toNode) {
        if (!toNode.hasSourceSection() && fromNode.hasSourceSection()) {
            // Pass on the source section to the new node.
            toNode.source = fromNode.source;
            toNode.charIndex = fromNode.charIndex | (toNode.charIndex & ~CHAR_INDEX_MASK);
            toNode.charLength = fromNode.charLength | (toNode.charLength & ~CHAR_LENGTH_MASK);
        }
    }

    public static void transferSourceSectionAddExpressionTag(JavaScriptNode fromNode, JavaScriptNode toNode) {
        if (!toNode.hasSourceSection() && fromNode.hasSourceSection()) {
            // Pass on the source section to the new node, but do not propagate tags.
            toNode.source = fromNode.source;
            toNode.charIndex = fromNode.charIndex & CHAR_INDEX_MASK;
            toNode.charLength = fromNode.charLength & CHAR_LENGTH_MASK;
            toNode.addExpressionTag();
        }
    }

    public static void transferSourceSection(JavaScriptNode fromNode, JavaScriptNode toNode) {
        if (!toNode.hasSourceSection() && fromNode.hasSourceSection()) {
            // Pass on the source section to the new node, but do not propagate tags.
            toNode.source = fromNode.source;
            toNode.charIndex = fromNode.charIndex & CHAR_INDEX_MASK;
            toNode.charLength = fromNode.charLength & CHAR_LENGTH_MASK;
        }
    }

    public final boolean hasSourceSection() {
        return source != null;
    }

    @Override
    public final SourceSection getSourceSection() {
        if (hasSourceSection()) {
            Object src = source;
            if (src instanceof SourceSection) {
                return (SourceSection) src;
            } else {
                SourceSection section = ((Source) src).createSection(charIndex & CHAR_INDEX_MASK, charLength & CHAR_LENGTH_MASK);
                source = section;
                return section;
            }
        }
        return null;
    }

    public final void setSourceSection(SourceSection section) {
        CompilerAsserts.neverPartOfCompilation();
        if (hasSourceSection()) {
            checkSameSourceSection(section);
        }
        this.source = section;
    }

    public final void setSourceSection(Source source, int charIndex, int charLength) {
        CompilerAsserts.neverPartOfCompilation();
        checkValidSourceSection(source, charIndex, charLength);
        if (hasSourceSection()) {
            checkSameSourceSection(source.createSection(charIndex, charLength));
        }
        assert charIndex <= CHAR_INDEX_MASK && charLength <= CHAR_LENGTH_MASK;
        this.charIndex = charIndex | (this.charIndex & ~CHAR_INDEX_MASK);
        this.charLength = charLength | (this.charLength & ~CHAR_LENGTH_MASK);
        this.source = source;
    }

    /** @see Source#createSection(int, int) */
    private static void checkValidSourceSection(Source source, int charIndex, int charLength) {
        if (charIndex < 0) {
            throw new IllegalArgumentException("charIndex < 0");
        } else if (charLength < 0) {
            throw new IllegalArgumentException("length < 0");
        }
        assert charIndex + charLength <= source.getCharacters().length();
    }

    private void checkSameSourceSection(SourceSection newSection) {
        SourceSection sourceSection = getSourceSection();
        if (sourceSection != null && !sourceSection.equals(newSection) && !equivalentUnavailableSections(sourceSection, newSection)) {
            throw new IllegalStateException(String.format("Source section is already assigned. Old: %s, new: %s", sourceSection, newSection));
        }
    }

    private static boolean equivalentUnavailableSections(SourceSection section1, SourceSection section2) {
        return !section1.isAvailable() && !section2.isAvailable() && section1.getSource().equals(section2.getSource());
    }

    public boolean isResultAlwaysOfType(@SuppressWarnings("unused") Class clazz) {
        return false;
    }

    @Override
    public boolean hasTag(Class tag) {
        if (tag == StandardTags.StatementTag.class) {
            return (charLength & STATEMENT_TAG_BIT) != 0;
        } else if (tag == StandardTags.CallTag.class) {
            return (charLength & CALL_TAG_BIT) != 0;
        } else if (tag == StandardTags.RootBodyTag.class) {
            return (charIndex & ROOT_BODY_TAG_BIT) != 0;
        } else if (tag == StandardTags.ExpressionTag.class) {
            return (charIndex & EXPRESSION_TAG_BIT) != 0;
        } else {
            return false;
        }
    }

    public final void addStatementTag() {
        charLength |= STATEMENT_TAG_BIT;
    }

    public final void addCallTag() {
        charLength |= CALL_TAG_BIT;
    }

    public final void addRootBodyTag() {
        charIndex |= ROOT_BODY_TAG_BIT;
    }

    public final void addExpressionTag() {
        charIndex |= EXPRESSION_TAG_BIT;
    }

    final boolean hasImportantTag() {
        return ((charIndex & ROOT_BODY_TAG_BIT) != 0 ||
                        (charIndex & EXPRESSION_TAG_BIT) != 0 ||
                        (charLength & STATEMENT_TAG_BIT) != 0 ||
                        (charLength & CALL_TAG_BIT) != 0);
    }

    protected JavaScriptNode copyUninitialized(Set> materializedTags) {
        if (this instanceof WrapperNode) {
            WrapperNode wrapperNode = (WrapperNode) this;
            return cloneUninitialized((JavaScriptNode) wrapperNode.getDelegateNode(), materializedTags);
        }

        throw Errors.notImplemented(getClass().getSimpleName() + ".copyUninitialized()");
    }

    @SuppressWarnings("unchecked")
    public static  T cloneUninitialized(T node, Set> materializedTags) {
        if (node == null) {
            return null;
        } else {
            T copy = node;
            if (materializedTags != null && node.isInstrumentable()) {
                copy = (T) node.materializeInstrumentableNodes(materializedTags);
            }
            if (node == copy) {
                copy = (T) node.copyUninitialized(materializedTags);
                // Assertion might not always hold and fail spuriously.
                assert copy.getClass() == node.getClass() || node instanceof JSBuiltinNode || node instanceof WrapperNode : node.getClass() + " => " + copy.getClass();
                transferSourceSectionAndTags(node, copy);
            }
            return copy;
        }
    }

    public static  T[] cloneUninitialized(T[] nodeArray, Set> materializedTags) {
        if (nodeArray == null) {
            return null;
        } else {
            T[] copy = nodeArray.clone();
            for (int i = 0; i < copy.length; i++) {
                copy[i] = cloneUninitialized(copy[i], materializedTags);
            }
            return copy;
        }
    }

    public String expressionToString() {
        return null;
    }

    // NodeLibrary

    @ExportMessage
    boolean accepts(@Cached(value = "this", adopt = false) JavaScriptNode cachedNode) {
        return this == cachedNode;
    }

    @ExportMessage
    final boolean hasScope(@SuppressWarnings("unused") Frame frame) {
        return this.getParent() != null;
    }

    @ExportMessage
    final Object getScope(Frame frame, boolean nodeEnter,
                    @Cached(value = "findBlockScopeNode(this)", allowUncached = true, adopt = false, neverDefault = false) Node blockNode,
                    @Cached(value = "findFrameScopeNode(blockNode)", allowUncached = true, adopt = false, neverDefault = false) Node frameBlockNode) throws UnsupportedMessageException {
        if (hasScope(frame)) {
            Frame functionFrame;
            Frame scopeFrame;
            if (frame != null) {
                RootNode rootNode = getRootNode();
                if (rootNode instanceof JavaScriptRootNode && ((JavaScriptRootNode) rootNode).isResumption() && frame.getFrameDescriptor() == rootNode.getFrameDescriptor()) {
                    functionFrame = JSArguments.getResumeExecutionContext(frame.getArguments());
                } else if (rootNode.getFrameDescriptor() == JavaScriptRootNode.MODULE_DUMMY_FRAMEDESCRIPTOR) {
                    functionFrame = ((JSModuleRecord) JSArguments.getUserArgument(frame.getArguments(), 0)).getEnvironment();
                } else {
                    functionFrame = frame.materialize();
                }
                if (frameBlockNode instanceof BlockScopeNode.FrameBlockScopeNode) {
                    Object maybeScopeFrame = ((BlockScopeNode.FrameBlockScopeNode) frameBlockNode).getBlockScope((VirtualFrame) functionFrame);
                    if (maybeScopeFrame instanceof Frame) {
                        scopeFrame = (Frame) maybeScopeFrame;
                    } else {
                        scopeFrame = functionFrame;
                    }
                } else {
                    scopeFrame = functionFrame;
                }
            } else {
                functionFrame = null;
                scopeFrame = null;
            }
            return ScopeVariables.create(scopeFrame, nodeEnter, blockNode, functionFrame);
        } else {
            throw UnsupportedMessageException.create();
        }
    }

    @ExportMessage
    @SuppressWarnings("static-method")
    final boolean hasReceiverMember(Frame frame) {
        return frame != null;
    }

    @ExportMessage
    @SuppressWarnings("static-method")
    final Object getReceiverMember(Frame frame) throws UnsupportedMessageException {
        if (frame == null) {
            throw UnsupportedMessageException.create();
        }
        return ScopeVariables.RECEIVER_MEMBER;
    }

    @ExportMessage
    @SuppressWarnings("static-method")
    boolean hasRootInstance(Frame frame) {
        return frame != null;
    }

    @ExportMessage
    @SuppressWarnings("static-method")
    Object getRootInstance(Frame frame) throws UnsupportedMessageException {
        if (frame == null) {
            throw UnsupportedMessageException.create();
        }
        return JSFrameUtil.getFunctionObject(frame);
    }

    @TruffleBoundary
    public static Node findBlockScopeNode(Node node) {
        if (node == null) {
            return null;
        }
        Node parent = node;
        for (Node n = parent; n != null; parent = n, n = n.getParent()) {
            if (n instanceof BlockScopeNode) {
                return n;
            }
        }
        assert parent instanceof RootNode : "Node " + node + " is not adopted.";
        return parent;
    }

    @TruffleBoundary
    static Node findFrameScopeNode(Node node) {
        if (node == null) {
            return null;
        }
        Node parent = node;
        for (Node n = parent; n != null; parent = n, n = n.getParent()) {
            if (n instanceof BlockScopeNode.FrameBlockScopeNode) {
                return n;
            }
        }
        assert parent instanceof RootNode : "Node " + node + " is not adopted.";
        return parent;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy