painless: use Java 9 Indy String Concats, if available
This commit is contained in:
parent
be0bbce5df
commit
5622e9c9d3
|
@ -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,
|
||||
|
|
|
@ -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>");
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);"));
|
||||
|
|
Loading…
Reference in New Issue