painless: use Java 9 Indy String Concats, if available

This commit is contained in:
Uwe Schindler 2016-05-17 11:15:36 +02:00
parent be0bbce5df
commit 5622e9c9d3
5 changed files with 99 additions and 21 deletions

View File

@ -29,6 +29,11 @@ import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import static org.elasticsearch.painless.WriterConstants.ADDEXACT_INT;
import static org.elasticsearch.painless.WriterConstants.ADDEXACT_LONG;
import static org.elasticsearch.painless.WriterConstants.ADDWOOVERLOW_DOUBLE;
@ -48,6 +53,8 @@ import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_DOUBLE;
import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_FLOAT;
import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_INT;
import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_LONG;
import static org.elasticsearch.painless.WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.MAX_INDY_STRING_CONCAT_ARGS;
import static org.elasticsearch.painless.WriterConstants.MULEXACT_INT;
import static org.elasticsearch.painless.WriterConstants.MULEXACT_LONG;
import static org.elasticsearch.painless.WriterConstants.MULWOOVERLOW_DOUBLE;
@ -66,6 +73,7 @@ import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_ST
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_CONSTRUCTOR;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_TOSTRING;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_TYPE;
import static org.elasticsearch.painless.WriterConstants.STRING_TYPE;
import static org.elasticsearch.painless.WriterConstants.SUBEXACT_INT;
import static org.elasticsearch.painless.WriterConstants.SUBEXACT_LONG;
import static org.elasticsearch.painless.WriterConstants.SUBWOOVERLOW_DOUBLE;
@ -97,6 +105,8 @@ import static org.elasticsearch.painless.WriterConstants.TOSHORTWOOVERFLOW_FLOAT
*/
public final class MethodWriter extends GeneratorAdapter {
private final Deque<List<org.objectweb.asm.Type>> stringConcatArgs = (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE == null) ? null : new ArrayDeque<>();
MethodWriter(int access, Method method, org.objectweb.asm.Type[] exceptions, ClassVisitor cv) {
super(Opcodes.ASM5, cv.visitMethod(access, method.getName(), method.getDescriptor(), null, getInternalNames(exceptions)),
access, method.getName(), method.getDescriptor());
@ -171,30 +181,58 @@ public final class MethodWriter extends GeneratorAdapter {
visitJumpInsn(Opcodes.IFEQ, fals);
}
}
public void writeNewStrings() {
newInstance(STRINGBUILDER_TYPE);
dup();
invokeConstructor(STRINGBUILDER_TYPE, STRINGBUILDER_CONSTRUCTOR);
if (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE != null) {
// Java 9+: we just push our argument collector onto deque
stringConcatArgs.push(new ArrayList<>());
} else {
// Java 8: create a StringBuilder in bytecode
newInstance(STRINGBUILDER_TYPE);
dup();
invokeConstructor(STRINGBUILDER_TYPE, STRINGBUILDER_CONSTRUCTOR);
}
}
public void writeAppendStrings(final Sort sort) {
switch (sort) {
case BOOL: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_BOOLEAN); break;
case CHAR: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_CHAR); break;
case BYTE:
case SHORT:
case INT: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_INT); break;
case LONG: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_LONG); break;
case FLOAT: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_FLOAT); break;
case DOUBLE: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_DOUBLE); break;
case STRING: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_STRING); break;
default: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_OBJECT);
public void writeAppendStrings(final Type type) {
if (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE != null) {
// Java 9+: record type information
stringConcatArgs.peek().add(type.type);
// prevent too many concat args.
// If there are too many, do the actual concat:
if (stringConcatArgs.peek().size() >= MAX_INDY_STRING_CONCAT_ARGS) {
writeToStrings();
writeNewStrings();
// add the return value type as new first param for next concat:
stringConcatArgs.peek().add(STRING_TYPE);
}
} else {
// Java 8: push a StringBuilder append
switch (type.sort) {
case BOOL: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_BOOLEAN); break;
case CHAR: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_CHAR); break;
case BYTE:
case SHORT:
case INT: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_INT); break;
case LONG: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_LONG); break;
case FLOAT: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_FLOAT); break;
case DOUBLE: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_DOUBLE); break;
case STRING: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_STRING); break;
default: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_OBJECT);
}
}
}
public void writeToStrings() {
invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_TOSTRING);
if (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE != null) {
// Java 9+: use type information and push invokeDynamic
final String desc = org.objectweb.asm.Type.getMethodDescriptor(STRING_TYPE,
stringConcatArgs.pop().stream().toArray(org.objectweb.asm.Type[]::new));
invokeDynamic("concat", desc, INDY_STRING_CONCAT_BOOTSTRAP_HANDLE);
} else {
// Java 8: call toString() on StringBuilder
invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_TOSTRING);
}
}
public void writeBinaryInstruction(final CompilerSettings settings, final Definition definition,

View File

@ -27,6 +27,7 @@ import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Map;
@ -81,6 +82,27 @@ public final class WriterConstants {
public final static Method DEF_GT_CALL = getAsmMethod(boolean.class, "gt" , Object.class, Object.class);
public final static Method DEF_GTE_CALL = getAsmMethod(boolean.class, "gte", Object.class, Object.class);
/** dynamic invokedynamic bootstrap for indy string concats (Java 9+) */
public final static Handle INDY_STRING_CONCAT_BOOTSTRAP_HANDLE;
static {
Handle bs;
try {
final Class<?> factory = Class.forName("java.lang.invoke.StringConcatFactory");
final String methodName = "makeConcat";
final MethodType type = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
// ensure it is there:
MethodHandles.publicLookup().findStatic(factory, methodName, type);
bs = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(factory), methodName, type.toMethodDescriptorString());
} catch (ReflectiveOperationException e) {
// not Java 9 - we set it null, so MethodWriter uses StringBuilder:
bs = null;
}
INDY_STRING_CONCAT_BOOTSTRAP_HANDLE = bs;
}
public final static int MAX_INDY_STRING_CONCAT_ARGS = 200;
public final static Type STRING_TYPE = Type.getType(String.class);
public final static Type STRINGBUILDER_TYPE = Type.getType(StringBuilder.class);
public final static Method STRINGBUILDER_CONSTRUCTOR = getAsmMethod(void.class, "<init>");

View File

@ -503,13 +503,13 @@ public final class EBinary extends AExpression {
left.write(settings, definition, adapter);
if (!(left instanceof EBinary) || ((EBinary)left).operation != Operation.ADD || left.actual.sort != Sort.STRING) {
adapter.writeAppendStrings(left.actual.sort);
adapter.writeAppendStrings(left.actual);
}
right.write(settings, definition, adapter);
if (!(right instanceof EBinary) || ((EBinary)right).operation != Operation.ADD || right.actual.sort != Sort.STRING) {
adapter.writeAppendStrings(right.actual.sort);
adapter.writeAppendStrings(right.actual);
}
if (!cat) {

View File

@ -266,13 +266,13 @@ public final class EChain extends AExpression {
if (cat) {
adapter.writeDup(link.size, 1);
link.load(settings, definition, adapter);
adapter.writeAppendStrings(link.after.sort);
adapter.writeAppendStrings(link.after);
expression.write(settings, definition, adapter);
if (!(expression instanceof EBinary) ||
((EBinary)expression).operation != Operation.ADD || expression.actual.sort != Sort.STRING) {
adapter.writeAppendStrings(expression.actual.sort);
adapter.writeAppendStrings(expression.actual);
}
adapter.writeToStrings();

View File

@ -19,6 +19,8 @@
package org.elasticsearch.painless;
import java.util.Locale;
public class StringTests extends ScriptTestCase {
public void testAppend() {
@ -63,6 +65,22 @@ public class StringTests extends ScriptTestCase {
assertEquals("cat" + "cat", exec("String s = 'cat'; return s + s;"));
}
public void testAppendMultiple() {
assertEquals("cat" + true + "abc" + null, exec("String s = \"cat\"; return s + true + 'abc' + null;"));
}
public void testAppendMany() {
StringBuilder script = new StringBuilder("String s = \"cat\"; return s");
StringBuilder result = new StringBuilder("cat");
for (int i = 0; i < WriterConstants.MAX_INDY_STRING_CONCAT_ARGS + 10; i++) {
final String s = String.format(Locale.ROOT, "%03d", i);
script.append(" + '").append(s).append("'.toString()");
result.append(s);
}
//System.out.println(Debugger.toString(script.toString()));
assertEquals(result.toString(), exec(script.toString()));
}
public void testStringAPI() {
assertEquals("", exec("return new String();"));
assertEquals('x', exec("String s = \"x\"; return s.charAt(0);"));