Search in sources :

Example 1 with Method

use of org.elasticsearch.painless.Definition.Method in project elasticsearch by elastic.

the class Def method lookupReferenceInternal.

/** Returns a method handle to an implementation of clazz, given method reference signature. */
private static MethodHandle lookupReferenceInternal(Lookup lookup, Definition.Type clazz, String type, String call, Class<?>... captures) throws Throwable {
    final FunctionRef ref;
    if ("this".equals(type)) {
        // user written method
        Method interfaceMethod = clazz.struct.getFunctionalMethod();
        if (interfaceMethod == null) {
            throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " + "to [" + clazz.name + "], not a functional interface");
        }
        int arity = interfaceMethod.arguments.size() + captures.length;
        final MethodHandle handle;
        try {
            MethodHandle accessor = lookup.findStaticGetter(lookup.lookupClass(), getUserFunctionHandleFieldName(call, arity), MethodHandle.class);
            handle = (MethodHandle) accessor.invokeExact();
        } catch (NoSuchFieldException | IllegalAccessException e) {
            // because the arity does not match the expected interface type.
            if (call.contains("$")) {
                throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name + "] in [" + clazz.clazz + "]");
            }
            throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments.");
        }
        ref = new FunctionRef(clazz, interfaceMethod, handle, captures.length);
    } else {
        // whitelist lookup
        ref = new FunctionRef(clazz, type, call, captures.length);
    }
    final CallSite callSite;
    if (ref.needsBridges()) {
        callSite = LambdaMetafactory.altMetafactory(lookup, ref.invokedName, ref.invokedType, ref.samMethodType, ref.implMethod, ref.samMethodType, LambdaMetafactory.FLAG_BRIDGES, 1, ref.interfaceMethodType);
    } else {
        callSite = LambdaMetafactory.altMetafactory(lookup, ref.invokedName, ref.invokedType, ref.samMethodType, ref.implMethod, ref.samMethodType, 0);
    }
    return callSite.dynamicInvoker().asType(MethodType.methodType(clazz.clazz, captures));
}
Also used : CallSite(java.lang.invoke.CallSite) Method(org.elasticsearch.painless.Definition.Method) MethodHandle(java.lang.invoke.MethodHandle)

Example 2 with Method

use of org.elasticsearch.painless.Definition.Method in project elasticsearch by elastic.

the class Def method lookupReference.

/**
      * Returns an implementation of interfaceClass that calls receiverClass.name
      * <p>
      * This is just like LambdaMetaFactory, only with a dynamic type. The interface type is known,
      * so we simply need to lookup the matching implementation method based on receiver type.
      */
static MethodHandle lookupReference(Lookup lookup, String interfaceClass, Class<?> receiverClass, String name) throws Throwable {
    Definition.Type interfaceType = Definition.getType(interfaceClass);
    Method interfaceMethod = interfaceType.struct.getFunctionalMethod();
    if (interfaceMethod == null) {
        throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
    }
    int arity = interfaceMethod.arguments.size();
    Method implMethod = lookupMethodInternal(receiverClass, name, arity);
    return lookupReferenceInternal(lookup, interfaceType, implMethod.owner.name, implMethod.name, receiverClass);
}
Also used : Method(org.elasticsearch.painless.Definition.Method)

Example 3 with Method

use of org.elasticsearch.painless.Definition.Method in project elasticsearch by elastic.

the class Def method lookupMethod.

/**
     * Looks up handle for a dynamic method call, with lambda replacement
     * <p>
     * A dynamic method call for variable {@code x} of type {@code def} looks like:
     * {@code x.method(args...)}
     * <p>
     * This method traverses {@code recieverClass}'s class hierarchy (including interfaces)
     * until it finds a matching whitelisted method. If one is not found, it throws an exception.
     * Otherwise it returns a handle to the matching method.
     * <p>
     * @param lookup caller's lookup
     * @param callSiteType callsite's type
     * @param receiverClass Class of the object to invoke the method on.
     * @param name Name of the method.
     * @param args bootstrap args passed to callsite
     * @return pointer to matching method to invoke. never returns null.
     * @throws IllegalArgumentException if no matching whitelisted method was found.
     * @throws Throwable if a method reference cannot be converted to an functional interface
     */
