blob: 4ade741a590a8a72844ce291593a5e5cbf8236cc [file] [log] [blame]
/*
* Copyright (c) 2015, 2018, 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.*;
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 && op.operator.opcode == string_add) {
return res
.appendList(collect(op.lhs, res))
.appendList(collect(op.rhs, res));
}
}
return res.append(tree);
}
/**
* If the type is not accessible from current context, try to figure out the
* sharpest accessible supertype.
*
* @param originalType type to sharpen
* @return sharped type
*/
Type sharpestAccessible(Type originalType) {
if (originalType.hasTag(ARRAY)) {
return types.makeArrayType(sharpestAccessible(types.elemtype(originalType)));
}
Type type = originalType;
while (!rs.isAccessible(gen.getAttrEnv(), type.asElement())) {
type = types.supertype(type);
}
return type;
}
/**
* "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.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.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);
l.duplicate();
l.load();
emit(tree.pos(), args, false, tree.type);
return l;
}
@Override
public Item makeConcat(JCTree.JCBinary tree) {
List<JCTree> args = collectAll(tree.lhs, tree.rhs);
emit(tree.pos(), args, true, tree.type);
return gen.getItems().makeStackItem(syms.stringType);
}
protected abstract void emit(JCDiagnostic.DiagnosticPosition pos, List<JCTree> args, boolean generateFirstArg, Type type);
/** 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(JCDiagnostic.DiagnosticPosition pos, List<JCTree> args, boolean generateFirstArg, Type type) {
List<List<JCTree>> split = split(args);
boolean first = true;
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(sharpestAccessible(arg.type));
}
if (!first || generateFirstArg) {
gen.genExpr(arg, arg.type).load();
}
first = false;
}
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.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(JCDiagnostic.DiagnosticPosition pos, List<JCTree> args, boolean generateFirstArg, Type type) {
List<List<JCTree>> split = split(args);
boolean first = true;
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(sharpestAccessible(arg.type));
if (!first || generateFirstArg) {
gen.genExpr(arg, arg.type).load();
}
first = false;
}
}
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.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);
}
}
}
}