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