1 /* 2 * Copyright (c) 2015, 2018, 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 instance(Context context)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 makeConcat(Context context)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 StringConcat(Context context)108 protected StringConcat(Context context) { 109 context.put(concatKey, this); 110 gen = Gen.instance(context); 111 syms = Symtab.instance(context); 112 types = Types.instance(context); 113 names = Names.instance(context); 114 make = TreeMaker.instance(context); 115 rs = Resolve.instance(context); 116 sbAppends = new HashMap<>(); 117 } 118 makeConcat(JCTree.JCAssignOp tree)119 public abstract Item makeConcat(JCTree.JCAssignOp tree); makeConcat(JCTree.JCBinary tree)120 public abstract Item makeConcat(JCTree.JCBinary tree); 121 collectAll(JCTree tree)122 protected List<JCTree> collectAll(JCTree tree) { 123 return collect(tree, List.nil()); 124 } 125 collectAll(JCTree.JCExpression lhs, JCTree.JCExpression rhs)126 protected List<JCTree> collectAll(JCTree.JCExpression lhs, JCTree.JCExpression rhs) { 127 return List.<JCTree>nil() 128 .appendList(collectAll(lhs)) 129 .appendList(collectAll(rhs)); 130 } 131 collect(JCTree tree, List<JCTree> res)132 private List<JCTree> collect(JCTree tree, List<JCTree> res) { 133 tree = TreeInfo.skipParens(tree); 134 if (tree.hasTag(PLUS) && tree.type.constValue() == null) { 135 JCTree.JCBinary op = (JCTree.JCBinary) tree; 136 if (op.operator.kind == MTH && 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 * If the type is not accessible from current context, try to figure out the 147 * sharpest accessible supertype. 148 * 149 * @param originalType type to sharpen 150 * @return sharped type 151 */ sharpestAccessible(Type originalType)152 Type sharpestAccessible(Type originalType) { 153 if (originalType.hasTag(ARRAY)) { 154 return types.makeArrayType(sharpestAccessible(types.elemtype(originalType))); 155 } 156 157 Type type = originalType; 158 while (!rs.isAccessible(gen.getAttrEnv(), type.asElement())) { 159 type = types.supertype(type); 160 } 161 return type; 162 } 163 164 /** 165 * "Legacy" bytecode flavor: emit the StringBuilder.append chains for string 166 * concatenation. 167 */ 168 private static class Inline extends StringConcat { Inline(Context context)169 public Inline(Context context) { 170 super(context); 171 } 172 173 @Override makeConcat(JCTree.JCAssignOp tree)174 public Item makeConcat(JCTree.JCAssignOp tree) { 175 // Generate code to make a string builder 176 JCDiagnostic.DiagnosticPosition pos = tree.pos(); 177 178 // Create a string builder. 179 newStringBuilder(tree); 180 181 // Generate code for first string, possibly save one 182 // copy under builder 183 Item l = gen.genExpr(tree.lhs, tree.lhs.type); 184 if (l.width() > 0) { 185 gen.getCode().emitop0(dup_x1 + 3 * (l.width() - 1)); 186 } 187 188 // Load first string and append to builder. 189 l.load(); 190 appendString(tree.lhs); 191 192 // Append all other strings to builder. 193 List<JCTree> args = collectAll(tree.rhs); 194 for (JCTree t : args) { 195 gen.genExpr(t, t.type).load(); 196 appendString(t); 197 } 198 199 // Convert builder to string. 200 builderToString(pos); 201 202 return l; 203 } 204 205 @Override makeConcat(JCTree.JCBinary tree)206 public Item makeConcat(JCTree.JCBinary tree) { 207 JCDiagnostic.DiagnosticPosition pos = tree.pos(); 208 209 // Create a string builder. 210 newStringBuilder(tree); 211 212 // Append all strings to builder. 213 List<JCTree> args = collectAll(tree); 214 for (JCTree t : args) { 215 gen.genExpr(t, t.type).load(); 216 appendString(t); 217 } 218 219 // Convert builder to string. 220 builderToString(pos); 221 222 return gen.getItems().makeStackItem(syms.stringType); 223 } 224 newStringBuilder(JCTree tree)225 private JCDiagnostic.DiagnosticPosition newStringBuilder(JCTree tree) { 226 JCDiagnostic.DiagnosticPosition pos = tree.pos(); 227 gen.getCode().emitop2(new_, gen.makeRef(pos, syms.stringBuilderType), syms.stringBuilderType); 228 gen.getCode().emitop0(dup); 229 gen.callMethod(pos, syms.stringBuilderType, names.init, List.nil(), false); 230 return pos; 231 } 232 appendString(JCTree tree)233 private void appendString(JCTree tree) { 234 Type t = tree.type.baseType(); 235 if (!t.isPrimitive() && t.tsym != syms.stringType.tsym) { 236 t = syms.objectType; 237 } 238 239 Assert.checkNull(t.constValue()); 240 Symbol method = sbAppends.get(t); 241 if (method == null) { 242 method = rs.resolveInternalMethod(tree.pos(), gen.getAttrEnv(), syms.stringBuilderType, names.append, List.of(t), null); 243 sbAppends.put(t, method); 244 } 245 246 gen.getItems().makeMemberItem(method, false).invoke(); 247 } 248 builderToString(JCDiagnostic.DiagnosticPosition pos)249 private void builderToString(JCDiagnostic.DiagnosticPosition pos) { 250 gen.callMethod(pos, syms.stringBuilderType, names.toString, List.nil(), false); 251 } 252 } 253 254 /** 255 * Base class for indified concatenation bytecode flavors. 256 */ 257 private static abstract class Indy extends StringConcat { Indy(Context context)258 public Indy(Context context) { 259 super(context); 260 } 261 262 @Override makeConcat(JCTree.JCAssignOp tree)263 public Item makeConcat(JCTree.JCAssignOp tree) { 264 List<JCTree> args = collectAll(tree.lhs, tree.rhs); 265 Item l = gen.genExpr(tree.lhs, tree.lhs.type); 266 l.duplicate(); 267 l.load(); 268 emit(tree.pos(), args, false, tree.type); 269 return l; 270 } 271 272 @Override makeConcat(JCTree.JCBinary tree)273 public Item makeConcat(JCTree.JCBinary tree) { 274 List<JCTree> args = collectAll(tree.lhs, tree.rhs); 275 emit(tree.pos(), args, true, tree.type); 276 return gen.getItems().makeStackItem(syms.stringType); 277 } 278 emit(JCDiagnostic.DiagnosticPosition pos, List<JCTree> args, boolean generateFirstArg, Type type)279 protected abstract void emit(JCDiagnostic.DiagnosticPosition pos, List<JCTree> args, boolean generateFirstArg, Type type); 280 281 /** Peel the argument list into smaller chunks. */ split(List<JCTree> args)282 protected List<List<JCTree>> split(List<JCTree> args) { 283 ListBuffer<List<JCTree>> splits = new ListBuffer<>(); 284 285 int slots = 0; 286 287 // Need to peel, so that neither call has more than acceptable number 288 // of slots for the arguments. 289 ListBuffer<JCTree> cArgs = new ListBuffer<>(); 290 for (JCTree t : args) { 291 int needSlots = (t.type.getTag() == LONG || t.type.getTag() == DOUBLE) ? 2 : 1; 292 if (slots + needSlots >= MAX_INDY_CONCAT_ARG_SLOTS) { 293 splits.add(cArgs.toList()); 294 cArgs.clear(); 295 slots = 0; 296 } 297 cArgs.add(t); 298 slots += needSlots; 299 } 300 301 // Flush the tail slice 302 if (!cArgs.isEmpty()) { 303 splits.add(cArgs.toList()); 304 } 305 306 return splits.toList(); 307 } 308 } 309 310 /** 311 * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory, 312 * without handling constants specially. 313 * 314 * We bypass empty strings, because they have no meaning at this level. This 315 * captures the Java language trick to force String concat with e.g. ("" + int)-like 316 * expression. Down here, we already know we are in String concat business, and do 317 * not require these markers. 318 */ 319 private static class IndyPlain extends Indy { IndyPlain(Context context)320 public IndyPlain(Context context) { 321 super(context); 322 } 323 324 /** Emit the indy concat for all these arguments, possibly peeling along the way */ emit(JCDiagnostic.DiagnosticPosition pos, List<JCTree> args, boolean generateFirstArg, Type type)325 protected void emit(JCDiagnostic.DiagnosticPosition pos, List<JCTree> args, boolean generateFirstArg, Type type) { 326 List<List<JCTree>> split = split(args); 327 328 boolean first = true; 329 for (List<JCTree> t : split) { 330 Assert.check(!t.isEmpty(), "Arguments list is empty"); 331 332 ListBuffer<Type> dynamicArgs = new ListBuffer<>(); 333 for (JCTree arg : t) { 334 Object constVal = arg.type.constValue(); 335 if ("".equals(constVal)) continue; 336 if (arg.type == syms.botType) { 337 dynamicArgs.add(types.boxedClass(syms.voidType).type); 338 } else { 339 dynamicArgs.add(sharpestAccessible(arg.type)); 340 } 341 if (!first || generateFirstArg) { 342 gen.genExpr(arg, arg.type).load(); 343 } 344 first = false; 345 } 346 doCall(type, pos, dynamicArgs.toList()); 347 } 348 349 // More that one peel slice produced: concatenate the results 350 if (split.size() > 1) { 351 ListBuffer<Type> argTypes = new ListBuffer<>(); 352 for (int c = 0; c < split.size(); c++) { 353 argTypes.append(syms.stringType); 354 } 355 doCall(type, pos, argTypes.toList()); 356 } 357 } 358 359 /** Produce the actual invokedynamic call to StringConcatFactory */ doCall(Type type, JCDiagnostic.DiagnosticPosition pos, List<Type> dynamicArgTypes)360 private void doCall(Type type, JCDiagnostic.DiagnosticPosition pos, List<Type> dynamicArgTypes) { 361 Type.MethodType indyType = new Type.MethodType(dynamicArgTypes, 362 type, 363 List.nil(), 364 syms.methodClass); 365 366 int prevPos = make.pos; 367 try { 368 make.at(pos); 369 370 List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType, 371 syms.stringType, 372 syms.methodTypeType); 373 374 Symbol bsm = rs.resolveInternalMethod(pos, 375 gen.getAttrEnv(), 376 syms.stringConcatFactory, 377 names.makeConcat, 378 bsm_staticArgs, 379 null); 380 381 Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(names.makeConcat, 382 syms.noSymbol, 383 ((MethodSymbol)bsm).asHandle(), 384 indyType, 385 List.nil().toArray(new LoadableConstant[0])); 386 387 Items.Item item = gen.getItems().makeDynamicItem(dynSym); 388 item.invoke(); 389 } finally { 390 make.at(prevPos); 391 } 392 } 393 } 394 395 /** 396 * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory. 397 * This code concatenates all known constants into the recipe, possibly escaping 398 * some constants separately. 399 * 400 * We also bypass empty strings, because they have no meaning at this level. This 401 * captures the Java language trick to force String concat with e.g. ("" + int)-like 402 * expression. Down here, we already know we are in String concat business, and do 403 * not require these markers. 404 */ 405 private static final class IndyConstants extends Indy { IndyConstants(Context context)406 public IndyConstants(Context context) { 407 super(context); 408 } 409 410 @Override emit(JCDiagnostic.DiagnosticPosition pos, List<JCTree> args, boolean generateFirstArg, Type type)411 protected void emit(JCDiagnostic.DiagnosticPosition pos, List<JCTree> args, boolean generateFirstArg, Type type) { 412 List<List<JCTree>> split = split(args); 413 414 boolean first = true; 415 for (List<JCTree> t : split) { 416 Assert.check(!t.isEmpty(), "Arguments list is empty"); 417 418 StringBuilder recipe = new StringBuilder(t.size()); 419 ListBuffer<Type> dynamicArgs = new ListBuffer<>(); 420 ListBuffer<LoadableConstant> staticArgs = new ListBuffer<>(); 421 422 for (JCTree arg : t) { 423 Object constVal = arg.type.constValue(); 424 if ("".equals(constVal)) continue; 425 if (arg.type == syms.botType) { 426 // Concat the null into the recipe right away 427 recipe.append((String) null); 428 } else if (constVal != null) { 429 // Concat the String representation of the constant, except 430 // for the case it contains special tags, which requires us 431 // to expose it as detached constant. 432 String a = arg.type.stringValue(); 433 if (a.indexOf(TAG_CONST) != -1 || a.indexOf(TAG_ARG) != -1) { 434 recipe.append(TAG_CONST); 435 staticArgs.add(LoadableConstant.String(a)); 436 } else { 437 recipe.append(a); 438 } 439 } else { 440 // Ordinary arguments come through the dynamic arguments. 441 recipe.append(TAG_ARG); 442 dynamicArgs.add(sharpestAccessible(arg.type)); 443 if (!first || generateFirstArg) { 444 gen.genExpr(arg, arg.type).load(); 445 } 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 */ doCall(Type type, JCDiagnostic.DiagnosticPosition pos, String recipe, List<LoadableConstant> staticArgs, List<Type> dynamicArgTypes)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 Symbol 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 ((MethodSymbol)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 } 513