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

com.oracle.truffle.polyglot.HostExecuteNode 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: 24.1.1
Show newest version
/*
 * Copyright (c) 2017, 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.polyglot;

import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;

import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.ReportPolymorphism;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.api.profiles.ValueProfile;
import com.oracle.truffle.polyglot.HostMethodDesc.OverloadedMethod;
import com.oracle.truffle.polyglot.HostMethodDesc.SingleMethod;
import com.oracle.truffle.polyglot.PolyglotLanguageContext.ToGuestValueNode;
import com.oracle.truffle.polyglot.TargetMappingNode.SingleMappingNode;
import com.oracle.truffle.polyglot.TargetMappingNodeGen.SingleMappingNodeGen;

@ReportPolymorphism
@GenerateUncached
abstract class HostExecuteNode extends Node {
    static final int LIMIT = 3;
    private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];

    HostExecuteNode() {
    }

    static HostExecuteNode create() {
        return HostExecuteNodeGen.create();
    }

    public abstract Object execute(HostMethodDesc method, Object obj, Object[] args, PolyglotLanguageContext languageContext) throws UnsupportedTypeException, ArityException;

    static ToHostNode[] createToHost(int argsLength) {
        ToHostNode[] toJava = new ToHostNode[argsLength];
        for (int i = 0; i < argsLength; i++) {
            toJava[i] = ToHostNodeGen.create();
        }
        return toJava;
    }

    @SuppressWarnings("unused")
    @ExplodeLoop
    @Specialization(guards = {"!method.isVarArgs()", "method == cachedMethod"}, limit = "LIMIT")
    Object doFixed(SingleMethod method, Object obj, Object[] args, PolyglotLanguageContext languageContext,
                    @Cached("method") SingleMethod cachedMethod,
                    @Cached("createToHost(method.getParameterCount())") ToHostNode[] toJavaNodes,
                    @Cached ToGuestValueNode toGuest,
                    @Cached("createClassProfile()") ValueProfile receiverProfile,
                    @Cached BranchProfile errorBranch,
                    @Cached(value = "languageContext.context.engine", allowUncached = true) PolyglotEngineImpl engine) throws ArityException, UnsupportedTypeException {
        int arity = cachedMethod.getParameterCount();
        if (args.length != arity) {
            errorBranch.enter();
            throw ArityException.create(arity, args.length);
        }
        Class[] types = cachedMethod.getParameterTypes();
        Type[] genericTypes = cachedMethod.getGenericParameterTypes();
        Object[] convertedArguments = new Object[args.length];
        try {
            for (int i = 0; i < toJavaNodes.length; i++) {
                convertedArguments[i] = toJavaNodes[i].execute(args[i], types[i], genericTypes[i], languageContext, true);
            }
        } catch (PolyglotEngineException e) {
            errorBranch.enter();
            throw HostInteropErrors.unsupportedTypeException(args, e.e);
        }
        return doInvoke(cachedMethod, receiverProfile.profile(obj), convertedArguments, engine, languageContext, toGuest);
    }

    @SuppressWarnings("unused")
    @Specialization(guards = {"method.isVarArgs()", "method == cachedMethod"}, limit = "LIMIT")
    Object doVarArgs(SingleMethod method, Object obj, Object[] args, PolyglotLanguageContext languageContext,
                    @Cached("method") SingleMethod cachedMethod,
                    @Cached ToHostNode toJavaNode,
                    @Cached ToGuestValueNode toGuest,
                    @Cached("createClassProfile()") ValueProfile receiverProfile,
                    @Cached BranchProfile errorBranch,
                    @Cached(value = "languageContext.context.engine", allowUncached = true) PolyglotEngineImpl engine) throws ArityException, UnsupportedTypeException {
        int parameterCount = cachedMethod.getParameterCount();
        int minArity = parameterCount - 1;
        if (args.length < minArity) {
            errorBranch.enter();
            throw ArityException.create(minArity, args.length);
        }
        Class[] types = cachedMethod.getParameterTypes();
        Type[] genericTypes = cachedMethod.getGenericParameterTypes();
        Object[] convertedArguments = new Object[args.length];
        try {
            for (int i = 0; i < minArity; i++) {
                convertedArguments[i] = toJavaNode.execute(args[i], types[i], genericTypes[i], languageContext, true);
            }
            if (asVarArgs(args, cachedMethod, languageContext)) {
                for (int i = minArity; i < args.length; i++) {
                    Class expectedType = types[minArity].getComponentType();
                    Type expectedGenericType = getGenericComponentType(genericTypes[minArity]);
                    convertedArguments[i] = toJavaNode.execute(args[i], expectedType, expectedGenericType, languageContext, true);
                }
                convertedArguments = createVarArgsArray(cachedMethod, convertedArguments, parameterCount);
            } else {
                convertedArguments[minArity] = toJavaNode.execute(args[minArity], types[minArity], genericTypes[minArity], languageContext, true);
            }
        } catch (PolyglotEngineException e) {
            errorBranch.enter();
            throw HostInteropErrors.unsupportedTypeException(args, e.e);
        }
        return doInvoke(cachedMethod, receiverProfile.profile(obj), convertedArguments, engine, languageContext, toGuest);
    }

    @Specialization(replaces = {"doFixed", "doVarArgs"})
    static Object doSingleUncached(SingleMethod method, Object obj, Object[] args, PolyglotLanguageContext languageContext,
                    @Shared("toHost") @Cached ToHostNode toJavaNode,
                    @Shared("toGuest") @Cached ToGuestValueNode toGuest,
                    @Shared("varArgsProfile") @Cached ConditionProfile isVarArgsProfile,
                    @Shared("hostMethodProfile") @Cached HostMethodProfileNode methodProfile,
                    @Shared("errorBranch") @Cached BranchProfile errorBranch,
                    @Shared("engine") @Cached(value = "languageContext.context.engine", allowUncached = true) PolyglotEngineImpl engine) throws ArityException, UnsupportedTypeException {
        int parameterCount = method.getParameterCount();
        int minArity;
        boolean arityError;
        if (isVarArgsProfile.profile(method.isVarArgs())) {
            minArity = parameterCount - 1;
            arityError = args.length < minArity;
        } else {
            minArity = parameterCount;
            arityError = args.length != minArity;
        }
        if (arityError) {
            errorBranch.enter();
            throw ArityException.create(minArity, args.length);
        }
        Object[] convertedArguments;
        try {
            convertedArguments = prepareArgumentsUncached(method, args, languageContext, toJavaNode, isVarArgsProfile);
        } catch (PolyglotEngineException e) {
            errorBranch.enter();
            throw HostInteropErrors.unsupportedTypeException(args, e.e);
        }
        return doInvoke(methodProfile.execute(method), obj, convertedArguments, engine, languageContext, toGuest);
    }

    // Note: checkArgTypes must be evaluated after selectOverload.
    @SuppressWarnings({"unused", "static-method"})
    @ExplodeLoop
    @Specialization(guards = {"method == cachedMethod", "checkArgTypes(args, cachedArgTypes, interop, languageContext, asVarArgs)"}, limit = "LIMIT")
    final Object doOverloadedCached(OverloadedMethod method, Object obj, Object[] args, PolyglotLanguageContext languageContext,
                    @Cached("method") OverloadedMethod cachedMethod,
                    @Cached ToHostNode toJavaNode,
                    @Cached ToGuestValueNode toGuest,
                    @CachedLibrary(limit = "LIMIT") InteropLibrary interop,
                    @Cached("createArgTypesArray(args)") TypeCheckNode[] cachedArgTypes,
                    @Cached("selectOverload(method, args, languageContext, cachedArgTypes)") SingleMethod overload,
                    @Cached("asVarArgs(args, overload, languageContext)") boolean asVarArgs,
                    @Cached("createClassProfile()") ValueProfile receiverProfile,
                    @Cached BranchProfile errorBranch,
                    @Cached(value = "languageContext.context.engine", allowUncached = true) PolyglotEngineImpl engine) throws ArityException, UnsupportedTypeException {
        assert overload == selectOverload(method, args, languageContext);
        Class[] types = overload.getParameterTypes();
        Type[] genericTypes = overload.getGenericParameterTypes();
        Object[] convertedArguments = new Object[cachedArgTypes.length];
        try {
            if (asVarArgs) {
                assert overload.isVarArgs();
                int parameterCount = overload.getParameterCount();
                for (int i = 0; i < cachedArgTypes.length; i++) {
                    Class expectedType = i < parameterCount - 1 ? types[i] : types[parameterCount - 1].getComponentType();
                    Type expectedGenericType = i < parameterCount - 1 ? genericTypes[i] : getGenericComponentType(genericTypes[parameterCount - 1]);
                    convertedArguments[i] = toJavaNode.execute(args[i], expectedType, expectedGenericType, languageContext, true);
                }
                convertedArguments = createVarArgsArray(overload, convertedArguments, parameterCount);
            } else {
                for (int i = 0; i < cachedArgTypes.length; i++) {
                    convertedArguments[i] = toJavaNode.execute(args[i], types[i], genericTypes[i], languageContext, true);
                }
            }
        } catch (PolyglotEngineException e) {
            errorBranch.enter();
            throw HostInteropErrors.unsupportedTypeException(args, e.e);
        }
        return doInvoke(overload, receiverProfile.profile(obj), convertedArguments, engine, languageContext, toGuest);
    }

    @SuppressWarnings("static-method")
    @Specialization(replaces = "doOverloadedCached")
    final Object doOverloadedUncached(OverloadedMethod method, Object obj, Object[] args, PolyglotLanguageContext languageContext,
                    @Shared("toHost") @Cached ToHostNode toJavaNode,
                    @Shared("toGuest") @Cached ToGuestValueNode toGuest,
                    @Shared("varArgsProfile") @Cached ConditionProfile isVarArgsProfile,
                    @Shared("hostMethodProfile") @Cached HostMethodProfileNode methodProfile,
                    @Shared("errorBranch") @Cached BranchProfile errorBranch,
                    @Shared("engine") @Cached(value = "languageContext.context.engine", allowUncached = true) PolyglotEngineImpl engine) throws ArityException, UnsupportedTypeException {
        SingleMethod overload = selectOverload(method, args, languageContext);
        Object[] convertedArguments;
        try {
            convertedArguments = prepareArgumentsUncached(overload, args, languageContext, toJavaNode, isVarArgsProfile);
        } catch (PolyglotEngineException e) {
            errorBranch.enter();
            throw HostInteropErrors.unsupportedTypeException(args, e.e);
        }
        return doInvoke(methodProfile.execute(overload), obj, convertedArguments, engine, languageContext, toGuest);
    }

    private static Object[] prepareArgumentsUncached(SingleMethod method, Object[] args, PolyglotLanguageContext languageContext, ToHostNode toJavaNode, ConditionProfile isVarArgsProfile) {
        Class[] types = method.getParameterTypes();
        Type[] genericTypes = method.getGenericParameterTypes();
        Object[] convertedArguments = new Object[args.length];
        if (isVarArgsProfile.profile(method.isVarArgs()) && asVarArgs(args, method, languageContext)) {
            int parameterCount = method.getParameterCount();
            for (int i = 0; i < args.length; i++) {
                Class expectedType = i < parameterCount - 1 ? types[i] : types[parameterCount - 1].getComponentType();
                Type expectedGenericType = i < parameterCount - 1 ? genericTypes[i] : getGenericComponentType(genericTypes[parameterCount - 1]);
                convertedArguments[i] = toJavaNode.execute(args[i], expectedType, expectedGenericType, languageContext, true);
            }
            convertedArguments = createVarArgsArray(method, convertedArguments, parameterCount);
        } else {
            for (int i = 0; i < args.length; i++) {
                convertedArguments[i] = toJavaNode.execute(args[i], types[i], genericTypes[i], languageContext, true);
            }
        }
        return convertedArguments;
    }

    static TypeCheckNode[] createArgTypesArray(Object[] args) {
        TypeCheckNode[] nodes = new TypeCheckNode[args.length];
        // fill with null checks so the DSL does not complain when it tries to adopt
        Arrays.fill(nodes, NullCheckNode.INSTANCE);
        return nodes;
    }

    @SuppressWarnings("unchecked")
    private void fillArgTypesArray(Object[] args, TypeCheckNode[] cachedArgTypes, SingleMethod selected, boolean varArgs, List applicable, int priority,
                    PolyglotLanguageContext languageContext) {
        if (cachedArgTypes == null) {
            return;
        }
        HostClassCache cache = languageContext.getEngine().getHostClassCache();
        boolean multiple = applicable.size() > 1;
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            Class targetType = getParameterType(selected.getParameterTypes(), i, varArgs);
            Set otherPossibleMappings = null;
            if (multiple) {
                for (SingleMethod other : applicable) {
                    if (other == selected) {
                        continue;
                    }
                    if (other.isVarArgs() != varArgs) {
                        continue;
                    }
                    Class paramType = getParameterType(other.getParameterTypes(), i, varArgs);
                    if (paramType == targetType) {
                        continue;
                    }
                    /*
                     * All converters that are currently not applicable of other methods need to be
                     * checked for not applicable in order to ensure that mappings with priority
                     * continue to not apply.
                     */
                    if (!ToHostNode.canConvert(arg, paramType, paramType, null, languageContext, priority,
                                    InteropLibrary.getFactory().getUncached(), TargetMappingNodeGen.getUncached())) {
                        PolyglotTargetMapping[] otherMappings = cache.getMappings(paramType);
                        if (otherPossibleMappings == null) {
                            otherPossibleMappings = new LinkedHashSet<>();
                        }
                        for (PolyglotTargetMapping mapping : otherMappings) {
                            otherPossibleMappings.add(mapping);
                        }
                    }
                }
            }
            TypeCheckNode argType;
            if (arg == null) {
                argType = NullCheckNode.INSTANCE;
            } else if (multiple && ToHostNode.isPrimitiveTarget(targetType)) {
                argType = createPrimitiveTargetCheck(applicable, selected, arg, targetType, i, priority, varArgs);
            } else if (arg instanceof HostObject) {
                argType = new JavaObjectType(((HostObject) arg).getObjectClass());
            } else {
                argType = new DirectTypeCheck(arg.getClass());
            }
            PolyglotTargetMapping[] mappings = cache.getMappings(targetType);
            if (mappings.length > 0 || otherPossibleMappings != null) {
                PolyglotTargetMapping[] otherMappings = otherPossibleMappings != null ? otherPossibleMappings.toArray(HostClassCache.EMPTY_MAPPINGS) : HostClassCache.EMPTY_MAPPINGS;
                argType = new TargetMappingType(argType, mappings, otherMappings, priority);
            }
            /*
             * We need to eagerly insert as the cachedArgTypes might be used before they are adopted
             * by the DSL.
             */
            cachedArgTypes[i] = insert(argType);
        }

        assert checkArgTypes(args, cachedArgTypes, InteropLibrary.getFactory().getUncached(), languageContext, false) : Arrays.toString(cachedArgTypes);
    }

    private static TypeCheckNode createPrimitiveTargetCheck(List applicable, SingleMethod selected, Object arg, Class targetType, int parameterIndex, int priority, boolean varArgs) {
        Class currentTargetType = targetType;

        Collection> otherPossibleTypes = new ArrayList<>();
        for (SingleMethod other : applicable) {
            if (other == selected) {
                continue;
            }
            if (other.isVarArgs() != varArgs) {
                continue;
            }
            Class paramType = getParameterType(other.getParameterTypes(), parameterIndex, varArgs);
            if (paramType == targetType) {
                continue;
            } else if (otherPossibleTypes.contains(paramType)) {
                continue;
            }
            /*
             * If the other param type is a subtype of this param type, and the argument is not
             * already a subtype of it, another value may change the outcome of overload resolution,
             * so we have to guard against it. If the argument is already a subtype of the other
             * param type, we must not guard against the other param type, and we do not have to as
             * this overload was better fit regardless.
             */
            if ((ToHostNode.isPrimitiveTarget(paramType) || ToHostNode.isPrimitiveTarget(targetType)) &&
                            isAssignableFrom(targetType, paramType) && !isSubtypeOf(arg, paramType)) {
                otherPossibleTypes.add(paramType);
            }
        }
        return new PrimitiveType(currentTargetType, otherPossibleTypes.toArray(EMPTY_CLASS_ARRAY), priority);
    }

    @ExplodeLoop
    static boolean checkArgTypes(Object[] args, TypeCheckNode[] argTypes, InteropLibrary interop, PolyglotLanguageContext languageContext, @SuppressWarnings("unused") boolean dummy) {
        if (args.length != argTypes.length) {
            return false;
        }
        for (int i = 0; i < argTypes.length; i++) {
            TypeCheckNode argType = argTypes[i];
            if (!argType.execute(args[i], interop, languageContext)) {
                return false;
            }
        }
        return true;
    }

    @TruffleBoundary
    static boolean asVarArgs(Object[] args, SingleMethod overload, PolyglotLanguageContext languageContext) {
        if (overload.isVarArgs()) {
            int parameterCount = overload.getParameterCount();
            if (args.length == parameterCount) {
                Class varArgParamType = overload.getParameterTypes()[parameterCount - 1];
                return !ToHostNode.canConvert(args[parameterCount - 1], varArgParamType, overload.getGenericParameterTypes()[parameterCount - 1],
                                null, languageContext, ToHostNode.LOOSE,
                                InteropLibrary.getFactory().getUncached(), TargetMappingNode.getUncached());
            } else {
                assert args.length != parameterCount;
                return true;
            }
        } else {
            return false;
        }
    }

    static Class primitiveTypeToBoxedType(Class primitiveType) {
        assert primitiveType.isPrimitive();
        if (primitiveType == boolean.class) {
            return Boolean.class;
        } else if (primitiveType == byte.class) {
            return Byte.class;
        } else if (primitiveType == short.class) {
            return Short.class;
        } else if (primitiveType == char.class) {
            return Character.class;
        } else if (primitiveType == int.class) {
            return Integer.class;
        } else if (primitiveType == long.class) {
            return Long.class;
        } else if (primitiveType == float.class) {
            return Float.class;
        } else if (primitiveType == double.class) {
            return Double.class;
        } else {
            throw new IllegalArgumentException();
        }
    }

    static Class boxedTypeToPrimitiveType(Class primitiveType) {
        if (primitiveType == Boolean.class) {
            return boolean.class;
        } else if (primitiveType == Byte.class) {
            return byte.class;
        } else if (primitiveType == Short.class) {
            return short.class;
        } else if (primitiveType == Character.class) {
            return char.class;
        } else if (primitiveType == Integer.class) {
            return int.class;
        } else if (primitiveType == Long.class) {
            return long.class;
        } else if (primitiveType == Float.class) {
            return float.class;
        } else if (primitiveType == Double.class) {
            return double.class;
        } else {
            return null;
        }
    }

    @TruffleBoundary
    SingleMethod selectOverload(OverloadedMethod method, Object[] args, PolyglotLanguageContext languageContext) throws ArityException, UnsupportedTypeException {
        return selectOverload(method, args, languageContext, null);
    }

    @TruffleBoundary
    SingleMethod selectOverload(OverloadedMethod method, Object[] args, PolyglotLanguageContext languageContext, TypeCheckNode[] cachedArgTypes)
                    throws ArityException, UnsupportedTypeException {
        SingleMethod[] overloads = method.getOverloads();
        List applicableByArity = new ArrayList<>();
        int minOverallArity = Integer.MAX_VALUE;
        int maxOverallArity = 0;
        boolean anyVarArgs = false;
        for (SingleMethod overload : overloads) {
            int paramCount = overload.getParameterCount();
            if (!overload.isVarArgs()) {
                if (args.length != paramCount) {
                    minOverallArity = Math.min(minOverallArity, paramCount);
                    maxOverallArity = Math.max(maxOverallArity, paramCount);
                    continue;
                }
            } else {
                anyVarArgs = true;
                int fixedParamCount = paramCount - 1;
                if (args.length < fixedParamCount) {
                    minOverallArity = Math.min(minOverallArity, fixedParamCount);
                    maxOverallArity = Math.max(maxOverallArity, fixedParamCount);
                    continue;
                }
            }
            applicableByArity.add(overload);
        }
        if (applicableByArity.isEmpty()) {
            throw ArityException.create((args.length > maxOverallArity ? maxOverallArity : minOverallArity), args.length);
        }

        SingleMethod best;
        for (int priority : ToHostNode.PRIORITIES) {
            best = findBestCandidate(applicableByArity, args, languageContext, false, priority, cachedArgTypes);
            if (best != null) {
                return best;
            }
        }
        if (anyVarArgs) {
            for (int priority : ToHostNode.PRIORITIES) {
                best = findBestCandidate(applicableByArity, args, languageContext, true, priority, cachedArgTypes);
                if (best != null) {
                    return best;
                }
            }
        }

        throw noApplicableOverloadsException(overloads, args);
    }

    @SuppressWarnings("static-method")
    private SingleMethod findBestCandidate(List applicableByArity, Object[] args, PolyglotLanguageContext languageContext, boolean varArgs, int priority,
                    TypeCheckNode[] cachedArgTypes) throws UnsupportedTypeException {
        List candidates = new ArrayList<>();

        if (!varArgs) {
            for (SingleMethod candidate : applicableByArity) {
                int paramCount = candidate.getParameterCount();
                if (!candidate.isVarArgs() || paramCount == args.length) {
                    assert paramCount == args.length;
                    Class[] parameterTypes = candidate.getParameterTypes();
                    Type[] genericParameterTypes = candidate.getGenericParameterTypes();
                    boolean applicable = true;
                    for (int i = 0; i < paramCount; i++) {
                        if (!ToHostNode.canConvert(args[i], parameterTypes[i], genericParameterTypes[i], null,
                                        languageContext, priority, InteropLibrary.getFactory().getUncached(args[i]),
                                        TargetMappingNode.getUncached())) {
                            applicable = false;
                            break;
                        }
                    }
                    if (applicable) {
                        candidates.add(candidate);
                    }
                }
            }
        } else {
            for (SingleMethod candidate : applicableByArity) {
                if (candidate.isVarArgs()) {
                    int parameterCount = candidate.getParameterCount();
                    Class[] parameterTypes = candidate.getParameterTypes();
                    Type[] genericParameterTypes = candidate.getGenericParameterTypes();
                    boolean applicable = true;
                    for (int i = 0; i < parameterCount - 1; i++) {
                        if (!ToHostNode.canConvert(args[i], parameterTypes[i], genericParameterTypes[i], null,
                                        languageContext, priority, InteropLibrary.getFactory().getUncached(args[i]),
                                        TargetMappingNode.getUncached())) {
                            applicable = false;
                            break;
                        }
                    }
                    if (applicable) {
                        Class varArgsComponentType = parameterTypes[parameterCount - 1].getComponentType();
                        Type varArgsGenericComponentType = genericParameterTypes[parameterCount - 1];
                        if (varArgsGenericComponentType instanceof GenericArrayType) {
                            final GenericArrayType arrayType = (GenericArrayType) varArgsGenericComponentType;
                            varArgsGenericComponentType = arrayType.getGenericComponentType();
                        } else {
                            varArgsGenericComponentType = varArgsComponentType;
                        }
                        for (int i = parameterCount - 1; i < args.length; i++) {
                            if (!ToHostNode.canConvert(args[i], varArgsComponentType, varArgsGenericComponentType, null,
                                            languageContext, priority,
                                            InteropLibrary.getFactory().getUncached(args[i]), TargetMappingNode.getUncached())) {
                                applicable = false;
                                break;
                            }
                        }
                        if (applicable) {
                            candidates.add(candidate);
                        }
                    }
                }
            }
        }

        if (!candidates.isEmpty()) {
            if (candidates.size() == 1) {
                SingleMethod best = candidates.get(0);

                if (cachedArgTypes != null) {
                    fillArgTypesArray(args, cachedArgTypes, best, varArgs, applicableByArity, priority, languageContext);
                }

                return best;
            } else {
                SingleMethod best = findMostSpecificOverload(languageContext, candidates, args, varArgs, priority);
                if (best != null) {
                    if (cachedArgTypes != null) {
                        fillArgTypesArray(args, cachedArgTypes, best, varArgs, applicableByArity, priority, languageContext);
                    }

                    return best;
                }
                throw ambiguousOverloadsException(candidates, args);
            }
        }
        return null;
    }

    private static SingleMethod findMostSpecificOverload(PolyglotLanguageContext languageContext, List candidates, Object[] args, boolean varArgs, int priority) {
        assert candidates.size() >= 2;
        if (candidates.size() == 2) {
            int res = compareOverloads(languageContext, candidates.get(0), candidates.get(1), args, varArgs, priority);
            return res == 0 ? null : (res < 0 ? candidates.get(0) : candidates.get(1));
        }

        Iterator candIt = candidates.iterator();
        List best = new LinkedList<>();
        best.add(candIt.next());

        while (candIt.hasNext()) {
            SingleMethod cand = candIt.next();
            boolean add = false;
            for (Iterator bestIt = best.iterator(); bestIt.hasNext();) {
                int res = compareOverloads(languageContext, cand, bestIt.next(), args, varArgs, priority);
                if (res == 0) {
                    add = true;
                } else if (res < 0) {
                    bestIt.remove();
                    add = true;
                } else {
                    assert res > 0;
                }
            }
            if (add) {
                best.add(cand);
            }
        }

        assert !best.isEmpty();
        if (best.size() == 1) {
            return best.get(0);
        }
        return null; // ambiguous
    }

    private static int compareOverloads(PolyglotLanguageContext languageContext, SingleMethod m1, SingleMethod m2, Object[] args, boolean varArgs, int priority) {
        int res = 0;
        int maxParamCount = Math.max(m1.getParameterCount(), m2.getParameterCount());
        assert !varArgs || m1.isVarArgs() && m2.isVarArgs();
        assert varArgs || (m1.getParameterCount() == m2.getParameterCount() && args.length == m1.getParameterCount());
        assert maxParamCount <= args.length;
        for (int i = 0; i < maxParamCount; i++) {
            Class t1 = getParameterType(m1.getParameterTypes(), i, varArgs);
            Class t2 = getParameterType(m2.getParameterTypes(), i, varArgs);
            if (t1 == t2) {
                continue;
            }
            int r = compareByPriority(languageContext, t1, t2, args[i], priority);
            if (r == 0) {
                r = compareAssignable(t1, t2);
                if (r == 0) {
                    continue;
                }
            }
            if (res == 0) {
                res = r;
            } else if (res != r) {
                // cannot determine definite ranking between these two overloads
                res = 0;
                break;
            }
        }
        return res;
    }

    private static Class getParameterType(Class[] parameterTypes, int i, boolean varArgs) {
        return varArgs && i >= parameterTypes.length - 1 ? parameterTypes[parameterTypes.length - 1].getComponentType() : parameterTypes[i];
    }

    private static int compareByPriority(PolyglotLanguageContext languageContext, Class t1, Class t2, Object arg, int priority) {
        if (priority <= ToHostNode.STRICT) {
            return 0;
        }
        InteropLibrary argInterop = InteropLibrary.getFactory().getUncached(arg);
        TargetMappingNode mapping = TargetMappingNode.getUncached();
        for (int p : ToHostNode.PRIORITIES) {
            if (p > priority) {
                break;
            }
            boolean p1 = ToHostNode.canConvert(arg, t1, t1, null, languageContext, p, argInterop, mapping);
            boolean p2 = ToHostNode.canConvert(arg, t2, t2, null, languageContext, p, argInterop, mapping);
            if (p1 != p2) {
                return p1 ? -1 : 1;
            }
        }
        return 0;
    }

    private static int compareAssignable(Class t1, Class t2) {
        if (isAssignableFrom(t1, t2)) {
            // t1 > t2 (t2 more specific)
            return 1;
        } else if (isAssignableFrom(t2, t1)) {
            // t1 < t2 (t1 more specific)
            return -1;
        } else {
            return 0;
        }
    }

    private static boolean isAssignableFrom(Class toType, Class fromType) {
        if (toType.isAssignableFrom(fromType)) {
            return true;
        }
        boolean fromIsPrimitive = fromType.isPrimitive();
        boolean toIsPrimitive = toType.isPrimitive();
        Class fromAsPrimitive = fromIsPrimitive ? fromType : boxedTypeToPrimitiveType(fromType);
        Class toAsPrimitive = toIsPrimitive ? toType : boxedTypeToPrimitiveType(toType);
        if (toAsPrimitive != null && fromAsPrimitive != null) {
            if (toAsPrimitive == fromAsPrimitive) {
                assert fromIsPrimitive != toIsPrimitive;
                // primitive <: boxed
                return fromIsPrimitive;
            } else if (isWideningPrimitiveConversion(toAsPrimitive, fromAsPrimitive)) {
                // primitive|boxed <: wider primitive|boxed
                return true;
            }
        } else if (fromAsPrimitive == char.class && (toType == String.class || toType == CharSequence.class)) {
            // char|Character <: String|CharSequence
            return true;
        } else if (toAsPrimitive == null && fromAsPrimitive != null && toType.isAssignableFrom(primitiveTypeToBoxedType(fromAsPrimitive))) {
            // primitive|boxed <: Number et al
            return true;
        }
        return false;
    }

    private static boolean isSubtypeOf(Object argument, Class parameterType) {
        Object value = argument;
        if (argument instanceof HostObject) {
            value = ((HostObject) argument).obj;
        }
        if (!parameterType.isPrimitive()) {
            return value == null || (parameterType.isInstance(value) && !(value instanceof TruffleObject));
        } else {
            if (value != null) {
                Class boxedToPrimitive = boxedTypeToPrimitiveType(value.getClass());
                if (boxedToPrimitive != null) {
                    return (boxedToPrimitive == parameterType || isWideningPrimitiveConversion(parameterType, boxedToPrimitive));
                }
            }
        }
        return false;
    }

    private static boolean isWideningPrimitiveConversion(Class toType, Class fromType) {
        assert toType.isPrimitive();
        if (fromType == byte.class) {
            return toType == short.class || toType == int.class || toType == long.class || toType == float.class || toType == double.class;
        } else if (fromType == short.class) {
            return toType == int.class || toType == long.class || toType == float.class || toType == double.class;
        } else if (fromType == char.class) {
            return toType == int.class || toType == long.class || toType == float.class || toType == double.class;
        } else if (fromType == int.class) {
            return toType == long.class || toType == float.class || toType == double.class;
        } else if (fromType == long.class) {
            return toType == float.class || toType == double.class;
        } else if (fromType == float.class) {
            return toType == double.class;
        } else {
            return false;
        }
    }

    private static RuntimeException ambiguousOverloadsException(List candidates, Object[] args) throws UnsupportedTypeException {
        String message = String.format("Multiple applicable overloads found for method name %s (candidates: %s, arguments: %s)", candidates.get(0).getName(), candidates, arrayToStringWithTypes(args));
        throw UnsupportedTypeException.create(args, message);
    }

    private static RuntimeException noApplicableOverloadsException(SingleMethod[] overloads, Object[] args) throws UnsupportedTypeException {
        String message = String.format("no applicable overload found (overloads: %s, arguments: %s)", Arrays.toString(overloads), arrayToStringWithTypes(args));
        throw UnsupportedTypeException.create(args, message);
    }

    private static Type getGenericComponentType(Type type) {
        return type instanceof GenericArrayType ? ((GenericArrayType) type).getGenericComponentType() : ((Class) type).getComponentType();
    }

    @TruffleBoundary
    private static Object[] createVarArgsArray(SingleMethod method, Object[] args, int parameterCount) {
        Object[] arguments;
        Class[] parameterTypes = method.getParameterTypes();
        arguments = new Object[parameterCount];
        for (int i = 0; i < parameterCount - 1; i++) {
            arguments[i] = args[i];
        }
        Class varArgsType = parameterTypes[parameterCount - 1].getComponentType();
        Object varArgs = Array.newInstance(varArgsType, args.length - parameterCount + 1);
        for (int i = parameterCount - 1, j = 0; i < args.length; i++, j++) {
            Array.set(varArgs, j, args[i]);
        }
        arguments[parameterCount - 1] = varArgs;
        return arguments;
    }

    private static Object doInvoke(SingleMethod method, Object obj, Object[] arguments, PolyglotEngineImpl engine, PolyglotLanguageContext languageContext, ToGuestValueNode toGuest) {
        assert engine == languageContext.context.engine;
        assert arguments.length == method.getParameterCount();
        Object ret = method.invokeGuestToHost(obj, arguments, engine, languageContext, toGuest);
        return toGuest.execute(languageContext, ret);
    }

    private static String arrayToStringWithTypes(Object[] args) {
        StringJoiner sj = new StringJoiner(", ", "[", "]");
        for (Object arg : args) {
            sj.add(arg == null ? null : arg.toString() + " (" + arg.getClass().getSimpleName() + ")");
        }
        return sj.toString();
    }

    abstract static class TypeCheckNode extends Node {

        abstract boolean execute(Object test, InteropLibrary interop, PolyglotLanguageContext languageContext);

    }

    static final class NullCheckNode extends TypeCheckNode {

        static final NullCheckNode INSTANCE = new NullCheckNode();

        @Override
        boolean execute(Object test, InteropLibrary interop, PolyglotLanguageContext languageContext) {
            return test == null;
        }

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

        @Override
        public String toString() {
            return "null";
        }

    }

    static final class DirectTypeCheck extends TypeCheckNode {
        final Class clazz;

        DirectTypeCheck(Class clazz) {
            this.clazz = clazz;
        }

        @Override
        boolean execute(Object test, InteropLibrary interop, PolyglotLanguageContext languageContext) {
            return test != null && test.getClass() == clazz;
        }

        @Override
        public String toString() {
            return clazz.toString();
        }
    }

    static final class JavaObjectType extends TypeCheckNode {
        final Class clazz;

        JavaObjectType(Class clazz) {
            this.clazz = clazz;
        }

        @Override
        boolean execute(Object arg, InteropLibrary interop, PolyglotLanguageContext languageContext) {
            return arg instanceof HostObject && ((HostObject) arg).getObjectClass() == clazz;
        }

        @Override
        public int hashCode() {
            return ((clazz == null) ? 0 : clazz.hashCode());
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof JavaObjectType)) {
                return false;
            }
            JavaObjectType other = (JavaObjectType) obj;
            return Objects.equals(this.clazz, other.clazz);
        }

        @Override
        public String toString() {
            return "JavaObject[" + clazz.getTypeName() + "]";
        }
    }

    static final class TargetMappingType extends TypeCheckNode {

        @CompilationFinal(dimensions = 1) final PolyglotTargetMapping[] mappings;
        @CompilationFinal(dimensions = 1) final PolyglotTargetMapping[] otherMappings;

        @Child TypeCheckNode fallback;
        @Child TargetMappingNode targetMapping;
        @Children final SingleMappingNode[] mappingNodes;
        @Children final SingleMappingNode[] otherMappingNodes;
        final int priority;

        TargetMappingType(TypeCheckNode fallback,
                        PolyglotTargetMapping[] mappings,
                        PolyglotTargetMapping[] otherMappings,
                        int priority) {
            this.fallback = fallback;
            this.priority = priority;
            this.mappings = mappings;
            this.otherMappings = otherMappings;
            this.mappingNodes = new SingleMappingNode[mappings.length];
            for (int i = 0; i < mappings.length; i++) {
                mappingNodes[i] = SingleMappingNodeGen.create();
            }
            this.otherMappingNodes = new SingleMappingNode[otherMappings.length];
            for (int i = 0; i < otherMappings.length; i++) {
                otherMappingNodes[i] = SingleMappingNodeGen.create();
            }
            this.targetMapping = TargetMappingNode.create();
        }

        @Override
        @ExplodeLoop
        boolean execute(Object test, InteropLibrary interop, PolyglotLanguageContext languageContext) {
            for (int i = 0; i < otherMappingNodes.length; i++) {
                PolyglotTargetMapping mapping = otherMappings[i];
                if (mapping.hostPriority > priority) {
                    break;
                }
                Object result = otherMappingNodes[i].execute(test, mapping, languageContext, interop, true);
                if (result == Boolean.TRUE) {
                    return false;
                }
            }

            for (int i = 0; i < mappingNodes.length; i++) {
                PolyglotTargetMapping mapping = mappings[i];
                if (mapping.hostPriority > priority) {
                    break;
                }
                Object result = mappingNodes[i].execute(test, mapping, languageContext, interop, true);
                if (result == Boolean.TRUE) {
                    return true;
                }
            }
            return fallback.execute(test, interop, languageContext);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof TargetMappingType)) {
                return false;
            }
            TargetMappingType other = (TargetMappingType) obj;
            return Arrays.equals(this.mappings, other.mappings);
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(mappings);
        }

    }

    static final class PrimitiveType extends TypeCheckNode {
        final Class targetType;
        @CompilationFinal(dimensions = 1) final Class[] otherTypes;
        final int priority;

        PrimitiveType(Class targetType, Class[] otherTypes, int priority) {
            this.targetType = targetType;
            this.otherTypes = otherTypes;
            this.priority = priority;
        }

        @Override
        public int hashCode() {
            return ((targetType == null) ? 0 : targetType.hashCode());
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof PrimitiveType)) {
                return false;
            }
            PrimitiveType other = (PrimitiveType) obj;
            return Objects.equals(this.targetType, other.targetType) && Arrays.equals(this.otherTypes, other.otherTypes);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("Primitive[");
            sb.append(targetType.getTypeName());
            if (otherTypes.length > 0) {
                for (Class otherType : otherTypes) {
                    sb.append(", !");
                    sb.append(otherType.getTypeName());
                }
            }
            sb.append(']');
            return sb.toString();
        }

        @ExplodeLoop
        @Override
        public boolean execute(Object value, InteropLibrary interop, PolyglotLanguageContext languageContext) {
            for (Class otherType : otherTypes) {
                if (ToHostNode.canConvert(value, otherType, otherType, null, languageContext, priority, interop, null)) {
                    return false;
                }
            }
            return ToHostNode.canConvert(value, targetType, targetType, null, languageContext, priority, interop, null);
        }
    }

    @GenerateUncached
    abstract static class HostMethodProfileNode extends Node {
        public abstract SingleMethod execute(SingleMethod method);

        @Specialization
        static SingleMethod mono(SingleMethod.MHBase method) {
            return method;
        }

        @Specialization
        static SingleMethod mono(SingleMethod.ReflectBase method) {
            return method;
        }

        @Specialization(replaces = "mono")
        static SingleMethod poly(SingleMethod method) {
            return method;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy