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