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>
--- 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);
}
}