static MethodHandle lookupMethod(Lookup lookup, MethodType callSiteType, Class<?> receiverClass, String name, Object[] args) throws Throwable {
    String recipeString = (String) args[0];
    int numArguments = callSiteType.parameterCount();
    // simple case: no lambdas
    if (recipeString.isEmpty()) {
        return lookupMethodInternal(receiverClass, name, numArguments - 1).handle;
    }
    // convert recipe string to a bitset for convenience (the code below should be refactored...)
    BitSet lambdaArgs = new BitSet();
    for (int i = 0; i < recipeString.length(); i++) {
        lambdaArgs.set(recipeString.charAt(i));
    }
    // otherwise: first we have to compute the "real" arity. This is because we have extra arguments:
    // e.g. f(a, g(x), b, h(y), i()) looks like f(a, g, x, b, h, y, i). 
    int arity = callSiteType.parameterCount() - 1;
    int upTo = 1;
    for (int i = 1; i < numArguments; i++) {
        if (lambdaArgs.get(i - 1)) {
            String signature = (String) args[upTo++];
            int numCaptures = Integer.parseInt(signature.substring(signature.indexOf(',') + 1));
            arity -= numCaptures;
        }
    }
    // lookup the method with the proper arity, then we know everything (e.g. interface types of parameters).
    // based on these we can finally link any remaining lambdas that were deferred.
    Method method = lookupMethodInternal(receiverClass, name, arity);
    MethodHandle handle = method.handle;
    int replaced = 0;
    upTo = 1;
    for (int i = 1; i < numArguments; i++) {
        // its a functional reference, replace the argument with an impl
        if (lambdaArgs.get(i - 1)) {
            // decode signature of form 'type.call,2' 
            String signature = (String) args[upTo++];
            int separator = signature.lastIndexOf('.');
            int separator2 = signature.indexOf(',');
            String type = signature.substring(1, separator);
            String call = signature.substring(separator + 1, separator2);
            int numCaptures = Integer.parseInt(signature.substring(separator2 + 1));
            Class<?>[] captures = new Class<?>[numCaptures];
            for (int capture = 0; capture < captures.length; capture++) {
                captures[capture] = callSiteType.parameterType(i + 1 + capture);
            }
            MethodHandle filter;
            Definition.Type interfaceType = method.arguments.get(i - 1 - replaced);
            if (signature.charAt(0) == 'S') {
                // the implementation is strongly typed, now that we know the interface type,
                // we have everything.
                filter = lookupReferenceInternal(lookup, interfaceType, type, call, captures);
            } else if (signature.charAt(0) == 'D') {
                // the interface type is now known, but we need to get the implementation.
                // this is dynamically based on the receiver type (and cached separately, underneath
                // this cache). It won't blow up since we never nest here (just references)
                MethodType nestedType = MethodType.methodType(interfaceType.clazz, captures);
                CallSite nested = DefBootstrap.bootstrap(lookup, call, nestedType, 0, DefBootstrap.REFERENCE, interfaceType.name);
                filter = nested.dynamicInvoker();
            } else {
                throw new AssertionError();
            }
            // the filter now ignores the signature (placeholder) on the stack
            filter = MethodHandles.dropArguments(filter, 0, String.class);
            handle = MethodHandles.collectArguments(handle, i, filter);
            i += numCaptures;
            replaced += numCaptures;
        }
    }
    return handle;
}
Also used : MethodType(java.lang.invoke.MethodType) BitSet(java.util.BitSet) RuntimeClass(org.elasticsearch.painless.Definition.RuntimeClass) CallSite(java.lang.invoke.CallSite) Method(org.elasticsearch.painless.Definition.Method) MethodHandle(java.lang.invoke.MethodHandle)

Example 4 with Method

use of org.elasticsearch.painless.Definition.Method in project elasticsearch by elastic.

the class SSource method analyze.

public void analyze() {
    Map<MethodKey, Method> methods = new HashMap<>();
    for (SFunction function : functions) {
        function.generateSignature();
        MethodKey key = new MethodKey(function.name, function.parameters.size());
        if (methods.put(key, function.method) != null) {
            throw createError(new IllegalArgumentException("Duplicate functions with name [" + function.name + "]."));
        }
    }
    analyze(Locals.newProgramScope(methods.values()));
}
Also used : HashMap(java.util.HashMap) Method(org.elasticsearch.painless.Definition.Method) MethodKey(org.elasticsearch.painless.Definition.MethodKey)

Example 5 with Method

use of org.elasticsearch.painless.Definition.Method in project elasticsearch by elastic.

the class ELambda method analyze.

@Override
void analyze(Locals locals) {
    final Type returnType;
    final List<String> actualParamTypeStrs;
    Method interfaceMethod;
    // inspect the target first, set interface method if we know it.
    if (expected == null) {
        interfaceMethod = null;
        // we don't know anything: treat as def
        returnType = Definition.DEF_TYPE;
        // don't infer any types
        actualParamTypeStrs = paramTypeStrs;
    } else {
        // we know the method statically, infer return type and any unknown/def types
        interfaceMethod = expected.struct.getFunctionalMethod();
        if (interfaceMethod == null) {
            throw createError(new IllegalArgumentException("Cannot pass lambda to [" + expected.name + "], not a functional interface"));
        }
        // check arity before we manipulate parameters
        if (interfaceMethod.arguments.size() != paramTypeStrs.size())
            throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name + "] in [" + expected.clazz + "]");
        // for method invocation, its allowed to ignore the return value
        if (interfaceMethod.rtn == Definition.VOID_TYPE) {
            returnType = Definition.DEF_TYPE;
        } else {
            returnType = interfaceMethod.rtn;
        }
        // replace any def types with the actual type (which could still be def)
        actualParamTypeStrs = new ArrayList<String>();
        for (int i = 0; i < paramTypeStrs.size(); i++) {
            String paramType = paramTypeStrs.get(i);
            if (paramType.equals(Definition.DEF_TYPE.name)) {
                actualParamTypeStrs.add(interfaceMethod.arguments.get(i).name);
            } else {
                actualParamTypeStrs.add(paramType);
            }
        }
    }
    // gather any variables used by the lambda body first.
    Set<String> variables = new HashSet<>();
    for (AStatement statement : statements) {
        statement.extractVariables(variables);
    }
    // any of those variables defined in our scope need to be captured
    captures = new ArrayList<>();
    for (String variable : variables) {
        if (locals.hasVariable(variable)) {
            captures.add(locals.getVariable(location, variable));
        }
    }
    // prepend capture list to lambda's arguments
    List<String> paramTypes = new ArrayList<>();
    List<String> paramNames = new ArrayList<>();
    for (Variable var : captures) {
        paramTypes.add(var.type.name);
        paramNames.add(var.name);
    }
    paramTypes.addAll(actualParamTypeStrs);
    paramNames.addAll(paramNameStrs);
    // desugar lambda body into a synthetic method
    desugared = new SFunction(reserved, location, returnType.name, name, paramTypes, paramNames, statements, true);
    desugared.generateSignature();
    desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), returnType, desugared.parameters, captures.size(), reserved.getMaxLoopCounter()));
    // setup method reference to synthetic method
    if (expected == null) {
        ref = null;
        actual = Definition.getType("String");
        defPointer = "Sthis." + name + "," + captures.size();
    } else {
        defPointer = null;
        try {
            ref = new FunctionRef(expected, interfaceMethod, desugared.method, captures.size());
        } catch (IllegalArgumentException e) {
            throw createError(e);
        }
        actual = expected;
    }
}
Also used : Variable(org.elasticsearch.painless.Locals.Variable) ArrayList(java.util.ArrayList) Method(org.elasticsearch.painless.Definition.Method) Type(org.elasticsearch.painless.Definition.Type) HashSet(java.util.HashSet) FunctionRef(org.elasticsearch.painless.FunctionRef)

Aggregations

Method (org.elasticsearch.painless.Definition.Method)13 MethodKey (org.elasticsearch.painless.Definition.MethodKey)5 Struct (org.elasticsearch.painless.Definition.Struct)4 Type (org.elasticsearch.painless.Definition.Type)3 CallSite (java.lang.invoke.CallSite)2 MethodHandle (java.lang.invoke.MethodHandle)2 MethodType (java.lang.invoke.MethodType)2 ArrayList (java.util.ArrayList)2 List (java.util.List)2 Map (java.util.Map)2 Field (org.elasticsearch.painless.Definition.Field)2 RuntimeClass (org.elasticsearch.painless.Definition.RuntimeClass)2 FunctionRef (org.elasticsearch.painless.FunctionRef)2 Location (org.elasticsearch.painless.Location)2 IOException (java.io.IOException)1 PrintStream (java.io.PrintStream)1 Modifier (java.lang.reflect.Modifier)1 StandardCharsets (java.nio.charset.StandardCharsets)1 Files (java.nio.file.Files)1 Path (java.nio.file.Path)1