Merge pull request #18958 from rmuir/explicit_casts

Fix explicit casts and improve tests.
This commit is contained in:
Robert Muir 2016-06-20 13:40:11 -04:00 committed by GitHub
commit e0a8213deb
23 changed files with 580 additions and 157 deletions

View File

@ -34,6 +34,11 @@ public final class CompilerSettings {
*/ */
public static final String PICKY = "picky"; public static final String PICKY = "picky";
/**
* For testing: do not use.
*/
public static final String INITIAL_CALL_SITE_DEPTH = "initialCallSiteDepth";
/** /**
* The maximum number of statements allowed to be run in a loop. * The maximum number of statements allowed to be run in a loop.
*/ */
@ -45,6 +50,11 @@ public final class CompilerSettings {
*/ */
private boolean picky = false; private boolean picky = false;
/**
* For testing. Do not use.
*/
private int initialCallSiteDepth = 0;
/** /**
* Returns the value for the cumulative total number of statements that can be made in all loops * Returns the value for the cumulative total number of statements that can be made in all loops
* in a script before an exception is thrown. This attempts to prevent infinite loops. Note if * in a script before an exception is thrown. This attempts to prevent infinite loops. Note if
@ -78,4 +88,20 @@ public final class CompilerSettings {
public void setPicky(boolean picky) { public void setPicky(boolean picky) {
this.picky = picky; this.picky = picky;
} }
/**
* Returns initial call site depth. This means we pretend we've already seen N different types,
* to better exercise fallback code in tests.
*/
public int getInitialCallSiteDepth() {
return initialCallSiteDepth;
}
/**
* For testing megamorphic fallbacks. Do not use.
* @see #getInitialCallSiteDepth()
*/
public void setInitialCallSiteDepth(int depth) {
this.initialCallSiteDepth = depth;
}
} }

View File

@ -287,6 +287,7 @@ public final class Def {
CallSite nested = DefBootstrap.bootstrap(lookup, CallSite nested = DefBootstrap.bootstrap(lookup,
call, call,
nestedType, nestedType,
0,
DefBootstrap.REFERENCE, DefBootstrap.REFERENCE,
interfaceType.name); interfaceType.name);
filter = nested.dynamicInvoker(); filter = nested.dynamicInvoker();

View File

@ -27,6 +27,7 @@ import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite; import java.lang.invoke.MutableCallSite;
import java.lang.invoke.WrongMethodTypeException;
/** /**
* Painless invokedynamic bootstrap for the call site. * Painless invokedynamic bootstrap for the call site.
@ -80,10 +81,19 @@ public final class DefBootstrap {
/** /**
* static bootstrap parameter indicating the binary operator is part of compound assignment (e.g. +=). * 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; public static final int OPERATOR_COMPOUND_ASSIGNMENT = 1 << 1;
/**
* static bootstrap parameter indicating an explicit cast to the return type.
* <p>
* may require {@link MethodHandles#explicitCastArguments}, depending on types seen.
*/
public static final int OPERATOR_EXPLICIT_CAST = 1 << 2;
/** /**
* CallSite that implements the polymorphic inlining cache (PIC). * CallSite that implements the polymorphic inlining cache (PIC).
*/ */
@ -97,7 +107,7 @@ public final class DefBootstrap {
private final Object[] args; private final Object[] args;
int depth; // pkg-protected for testing int depth; // pkg-protected for testing
PIC(Lookup lookup, String name, MethodType type, int flavor, Object[] args) { PIC(Lookup lookup, String name, MethodType type, int initialDepth, int flavor, Object[] args) {
super(type); super(type);
if (type.parameterType(0) != Object.class) { if (type.parameterType(0) != Object.class) {
throw new BootstrapMethodError("The receiver type (1st arg) of invokedynamic descriptor must be Object."); throw new BootstrapMethodError("The receiver type (1st arg) of invokedynamic descriptor must be Object.");
@ -106,6 +116,7 @@ public final class DefBootstrap {
this.name = name; this.name = name;
this.flavor = flavor; this.flavor = flavor;
this.args = args; this.args = args;
this.depth = initialDepth;
MethodHandle fallback = FALLBACK.bindTo(this) MethodHandle fallback = FALLBACK.bindTo(this)
.asCollector(Object[].class, type.parameterCount()) .asCollector(Object[].class, type.parameterCount())
@ -226,11 +237,14 @@ public final class DefBootstrap {
private final int flavor; private final int flavor;
private final int flags; private final int flags;
MIC(String name, MethodType type, int flavor, int flags) { MIC(String name, MethodType type, int initialDepth, int flavor, int flags) {
super(type); super(type);
this.name = name; this.name = name;
this.flavor = flavor; this.flavor = flavor;
this.flags = flags; this.flags = flags;
if (initialDepth > 0) {
initialized = true;
}
MethodHandle fallback = FALLBACK.bindTo(this) MethodHandle fallback = FALLBACK.bindTo(this)
.asCollector(Object[].class, type.parameterCount()) .asCollector(Object[].class, type.parameterCount())
@ -248,7 +262,9 @@ public final class DefBootstrap {
case SHIFT_OPERATOR: case SHIFT_OPERATOR:
// shifts are treated as unary, as java allows long arguments without a cast (but bits are ignored) // shifts are treated as unary, as java allows long arguments without a cast (but bits are ignored)
MethodHandle unary = DefMath.lookupUnary(args[0].getClass(), name); MethodHandle unary = DefMath.lookupUnary(args[0].getClass(), name);
if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) { if ((flags & OPERATOR_EXPLICIT_CAST) != 0) {
unary = DefMath.cast(type().returnType(), unary);
} else if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) {
unary = DefMath.cast(args[0].getClass(), unary); unary = DefMath.cast(args[0].getClass(), unary);
} }
return unary; return unary;
@ -257,7 +273,9 @@ public final class DefBootstrap {
return lookupGeneric(); // can handle nulls, casts if supported return lookupGeneric(); // can handle nulls, casts if supported
} else { } else {
MethodHandle binary = DefMath.lookupBinary(args[0].getClass(), args[1].getClass(), name); MethodHandle binary = DefMath.lookupBinary(args[0].getClass(), args[1].getClass(), name);
if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) { if ((flags & OPERATOR_EXPLICIT_CAST) != 0) {
binary = DefMath.cast(type().returnType(), binary);
} else if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) {
binary = DefMath.cast(args[0].getClass(), binary); binary = DefMath.cast(args[0].getClass(), binary);
} }
return binary; return binary;
@ -267,11 +285,15 @@ public final class DefBootstrap {
} }
private MethodHandle lookupGeneric() { private MethodHandle lookupGeneric() {
if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) { MethodHandle target = DefMath.lookupGeneric(name);
return DefMath.lookupGenericWithCast(name); if ((flags & OPERATOR_EXPLICIT_CAST) != 0) {
} else { // static cast to the return type
return DefMath.lookupGeneric(name); target = DefMath.dynamicCast(target, type().returnType());
} else if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) {
// dynamic cast to the receiver's type
target = DefMath.dynamicCast(target);
} }
return target;
} }
/** /**
@ -288,7 +310,15 @@ public final class DefBootstrap {
} }
final MethodType type = type(); final MethodType type = type();
final MethodHandle target = lookup(args).asType(type); MethodHandle target = lookup(args);
// for math operators: WrongMethodType can be confusing. convert into a ClassCastException if they screw up.
try {
target = target.asType(type);
} catch (WrongMethodTypeException e) {
Exception exc = new ClassCastException("Cannot cast from: " + target.type().returnType() + " to " + type.returnType());
exc.initCause(e);
throw exc;
}
final MethodHandle test; final MethodHandle test;
if (flavor == BINARY_OPERATOR || flavor == SHIFT_OPERATOR) { if (flavor == BINARY_OPERATOR || flavor == SHIFT_OPERATOR) {
@ -384,12 +414,16 @@ public final class DefBootstrap {
/** /**
* invokeDynamic bootstrap method * invokeDynamic bootstrap method
* <p> * <p>
* In addition to ordinary parameters, we also take a static parameter {@code flavor} which * In addition to ordinary parameters, we also take some static parameters:
* tells us what type of dynamic call it is (and which part of whitelist to look at). * <ul>
* <li>{@code initialDepth}: initial call site depth. this is used to exercise megamorphic fallback.
* <li>{@code flavor}: type of dynamic call it is (and which part of whitelist to look at).
* <li>{@code args}: flavor-specific args.
* </ul>
* <p> * <p>
* see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic * 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, Object... args) { public static CallSite bootstrap(Lookup lookup, String name, MethodType type, int initialDepth, int flavor, Object... args) {
// validate arguments // validate arguments
switch(flavor) { switch(flavor) {
// "function-call" like things get a polymorphic cache // "function-call" like things get a polymorphic cache
@ -408,7 +442,7 @@ public final class DefBootstrap {
if (args.length != numLambdas + 1) { if (args.length != numLambdas + 1) {
throw new BootstrapMethodError("Illegal number of parameters: expected " + numLambdas + " references"); throw new BootstrapMethodError("Illegal number of parameters: expected " + numLambdas + " references");
} }
return new PIC(lookup, name, type, flavor, args); return new PIC(lookup, name, type, initialDepth, flavor, args);
case LOAD: case LOAD:
case STORE: case STORE:
case ARRAY_LOAD: case ARRAY_LOAD:
@ -417,7 +451,7 @@ public final class DefBootstrap {
if (args.length > 0) { if (args.length > 0) {
throw new BootstrapMethodError("Illegal static bootstrap parameters for flavor: " + flavor); throw new BootstrapMethodError("Illegal static bootstrap parameters for flavor: " + flavor);
} }
return new PIC(lookup, name, type, flavor, args); return new PIC(lookup, name, type, initialDepth, flavor, args);
case REFERENCE: case REFERENCE:
if (args.length != 1) { if (args.length != 1) {
throw new BootstrapMethodError("Invalid number of parameters for reference call"); throw new BootstrapMethodError("Invalid number of parameters for reference call");
@ -425,7 +459,7 @@ public final class DefBootstrap {
if (args[0] instanceof String == false) { if (args[0] instanceof String == false) {
throw new BootstrapMethodError("Illegal parameter for reference call: " + args[0]); throw new BootstrapMethodError("Illegal parameter for reference call: " + args[0]);
} }
return new PIC(lookup, name, type, flavor, args); return new PIC(lookup, name, type, initialDepth, flavor, args);
// operators get monomorphic cache, with a generic impl for a fallback // operators get monomorphic cache, with a generic impl for a fallback
case UNARY_OPERATOR: case UNARY_OPERATOR:
@ -442,11 +476,11 @@ public final class DefBootstrap {
// we just don't need it anywhere else. // we just don't need it anywhere else.
throw new BootstrapMethodError("This parameter is only supported for BINARY_OPERATORs"); throw new BootstrapMethodError("This parameter is only supported for BINARY_OPERATORs");
} }
if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0 && flavor != BINARY_OPERATOR) { if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0 && flavor != BINARY_OPERATOR && flavor != SHIFT_OPERATOR) {
// we just don't need it anywhere else. // we just don't need it anywhere else.
throw new BootstrapMethodError("This parameter is only supported for BINARY_OPERATORs"); throw new BootstrapMethodError("This parameter is only supported for BINARY/SHIFT_OPERATORs");
} }
return new MIC(name, type, flavor, flags); return new MIC(name, type, initialDepth, flavor, flags);
default: default:
throw new BootstrapMethodError("Illegal static bootstrap parameter for flavor: " + flavor); throw new BootstrapMethodError("Illegal static bootstrap parameter for flavor: " + flavor);
} }

View File

@ -1027,25 +1027,7 @@ public class DefMath {
* class if its not a boxed type. * class if its not a boxed type.
*/ */
private static Class<?> unbox(Class<?> clazz) { private static Class<?> unbox(Class<?> clazz) {
if (clazz == Boolean.class) { return MethodType.methodType(clazz).unwrap().returnType();
return boolean.class;
} else if (clazz == Byte.class) {
return byte.class;
} else if (clazz == Short.class) {
return short.class;
} else if (clazz == Character.class) {
return char.class;
} else if (clazz == Integer.class) {
return int.class;
} else if (clazz == Long.class) {
return long.class;
} else if (clazz == Float.class) {
return float.class;
} else if (clazz == Double.class) {
return double.class;
} else {
return clazz;
}
} }
/** Unary promotion. All Objects are promoted to Object. */ /** Unary promotion. All Objects are promoted to Object. */
@ -1147,39 +1129,49 @@ public class DefMath {
return TYPE_OP_MAPPING.get(Object.class).get(name); return TYPE_OP_MAPPING.get(Object.class).get(name);
} }
/** /**
* Slow dynamic cast: casts {@code returnValue} to the runtime type of {@code lhs} * Slow dynamic cast: casts {@code returnValue} to the runtime type of {@code lhs}
* based upon inspection. If {@code lhs} is null, no cast takes place. * based upon inspection. If {@code lhs} is null, no cast takes place.
* This is used for the generic fallback case of compound assignment. * This is used for the generic fallback case of compound assignment.
*/ */
static Object dynamicCast(Object returnValue, Object lhs) { static Object dynamicReceiverCast(Object returnValue, Object lhs) {
if (lhs != null) { if (lhs != null) {
Class<?> c = lhs.getClass(); return dynamicCast(lhs.getClass(), returnValue);
if (c == returnValue.getClass()) {
return returnValue;
}
if (c == Integer.class) {
return getNumber(returnValue).intValue();
} else if (c == Long.class) {
return getNumber(returnValue).longValue();
} else if (c == Double.class) {
return getNumber(returnValue).doubleValue();
} else if (c == Float.class) {
return getNumber(returnValue).floatValue();
} else if (c == Short.class) {
return getNumber(returnValue).shortValue();
} else if (c == Byte.class) {
return getNumber(returnValue).byteValue();
} else if (c == Character.class) {
return (char) getNumber(returnValue).intValue();
}
return lhs.getClass().cast(returnValue);
} else { } else {
return returnValue; return returnValue;
} }
} }
/**
* Slow dynamic cast: casts {@code value} to an instance of {@code clazz}
* based upon inspection. If {@code lhs} is null, no cast takes place.
*/
static Object dynamicCast(Class<?> clazz, Object value) {
if (value != null) {
if (clazz == value.getClass()) {
return value;
}
if (clazz == Integer.class) {
return getNumber(value).intValue();
} else if (clazz == Long.class) {
return getNumber(value).longValue();
} else if (clazz == Double.class) {
return getNumber(value).doubleValue();
} else if (clazz == Float.class) {
return getNumber(value).floatValue();
} else if (clazz == Short.class) {
return getNumber(value).shortValue();
} else if (clazz == Byte.class) {
return getNumber(value).byteValue();
} else if (clazz == Character.class) {
return (char) getNumber(value).intValue();
}
return clazz.cast(value);
} else {
return value;
}
}
/** Slowly returns a Number for o. Just for supporting dynamicCast */ /** Slowly returns a Number for o. Just for supporting dynamicCast */
static Number getNumber(Object o) { static Number getNumber(Object o) {
if (o instanceof Number) { if (o instanceof Number) {
@ -1192,11 +1184,15 @@ public class DefMath {
} }
private static final MethodHandle DYNAMIC_CAST; private static final MethodHandle DYNAMIC_CAST;
private static final MethodHandle DYNAMIC_RECEIVER_CAST;
static { static {
final Lookup lookup = MethodHandles.lookup(); final Lookup lookup = MethodHandles.lookup();
try { try {
DYNAMIC_CAST = lookup.findStatic(lookup.lookupClass(), DYNAMIC_CAST = lookup.findStatic(lookup.lookupClass(),
"dynamicCast", "dynamicCast",
MethodType.methodType(Object.class, Class.class, Object.class));
DYNAMIC_RECEIVER_CAST = lookup.findStatic(lookup.lookupClass(),
"dynamicReceiverCast",
MethodType.methodType(Object.class, Object.class, Object.class)); MethodType.methodType(Object.class, Object.class, Object.class));
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw new AssertionError(e); throw new AssertionError(e);
@ -1204,16 +1200,24 @@ public class DefMath {
} }
/** Looks up generic method, with a dynamic cast to the receiver's type. (compound assignment) */ /** Looks up generic method, with a dynamic cast to the receiver's type. (compound assignment) */
public static MethodHandle lookupGenericWithCast(String name) { public static MethodHandle dynamicCast(MethodHandle target) {
MethodHandle generic = lookupGeneric(name); // adapt dynamic receiver cast to the generic method
// adapt dynamic cast to the generic method MethodHandle cast = DYNAMIC_RECEIVER_CAST.asType(MethodType.methodType(target.type().returnType(),
MethodHandle cast = DYNAMIC_CAST.asType(MethodType.methodType(generic.type().returnType(), target.type().returnType(),
generic.type().returnType(), target.type().parameterType(0)));
generic.type().parameterType(0)));
// drop the RHS parameter // drop the RHS parameter
cast = MethodHandles.dropArguments(cast, 2, generic.type().parameterType(1)); cast = MethodHandles.dropArguments(cast, 2, target.type().parameterType(1));
// combine: f(x,y) -> g(f(x,y), x, y); // combine: f(x,y) -> g(f(x,y), x, y);
return MethodHandles.foldArguments(cast, generic); return MethodHandles.foldArguments(cast, target);
}
/** Looks up generic method, with a dynamic cast to the specified type. (explicit assignment) */
public static MethodHandle dynamicCast(MethodHandle target, Class<?> desired) {
// adapt dynamic cast to the generic method
desired = MethodType.methodType(desired).wrap().returnType();
// bind to the boxed type
MethodHandle cast = DYNAMIC_CAST.bindTo(desired);
return MethodHandles.filterReturnValue(target, cast);
} }
/** Forces a cast to class A for target (only if types differ) */ /** Forces a cast to class A for target (only if types differ) */
@ -1221,12 +1225,18 @@ public class DefMath {
MethodType newType = MethodType.methodType(classA).unwrap(); MethodType newType = MethodType.methodType(classA).unwrap();
MethodType targetType = MethodType.methodType(target.type().returnType()).unwrap(); MethodType targetType = MethodType.methodType(target.type().returnType()).unwrap();
// don't do a conversion if types are the same. explicitCastArguments has this opto,
// but we do it explicitly, to make the boolean check simpler
if (newType.returnType() == targetType.returnType()) { if (newType.returnType() == targetType.returnType()) {
return target; // no conversion return target;
} }
// this is safe for our uses of it here only, because we change just the return value, // we don't allow the to/from boolean conversions of explicitCastArguments
// the original method itself does all the type checks correctly. if (newType.returnType() == boolean.class || targetType.returnType() == boolean.class) {
throw new ClassCastException("Cannot cast " + targetType.returnType() + " to " + newType.returnType());
}
// null return values are not possible for our arguments.
return MethodHandles.explicitCastArguments(target, target.type().changeReturnType(newType.returnType())); return MethodHandles.explicitCastArguments(target, target.type().changeReturnType(newType.returnType()));
} }
} }

View File

@ -30,6 +30,7 @@ import org.objectweb.asm.commons.Method;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet; import java.util.BitSet;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
@ -78,15 +79,17 @@ import static org.elasticsearch.painless.WriterConstants.UTILITY_TYPE;
*/ */
public final class MethodWriter extends GeneratorAdapter { public final class MethodWriter extends GeneratorAdapter {
private final BitSet statements; private final BitSet statements;
private final CompilerSettings settings;
private final Deque<List<org.objectweb.asm.Type>> stringConcatArgs = private final Deque<List<org.objectweb.asm.Type>> stringConcatArgs =
(INDY_STRING_CONCAT_BOOTSTRAP_HANDLE == null) ? null : new ArrayDeque<>(); (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE == null) ? null : new ArrayDeque<>();
public MethodWriter(int access, Method method, ClassVisitor cw, BitSet statements) { public MethodWriter(int access, Method method, ClassVisitor cw, BitSet statements, CompilerSettings settings) {
super(Opcodes.ASM5, cw.visitMethod(access, method.getName(), method.getDescriptor(), null, null), super(Opcodes.ASM5, cw.visitMethod(access, method.getName(), method.getDescriptor(), null, null),
access, method.getName(), method.getDescriptor()); access, method.getName(), method.getDescriptor());
this.statements = statements; this.statements = statements;
this.settings = settings;
} }
/** /**
@ -269,23 +272,18 @@ public final class MethodWriter extends GeneratorAdapter {
/** Writes a dynamic binary instruction: returnType, lhs, and rhs can be different */ /** Writes a dynamic binary instruction: returnType, lhs, and rhs can be different */
public void writeDynamicBinaryInstruction(Location location, Type returnType, Type lhs, Type rhs, public void writeDynamicBinaryInstruction(Location location, Type returnType, Type lhs, Type rhs,
Operation operation, boolean compoundAssignment) { Operation operation, int flags) {
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(returnType.type, lhs.type, rhs.type); org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(returnType.type, lhs.type, rhs.type);
String descriptor = methodType.getDescriptor();
int flags = 0;
if (compoundAssignment) {
flags |= DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT;
}
switch (operation) { switch (operation) {
case MUL: case MUL:
invokeDynamic("mul", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags); invokeDefCall("mul", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break; break;
case DIV: case DIV:
invokeDynamic("div", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags); invokeDefCall("div", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break; break;
case REM: case REM:
invokeDynamic("rem", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags); invokeDefCall("rem", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break; break;
case ADD: case ADD:
// if either side is primitive, then the + operator should always throw NPE on null, // if either side is primitive, then the + operator should always throw NPE on null,
@ -295,28 +293,28 @@ public final class MethodWriter extends GeneratorAdapter {
if (!hasPrimitiveArg) { if (!hasPrimitiveArg) {
flags |= DefBootstrap.OPERATOR_ALLOWS_NULL; flags |= DefBootstrap.OPERATOR_ALLOWS_NULL;
} }
invokeDynamic("add", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags); invokeDefCall("add", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break; break;
case SUB: case SUB:
invokeDynamic("sub", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags); invokeDefCall("sub", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break; break;
case LSH: case LSH:
invokeDynamic("lsh", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.SHIFT_OPERATOR, flags); invokeDefCall("lsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
break; break;
case USH: case USH:
invokeDynamic("ush", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.SHIFT_OPERATOR, flags); invokeDefCall("ush", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
break; break;
case RSH: case RSH:
invokeDynamic("rsh", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.SHIFT_OPERATOR, flags); invokeDefCall("rsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
break; break;
case BWAND: case BWAND:
invokeDynamic("and", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags); invokeDefCall("and", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break; break;
case XOR: case XOR:
invokeDynamic("xor", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags); invokeDefCall("xor", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break; break;
case BWOR: case BWOR:
invokeDynamic("or", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags); invokeDefCall("or", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break; break;
default: default:
throw location.createError(new IllegalStateException("Illegal tree structure.")); throw location.createError(new IllegalStateException("Illegal tree structure."));
@ -391,4 +389,19 @@ public final class MethodWriter extends GeneratorAdapter {
public void visitEnd() { public void visitEnd() {
throw new AssertionError("Should never call this method on MethodWriter, use endMethod() instead"); throw new AssertionError("Should never call this method on MethodWriter, use endMethod() instead");
} }
/**
* Writes a dynamic call for a def method.
* @param name method name
* @param methodType callsite signature
* @param flavor type of call
* @param params flavor-specific parameters
*/
public void invokeDefCall(String name, org.objectweb.asm.Type methodType, int flavor, Object... params) {
Object[] args = new Object[params.length + 2];
args[0] = settings.getInitialCallSiteDepth();
args[1] = flavor;
System.arraycopy(params, 0, args, 2, params.length);
invokeDynamic(name, methodType.getDescriptor(), DEF_BOOTSTRAP_HANDLE, args);
}
} }

View File

@ -128,6 +128,12 @@ public final class PainlessScriptEngineService extends AbstractComponent impleme
compilerSettings.setPicky(Boolean.parseBoolean(value)); compilerSettings.setPicky(Boolean.parseBoolean(value));
} }
value = copy.remove(CompilerSettings.INITIAL_CALL_SITE_DEPTH);
if (value != null) {
compilerSettings.setInitialCallSiteDepth(Integer.parseInt(value));
}
if (!copy.isEmpty()) { if (!copy.isEmpty()) {
throw new IllegalArgumentException("Unrecognized compile-time parameter(s): " + copy); throw new IllegalArgumentException("Unrecognized compile-time parameter(s): " + copy);
} }

View File

@ -85,9 +85,10 @@ public final class WriterConstants {
public final static Method MATCHER_FIND = getAsmMethod(boolean.class, "find"); public final static Method MATCHER_FIND = getAsmMethod(boolean.class, "find");
/** dynamic callsite bootstrap signature */ /** dynamic callsite bootstrap signature */
public final static MethodType DEF_BOOTSTRAP_TYPE = final static MethodType DEF_BOOTSTRAP_TYPE =
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, int.class, Object[].class); MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class,
public final static Handle DEF_BOOTSTRAP_HANDLE = int.class, int.class, Object[].class);
final static Handle DEF_BOOTSTRAP_HANDLE =
new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(DefBootstrap.class), new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(DefBootstrap.class),
"bootstrap", DEF_BOOTSTRAP_TYPE.toMethodDescriptorString(), false); "bootstrap", DEF_BOOTSTRAP_TYPE.toMethodDescriptorString(), false);

View File

@ -241,7 +241,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
statements.add((AStatement)visit(statement)); statements.add((AStatement)visit(statement));
} }
return new SSource(sourceName, sourceText, debugStream, (MainMethodReserved)reserved.pop(), return new SSource(settings, sourceName, sourceText, debugStream, (MainMethodReserved)reserved.pop(),
location(ctx), functions, globals, statements); location(ctx), functions, globals, statements);
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
@ -46,6 +47,7 @@ public final class EBinary extends AExpression {
Type shiftDistance; // for shifts, the RHS is promoted independently Type shiftDistance; // for shifts, the RHS is promoted independently
boolean cat = false; boolean cat = false;
boolean originallyExplicit = false; // record whether there was originally an explicit cast
public EBinary(Location location, Operation operation, AExpression left, AExpression right) { public EBinary(Location location, Operation operation, AExpression left, AExpression right) {
super(location); super(location);
@ -63,6 +65,7 @@ public final class EBinary extends AExpression {
@Override @Override
void analyze(Locals locals) { void analyze(Locals locals) {
originallyExplicit = explicit;
if (operation == Operation.MUL) { if (operation == Operation.MUL) {
analyzeMul(locals); analyzeMul(locals);
} else if (operation == Operation.DIV) { } else if (operation == Operation.DIV) {
@ -649,7 +652,13 @@ public final class EBinary extends AExpression {
right.write(writer, globals); right.write(writer, globals);
if (promote.sort == Sort.DEF || (shiftDistance != null && shiftDistance.sort == Sort.DEF)) { if (promote.sort == Sort.DEF || (shiftDistance != null && shiftDistance.sort == Sort.DEF)) {
writer.writeDynamicBinaryInstruction(location, actual, left.actual, right.actual, operation, false); // def calls adopt the wanted return value. if there was a narrowing cast,
// we need to flag that so that its done at runtime.
int flags = 0;
if (originallyExplicit) {
flags |= DefBootstrap.OPERATOR_EXPLICIT_CAST;
}
writer.writeDynamicBinaryInstruction(location, actual, left.actual, right.actual, operation, flags);
} else { } else {
writer.writeBinaryInstruction(location, actual, operation); writer.writeBinaryInstruction(location, actual, operation);
} }

View File

@ -30,7 +30,6 @@ import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type; import org.objectweb.asm.Type;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE; import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
import java.lang.invoke.LambdaMetafactory; import java.lang.invoke.LambdaMetafactory;
@ -97,8 +96,8 @@ public class ECapturingFunctionRef extends AExpression implements ILambda {
} else if (ref == null) { } else if (ref == null) {
// typed interface, dynamic implementation // typed interface, dynamic implementation
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot()); writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());
String descriptor = Type.getMethodType(expected.type, captured.type.type).getDescriptor(); Type methodType = Type.getMethodType(expected.type, captured.type.type);
writer.invokeDynamic(call, descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.REFERENCE, expected.name); writer.invokeDefCall(call, methodType, DefBootstrap.REFERENCE, expected.name);
} else { } else {
// typed interface, typed implementation // typed interface, typed implementation
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot()); writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());

View File

@ -26,6 +26,7 @@ import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Operation; import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -226,7 +227,10 @@ public final class EChain extends AExpression {
expression.expected = expression.actual; expression.expected = expression.actual;
} else if (shift) { } else if (shift) {
if (shiftDistance.sort == Sort.LONG) { if (promote.sort == Sort.DEF) {
// shifts are promoted independently, but for the def type, we need object.
expression.expected = promote;
} else if (shiftDistance.sort == Sort.LONG) {
expression.expected = Definition.INT_TYPE; expression.expected = Definition.INT_TYPE;
expression.explicit = true; expression.explicit = true;
} else { } else {
@ -365,7 +369,7 @@ public final class EChain extends AExpression {
// write the operation instruction for compound assignment // write the operation instruction for compound assignment
if (promote.sort == Sort.DEF) { if (promote.sort == Sort.DEF) {
writer.writeDynamicBinaryInstruction(location, promote, writer.writeDynamicBinaryInstruction(location, promote,
Definition.DEF_TYPE, Definition.DEF_TYPE, operation, true); Definition.DEF_TYPE, Definition.DEF_TYPE, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
} else { } else {
writer.writeBinaryInstruction(location, promote, operation); writer.writeBinaryInstruction(location, promote, operation);
} }

View File

@ -37,7 +37,6 @@ import org.elasticsearch.painless.MethodWriter;
import static org.elasticsearch.painless.WriterConstants.OBJECTS_TYPE; import static org.elasticsearch.painless.WriterConstants.OBJECTS_TYPE;
import static org.elasticsearch.painless.WriterConstants.EQUALS; import static org.elasticsearch.painless.WriterConstants.EQUALS;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
/** /**
* Represents a comparison expression. * Represents a comparison expression.
@ -508,8 +507,7 @@ public final class EComp extends AExpression {
if (right.isNull) { if (right.isNull) {
writer.ifNull(jump); writer.ifNull(jump);
} else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) { } else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) {
writer.invokeDynamic("eq", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, writer.invokeDefCall("eq", descriptor, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
DefBootstrap.OPERATOR_ALLOWS_NULL);
writejump = false; writejump = false;
} else { } else {
writer.ifCmp(promotedType.type, MethodWriter.EQ, jump); writer.ifCmp(promotedType.type, MethodWriter.EQ, jump);
@ -518,23 +516,22 @@ public final class EComp extends AExpression {
if (right.isNull) { if (right.isNull) {
writer.ifNonNull(jump); writer.ifNonNull(jump);
} else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) { } else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) {
writer.invokeDynamic("eq", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, writer.invokeDefCall("eq", descriptor, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
DefBootstrap.OPERATOR_ALLOWS_NULL);
writer.ifZCmp(MethodWriter.EQ, jump); writer.ifZCmp(MethodWriter.EQ, jump);
} else { } else {
writer.ifCmp(promotedType.type, MethodWriter.NE, jump); writer.ifCmp(promotedType.type, MethodWriter.NE, jump);
} }
} else if (lt) { } else if (lt) {
writer.invokeDynamic("lt", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, 0); writer.invokeDefCall("lt", descriptor, DefBootstrap.BINARY_OPERATOR, 0);
writejump = false; writejump = false;
} else if (lte) { } else if (lte) {
writer.invokeDynamic("lte", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, 0); writer.invokeDefCall("lte", descriptor, DefBootstrap.BINARY_OPERATOR, 0);
writejump = false; writejump = false;
} else if (gt) { } else if (gt) {
writer.invokeDynamic("gt", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, 0); writer.invokeDefCall("gt", descriptor, DefBootstrap.BINARY_OPERATOR, 0);
writejump = false; writejump = false;
} else if (gte) { } else if (gte) {
writer.invokeDynamic("gte", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, 0); writer.invokeDefCall("gte", descriptor, DefBootstrap.BINARY_OPERATOR, 0);
writejump = false; writejump = false;
} else { } else {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));

View File

@ -35,8 +35,6 @@ import java.util.Set;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
/** /**
* Represents a unary math expression. * Represents a unary math expression.
*/ */
@ -45,6 +43,7 @@ public final class EUnary extends AExpression {
final Operation operation; final Operation operation;
AExpression child; AExpression child;
Type promote; Type promote;
boolean originallyExplicit = false; // record whether there was originally an explicit cast
public EUnary(Location location, Operation operation, AExpression child) { public EUnary(Location location, Operation operation, AExpression child) {
super(location); super(location);
@ -60,6 +59,7 @@ public final class EUnary extends AExpression {
@Override @Override
void analyze(Locals locals) { void analyze(Locals locals) {
originallyExplicit = explicit;
if (operation == Operation.NOT) { if (operation == Operation.NOT) {
analyzeNot(locals); analyzeNot(locals);
} else if (operation == Operation.BWNOT) { } else if (operation == Operation.BWNOT) {
@ -212,10 +212,16 @@ public final class EUnary extends AExpression {
Sort sort = promote.sort; Sort sort = promote.sort;
child.write(writer, globals); child.write(writer, globals);
// def calls adopt the wanted return value. if there was a narrowing cast,
// we need to flag that so that its done at runtime.
int defFlags = 0;
if (originallyExplicit) {
defFlags |= DefBootstrap.OPERATOR_EXPLICIT_CAST;
}
if (operation == Operation.BWNOT) { if (operation == Operation.BWNOT) {
if (sort == Sort.DEF) { if (sort == Sort.DEF) {
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type); org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type);
writer.invokeDynamic("not", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.UNARY_OPERATOR, 0); writer.invokeDefCall("not", descriptor, DefBootstrap.UNARY_OPERATOR, defFlags);
} else { } else {
if (sort == Sort.INT) { if (sort == Sort.INT) {
writer.push(-1); writer.push(-1);
@ -230,14 +236,14 @@ public final class EUnary extends AExpression {
} else if (operation == Operation.SUB) { } else if (operation == Operation.SUB) {
if (sort == Sort.DEF) { if (sort == Sort.DEF) {
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type); org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type);
writer.invokeDynamic("neg", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.UNARY_OPERATOR, 0); writer.invokeDefCall("neg", descriptor, DefBootstrap.UNARY_OPERATOR, defFlags);
} else { } else {
writer.math(MethodWriter.NEG, actual.type); writer.math(MethodWriter.NEG, actual.type);
} }
} else if (operation == Operation.ADD) { } else if (operation == Operation.ADD) {
if (sort == Sort.DEF) { if (sort == Sort.DEF) {
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type); org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type);
writer.invokeDynamic("plus", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.UNARY_OPERATOR, 0); writer.invokeDefCall("plus", descriptor, DefBootstrap.UNARY_OPERATOR, defFlags);
} }
} else { } else {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));

View File

@ -31,8 +31,6 @@ import java.util.Set;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
/** /**
* Represents an array load/store or shortcut on a def type. (Internal only.) * Represents an array load/store or shortcut on a def type. (Internal only.)
*/ */
@ -71,15 +69,15 @@ final class LDefArray extends ALink implements IDefLink {
void load(MethodWriter writer, Globals globals) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type); Type methodType = Type.getMethodType(after.type, Definition.DEF_TYPE.type, index.actual.type);
writer.invokeDynamic("arrayLoad", desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.ARRAY_LOAD); writer.invokeDefCall("arrayLoad", methodType, DefBootstrap.ARRAY_LOAD);
} }
@Override @Override
void store(MethodWriter writer, Globals globals) { void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type); Type methodType = Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
writer.invokeDynamic("arrayStore", desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.ARRAY_STORE); writer.invokeDefCall("arrayStore", methodType, DefBootstrap.ARRAY_STORE);
} }
} }

View File

@ -32,8 +32,6 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
/** /**
* Represents a method call made on a def type. (Internal only.) * Represents a method call made on a def type. (Internal only.)
*/ */
@ -96,32 +94,30 @@ final class LDefCall extends ALink implements IDefLink {
void load(MethodWriter writer, Globals globals) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
StringBuilder signature = new StringBuilder(); List<Type> parameterTypes = new ArrayList<>();
signature.append('(');
// first parameter is the receiver, we never know its type: always Object // first parameter is the receiver, we never know its type: always Object
signature.append(Definition.DEF_TYPE.type.getDescriptor()); parameterTypes.add(Definition.DEF_TYPE.type);
// append each argument
for (AExpression argument : arguments) { for (AExpression argument : arguments) {
signature.append(argument.actual.type.getDescriptor()); parameterTypes.add(argument.actual.type);
if (argument instanceof ILambda) { if (argument instanceof ILambda) {
ILambda lambda = (ILambda) argument; ILambda lambda = (ILambda) argument;
for (Type capture : lambda.getCaptures()) { for (Type capture : lambda.getCaptures()) {
signature.append(capture.getDescriptor()); parameterTypes.add(capture);
} }
} }
argument.write(writer, globals); argument.write(writer, globals);
} }
signature.append(')'); // create method type from return value and arguments
// return value Type methodType = Type.getMethodType(after.type, parameterTypes.toArray(new Type[0]));
signature.append(after.type.getDescriptor());
List<Object> args = new ArrayList<>(); List<Object> args = new ArrayList<>();
args.add(DefBootstrap.METHOD_CALL);
args.add(recipe.toString()); args.add(recipe.toString());
args.addAll(pointers); args.addAll(pointers);
writer.invokeDynamic(name, signature.toString(), DEF_BOOTSTRAP_HANDLE, args.toArray()); writer.invokeDefCall(name, methodType, DefBootstrap.METHOD_CALL, args.toArray());
} }
@Override @Override

View File

@ -31,8 +31,6 @@ import java.util.Set;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
/** /**
* Represents a field load/store or shortcut on a def type. (Internal only.) * Represents a field load/store or shortcut on a def type. (Internal only.)
*/ */
@ -65,15 +63,15 @@ final class LDefField extends ALink implements IDefLink {
void load(MethodWriter writer, Globals globals) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type); Type methodType = Type.getMethodType(after.type, Definition.DEF_TYPE.type);
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.LOAD); writer.invokeDefCall(value, methodType, DefBootstrap.LOAD);
} }
@Override @Override
void store(MethodWriter writer, Globals globals) { void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type); Type methodType = Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.STORE); writer.invokeDefCall(value, methodType, DefBootstrap.STORE);
} }
} }

View File

@ -38,7 +38,6 @@ import org.objectweb.asm.Opcodes;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT; import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT; import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE; import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE;
@ -205,8 +204,8 @@ public class SEach extends AStatement {
if (method == null) { if (method == null) {
Type itr = Definition.getType("Iterator"); Type itr = Definition.getType("Iterator");
String desc = org.objectweb.asm.Type.getMethodDescriptor(itr.type, Definition.DEF_TYPE.type); org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(itr.type, Definition.DEF_TYPE.type);
writer.invokeDynamic("iterator", desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.ITERATOR); writer.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR);
} else if (java.lang.reflect.Modifier.isInterface(method.owner.clazz.getModifiers())) { } else if (java.lang.reflect.Modifier.isInterface(method.owner.clazz.getModifiers())) {
writer.invokeInterface(method.owner.type, method.method); writer.invokeInterface(method.owner.type, method.method);
} else { } else {

View File

@ -19,6 +19,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.CompilerSettings;
import org.elasticsearch.painless.Constant; import org.elasticsearch.painless.Constant;
import org.elasticsearch.painless.Def; import org.elasticsearch.painless.Def;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
@ -151,12 +152,12 @@ public class SFunction extends AStatement {
} }
/** Writes the function to given ClassVisitor. */ /** Writes the function to given ClassVisitor. */
void write (ClassVisitor writer, Globals globals) { void write (ClassVisitor writer, CompilerSettings settings, Globals globals) {
int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC; int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
if (synthetic) { if (synthetic) {
access |= Opcodes.ACC_SYNTHETIC; access |= Opcodes.ACC_SYNTHETIC;
} }
final MethodWriter function = new MethodWriter(access, method.method, writer, globals.getStatements()); final MethodWriter function = new MethodWriter(access, method.method, writer, globals.getStatements(), settings);
write(function, globals); write(function, globals);
function.endMethod(); function.endMethod();
} }

View File

@ -19,6 +19,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.CompilerSettings;
import org.elasticsearch.painless.Constant; import org.elasticsearch.painless.Constant;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey; import org.elasticsearch.painless.Definition.MethodKey;
@ -61,6 +62,7 @@ public final class SSource extends AStatement {
final String name; final String name;
final String source; final String source;
final Printer debugStream; final Printer debugStream;
final CompilerSettings settings;
final MainMethodReserved reserved; final MainMethodReserved reserved;
final List<SFunction> functions; final List<SFunction> functions;
final Globals globals; final Globals globals;
@ -69,10 +71,11 @@ public final class SSource extends AStatement {
private Locals mainMethod; private Locals mainMethod;
private byte[] bytes; private byte[] bytes;
public SSource(String name, String source, Printer debugStream, MainMethodReserved reserved, Location location, public SSource(CompilerSettings settings, String name, String source, Printer debugStream,
MainMethodReserved reserved, Location location,
List<SFunction> functions, Globals globals, List<AStatement> statements) { List<SFunction> functions, Globals globals, List<AStatement> statements) {
super(location); super(location);
this.settings = Objects.requireNonNull(settings);
this.name = Objects.requireNonNull(name); this.name = Objects.requireNonNull(name);
this.source = Objects.requireNonNull(source); this.source = Objects.requireNonNull(source);
this.debugStream = debugStream; this.debugStream = debugStream;
@ -159,7 +162,7 @@ public final class SSource extends AStatement {
visitor.visitSource(Location.computeSourceName(name, source), null); visitor.visitSource(Location.computeSourceName(name, source), null);
// Write the constructor: // Write the constructor:
MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, globals.getStatements()); MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, globals.getStatements(), settings);
constructor.visitCode(); constructor.visitCode();
constructor.loadThis(); constructor.loadThis();
constructor.loadArgs(); constructor.loadArgs();
@ -168,14 +171,14 @@ public final class SSource extends AStatement {
constructor.endMethod(); constructor.endMethod();
// Write the execute method: // Write the execute method:
MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, visitor, globals.getStatements()); MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, visitor, globals.getStatements(), settings);
execute.visitCode(); execute.visitCode();
write(execute, globals); write(execute, globals);
execute.endMethod(); execute.endMethod();
// Write all functions: // Write all functions:
for (SFunction function : functions) { for (SFunction function : functions) {
function.write(visitor, globals); function.write(visitor, settings, globals);
} }
// Write all synthetic functions. Note that this process may add more :) // Write all synthetic functions. Note that this process may add more :)
@ -183,7 +186,7 @@ public final class SSource extends AStatement {
List<SFunction> current = new ArrayList<>(globals.getSyntheticMethods().values()); List<SFunction> current = new ArrayList<>(globals.getSyntheticMethods().values());
globals.getSyntheticMethods().clear(); globals.getSyntheticMethods().clear();
for (SFunction function : current) { for (SFunction function : current) {
function.write(visitor, globals); function.write(visitor, settings, globals);
} }
} }
@ -203,7 +206,7 @@ public final class SSource extends AStatement {
// Initialize the constants in a static initializer // Initialize the constants in a static initializer
final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC, final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC,
WriterConstants.CLINIT, visitor, globals.getStatements()); WriterConstants.CLINIT, visitor, globals.getStatements(), settings);
for (Constant constant : inits) { for (Constant constant : inits) {
constant.initializer.accept(clinit); constant.initializer.accept(clinit);
clinit.putStatic(CLASS_TYPE, constant.name, constant.type); clinit.putStatic(CLASS_TYPE, constant.name, constant.type);

View File

@ -0,0 +1,307 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless;
/** Tests for explicit casts */
public class CastTests extends ScriptTestCase {
/**
* Unary operator with explicit cast
*/
public void testUnaryOperator() {
assertEquals((byte)5, exec("long x = 5L; return (byte) (+x);"));
assertEquals((short)5, exec("long x = 5L; return (short) (+x);"));
assertEquals((char)5, exec("long x = 5L; return (char) (+x);"));
assertEquals(5, exec("long x = 5L; return (int) (+x);"));
assertEquals(5F, exec("long x = 5L; return (float) (+x);"));
assertEquals(5L, exec("long x = 5L; return (long) (+x);"));
assertEquals(5D, exec("long x = 5L; return (double) (+x);"));
}
/**
* Binary operators with explicit cast
*/
public void testBinaryOperator() {
assertEquals((byte)6, exec("long x = 5L; return (byte) (x + 1);"));
assertEquals((short)6, exec("long x = 5L; return (short) (x + 1);"));
assertEquals((char)6, exec("long x = 5L; return (char) (x + 1);"));
assertEquals(6, exec("long x = 5L; return (int) (x + 1);"));
assertEquals(6F, exec("long x = 5L; return (float) (x + 1);"));
assertEquals(6L, exec("long x = 5L; return (long) (x + 1);"));
assertEquals(6D, exec("long x = 5L; return (double) (x + 1);"));
}
/**
* Binary compound assignment with explicit cast
*/
public void testBinaryCompoundAssignment() {
assertEquals((byte)6, exec("long x = 5L; return (byte) (x += 1);"));
assertEquals((short)6, exec("long x = 5L; return (short) (x += 1);"));
assertEquals((char)6, exec("long x = 5L; return (char) (x += 1);"));
assertEquals(6, exec("long x = 5L; return (int) (x += 1);"));
assertEquals(6F, exec("long x = 5L; return (float) (x += 1);"));
assertEquals(6L, exec("long x = 5L; return (long) (x += 1);"));
assertEquals(6D, exec("long x = 5L; return (double) (x += 1);"));
}
/**
* Binary compound prefix with explicit cast
*/
public void testBinaryPrefix() {
assertEquals((byte)6, exec("long x = 5L; return (byte) (++x);"));
assertEquals((short)6, exec("long x = 5L; return (short) (++x);"));
assertEquals((char)6, exec("long x = 5L; return (char) (++x);"));
assertEquals(6, exec("long x = 5L; return (int) (++x);"));
assertEquals(6F, exec("long x = 5L; return (float) (++x);"));
assertEquals(6L, exec("long x = 5L; return (long) (++x);"));
assertEquals(6D, exec("long x = 5L; return (double) (++x);"));
}
/**
* Binary compound postifx with explicit cast
*/
public void testBinaryPostfix() {
assertEquals((byte)5, exec("long x = 5L; return (byte) (x++);"));
assertEquals((short)5, exec("long x = 5L; return (short) (x++);"));
assertEquals((char)5, exec("long x = 5L; return (char) (x++);"));
assertEquals(5, exec("long x = 5L; return (int) (x++);"));
assertEquals(5F, exec("long x = 5L; return (float) (x++);"));
assertEquals(5L, exec("long x = 5L; return (long) (x++);"));
assertEquals(5D, exec("long x = 5L; return (double) (x++);"));
}
/**
* Shift operators with explicit cast
*/
public void testShiftOperator() {
assertEquals((byte)10, exec("long x = 5L; return (byte) (x << 1);"));
assertEquals((short)10, exec("long x = 5L; return (short) (x << 1);"));
assertEquals((char)10, exec("long x = 5L; return (char) (x << 1);"));
assertEquals(10, exec("long x = 5L; return (int) (x << 1);"));
assertEquals(10F, exec("long x = 5L; return (float) (x << 1);"));
assertEquals(10L, exec("long x = 5L; return (long) (x << 1);"));
assertEquals(10D, exec("long x = 5L; return (double) (x << 1);"));
}
/**
* Shift compound assignment with explicit cast
*/
public void testShiftCompoundAssignment() {
assertEquals((byte)10, exec("long x = 5L; return (byte) (x <<= 1);"));
assertEquals((short)10, exec("long x = 5L; return (short) (x <<= 1);"));
assertEquals((char)10, exec("long x = 5L; return (char) (x <<= 1);"));
assertEquals(10, exec("long x = 5L; return (int) (x <<= 1);"));
assertEquals(10F, exec("long x = 5L; return (float) (x <<= 1);"));
assertEquals(10L, exec("long x = 5L; return (long) (x <<= 1);"));
assertEquals(10D, exec("long x = 5L; return (double) (x <<= 1);"));
}
/**
* Test that without a cast, we fail when conversions would narrow.
*/
public void testIllegalConversions() {
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; int y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; int y = (x + x); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("boolean x = true; int y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("boolean x = true; int y = (x ^ false); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; boolean y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; boolean y = (x + x); return y");
});
}
/**
* Test that even with a cast, some things aren't allowed.
*/
public void testIllegalExplicitConversions() {
expectScriptThrows(ClassCastException.class, () -> {
exec("boolean x = true; int y = (int) +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("boolean x = true; int y = (int) (x ^ false); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; boolean y = (boolean) +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("long x = 5L; boolean y = (boolean) (x + x); return y");
});
}
/**
* Currently these do not adopt the return value, we issue a separate cast!
*/
public void testMethodCallDef() {
assertEquals(5, exec("def x = 5; return (int)x.longValue();"));
}
/**
* Currently these do not adopt the argument value, we issue a separate cast!
*/
public void testArgumentsDef() {
assertEquals(5, exec("def x = 5L; return (+(int)x);"));
assertEquals(6, exec("def x = 5; def y = 1L; return x + (int)y"));
assertEquals('b', exec("def x = 'abcdeg'; def y = 1L; x.charAt((int)y)"));
}
/**
* Unary operators adopt the return value
*/
public void testUnaryOperatorDef() {
assertEquals((byte)5, exec("def x = 5L; return (byte) (+x);"));
assertEquals((short)5, exec("def x = 5L; return (short) (+x);"));
assertEquals((char)5, exec("def x = 5L; return (char) (+x);"));
assertEquals(5, exec("def x = 5L; return (int) (+x);"));
assertEquals(5F, exec("def x = 5L; return (float) (+x);"));
assertEquals(5L, exec("def x = 5L; return (long) (+x);"));
assertEquals(5D, exec("def x = 5L; return (double) (+x);"));
}
/**
* Binary operators adopt the return value
*/
public void testBinaryOperatorDef() {
assertEquals((byte)6, exec("def x = 5L; return (byte) (x + 1);"));
assertEquals((short)6, exec("def x = 5L; return (short) (x + 1);"));
assertEquals((char)6, exec("def x = 5L; return (char) (x + 1);"));
assertEquals(6, exec("def x = 5L; return (int) (x + 1);"));
assertEquals(6F, exec("def x = 5L; return (float) (x + 1);"));
assertEquals(6L, exec("def x = 5L; return (long) (x + 1);"));
assertEquals(6D, exec("def x = 5L; return (double) (x + 1);"));
}
/**
* Binary operators don't yet adopt the return value with compound assignment
*/
public void testBinaryCompoundAssignmentDef() {
assertEquals((byte)6, exec("def x = 5L; return (byte) (x += 1);"));
assertEquals((short)6, exec("def x = 5L; return (short) (x += 1);"));
assertEquals((char)6, exec("def x = 5L; return (char) (x += 1);"));
assertEquals(6, exec("def x = 5L; return (int) (x += 1);"));
assertEquals(6F, exec("def x = 5L; return (float) (x += 1);"));
assertEquals(6L, exec("def x = 5L; return (long) (x += 1);"));
assertEquals(6D, exec("def x = 5L; return (double) (x += 1);"));
}
/**
* Binary operators don't yet adopt the return value with compound assignment
*/
public void testBinaryCompoundAssignmentPrefix() {
assertEquals((byte)6, exec("def x = 5L; return (byte) (++x);"));
assertEquals((short)6, exec("def x = 5L; return (short) (++x);"));
assertEquals((char)6, exec("def x = 5L; return (char) (++x);"));
assertEquals(6, exec("def x = 5L; return (int) (++x);"));
assertEquals(6F, exec("def x = 5L; return (float) (++x);"));
assertEquals(6L, exec("def x = 5L; return (long) (++x);"));
assertEquals(6D, exec("def x = 5L; return (double) (++x);"));
}
/**
* Binary operators don't yet adopt the return value with compound assignment
*/
public void testBinaryCompoundAssignmentPostfix() {
assertEquals((byte)5, exec("def x = 5L; return (byte) (x++);"));
assertEquals((short)5, exec("def x = 5L; return (short) (x++);"));
assertEquals((char)5, exec("def x = 5L; return (char) (x++);"));
assertEquals(5, exec("def x = 5L; return (int) (x++);"));
assertEquals(5F, exec("def x = 5L; return (float) (x++);"));
assertEquals(5L, exec("def x = 5L; return (long) (x++);"));
assertEquals(5D, exec("def x = 5L; return (double) (x++);"));
}
/**
* Shift operators adopt the return value
*/
public void testShiftOperatorDef() {
assertEquals((byte)10, exec("def x = 5L; return (byte) (x << 1);"));
assertEquals((short)10, exec("def x = 5L; return (short) (x << 1);"));
assertEquals((char)10, exec("def x = 5L; return (char) (x << 1);"));
assertEquals(10, exec("def x = 5L; return (int) (x << 1);"));
assertEquals(10F, exec("def x = 5L; return (float) (x << 1);"));
assertEquals(10L, exec("def x = 5L; return (long) (x << 1);"));
assertEquals(10D, exec("def x = 5L; return (double) (x << 1);"));
}
/**
* Shift operators don't yet adopt the return value with compound assignment
*/
public void testShiftCompoundAssignmentDef() {
assertEquals((byte)10, exec("def x = 5L; return (byte) (x <<= 1);"));
assertEquals((short)10, exec("def x = 5L; return (short) (x <<= 1);"));
assertEquals((char)10, exec("def x = 5L; return (char) (x <<= 1);"));
assertEquals(10, exec("def x = 5L; return (int) (x <<= 1);"));
assertEquals(10F, exec("def x = 5L; return (float) (x <<= 1);"));
assertEquals(10L, exec("def x = 5L; return (long) (x <<= 1);"));
assertEquals(10D, exec("def x = 5L; return (double) (x <<= 1);"));
}
/**
* Test that without a cast, we fail when conversions would narrow.
*/
public void testIllegalConversionsDef() {
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; int y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; int y = (x + x); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = true; int y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = true; int y = (x ^ false); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; boolean y = +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; boolean y = (x + x); return y");
});
}
/**
* Test that even with a cast, some things aren't allowed.
* (stuff that methodhandles explicitCastArguments would otherwise allow)
*/
public void testIllegalExplicitConversionsDef() {
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = true; int y = (int) +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = true; int y = (int) (x ^ false); return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; boolean y = (boolean) +x; return y");
});
expectScriptThrows(ClassCastException.class, () -> {
exec("def x = 5L; boolean y = (boolean) (x + x); return y");
});
}
}

View File

@ -36,6 +36,7 @@ public class DefBootstrapTests extends ESTestCase {
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(), CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"toString", "toString",
MethodType.methodType(String.class, Object.class), MethodType.methodType(String.class, Object.class),
0,
DefBootstrap.METHOD_CALL, ""); DefBootstrap.METHOD_CALL, "");
MethodHandle handle = site.dynamicInvoker(); MethodHandle handle = site.dynamicInvoker();
assertDepthEquals(site, 0); assertDepthEquals(site, 0);
@ -53,6 +54,7 @@ public class DefBootstrapTests extends ESTestCase {
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(), CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"toString", "toString",
MethodType.methodType(String.class, Object.class), MethodType.methodType(String.class, Object.class),
0,
DefBootstrap.METHOD_CALL, ""); DefBootstrap.METHOD_CALL, "");
MethodHandle handle = site.dynamicInvoker(); MethodHandle handle = site.dynamicInvoker();
assertDepthEquals(site, 0); assertDepthEquals(site, 0);
@ -75,6 +77,7 @@ public class DefBootstrapTests extends ESTestCase {
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(), CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"toString", "toString",
MethodType.methodType(String.class, Object.class), MethodType.methodType(String.class, Object.class),
0,
DefBootstrap.METHOD_CALL, ""); DefBootstrap.METHOD_CALL, "");
MethodHandle handle = site.dynamicInvoker(); MethodHandle handle = site.dynamicInvoker();
assertDepthEquals(site, 0); assertDepthEquals(site, 0);
@ -98,6 +101,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.PIC site = (DefBootstrap.PIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), DefBootstrap.PIC site = (DefBootstrap.PIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"size", "size",
MethodType.methodType(int.class, Object.class), MethodType.methodType(int.class, Object.class),
0,
DefBootstrap.METHOD_CALL, ""); DefBootstrap.METHOD_CALL, "");
site.depth = DefBootstrap.PIC.MAX_DEPTH; // mark megamorphic site.depth = DefBootstrap.PIC.MAX_DEPTH; // mark megamorphic
MethodHandle handle = site.dynamicInvoker(); MethodHandle handle = site.dynamicInvoker();
@ -127,6 +131,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"add", "add",
MethodType.methodType(Object.class, Object.class, Object.class), MethodType.methodType(Object.class, Object.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
MethodHandle handle = site.dynamicInvoker(); MethodHandle handle = site.dynamicInvoker();
assertEquals("nulltest", (Object)handle.invokeExact((Object)null, (Object)"test")); assertEquals("nulltest", (Object)handle.invokeExact((Object)null, (Object)"test"));
@ -136,6 +141,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"add", "add",
MethodType.methodType(Object.class, Object.class, Object.class), MethodType.methodType(Object.class, Object.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
MethodHandle handle = site.dynamicInvoker(); MethodHandle handle = site.dynamicInvoker();
assertEquals(2, (Object)handle.invokeExact((Object)1, (Object)1)); assertEquals(2, (Object)handle.invokeExact((Object)1, (Object)1));
@ -146,6 +152,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"eq", "eq",
MethodType.methodType(boolean.class, Object.class, Object.class), MethodType.methodType(boolean.class, Object.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
MethodHandle handle = site.dynamicInvoker(); MethodHandle handle = site.dynamicInvoker();
assertFalse((boolean) handle.invokeExact((Object)null, (Object)"test")); assertFalse((boolean) handle.invokeExact((Object)null, (Object)"test"));
@ -156,6 +163,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"eq", "eq",
MethodType.methodType(boolean.class, Object.class, Object.class), MethodType.methodType(boolean.class, Object.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
MethodHandle handle = site.dynamicInvoker(); MethodHandle handle = site.dynamicInvoker();
assertTrue((boolean) handle.invokeExact((Object)1, (Object)1)); assertTrue((boolean) handle.invokeExact((Object)1, (Object)1));
@ -171,6 +179,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"add", "add",
MethodType.methodType(Object.class, int.class, Object.class), MethodType.methodType(Object.class, int.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, 0); DefBootstrap.BINARY_OPERATOR, 0);
MethodHandle handle = site.dynamicInvoker(); MethodHandle handle = site.dynamicInvoker();
expectThrows(NullPointerException.class, () -> { expectThrows(NullPointerException.class, () -> {
@ -182,6 +191,7 @@ public class DefBootstrapTests extends ESTestCase {
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(), DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
"add", "add",
MethodType.methodType(Object.class, int.class, Object.class), MethodType.methodType(Object.class, int.class, Object.class),
0,
DefBootstrap.BINARY_OPERATOR, 0); DefBootstrap.BINARY_OPERATOR, 0);
MethodHandle handle = site.dynamicInvoker(); MethodHandle handle = site.dynamicInvoker();
assertEquals(2, (Object)handle.invokeExact(1, (Object)1)); assertEquals(2, (Object)handle.invokeExact(1, (Object)1));

View File

@ -243,18 +243,22 @@ public class DefOptimizationTests extends ScriptTestCase {
public void testAddOptNullGuards() { public void testAddOptNullGuards() {
// needs null guard // needs null guard
assertBytecodeHasPattern("def x = 1; def y = 2; return x + y", assertBytecodeHasPattern("def x = 1; def y = 2; return x + y",
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + DefBootstrap.BINARY_OPERATOR "(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + "\\d+"
+ ",\\s+" + DefBootstrap.BINARY_OPERATOR
+ ",\\s+" + DefBootstrap.OPERATOR_ALLOWS_NULL + ".*"); + ",\\s+" + DefBootstrap.OPERATOR_ALLOWS_NULL + ".*");
// still needs null guard, NPE is the wrong thing! // still needs null guard, NPE is the wrong thing!
assertBytecodeHasPattern("def x = 1; def y = 2; double z = x + y", assertBytecodeHasPattern("def x = 1; def y = 2; double z = x + y",
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + DefBootstrap.BINARY_OPERATOR "(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + "\\d+"
+ ",\\s+" + DefBootstrap.BINARY_OPERATOR
+ ",\\s+" + DefBootstrap.OPERATOR_ALLOWS_NULL + ".*"); + ",\\s+" + DefBootstrap.OPERATOR_ALLOWS_NULL + ".*");
// a primitive argument is present: no null guard needed // a primitive argument is present: no null guard needed
assertBytecodeHasPattern("def x = 1; int y = 2; return x + y", assertBytecodeHasPattern("def x = 1; int y = 2; return x + y",
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + DefBootstrap.BINARY_OPERATOR "(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + "\\d+"
+ ",\\s+" + DefBootstrap.BINARY_OPERATOR
+ ",\\s+" + 0 + ".*"); + ",\\s+" + 0 + ".*");
assertBytecodeHasPattern("int x = 1; def y = 2; return x + y", assertBytecodeHasPattern("int x = 1; def y = 2; return x + y",
"(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + DefBootstrap.BINARY_OPERATOR "(?s).*INVOKEDYNAMIC add.*arguments:\\s+" + "\\d+"
+ ",\\s+" + DefBootstrap.BINARY_OPERATOR
+ ",\\s+" + 0 + ".*"); + ",\\s+" + 0 + ".*");
} }

View File

@ -56,6 +56,7 @@ public abstract class ScriptTestCase extends ESTestCase {
public Object exec(String script, Map<String, Object> vars) { public Object exec(String script, Map<String, Object> vars) {
Map<String,String> compilerSettings = new HashMap<>(); Map<String,String> compilerSettings = new HashMap<>();
compilerSettings.put(CompilerSettings.PICKY, "true"); compilerSettings.put(CompilerSettings.PICKY, "true");
compilerSettings.put(CompilerSettings.INITIAL_CALL_SITE_DEPTH, random().nextBoolean() ? "0" : "10");
return exec(script, vars, compilerSettings, null); return exec(script, vars, compilerSettings, null);
} }