mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-02 14:38:28 +00:00
8148483: JEP 280: Indify String Concatenation
Co-authored-by: Remi Forax <forax@univ-mlv.fr> Co-authored-by: Peter Levart <peter.levart@gmail.com> Reviewed-by: psandoz, mcimadamore, igerasim, forax, plevart, vlivanov, ihse
This commit is contained in:
parent
8aa9bf5cc6
commit
fca18168e4
@ -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.ClassSymbol;
|
||||
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.JCPrimitiveType;
|
||||
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 com.sun.tools.javac.util.Names;
|
||||
|
||||
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 class Symtab {
|
||||
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 @@ public class Symtab {
|
||||
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 @@ public class Symtab {
|
||||
synthesizeEmptyInterfaceIfMissing(serializableType);
|
||||
synthesizeEmptyInterfaceIfMissing(lambdaMetafactory);
|
||||
synthesizeEmptyInterfaceIfMissing(serializedLambdaType);
|
||||
synthesizeEmptyInterfaceIfMissing(stringConcatFactory);
|
||||
synthesizeBoxTypeIfMissing(doubleType);
|
||||
synthesizeBoxTypeIfMissing(floatType);
|
||||
synthesizeBoxTypeIfMissing(voidType);
|
||||
|
||||
@ -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 com.sun.tools.javac.tree.JCTree.*;
|
||||
|
||||
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 @@ public class Gen extends JCTree.Visitor {
|
||||
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 @@ public class Gen extends JCTree.Visitor {
|
||||
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 class Gen extends JCTree.Visitor {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public class Gen extends JCTree.Visitor {
|
||||
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 class Gen extends JCTree.Visitor {
|
||||
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 @@ public class Gen extends JCTree.Visitor {
|
||||
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.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -135,4 +135,10 @@ public enum Target {
|
||||
return hasInvokedynamic();
|
||||
}
|
||||
|
||||
/** Does the target JDK contain StringConcatFactory class?
|
||||
*/
|
||||
public boolean hasStringConcatFactory() {
|
||||
return compareTo(JDK1_9) >= 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -179,6 +179,10 @@ public class Names {
|
||||
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 @@ public class Names {
|
||||
lambda = fromString("lambda$");
|
||||
metafactory = fromString("metafactory");
|
||||
altMetafactory = fromString("altMetafactory");
|
||||
|
||||
// string concat
|
||||
makeConcat = fromString("makeConcat");
|
||||
makeConcatWithConstants = fromString("makeConcatWithConstants");
|
||||
}
|
||||
|
||||
protected Name.Table createTable(Options options) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
74
langtools/test/tools/javac/TestIndyStringConcat.java
Normal file
74
langtools/test/tools/javac/TestIndyStringConcat.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,10 +58,12 @@ public class T6868539
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user