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

com.oracle.graal.python.nodes.function.BuiltinFunctionRootNode Maven / Gradle / Ivy

There is a newer version: 24.1.1
Show newest version
/*
 * Copyright (c) 2017, 2023, Oracle and/or its affiliates.
 * Copyright (c) 2013, Regents of the University of California
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided
 * with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.oracle.graal.python.nodes.function;

import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING;
import static com.oracle.graal.python.util.PythonUtils.toTruffleStringArrayUncached;
import static com.oracle.graal.python.util.PythonUtils.toTruffleStringUncached;
import static com.oracle.graal.python.util.PythonUtils.tsLiteral;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.oracle.graal.python.PythonLanguage;
import com.oracle.graal.python.builtins.Builtin;
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
import com.oracle.graal.python.builtins.objects.function.Signature;
import com.oracle.graal.python.nodes.PRootNode;
import com.oracle.graal.python.nodes.argument.ReadArgumentNode;
import com.oracle.graal.python.nodes.argument.ReadIndexedArgumentNode;
import com.oracle.graal.python.nodes.argument.ReadVarArgsNode;
import com.oracle.graal.python.nodes.argument.ReadVarKeywordsNode;
import com.oracle.graal.python.nodes.function.builtins.BuiltinCallNode;
import com.oracle.graal.python.nodes.function.builtins.BuiltinCallNode.BuiltinAnyCallNode;
import com.oracle.graal.python.nodes.function.builtins.BuiltinCallNode.BuiltinBinaryCallNode;
import com.oracle.graal.python.nodes.function.builtins.BuiltinCallNode.BuiltinQuaternaryCallNode;
import com.oracle.graal.python.nodes.function.builtins.BuiltinCallNode.BuiltinSenaryCallNode;
import com.oracle.graal.python.nodes.function.builtins.BuiltinCallNode.BuiltinTernaryCallNode;
import com.oracle.graal.python.nodes.function.builtins.BuiltinCallNode.BuiltinUnaryCallNode;
import com.oracle.graal.python.nodes.function.builtins.BuiltinCallNode.BuiltinVarArgsCallNode;
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.PythonQuaternaryBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.PythonSenaryBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.WrapBinaryfuncR;
import com.oracle.graal.python.nodes.function.builtins.WrapTpNew;
import com.oracle.graal.python.runtime.ExecutionContext.CalleeContext;
import com.oracle.graal.python.util.PythonUtils;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.GeneratedBy;
import com.oracle.truffle.api.dsl.NodeFactory;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeUtil;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.strings.TruffleString;

/**
 * CPython wraps built-in types' slots so the C can take the direct arguments. The slot wrappers for
 * binary and ternay functions (wrap_unaryfunc, wrap_binaryfunc_r, wrap_ternaryfunc,
 * wrap_ternaryfunc_r) extract the arguments using PyArg_UnpackTuple. For the reverse operations,
 * they also swap arguments appropriately, so that in C they can use one function to implement both
 * the operation and its reverse.
 *
 * This BuiltinFunctionRootNode similarly maps from an argument array to the signature of the Java
 * execute method we want and cares about swapping arguments back if needed.
 */
public final class BuiltinFunctionRootNode extends PRootNode {
    public static final TruffleString T_DOLLAR_DECL_TYPE = tsLiteral("$decl_type");
    private static final TruffleString T_DOLLAR_CLS = tsLiteral("$cls");
    private static final TruffleString T_DOLLAR_SELF = tsLiteral("$self");

    private final Signature signature;
    private final Builtin builtin;
    private final String name;
    private final NodeFactory factory;
    private final boolean declaresExplicitSelf;
    @Child private BuiltinCallNode body;
    @Child private CalleeContext calleeContext = CalleeContext.create();
    private final PythonBuiltinClassType constructsClass;

    public BuiltinFunctionRootNode(PythonLanguage language, Signature signature, Builtin builtin, NodeFactory factory, boolean declaresExplicitSelf,
                    PythonBuiltinClassType constructsClass) {
        super(language);
        CompilerAsserts.neverPartOfCompilation();
        this.signature = signature;
        this.builtin = builtin;
        this.name = builtin.name();
        this.factory = factory;
        this.declaresExplicitSelf = declaresExplicitSelf;
        this.constructsClass = constructsClass;
        if (builtin.alwaysNeedsCallerFrame()) {
            setNeedsCallerFrame();
        }
    }

    public BuiltinFunctionRootNode(PythonLanguage language, Builtin builtin, NodeFactory factory, boolean declaresExplicitSelf,
                    PythonBuiltinClassType constructsClass) {
        this(language, createSignature(factory, builtin, declaresExplicitSelf, constructsClass != PythonBuiltinClassType.nil), builtin, factory, declaresExplicitSelf, constructsClass);
    }

    public BuiltinFunctionRootNode(PythonLanguage language, Builtin builtin, NodeFactory factory, boolean declaresExplicitSelf) {
        this(language, builtin, factory, declaresExplicitSelf, builtin.constructsClass());
    }

    public static class StandaloneBuiltinFactory implements NodeFactory {
        private final T node;

        public StandaloneBuiltinFactory(T node) {
            this.node = node;
        }

        @Override
        public T createNode(Object... arguments) {
            return NodeUtil.cloneNode(node);
        }

        @Override
        public Class getNodeClass() {
            return determineNodeClass(node);
        }

        @SuppressWarnings("unchecked")
        private static  Class determineNodeClass(T node) {
            CompilerAsserts.neverPartOfCompilation();
            Class nodeClass = (Class) node.getClass();
            GeneratedBy genBy = nodeClass.getAnnotation(GeneratedBy.class);
            if (genBy != null) {
                nodeClass = (Class) genBy.value();
                assert nodeClass.isAssignableFrom(node.getClass());
            }
            return nodeClass;
        }

        @Override
        public List>> getNodeSignatures() {
            throw new IllegalAccessError();
        }

        @Override
        public List> getExecutionSignature() {
            throw new IllegalAccessError();
        }
    }

    /**
     * Should return a signature compatible with {@link #createArgumentsList(Builtin, boolean)}
     */
    private static Signature createSignature(NodeFactory factory, Builtin builtin, boolean declaresExplicitSelf, boolean constructsClass) {
        TruffleString[] parameterNames = toTruffleStringArrayUncached(builtin.parameterNames());
        int maxNumPosArgs = Math.max(builtin.minNumOfPositionalArgs(), parameterNames.length);

        if (builtin.maxNumOfPositionalArgs() >= 0) {
            maxNumPosArgs = builtin.maxNumOfPositionalArgs();
            assert parameterNames.length == 0 : "either give all parameter names explicitly, or define the max number: " + builtin.name() + " - " + String.join(",", builtin.parameterNames()) +
                            " vs " + builtin.maxNumOfPositionalArgs() + " - " + factory.toString();
        }

        assert validateBuiltin(factory, builtin);

        if (constructsClass && maxNumPosArgs == 0) {
            // we have this convention to always declare the cls argument without setting the num
            // args
            maxNumPosArgs = 1;
        }
        int posOnlyArgs = builtin.numOfPositionalOnlyArgs();
        if (parameterNames.length == 0) {
            // Unnamed arguments shall be positional-only
            posOnlyArgs = maxNumPosArgs;
        }
        if (!declaresExplicitSelf) {
            // if we don't take the explicit self, we still need to accept it by signature
            maxNumPosArgs++;
            posOnlyArgs++;
        }

        if (maxNumPosArgs > 0) {
            if (parameterNames.length == 0) {
                // PythonLanguage.getLogger().log(Level.FINEST, "missing parameter names for builtin
                // " + factory);
                parameterNames = new TruffleString[maxNumPosArgs];
                int i = 0;
                if (constructsClass) {
                    if (!declaresExplicitSelf) {
                        parameterNames[i++] = T_DOLLAR_DECL_TYPE;
                    }
                    parameterNames[i++] = T_DOLLAR_CLS;
                } else {
                    parameterNames[i++] = T_DOLLAR_SELF;
                }
                for (int p = 'a'; i < parameterNames.length; i++, p++) {
                    parameterNames[i] = TruffleString.fromCodePointUncached(p, TS_ENCODING);
                }
            } else {
                if (declaresExplicitSelf) {
                    assert parameterNames.length == maxNumPosArgs : "not enough parameter ids on " + factory;
                } else {
                    // we don't declare the "self" as a parameter id unless it's explicit
                    assert parameterNames.length + 1 == maxNumPosArgs : "not enough parameter ids on " + factory;
                    parameterNames = Arrays.copyOf(parameterNames, parameterNames.length + 1);
                    PythonUtils.arraycopy(parameterNames, 0, parameterNames, 1, parameterNames.length - 1);
                    parameterNames[0] = constructsClass ? T_DOLLAR_DECL_TYPE : T_DOLLAR_SELF;
                }
            }
        }
        assert canUseSpecialBuiltinNode(builtin) || !usesSpecialBuiltinNode(factory.getNodeClass()) : factory.getNodeClass().getName() +
                        " must not use PythonUnary/Binary/Ternary/QuaternaryBultinNode";
        return new Signature(posOnlyArgs, builtin.takesVarKeywordArgs(), builtin.takesVarArgs() ? parameterNames.length : -1,
                        builtin.varArgsMarker(), parameterNames, toTruffleStringArrayUncached(builtin.keywordOnlyNames()), false, toTruffleStringUncached(builtin.raiseErrorName()));
    }

    private static boolean validateBuiltin(NodeFactory factory, Builtin builtin) {
        Class nodeClass = factory.getNodeClass();
        if (PythonUnaryBuiltinNode.class.isAssignableFrom(nodeClass)) {
            validateBuiltinForArity(builtin, nodeClass, 1);
        } else if (PythonBinaryBuiltinNode.class.isAssignableFrom(nodeClass)) {
            validateBuiltinForArity(builtin, nodeClass, 2);
        } else if (PythonTernaryBuiltinNode.class.isAssignableFrom(nodeClass)) {
            validateBuiltinForArity(builtin, nodeClass, 3);
        } else if (PythonQuaternaryBuiltinNode.class.isAssignableFrom(nodeClass)) {
            validateBuiltinForArity(builtin, nodeClass, 4);
        } else if (PythonVarargsBuiltinNode.class.isAssignableFrom(nodeClass)) {
            assert builtin.takesVarArgs() : "PythonVararagsBuiltin subclass must take varargs, builtin " + nodeClass.getName();
            assert builtin.takesVarKeywordArgs() : "PythonVararagsBuiltin subclass must take varkwargs, builtin " + nodeClass.getName();
        }
        return true;
    }

    private static void validateBuiltinForArity(Builtin builtin, Class nodeClass, int arity) {
        int minNumPosArgs = builtin.minNumOfPositionalArgs();
        int maxNumPosArgs = builtin.maxNumOfPositionalArgs();
        if (builtin.parameterNames().length > 0) {
            assert builtin.parameterNames().length == arity : "Mismatch in parameter list length and arity for n-ary builtin " + nodeClass.getName();
            maxNumPosArgs = builtin.parameterNames().length;
        } else if (maxNumPosArgs == -1) {
            maxNumPosArgs = minNumPosArgs;
        }
        assert minNumPosArgs <= arity && minNumPosArgs <= maxNumPosArgs : "Invalid number of min arguments for a n-ary builtin " + nodeClass.getName();
        assert maxNumPosArgs == arity : "Invalid number of max arguments for a n-ary builtin " + nodeClass.getName();
        assert !builtin.takesVarArgs() && !builtin.takesVarKeywordArgs() : "Invalid varargs declaration for a n-ary builtin " + nodeClass.getName();
    }

    // Nodes for specific number of args n=1..4 (PythonUnaryBultinNode..PythonQuaternaryBultinNode)
    // can only be used by builtins with up to n positional arguments (without varargs/kwargs).
    // (Note that this does not apply to PythonVarargsBuiltinNode which can be used with
    // varargs/kwargs builtins.)
    private static boolean canUseSpecialBuiltinNode(Builtin builtin) {
        return !builtin.takesVarArgs() && !builtin.takesVarKeywordArgs() && !builtin.varArgsMarker() && builtin.keywordOnlyNames().length == 0;
    }

    private static boolean usesSpecialBuiltinNode(Class clazz) {
        return PythonUnaryBuiltinNode.class.isAssignableFrom(clazz) || PythonBinaryBuiltinNode.class.isAssignableFrom(clazz) || PythonTernaryBuiltinNode.class.isAssignableFrom(clazz) ||
                        PythonQuaternaryBuiltinNode.class.isAssignableFrom(clazz);
    }

    /**
     * Must return argument reads compatible with
     * {@link #createSignature(NodeFactory, Builtin, boolean, boolean)}
     */
    private static ReadArgumentNode[] createArgumentsList(Builtin builtin, boolean needsExplicitSelf) {
        ArrayList args = new ArrayList<>();

        String[] parameterNames = builtin.parameterNames();
        int maxNumPosArgs = Math.max(builtin.minNumOfPositionalArgs(), parameterNames.length);

        if (builtin.maxNumOfPositionalArgs() >= 0) {
            maxNumPosArgs = builtin.maxNumOfPositionalArgs();
            assert parameterNames.length == 0 : "either give all parameter names explicitly, or define the max number: " + builtin.name();
        }

        // if we don't declare the explicit self, we just ignore it
        int skip = needsExplicitSelf ? 0 : 1;

        // read those arguments that only come positionally
        for (int i = 0; i < maxNumPosArgs; i++) {
            args.add(ReadIndexedArgumentNode.create(i + skip));
        }

        // read splat args if any
        if (builtin.takesVarArgs()) {
            args.add(ReadVarArgsNode.create(true));
        }

        int keywordCount = builtin.keywordOnlyNames().length;
        for (int i = 0; i < keywordCount; i++) {
            args.add(ReadIndexedArgumentNode.create(i + maxNumPosArgs + skip));
        }

        if (builtin.takesVarKeywordArgs()) {
            args.add(ReadVarKeywordsNode.create());
        }

        return args.toArray(new ReadArgumentNode[args.size()]);
    }

