Skip to content

Commit

Permalink
Painless: Replace Painless Type with Java Class during Casts (#27847)
Browse files Browse the repository at this point in the history
This is the first step in a series to replace Painless Type with Java Class for any casting done during compilation. There should be no behavioural change.
  • Loading branch information
jdconrad authored Jan 22, 2018
1 parent ba5b583 commit ef5c041
Show file tree
Hide file tree
Showing 6 changed files with 597 additions and 463 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -76,6 +77,13 @@ public final class Definition {
public final Type ArrayListType;
public final Type HashMapType;

/** Marker class for def type to be used during type analysis. */
public static final class def {
private def() {

}
}

public static final class Type {
public final String name;
public final int dimensions;
Expand Down Expand Up @@ -365,40 +373,41 @@ public Method getFunctionalMethod() {
}

public static class Cast {

/** Create a standard cast with no boxing/unboxing. */
public static Cast standard(Type from, Type to, boolean explicit) {
public static Cast standard(Class<?> from, Class<?> to, boolean explicit) {
return new Cast(from, to, explicit, null, null, null, null);
}

/** Create a cast where the from type will be unboxed, and then the cast will be performed. */
public static Cast unboxFrom(Type from, Type to, boolean explicit, Type unboxFrom) {
public static Cast unboxFrom(Class<?> from, Class<?> to, boolean explicit, Class<?> unboxFrom) {
return new Cast(from, to, explicit, unboxFrom, null, null, null);
}

/** Create a cast where the to type will be unboxed, and then the cast will be performed. */
public static Cast unboxTo(Type from, Type to, boolean explicit, Type unboxTo) {
public static Cast unboxTo(Class<?> from, Class<?> to, boolean explicit, Class<?> unboxTo) {
return new Cast(from, to, explicit, null, unboxTo, null, null);
}

/** Create a cast where the from type will be boxed, and then the cast will be performed. */
public static Cast boxFrom(Type from, Type to, boolean explicit, Type boxFrom) {
public static Cast boxFrom(Class<?> from, Class<?> to, boolean explicit, Class<?> boxFrom) {
return new Cast(from, to, explicit, null, null, boxFrom, null);
}

/** Create a cast where the to type will be boxed, and then the cast will be performed. */
public static Cast boxTo(Type from, Type to, boolean explicit, Type boxTo) {
public static Cast boxTo(Class<?> from, Class<?> to, boolean explicit, Class<?> boxTo) {
return new Cast(from, to, explicit, null, null, null, boxTo);
}

public final Type from;
public final Type to;
public final Class<?> from;
public final Class<?> to;
public final boolean explicit;
public final Type unboxFrom;
public final Type unboxTo;
public final Type boxFrom;
public final Type boxTo;
public final Class<?> unboxFrom;
public final Class<?> unboxTo;
public final Class<?> boxFrom;
public final Class<?> boxTo;

private Cast(Type from, Type to, boolean explicit, Type unboxFrom, Type unboxTo, Type boxFrom, Type boxTo) {
private Cast(Class<?> from, Class<?> to, boolean explicit, Class<?> unboxFrom, Class<?> unboxTo, Class<?> boxFrom, Class<?> boxTo) {
this.from = from;
this.to = to;
this.explicit = explicit;
Expand Down Expand Up @@ -499,6 +508,92 @@ public static boolean isConstantType(Type constant) {
constant.clazz == String.class;
}

public static Class<?> ObjectClassTodefClass(Class<?> clazz) {
if (clazz.isArray()) {
Class<?> component = clazz.getComponentType();
int dimensions = 1;

while (component.isArray()) {
component = component.getComponentType();
++dimensions;
}

if (component == Object.class) {
char[] braces = new char[dimensions];
Arrays.fill(braces, '[');

String descriptor = new String(braces) + org.objectweb.asm.Type.getType(def.class).getDescriptor();
org.objectweb.asm.Type type = org.objectweb.asm.Type.getType(descriptor);

try {
return Class.forName(type.getInternalName().replace('/', '.'));
} catch (ClassNotFoundException exception) {
throw new IllegalStateException("internal error", exception);
}
}
} else if (clazz == Object.class) {
return def.class;
}

return clazz;
}

public static Class<?> defClassToObjectClass(Class<?> clazz) {
if (clazz.isArray()) {
Class<?> component = clazz.getComponentType();
int dimensions = 1;

while (component.isArray()) {
component = component.getComponentType();
++dimensions;
}

if (component == def.class) {
char[] braces = new char[dimensions];
Arrays.fill(braces, '[');

String descriptor = new String(braces) + org.objectweb.asm.Type.getType(Object.class).getDescriptor();
org.objectweb.asm.Type type = org.objectweb.asm.Type.getType(descriptor);

try {
return Class.forName(type.getInternalName().replace('/', '.'));
} catch (ClassNotFoundException exception) {
throw new IllegalStateException("internal error", exception);
}
}
} else if (clazz == def.class) {
return Object.class;
}

return clazz;
}

public static String ClassToName(Class<?> clazz) {
if (clazz.isArray()) {
Class<?> component = clazz.getComponentType();
int dimensions = 1;

while (component.isArray()) {
component = component.getComponentType();
++dimensions;
}

if (component == def.class) {
StringBuilder builder = new StringBuilder("def");

for (int dimension = 0; dimension < dimensions; dimensions++) {
builder.append("[]");
}

return builder.toString();
}
} else if (clazz == def.class) {
return "def";
}

return clazz.getCanonicalName();
}

public RuntimeClass getRuntimeClass(Class<?> clazz) {
return runtimeMap.get(clazz);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@
package org.elasticsearch.painless;

import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Definition.def;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
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;
Expand Down Expand Up @@ -128,68 +130,68 @@ public void writeLoopCounter(int slot, int count, Location location) {
mark(end);
}

public void writeCast(final Cast cast) {
public void writeCast(Cast cast) {
if (cast != null) {
if (cast.from.clazz == char.class && cast.to.clazz == String.class) {
if (cast.from == char.class && cast.to == String.class) {
invokeStatic(UTILITY_TYPE, CHAR_TO_STRING);
} else if (cast.from.clazz == String.class && cast.to.clazz == char.class) {
} else if (cast.from == String.class && cast.to == char.class) {
invokeStatic(UTILITY_TYPE, STRING_TO_CHAR);
} else if (cast.unboxFrom != null) {
unbox(cast.unboxFrom.type);
unbox(getType(cast.unboxFrom));
writeCast(cast.from, cast.to);
} else if (cast.unboxTo != null) {
if (cast.from.dynamic) {
if (cast.from == def.class) {
if (cast.explicit) {
if (cast.to.clazz == Boolean.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
else if (cast.to.clazz == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_EXPLICIT);
else if (cast.to.clazz == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_EXPLICIT);
else if (cast.to.clazz == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_EXPLICIT);
else if (cast.to.clazz == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_EXPLICIT);
else if (cast.to.clazz == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_EXPLICIT);
else if (cast.to.clazz == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_EXPLICIT);
else if (cast.to.clazz == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_EXPLICIT);
if (cast.to == Boolean.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
else if (cast.to == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_EXPLICIT);
else if (cast.to == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_EXPLICIT);
else if (cast.to == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_EXPLICIT);
else if (cast.to == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_EXPLICIT);
else if (cast.to == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_EXPLICIT);
else if (cast.to == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_EXPLICIT);
else if (cast.to == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_EXPLICIT);
else {
throw new IllegalStateException("Illegal tree structure.");
}
} else {
if (cast.to.clazz == Boolean.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
else if (cast.to.clazz == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_IMPLICIT);
else if (cast.to.clazz == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_IMPLICIT);
else if (cast.to.clazz == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_IMPLICIT);
else if (cast.to.clazz == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_IMPLICIT);
else if (cast.to.clazz == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_IMPLICIT);
else if (cast.to.clazz == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_IMPLICIT);
else if (cast.to.clazz == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_IMPLICIT);
if (cast.to == Boolean.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
else if (cast.to == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_IMPLICIT);
else if (cast.to == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_IMPLICIT);
else if (cast.to == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_IMPLICIT);
else if (cast.to == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_IMPLICIT);
else if (cast.to == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_IMPLICIT);
else if (cast.to == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_IMPLICIT);
else if (cast.to == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_IMPLICIT);
else {
throw new IllegalStateException("Illegal tree structure.");
}
}
} else {
writeCast(cast.from, cast.to);
unbox(cast.unboxTo.type);
unbox(getType(cast.unboxTo));
}
} else if (cast.boxFrom != null) {
box(cast.boxFrom.type);
box(getType(cast.boxFrom));
writeCast(cast.from, cast.to);
} else if (cast.boxTo != null) {
writeCast(cast.from, cast.to);
box(cast.boxTo.type);
box(getType(cast.boxTo));
} else {
writeCast(cast.from, cast.to);
}
}
}

private void writeCast(final Type from, final Type to) {
private void writeCast(Class<?> from, Class<?> to) {
if (from.equals(to)) {
return;
}

if (from.clazz != boolean.class && from.clazz.isPrimitive() && to.clazz != boolean.class && to.clazz.isPrimitive()) {
cast(from.type, to.type);
if (from != boolean.class && from.isPrimitive() && to != boolean.class && to.isPrimitive()) {
cast(getType(from), getType(to));
} else {
if (!to.clazz.isAssignableFrom(from.clazz)) {
checkCast(to.type);
if (!to.isAssignableFrom(from)) {
checkCast(getType(to));
}
}
}
Expand All @@ -202,6 +204,29 @@ public void box(org.objectweb.asm.Type type) {
valueOf(type);
}

public static Type getType(Class<?> clazz) {
if (clazz.isArray()) {
Class<?> component = clazz.getComponentType();
int dimensions = 1;

while (component.isArray()) {
component = component.getComponentType();
++dimensions;
}

if (component == def.class) {
char[] braces = new char[dimensions];
Arrays.fill(braces, '[');

return Type.getType(new String(braces) + Type.getType(Object.class).getDescriptor());
}
} else if (clazz == def.class) {
return Type.getType(Object.class);
}

return Type.getType(clazz);
}

public void writeBranch(final Label tru, final Label fals) {
if (tru != null) {
visitJumpInsn(Opcodes.IFNE, tru);
Expand All @@ -227,7 +252,7 @@ public int writeNewStrings() {
}
}

public void writeAppendStrings(final Type type) {
public void writeAppendStrings(final Definition.Type type) {
if (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE != null) {
// Java 9+: record type information
stringConcatArgs.peek().add(type.type);
Expand Down Expand Up @@ -267,7 +292,7 @@ public void writeToStrings() {
}

/** Writes a dynamic binary instruction: returnType, lhs, and rhs can be different */
public void writeDynamicBinaryInstruction(Location location, Type returnType, Type lhs, Type rhs,
public void writeDynamicBinaryInstruction(Location location, Definition.Type returnType, Definition.Type lhs, Definition.Type rhs,
Operation operation, int flags) {
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(returnType.type, lhs.type, rhs.type);

Expand Down Expand Up @@ -318,7 +343,7 @@ public void writeDynamicBinaryInstruction(Location location, Type returnType, Ty
}

/** Writes a static binary instruction */
public void writeBinaryInstruction(Location location, Type type, Operation operation) {
public void writeBinaryInstruction(Location location, Definition.Type type, Operation operation) {
if ((type.clazz == float.class || type.clazz == double.class) &&
(operation == Operation.LSH || operation == Operation.USH ||
operation == Operation.RSH || operation == Operation.BWAND ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.elasticsearch.painless.node;

import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Cast;

import java.util.Objects;
Expand Down Expand Up @@ -63,6 +64,6 @@ void write(MethodWriter writer, Globals globals) {

@Override
public String toString() {
return singleLineToString(cast.to, child);
return singleLineToString(Definition.ClassToName(cast.to), child);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@ private static void assertCast(Type actual, Type expected, boolean mustBeExplici
}

Cast cast = definition.caster.getLegalCast(location, actual, expected, true, false);
assertEquals(actual, cast.from);
assertEquals(expected, cast.to);
assertEquals(actual.clazz, cast.from);
assertEquals(expected.clazz, cast.to);

if (mustBeExplicit) {
ClassCastException error = expectThrows(ClassCastException.class,
() -> definition.caster.getLegalCast(location, actual, expected, false, false));
assertTrue(error.getMessage().startsWith("Cannot cast"));
} else {
cast = definition.caster.getLegalCast(location, actual, expected, false, false);
assertEquals(actual, cast.from);
assertEquals(expected, cast.to);
assertEquals(actual.clazz, cast.from);
assertEquals(expected.clazz, cast.to);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,12 @@ public void testECapturingFunctionRef() {
public void testECast() {
Location l = new Location(getTestName(), 0);
AExpression child = new EConstant(l, "test");
Cast cast = Cast.standard(definition.StringType, definition.IntegerType, true);
Cast cast = Cast.standard(String.class, Integer.class, true);
assertEquals("(ECast java.lang.Integer (EConstant String 'test'))", new ECast(l, child, cast).toString());

l = new Location(getTestName(), 1);
child = new EBinary(l, Operation.ADD, new EConstant(l, "test"), new EConstant(l, 12));
cast = Cast.standard(definition.IntegerType, definition.BooleanType, true);
cast = Cast.standard(Integer.class, Boolean.class, true);
assertEquals("(ECast java.lang.Boolean (EBinary (EConstant String 'test') + (EConstant Integer 12)))",
new ECast(l, child, cast).toString());
}
Expand Down

0 comments on commit ef5c041

Please sign in to comment.