From 5622e9c9d3d07e8916b9ad2e6c1440fa5fa1955b Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Tue, 17 May 2016 11:15:36 +0200 Subject: [PATCH] painless: use Java 9 Indy String Concats, if available --- .../elasticsearch/painless/MethodWriter.java | 72 ++++++++++++++----- .../painless/WriterConstants.java | 22 ++++++ .../elasticsearch/painless/node/EBinary.java | 4 +- .../elasticsearch/painless/node/EChain.java | 4 +- .../elasticsearch/painless/StringTests.java | 18 +++++ 5 files changed, 99 insertions(+), 21 deletions(-) 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 2daa5d5589c..a982b889194 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 @@ -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> 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, 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 86111d8f1a5..6bdb9856114 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 @@ -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, ""); 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 67645211b8f..07fe1ff3447 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 @@ -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) { 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 b98962a8f22..39afcd935ad 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 @@ -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(); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/StringTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/StringTests.java index 0c2b2f4386c..63fdcf315b2 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/StringTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/StringTests.java @@ -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);"));