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