1 /* 2 * Copyright (c) 2015, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.javac.jvm; 27 28 import com.sun.tools.javac.code.*; 29 import com.sun.tools.javac.comp.Resolve; 30 import com.sun.tools.javac.tree.JCTree; 31 import com.sun.tools.javac.tree.TreeInfo; 32 import com.sun.tools.javac.tree.TreeMaker; 33 import com.sun.tools.javac.util.*; 34 35 import static com.sun.tools.javac.code.Kinds.Kind.MTH; 36 import static com.sun.tools.javac.code.TypeTag.DOUBLE; 37 import static com.sun.tools.javac.code.TypeTag.LONG; 38 import static com.sun.tools.javac.jvm.ByteCodes.*; 39 import static com.sun.tools.javac.tree.JCTree.Tag.PLUS; 40 import com.sun.tools.javac.jvm.Items.*; 41 42 import java.util.HashMap; 43 import java.util.Map; 44 45 /** This lowers the String concatenation to something that JVM can understand. 46 * 47 * <p><b>This is NOT part of any supported API. 48 * If you write code that depends on this, you do so at your own risk. 49 * This code and its internal interfaces are subject to change or 50 * deletion without notice.</b> 51 */ 52 public abstract class StringConcat { 53 54 /** 55 * Maximum number of slots for String Concat call. 56 * JDK's StringConcatFactory does not support more than that. 57 */ 58 private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200; 59 private static final char TAG_ARG = '\u0001'; 60 private static final char TAG_CONST = '\u0002'; 61 62 protected final Gen gen; 63 protected final Symtab syms; 64 protected final Names names; 65 protected final TreeMaker make; 66 protected final Types types; 67 protected final Map<Type, Symbol> sbAppends; 68 protected final Resolve rs; 69 70 protected static final Context.Key<StringConcat> concatKey = new Context.Key<>(); 71 72 public static StringConcat instance(Context context) { 73 StringConcat instance = context.get(concatKey); 74 if (instance == null) { 75 instance = makeConcat(context); 76 } 77 return instance; 78 } 79 80 private static StringConcat makeConcat(Context context) { 81 Target target = Target.instance(context); 82 String opt = Options.instance(context).get("stringConcat"); 83 if (target.hasStringConcatFactory()) { 84 if (opt == null) { 85 opt = "indyWithConstants"; 86 } 87 } else { 88 if (opt != null && !"inline".equals(opt)) { 89 Assert.error("StringConcatFactory-based string concat is requested on a platform that does not support it."); 90 } 91 opt = "inline"; 92 } 93 94 switch (opt) { 95 case "inline": 96 return new Inline(context); 97 case "indy": 98 return new IndyPlain(context); 99 case "indyWithConstants": 100 return new IndyConstants(context); 101 default: 102 Assert.error("Unknown stringConcat: " + opt); 103 throw new IllegalStateException("Unknown stringConcat: " + opt); 104 } 105 } 106 107 protected StringConcat(Context context) { 108 context.put(concatKey, this); 109 gen = Gen.instance(context); 110 syms = Symtab.instance(context); 111 types = Types.instance(context); 112 names = Names.instance(context); 113 make = TreeMaker.instance(context); 114 rs = Resolve.instance(context); 115 sbAppends = new HashMap<>(); 116 } 117 118 public abstract Item makeConcat(JCTree.JCAssignOp tree); 119 public abstract Item makeConcat(JCTree.JCBinary tree); 120 121 protected List<JCTree> collectAll(JCTree tree) { 122 return collect(tree, List.nil()); 123 } 124 125 protected List<JCTree> collectAll(JCTree.JCExpression lhs, JCTree.JCExpression rhs) { 126 return List.<JCTree>nil() 127 .appendList(collectAll(lhs)) 128 .appendList(collectAll(rhs)); 129 } 130 131 private List<JCTree> collect(JCTree tree, List<JCTree> res) { 132 tree = TreeInfo.skipParens(tree); 133 if (tree.hasTag(PLUS) && tree.type.constValue() == null) { 134 JCTree.JCBinary op = (JCTree.JCBinary) tree; 135 if (op.operator.kind == MTH && 136 ((Symbol.OperatorSymbol) op.operator).opcode == string_add) { 137 return res 138 .appendList(collect(op.lhs, res)) 139 .appendList(collect(op.rhs, res)); 140 } 141 } 142 return res.append(tree); 143 } 144 145 /** 146 * "Legacy" bytecode flavor: emit the StringBuilder.append chains for string 147 * concatenation. 148 */ 149 private static class Inline extends StringConcat { 150 public Inline(Context context) { 151 super(context); 152 } 153 154 @Override 155 public Item makeConcat(JCTree.JCAssignOp tree) { 156 // Generate code to make a string builder 157 JCDiagnostic.DiagnosticPosition pos = tree.pos(); 158 159 // Create a string builder. 160 newStringBuilder(tree); 161 162 // Generate code for first string, possibly save one 163 // copy under builder 164 Item l = gen.genExpr(tree.lhs, tree.lhs.type); 165 if (l.width() > 0) { 166 gen.getCode().emitop0(dup_x1 + 3 * (l.width() - 1)); 167 } 168 169 // Load first string and append to builder. 170 l.load(); 171 appendString(tree.lhs); 172 173 // Append all other strings to builder. 174 List<JCTree> args = collectAll(tree.rhs); 175 for (JCTree t : args) { 176 gen.genExpr(t, t.type).load(); 177 appendString(t); 178 } 179 180 // Convert builder to string. 181 builderToString(pos); 182 183 return l; 184 } 185 186 @Override 187 public Item makeConcat(JCTree.JCBinary tree) { 188 JCDiagnostic.DiagnosticPosition pos = tree.pos(); 189 190 // Create a string builder. 191 newStringBuilder(tree); 192 193 // Append all strings to builder. 194 List<JCTree> args = collectAll(tree); 195 for (JCTree t : args) { 196 gen.genExpr(t, t.type).load(); 197 appendString(t); 198 } 199 200 // Convert builder to string. 201 builderToString(pos); 202 203 return gen.getItems().makeStackItem(syms.stringType); 204 } 205 206 private JCDiagnostic.DiagnosticPosition newStringBuilder(JCTree tree) { 207 JCDiagnostic.DiagnosticPosition pos = tree.pos(); 208 gen.getCode().emitop2(new_, gen.makeRef(pos, syms.stringBuilderType)); 209 gen.getCode().emitop0(dup); 210 gen.callMethod(pos, syms.stringBuilderType, names.init, List.<Type>nil(), false); 211 return pos; 212 } 213 214 private void appendString(JCTree tree) { 215 Type t = tree.type.baseType(); 216 if (!t.isPrimitive() && t.tsym != syms.stringType.tsym) { 217 t = syms.objectType; 218 } 219 220 Assert.checkNull(t.constValue()); 221 Symbol method = sbAppends.get(t); 222 if (method == null) { 223 method = rs.resolveInternalMethod(tree.pos(), gen.getAttrEnv(), syms.stringBuilderType, names.append, List.of(t), null); 224 sbAppends.put(t, method); 225 } 226 227 gen.getItems().makeMemberItem(method, false).invoke(); 228 } 229 230 private void builderToString(JCDiagnostic.DiagnosticPosition pos) { 231 gen.callMethod(pos, syms.stringBuilderType, names.toString, List.<Type>nil(), false); 232 } 233 } 234 235 /** 236 * Base class for indified concatenation bytecode flavors. 237 */ 238 private static abstract class Indy extends StringConcat { 239 public Indy(Context context) { 240 super(context); 241 } 242 243 @Override 244 public Item makeConcat(JCTree.JCAssignOp tree) { 245 List<JCTree> args = collectAll(tree.lhs, tree.rhs); 246 Item l = gen.genExpr(tree.lhs, tree.lhs.type); 247 emit(args, tree.type, tree.pos()); 248 return l; 249 } 250 251 @Override 252 public Item makeConcat(JCTree.JCBinary tree) { 253 List<JCTree> args = collectAll(tree.lhs, tree.rhs); 254 emit(args, tree.type, tree.pos()); 255 return gen.getItems().makeStackItem(syms.stringType); 256 } 257 258 protected abstract void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos); 259 260 /** Peel the argument list into smaller chunks. */ 261 protected List<List<JCTree>> split(List<JCTree> args) { 262 ListBuffer<List<JCTree>> splits = new ListBuffer<>(); 263 264 int slots = 0; 265 266 // Need to peel, so that neither call has more than acceptable number 267 // of slots for the arguments. 268 ListBuffer<JCTree> cArgs = new ListBuffer<>(); 269 for (JCTree t : args) { 270 int needSlots = (t.type.getTag() == LONG || t.type.getTag() == DOUBLE) ? 2 : 1; 271 if (slots + needSlots >= MAX_INDY_CONCAT_ARG_SLOTS) { 272 splits.add(cArgs.toList()); 273 cArgs.clear(); 274 slots = 0; 275 } 276 cArgs.add(t); 277 slots += needSlots; 278 } 279 280 // Flush the tail slice 281 if (!cArgs.isEmpty()) { 282 splits.add(cArgs.toList()); 283 } 284 285 return splits.toList(); 286 } 287 } 288 289 /** 290 * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory, 291 * without handling constants specially. 292 * 293 * We bypass empty strings, because they have no meaning at this level. This 294 * captures the Java language trick to force String concat with e.g. ("" + int)-like 295 * expression. Down here, we already know we are in String concat business, and do 296 * not require these markers. 297 */ 298 private static class IndyPlain extends Indy { 299 public IndyPlain(Context context) { 300 super(context); 301 } 302 303 /** Emit the indy concat for all these arguments, possibly peeling along the way */ 304 protected void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos) { 305 List<List<JCTree>> split = split(args); 306 307 for (List<JCTree> t : split) { 308 Assert.check(!t.isEmpty(), "Arguments list is empty"); 309 310 ListBuffer<Type> dynamicArgs = new ListBuffer<>(); 311 for (JCTree arg : t) { 312 Object constVal = arg.type.constValue(); 313 if ("".equals(constVal)) continue; 314 if (arg.type == syms.botType) { 315 dynamicArgs.add(types.boxedClass(syms.voidType).type); 316 } else { 317 dynamicArgs.add(arg.type); 318 } 319 gen.genExpr(arg, arg.type).load(); 320 } 321 322 doCall(type, pos, dynamicArgs.toList()); 323 } 324 325 // More that one peel slice produced: concatenate the results 326 if (split.size() > 1) { 327 ListBuffer<Type> argTypes = new ListBuffer<>(); 328 for (int c = 0; c < split.size(); c++) { 329 argTypes.append(syms.stringType); 330 } 331 doCall(type, pos, argTypes.toList()); 332 } 333 } 334 335 /** Produce the actual invokedynamic call to StringConcatFactory */ 336 private void doCall(Type type, JCDiagnostic.DiagnosticPosition pos, List<Type> dynamicArgTypes) { 337 Type.MethodType indyType = new Type.MethodType(dynamicArgTypes, 338 type, 339 List.<Type>nil(), 340 syms.methodClass); 341 342 int prevPos = make.pos; 343 try { 344 make.at(pos); 345 346 List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType, 347 syms.stringType, 348 syms.methodTypeType); 349 350 Symbol bsm = rs.resolveInternalMethod(pos, 351 gen.getAttrEnv(), 352 syms.stringConcatFactory, 353 names.makeConcat, 354 bsm_staticArgs, 355 null); 356 357 Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(names.makeConcat, 358 syms.noSymbol, 359 ClassFile.REF_invokeStatic, 360 (Symbol.MethodSymbol)bsm, 361 indyType, 362 List.nil().toArray()); 363 364 Items.Item item = gen.getItems().makeDynamicItem(dynSym); 365 item.invoke(); 366 } finally { 367 make.at(prevPos); 368 } 369 } 370 } 371 372 /** 373 * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory. 374 * This code concatenates all known constants into the recipe, possibly escaping 375 * some constants separately. 376 * 377 * We also bypass empty strings, because they have no meaning at this level. This 378 * captures the Java language trick to force String concat with e.g. ("" + int)-like 379 * expression. Down here, we already know we are in String concat business, and do 380 * not require these markers. 381 */ 382 private static final class IndyConstants extends Indy { 383 public IndyConstants(Context context) { 384 super(context); 385 } 386 387 @Override 388 protected void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos) { 389 List<List<JCTree>> split = split(args); 390 391 for (List<JCTree> t : split) { 392 Assert.check(!t.isEmpty(), "Arguments list is empty"); 393 394 StringBuilder recipe = new StringBuilder(t.size()); 395 ListBuffer<Type> dynamicArgs = new ListBuffer<>(); 396 ListBuffer<Object> staticArgs = new ListBuffer<>(); 397 398 for (JCTree arg : t) { 399 Object constVal = arg.type.constValue(); 400 if ("".equals(constVal)) continue; 401 if (arg.type == syms.botType) { 402 // Concat the null into the recipe right away 403 recipe.append((String) null); 404 } else if (constVal != null) { 405 // Concat the String representation of the constant, except 406 // for the case it contains special tags, which requires us 407 // to expose it as detached constant. 408 String a = arg.type.stringValue(); 409 if (a.indexOf(TAG_CONST) != -1 || a.indexOf(TAG_ARG) != -1) { 410 recipe.append(TAG_CONST); 411 staticArgs.add(a); 412 } else { 413 recipe.append(a); 414 } 415 } else { 416 // Ordinary arguments come through the dynamic arguments. 417 recipe.append(TAG_ARG); 418 dynamicArgs.add(arg.type); 419 gen.genExpr(arg, arg.type).load(); 420 } 421 } 422 423 doCall(type, pos, recipe.toString(), staticArgs.toList(), dynamicArgs.toList()); 424 } 425 426 // More that one peel slice produced: concatenate the results 427 // All arguments are assumed to be non-constant Strings. 428 if (split.size() > 1) { 429 ListBuffer<Type> argTypes = new ListBuffer<>(); 430 StringBuilder recipe = new StringBuilder(); 431 for (int c = 0; c < split.size(); c++) { 432 argTypes.append(syms.stringType); 433 recipe.append(TAG_ARG); 434 } 435 doCall(type, pos, recipe.toString(), List.nil(), argTypes.toList()); 436 } 437 } 438 439 /** Produce the actual invokedynamic call to StringConcatFactory */ 440 private void doCall(Type type, JCDiagnostic.DiagnosticPosition pos, String recipe, List<Object> staticArgs, List<Type> dynamicArgTypes) { 441 Type.MethodType indyType = new Type.MethodType(dynamicArgTypes, 442 type, 443 List.<Type>nil(), 444 syms.methodClass); 445 446 int prevPos = make.pos; 447 try { 448 make.at(pos); 449 450 ListBuffer<Type> constTypes = new ListBuffer<>(); 451 ListBuffer<Object> constants = new ListBuffer<>(); 452 for (Object t : staticArgs) { 453 constants.add(t); 454 constTypes.add(syms.stringType); 455 } 456 457 List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType, 458 syms.stringType, 459 syms.methodTypeType) 460 .append(syms.stringType) 461 .appendList(constTypes); 462 463 Symbol bsm = rs.resolveInternalMethod(pos, 464 gen.getAttrEnv(), 465 syms.stringConcatFactory, 466 names.makeConcatWithConstants, 467 bsm_staticArgs, 468 null); 469 470 Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(names.makeConcatWithConstants, 471 syms.noSymbol, 472 ClassFile.REF_invokeStatic, 473 (Symbol.MethodSymbol)bsm, 474 indyType, 475 List.<Object>of(recipe).appendList(constants).toArray()); 476 477 Items.Item item = gen.getItems().makeDynamicItem(dynSym); 478 item.invoke(); 479 } finally { 480 make.at(prevPos); 481 } 482 } 483 } 484 485 }