Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Method reference support #18748

Merged
merged 13 commits into from Jun 7, 2016
5 changes: 5 additions & 0 deletions buildSrc/src/main/resources/forbidden/es-core-signatures.txt
Expand Up @@ -92,3 +92,8 @@ org.joda.time.DateTime#<init>(int, int, int, int, int, int)
org.joda.time.DateTime#<init>(int, int, int, int, int, int, int)
org.joda.time.DateTime#now()
org.joda.time.DateTimeZone#getDefault()

@defaultMessage Don't use MethodHandles in slow ways, except in tests.
java.lang.invoke.MethodHandle#invoke(java.lang.Object[])
java.lang.invoke.MethodHandle#invokeWithArguments(java.lang.Object[])
java.lang.invoke.MethodHandle#invokeWithArguments(java.util.List)
2 changes: 1 addition & 1 deletion modules/lang-painless/src/main/antlr/PainlessParser.g4
Expand Up @@ -74,7 +74,7 @@ decltype
;

funcref
: TYPE REF ID
: TYPE REF ( ID | NEW )
;

declvar
Expand Down
Expand Up @@ -22,7 +22,6 @@
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.node.ANode;

/**
* Used during the analysis phase to collect legal type casts and promotions
Expand Down
Expand Up @@ -22,6 +22,9 @@
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.RuntimeClass;

import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaConversionException;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
Expand Down Expand Up @@ -155,52 +158,118 @@ static MethodHandle arrayLengthGetter(Class<?> arrayType) {
}

/**
* Looks up handle for a dynamic method call.
* Looks up method entry for a dynamic method call.
* <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.
* Otherwise it returns the matching method.
* <p>
* @param receiverClass Class of the object to invoke the method on.
* @param name Name of the method.
* @param type Callsite signature. Need not match exactly, except the number of parameters.
* @return pointer to matching method to invoke. never returns null.
* @param arity arity of method
* @return matching method to invoke. never returns null.
* @throws IllegalArgumentException if no matching whitelisted method was found.
*/
static MethodHandle lookupMethod(Class<?> receiverClass, String name, MethodType type) {
// we don't consider receiver an argument/counting towards arity
type = type.dropParameterTypes(0, 1);
Definition.MethodKey key = new Definition.MethodKey(name, type.parameterCount());
// check whitelist for matching method
for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
RuntimeClass struct = Definition.getRuntimeClass(clazz);

if (struct != null) {
Method method = struct.methods.get(key);
if (method != null) {
return method.handle;
}
}
static Method lookupMethodInternal(Class<?> receiverClass, String name, int arity) {
Definition.MethodKey key = new Definition.MethodKey(name, arity);
// check whitelist for matching method
for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
RuntimeClass struct = Definition.getRuntimeClass(clazz);

for (final Class<?> iface : clazz.getInterfaces()) {
struct = Definition.getRuntimeClass(iface);
if (struct != null) {
Method method = struct.methods.get(key);
if (method != null) {
return method;
}
}

if (struct != null) {
Method method = struct.methods.get(key);
if (method != null) {
return method.handle;
}
for (Class<?> iface : clazz.getInterfaces()) {
struct = Definition.getRuntimeClass(iface);

if (struct != null) {
Method method = struct.methods.get(key);
if (method != null) {
return method;
}
}
}
}

throw new IllegalArgumentException("Unable to find dynamic method [" + name + "] with [" + arity + "] arguments " +
"for class [" + receiverClass.getCanonicalName() + "].");
}

/**
* 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 receiverClass Class of the object to invoke the method on.
* @param name Name of the method.
* @param args args passed to callsite
* @param recipe bitset marking functional parameters
* @return pointer to matching method to invoke. never returns null.
* @throws LambdaConversionException if a method reference cannot be converted to an functional interface
* @throws IllegalArgumentException if no matching whitelisted method was found.
*/
static MethodHandle lookupMethod(Lookup lookup, Class<?> receiverClass, String name,
Object args[], long recipe) throws LambdaConversionException {
Method method = lookupMethodInternal(receiverClass, name, args.length - 1);
MethodHandle handle = method.handle;

if (recipe != 0) {
MethodHandle filters[] = new MethodHandle[args.length];
for (int i = 0; i < args.length; i++) {
// its a functional reference, replace the argument with an impl
if ((recipe & (1L << (i - 1))) != 0) {
filters[i] = lookupReference(lookup, method.arguments.get(i - 1), (String) args[i]);
}
}
handle = MethodHandles.filterArguments(handle, 0, filters);
}

// no matching methods in whitelist found
throw new IllegalArgumentException("Unable to find dynamic method [" + name + "] with signature [" + type + "] " +
"for class [" + receiverClass.getCanonicalName() + "].");
}

return handle;
}

/** Returns a method handle to an implementation of clazz, given method reference signature
* @throws LambdaConversionException if a method reference cannot be converted to an functional interface
*/
private static MethodHandle lookupReference(Lookup lookup, Definition.Type clazz, String signature) throws LambdaConversionException {
int separator = signature.indexOf('.');
FunctionRef ref = new FunctionRef(clazz, signature.substring(0, separator), signature.substring(separator+1));
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);
}
// we could actually invoke and cache here (in non-capturing cases), but this is not a speedup.
MethodHandle factory = callSite.dynamicInvoker().asType(MethodType.methodType(clazz.clazz));
return MethodHandles.dropArguments(factory, 0, Object.class);
}


/**
* Looks up handle for a dynamic field getter (field load)
Expand Down
@@ -1,5 +1,7 @@
package org.elasticsearch.painless;

import org.elasticsearch.common.SuppressForbidden;

/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
Expand Down Expand Up @@ -66,14 +68,20 @@ static final class PIC extends MutableCallSite {
/** maximum number of types before we go megamorphic */
static final int MAX_DEPTH = 5;

private final Lookup lookup;
private final String name;
private final int flavor;
private final long recipe;
int depth; // pkg-protected for testing

PIC(String name, MethodType type, int flavor) {
PIC(Lookup lookup, String name, MethodType type, int flavor, long recipe) {
super(type);
this.lookup = lookup;
this.name = name;
this.flavor = flavor;
this.recipe = recipe;
assert recipe == 0 || flavor == METHOD_CALL;
assert Long.bitCount(recipe) <= type.parameterCount();

final MethodHandle fallback = FALLBACK.bindTo(this)
.asCollector(Object[].class, type.parameterCount())
Expand All @@ -93,10 +101,10 @@ static boolean checkClass(Class<?> clazz, Object receiver) {
/**
* Does a slow lookup against the whitelist.
*/
private static MethodHandle lookup(int flavor, Class<?> clazz, String name, MethodType type) {
private MethodHandle lookup(int flavor, Class<?> clazz, String name, Object[] args, long recipe) throws Throwable {
switch(flavor) {
case METHOD_CALL:
return Def.lookupMethod(clazz, name, type);
return Def.lookupMethod(lookup, clazz, name, args, recipe);
case LOAD:
return Def.lookupGetter(clazz, name);
case STORE:
Expand All @@ -115,11 +123,12 @@ private static MethodHandle lookup(int flavor, Class<?> clazz, String name, Meth
* Called when a new type is encountered (or, when we have encountered more than {@code MAX_DEPTH}
* types at this call site and given up on caching).
*/
@SuppressForbidden(reason = "slow path")
Object fallback(Object[] args) throws Throwable {
final MethodType type = type();
final Object receiver = args[0];
final Class<?> receiverClass = receiver.getClass();
final MethodHandle target = lookup(flavor, receiverClass, name, type).asType(type);
final MethodHandle target = lookup(flavor, receiverClass, name, args, recipe).asType(type);

if (depth >= MAX_DEPTH) {
// revert to a vtable call
Expand Down Expand Up @@ -161,8 +170,8 @@ Object fallback(Object[] args) throws Throwable {
* <p>
* see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic
*/
public static CallSite bootstrap(Lookup lookup, String name, MethodType type, int flavor) {
return new PIC(name, type, flavor);
public static CallSite bootstrap(Lookup lookup, String name, MethodType type, int flavor, long recipe) {
return new PIC(lookup, name, type, flavor, recipe);
}

}