Skip to content

Commit

Permalink
Painless: Complete Removal of Painless Type (#31699)
Browse files Browse the repository at this point in the history
This completes the removal of Painless Type. The new data structures in the definition are a map of names (String) to Java Classes and a map of Java Classes to Painless Structs. The names to Java Classes map can contain a 2 to 1 ratio of names to classes depending on whether or not a short (imported) name is used. The Java Classes to Painless Structs is 1 to 1 always where the Java Class name must match the Painless Struct name. This should lead a significantly simpler type system in Painless moving forward since the Painless Type only held redundant information since Painless does not support generics.
  • Loading branch information
jdconrad committed Jul 3, 2018
1 parent 7efc856 commit 1d4d009
Show file tree
Hide file tree
Showing 35 changed files with 541 additions and 705 deletions.
6 changes: 3 additions & 3 deletions modules/lang-painless/src/main/antlr/PainlessLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@

lexer grammar PainlessLexer;

@members{
@members {
/**
* Check against the current whitelist to determine whether a token is a type
* or not. Called by the {@code TYPE} token defined in {@code PainlessLexer.g4}.
* See also
* <a href="https://en.wikipedia.org/wiki/The_lexer_hack">The lexer hack</a>.
*/
protected abstract boolean isSimpleType(String name);
protected abstract boolean isType(String name);
/**
* Is the preceding {@code /} a the beginning of a regex (true) or a division
Expand Down Expand Up @@ -133,7 +133,7 @@ NULL: 'null';
// or not. Note this works by processing one character at a time
// and the rule is added or removed as this happens. This is also known
// as "the lexer hack." See (https://en.wikipedia.org/wiki/The_lexer_hack).
TYPE: ID ( DOT ID )* { isSimpleType(getText()) }?;
TYPE: ID ( DOT ID )* { isType(getText()) }?;
ID: [_a-zA-Z] [_a-zA-Z0-9]*;

mode AFTER_DOT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ static Method lookupMethodInternal(Definition definition, Class<?> receiverClass
Definition.MethodKey key = new Definition.MethodKey(name, arity);
// check whitelist for matching method
for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
Struct struct = definition.RuntimeClassToStruct(clazz);
Struct struct = definition.getPainlessStructFromJavaClass(clazz);

if (struct != null) {
Method method = struct.methods.get(key);
Expand All @@ -195,7 +195,7 @@ static Method lookupMethodInternal(Definition definition, Class<?> receiverClass
}

for (Class<?> iface : clazz.getInterfaces()) {
struct = definition.RuntimeClassToStruct(iface);
struct = definition.getPainlessStructFromJavaClass(iface);

if (struct != null) {
Method method = struct.methods.get(key);
Expand Down Expand Up @@ -279,7 +279,7 @@ static MethodHandle lookupMethod(Definition definition, Lookup lookup, MethodTyp
captures[capture] = callSiteType.parameterType(i + 1 + capture);
}
MethodHandle filter;
Definition.Type interfaceType = definition.ClassToType(method.arguments.get(i - 1 - replaced));
Class<?> 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.
Expand All @@ -293,14 +293,14 @@ static MethodHandle lookupMethod(Definition definition, Lookup lookup, MethodTyp
// 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);
MethodType nestedType = MethodType.methodType(interfaceType, captures);
CallSite nested = DefBootstrap.bootstrap(definition,
lookup,
call,
nestedType,
0,
DefBootstrap.REFERENCE,
interfaceType.name);
Definition.ClassToName(interfaceType));
filter = nested.dynamicInvoker();
} else {
throw new AssertionError();
Expand All @@ -324,8 +324,8 @@ static MethodHandle lookupMethod(Definition definition, Lookup lookup, MethodTyp
*/
static MethodHandle lookupReference(Definition definition, Lookup lookup, String interfaceClass,
Class<?> receiverClass, String name) throws Throwable {
Definition.Type interfaceType = definition.getType(interfaceClass);
Method interfaceMethod = interfaceType.struct.functionalMethod;
Class<?> interfaceType = definition.getJavaClassFromPainlessType(interfaceClass);
Method interfaceMethod = definition.getPainlessStructFromJavaClass(interfaceType).functionalMethod;
if (interfaceMethod == null) {
throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
}
Expand All @@ -337,15 +337,15 @@ static MethodHandle lookupReference(Definition definition, Lookup lookup, String

/** Returns a method handle to an implementation of clazz, given method reference signature. */
private static MethodHandle lookupReferenceInternal(Definition definition, Lookup lookup,
Definition.Type clazz, String type, String call, Class<?>... captures)
Class<?> clazz, String type, String call, Class<?>... captures)
throws Throwable {
final FunctionRef ref;
if ("this".equals(type)) {
// user written method
Method interfaceMethod = clazz.struct.functionalMethod;
Method interfaceMethod = definition.getPainlessStructFromJavaClass(clazz).functionalMethod;
if (interfaceMethod == null) {
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
"to [" + clazz.name + "], not a functional interface");
"to [" + Definition.ClassToName(clazz) + "], not a functional interface");
}
int arity = interfaceMethod.arguments.size() + captures.length;
final MethodHandle handle;
Expand All @@ -359,14 +359,14 @@ private static MethodHandle lookupReferenceInternal(Definition definition, Looku
// 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 + "]");
"] in [" + clazz + "]");
}
throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments.");
}
ref = new FunctionRef(clazz.clazz, interfaceMethod, call, handle.type(), captures.length);
ref = new FunctionRef(clazz, interfaceMethod, call, handle.type(), captures.length);
} else {
// whitelist lookup
ref = new FunctionRef(definition, clazz.clazz, type, call, captures.length);
ref = new FunctionRef(definition, clazz, type, call, captures.length);
}
final CallSite callSite = LambdaBootstrap.lambdaBootstrap(
lookup,
Expand All @@ -379,7 +379,7 @@ private static MethodHandle lookupReferenceInternal(Definition definition, Looku
ref.delegateMethodType,
ref.isDelegateInterface ? 1 : 0
);
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz.clazz, captures));
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, captures));
}

/** gets the field name used to lookup up the MethodHandle for a function. */
Expand Down Expand Up @@ -416,7 +416,7 @@ public static String getUserFunctionHandleFieldName(String name, int arity) {
static MethodHandle lookupGetter(Definition definition, Class<?> receiverClass, String name) {
// first try whitelist
for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
Struct struct = definition.RuntimeClassToStruct(clazz);
Struct struct = definition.getPainlessStructFromJavaClass(clazz);

if (struct != null) {
MethodHandle handle = struct.getters.get(name);
Expand All @@ -426,7 +426,7 @@ static MethodHandle lookupGetter(Definition definition, Class<?> receiverClass,
}

for (final Class<?> iface : clazz.getInterfaces()) {
struct = definition.RuntimeClassToStruct(iface);
struct = definition.getPainlessStructFromJavaClass(iface);

if (struct != null) {
MethodHandle handle = struct.getters.get(name);
Expand Down Expand Up @@ -487,7 +487,7 @@ static MethodHandle lookupGetter(Definition definition, Class<?> receiverClass,
static MethodHandle lookupSetter(Definition definition, Class<?> receiverClass, String name) {
// first try whitelist
for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
Struct struct = definition.RuntimeClassToStruct(clazz);
Struct struct = definition.getPainlessStructFromJavaClass(clazz);

if (struct != null) {
MethodHandle handle = struct.setters.get(name);
Expand All @@ -497,7 +497,7 @@ static MethodHandle lookupSetter(Definition definition, Class<?> receiverClass,
}

for (final Class<?> iface : clazz.getInterfaces()) {
struct = definition.RuntimeClassToStruct(iface);
struct = definition.getPainlessStructFromJavaClass(iface);

if (struct != null) {
MethodHandle handle = struct.setters.get(name);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
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 All @@ -21,6 +17,10 @@
* under the License.
*/

package org.elasticsearch.painless;

import org.elasticsearch.common.SuppressForbidden;

import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
Expand Down Expand Up @@ -72,24 +72,24 @@ private DefBootstrap() {} // no instance!
public static final int SHIFT_OPERATOR = 9;
/** static bootstrap parameter indicating a request to normalize an index for array-like-access */
public static final int INDEX_NORMALIZE = 10;

// constants for the flags parameter of operators
/**
* static bootstrap parameter indicating the binary operator allows nulls (e.g. == and +)
/**
* static bootstrap parameter indicating the binary operator allows nulls (e.g. == and +)
* <p>
* requires additional {@link MethodHandles#catchException} guard, which will invoke
* the fallback if a null is encountered.
*/
public static final int OPERATOR_ALLOWS_NULL = 1 << 0;

/**
* static bootstrap parameter indicating the binary operator is part of compound assignment (e.g. +=).
* <p>
* may require {@link MethodHandles#explicitCastArguments}, or a dynamic cast
* to cast back to the receiver's type, depending on types seen.
*/
public static final int OPERATOR_COMPOUND_ASSIGNMENT = 1 << 1;

/**
* static bootstrap parameter indicating an explicit cast to the return type.
* <p>
Expand Down Expand Up @@ -129,7 +129,7 @@ static final class PIC extends MutableCallSite {

setTarget(fallback);
}

/**
* guard method for inline caching: checks the receiver's class is the same
* as the cached class
Expand Down Expand Up @@ -162,7 +162,7 @@ private MethodHandle lookup(int flavor, String name, Class<?> receiver) throws T
default: throw new AssertionError();
}
}

/**
* Creates the {@link MethodHandle} for the megamorphic call site
* using {@link ClassValue} and {@link MethodHandles#exactInvoker(MethodType)}:
Expand All @@ -182,7 +182,7 @@ protected MethodHandle computeValue(Class<?> receiverType) {
}
};
return MethodHandles.foldArguments(MethodHandles.exactInvoker(type),
MEGAMORPHIC_LOOKUP.bindTo(megamorphicCache));
MEGAMORPHIC_LOOKUP.bindTo(megamorphicCache));
}

/**
Expand All @@ -195,18 +195,18 @@ Object fallback(final Object[] callArgs) throws Throwable {
if (depth >= MAX_DEPTH) {
// we revert the whole cache and build a new megamorphic one
final MethodHandle target = this.createMegamorphicHandle();

setTarget(target);
return target.invokeWithArguments(callArgs);
return target.invokeWithArguments(callArgs);
} else {
final Class<?> receiver = callArgs[0].getClass();
final MethodHandle target = lookup(flavor, name, receiver).asType(type());

MethodHandle test = CHECK_CLASS.bindTo(receiver);
MethodHandle guard = MethodHandles.guardWithTest(test, target, getTarget());

depth++;

setTarget(guard);
return target.invokeWithArguments(callArgs);
}
Expand All @@ -225,15 +225,15 @@ Object fallback(final Object[] callArgs) throws Throwable {
MethodType.methodType(Object.class, Object[].class));
MethodHandle mh = publicLookup.findVirtual(ClassValue.class, "get",
MethodType.methodType(Object.class, Class.class));
mh = MethodHandles.filterArguments(mh, 1,
mh = MethodHandles.filterArguments(mh, 1,
publicLookup.findVirtual(Object.class, "getClass", MethodType.methodType(Class.class)));
MEGAMORPHIC_LOOKUP = mh.asType(mh.type().changeReturnType(MethodHandle.class));
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}
}

/**
* CallSite that implements the monomorphic inlining cache (for operators).
*/
Expand All @@ -252,14 +252,14 @@ static final class MIC extends MutableCallSite {
if (initialDepth > 0) {
initialized = true;
}

MethodHandle fallback = FALLBACK.bindTo(this)
.asCollector(Object[].class, type.parameterCount())
.asType(type);

setTarget(fallback);
}

/**
* Does a slow lookup for the operator
*/
Expand Down Expand Up @@ -290,7 +290,7 @@ private MethodHandle lookup(Object[] args) throws Throwable {
default: throw new AssertionError();
}
}

private MethodHandle lookupGeneric() {
MethodHandle target = DefMath.lookupGeneric(name);
if ((flags & OPERATOR_EXPLICIT_CAST) != 0) {
Expand All @@ -302,7 +302,7 @@ private MethodHandle lookupGeneric() {
}
return target;
}

/**
* Called when a new type is encountered or if cached type does not match.
* In that case we revert to a generic, but slower operator handling.
Expand All @@ -315,7 +315,7 @@ Object fallback(Object[] args) throws Throwable {
setTarget(generic.asType(type()));
return generic.invokeWithArguments(args);
}

final MethodType type = type();
MethodHandle target = lookup(args);
// for math operators: WrongMethodType can be confusing. convert into a ClassCastException if they screw up.
Expand Down Expand Up @@ -361,18 +361,18 @@ Object fallback(Object[] args) throws Throwable {
// very special cases, where even the receiver can be null (see JLS rules for string concat)
// we wrap + with an NPE catcher, and use our generic method in that case.
if (flavor == BINARY_OPERATOR && (flags & OPERATOR_ALLOWS_NULL) != 0) {
MethodHandle handler = MethodHandles.dropArguments(lookupGeneric().asType(type()),
0,
MethodHandle handler = MethodHandles.dropArguments(lookupGeneric().asType(type()),
0,
NullPointerException.class);
guard = MethodHandles.catchException(guard, NullPointerException.class, handler);
}

initialized = true;

setTarget(guard);
return target.invokeWithArguments(args);
}

/**
* guard method for inline caching: checks the receiver's class is the same
* as the cached class
Expand All @@ -388,15 +388,15 @@ static boolean checkLHS(Class<?> clazz, Object leftObject) {
static boolean checkRHS(Class<?> left, Class<?> right, Object leftObject, Object rightObject) {
return rightObject.getClass() == right;
}

/**
* guard method for inline caching: checks the receiver's class and the first argument
* are the same as the cached receiver and first argument.
*/
static boolean checkBoth(Class<?> left, Class<?> right, Object leftObject, Object rightObject) {
return leftObject.getClass() == left && rightObject.getClass() == right;
}

private static final MethodHandle CHECK_LHS;
private static final MethodHandle CHECK_RHS;
private static final MethodHandle CHECK_BOTH;
Expand Down
Loading

0 comments on commit 1d4d009

Please sign in to comment.