    @Override
    public boolean isCloningAllowed() {
        return true;
    }

    @Override
    public boolean isCaptureFramesForTrace() {
        return false;
    }

    @Override
    public boolean isInternal() {
        return true;
    }

    @Override
    public boolean setsUpCalleeContext() {
        return true;
    }

    @Override
    public Object execute(VirtualFrame frame) {
        if (body == null) {
            CompilerDirectives.transferToInterpreterAndInvalidate();
            BuiltinCallNode newBody;
            ReadArgumentNode[] argumentsList = createArgumentsList(builtin, declaresExplicitSelf);
            if (PythonBuiltinNode.class.isAssignableFrom(factory.getNodeClass())) {
                newBody = new BuiltinAnyCallNode((PythonBuiltinNode) factory.createNode((Object) argumentsList));
            } else {
                PythonBuiltinBaseNode node = factory.createNode();
                if (node instanceof PythonUnaryBuiltinNode) {
                    assert argumentsList.length == 1 : "mismatch in number of arguments for " + node.getClass().getName() + ", expected 1, got " + argumentsList.length;
                    newBody = new BuiltinUnaryCallNode((PythonUnaryBuiltinNode) node, argumentsList[0]);
                } else if (node instanceof PythonBinaryBuiltinNode) {
                    assert argumentsList.length == 2 : "mismatch in number of arguments for " + node.getClass().getName() + ", expected 2, got " + argumentsList.length;
                    newBody = new BuiltinBinaryCallNode((PythonBinaryBuiltinNode) node, argumentsList[0], argumentsList[1]);
                } else if (node instanceof PythonTernaryBuiltinNode) {
                    assert argumentsList.length == 3 : "mismatch in number of arguments for " + node.getClass().getName() + ", expected 3, got " + argumentsList.length;
                    newBody = new BuiltinTernaryCallNode((PythonTernaryBuiltinNode) node, argumentsList[0], argumentsList[1], argumentsList[2]);
                } else if (node instanceof PythonQuaternaryBuiltinNode) {
                    assert argumentsList.length == 4 : "mismatch in number of arguments for " + node.getClass().getName() + ", expected 4, got " + argumentsList.length;
                    newBody = new BuiltinQuaternaryCallNode((PythonQuaternaryBuiltinNode) node, argumentsList[0], argumentsList[1], argumentsList[2], argumentsList[3]);
                } else if (node instanceof PythonSenaryBuiltinNode) {
                    assert argumentsList.length == 6 : "mismatch in number of arguments for " + node.getClass().getName() + ", expected 6, got " + argumentsList.length;
                    newBody = new BuiltinSenaryCallNode((PythonSenaryBuiltinNode) node, argumentsList[0], argumentsList[1], argumentsList[2], argumentsList[3], argumentsList[4], argumentsList[5]);
                } else if (node instanceof PythonVarargsBuiltinNode) {
                    assert argumentsList.length == 3 : "mismatch in number of arguments for " + node.getClass().getName() + ", expected 3, got " + argumentsList.length;
                    assert argumentsList[0] != null && argumentsList[1] != null && argumentsList[2] != null;
                    newBody = new BuiltinVarArgsCallNode((PythonVarargsBuiltinNode) node, argumentsList[0], argumentsList[1], argumentsList[2]);
                } else {
                    throw new RuntimeException("unexpected builtin node type: " + node.getClass());
                }
            }

            if (builtin.reverseOperation()) {
                body = insert(new WrapBinaryfuncR(newBody));
            } else if (constructsClass != PythonBuiltinClassType.nil) {
                body = insert(new WrapTpNew(newBody, constructsClass));
            } else {
                body = insert(newBody);
            }
        }
        calleeContext.enter(frame);
        try {
            return body.execute(frame);
        } finally {
            calleeContext.exit(frame, this);
        }
    }

    public String getFunctionName() {
        return name;
    }

    public NodeFactory getFactory() {
        return factory;
    }

    public Builtin getBuiltin() {
        return builtin;
    }

    @Override
    public String toString() {
        CompilerAsserts.neverPartOfCompilation();
        Class clazz = factory.getNodeClass().getEnclosingClass();
        String context = clazz == null ? "" : clazz.getSimpleName() + ".";
        return "";
    }

    @Override
    public String getName() {
        return name;
    }

    public boolean declaresExplicitSelf() {
        return declaresExplicitSelf;
    }

    @Override
    public Signature getSignature() {
        return signature;
    }

    @Override
    public boolean isPythonInternal() {
        return true;
    }

    @Override
    protected boolean isCloneUninitializedSupported() {
        return true;
    }

    @Override
    protected RootNode cloneUninitialized() {
        return new BuiltinFunctionRootNode(getLanguage(PythonLanguage.class), signature, builtin, factory, declaresExplicitSelf, constructsClass);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy