Fix explicit casts and improve tests.
This commit is contained in:
parent
8f96dd53aa
commit
8d9fa7e0b5
|
@ -34,6 +34,11 @@ public final class CompilerSettings {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -45,6 +50,11 @@ public final class CompilerSettings {
|
|||
*/
|
||||
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
|
||||
* 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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,6 +280,7 @@ public final class Def {
|
|||
CallSite nested = DefBootstrap.bootstrap(lookup,
|
||||
call,
|
||||
nestedType,
|
||||
0,
|
||||
DefBootstrap.REFERENCE,
|
||||
interfaceType.name);
|
||||
filter = nested.dynamicInvoker();
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.lang.invoke.MethodHandles;
|
|||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.invoke.MutableCallSite;
|
||||
import java.lang.invoke.WrongMethodTypeException;
|
||||
|
||||
/**
|
||||
* 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. +=).
|
||||
*
|
||||
* <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>
|
||||
* 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).
|
||||
*/
|
||||
|
@ -97,7 +107,7 @@ public final class DefBootstrap {
|
|||
private final Object[] args;
|
||||
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);
|
||||
if (type.parameterType(0) != Object.class) {
|
||||
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.flavor = flavor;
|
||||
this.args = args;
|
||||
this.depth = initialDepth;
|
||||
|
||||
MethodHandle fallback = FALLBACK.bindTo(this)
|
||||
.asCollector(Object[].class, type.parameterCount())
|
||||
|
@ -226,11 +237,14 @@ public final class DefBootstrap {
|
|||
private final int flavor;
|
||||
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);
|
||||
this.name = name;
|
||||
this.flavor = flavor;
|
||||
this.flags = flags;
|
||||
if (initialDepth > 0) {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
MethodHandle fallback = FALLBACK.bindTo(this)
|
||||
.asCollector(Object[].class, type.parameterCount())
|
||||
|
@ -248,7 +262,9 @@ public final class DefBootstrap {
|
|||
case SHIFT_OPERATOR:
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
return unary;
|
||||
|
@ -257,7 +273,9 @@ public final class DefBootstrap {
|
|||
return lookupGeneric(); // can handle nulls, casts if supported
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
return binary;
|
||||
|
@ -267,11 +285,15 @@ public final class DefBootstrap {
|
|||
}
|
||||
|
||||
private MethodHandle lookupGeneric() {
|
||||
if ((flags & OPERATOR_COMPOUND_ASSIGNMENT) != 0) {
|
||||
return DefMath.lookupGenericWithCast(name);
|
||||
} else {
|
||||
return DefMath.lookupGeneric(name);
|
||||
MethodHandle target = DefMath.lookupGeneric(name);
|
||||
if ((flags & OPERATOR_EXPLICIT_CAST) != 0) {
|
||||
// static cast to the return type
|
||||
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 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;
|
||||
if (flavor == BINARY_OPERATOR || flavor == SHIFT_OPERATOR) {
|
||||
|
@ -384,12 +414,16 @@ public final class DefBootstrap {
|
|||
/**
|
||||
* invokeDynamic bootstrap method
|
||||
* <p>
|
||||
* In addition to ordinary parameters, we also take a static parameter {@code flavor} which
|
||||
* tells us what type of dynamic call it is (and which part of whitelist to look at).
|
||||
* In addition to ordinary parameters, we also take some static parameters:
|
||||
* <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>
|
||||
* 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
|
||||
switch(flavor) {
|
||||
// "function-call" like things get a polymorphic cache
|
||||
|
@ -408,7 +442,7 @@ public final class DefBootstrap {
|
|||
if (args.length != numLambdas + 1) {
|
||||
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 STORE:
|
||||
case ARRAY_LOAD:
|
||||
|
@ -417,7 +451,7 @@ public final class DefBootstrap {
|
|||
if (args.length > 0) {
|
||||
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:
|
||||
if (args.length != 1) {
|
||||
throw new BootstrapMethodError("Invalid number of parameters for reference call");
|
||||
|
@ -425,7 +459,7 @@ public final class DefBootstrap {
|
|||
if (args[0] instanceof String == false) {
|
||||
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
|
||||
case UNARY_OPERATOR:
|
||||
|
@ -442,11 +476,11 @@ public final class DefBootstrap {
|
|||
// we just don't need it anywhere else.
|
||||
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.
|
||||
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:
|
||||
throw new BootstrapMethodError("Illegal static bootstrap parameter for flavor: " + flavor);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.lang.invoke.MethodType;
|
|||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -1027,25 +1028,7 @@ public class DefMath {
|
|||
* class if its not a boxed type.
|
||||
*/
|
||||
private static Class<?> unbox(Class<?> clazz) {
|
||||
if (clazz == Boolean.class) {
|
||||
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;
|
||||
}
|
||||
return MethodType.methodType(clazz).unwrap().returnType();
|
||||
}
|
||||
|
||||
/** Unary promotion. All Objects are promoted to Object. */
|
||||
|
@ -1147,39 +1130,49 @@ public class DefMath {
|
|||
return TYPE_OP_MAPPING.get(Object.class).get(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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) {
|
||||
Class<?> c = lhs.getClass();
|
||||
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);
|
||||
return dynamicCast(lhs.getClass(), returnValue);
|
||||
} else {
|
||||
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 */
|
||||
static Number getNumber(Object o) {
|
||||
if (o instanceof Number) {
|
||||
|
@ -1192,11 +1185,15 @@ public class DefMath {
|
|||
}
|
||||
|
||||
private static final MethodHandle DYNAMIC_CAST;
|
||||
private static final MethodHandle DYNAMIC_RECEIVER_CAST;
|
||||
static {
|
||||
final Lookup lookup = MethodHandles.lookup();
|
||||
try {
|
||||
DYNAMIC_CAST = lookup.findStatic(lookup.lookupClass(),
|
||||
"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));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError(e);
|
||||
|
@ -1204,16 +1201,24 @@ public class DefMath {
|
|||
}
|
||||
|
||||
/** Looks up generic method, with a dynamic cast to the receiver's type. (compound assignment) */
|
||||
public static MethodHandle lookupGenericWithCast(String name) {
|
||||
MethodHandle generic = lookupGeneric(name);
|
||||
// adapt dynamic cast to the generic method
|
||||
MethodHandle cast = DYNAMIC_CAST.asType(MethodType.methodType(generic.type().returnType(),
|
||||
generic.type().returnType(),
|
||||
generic.type().parameterType(0)));
|
||||
public static MethodHandle dynamicCast(MethodHandle target) {
|
||||
// adapt dynamic receiver cast to the generic method
|
||||
MethodHandle cast = DYNAMIC_RECEIVER_CAST.asType(MethodType.methodType(target.type().returnType(),
|
||||
target.type().returnType(),
|
||||
target.type().parameterType(0)));
|
||||
// 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);
|
||||
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) */
|
||||
|
@ -1221,12 +1226,18 @@ public class DefMath {
|
|||
MethodType newType = MethodType.methodType(classA).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()) {
|
||||
return target; // no conversion
|
||||
return target;
|
||||
}
|
||||
|
||||
// this is safe for our uses of it here only, because we change just the return value,
|
||||
// the original method itself does all the type checks correctly.
|
||||
// we don't allow the to/from boolean conversions of explicitCastArguments
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.objectweb.asm.commons.Method;
|
|||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
@ -78,15 +79,17 @@ import static org.elasticsearch.painless.WriterConstants.UTILITY_TYPE;
|
|||
*/
|
||||
public final class MethodWriter extends GeneratorAdapter {
|
||||
private final BitSet statements;
|
||||
private final CompilerSettings settings;
|
||||
|
||||
private final Deque<List<org.objectweb.asm.Type>> stringConcatArgs =
|
||||
(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),
|
||||
access, method.getName(), method.getDescriptor());
|
||||
|
||||
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 */
|
||||
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);
|
||||
String descriptor = methodType.getDescriptor();
|
||||
|
||||
int flags = 0;
|
||||
if (compoundAssignment) {
|
||||
flags |= DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT;
|
||||
}
|
||||
switch (operation) {
|
||||
case MUL:
|
||||
invokeDynamic("mul", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("mul", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case DIV:
|
||||
invokeDynamic("div", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("div", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case REM:
|
||||
invokeDynamic("rem", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("rem", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case ADD:
|
||||
// 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) {
|
||||
flags |= DefBootstrap.OPERATOR_ALLOWS_NULL;
|
||||
}
|
||||
invokeDynamic("add", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("add", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case SUB:
|
||||
invokeDynamic("sub", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("sub", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case LSH:
|
||||
invokeDynamic("lsh", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
invokeDefCall("lsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
break;
|
||||
case USH:
|
||||
invokeDynamic("ush", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
invokeDefCall("ush", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
break;
|
||||
case RSH:
|
||||
invokeDynamic("rsh", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
invokeDefCall("rsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
|
||||
break;
|
||||
case BWAND:
|
||||
invokeDynamic("and", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("and", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case XOR:
|
||||
invokeDynamic("xor", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("xor", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
case BWOR:
|
||||
invokeDynamic("or", descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
invokeDefCall("or", methodType, DefBootstrap.BINARY_OPERATOR, flags);
|
||||
break;
|
||||
default:
|
||||
throw location.createError(new IllegalStateException("Illegal tree structure."));
|
||||
|
@ -391,4 +389,19 @@ public final class MethodWriter extends GeneratorAdapter {
|
|||
public void visitEnd() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,6 +128,12 @@ public final class PainlessScriptEngineService extends AbstractComponent impleme
|
|||
compilerSettings.setPicky(Boolean.parseBoolean(value));
|
||||
}
|
||||
|
||||
value = copy.remove(CompilerSettings.INITIAL_CALL_SITE_DEPTH);
|
||||
|
||||
if (value != null) {
|
||||
compilerSettings.setInitialCallSiteDepth(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
if (!copy.isEmpty()) {
|
||||
throw new IllegalArgumentException("Unrecognized compile-time parameter(s): " + copy);
|
||||
}
|
||||
|
|
|
@ -85,9 +85,10 @@ public final class WriterConstants {
|
|||
public final static Method MATCHER_FIND = getAsmMethod(boolean.class, "find");
|
||||
|
||||
/** dynamic callsite bootstrap signature */
|
||||
public final static MethodType DEF_BOOTSTRAP_TYPE =
|
||||
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, int.class, Object[].class);
|
||||
public final static Handle DEF_BOOTSTRAP_HANDLE =
|
||||
final static MethodType DEF_BOOTSTRAP_TYPE =
|
||||
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class,
|
||||
int.class, int.class, Object[].class);
|
||||
final static Handle DEF_BOOTSTRAP_HANDLE =
|
||||
new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(DefBootstrap.class),
|
||||
"bootstrap", DEF_BOOTSTRAP_TYPE.toMethodDescriptorString(), false);
|
||||
|
||||
|
|
|
@ -241,7 +241,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
|
@ -42,6 +43,7 @@ public final class EBinary extends AExpression {
|
|||
Type shiftDistance; // for shifts, the RHS is promoted independently
|
||||
|
||||
boolean cat = false;
|
||||
boolean originallyExplicit = false; // record whether there was originally an explicit cast
|
||||
|
||||
public EBinary(Location location, Operation operation, AExpression left, AExpression right) {
|
||||
super(location);
|
||||
|
@ -53,6 +55,7 @@ public final class EBinary extends AExpression {
|
|||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
originallyExplicit = explicit;
|
||||
if (operation == Operation.MUL) {
|
||||
analyzeMul(locals);
|
||||
} else if (operation == Operation.DIV) {
|
||||
|
@ -639,7 +642,13 @@ public final class EBinary extends AExpression {
|
|||
right.write(writer, globals);
|
||||
|
||||
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 {
|
||||
writer.writeBinaryInstruction(location, actual, operation);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.elasticsearch.painless.Locals.Variable;
|
|||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
import java.lang.invoke.LambdaMetafactory;
|
||||
|
@ -90,8 +89,8 @@ public class ECapturingFunctionRef extends AExpression implements ILambda {
|
|||
} else if (ref == null) {
|
||||
// typed interface, dynamic implementation
|
||||
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());
|
||||
String descriptor = Type.getMethodType(expected.type, captured.type.type).getDescriptor();
|
||||
writer.invokeDynamic(call, descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.REFERENCE, expected.name);
|
||||
Type methodType = Type.getMethodType(expected.type, captured.type.type);
|
||||
writer.invokeDefCall(call, methodType, DefBootstrap.REFERENCE, expected.name);
|
||||
} else {
|
||||
// typed interface, typed implementation
|
||||
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.elasticsearch.painless.Definition.Sort;
|
|||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
@ -214,7 +215,10 @@ public final class EChain extends AExpression {
|
|||
|
||||
expression.expected = expression.actual;
|
||||
} 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.explicit = true;
|
||||
} else {
|
||||
|
@ -353,7 +357,7 @@ public final class EChain extends AExpression {
|
|||
// write the operation instruction for compound assignment
|
||||
if (promote.sort == Sort.DEF) {
|
||||
writer.writeDynamicBinaryInstruction(location, promote,
|
||||
Definition.DEF_TYPE, Definition.DEF_TYPE, operation, true);
|
||||
Definition.DEF_TYPE, Definition.DEF_TYPE, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
|
||||
} else {
|
||||
writer.writeBinaryInstruction(location, promote, operation);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.elasticsearch.painless.MethodWriter;
|
|||
|
||||
import static org.elasticsearch.painless.WriterConstants.OBJECTS_TYPE;
|
||||
import static org.elasticsearch.painless.WriterConstants.EQUALS;
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
|
||||
/**
|
||||
* Represents a comparison expression.
|
||||
|
@ -498,8 +497,7 @@ public final class EComp extends AExpression {
|
|||
if (right.isNull) {
|
||||
writer.ifNull(jump);
|
||||
} else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) {
|
||||
writer.invokeDynamic("eq", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR,
|
||||
DefBootstrap.OPERATOR_ALLOWS_NULL);
|
||||
writer.invokeDefCall("eq", descriptor, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
|
||||
writejump = false;
|
||||
} else {
|
||||
writer.ifCmp(promotedType.type, MethodWriter.EQ, jump);
|
||||
|
@ -508,23 +506,22 @@ public final class EComp extends AExpression {
|
|||
if (right.isNull) {
|
||||
writer.ifNonNull(jump);
|
||||
} else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) {
|
||||
writer.invokeDynamic("eq", descriptor.getDescriptor(), DEF_BOOTSTRAP_HANDLE, DefBootstrap.BINARY_OPERATOR,
|
||||
DefBootstrap.OPERATOR_ALLOWS_NULL);
|
||||
writer.invokeDefCall("eq", descriptor, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
|
||||
writer.ifZCmp(MethodWriter.EQ, jump);
|
||||
} else {
|
||||
writer.ifCmp(promotedType.type, MethodWriter.NE, jump);
|
||||
}
|
||||
} 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;
|
||||
} 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;
|
||||
} 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;
|
||||
} 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;
|
||||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
|
|
|
@ -31,8 +31,6 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.objectweb.asm.Label;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
|
||||
/**
|
||||
* Represents a unary math expression.
|
||||
*/
|
||||
|
@ -41,6 +39,7 @@ public final class EUnary extends AExpression {
|
|||
final Operation operation;
|
||||
AExpression child;
|
||||
Type promote;
|
||||
boolean originallyExplicit = false; // record whether there was originally an explicit cast
|
||||
|
||||
public EUnary(Location location, Operation operation, AExpression child) {
|
||||
super(location);
|
||||
|
@ -51,6 +50,7 @@ public final class EUnary extends AExpression {
|
|||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
originallyExplicit = explicit;
|
||||
if (operation == Operation.NOT) {
|
||||
analyzeNot(locals);
|
||||
} else if (operation == Operation.BWNOT) {
|
||||
|
@ -203,10 +203,16 @@ public final class EUnary extends AExpression {
|
|||
Sort sort = promote.sort;
|
||||
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 (sort == Sort.DEF) {
|
||||
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 {
|
||||
if (sort == Sort.INT) {
|
||||
writer.push(-1);
|
||||
|
@ -221,14 +227,14 @@ public final class EUnary extends AExpression {
|
|||
} else if (operation == Operation.SUB) {
|
||||
if (sort == Sort.DEF) {
|
||||
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 {
|
||||
writer.math(MethodWriter.NEG, actual.type);
|
||||
}
|
||||
} else if (operation == Operation.ADD) {
|
||||
if (sort == Sort.DEF) {
|
||||
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 {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
|
|
|
@ -27,8 +27,6 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.objectweb.asm.Type;
|
||||
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.)
|
||||
*/
|
||||
|
@ -62,15 +60,15 @@ final class LDefArray extends ALink implements IDefLink {
|
|||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type);
|
||||
writer.invokeDynamic("arrayLoad", desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.ARRAY_LOAD);
|
||||
Type methodType = Type.getMethodType(after.type, Definition.DEF_TYPE.type, index.actual.type);
|
||||
writer.invokeDefCall("arrayLoad", methodType, DefBootstrap.ARRAY_LOAD);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
|
||||
writer.invokeDynamic("arrayStore", desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.ARRAY_STORE);
|
||||
Type methodType = Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
|
||||
writer.invokeDefCall("arrayStore", methodType, DefBootstrap.ARRAY_STORE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,6 @@ import org.objectweb.asm.Type;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
|
||||
/**
|
||||
* Represents a method call made on a def type. (Internal only.)
|
||||
*/
|
||||
|
@ -92,32 +90,30 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
void load(MethodWriter writer, Globals globals) {
|
||||
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
|
||||
signature.append(Definition.DEF_TYPE.type.getDescriptor());
|
||||
parameterTypes.add(Definition.DEF_TYPE.type);
|
||||
|
||||
// append each argument
|
||||
for (AExpression argument : arguments) {
|
||||
signature.append(argument.actual.type.getDescriptor());
|
||||
parameterTypes.add(argument.actual.type);
|
||||
if (argument instanceof ILambda) {
|
||||
ILambda lambda = (ILambda) argument;
|
||||
for (Type capture : lambda.getCaptures()) {
|
||||
signature.append(capture.getDescriptor());
|
||||
parameterTypes.add(capture);
|
||||
}
|
||||
}
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
signature.append(')');
|
||||
// return value
|
||||
signature.append(after.type.getDescriptor());
|
||||
// create method type from return value and arguments
|
||||
Type methodType = Type.getMethodType(after.type, parameterTypes.toArray(new Type[0]));
|
||||
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add(DefBootstrap.METHOD_CALL);
|
||||
args.add(recipe);
|
||||
args.addAll(pointers);
|
||||
writer.invokeDynamic(name, signature.toString(), DEF_BOOTSTRAP_HANDLE, args.toArray());
|
||||
writer.invokeDefCall(name, methodType, DefBootstrap.METHOD_CALL, args.toArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,8 +27,6 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.objectweb.asm.Type;
|
||||
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.)
|
||||
*/
|
||||
|
@ -59,15 +57,15 @@ final class LDefField extends ALink implements IDefLink {
|
|||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type);
|
||||
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.LOAD);
|
||||
Type methodType = Type.getMethodType(after.type, Definition.DEF_TYPE.type);
|
||||
writer.invokeDefCall(value, methodType, DefBootstrap.LOAD);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
|
||||
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.STORE);
|
||||
Type methodType = Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
|
||||
writer.invokeDefCall(value, methodType, DefBootstrap.STORE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ import org.elasticsearch.painless.Locals.Variable;
|
|||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT;
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT;
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE;
|
||||
|
@ -193,8 +192,8 @@ public class SEach extends AStatement {
|
|||
|
||||
if (method == null) {
|
||||
Type itr = Definition.getType("Iterator");
|
||||
String desc = org.objectweb.asm.Type.getMethodDescriptor(itr.type, Definition.DEF_TYPE.type);
|
||||
writer.invokeDynamic("iterator", desc, DEF_BOOTSTRAP_HANDLE, DefBootstrap.ITERATOR);
|
||||
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(itr.type, Definition.DEF_TYPE.type);
|
||||
writer.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR);
|
||||
} else if (java.lang.reflect.Modifier.isInterface(method.owner.clazz.getModifiers())) {
|
||||
writer.invokeInterface(method.owner.type, method.method);
|
||||
} else {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.CompilerSettings;
|
||||
import org.elasticsearch.painless.Constant;
|
||||
import org.elasticsearch.painless.Def;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
|
@ -143,12 +144,12 @@ public class SFunction extends AStatement {
|
|||
}
|
||||
|
||||
/** 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;
|
||||
if (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);
|
||||
function.endMethod();
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.CompilerSettings;
|
||||
import org.elasticsearch.painless.Constant;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.MethodKey;
|
||||
|
@ -59,6 +60,7 @@ public final class SSource extends AStatement {
|
|||
final String name;
|
||||
final String source;
|
||||
final Printer debugStream;
|
||||
final CompilerSettings settings;
|
||||
final MainMethodReserved reserved;
|
||||
final List<SFunction> functions;
|
||||
final Globals globals;
|
||||
|
@ -67,10 +69,11 @@ public final class SSource extends AStatement {
|
|||
private Locals mainMethod;
|
||||
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) {
|
||||
super(location);
|
||||
|
||||
this.settings = settings;
|
||||
this.name = name;
|
||||
this.source = source;
|
||||
this.debugStream = debugStream;
|
||||
|
@ -151,7 +154,7 @@ public final class SSource extends AStatement {
|
|||
visitor.visitSource(Location.computeSourceName(name, source), null);
|
||||
|
||||
// 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.loadThis();
|
||||
constructor.loadArgs();
|
||||
|
@ -160,14 +163,14 @@ public final class SSource extends AStatement {
|
|||
constructor.endMethod();
|
||||
|
||||
// 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();
|
||||
write(execute, globals);
|
||||
execute.endMethod();
|
||||
|
||||
// Write all 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 :)
|
||||
|
@ -175,7 +178,7 @@ public final class SSource extends AStatement {
|
|||
List<SFunction> current = new ArrayList<>(globals.getSyntheticMethods().values());
|
||||
globals.getSyntheticMethods().clear();
|
||||
for (SFunction function : current) {
|
||||
function.write(visitor, globals);
|
||||
function.write(visitor, settings, globals);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,7 +198,7 @@ public final class SSource extends AStatement {
|
|||
|
||||
// Initialize the constants in a static initializer
|
||||
final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC,
|
||||
WriterConstants.CLINIT, visitor, globals.getStatements());
|
||||
WriterConstants.CLINIT, visitor, globals.getStatements(), settings);
|
||||
for (Constant constant : inits) {
|
||||
constant.initializer.accept(clinit);
|
||||
clinit.putStatic(CLASS_TYPE, constant.name, constant.type);
|
||||
|
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* 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();"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
});
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
|
||||
"toString",
|
||||
MethodType.methodType(String.class, Object.class),
|
||||
0,
|
||||
DefBootstrap.METHOD_CALL, 0L);
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
assertDepthEquals(site, 0);
|
||||
|
@ -53,6 +54,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
|
||||
"toString",
|
||||
MethodType.methodType(String.class, Object.class),
|
||||
0,
|
||||
DefBootstrap.METHOD_CALL, 0L);
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
assertDepthEquals(site, 0);
|
||||
|
@ -75,6 +77,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
CallSite site = DefBootstrap.bootstrap(MethodHandles.publicLookup(),
|
||||
"toString",
|
||||
MethodType.methodType(String.class, Object.class),
|
||||
0,
|
||||
DefBootstrap.METHOD_CALL, 0L);
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
assertDepthEquals(site, 0);
|
||||
|
@ -98,6 +101,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
DefBootstrap.PIC site = (DefBootstrap.PIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
|
||||
"size",
|
||||
MethodType.methodType(int.class, Object.class),
|
||||
0,
|
||||
DefBootstrap.METHOD_CALL, 0L);
|
||||
site.depth = DefBootstrap.PIC.MAX_DEPTH; // mark megamorphic
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
|
@ -127,6 +131,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
|
||||
"add",
|
||||
MethodType.methodType(Object.class, Object.class, Object.class),
|
||||
0,
|
||||
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
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(),
|
||||
"add",
|
||||
MethodType.methodType(Object.class, Object.class, Object.class),
|
||||
0,
|
||||
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
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(),
|
||||
"eq",
|
||||
MethodType.methodType(boolean.class, Object.class, Object.class),
|
||||
0,
|
||||
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
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(),
|
||||
"eq",
|
||||
MethodType.methodType(boolean.class, Object.class, Object.class),
|
||||
0,
|
||||
DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
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(),
|
||||
"add",
|
||||
MethodType.methodType(Object.class, int.class, Object.class),
|
||||
0,
|
||||
DefBootstrap.BINARY_OPERATOR, 0);
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
expectThrows(NullPointerException.class, () -> {
|
||||
|
@ -182,6 +191,7 @@ public class DefBootstrapTests extends ESTestCase {
|
|||
DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(MethodHandles.publicLookup(),
|
||||
"add",
|
||||
MethodType.methodType(Object.class, int.class, Object.class),
|
||||
0,
|
||||
DefBootstrap.BINARY_OPERATOR, 0);
|
||||
MethodHandle handle = site.dynamicInvoker();
|
||||
assertEquals(2, (Object)handle.invokeExact(1, (Object)1));
|
||||
|
|
|
@ -243,18 +243,22 @@ public class DefOptimizationTests extends ScriptTestCase {
|
|||
public void testAddOptNullGuards() {
|
||||
// needs null guard
|
||||
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 + ".*");
|
||||
// still needs null guard, NPE is the wrong thing!
|
||||
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 + ".*");
|
||||
// a primitive argument is present: no null guard needed
|
||||
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 + ".*");
|
||||
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 + ".*");
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ public abstract class ScriptTestCase extends ESTestCase {
|
|||
public Object exec(String script, Map<String, Object> vars) {
|
||||
Map<String,String> compilerSettings = new HashMap<>();
|
||||
compilerSettings.put(CompilerSettings.PICKY, "true");
|
||||
compilerSettings.put(CompilerSettings.INITIAL_CALL_SITE_DEPTH, random().nextBoolean() ? "0" : "10");
|
||||
return exec(script, vars, compilerSettings, null);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue