diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/CompilerSettings.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/CompilerSettings.java index 79b75b32b98..94d7a1305d6 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/CompilerSettings.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/CompilerSettings.java @@ -33,6 +33,11 @@ public final class CompilerSettings { * Constant to be used for enabling additional internal compilation checks (slower). */ 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. @@ -44,6 +49,11 @@ public final class CompilerSettings { * makes things slower too, it is only for debugging. */ 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 @@ -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; + } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java index a4033255b77..7c7ff5f7f5a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java @@ -279,7 +279,8 @@ public final class Def { MethodType nestedType = MethodType.methodType(interfaceType.clazz, captures); CallSite nested = DefBootstrap.bootstrap(lookup, call, - nestedType, + nestedType, + 0, DefBootstrap.REFERENCE, interfaceType.name); filter = nested.dynamicInvoker(); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java index a60985eec67..fa533eff213 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java @@ -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,9 +81,18 @@ public final class DefBootstrap { /** * static bootstrap parameter indicating the binary operator is part of compound assignment (e.g. +=). - * + *

+ * 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. + *

+ * 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 *

- * 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: + *

*

* 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); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefMath.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefMath.java index f56256f6cb2..65a26cd3da8 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefMath.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefMath.java @@ -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. */ @@ -1146,40 +1129,50 @@ public class DefMath { public static MethodHandle lookupGeneric(String name) { 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,28 +1185,40 @@ 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, Object.class, Object.class)); + "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); } } /** 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())); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java index 6241b5b4207..97686655a97 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java @@ -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> 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); + } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java index bb94785d339..365060fe004 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java @@ -127,6 +127,12 @@ public final class PainlessScriptEngineService extends AbstractComponent impleme if (value != null) { 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); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java index 6960e906339..d581ba8518a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java @@ -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); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java index b92bd6733c9..a84247afe25 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java @@ -241,7 +241,7 @@ public final class Walker extends PainlessParserBaseVisitor { 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); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java index 11b77b0441f..87b12284789 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java @@ -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); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java index 333787db962..62ecf63b9b3 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java @@ -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()); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java index b249cf4511e..aa5bbcaab9d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java @@ -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); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java index 53684a19005..31050f9f6ce 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java @@ -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.")); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java index f74b91dd5bc..40776811b67 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java @@ -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.")); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java index 02fbb2000f2..93fd21f00b0 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java @@ -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); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java index 716b26d6687..b104ce732ae 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java @@ -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 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 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 diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java index 125b2bee6fc..ba474764cd0 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java @@ -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); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java index 403b377ea9c..96258de0760 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java @@ -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 { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java index 8d3db7f811a..2426508d096 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java @@ -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(); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java index 5e932072ce1..6b870c7367d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java @@ -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 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 functions, Globals globals, List 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 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); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/CastTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/CastTests.java new file mode 100644 index 00000000000..fcee0ddeb3e --- /dev/null +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/CastTests.java @@ -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"); + }); + } +} diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java index 6529bed3a48..6babe51334a 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java @@ -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)); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java index 6fb9151cb0d..fe0f7b6ed82 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java @@ -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 + ".*"); } diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java index 40f59e91120..0bec55b5428 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java @@ -56,6 +56,7 @@ public abstract class ScriptTestCase extends ESTestCase { public Object exec(String script, Map vars) { Map 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); }