StringConcat.java revision 3286:7a9d55dbfb84
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 && 135 ((Symbol.OperatorSymbol) op.operator).opcode == string_add) { 136 return res 137 .appendList(collect(op.lhs, res)) 138 .appendList(collect(op.rhs, res)); 139 } 140 } 141 return res.append(tree); 142 } 143 144 /** 145 * If the type is not accessible from current context, try to figure out the 146 * sharpest accessible supertype. 147 * 148 * @param originalType type to sharpen 149 * @return sharped type 150 */ 151 Type sharpestAccessible(Type originalType) { 152 if (originalType.hasTag(ARRAY)) { 153 return types.makeArrayType(sharpestAccessible(types.elemtype(originalType))); 154 } 155 156 Type type = originalType; 157 while (!rs.isAccessible(gen.getAttrEnv(), type.asElement())) { 158 type = types.supertype(type); 159 } 160 return type; 161 } 162 163 /** 164 * "Legacy" bytecode flavor: emit the StringBuilder.append chains for string 165 * concatenation. 166 */ 167 private static class Inline extends StringConcat { 168 public Inline(Context context) { 169 super(context); 170 } 171 172 @Override 173 public Item makeConcat(JCTree.JCAssignOp tree) { 174 // Generate code to make a string builder 175 JCDiagnostic.DiagnosticPosition pos = tree.pos(); 176 177 // Create a string builder. 178 newStringBuilder(tree); 179 180 // Generate code for first string, possibly save one 181 // copy under builder 182 Item l = gen.genExpr(tree.lhs, tree.lhs.type); 183 if (l.width() > 0) { 184 gen.getCode().emitop0(dup_x1 + 3 * (l.width() - 1)); 185 } 186 187 // Load first string and append to builder. 188 l.load(); 189 appendString(tree.lhs); 190 191 // Append all other strings to builder. 192 List<JCTree> args = collectAll(tree.rhs); 193 for (JCTree t : args) { 194 gen.genExpr(t, t.type).load(); 195 appendString(t); 196 } 197 198 // Convert builder to string. 199 builderToString(pos); 200 201 return l; 202 } 203 204 @Override 205 public Item makeConcat(JCTree.JCBinary tree) { 206 JCDiagnostic.DiagnosticPosition pos = tree.pos(); 207 208 // Create a string builder. 209 newStringBuilder(tree); 210 211 // Append all strings to builder. 212 List<JCTree> args = collectAll(tree); 213 for (JCTree t : args) { 214 gen.genExpr(t, t.type).load(); 215 appendString(t); 216 } 217 218 // Convert builder to string. 219 builderToString(pos); 220 221 return gen.getItems().makeStackItem(syms.stringType); 222 } 223 224 private JCDiagnostic.DiagnosticPosition newStringBuilder(JCTree tree) { 225 JCDiagnostic.DiagnosticPosition pos = tree.pos(); 226 gen.getCode().emitop2(new_, gen.makeRef(pos, syms.stringBuilderType)); 227 gen.getCode().emitop0(dup); 228 gen.callMethod(pos, syms.stringBuilderType, names.init, List.<Type>nil(), false); 229 return pos; 230 } 231 232 private void appendString(JCTree tree) { 233 Type t = tree.type.baseType(); 234 if (!t.isPrimitive() && t.tsym != syms.stringType.tsym) { 235 t = syms.objectType; 236 } 237 238 Assert.checkNull(t.constValue()); 239 Symbol method = sbAppends.get(t); 240 if (method == null) { 241 method = rs.resolveInternalMethod(tree.pos(), gen.getAttrEnv(), syms.stringBuilderType, names.append, List.of(t), null); 242 sbAppends.put(t, method); 243 } 244 245 gen.getItems().makeMemberItem(method, false).invoke(); 246 } 247 248 private void builderToString(JCDiagnostic.DiagnosticPosition pos) { 249 gen.callMethod(pos, syms.stringBuilderType, names.toString, List.<Type>nil(), false); 250 } 251 } 252 253 /** 254 * Base class for indified concatenation bytecode flavors. 255 */ 256 private static abstract class Indy extends StringConcat { 257 public Indy(Context context) { 258 super(context); 259 } 260 261 @Override 262 public Item makeConcat(JCTree.JCAssignOp tree) { 263 List<JCTree> args = collectAll(tree.lhs, tree.rhs); 264 Item l = gen.genExpr(tree.lhs, tree.lhs.type); 265 emit(args, tree.type, tree.pos()); 266 return l; 267 } 268 269 @Override 270 public Item makeConcat(JCTree.JCBinary tree) { 271 List<JCTree> args = collectAll(tree.lhs, tree.rhs); 272 emit(args, tree.type, tree.pos()); 273 return gen.getItems().makeStackItem(syms.stringType); 274 } 275 276 protected abstract void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos); 277 278 /** Peel the argument list into smaller chunks. */ 279 protected List<List<JCTree>> split(List<JCTree> args) { 280 ListBuffer<List<JCTree>> splits = new ListBuffer<>(); 281 282 int slots = 0; 283 284 // Need to peel, so that neither call has more than acceptable number 285 // of slots for the arguments. 286 ListBuffer<JCTree> cArgs = new ListBuffer<>(); 287 for (JCTree t : args) { 288 int needSlots = (t.type.getTag() == LONG || t.type.getTag() == DOUBLE) ? 2 : 1; 289 if (slots + needSlots >= MAX_INDY_CONCAT_ARG_SLOTS) { 290 splits.add(cArgs.toList()); 291 cArgs.clear(); 292 slots = 0; 293 } 294 cArgs.add(t); 295 slots += needSlots; 296 } 297 298 // Flush the tail slice 299 if (!cArgs.isEmpty()) { 300 splits.add(cArgs.toList()); 301 } 302 303 return splits.toList(); 304 } 305 } 306 307 /** 308 * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory, 309 * without handling constants specially. 310 * 311 * We bypass empty strings, because they have no meaning at this level. This 312 * captures the Java language trick to force String concat with e.g. ("" + int)-like 313 * expression. Down here, we already know we are in String concat business, and do 314 * not require these markers. 315 */ 316 private static class IndyPlain extends Indy { 317 public IndyPlain(Context context) { 318 super(context); 319 } 320 321 /** Emit the indy concat for all these arguments, possibly peeling along the way */ 322 protected void emit(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos) { 323 List<List<JCTree>> split = split(args); 324 325 for (List<JCTree> t : split) { 326 Assert.check(!t.isEmpty(), "Arguments list is empty"); 327 328 ListBuffer<Type> dynamicArgs = new ListBuffer<>(); 329 for (JCTree arg : t) { 330 Object constVal = arg.type.constValue(); 331 if ("".equals(constVal)) continue; 332 if (arg.type == syms.botType) { 333 dynamicArgs.add(types.boxedClass(syms.voidType).type); 334 } else { 335 dynamicArgs.add(sharpestAccessible(arg.type)); 336 } 337 gen.genExpr(arg, arg.type).load(); 338 } 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.<Type>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 Symbol 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 ClassFile.REF_invokeStatic, 378 (Symbol.MethodSymbol)bsm, 379 indyType, 380 List.nil().toArray()); 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(List<JCTree> args, Type type, JCDiagnostic.DiagnosticPosition pos) { 407 List<List<JCTree>> split = split(args); 408 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<Object> 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(a); 430 } else { 431 recipe.append(a); 432 } 433 } else { 434 // Ordinary arguments come through the dynamic arguments. 435 recipe.append(TAG_ARG); 436 dynamicArgs.add(sharpestAccessible(arg.type)); 437 gen.genExpr(arg, arg.type).load(); 438 } 439 } 440 441 doCall(type, pos, recipe.toString(), staticArgs.toList(), dynamicArgs.toList()); 442 } 443 444 // More that one peel slice produced: concatenate the results 445 // All arguments are assumed to be non-constant Strings. 446 if (split.size() > 1) { 447 ListBuffer<Type> argTypes = new ListBuffer<>(); 448 StringBuilder recipe = new StringBuilder(); 449 for (int c = 0; c < split.size(); c++) { 450 argTypes.append(syms.stringType); 451 recipe.append(TAG_ARG); 452 } 453 doCall(type, pos, recipe.toString(), List.nil(), argTypes.toList()); 454 } 455 } 456 457 /** Produce the actual invokedynamic call to StringConcatFactory */ 458 private void doCall(Type type, JCDiagnostic.DiagnosticPosition pos, String recipe, List<Object> staticArgs, List<Type> dynamicArgTypes) { 459 Type.MethodType indyType = new Type.MethodType(dynamicArgTypes, 460 type, 461 List.<Type>nil(), 462 syms.methodClass); 463 464 int prevPos = make.pos; 465 try { 466 make.at(pos); 467 468 ListBuffer<Type> constTypes = new ListBuffer<>(); 469 ListBuffer<Object> constants = new ListBuffer<>(); 470 for (Object t : staticArgs) { 471 constants.add(t); 472 constTypes.add(syms.stringType); 473 } 474 475 List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType, 476 syms.stringType, 477 syms.methodTypeType) 478 .append(syms.stringType) 479 .appendList(constTypes); 480 481 Symbol bsm = rs.resolveInternalMethod(pos, 482 gen.getAttrEnv(), 483 syms.stringConcatFactory, 484 names.makeConcatWithConstants, 485 bsm_staticArgs, 486 null); 487 488 Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(names.makeConcatWithConstants, 489 syms.noSymbol, 490 ClassFile.REF_invokeStatic, 491 (Symbol.MethodSymbol)bsm, 492 indyType, 493 List.<Object>of(recipe).appendList(constants).toArray()); 494 495 Items.Item item = gen.getItems().makeDynamicItem(dynSym); 496 item.invoke(); 497 } finally { 498 make.at(prevPos); 499 } 500 } 501 } 502 503} 504