8148483: JEP 280: Indify String Concatenation
authorshade
Thu, 28 Jan 2016 19:42:46 +0300
changeset 35424 96661d1df628
parent 35423 36c7b4ec7a8b
child 35425 dc2b3ff15f13
8148483: JEP 280: Indify String Concatenation Reviewed-by: psandoz, mcimadamore, igerasim, forax, plevart, vlivanov, ihse Contributed-by: Aleksey Shipilev <aleksey.shipilev@oracle.com>, Remi Forax <forax@univ-mlv.fr>, Peter Levart <peter.levart@gmail.com>
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/StringConcat.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java
langtools/test/tools/javac/T5024091/T5024091.java
langtools/test/tools/javac/TestIndyStringConcat.java
langtools/test/tools/javap/T6868539.java
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java	Thu Jan 28 14:06:27 2016 +0000
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java	Thu Jan 28 19:42:46 2016 +0300
@@ -26,9 +26,7 @@
 package com.sun.tools.javac.code;
 
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 
 import javax.lang.model.element.ElementVisitor;
 import javax.tools.JavaFileObject;
@@ -39,7 +37,6 @@
 import com.sun.tools.javac.code.Symbol.Completer;
 import com.sun.tools.javac.code.Symbol.CompletionFailure;
 import com.sun.tools.javac.code.Symbol.MethodSymbol;
-import com.sun.tools.javac.code.Symbol.OperatorSymbol;
 import com.sun.tools.javac.code.Symbol.PackageSymbol;
 import com.sun.tools.javac.code.Symbol.TypeSymbol;
 import com.sun.tools.javac.code.Symbol.VarSymbol;
@@ -50,7 +47,6 @@
 import com.sun.tools.javac.code.Type.JCVoidType;
 import com.sun.tools.javac.code.Type.MethodType;
 import com.sun.tools.javac.code.Type.UnknownType;
-import com.sun.tools.javac.jvm.ByteCodes;
 import com.sun.tools.javac.jvm.Target;
 import com.sun.tools.javac.util.Assert;
 import com.sun.tools.javac.util.Context;
@@ -65,7 +61,6 @@
 
 import static com.sun.tools.javac.code.Flags.*;
 import static com.sun.tools.javac.code.Kinds.Kind.*;
-import static com.sun.tools.javac.jvm.ByteCodes.*;
 import static com.sun.tools.javac.code.TypeTag.*;
 
 /** A class that defines all predefined constants and operators
@@ -193,6 +188,7 @@
     public final Type autoCloseableType;
     public final Type trustMeType;
     public final Type lambdaMetafactory;
+    public final Type stringConcatFactory;
     public final Type repeatableType;
     public final Type documentedType;
     public final Type elementTypeType;
@@ -472,6 +468,7 @@
         trustMeType = enterClass("java.lang.SafeVarargs");
         nativeHeaderType = enterClass("java.lang.annotation.Native");
         lambdaMetafactory = enterClass("java.lang.invoke.LambdaMetafactory");
+        stringConcatFactory = enterClass("java.lang.invoke.StringConcatFactory");
         functionalInterfaceType = enterClass("java.lang.FunctionalInterface");
 
         synthesizeEmptyInterfaceIfMissing(autoCloseableType);
@@ -479,6 +476,7 @@
         synthesizeEmptyInterfaceIfMissing(serializableType);
         synthesizeEmptyInterfaceIfMissing(lambdaMetafactory);
         synthesizeEmptyInterfaceIfMissing(serializedLambdaType);
+        synthesizeEmptyInterfaceIfMissing(stringConcatFactory);
         synthesizeBoxTypeIfMissing(doubleType);
         synthesizeBoxTypeIfMissing(floatType);
         synthesizeBoxTypeIfMissing(voidType);
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java	Thu Jan 28 14:06:27 2016 +0000
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java	Thu Jan 28 19:42:46 2016 +0300
@@ -25,8 +25,6 @@
 
 package com.sun.tools.javac.jvm;
 
-import java.util.*;
-
 import com.sun.tools.javac.util.*;
 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
 import com.sun.tools.javac.util.List;
@@ -45,7 +43,6 @@
 
 import static com.sun.tools.javac.code.Flags.*;
 import static com.sun.tools.javac.code.Kinds.Kind.*;
-import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
 import static com.sun.tools.javac.code.TypeTag.*;
 import static com.sun.tools.javac.jvm.ByteCodes.*;
 import static com.sun.tools.javac.jvm.CRTFlags.*;
@@ -69,12 +66,12 @@
     private final TreeMaker make;
     private final Names names;
     private final Target target;
-    private final Map<Type,Symbol> stringBufferAppend;
     private Name accessDollar;
     private final Types types;
     private final Lower lower;
     private final Flow flow;
     private final Annotate annotate;
+    private final StringConcat concat;
 
     /** Format of stackmap tables to be generated. */
     private final Code.StackMapFormat stackMap;
@@ -105,8 +102,9 @@
         make = TreeMaker.instance(context);
         target = Target.instance(context);
         types = Types.instance(context);
+        concat = StringConcat.instance(context);
+
         methodType = new MethodType(null, null, null, syms.methodClass);
-        stringBufferAppend = new HashMap<>();
         accessDollar = names.
             fromString("access" + target.syntheticNameChar());
         flow = Flow.instance(context);
@@ -753,6 +751,18 @@
         }
     }
 
+    public Code getCode() {
+        return code;
+    }
+
+    public Items getItems() {
+        return items;
+    }
+
+    public Env<AttrContext> getAttrEnv() {
+        return attrEnv;
+    }
+
     /** Visitor class for expressions which might be constant expressions.
      *  This class is a subset of TreeScanner. Intended to visit trees pruned by
      *  Lower as long as constant expressions looking for references to any
@@ -1895,25 +1905,7 @@
         OperatorSymbol operator = (OperatorSymbol) tree.operator;
         Item l;
         if (operator.opcode == string_add) {
-            // Generate code to make a string buffer
-            makeStringBuffer(tree.pos());
-
-            // Generate code for first string, possibly save one
-            // copy under buffer
-            l = genExpr(tree.lhs, tree.lhs.type);
-            if (l.width() > 0) {
-                code.emitop0(dup_x1 + 3 * (l.width() - 1));
-            }
-
-            // Load first string and append to buffer.
-            l.load();
-            appendString(tree.lhs);
-
-            // Append all other strings to buffer.
-            appendStrings(tree.rhs);
-
-            // Convert buffer to string.
-            bufferToString(tree.pos());
+            l = concat.makeConcat(tree);
         } else {
             // Generate code for first expression
             l = genExpr(tree.lhs, tree.lhs.type);
@@ -2026,13 +2018,7 @@
     public void visitBinary(JCBinary tree) {
         OperatorSymbol operator = (OperatorSymbol)tree.operator;
         if (operator.opcode == string_add) {
-            // Create a string buffer.
-            makeStringBuffer(tree.pos());
-            // Append all strings to buffer.
-            appendStrings(tree);
-            // Convert buffer to string.
-            bufferToString(tree.pos());
-            result = items.makeStackItem(syms.stringType);
+            result = concat.makeConcat(tree);
         } else if (tree.hasTag(AND)) {
             CondItem lcond = genCond(tree.lhs, CRT_FLOW_CONTROLLER);
             if (!lcond.isFalse()) {
@@ -2066,67 +2052,7 @@
             result = completeBinop(tree.lhs, tree.rhs, operator);
         }
     }
-//where
-        /** Make a new string buffer.
-         */
-        void makeStringBuffer(DiagnosticPosition pos) {
-            code.emitop2(new_, makeRef(pos, syms.stringBuilderType));
-            code.emitop0(dup);
-            callMethod(
-                    pos, syms.stringBuilderType, names.init, List.<Type>nil(), false);
-        }
 
-        /** Append value (on tos) to string buffer (on tos - 1).
-         */
-        void appendString(JCTree tree) {
-            Type t = tree.type.baseType();
-            if (!t.isPrimitive() && t.tsym != syms.stringType.tsym) {
-                t = syms.objectType;
-            }
-            items.makeMemberItem(getStringBufferAppend(tree, t), false).invoke();
-        }
-        Symbol getStringBufferAppend(JCTree tree, Type t) {
-            Assert.checkNull(t.constValue());
-            Symbol method = stringBufferAppend.get(t);
-            if (method == null) {
-                method = rs.resolveInternalMethod(tree.pos(),
-                                                  attrEnv,
-                                                  syms.stringBuilderType,
-                                                  names.append,
-                                                  List.of(t),
-                                                  null);
-                stringBufferAppend.put(t, method);
-            }
-            return method;
-        }
-
-        /** Add all strings in tree to string buffer.
-         */
-        void appendStrings(JCTree tree) {
-            tree = TreeInfo.skipParens(tree);
-            if (tree.hasTag(PLUS) && tree.type.constValue() == null) {
-                JCBinary op = (JCBinary) tree;
-                if (op.operator.kind == MTH &&
-                    ((OperatorSymbol) op.operator).opcode == string_add) {
-                    appendStrings(op.lhs);
-                    appendStrings(op.rhs);
-                    return;
-                }
-            }
-            genExpr(tree, tree.type).load();
-            appendString(tree);
-        }
-
-        /** Convert string buffer on tos to string.
-         */
-        void bufferToString(DiagnosticPosition pos) {
-            callMethod(
-                    pos,
-                    syms.stringBuilderType,
-                    names.toString,
-                    List.<Type>nil(),
-                    false);
-        }
 
         /** Complete generating code for operation, with left operand
          *  already on stack.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/StringConcat.java	Thu Jan 28 19:42:46 2016 +0300
@@ -0,0 +1,485 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.tools.javac.jvm;
+
+import com.sun.tools.javac.code.*;
+import com.sun.tools.javac.comp.Resolve;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.TreeInfo;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.util.*;
+
+import static com.sun.tools.javac.code.Kinds.Kind.MTH;
+import static com.sun.tools.javac.code.TypeTag.DOUBLE;
+import static com.sun.tools.javac.code.TypeTag.LONG;
+import static com.sun.tools.javac.jvm.ByteCodes.*;
+import static com.sun.tools.javac.tree.JCTree.Tag.PLUS;
+import com.sun.tools.javac.jvm.Items.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** This lowers the String concatenation to something that JVM can understand.
+ *
+ *  <p><b>This is NOT part of any supported API.
+ *  If you write code that depends on this, you do so at your own risk.
+ *  This code and its internal interfaces are subject to change or
+ *  deletion without notice.</b>
+ */
+public abstract class StringConcat {
+
+    /**
+     * Maximum number of slots for String Concat call.
+     * JDK's StringConcatFactory does not support more than that.
+     */
+    private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200;
+    private static final char TAG_ARG   = '\u0001';
+    private static final char TAG_CONST = '\u0002';
+
+    protected final Gen gen;
+    protected final Symtab syms;
+    protected final Names names;
+    protected final TreeMaker make;
+    protected final Types types;
+    protected final Map<Type, Symbol> sbAppends;
+    protected final Resolve rs;
+
+    protected static final Context.Key<StringConcat> concatKey = new Context.Key<>();
+
+    public static StringConcat instance(Context context) {
+        StringConcat instance = context.get(concatKey);
+        if (instance == null) {
+            instance = makeConcat(context);
+        }
+        return instance;
+    }
+
+    private static StringConcat makeConcat(Context context) {
+        Target target = Target.instance(context);
+        String opt = Options.instance(context).get("stringConcat");
+        if (target.hasStringConcatFactory()) {
+            if (opt == null) {
+                opt = "indyWithConstants";
+            }
+        } else {
+            if (opt != null && !"inline".equals(opt)) {
+                Assert.error("StringConcatFactory-based string concat is requested on a platform that does not support it.");
+            }
+            opt = "inline";
+        }
+
+        switch (opt) {
+            case "inline":
+                return new Inline(context);
+            case "indy":
+                return new IndyPlain(context);
+            case "indyWithConstants":
+                return new IndyConstants(context);
+            default:
+                Assert.error("Unknown stringConcat: " + opt);
+                throw new IllegalStateException("Unknown stringConcat: " + opt);
+        }
+    }
+
+    protected StringConcat(Context context) {
+        context.put(concatKey, this);
+        gen = Gen.instance(context);
+        syms = Symtab.instance(context);
+        types = Types.instance(context);
+        names = Names.instance(context);
+        make = TreeMaker.instance(context);
+        rs = Resolve.instance(context);
+        sbAppends = new HashMap<>();
+    }
+
+    public abstract Item makeConcat(JCTree.JCAssignOp tree);
+    public abstract Item makeConcat(JCTree.JCBinary tree);
+
+    protected List<JCTree> collectAll(JCTree tree) {
+        return collect(tree, List.nil());
+    }
+
+    protected List<JCTree> collectAll(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
+        return List.<JCTree>nil()
+                .appendList(collectAll(lhs))
+                .appendList(collectAll(rhs));
+    }
+
+    private List<JCTree> collect(JCTree tree, List<JCTree> res) {
+        tree = TreeInfo.skipParens(tree);
+        if (tree.hasTag(PLUS) && tree.type.constValue() == null) {
+            JCTree.JCBinary op = (JCTree.JCBinary) tree;
+            if (op.operator.kind == MTH &&
+                    ((Symbol.OperatorSymbol) op.operator).opcode == string_add) {
+                return res
+                        .appendList(collect(op.lhs, res))
+                        .appendList(collect(op.rhs, res));
+            }
+        }
+        return res.append(tree);
+    }
+
+    /**
+     * "Legacy" bytecode flavor: emit the StringBuilder.append chains for string
+     * concatenation.
+     */
+    private static class Inline extends StringConcat {
+        public Inline(Context context) {
+            super(context);
+        }
+
+        @Override
+        public Item makeConcat(JCTree.JCAssignOp tree) {
+            // Generate code to make a string builder
+            JCDiagnostic.DiagnosticPosition pos = tree.pos();
+
+            // Create a string builder.
+            newStringBuilder(tree);
+
+            // Generate code for first string, possibly save one
+            // copy under builder
+            Item l = gen.genExpr(tree.lhs, tree.lhs.type);
+            if (l.width() > 0) {
+                gen.getCode().emitop0(dup_x1 + 3 * (l.width() - 1));
+            }
+
+            // Load first string and append to builder.
+            l.load();
+            appendString(tree.lhs);
+
+            // Append all other strings to builder.
+            List<JCTree> args = collectAll(tree.rhs);
+            for (JCTree t : args) {
+                gen.genExpr(t, t.type).load();
+                appendString(t);
+            }
+
+            // Convert builder to string.
+            builderToString(pos);
+
+            return l;
+        }
+
+        @Override
+        public Item makeConcat(JCTree.JCBinary tree) {
+            JCDiagnostic.DiagnosticPosition pos = tree.pos();
+
+            // Create a string builder.
+            newStringBuilder(tree);
+
+            // Append all strings to builder.
+            List<JCTree> args = collectAll(tree);
+            for (JCTree t : args) {
+                gen.genExpr(t, t.type).load();
+                appendString(t);
+            }
+
+            // Convert builder to string.
+            builderToString(pos);
+
+            return gen.getItems().makeStackItem(syms.stringType);
+        }
+
+        private JCDiagnostic.DiagnosticPosition newStringBuilder(JCTree tree) {
+            JCDiagnostic.DiagnosticPosition pos = tree.pos();
+            gen.getCode().emitop2(new_, gen.makeRef(pos, syms.stringBuilderType));
+            gen.getCode().emitop0(dup);
+            gen.callMethod(pos, syms.stringBuilderType, names.init, List.<Type>nil(), false);
+            return pos;
+        }
+
+        private void appendString(JCTree tree) {
+            Type t = tree.type.baseType();
+            if (!t.isPrimitive() && t.tsym != syms.stringType.tsym) {
+                t = syms.objectType;
+            }
+
+            Assert.checkNull(t.constValue());
+            Symbol method = sbAppends.get(t);
+            if (method == null) {
+                method = rs.resolveInternalMethod(tree.pos(), gen.getAttrEnv(), syms.stringBuilderType, names.append, List.of(t), null);
+                sbAppends.put(t, method);
+            }
+
+            gen.getItems().makeMemberItem(method, false).invoke();
+        }
+
+        private void builderToString(JCDiagnostic.DiagnosticPosition pos) {
+            gen.callMethod(pos, syms.stringBuilderType, names.toString, List.<Type>nil(), false);
+        }
+    }
+
+    /**
+     * Base class for indified concatenation bytecode flavors.
+     */
+    private static abstract class Indy extends StringConcat {
+        public Indy(Context context) {
+            super(context);
+        }
+
+        @Override
+        public Item makeConcat(JCTree.JCAssignOp tree) {
+            List<JCTree> args = collectAll(tree.lhs, tree.rhs);
+            Item l = gen.genExpr(tree.lhs, tree.lhs.type);
+            emit(args, tree.type, tree.pos());
+            return l;
+        }
+
+        @Override
+        public Item makeConcat(JCTree.JCBinary tree) {
+            List<JCTree> args = collectAll(tree.lhs, tree.rhs);
+            emit(args, tree.type, tree.pos());
+            return gen.getItems().makeStackItem(syms.stringType);
+        }
+
+        protected abstract void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos);
+
+        /** Peel the argument list into smaller chunks. */
+        protected List<List<JCTree>> split(List<JCTree> args) {
+            ListBuffer<List<JCTree>> splits = new ListBuffer<>();
+
+            int slots = 0;
+
+            // Need to peel, so that neither call has more than acceptable number
+            // of slots for the arguments.
+            ListBuffer<JCTree> cArgs = new ListBuffer<>();
+            for (JCTree t : args) {
+                int needSlots = (t.type.getTag() == LONG || t.type.getTag() == DOUBLE) ? 2 : 1;
+                if (slots + needSlots >= MAX_INDY_CONCAT_ARG_SLOTS) {
+                    splits.add(cArgs.toList());
+                    cArgs.clear();
+                    slots = 0;
+                }
+                cArgs.add(t);
+                slots += needSlots;
+            }
+
+            // Flush the tail slice
+            if (!cArgs.isEmpty()) {
+                splits.add(cArgs.toList());
+            }
+
+            return splits.toList();
+        }
+    }
+
+    /**
+     * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory,
+     * without handling constants specially.
+     *
+     * We bypass empty strings, because they have no meaning at this level. This
+     * captures the Java language trick to force String concat with e.g. ("" + int)-like
+     * expression. Down here, we already know we are in String concat business, and do
+     * not require these markers.
+     */
+    private static class IndyPlain extends Indy {
+        public IndyPlain(Context context) {
+            super(context);
+        }
+
+        /** Emit the indy concat for all these arguments, possibly peeling along the way */
+        protected void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos) {
+            List<List<JCTree>> split = split(args);
+
+            for (List<JCTree> t : split) {
+                Assert.check(!t.isEmpty(), "Arguments list is empty");
+
+                ListBuffer<Type> dynamicArgs = new ListBuffer<>();
+                for (JCTree arg : t) {
+                    Object constVal = arg.type.constValue();
+                    if ("".equals(constVal)) continue;
+                    if (arg.type == syms.botType) {
+                        dynamicArgs.add(types.boxedClass(syms.voidType).type);
+                    } else {
+                        dynamicArgs.add(arg.type);
+                    }
+                    gen.genExpr(arg, arg.type).load();
+                }
+
+                doCall(type, pos, dynamicArgs.toList());
+            }
+
+            // More that one peel slice produced: concatenate the results
+            if (split.size() > 1) {
+                ListBuffer<Type> argTypes = new ListBuffer<>();
+                for (int c = 0; c < split.size(); c++) {
+                    argTypes.append(syms.stringType);
+                }
+                doCall(type, pos, argTypes.toList());
+            }
+        }
+
+        /** Produce the actual invokedynamic call to StringConcatFactory */
+        private void doCall(Type type, JCDiagnostic.DiagnosticPosition pos, List<Type> dynamicArgTypes) {
+            Type.MethodType indyType = new Type.MethodType(dynamicArgTypes,
+                    type,
+                    List.<Type>nil(),
+                    syms.methodClass);
+
+            int prevPos = make.pos;
+            try {
+                make.at(pos);
+
+                List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType,
+                        syms.stringType,
+                        syms.methodTypeType);
+
+                Symbol bsm = rs.resolveInternalMethod(pos,
+                        gen.getAttrEnv(),
+                        syms.stringConcatFactory,
+                        names.makeConcat,
+                        bsm_staticArgs,
+                        null);
+
+                Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(names.makeConcat,
+                        syms.noSymbol,
+                        ClassFile.REF_invokeStatic,
+                        (Symbol.MethodSymbol)bsm,
+                        indyType,
+                        List.nil().toArray());
+
+                Items.Item item = gen.getItems().makeDynamicItem(dynSym);
+                item.invoke();
+            } finally {
+                make.at(prevPos);
+            }
+        }
+    }
+
+    /**
+     * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory.
+     * This code concatenates all known constants into the recipe, possibly escaping
+     * some constants separately.
+     *
+     * We also bypass empty strings, because they have no meaning at this level. This
+     * captures the Java language trick to force String concat with e.g. ("" + int)-like
+     * expression. Down here, we already know we are in String concat business, and do
+     * not require these markers.
+     */
+    private static final class IndyConstants extends Indy {
+        public IndyConstants(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos) {
+            List<List<JCTree>> split = split(args);
+
+            for (List<JCTree> t : split) {
+                Assert.check(!t.isEmpty(), "Arguments list is empty");
+
+                StringBuilder recipe = new StringBuilder(t.size());
+                ListBuffer<Type> dynamicArgs = new ListBuffer<>();
+                ListBuffer<Object> staticArgs = new ListBuffer<>();
+
+                for (JCTree arg : t) {
+                    Object constVal = arg.type.constValue();
+                    if ("".equals(constVal)) continue;
+                    if (arg.type == syms.botType) {
+                        // Concat the null into the recipe right away
+                        recipe.append((String) null);
+                    } else if (constVal != null) {
+                        // Concat the String representation of the constant, except
+                        // for the case it contains special tags, which requires us
+                        // to expose it as detached constant.
+                        String a = arg.type.stringValue();
+                        if (a.indexOf(TAG_CONST) != -1 || a.indexOf(TAG_ARG) != -1) {
+                            recipe.append(TAG_CONST);
+                            staticArgs.add(a);
+                        } else {
+                            recipe.append(a);
+                        }
+                    } else {
+                        // Ordinary arguments come through the dynamic arguments.
+                        recipe.append(TAG_ARG);
+                        dynamicArgs.add(arg.type);
+                        gen.genExpr(arg, arg.type).load();
+                    }
+                }
+
+                doCall(type, pos, recipe.toString(), staticArgs.toList(), dynamicArgs.toList());
+            }
+
+            // More that one peel slice produced: concatenate the results
+            // All arguments are assumed to be non-constant Strings.
+            if (split.size() > 1) {
+                ListBuffer<Type> argTypes = new ListBuffer<>();
+                StringBuilder recipe = new StringBuilder();
+                for (int c = 0; c < split.size(); c++) {
+                    argTypes.append(syms.stringType);
+                    recipe.append(TAG_ARG);
+                }
+                doCall(type, pos, recipe.toString(), List.nil(), argTypes.toList());
+            }
+        }
+
+        /** Produce the actual invokedynamic call to StringConcatFactory */
+        private void doCall(Type type, JCDiagnostic.DiagnosticPosition pos, String recipe, List<Object> staticArgs, List<Type> dynamicArgTypes) {
+            Type.MethodType indyType = new Type.MethodType(dynamicArgTypes,
+                    type,
+                    List.<Type>nil(),
+                    syms.methodClass);
+
+            int prevPos = make.pos;
+            try {
+                make.at(pos);
+
+                ListBuffer<Type> constTypes = new ListBuffer<>();
+                ListBuffer<Object> constants = new ListBuffer<>();
+                for (Object t : staticArgs) {
+                    constants.add(t);
+                    constTypes.add(syms.stringType);
+                }
+
+                List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType,
+                        syms.stringType,
+                        syms.methodTypeType)
+                        .append(syms.stringType)
+                        .appendList(constTypes);
+
+                Symbol bsm = rs.resolveInternalMethod(pos,
+                        gen.getAttrEnv(),
+                        syms.stringConcatFactory,
+                        names.makeConcatWithConstants,
+                        bsm_staticArgs,
+                        null);
+
+                Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(names.makeConcatWithConstants,
+                        syms.noSymbol,
+                        ClassFile.REF_invokeStatic,
+                        (Symbol.MethodSymbol)bsm,
+                        indyType,
+                        List.<Object>of(recipe).appendList(constants).toArray());
+
+                Items.Item item = gen.getItems().makeDynamicItem(dynSym);
+                item.invoke();
+            } finally {
+                make.at(prevPos);
+            }
+        }
+    }
+
+}
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java	Thu Jan 28 14:06:27 2016 +0000
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java	Thu Jan 28 19:42:46 2016 +0300
@@ -135,4 +135,10 @@
         return hasInvokedynamic();
     }
 
+    /** Does the target JDK contain StringConcatFactory class?
+     */
+    public boolean hasStringConcatFactory() {
+        return compareTo(JDK1_9) >= 0;
+    }
+
 }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java	Thu Jan 28 14:06:27 2016 +0000
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java	Thu Jan 28 19:42:46 2016 +0300
@@ -179,6 +179,10 @@
     public final Name altMetafactory;
     public final Name dollarThis;
 
+    // string concat
+    public final Name makeConcat;
+    public final Name makeConcatWithConstants;
+
     public final Name.Table table;
 
     public Names(Context context) {
@@ -316,6 +320,10 @@
         lambda = fromString("lambda$");
         metafactory = fromString("metafactory");
         altMetafactory = fromString("altMetafactory");
+
+        // string concat
+        makeConcat = fromString("makeConcat");
+        makeConcatWithConstants = fromString("makeConcatWithConstants");
     }
 
     protected Name.Table createTable(Options options) {
--- a/langtools/test/tools/javac/T5024091/T5024091.java	Thu Jan 28 14:06:27 2016 +0000
+++ b/langtools/test/tools/javac/T5024091/T5024091.java	Thu Jan 28 19:42:46 2016 +0300
@@ -3,7 +3,7 @@
  * @bug     5024091
  * @summary AssertionError shouldn't be thrown
  * @author  Wei Tao
- * @compile/fail/ref=T5024091.out -XDfailcomplete=java.lang.StringBuilder -XDdev -XDrawDiagnostics T5024091.java
+ * @compile/fail/ref=T5024091.out -XDfailcomplete=java.lang.StringBuilder -XDdev -XDrawDiagnostics -XDstringConcat=inline T5024091.java
  */
 
 public class T5024091 {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/TestIndyStringConcat.java	Thu Jan 28 19:42:46 2016 +0300
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Test that StringConcat is working for JDK >= 9
+ * @compile -source 6 -target 6 TestIndyStringConcat.java
+ * @run main TestIndyStringConcat false
+ * @clean TestIndyStringConcat*
+ * @compile -source 7 -target 7 TestIndyStringConcat.java
+ * @run main TestIndyStringConcat false
+ * @clean TestIndyStringConcat*
+ * @compile -source 8 -target 8 TestIndyStringConcat.java
+ * @run main TestIndyStringConcat false
+ * @clean TestIndyStringConcat*
+ * @compile -XDstringConcat=inline -source 9 -target 9 TestIndyStringConcat.java
+ * @run main TestIndyStringConcat false
+ * @clean TestIndyStringConcat*
+ * @compile -XDstringConcat=indy -source 9 -target 9 TestIndyStringConcat.java
+ * @run main TestIndyStringConcat true
+ * @clean TestIndyStringConcat*
+ * @compile -XDstringConcat=indyWithConstants -source 9 -target 9 TestIndyStringConcat.java
+ * @run main TestIndyStringConcat true
+ */
+public class TestIndyStringConcat {
+
+    private static class MyObject {
+        public String toString() {
+            throw new RuntimeException("Boyyaa");
+        }
+    }
+
+    class Inner { }
+
+    public static void main(String[] args) {
+        boolean useIndyConcat = Boolean.valueOf(args[0]);
+        try {
+            String s = "Foo" + new MyObject();
+        } catch (RuntimeException ex) {
+            boolean indifiedStringConcat = false;
+            ex.printStackTrace();
+            for (StackTraceElement e : ex.getStackTrace()) {
+                if (e.getClassName().startsWith("java.lang.String$Concat") &&
+                        e.getMethodName().equals("concat")) {
+                    indifiedStringConcat = true;
+                    break;
+                }
+            }
+            if (indifiedStringConcat != useIndyConcat) {
+                throw new AssertionError();
+            }
+        }
+    }
+}
--- a/langtools/test/tools/javap/T6868539.java	Thu Jan 28 14:06:27 2016 +0000
+++ b/langtools/test/tools/javap/T6868539.java	Thu Jan 28 19:42:46 2016 +0300
@@ -58,10 +58,12 @@
             throw new Error(errors + " found.");
     }
 
+    String notFound = " not found";
+
     void verify(String output, String... expects) {
         for (String expect: expects) {
             if (!output.matches("(?s).*" + expect + ".*"))
-                error(expect + " not found");
+                error(expect + notFound);
         }
     }