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