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.GeneratorAdapter;
|
||||||
import org.objectweb.asm.commons.Method;
|
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_INT;
|
||||||
import static org.elasticsearch.painless.WriterConstants.ADDEXACT_LONG;
|
import static org.elasticsearch.painless.WriterConstants.ADDEXACT_LONG;
|
||||||
import static org.elasticsearch.painless.WriterConstants.ADDWOOVERLOW_DOUBLE;
|
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_FLOAT;
|
||||||
import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_INT;
|
import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_INT;
|
||||||
import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_LONG;
|
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_INT;
|
||||||
import static org.elasticsearch.painless.WriterConstants.MULEXACT_LONG;
|
import static org.elasticsearch.painless.WriterConstants.MULEXACT_LONG;
|
||||||
import static org.elasticsearch.painless.WriterConstants.MULWOOVERLOW_DOUBLE;
|
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_CONSTRUCTOR;
|
||||||
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_TOSTRING;
|
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_TOSTRING;
|
||||||
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_TYPE;
|
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_INT;
|
||||||
import static org.elasticsearch.painless.WriterConstants.SUBEXACT_LONG;
|
import static org.elasticsearch.painless.WriterConstants.SUBEXACT_LONG;
|
||||||
import static org.elasticsearch.painless.WriterConstants.SUBWOOVERLOW_DOUBLE;
|
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 {
|
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) {
|
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)),
|
super(Opcodes.ASM5, cv.visitMethod(access, method.getName(), method.getDescriptor(), null, getInternalNames(exceptions)),
|
||||||
access, method.getName(), method.getDescriptor());
|
access, method.getName(), method.getDescriptor());
|
||||||
|
@ -171,30 +181,58 @@ public final class MethodWriter extends GeneratorAdapter {
|
||||||
visitJumpInsn(Opcodes.IFEQ, fals);
|
visitJumpInsn(Opcodes.IFEQ, fals);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeNewStrings() {
|
public void writeNewStrings() {
|
||||||
newInstance(STRINGBUILDER_TYPE);
|
if (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE != null) {
|
||||||
dup();
|
// Java 9+: we just push our argument collector onto deque
|
||||||
invokeConstructor(STRINGBUILDER_TYPE, STRINGBUILDER_CONSTRUCTOR);
|
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) {
|
public void writeAppendStrings(final Type type) {
|
||||||
switch (sort) {
|
if (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE != null) {
|
||||||
case BOOL: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_BOOLEAN); break;
|
// Java 9+: record type information
|
||||||
case CHAR: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_CHAR); break;
|
stringConcatArgs.peek().add(type.type);
|
||||||
case BYTE:
|
// prevent too many concat args.
|
||||||
case SHORT:
|
// If there are too many, do the actual concat:
|
||||||
case INT: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_INT); break;
|
if (stringConcatArgs.peek().size() >= MAX_INDY_STRING_CONCAT_ARGS) {
|
||||||
case LONG: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_LONG); break;
|
writeToStrings();
|
||||||
case FLOAT: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_FLOAT); break;
|
writeNewStrings();
|
||||||
case DOUBLE: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_DOUBLE); break;
|
// add the return value type as new first param for next concat:
|
||||||
case STRING: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_STRING); break;
|
stringConcatArgs.peek().add(STRING_TYPE);
|
||||||
default: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_OBJECT);
|
}
|
||||||
|
} 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() {
|
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,
|
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 org.objectweb.asm.commons.Method;
|
||||||
|
|
||||||
import java.lang.invoke.CallSite;
|
import java.lang.invoke.CallSite;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
import java.util.Map;
|
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_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);
|
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 Type STRINGBUILDER_TYPE = Type.getType(StringBuilder.class);
|
||||||
|
|
||||||
public final static Method STRINGBUILDER_CONSTRUCTOR = getAsmMethod(void.class, "<init>");
|
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);
|
left.write(settings, definition, adapter);
|
||||||
|
|
||||||
if (!(left instanceof EBinary) || ((EBinary)left).operation != Operation.ADD || left.actual.sort != Sort.STRING) {
|
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);
|
right.write(settings, definition, adapter);
|
||||||
|
|
||||||
if (!(right instanceof EBinary) || ((EBinary)right).operation != Operation.ADD || right.actual.sort != Sort.STRING) {
|
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) {
|
if (!cat) {
|
||||||
|
|
|
@ -266,13 +266,13 @@ public final class EChain extends AExpression {
|
||||||
if (cat) {
|
if (cat) {
|
||||||
adapter.writeDup(link.size, 1);
|
adapter.writeDup(link.size, 1);
|
||||||
link.load(settings, definition, adapter);
|
link.load(settings, definition, adapter);
|
||||||
adapter.writeAppendStrings(link.after.sort);
|
adapter.writeAppendStrings(link.after);
|
||||||
|
|
||||||
expression.write(settings, definition, adapter);
|
expression.write(settings, definition, adapter);
|
||||||
|
|
||||||
if (!(expression instanceof EBinary) ||
|
if (!(expression instanceof EBinary) ||
|
||||||
((EBinary)expression).operation != Operation.ADD || expression.actual.sort != Sort.STRING) {
|
((EBinary)expression).operation != Operation.ADD || expression.actual.sort != Sort.STRING) {
|
||||||
adapter.writeAppendStrings(expression.actual.sort);
|
adapter.writeAppendStrings(expression.actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.writeToStrings();
|
adapter.writeToStrings();
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
|
|
||||||
package org.elasticsearch.painless;
|
package org.elasticsearch.painless;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class StringTests extends ScriptTestCase {
|
public class StringTests extends ScriptTestCase {
|
||||||
|
|
||||||
public void testAppend() {
|
public void testAppend() {
|
||||||
|
@ -63,6 +65,22 @@ public class StringTests extends ScriptTestCase {
|
||||||
assertEquals("cat" + "cat", exec("String s = 'cat'; return s + s;"));
|
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() {
|
public void testStringAPI() {
|
||||||
assertEquals("", exec("return new String();"));
|
assertEquals("", exec("return new String();"));
|
||||||
assertEquals('x', exec("String s = \"x\"; return s.charAt(0);"));
|
assertEquals('x', exec("String s = \"x\"; return s.charAt(0);"));
|
||||||
|
|
Loading…
Reference in New Issue