author | shade |
Tue, 17 May 2016 22:28:00 +0300 | |
changeset 38372 | 017d7578731c |
parent 37781 | 71ed5645f17c |
child 38428 | 0079e7d659db |
permissions | -rw-r--r-- |
35388 | 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 |
||
26 |
package java.lang.invoke; |
|
27 |
||
28 |
import jdk.internal.org.objectweb.asm.ClassWriter; |
|
29 |
import jdk.internal.org.objectweb.asm.Label; |
|
30 |
import jdk.internal.org.objectweb.asm.MethodVisitor; |
|
31 |
import jdk.internal.org.objectweb.asm.Opcodes; |
|
32 |
import jdk.internal.vm.annotation.ForceInline; |
|
35700
b933119b8f84
8148869: StringConcatFactory MH_INLINE_SIZED_EXACT strategy does not work with -XX:-CompactStrings
shade
parents:
35639
diff
changeset
|
33 |
import jdk.internal.misc.Unsafe; |
35388 | 34 |
|
35 |
import java.lang.invoke.MethodHandles.Lookup; |
|
36 |
import java.util.*; |
|
37 |
import java.util.concurrent.ConcurrentHashMap; |
|
38 |
import java.util.concurrent.ConcurrentMap; |
|
39 |
import java.util.function.Function; |
|
37316
2540215e3ada
8152074: Avoid lambda usage in StringConcatFactory initializer
redestad
parents:
36001
diff
changeset
|
40 |
import sun.security.action.GetPropertyAction; |
35388 | 41 |
|
42 |
import static jdk.internal.org.objectweb.asm.Opcodes.*; |
|
43 |
||
44 |
/** |
|
45 |
* <p>Methods to facilitate the creation of String concatenation methods, that |
|
46 |
* can be used to efficiently concatenate a known number of arguments of known |
|
47 |
* types, possibly after type adaptation and partial evaluation of arguments. |
|
48 |
* These methods are typically used as <em>bootstrap methods</em> for {@code |
|
49 |
* invokedynamic} call sites, to support the <em>string concatenation</em> |
|
50 |
* feature of the Java Programming Language. |
|
51 |
* |
|
52 |
* <p>Indirect access to the behavior specified by the provided {@code |
|
53 |
* MethodHandle} proceeds in order through two phases: |
|
54 |
* |
|
55 |
* <ol> |
|
56 |
* <li><em>Linkage</em> occurs when the methods in this class are invoked. |
|
57 |
* They take as arguments a method type describing the concatenated arguments |
|
58 |
* count and types, and optionally the String <em>recipe</em>, plus the |
|
59 |
* constants that participate in the String concatenation. The details on |
|
60 |
* accepted recipe shapes are described further below. Linkage may involve |
|
61 |
* dynamically loading a new class that implements the expected concatenation |
|
62 |
* behavior. The {@code CallSite} holds the {@code MethodHandle} pointing to the |
|
63 |
* exact concatenation method. The concatenation methods may be shared among |
|
64 |
* different {@code CallSite}s, e.g. if linkage methods produce them as pure |
|
65 |
* functions.</li> |
|
66 |
* |
|
67 |
* <li><em>Invocation</em> occurs when a generated concatenation method is |
|
68 |
* invoked with the exact dynamic arguments. This may occur many times for a |
|
69 |
* single concatenation method. The method referenced by the behavior {@code |
|
70 |
* MethodHandle} is invoked with the static arguments and any additional dynamic |
|
71 |
* arguments provided on invocation, as if by {@link MethodHandle#invoke(Object...)}.</li> |
|
72 |
* </ol> |
|
73 |
* |
|
74 |
* <p> This class provides two forms of linkage methods: a simple version |
|
75 |
* ({@link #makeConcat(java.lang.invoke.MethodHandles.Lookup, String, |
|
76 |
* MethodType)}) using only the dynamic arguments, and an advanced version |
|
77 |
* ({@link #makeConcatWithConstants(java.lang.invoke.MethodHandles.Lookup, |
|
78 |
* String, MethodType, String, Object...)} using the advanced forms of capturing |
|
79 |
* the constant arguments. The advanced strategy can produce marginally better |
|
80 |
* invocation bytecode, at the expense of exploding the number of shapes of |
|
81 |
* string concatenation methods present at runtime, because those shapes would |
|
82 |
* include constant static arguments as well. |
|
83 |
* |
|
84 |
* @author Aleksey Shipilev |
|
85 |
* @author Remi Forax |
|
86 |
* @author Peter Levart |
|
87 |
* |
|
88 |
* @apiNote |
|
89 |
* <p>There is a JVM limit (classfile structural constraint): no method |
|
90 |
* can call with more than 255 slots. This limits the number of static and |
|
91 |
* dynamic arguments one can pass to bootstrap method. Since there are potential |
|
92 |
* concatenation strategies that use {@code MethodHandle} combinators, we need |
|
36441
f40a48b83f57
8151063: Typo in java.lang.invoke.StringConcatFactory javadoc
rriggs
parents:
36001
diff
changeset
|
93 |
* to reserve a few empty slots on the parameter lists to capture the |
35388 | 94 |
* temporal results. This is why bootstrap methods in this factory do not accept |
95 |
* more than 200 argument slots. Users requiring more than 200 argument slots in |
|
96 |
* concatenation are expected to split the large concatenation in smaller |
|
97 |
* expressions. |
|
35401 | 98 |
* |
99 |
* @since 9 |
|
35388 | 100 |
*/ |
101 |
public final class StringConcatFactory { |
|
102 |
||
103 |
/** |
|
104 |
* Tag used to demarcate an ordinary argument. |
|
105 |
*/ |
|
106 |
private static final char TAG_ARG = '\u0001'; |
|
107 |
||
108 |
/** |
|
109 |
* Tag used to demarcate a constant. |
|
110 |
*/ |
|
111 |
private static final char TAG_CONST = '\u0002'; |
|
112 |
||
113 |
/** |
|
114 |
* Maximum number of argument slots in String Concat call. |
|
115 |
* |
|
116 |
* While the maximum number of argument slots that indy call can handle is 253, |
|
117 |
* we do not use all those slots, to let the strategies with MethodHandle |
|
118 |
* combinators to use some arguments. |
|
119 |
*/ |
|
120 |
private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200; |
|
121 |
||
122 |
/** |
|
123 |
* Concatenation strategy to use. See {@link Strategy} for possible options. |
|
124 |
* This option is controllable with -Djava.lang.invoke.stringConcat JDK option. |
|
125 |
*/ |
|
37684
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
126 |
private static Strategy STRATEGY; |
35388 | 127 |
|
128 |
/** |
|
129 |
* Default strategy to use for concatenation. |
|
130 |
*/ |
|
131 |
private static final Strategy DEFAULT_STRATEGY = Strategy.BC_SB; |
|
132 |
||
133 |
private enum Strategy { |
|
134 |
/** |
|
135 |
* Bytecode generator, calling into {@link java.lang.StringBuilder}. |
|
136 |
*/ |
|
137 |
BC_SB, |
|
138 |
||
139 |
/** |
|
140 |
* Bytecode generator, calling into {@link java.lang.StringBuilder}; |
|
141 |
* but trying to estimate the required storage. |
|
142 |
*/ |
|
143 |
BC_SB_SIZED, |
|
144 |
||
145 |
/** |
|
146 |
* Bytecode generator, calling into {@link java.lang.StringBuilder}; |
|
147 |
* but computing the required storage exactly. |
|
148 |
*/ |
|
149 |
BC_SB_SIZED_EXACT, |
|
150 |
||
151 |
/** |
|
152 |
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. |
|
153 |
* This strategy also tries to estimate the required storage. |
|
154 |
*/ |
|
155 |
MH_SB_SIZED, |
|
156 |
||
157 |
/** |
|
158 |
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. |
|
159 |
* This strategy also estimate the required storage exactly. |
|
160 |
*/ |
|
161 |
MH_SB_SIZED_EXACT, |
|
162 |
||
163 |
/** |
|
164 |
* MethodHandle-based generator, that constructs its own byte[] array from |
|
165 |
* the arguments. It computes the required storage exactly. |
|
166 |
*/ |
|
167 |
MH_INLINE_SIZED_EXACT |
|
168 |
} |
|
169 |
||
170 |
/** |
|
171 |
* Enables debugging: this may print debugging messages, perform additional (non-neutral for performance) |
|
172 |
* checks, etc. |
|
173 |
*/ |
|
174 |
private static final boolean DEBUG; |
|
175 |
||
176 |
/** |
|
177 |
* Enables caching of strategy stubs. This may improve the linkage time by reusing the generated |
|
178 |
* code, at the expense of contaminating the profiles. |
|
179 |
*/ |
|
180 |
private static final boolean CACHE_ENABLE; |
|
181 |
||
182 |
private static final ConcurrentMap<Key, MethodHandle> CACHE; |
|
183 |
||
35776
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
184 |
/** |
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
185 |
* Dump generated classes to disk, for debugging purposes. |
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
186 |
*/ |
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
187 |
private static final ProxyClassesDumper DUMPER; |
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
188 |
|
35388 | 189 |
static { |
37684
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
190 |
// In case we need to double-back onto the StringConcatFactory during this |
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
191 |
// static initialization, make sure we have the reasonable defaults to complete |
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
192 |
// the static initialization properly. After that, actual users would use the |
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
193 |
// the proper values we have read from the the properties. |
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
194 |
STRATEGY = DEFAULT_STRATEGY; |
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
195 |
// CACHE_ENABLE = false; // implied |
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
196 |
// CACHE = null; // implied |
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
197 |
// DEBUG = false; // implied |
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
198 |
// DUMPER = null; // implied |
38b7cb606a05
8155090: String concatenation fails with a custom SecurityManager that uses concatenation
shade
parents:
37593
diff
changeset
|
199 |
|
37781
71ed5645f17c
8155775: Re-examine naming of privileged methods to access System properties
redestad
parents:
37684
diff
changeset
|
200 |
Properties props = GetPropertyAction.privilegedGetProperties(); |
37593
824750ada3d6
8154231: Simplify access to System properties from JDK code
redestad
parents:
37332
diff
changeset
|
201 |
final String strategy = |
824750ada3d6
8154231: Simplify access to System properties from JDK code
redestad
parents:
37332
diff
changeset
|
202 |
props.getProperty("java.lang.invoke.stringConcat"); |
824750ada3d6
8154231: Simplify access to System properties from JDK code
redestad
parents:
37332
diff
changeset
|
203 |
CACHE_ENABLE = Boolean.parseBoolean( |
824750ada3d6
8154231: Simplify access to System properties from JDK code
redestad
parents:
37332
diff
changeset
|
204 |
props.getProperty("java.lang.invoke.stringConcat.cache")); |
824750ada3d6
8154231: Simplify access to System properties from JDK code
redestad
parents:
37332
diff
changeset
|
205 |
DEBUG = Boolean.parseBoolean( |
824750ada3d6
8154231: Simplify access to System properties from JDK code
redestad
parents:
37332
diff
changeset
|
206 |
props.getProperty("java.lang.invoke.stringConcat.debug")); |
824750ada3d6
8154231: Simplify access to System properties from JDK code
redestad
parents:
37332
diff
changeset
|
207 |
final String dumpPath = |
824750ada3d6
8154231: Simplify access to System properties from JDK code
redestad
parents:
37332
diff
changeset
|
208 |
props.getProperty("java.lang.invoke.stringConcat.dumpClasses"); |
35388 | 209 |
|
210 |
STRATEGY = (strategy == null) ? DEFAULT_STRATEGY : Strategy.valueOf(strategy); |
|
211 |
CACHE = CACHE_ENABLE ? new ConcurrentHashMap<>() : null; |
|
35776
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
212 |
DUMPER = (dumpPath == null) ? null : ProxyClassesDumper.getInstance(dumpPath); |
35388 | 213 |
} |
214 |
||
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
215 |
/** |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
216 |
* Cache key is a composite of: |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
217 |
* - class name, that lets to disambiguate stubs, to avoid excess sharing |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
218 |
* - method type, describing the dynamic arguments for concatenation |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
219 |
* - concat recipe, describing the constants and concat shape |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
220 |
*/ |
35388 | 221 |
private static final class Key { |
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
222 |
final String className; |
35388 | 223 |
final MethodType mt; |
224 |
final Recipe recipe; |
|
225 |
||
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
226 |
public Key(String className, MethodType mt, Recipe recipe) { |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
227 |
this.className = className; |
35388 | 228 |
this.mt = mt; |
229 |
this.recipe = recipe; |
|
230 |
} |
|
231 |
||
232 |
@Override |
|
233 |
public boolean equals(Object o) { |
|
234 |
if (this == o) return true; |
|
235 |
if (o == null || getClass() != o.getClass()) return false; |
|
236 |
||
237 |
Key key = (Key) o; |
|
238 |
||
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
239 |
if (!className.equals(key.className)) return false; |
35388 | 240 |
if (!mt.equals(key.mt)) return false; |
241 |
if (!recipe.equals(key.recipe)) return false; |
|
242 |
return true; |
|
243 |
} |
|
244 |
||
245 |
@Override |
|
246 |
public int hashCode() { |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
247 |
int result = className.hashCode(); |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
248 |
result = 31 * result + mt.hashCode(); |
35388 | 249 |
result = 31 * result + recipe.hashCode(); |
250 |
return result; |
|
251 |
} |
|
252 |
} |
|
253 |
||
254 |
/** |
|
255 |
* Parses the recipe string, and produces the traversable collection of |
|
256 |
* {@link java.lang.invoke.StringConcatFactory.RecipeElement}-s for generator |
|
257 |
* strategies. Notably, this class parses out the constants from the recipe |
|
258 |
* and from other static arguments. |
|
259 |
*/ |
|
260 |
private static final class Recipe { |
|
261 |
private final List<RecipeElement> elements; |
|
262 |
||
263 |
public Recipe(String src, Object[] constants) { |
|
264 |
List<RecipeElement> el = new ArrayList<>(); |
|
265 |
||
266 |
int constC = 0; |
|
267 |
int argC = 0; |
|
268 |
||
269 |
StringBuilder acc = new StringBuilder(); |
|
270 |
||
271 |
for (int i = 0; i < src.length(); i++) { |
|
272 |
char c = src.charAt(i); |
|
273 |
||
274 |
if (c == TAG_CONST || c == TAG_ARG) { |
|
275 |
// Detected a special tag, flush all accumulated characters |
|
276 |
// as a constant first: |
|
277 |
if (acc.length() > 0) { |
|
278 |
el.add(new RecipeElement(acc.toString())); |
|
279 |
acc.setLength(0); |
|
280 |
} |
|
281 |
if (c == TAG_CONST) { |
|
282 |
Object cnst = constants[constC++]; |
|
283 |
el.add(new RecipeElement(cnst)); |
|
284 |
} |
|
285 |
if (c == TAG_ARG) { |
|
286 |
el.add(new RecipeElement(argC++)); |
|
287 |
} |
|
288 |
} else { |
|
289 |
// Not a special characters, this is a constant embedded into |
|
290 |
// the recipe itself. |
|
291 |
acc.append(c); |
|
292 |
} |
|
293 |
} |
|
294 |
||
295 |
// Flush the remaining characters as constant: |
|
296 |
if (acc.length() > 0) { |
|
297 |
el.add(new RecipeElement(acc.toString())); |
|
298 |
} |
|
299 |
||
36737
67b2804be5ab
8152951: Avoid calculating the reverse of StringConcatFactory$Recipe elements
redestad
parents:
36668
diff
changeset
|
300 |
elements = el; |
35388 | 301 |
} |
302 |
||
36737
67b2804be5ab
8152951: Avoid calculating the reverse of StringConcatFactory$Recipe elements
redestad
parents:
36668
diff
changeset
|
303 |
public List<RecipeElement> getElements() { |
35388 | 304 |
return elements; |
305 |
} |
|
306 |
||
307 |
@Override |
|
308 |
public boolean equals(Object o) { |
|
309 |
if (this == o) return true; |
|
310 |
if (o == null || getClass() != o.getClass()) return false; |
|
311 |
||
312 |
Recipe recipe = (Recipe) o; |
|
313 |
return elements.equals(recipe.elements); |
|
314 |
} |
|
315 |
||
316 |
@Override |
|
317 |
public int hashCode() { |
|
318 |
return elements.hashCode(); |
|
319 |
} |
|
320 |
} |
|
321 |
||
322 |
private static final class RecipeElement { |
|
323 |
private final Object value; |
|
324 |
private final int argPos; |
|
325 |
private final Tag tag; |
|
326 |
||
327 |
public RecipeElement(Object cnst) { |
|
328 |
this.value = Objects.requireNonNull(cnst); |
|
329 |
this.argPos = -1; |
|
330 |
this.tag = Tag.CONST; |
|
331 |
} |
|
332 |
||
333 |
public RecipeElement(int arg) { |
|
334 |
this.value = null; |
|
335 |
this.argPos = arg; |
|
336 |
this.tag = Tag.ARG; |
|
337 |
} |
|
338 |
||
339 |
public Object getValue() { |
|
340 |
assert (tag == Tag.CONST); |
|
341 |
return value; |
|
342 |
} |
|
343 |
||
344 |
public int getArgPos() { |
|
345 |
assert (tag == Tag.ARG); |
|
346 |
return argPos; |
|
347 |
} |
|
348 |
||
349 |
public Tag getTag() { |
|
350 |
return tag; |
|
351 |
} |
|
352 |
||
353 |
@Override |
|
354 |
public boolean equals(Object o) { |
|
355 |
if (this == o) return true; |
|
356 |
if (o == null || getClass() != o.getClass()) return false; |
|
357 |
||
358 |
RecipeElement that = (RecipeElement) o; |
|
359 |
||
360 |
if (tag != that.tag) return false; |
|
361 |
if (tag == Tag.CONST && (!value.equals(that.value))) return false; |
|
362 |
if (tag == Tag.ARG && (argPos != that.argPos)) return false; |
|
363 |
return true; |
|
364 |
} |
|
365 |
||
366 |
@Override |
|
367 |
public int hashCode() { |
|
368 |
return tag.hashCode(); |
|
369 |
} |
|
370 |
} |
|
371 |
||
372 |
private enum Tag { |
|
373 |
CONST, ARG |
|
374 |
} |
|
375 |
||
376 |
/** |
|
377 |
* Facilitates the creation of optimized String concatenation methods, that |
|
378 |
* can be used to efficiently concatenate a known number of arguments of |
|
379 |
* known types, possibly after type adaptation and partial evaluation of |
|
380 |
* arguments. Typically used as a <em>bootstrap method</em> for {@code |
|
381 |
* invokedynamic} call sites, to support the <em>string concatenation</em> |
|
382 |
* feature of the Java Programming Language. |
|
383 |
* |
|
384 |
* <p>When the target of the {@code CallSite} returned from this method is |
|
385 |
* invoked, it returns the result of String concatenation, taking all |
|
386 |
* function arguments passed to the linkage method as inputs for |
|
387 |
* concatenation. The target signature is given by {@code concatType}. |
|
388 |
* The arguments are concatenated as per requirements stated in JLS 15.18.1 |
|
389 |
* "String Concatenation Operator +". Notably, the inputs are converted as |
|
390 |
* per JLS 5.1.11 "String Conversion", and combined from left to right. |
|
391 |
* |
|
392 |
* <p>Assume the linkage arguments are as follows: |
|
393 |
* |
|
394 |
* <ul> |
|
395 |
* <li>{@code concatType}, describing the {@code CallSite} signature</li> |
|
396 |
* </ul> |
|
397 |
* |
|
398 |
* <p>Then the following linkage invariants must hold: |
|
399 |
* |
|
400 |
* <ul> |
|
401 |
* <li>The parameter count in {@code concatType} is less than or equal to 200</li> |
|
402 |
* |
|
403 |
* <li>The return type in {@code concatType} is assignable from {@link java.lang.String}</li> |
|
404 |
* </ul> |
|
405 |
* |
|
406 |
* @param lookup Represents a lookup context with the accessibility |
|
407 |
* privileges of the caller. When used with {@code |
|
408 |
* invokedynamic}, this is stacked automatically by the VM. |
|
409 |
* @param name The name of the method to implement. This name is |
|
410 |
* arbitrary, and has no meaning for this linkage method. |
|
411 |
* When used with {@code invokedynamic}, this is provided by |
|
412 |
* the {@code NameAndType} of the {@code InvokeDynamic} |
|
413 |
* structure and is stacked automatically by the VM. |
|
414 |
* @param concatType The expected signature of the {@code CallSite}. The |
|
415 |
* parameter types represent the types of concatenation |
|
416 |
* arguments; the return type is always assignable from {@link |
|
417 |
* java.lang.String}. When used with {@code invokedynamic}, |
|
418 |
* this is provided by the {@code NameAndType} of the {@code |
|
419 |
* InvokeDynamic} structure and is stacked automatically by |
|
420 |
* the VM. |
|
421 |
* @return a CallSite whose target can be used to perform String |
|
422 |
* concatenation, with dynamic concatenation arguments described by the given |
|
423 |
* {@code concatType}. |
|
424 |
* @throws StringConcatException If any of the linkage invariants described |
|
425 |
* here are violated. |
|
426 |
* @throws NullPointerException If any of the incoming arguments is null. |
|
427 |
* This will never happen when a bootstrap method |
|
428 |
* is called with invokedynamic. |
|
429 |
* |
|
430 |
* @jls 5.1.11 String Conversion |
|
431 |
* @jls 15.18.1 String Concatenation Operator + |
|
432 |
*/ |
|
433 |
public static CallSite makeConcat(MethodHandles.Lookup lookup, |
|
434 |
String name, |
|
435 |
MethodType concatType) throws StringConcatException { |
|
436 |
if (DEBUG) { |
|
437 |
System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType); |
|
438 |
} |
|
439 |
||
440 |
return doStringConcat(lookup, name, concatType, true, null); |
|
441 |
} |
|
442 |
||
443 |
/** |
|
444 |
* Facilitates the creation of optimized String concatenation methods, that |
|
445 |
* can be used to efficiently concatenate a known number of arguments of |
|
446 |
* known types, possibly after type adaptation and partial evaluation of |
|
447 |
* arguments. Typically used as a <em>bootstrap method</em> for {@code |
|
448 |
* invokedynamic} call sites, to support the <em>string concatenation</em> |
|
449 |
* feature of the Java Programming Language. |
|
450 |
* |
|
451 |
* <p>When the target of the {@code CallSite} returned from this method is |
|
452 |
* invoked, it returns the result of String concatenation, taking all |
|
453 |
* function arguments and constants passed to the linkage method as inputs for |
|
454 |
* concatenation. The target signature is given by {@code concatType}, and |
|
455 |
* does not include constants. The arguments are concatenated as per requirements |
|
456 |
* stated in JLS 15.18.1 "String Concatenation Operator +". Notably, the inputs |
|
457 |
* are converted as per JLS 5.1.11 "String Conversion", and combined from left |
|
458 |
* to right. |
|
459 |
* |
|
460 |
* <p>The concatenation <em>recipe</em> is a String description for the way to |
|
461 |
* construct a concatenated String from the arguments and constants. The |
|
462 |
* recipe is processed from left to right, and each character represents an |
|
463 |
* input to concatenation. Recipe characters mean: |
|
464 |
* |
|
465 |
* <ul> |
|
466 |
* |
|
467 |
* <li><em>\1 (Unicode point 0001)</em>: an ordinary argument. This |
|
468 |
* input is passed through dynamic argument, and is provided during the |
|
469 |
* concatenation method invocation. This input can be null.</li> |
|
470 |
* |
|
471 |
* <li><em>\2 (Unicode point 0002):</em> a constant. This input passed |
|
472 |
* through static bootstrap argument. This constant can be any value |
|
473 |
* representable in constant pool. If necessary, the factory would call |
|
474 |
* {@code toString} to perform a one-time String conversion.</li> |
|
475 |
* |
|
476 |
* <li><em>Any other char value:</em> a single character constant.</li> |
|
477 |
* </ul> |
|
478 |
* |
|
479 |
* <p>Assume the linkage arguments are as follows: |
|
480 |
* |
|
481 |
* <ul> |
|
482 |
* <li>{@code concatType}, describing the {@code CallSite} signature</li> |
|
483 |
* <li>{@code recipe}, describing the String recipe</li> |
|
484 |
* <li>{@code constants}, the vararg array of constants</li> |
|
485 |
* </ul> |
|
486 |
* |
|
487 |
* <p>Then the following linkage invariants must hold: |
|
488 |
* |
|
489 |
* <ul> |
|
490 |
* <li>The parameter count in {@code concatType} is less than or equal to |
|
491 |
* 200</li> |
|
492 |
* |
|
493 |
* <li>The parameter count in {@code concatType} equals to number of \1 tags |
|
494 |
* in {@code recipe}</li> |
|
495 |
* |
|
496 |
* <li>The return type in {@code concatType} is assignable |
|
497 |
* from {@link java.lang.String}, and matches the return type of the |
|
498 |
* returned {@link MethodHandle}</li> |
|
499 |
* |
|
500 |
* <li>The number of elements in {@code constants} equals to number of \2 |
|
501 |
* tags in {@code recipe}</li> |
|
502 |
* </ul> |
|
503 |
* |
|
504 |
* @param lookup Represents a lookup context with the accessibility |
|
505 |
* privileges of the caller. When used with {@code |
|
506 |
* invokedynamic}, this is stacked automatically by the |
|
507 |
* VM. |
|
508 |
* @param name The name of the method to implement. This name is |
|
509 |
* arbitrary, and has no meaning for this linkage method. |
|
510 |
* When used with {@code invokedynamic}, this is provided |
|
511 |
* by the {@code NameAndType} of the {@code InvokeDynamic} |
|
512 |
* structure and is stacked automatically by the VM. |
|
513 |
* @param concatType The expected signature of the {@code CallSite}. The |
|
514 |
* parameter types represent the types of dynamic concatenation |
|
515 |
* arguments; the return type is always assignable from {@link |
|
516 |
* java.lang.String}. When used with {@code |
|
517 |
* invokedynamic}, this is provided by the {@code |
|
518 |
* NameAndType} of the {@code InvokeDynamic} structure and |
|
519 |
* is stacked automatically by the VM. |
|
520 |
* @param recipe Concatenation recipe, described above. |
|
521 |
* @param constants A vararg parameter representing the constants passed to |
|
522 |
* the linkage method. |
|
523 |
* @return a CallSite whose target can be used to perform String |
|
524 |
* concatenation, with dynamic concatenation arguments described by the given |
|
525 |
* {@code concatType}. |
|
526 |
* @throws StringConcatException If any of the linkage invariants described |
|
527 |
* here are violated. |
|
528 |
* @throws NullPointerException If any of the incoming arguments is null, or |
|
529 |
* any constant in {@code recipe} is null. |
|
530 |
* This will never happen when a bootstrap method |
|
531 |
* is called with invokedynamic. |
|
532 |
* @apiNote Code generators have three distinct ways to process a constant |
|
533 |
* string operand S in a string concatenation expression. First, S can be |
|
534 |
* materialized as a reference (using ldc) and passed as an ordinary argument |
|
535 |
* (recipe '\1'). Or, S can be stored in the constant pool and passed as a |
|
536 |
* constant (recipe '\2') . Finally, if S contains neither of the recipe |
|
537 |
* tag characters ('\1', '\2') then S can be interpolated into the recipe |
|
538 |
* itself, causing its characters to be inserted into the result. |
|
539 |
* |
|
540 |
* @jls 5.1.11 String Conversion |
|
541 |
* @jls 15.18.1 String Concatenation Operator + |
|
542 |
*/ |
|
543 |
public static CallSite makeConcatWithConstants(MethodHandles.Lookup lookup, |
|
544 |
String name, |
|
545 |
MethodType concatType, |
|
546 |
String recipe, |
|
547 |
Object... constants) throws StringConcatException { |
|
548 |
if (DEBUG) { |
|
549 |
System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType + ", {" + recipe + "}, " + Arrays.toString(constants)); |
|
550 |
} |
|
551 |
||
552 |
return doStringConcat(lookup, name, concatType, false, recipe, constants); |
|
553 |
} |
|
554 |
||
555 |
private static CallSite doStringConcat(MethodHandles.Lookup lookup, |
|
556 |
String name, |
|
557 |
MethodType concatType, |
|
558 |
boolean generateRecipe, |
|
559 |
String recipe, |
|
560 |
Object... constants) throws StringConcatException { |
|
561 |
Objects.requireNonNull(lookup, "Lookup is null"); |
|
562 |
Objects.requireNonNull(name, "Name is null"); |
|
563 |
Objects.requireNonNull(concatType, "Concat type is null"); |
|
564 |
Objects.requireNonNull(constants, "Constants are null"); |
|
565 |
||
566 |
for (Object o : constants) { |
|
567 |
Objects.requireNonNull(o, "Cannot accept null constants"); |
|
568 |
} |
|
569 |
||
35776
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
570 |
if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) { |
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
571 |
throw new StringConcatException(String.format( |
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
572 |
"Invalid caller: %s", |
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
573 |
lookup.lookupClass().getName())); |
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
574 |
} |
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
575 |
|
35388 | 576 |
int cCount = 0; |
577 |
int oCount = 0; |
|
578 |
if (generateRecipe) { |
|
579 |
// Mock the recipe to reuse the concat generator code |
|
580 |
char[] value = new char[concatType.parameterCount()]; |
|
581 |
Arrays.fill(value, TAG_ARG); |
|
582 |
recipe = new String(value); |
|
583 |
oCount = concatType.parameterCount(); |
|
584 |
} else { |
|
585 |
Objects.requireNonNull(recipe, "Recipe is null"); |
|
586 |
||
587 |
for (int i = 0; i < recipe.length(); i++) { |
|
588 |
char c = recipe.charAt(i); |
|
589 |
if (c == TAG_CONST) cCount++; |
|
590 |
if (c == TAG_ARG) oCount++; |
|
591 |
} |
|
592 |
} |
|
593 |
||
594 |
if (oCount != concatType.parameterCount()) { |
|
595 |
throw new StringConcatException( |
|
596 |
"Mismatched number of concat arguments: recipe wants " + |
|
597 |
oCount + |
|
598 |
" arguments, but signature provides " + |
|
599 |
concatType.parameterCount()); |
|
600 |
} |
|
601 |
||
602 |
if (cCount != constants.length) { |
|
603 |
throw new StringConcatException( |
|
604 |
"Mismatched number of concat constants: recipe wants " + |
|
605 |
cCount + |
|
606 |
" constants, but only " + |
|
607 |
constants.length + |
|
608 |
" are passed"); |
|
609 |
} |
|
610 |
||
611 |
if (!concatType.returnType().isAssignableFrom(String.class)) { |
|
612 |
throw new StringConcatException( |
|
613 |
"The return type should be compatible with String, but it is " + |
|
614 |
concatType.returnType()); |
|
615 |
} |
|
616 |
||
617 |
if (concatType.parameterCount() > MAX_INDY_CONCAT_ARG_SLOTS) { |
|
618 |
throw new StringConcatException("Too many concat argument slots: " + |
|
619 |
concatType.parameterCount() + |
|
620 |
", can only accept " + |
|
621 |
MAX_INDY_CONCAT_ARG_SLOTS); |
|
622 |
} |
|
623 |
||
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
624 |
String className = getClassName(lookup.lookupClass()); |
35388 | 625 |
MethodType mt = adaptType(concatType); |
626 |
Recipe rec = new Recipe(recipe, constants); |
|
627 |
||
628 |
MethodHandle mh; |
|
629 |
if (CACHE_ENABLE) { |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
630 |
Key key = new Key(className, mt, rec); |
35388 | 631 |
mh = CACHE.get(key); |
632 |
if (mh == null) { |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
633 |
mh = generate(lookup, className, mt, rec); |
35388 | 634 |
CACHE.put(key, mh); |
635 |
} |
|
636 |
} else { |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
637 |
mh = generate(lookup, className, mt, rec); |
35388 | 638 |
} |
639 |
return new ConstantCallSite(mh.asType(concatType)); |
|
640 |
} |
|
641 |
||
642 |
/** |
|
643 |
* Adapt method type to an API we are going to use. |
|
644 |
* |
|
645 |
* This strips the concrete classes from the signatures, thus preventing |
|
646 |
* class leakage when we cache the concatenation stubs. |
|
647 |
* |
|
648 |
* @param args actual argument types |
|
649 |
* @return argument types the strategy is going to use |
|
650 |
*/ |
|
651 |
private static MethodType adaptType(MethodType args) { |
|
652 |
Class<?>[] ptypes = args.parameterArray(); |
|
653 |
boolean changed = false; |
|
654 |
for (int i = 0; i < ptypes.length; i++) { |
|
655 |
Class<?> ptype = ptypes[i]; |
|
656 |
if (!ptype.isPrimitive() && |
|
657 |
ptype != String.class && |
|
658 |
ptype != Object.class) { // truncate to Object |
|
659 |
ptypes[i] = Object.class; |
|
660 |
changed = true; |
|
661 |
} |
|
662 |
// else other primitives or String or Object (unchanged) |
|
663 |
} |
|
664 |
return changed |
|
665 |
? MethodType.methodType(args.returnType(), ptypes) |
|
666 |
: args; |
|
667 |
} |
|
668 |
||
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
669 |
private static String getClassName(Class<?> hostClass) throws StringConcatException { |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
670 |
/* |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
671 |
When cache is enabled, we want to cache as much as we can. |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
672 |
|
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
673 |
However, there are two peculiarities: |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
674 |
|
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
675 |
a) The generated class should stay within the same package as the |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
676 |
host class, to allow Unsafe.defineAnonymousClass access controls |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
677 |
to work properly. JDK may choose to fail with IllegalAccessException |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
678 |
when accessing a VM anonymous class with non-privileged callers, |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
679 |
see JDK-8058575. |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
680 |
|
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
681 |
b) If we mark the stub with some prefix, say, derived from the package |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
682 |
name because of (a), we can technically use that stub in other packages. |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
683 |
But the call stack traces would be extremely puzzling to unsuspecting users |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
684 |
and profiling tools: whatever stub wins the race, would be linked in all |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
685 |
similar callsites. |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
686 |
|
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
687 |
Therefore, we set the class prefix to match the host class package, and use |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
688 |
the prefix as the cache key too. This only affects BC_* strategies, and only when |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
689 |
cache is enabled. |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
690 |
*/ |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
691 |
|
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
692 |
switch (STRATEGY) { |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
693 |
case BC_SB: |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
694 |
case BC_SB_SIZED: |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
695 |
case BC_SB_SIZED_EXACT: { |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
696 |
if (CACHE_ENABLE) { |
36511 | 697 |
String pkgName = hostClass.getPackageName(); |
698 |
return (pkgName != null && !pkgName.isEmpty() ? pkgName.replace('.', '/') + "/" : "") + "Stubs$$StringConcat"; |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
699 |
} else { |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
700 |
return hostClass.getName().replace('.', '/') + "$$StringConcat"; |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
701 |
} |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
702 |
} |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
703 |
case MH_SB_SIZED: |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
704 |
case MH_SB_SIZED_EXACT: |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
705 |
case MH_INLINE_SIZED_EXACT: |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
706 |
// MethodHandle strategies do not need a class name. |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
707 |
return ""; |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
708 |
default: |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
709 |
throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented"); |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
710 |
} |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
711 |
} |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
712 |
|
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
713 |
private static MethodHandle generate(Lookup lookup, String className, MethodType mt, Recipe recipe) throws StringConcatException { |
35388 | 714 |
try { |
715 |
switch (STRATEGY) { |
|
716 |
case BC_SB: |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
717 |
return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.DEFAULT); |
35388 | 718 |
case BC_SB_SIZED: |
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
719 |
return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED); |
35388 | 720 |
case BC_SB_SIZED_EXACT: |
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
721 |
return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED_EXACT); |
35388 | 722 |
case MH_SB_SIZED: |
723 |
return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED); |
|
724 |
case MH_SB_SIZED_EXACT: |
|
725 |
return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED_EXACT); |
|
726 |
case MH_INLINE_SIZED_EXACT: |
|
727 |
return MethodHandleInlineCopyStrategy.generate(mt, recipe); |
|
728 |
default: |
|
729 |
throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented"); |
|
730 |
} |
|
731 |
} catch (Throwable t) { |
|
732 |
throw new StringConcatException("Generator failed", t); |
|
733 |
} |
|
734 |
} |
|
735 |
||
736 |
private enum Mode { |
|
737 |
DEFAULT(false, false), |
|
738 |
SIZED(true, false), |
|
739 |
SIZED_EXACT(true, true); |
|
740 |
||
741 |
private final boolean sized; |
|
742 |
private final boolean exact; |
|
743 |
||
744 |
Mode(boolean sized, boolean exact) { |
|
745 |
this.sized = sized; |
|
746 |
this.exact = exact; |
|
747 |
} |
|
748 |
||
749 |
boolean isSized() { |
|
750 |
return sized; |
|
751 |
} |
|
752 |
||
753 |
boolean isExact() { |
|
754 |
return exact; |
|
755 |
} |
|
756 |
} |
|
757 |
||
758 |
/** |
|
759 |
* Bytecode StringBuilder strategy. |
|
760 |
* |
|
761 |
* <p>This strategy operates in three modes, gated by {@link Mode}. |
|
762 |
* |
|
763 |
* <p><b>{@link Strategy#BC_SB}: "bytecode StringBuilder".</b> |
|
764 |
* |
|
765 |
* <p>This strategy spins up the bytecode that has the same StringBuilder |
|
766 |
* chain javac would otherwise emit. This strategy uses only the public API, |
|
767 |
* and comes as the baseline for the current JDK behavior. On other words, |
|
768 |
* this strategy moves the javac generated bytecode to runtime. The |
|
769 |
* generated bytecode is loaded via Unsafe.defineAnonymousClass, but with |
|
770 |
* the caller class coming from the BSM -- in other words, the protection |
|
771 |
* guarantees are inherited from the method where invokedynamic was |
|
772 |
* originally called. This means, among other things, that the bytecode is |
|
773 |
* verified for all non-JDK uses. |
|
774 |
* |
|
775 |
* <p><b>{@link Strategy#BC_SB_SIZED}: "bytecode StringBuilder, but |
|
776 |
* sized".</b> |
|
777 |
* |
|
778 |
* <p>This strategy acts similarly to {@link Strategy#BC_SB}, but it also |
|
779 |
* tries to guess the capacity required for StringBuilder to accept all |
|
780 |
* arguments without resizing. This strategy only makes an educated guess: |
|
781 |
* it only guesses the space required for known types (e.g. primitives and |
|
782 |
* Strings), but does not otherwise convert arguments. Therefore, the |
|
783 |
* capacity estimate may be wrong, and StringBuilder may have to |
|
784 |
* transparently resize or trim when doing the actual concatenation. While |
|
785 |
* this does not constitute a correctness issue (in the end, that what BC_SB |
|
786 |
* has to do anyway), this does pose a potential performance problem. |
|
787 |
* |
|
788 |
* <p><b>{@link Strategy#BC_SB_SIZED_EXACT}: "bytecode StringBuilder, but |
|
789 |
* sized exactly".</b> |
|
790 |
* |
|
791 |
* <p>This strategy improves on @link Strategy#BC_SB_SIZED}, by first |
|
792 |
* converting all arguments to String in order to get the exact capacity |
|
793 |
* StringBuilder should have. The conversion is done via the public |
|
794 |
* String.valueOf and/or Object.toString methods, and does not touch any |
|
795 |
* private String API. |
|
796 |
*/ |
|
797 |
private static final class BytecodeStringBuilderStrategy { |
|
798 |
static final Unsafe UNSAFE = Unsafe.getUnsafe(); |
|
799 |
static final int CLASSFILE_VERSION = 52; |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
800 |
static final String METHOD_NAME = "concat"; |
35388 | 801 |
|
802 |
private BytecodeStringBuilderStrategy() { |
|
803 |
// no instantiation |
|
804 |
} |
|
805 |
||
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
806 |
private static MethodHandle generate(Lookup lookup, String className, MethodType args, Recipe recipe, Mode mode) throws Exception { |
35388 | 807 |
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); |
808 |
||
809 |
cw.visit(CLASSFILE_VERSION, |
|
810 |
ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC, |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
811 |
className, // Unsafe.defineAnonymousClass would append an unique ID |
35388 | 812 |
null, |
813 |
"java/lang/Object", |
|
814 |
null |
|
815 |
); |
|
816 |
||
817 |
MethodVisitor mv = cw.visitMethod( |
|
818 |
ACC_PUBLIC + ACC_STATIC + ACC_FINAL, |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
819 |
METHOD_NAME, |
35388 | 820 |
args.toMethodDescriptorString(), |
821 |
null, |
|
822 |
null); |
|
823 |
||
824 |
mv.visitAnnotation("Ljdk/internal/vm/annotation/ForceInline;", true); |
|
825 |
mv.visitCode(); |
|
826 |
||
827 |
Class<?>[] arr = args.parameterArray(); |
|
828 |
boolean[] guaranteedNonNull = new boolean[arr.length]; |
|
829 |
||
830 |
if (mode.isExact()) { |
|
831 |
/* |
|
832 |
In exact mode, we need to convert all arguments to their String representations, |
|
833 |
as this allows to compute their String sizes exactly. We cannot use private |
|
834 |
methods for primitives in here, therefore we need to convert those as well. |
|
835 |
||
836 |
We also record what arguments are guaranteed to be non-null as the result |
|
837 |
of the conversion. String.valueOf does the null checks for us. The only |
|
838 |
corner case to take care of is String.valueOf(Object) returning null itself. |
|
839 |
||
840 |
Also, if any conversion happened, then the slot indices in the incoming |
|
841 |
arguments are not equal to the final local maps. The only case this may break |
|
842 |
is when converting 2-slot long/double argument to 1-slot String. Therefore, |
|
843 |
we get away with tracking modified offset, since no conversion can overwrite |
|
844 |
the upcoming the argument. |
|
845 |
*/ |
|
846 |
||
847 |
int off = 0; |
|
848 |
int modOff = 0; |
|
849 |
for (int c = 0; c < arr.length; c++) { |
|
850 |
Class<?> cl = arr[c]; |
|
851 |
if (cl == String.class) { |
|
852 |
if (off != modOff) { |
|
853 |
mv.visitIntInsn(getLoadOpcode(cl), off); |
|
854 |
mv.visitIntInsn(ASTORE, modOff); |
|
855 |
} |
|
856 |
} else { |
|
857 |
mv.visitIntInsn(getLoadOpcode(cl), off); |
|
858 |
mv.visitMethodInsn( |
|
859 |
INVOKESTATIC, |
|
860 |
"java/lang/String", |
|
861 |
"valueOf", |
|
862 |
getStringValueOfDesc(cl), |
|
863 |
false |
|
864 |
); |
|
865 |
mv.visitIntInsn(ASTORE, modOff); |
|
866 |
arr[c] = String.class; |
|
867 |
guaranteedNonNull[c] = cl.isPrimitive(); |
|
868 |
} |
|
869 |
off += getParameterSize(cl); |
|
870 |
modOff += getParameterSize(String.class); |
|
871 |
} |
|
872 |
} |
|
873 |
||
874 |
if (mode.isSized()) { |
|
875 |
/* |
|
876 |
When operating in sized mode (this includes exact mode), it makes sense to make |
|
877 |
StringBuilder append chains look familiar to OptimizeStringConcat. For that, we |
|
878 |
need to do null-checks early, not make the append chain shape simpler. |
|
879 |
*/ |
|
880 |
||
881 |
int off = 0; |
|
882 |
for (RecipeElement el : recipe.getElements()) { |
|
883 |
switch (el.getTag()) { |
|
884 |
case CONST: { |
|
885 |
// Guaranteed non-null, no null check required. |
|
886 |
break; |
|
887 |
} |
|
888 |
case ARG: { |
|
889 |
// Null-checks are needed only for String arguments, and when a previous stage |
|
890 |
// did not do implicit null-checks. If a String is null, we eagerly replace it |
|
891 |
// with "null" constant. Note, we omit Objects here, because we don't call |
|
892 |
// .length() on them down below. |
|
893 |
int ac = el.getArgPos(); |
|
894 |
Class<?> cl = arr[ac]; |
|
895 |
if (cl == String.class && !guaranteedNonNull[ac]) { |
|
896 |
Label l0 = new Label(); |
|
897 |
mv.visitIntInsn(ALOAD, off); |
|
898 |
mv.visitJumpInsn(IFNONNULL, l0); |
|
899 |
mv.visitLdcInsn("null"); |
|
900 |
mv.visitIntInsn(ASTORE, off); |
|
901 |
mv.visitLabel(l0); |
|
902 |
} |
|
903 |
off += getParameterSize(cl); |
|
904 |
break; |
|
905 |
} |
|
906 |
default: |
|
907 |
throw new StringConcatException("Unhandled tag: " + el.getTag()); |
|
908 |
} |
|
909 |
} |
|
910 |
} |
|
911 |
||
912 |
// Prepare StringBuilder instance |
|
913 |
mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); |
|
914 |
mv.visitInsn(DUP); |
|
915 |
||
916 |
if (mode.isSized()) { |
|
917 |
/* |
|
918 |
Sized mode requires us to walk through the arguments, and estimate the final length. |
|
919 |
In exact mode, this will operate on Strings only. This code would accumulate the |
|
920 |
final length on stack. |
|
921 |
*/ |
|
922 |
int len = 0; |
|
923 |
int off = 0; |
|
924 |
||
925 |
mv.visitInsn(ICONST_0); |
|
926 |
||
927 |
for (RecipeElement el : recipe.getElements()) { |
|
928 |
switch (el.getTag()) { |
|
929 |
case CONST: { |
|
930 |
Object cnst = el.getValue(); |
|
931 |
len += cnst.toString().length(); |
|
932 |
break; |
|
933 |
} |
|
934 |
case ARG: { |
|
935 |
/* |
|
936 |
If an argument is String, then we can call .length() on it. Sized/Exact modes have |
|
937 |
converted arguments for us. If an argument is primitive, we can provide a guess |
|
938 |
for its String representation size. |
|
939 |
*/ |
|
940 |
Class<?> cl = arr[el.getArgPos()]; |
|
941 |
if (cl == String.class) { |
|
942 |
mv.visitIntInsn(ALOAD, off); |
|
943 |
mv.visitMethodInsn( |
|
944 |
INVOKEVIRTUAL, |
|
945 |
"java/lang/String", |
|
946 |
"length", |
|
947 |
"()I", |
|
948 |
false |
|
949 |
); |
|
950 |
mv.visitInsn(IADD); |
|
951 |
} else if (cl.isPrimitive()) { |
|
952 |
len += estimateSize(cl); |
|
953 |
} |
|
954 |
off += getParameterSize(cl); |
|
955 |
break; |
|
956 |
} |
|
957 |
default: |
|
958 |
throw new StringConcatException("Unhandled tag: " + el.getTag()); |
|
959 |
} |
|
960 |
} |
|
961 |
||
962 |
// Constants have non-zero length, mix in |
|
963 |
if (len > 0) { |
|
964 |
iconst(mv, len); |
|
965 |
mv.visitInsn(IADD); |
|
966 |
} |
|
967 |
||
968 |
mv.visitMethodInsn( |
|
969 |
INVOKESPECIAL, |
|
970 |
"java/lang/StringBuilder", |
|
971 |
"<init>", |
|
972 |
"(I)V", |
|
973 |
false |
|
974 |
); |
|
975 |
} else { |
|
976 |
mv.visitMethodInsn( |
|
977 |
INVOKESPECIAL, |
|
978 |
"java/lang/StringBuilder", |
|
979 |
"<init>", |
|
980 |
"()V", |
|
981 |
false |
|
982 |
); |
|
983 |
} |
|
984 |
||
985 |
// At this point, we have a blank StringBuilder on stack, fill it in with .append calls. |
|
986 |
{ |
|
987 |
int off = 0; |
|
988 |
for (RecipeElement el : recipe.getElements()) { |
|
989 |
String desc; |
|
990 |
switch (el.getTag()) { |
|
991 |
case CONST: { |
|
992 |
Object cnst = el.getValue(); |
|
993 |
mv.visitLdcInsn(cnst); |
|
994 |
desc = getSBAppendDesc(cnst.getClass()); |
|
995 |
break; |
|
996 |
} |
|
997 |
case ARG: { |
|
998 |
Class<?> cl = arr[el.getArgPos()]; |
|
999 |
mv.visitVarInsn(getLoadOpcode(cl), off); |
|
1000 |
off += getParameterSize(cl); |
|
1001 |
desc = getSBAppendDesc(cl); |
|
1002 |
break; |
|
1003 |
} |
|
1004 |
default: |
|
1005 |
throw new StringConcatException("Unhandled tag: " + el.getTag()); |
|
1006 |
} |
|
1007 |
mv.visitMethodInsn( |
|
1008 |
INVOKEVIRTUAL, |
|
1009 |
"java/lang/StringBuilder", |
|
1010 |
"append", |
|
1011 |
desc, |
|
1012 |
false |
|
1013 |
); |
|
1014 |
} |
|
1015 |
} |
|
1016 |
||
1017 |
if (DEBUG && mode.isExact()) { |
|
1018 |
/* |
|
1019 |
Exactness checks compare the final StringBuilder.capacity() with a resulting |
|
1020 |
String.length(). If these values disagree, that means StringBuilder had to perform |
|
1021 |
storage trimming, which defeats the purpose of exact strategies. |
|
1022 |
*/ |
|
1023 |
||
35639
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1024 |
/* |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1025 |
The logic for this check is as follows: |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1026 |
|
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1027 |
Stack before: Op: |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1028 |
(SB) dup, dup |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1029 |
(SB, SB, SB) capacity() |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1030 |
(int, SB, SB) swap |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1031 |
(SB, int, SB) toString() |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1032 |
(S, int, SB) length() |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1033 |
(int, int, SB) if_icmpeq |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1034 |
(SB) <end> |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1035 |
|
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1036 |
Note that it leaves the same StringBuilder on exit, like the one on enter. |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1037 |
*/ |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1038 |
|
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1039 |
mv.visitInsn(DUP); |
35388 | 1040 |
mv.visitInsn(DUP); |
1041 |
||
1042 |
mv.visitMethodInsn( |
|
1043 |
INVOKEVIRTUAL, |
|
1044 |
"java/lang/StringBuilder", |
|
1045 |
"capacity", |
|
1046 |
"()I", |
|
1047 |
false |
|
1048 |
); |
|
1049 |
||
35639
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1050 |
mv.visitInsn(SWAP); |
35388 | 1051 |
|
1052 |
mv.visitMethodInsn( |
|
1053 |
INVOKEVIRTUAL, |
|
1054 |
"java/lang/StringBuilder", |
|
1055 |
"toString", |
|
1056 |
"()Ljava/lang/String;", |
|
1057 |
false |
|
1058 |
); |
|
1059 |
||
1060 |
mv.visitMethodInsn( |
|
1061 |
INVOKEVIRTUAL, |
|
1062 |
"java/lang/String", |
|
1063 |
"length", |
|
1064 |
"()I", |
|
1065 |
false |
|
1066 |
); |
|
1067 |
||
1068 |
Label l0 = new Label(); |
|
1069 |
mv.visitJumpInsn(IF_ICMPEQ, l0); |
|
1070 |
||
1071 |
mv.visitTypeInsn(NEW, "java/lang/AssertionError"); |
|
1072 |
mv.visitInsn(DUP); |
|
1073 |
mv.visitLdcInsn("Failed exactness check"); |
|
1074 |
mv.visitMethodInsn(INVOKESPECIAL, |
|
1075 |
"java/lang/AssertionError", |
|
1076 |
"<init>", |
|
1077 |
"(Ljava/lang/Object;)V", |
|
1078 |
false); |
|
1079 |
mv.visitInsn(ATHROW); |
|
1080 |
||
1081 |
mv.visitLabel(l0); |
|
1082 |
} |
|
1083 |
||
35639
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1084 |
mv.visitMethodInsn( |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1085 |
INVOKEVIRTUAL, |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1086 |
"java/lang/StringBuilder", |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1087 |
"toString", |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1088 |
"()Ljava/lang/String;", |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1089 |
false |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1090 |
); |
f34e7e8b4eac
8148787: StringConcatFactory exactness check produces bad bytecode when a non-arg concat is requested
shade
parents:
35401
diff
changeset
|
1091 |
|
35388 | 1092 |
mv.visitInsn(ARETURN); |
1093 |
||
1094 |
mv.visitMaxs(-1, -1); |
|
1095 |
mv.visitEnd(); |
|
1096 |
cw.visitEnd(); |
|
1097 |
||
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1098 |
byte[] classBytes = cw.toByteArray(); |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1099 |
try { |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1100 |
Class<?> hostClass = lookup.lookupClass(); |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1101 |
Class<?> innerClass = UNSAFE.defineAnonymousClass(hostClass, classBytes, null); |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1102 |
UNSAFE.ensureClassInitialized(innerClass); |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1103 |
dumpIfEnabled(innerClass.getName(), classBytes); |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1104 |
return Lookup.IMPL_LOOKUP.findStatic(innerClass, METHOD_NAME, args); |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1105 |
} catch (Throwable e) { |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1106 |
dumpIfEnabled(className + "$$FAILED", classBytes); |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1107 |
throw new StringConcatException("Error while spinning the class", e); |
35776
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
1108 |
} |
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1109 |
} |
35776
dd5df83fcc0d
8149459: StringConcatFactory should be synced up with LambdaMetafactory
shade
parents:
35700
diff
changeset
|
1110 |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1111 |
private static void dumpIfEnabled(String name, byte[] bytes) { |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1112 |
if (DUMPER != null) { |
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1113 |
DUMPER.dumpClass(name, bytes); |
35388 | 1114 |
} |
1115 |
} |
|
1116 |
||
1117 |
private static String getSBAppendDesc(Class<?> cl) { |
|
1118 |
if (cl.isPrimitive()) { |
|
1119 |
if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) { |
|
1120 |
return "(I)Ljava/lang/StringBuilder;"; |
|
1121 |
} else if (cl == Boolean.TYPE) { |
|
1122 |
return "(Z)Ljava/lang/StringBuilder;"; |
|
1123 |
} else if (cl == Character.TYPE) { |
|
1124 |
return "(C)Ljava/lang/StringBuilder;"; |
|
1125 |
} else if (cl == Double.TYPE) { |
|
1126 |
return "(D)Ljava/lang/StringBuilder;"; |
|
1127 |
} else if (cl == Float.TYPE) { |
|
1128 |
return "(F)Ljava/lang/StringBuilder;"; |
|
1129 |
} else if (cl == Long.TYPE) { |
|
1130 |
return "(J)Ljava/lang/StringBuilder;"; |
|
1131 |
} else { |
|
1132 |
throw new IllegalStateException("Unhandled primitive StringBuilder.append: " + cl); |
|
1133 |
} |
|
1134 |
} else if (cl == String.class) { |
|
1135 |
return "(Ljava/lang/String;)Ljava/lang/StringBuilder;"; |
|
1136 |
} else { |
|
1137 |
return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;"; |
|
1138 |
} |
|
1139 |
} |
|
1140 |
||
1141 |
private static String getStringValueOfDesc(Class<?> cl) { |
|
1142 |
if (cl.isPrimitive()) { |
|
1143 |
if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) { |
|
1144 |
return "(I)Ljava/lang/String;"; |
|
1145 |
} else if (cl == Boolean.TYPE) { |
|
1146 |
return "(Z)Ljava/lang/String;"; |
|
1147 |
} else if (cl == Character.TYPE) { |
|
1148 |
return "(C)Ljava/lang/String;"; |
|
1149 |
} else if (cl == Double.TYPE) { |
|
1150 |
return "(D)Ljava/lang/String;"; |
|
1151 |
} else if (cl == Float.TYPE) { |
|
1152 |
return "(F)Ljava/lang/String;"; |
|
1153 |
} else if (cl == Long.TYPE) { |
|
1154 |
return "(J)Ljava/lang/String;"; |
|
1155 |
} else { |
|
1156 |
throw new IllegalStateException("Unhandled String.valueOf: " + cl); |
|
1157 |
} |
|
1158 |
} else if (cl == String.class) { |
|
1159 |
return "(Ljava/lang/String;)Ljava/lang/String;"; |
|
1160 |
} else { |
|
1161 |
return "(Ljava/lang/Object;)Ljava/lang/String;"; |
|
1162 |
} |
|
1163 |
} |
|
1164 |
||
1165 |
/** |
|
1166 |
* The following method is copied from |
|
1167 |
* org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small |
|
1168 |
* and fast Java bytecode manipulation framework. |
|
1169 |
* Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved. |
|
1170 |
*/ |
|
1171 |
private static void iconst(MethodVisitor mv, final int cst) { |
|
1172 |
if (cst >= -1 && cst <= 5) { |
|
1173 |
mv.visitInsn(Opcodes.ICONST_0 + cst); |
|
1174 |
} else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { |
|
1175 |
mv.visitIntInsn(Opcodes.BIPUSH, cst); |
|
1176 |
} else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { |
|
1177 |
mv.visitIntInsn(Opcodes.SIPUSH, cst); |
|
1178 |
} else { |
|
1179 |
mv.visitLdcInsn(cst); |
|
1180 |
} |
|
1181 |
} |
|
1182 |
||
1183 |
private static int getLoadOpcode(Class<?> c) { |
|
1184 |
if (c == Void.TYPE) { |
|
1185 |
throw new InternalError("Unexpected void type of load opcode"); |
|
1186 |
} |
|
1187 |
return ILOAD + getOpcodeOffset(c); |
|
1188 |
} |
|
1189 |
||
1190 |
private static int getOpcodeOffset(Class<?> c) { |
|
1191 |
if (c.isPrimitive()) { |
|
1192 |
if (c == Long.TYPE) { |
|
1193 |
return 1; |
|
1194 |
} else if (c == Float.TYPE) { |
|
1195 |
return 2; |
|
1196 |
} else if (c == Double.TYPE) { |
|
1197 |
return 3; |
|
1198 |
} |
|
1199 |
return 0; |
|
1200 |
} else { |
|
1201 |
return 4; |
|
1202 |
} |
|
1203 |
} |
|
1204 |
||
1205 |
private static int getParameterSize(Class<?> c) { |
|
1206 |
if (c == Void.TYPE) { |
|
1207 |
return 0; |
|
1208 |
} else if (c == Long.TYPE || c == Double.TYPE) { |
|
1209 |
return 2; |
|
1210 |
} |
|
1211 |
return 1; |
|
1212 |
} |
|
1213 |
} |
|
1214 |
||
1215 |
/** |
|
1216 |
* MethodHandle StringBuilder strategy. |
|
1217 |
* |
|
1218 |
* <p>This strategy operates in two modes, gated by {@link Mode}. |
|
1219 |
* |
|
1220 |
* <p><b>{@link Strategy#MH_SB_SIZED}: "MethodHandles StringBuilder, |
|
1221 |
* sized".</b> |
|
1222 |
* |
|
1223 |
* <p>This strategy avoids spinning up the bytecode by building the |
|
1224 |
* computation on MethodHandle combinators. The computation is built with |
|
1225 |
* public MethodHandle APIs, resolved from a public Lookup sequence, and |
|
1226 |
* ends up calling the public StringBuilder API. Therefore, this strategy |
|
1227 |
* does not use any private API at all, even the Unsafe.defineAnonymousClass, |
|
1228 |
* since everything is handled under cover by java.lang.invoke APIs. |
|
1229 |
* |
|
1230 |
* <p><b>{@link Strategy#MH_SB_SIZED_EXACT}: "MethodHandles StringBuilder, |
|
1231 |
* sized exactly".</b> |
|
1232 |
* |
|
1233 |
* <p>This strategy improves on @link Strategy#MH_SB_SIZED}, by first |
|
1234 |
* converting all arguments to String in order to get the exact capacity |
|
1235 |
* StringBuilder should have. The conversion is done via the public |
|
1236 |
* String.valueOf and/or Object.toString methods, and does not touch any |
|
1237 |
* private String API. |
|
1238 |
*/ |
|
1239 |
private static final class MethodHandleStringBuilderStrategy { |
|
1240 |
||
1241 |
private MethodHandleStringBuilderStrategy() { |
|
1242 |
// no instantiation |
|
1243 |
} |
|
1244 |
||
1245 |
private static MethodHandle generate(MethodType mt, Recipe recipe, Mode mode) throws Exception { |
|
1246 |
int pc = mt.parameterCount(); |
|
1247 |
||
1248 |
Class<?>[] ptypes = mt.parameterArray(); |
|
1249 |
MethodHandle[] filters = new MethodHandle[ptypes.length]; |
|
1250 |
for (int i = 0; i < ptypes.length; i++) { |
|
1251 |
MethodHandle filter; |
|
1252 |
switch (mode) { |
|
1253 |
case SIZED: |
|
1254 |
// In sized mode, we convert all references and floats/doubles |
|
1255 |
// to String: there is no specialization for different |
|
1256 |
// classes in StringBuilder API, and it will convert to |
|
1257 |
// String internally anyhow. |
|
1258 |
filter = Stringifiers.forMost(ptypes[i]); |
|
1259 |
break; |
|
1260 |
case SIZED_EXACT: |
|
1261 |
// In exact mode, we convert everything to String: |
|
1262 |
// this helps to compute the storage exactly. |
|
1263 |
filter = Stringifiers.forAny(ptypes[i]); |
|
1264 |
break; |
|
1265 |
default: |
|
1266 |
throw new StringConcatException("Not supported"); |
|
1267 |
} |
|
1268 |
if (filter != null) { |
|
1269 |
filters[i] = filter; |
|
1270 |
ptypes[i] = filter.type().returnType(); |
|
1271 |
} |
|
1272 |
} |
|
1273 |
||
1274 |
List<Class<?>> ptypesList = Arrays.asList(ptypes); |
|
1275 |
MethodHandle[] lengthers = new MethodHandle[pc]; |
|
1276 |
||
1277 |
// Figure out lengths: constants' lengths can be deduced on the spot. |
|
1278 |
// All reference arguments were filtered to String in the combinators below, so we can |
|
1279 |
// call the usual String.length(). Primitive values string sizes can be estimated. |
|
1280 |
int initial = 0; |
|
1281 |
for (RecipeElement el : recipe.getElements()) { |
|
1282 |
switch (el.getTag()) { |
|
1283 |
case CONST: { |
|
1284 |
Object cnst = el.getValue(); |
|
1285 |
initial += cnst.toString().length(); |
|
1286 |
break; |
|
1287 |
} |
|
1288 |
case ARG: { |
|
1289 |
final int i = el.getArgPos(); |
|
1290 |
Class<?> type = ptypesList.get(i); |
|
1291 |
if (type.isPrimitive()) { |
|
1292 |
MethodHandle est = MethodHandles.constant(int.class, estimateSize(type)); |
|
1293 |
est = MethodHandles.dropArguments(est, 0, type); |
|
1294 |
lengthers[i] = est; |
|
1295 |
} else { |
|
1296 |
lengthers[i] = STRING_LENGTH; |
|
1297 |
} |
|
1298 |
break; |
|
1299 |
} |
|
1300 |
default: |
|
1301 |
throw new StringConcatException("Unhandled tag: " + el.getTag()); |
|
1302 |
} |
|
1303 |
} |
|
1304 |
||
1305 |
// Create (StringBuilder, <args>) shape for appending: |
|
1306 |
MethodHandle builder = MethodHandles.dropArguments(MethodHandles.identity(StringBuilder.class), 1, ptypesList); |
|
1307 |
||
1308 |
// Compose append calls. This is done in reverse because the application order is |
|
1309 |
// reverse as well. |
|
36737
67b2804be5ab
8152951: Avoid calculating the reverse of StringConcatFactory$Recipe elements
redestad
parents:
36668
diff
changeset
|
1310 |
List<RecipeElement> elements = recipe.getElements(); |
67b2804be5ab
8152951: Avoid calculating the reverse of StringConcatFactory$Recipe elements
redestad
parents:
36668
diff
changeset
|
1311 |
for (int i = elements.size() - 1; i >= 0; i--) { |
67b2804be5ab
8152951: Avoid calculating the reverse of StringConcatFactory$Recipe elements
redestad
parents:
36668
diff
changeset
|
1312 |
RecipeElement el = elements.get(i); |
35388 | 1313 |
MethodHandle appender; |
1314 |
switch (el.getTag()) { |
|
1315 |
case CONST: { |
|
1316 |
Object constant = el.getValue(); |
|
1317 |
MethodHandle mh = appender(adaptToStringBuilder(constant.getClass())); |
|
1318 |
appender = MethodHandles.insertArguments(mh, 1, constant); |
|
1319 |
break; |
|
1320 |
} |
|
1321 |
case ARG: { |
|
1322 |
int ac = el.getArgPos(); |
|
1323 |
appender = appender(ptypesList.get(ac)); |
|
1324 |
||
1325 |
// Insert dummy arguments to match the prefix in the signature. |
|
1326 |
// The actual appender argument will be the ac-ith argument. |
|
1327 |
if (ac != 0) { |
|
1328 |
appender = MethodHandles.dropArguments(appender, 1, ptypesList.subList(0, ac)); |
|
1329 |
} |
|
1330 |
break; |
|
1331 |
} |
|
1332 |
default: |
|
1333 |
throw new StringConcatException("Unhandled tag: " + el.getTag()); |
|
1334 |
} |
|
1335 |
builder = MethodHandles.foldArguments(builder, appender); |
|
1336 |
} |
|
1337 |
||
1338 |
// Build the sub-tree that adds the sizes and produces a StringBuilder: |
|
1339 |
||
1340 |
// a) Start with the reducer that accepts all arguments, plus one |
|
1341 |
// slot for the initial value. Inject the initial value right away. |
|
1342 |
// This produces (<ints>)int shape: |
|
1343 |
MethodHandle sum = getReducerFor(pc + 1); |
|
1344 |
MethodHandle adder = MethodHandles.insertArguments(sum, 0, initial); |
|
1345 |
||
1346 |
// b) Apply lengthers to transform arguments to lengths, producing (<args>)int |
|
1347 |
adder = MethodHandles.filterArguments(adder, 0, lengthers); |
|
1348 |
||
1349 |
// c) Instantiate StringBuilder (<args>)int -> (<args>)StringBuilder |
|
1350 |
MethodHandle newBuilder = MethodHandles.filterReturnValue(adder, NEW_STRING_BUILDER); |
|
1351 |
||
1352 |
// d) Fold in StringBuilder constructor, this produces (<args>)StringBuilder |
|
1353 |
MethodHandle mh = MethodHandles.foldArguments(builder, newBuilder); |
|
1354 |
||
1355 |
// Convert non-primitive arguments to Strings |
|
1356 |
mh = MethodHandles.filterArguments(mh, 0, filters); |
|
1357 |
||
1358 |
// Convert (<args>)StringBuilder to (<args>)String |
|
1359 |
if (DEBUG && mode.isExact()) { |
|
1360 |
mh = MethodHandles.filterReturnValue(mh, BUILDER_TO_STRING_CHECKED); |
|
1361 |
} else { |
|
1362 |
mh = MethodHandles.filterReturnValue(mh, BUILDER_TO_STRING); |
|
1363 |
} |
|
1364 |
||
1365 |
return mh; |
|
1366 |
} |
|
1367 |
||
1368 |
private static MethodHandle getReducerFor(int cnt) { |
|
1369 |
return SUMMERS.computeIfAbsent(cnt, SUMMER); |
|
1370 |
} |
|
1371 |
||
1372 |
private static MethodHandle appender(Class<?> appendType) { |
|
1373 |
MethodHandle appender = lookupVirtual(MethodHandles.publicLookup(), StringBuilder.class, "append", |
|
1374 |
StringBuilder.class, adaptToStringBuilder(appendType)); |
|
1375 |
||
1376 |
// appenders should return void, this would not modify the target signature during folding |
|
1377 |
MethodType nt = MethodType.methodType(void.class, StringBuilder.class, appendType); |
|
1378 |
return appender.asType(nt); |
|
1379 |
} |
|
1380 |
||
1381 |
private static String toStringChecked(StringBuilder sb) { |
|
1382 |
String s = sb.toString(); |
|
1383 |
if (s.length() != sb.capacity()) { |
|
1384 |
throw new AssertionError("Exactness check failed: result length = " + s.length() + ", buffer capacity = " + sb.capacity()); |
|
1385 |
} |
|
1386 |
return s; |
|
1387 |
} |
|
1388 |
||
1389 |
private static int sum(int v1, int v2) { |
|
1390 |
return v1 + v2; |
|
1391 |
} |
|
1392 |
||
1393 |
private static int sum(int v1, int v2, int v3) { |
|
1394 |
return v1 + v2 + v3; |
|
1395 |
} |
|
1396 |
||
1397 |
private static int sum(int v1, int v2, int v3, int v4) { |
|
1398 |
return v1 + v2 + v3 + v4; |
|
1399 |
} |
|
1400 |
||
1401 |
private static int sum(int v1, int v2, int v3, int v4, int v5) { |
|
1402 |
return v1 + v2 + v3 + v4 + v5; |
|
1403 |
} |
|
1404 |
||
1405 |
private static int sum(int v1, int v2, int v3, int v4, int v5, int v6) { |
|
1406 |
return v1 + v2 + v3 + v4 + v5 + v6; |
|
1407 |
} |
|
1408 |
||
1409 |
private static int sum(int v1, int v2, int v3, int v4, int v5, int v6, int v7) { |
|
1410 |
return v1 + v2 + v3 + v4 + v5 + v6 + v7; |
|
1411 |
} |
|
1412 |
||
1413 |
private static int sum(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8) { |
|
1414 |
return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8; |
|
1415 |
} |
|
1416 |
||
1417 |
private static int sum(int initial, int[] vs) { |
|
1418 |
int sum = initial; |
|
1419 |
for (int v : vs) { |
|
1420 |
sum += v; |
|
1421 |
} |
|
1422 |
return sum; |
|
1423 |
} |
|
1424 |
||
1425 |
private static final ConcurrentMap<Integer, MethodHandle> SUMMERS; |
|
1426 |
||
1427 |
// This one is deliberately non-lambdified to optimize startup time: |
|
1428 |
private static final Function<Integer, MethodHandle> SUMMER = new Function<Integer, MethodHandle>() { |
|
1429 |
@Override |
|
1430 |
public MethodHandle apply(Integer cnt) { |
|
1431 |
if (cnt == 1) { |
|
1432 |
return MethodHandles.identity(int.class); |
|
1433 |
} else if (cnt <= 8) { |
|
1434 |
// Variable-arity collectors are not as efficient as small-count methods, |
|
1435 |
// unroll some initial sizes. |
|
1436 |
Class<?>[] cls = new Class<?>[cnt]; |
|
1437 |
Arrays.fill(cls, int.class); |
|
1438 |
return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, cls); |
|
1439 |
} else { |
|
1440 |
return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, int.class, int[].class) |
|
1441 |
.asCollector(int[].class, cnt - 1); |
|
1442 |
} |
|
1443 |
} |
|
1444 |
}; |
|
1445 |
||
1446 |
private static final MethodHandle NEW_STRING_BUILDER, STRING_LENGTH, BUILDER_TO_STRING, BUILDER_TO_STRING_CHECKED; |
|
1447 |
||
1448 |
static { |
|
1449 |
SUMMERS = new ConcurrentHashMap<>(); |
|
1450 |
Lookup publicLookup = MethodHandles.publicLookup(); |
|
1451 |
NEW_STRING_BUILDER = lookupConstructor(publicLookup, StringBuilder.class, int.class); |
|
1452 |
STRING_LENGTH = lookupVirtual(publicLookup, String.class, "length", int.class); |
|
1453 |
BUILDER_TO_STRING = lookupVirtual(publicLookup, StringBuilder.class, "toString", String.class); |
|
1454 |
if (DEBUG) { |
|
1455 |
BUILDER_TO_STRING_CHECKED = lookupStatic(MethodHandles.Lookup.IMPL_LOOKUP, |
|
1456 |
MethodHandleStringBuilderStrategy.class, "toStringChecked", String.class, StringBuilder.class); |
|
1457 |
} else { |
|
1458 |
BUILDER_TO_STRING_CHECKED = null; |
|
1459 |
} |
|
1460 |
} |
|
1461 |
||
1462 |
} |
|
1463 |
||
1464 |
||
1465 |
/** |
|
1466 |
* <p><b>{@link Strategy#MH_INLINE_SIZED_EXACT}: "MethodHandles inline, |
|
1467 |
* sized exactly".</b> |
|
1468 |
* |
|
1469 |
* <p>This strategy replicates what StringBuilders are doing: it builds the |
|
1470 |
* byte[] array on its own and passes that byte[] array to String |
|
1471 |
* constructor. This strategy requires access to some private APIs in JDK, |
|
1472 |
* most notably, the read-only Integer/Long.stringSize methods that measure |
|
1473 |
* the character length of the integers, and the private String constructor |
|
1474 |
* that accepts byte[] arrays without copying. While this strategy assumes a |
|
1475 |
* particular implementation details for String, this opens the door for |
|
1476 |
* building a very optimal concatenation sequence. This is the only strategy |
|
1477 |
* that requires porting if there are private JDK changes occur. |
|
1478 |
*/ |
|
1479 |
private static final class MethodHandleInlineCopyStrategy { |
|
36668
ceb837301c53
8150463: StringConcat MH_INLINE_SIZED_EXACT should skip storage initialization
shade
parents:
36511
diff
changeset
|
1480 |
static final Unsafe UNSAFE = Unsafe.getUnsafe(); |
35388 | 1481 |
|
1482 |
private MethodHandleInlineCopyStrategy() { |
|
1483 |
// no instantiation |
|
1484 |
} |
|
1485 |
||
1486 |
static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable { |
|
1487 |
||
1488 |
// Create filters and obtain filtered parameter types. Filters would be used in the beginning |
|
1489 |
// to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings). |
|
1490 |
// The filtered argument type list is used all over in the combinators below. |
|
1491 |
Class<?>[] ptypes = mt.parameterArray(); |
|
1492 |
MethodHandle[] filters = null; |
|
1493 |
for (int i = 0; i < ptypes.length; i++) { |
|
1494 |
MethodHandle filter = Stringifiers.forMost(ptypes[i]); |
|
1495 |
if (filter != null) { |
|
1496 |
if (filters == null) { |
|
1497 |
filters = new MethodHandle[ptypes.length]; |
|
1498 |
} |
|
1499 |
filters[i] = filter; |
|
1500 |
ptypes[i] = filter.type().returnType(); |
|
1501 |
} |
|
1502 |
} |
|
1503 |
List<Class<?>> ptypesList = Arrays.asList(ptypes); |
|
1504 |
||
1505 |
// Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes" |
|
1506 |
// with the (int, byte[], byte)String in String helper. The combinators are assembled bottom-up, |
|
1507 |
// which makes the code arguably hard to read. |
|
1508 |
||
1509 |
// Drop all remaining parameter types, leave only helper arguments: |
|
1510 |
MethodHandle mh; |
|
1511 |
||
1512 |
mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes); |
|
1513 |
mh = MethodHandles.dropArguments(mh, 0, int.class); |
|
1514 |
||
36668
ceb837301c53
8150463: StringConcat MH_INLINE_SIZED_EXACT should skip storage initialization
shade
parents:
36511
diff
changeset
|
1515 |
// Safety: check that remaining index is zero -- that would mean the storage is completely |
ceb837301c53
8150463: StringConcat MH_INLINE_SIZED_EXACT should skip storage initialization
shade
parents:
36511
diff
changeset
|
1516 |
// overwritten, and no leakage of uninitialized data occurred. |
ceb837301c53
8150463: StringConcat MH_INLINE_SIZED_EXACT should skip storage initialization
shade
parents:
36511
diff
changeset
|
1517 |
mh = MethodHandles.filterArgument(mh, 0, CHECK_INDEX); |
35388 | 1518 |
|
1519 |
// Mix in prependers. This happens when (int, byte[], byte) = (index, storage, coder) is already |
|
1520 |
// known from the combinators below. We are assembling the string backwards, so "index" is the |
|
1521 |
// *ending* index. |
|
1522 |
for (RecipeElement el : recipe.getElements()) { |
|
1523 |
MethodHandle prepender; |
|
1524 |
switch (el.getTag()) { |
|
1525 |
case CONST: { |
|
1526 |
Object cnst = el.getValue(); |
|
1527 |
prepender = MethodHandles.insertArguments(prepender(cnst.getClass()), 3, cnst); |
|
1528 |
break; |
|
1529 |
} |
|
1530 |
case ARG: { |
|
1531 |
int pos = el.getArgPos(); |
|
1532 |
prepender = selectArgument(prepender(ptypesList.get(pos)), 3, ptypesList, pos); |
|
1533 |
break; |
|
1534 |
} |
|
1535 |
default: |
|
1536 |
throw new StringConcatException("Unhandled tag: " + el.getTag()); |
|
1537 |
} |
|
1538 |
||
1539 |
// Remove "old" index from arguments |
|
1540 |
mh = MethodHandles.dropArguments(mh, 1, int.class); |
|
1541 |
||
1542 |
// Do the prepend, and put "new" index at index 0 |
|
1543 |
mh = MethodHandles.foldArguments(mh, prepender); |
|
1544 |
} |
|
1545 |
||
1546 |
// Prepare the argument list for prepending. The tree below would instantiate |
|
1547 |
// the storage byte[] into argument 0, so we need to swap "storage" and "index". |
|
1548 |
// The index at this point equals to "size", and resides at argument 1. |
|
1549 |
{ |
|
1550 |
MethodType nmt = mh.type() |
|
1551 |
.changeParameterType(0, byte[].class) |
|
1552 |
.changeParameterType(1, int.class); |
|
1553 |
mh = MethodHandles.permuteArguments(mh, nmt, swap10(nmt.parameterCount())); |
|
1554 |
} |
|
1555 |
||
1556 |
// Fold in byte[] instantiation at argument 0. |
|
1557 |
MethodHandle combiner = MethodHandles.dropArguments(NEW_ARRAY, 2, ptypesList); |
|
1558 |
mh = MethodHandles.foldArguments(mh, combiner); |
|
1559 |
||
1560 |
// Start combining length and coder mixers. |
|
1561 |
// |
|
1562 |
// Length is easy: constant lengths can be computed on the spot, and all non-constant |
|
1563 |
// shapes have been either converted to Strings, or explicit methods for getting the |
|
1564 |
// string length out of primitives are provided. |
|
1565 |
// |
|
1566 |
// Coders are more interesting. Only Object, String and char arguments (and constants) |
|
1567 |
// can have non-Latin1 encoding. It is easier to blindly convert constants to String, |
|
1568 |
// and deduce the coder from there. Arguments would be either converted to Strings |
|
1569 |
// during the initial filtering, or handled by primitive specializations in CODER_MIXERS. |
|
1570 |
// |
|
1571 |
// The method handle shape after all length and coder mixers is: |
|
1572 |
// (int, byte, <args>)String = ("index", "coder", <args>) |
|
35700
b933119b8f84
8148869: StringConcatFactory MH_INLINE_SIZED_EXACT strategy does not work with -XX:-CompactStrings
shade
parents:
35639
diff
changeset
|
1573 |
byte initialCoder = INITIAL_CODER; |
35388 | 1574 |
int initialLen = 0; // initial length, in characters |
1575 |
for (RecipeElement el : recipe.getElements()) { |
|
1576 |
switch (el.getTag()) { |
|
1577 |
case CONST: { |
|
1578 |
Object constant = el.getValue(); |
|
1579 |
String s = constant.toString(); |
|
1580 |
initialCoder = (byte) coderMixer(String.class).invoke(initialCoder, s); |
|
1581 |
initialLen += s.length(); |
|
1582 |
break; |
|
1583 |
} |
|
1584 |
case ARG: { |
|
1585 |
int ac = el.getArgPos(); |
|
1586 |
||
1587 |
Class<?> argClass = ptypesList.get(ac); |
|
1588 |
MethodHandle lm = selectArgument(lengthMixer(argClass), 1, ptypesList, ac); |
|
1589 |
lm = MethodHandles.dropArguments(lm, 0, byte.class); // (*) |
|
1590 |
lm = MethodHandles.dropArguments(lm, 2, byte.class); |
|
1591 |
||
1592 |
MethodHandle cm = selectArgument(coderMixer(argClass), 1, ptypesList, ac); |
|
1593 |
cm = MethodHandles.dropArguments(cm, 0, int.class); // (**) |
|
1594 |
||
1595 |
// Read this bottom up: |
|
1596 |
||
1597 |
// 4. Drop old index and coder, producing ("new-index", "new-coder", <args>) |
|
1598 |
mh = MethodHandles.dropArguments(mh, 2, int.class, byte.class); |
|
1599 |
||
1600 |
// 3. Compute "new-index", producing ("new-index", "new-coder", "old-index", "old-coder", <args>) |
|
1601 |
// Length mixer ignores both "new-coder" and "old-coder" due to dropArguments above (*) |
|
1602 |
mh = MethodHandles.foldArguments(mh, lm); |
|
1603 |
||
1604 |
// 2. Compute "new-coder", producing ("new-coder", "old-index", "old-coder", <args>) |
|
1605 |
// Coder mixer ignores the "old-index" arg due to dropArguments above (**) |
|
1606 |
mh = MethodHandles.foldArguments(mh, cm); |
|
1607 |
||
1608 |
// 1. The mh shape here is ("old-index", "old-coder", <args>) |
|
1609 |
break; |
|
1610 |
} |
|
1611 |
default: |
|
1612 |
throw new StringConcatException("Unhandled tag: " + el.getTag()); |
|
1613 |
} |
|
1614 |
} |
|
1615 |
||
1616 |
// Insert initial lengths and coders here. |
|
1617 |
// The method handle shape here is (<args>). |
|
1618 |
mh = MethodHandles.insertArguments(mh, 0, initialLen, initialCoder); |
|
1619 |
||
1620 |
// Apply filters, converting the arguments: |
|
1621 |
if (filters != null) { |
|
1622 |
mh = MethodHandles.filterArguments(mh, 0, filters); |
|
1623 |
} |
|
1624 |
||
1625 |
return mh; |
|
1626 |
} |
|
1627 |
||
1628 |
private static int[] swap10(int count) { |
|
1629 |
int[] perm = new int[count]; |
|
1630 |
perm[0] = 1; |
|
1631 |
perm[1] = 0; |
|
1632 |
for (int i = 2; i < count; i++) { |
|
1633 |
perm[i] = i; |
|
1634 |
} |
|
1635 |
return perm; |
|
1636 |
} |
|
1637 |
||
1638 |
// Adapts: (...prefix..., parameter[pos])R -> (...prefix..., ...parameters...)R |
|
1639 |
private static MethodHandle selectArgument(MethodHandle mh, int prefix, List<Class<?>> ptypes, int pos) { |
|
1640 |
if (pos == 0) { |
|
1641 |
return MethodHandles.dropArguments(mh, prefix + 1, ptypes.subList(1, ptypes.size())); |
|
1642 |
} else if (pos == ptypes.size() - 1) { |
|
1643 |
return MethodHandles.dropArguments(mh, prefix, ptypes.subList(0, ptypes.size() - 1)); |
|
1644 |
} else { // 0 < pos < ptypes.size() - 1 |
|
1645 |
MethodHandle t = MethodHandles.dropArguments(mh, prefix, ptypes.subList(0, pos)); |
|
1646 |
return MethodHandles.dropArguments(t, prefix + 1 + pos, ptypes.subList(pos + 1, ptypes.size())); |
|
1647 |
} |
|
1648 |
} |
|
1649 |
||
1650 |
@ForceInline |
|
1651 |
private static byte[] newArray(int length, byte coder) { |
|
36668
ceb837301c53
8150463: StringConcat MH_INLINE_SIZED_EXACT should skip storage initialization
shade
parents:
36511
diff
changeset
|
1652 |
return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder); |
35388 | 1653 |
} |
1654 |
||
1655 |
@ForceInline |
|
1656 |
private static int checkIndex(int index) { |
|
1657 |
if (index != 0) { |
|
36668
ceb837301c53
8150463: StringConcat MH_INLINE_SIZED_EXACT should skip storage initialization
shade
parents:
36511
diff
changeset
|
1658 |
throw new IllegalStateException("Storage is not completely initialized, " + index + " bytes left"); |
35388 | 1659 |
} |
1660 |
return index; |
|
1661 |
} |
|
1662 |
||
1663 |
private static MethodHandle prepender(Class<?> cl) { |
|
1664 |
return PREPENDERS.computeIfAbsent(cl, PREPEND); |
|
1665 |
} |
|
1666 |
||
1667 |
private static MethodHandle coderMixer(Class<?> cl) { |
|
1668 |
return CODER_MIXERS.computeIfAbsent(cl, CODER_MIX); |
|
1669 |
} |
|
1670 |
||
1671 |
private static MethodHandle lengthMixer(Class<?> cl) { |
|
1672 |
return LENGTH_MIXERS.computeIfAbsent(cl, LENGTH_MIX); |
|
1673 |
} |
|
1674 |
||
1675 |
// This one is deliberately non-lambdified to optimize startup time: |
|
1676 |
private static final Function<Class<?>, MethodHandle> PREPEND = new Function<Class<?>, MethodHandle>() { |
|
1677 |
@Override |
|
1678 |
public MethodHandle apply(Class<?> c) { |
|
1679 |
return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", int.class, int.class, byte[].class, byte.class, c); |
|
1680 |
} |
|
1681 |
}; |
|
1682 |
||
1683 |
// This one is deliberately non-lambdified to optimize startup time: |
|
1684 |
private static final Function<Class<?>, MethodHandle> CODER_MIX = new Function<Class<?>, MethodHandle>() { |
|
1685 |
@Override |
|
1686 |
public MethodHandle apply(Class<?> c) { |
|
1687 |
return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mixCoder", byte.class, byte.class, c); |
|
1688 |
} |
|
1689 |
}; |
|
1690 |
||
1691 |
// This one is deliberately non-lambdified to optimize startup time: |
|
1692 |
private static final Function<Class<?>, MethodHandle> LENGTH_MIX = new Function<Class<?>, MethodHandle>() { |
|
1693 |
@Override |
|
1694 |
public MethodHandle apply(Class<?> c) { |
|
1695 |
return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mixLen", int.class, int.class, c); |
|
1696 |
} |
|
1697 |
}; |
|
1698 |
||
1699 |
private static final MethodHandle NEW_STRING; |
|
1700 |
private static final MethodHandle CHECK_INDEX; |
|
1701 |
private static final MethodHandle NEW_ARRAY; |
|
1702 |
private static final ConcurrentMap<Class<?>, MethodHandle> PREPENDERS; |
|
1703 |
private static final ConcurrentMap<Class<?>, MethodHandle> LENGTH_MIXERS; |
|
1704 |
private static final ConcurrentMap<Class<?>, MethodHandle> CODER_MIXERS; |
|
1705 |
private static final Class<?> STRING_HELPER; |
|
35700
b933119b8f84
8148869: StringConcatFactory MH_INLINE_SIZED_EXACT strategy does not work with -XX:-CompactStrings
shade
parents:
35639
diff
changeset
|
1706 |
private static final byte INITIAL_CODER; |
35388 | 1707 |
|
1708 |
static { |
|
1709 |
try { |
|
1710 |
STRING_HELPER = Class.forName("java.lang.StringConcatHelper"); |
|
35700
b933119b8f84
8148869: StringConcatFactory MH_INLINE_SIZED_EXACT strategy does not work with -XX:-CompactStrings
shade
parents:
35639
diff
changeset
|
1711 |
MethodHandle initCoder = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "initialCoder", byte.class); |
b933119b8f84
8148869: StringConcatFactory MH_INLINE_SIZED_EXACT strategy does not work with -XX:-CompactStrings
shade
parents:
35639
diff
changeset
|
1712 |
INITIAL_CODER = (byte) initCoder.invoke(); |
b933119b8f84
8148869: StringConcatFactory MH_INLINE_SIZED_EXACT strategy does not work with -XX:-CompactStrings
shade
parents:
35639
diff
changeset
|
1713 |
} catch (Throwable e) { |
35388 | 1714 |
throw new AssertionError(e); |
1715 |
} |
|
1716 |
||
1717 |
PREPENDERS = new ConcurrentHashMap<>(); |
|
1718 |
LENGTH_MIXERS = new ConcurrentHashMap<>(); |
|
1719 |
CODER_MIXERS = new ConcurrentHashMap<>(); |
|
1720 |
||
1721 |
NEW_STRING = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newString", String.class, byte[].class, byte.class); |
|
1722 |
NEW_ARRAY = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "newArray", byte[].class, int.class, byte.class); |
|
36668
ceb837301c53
8150463: StringConcat MH_INLINE_SIZED_EXACT should skip storage initialization
shade
parents:
36511
diff
changeset
|
1723 |
CHECK_INDEX = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "checkIndex", int.class, int.class); |
35388 | 1724 |
} |
1725 |
} |
|
1726 |
||
1727 |
/** |
|
1728 |
* Public gateways to public "stringify" methods. These methods have the form String apply(T obj), and normally |
|
1729 |
* delegate to {@code String.valueOf}, depending on argument's type. |
|
1730 |
*/ |
|
1731 |
private static final class Stringifiers { |
|
1732 |
private Stringifiers() { |
|
1733 |
// no instantiation |
|
1734 |
} |
|
1735 |
||
1736 |
// This one is deliberately non-lambdified to optimize startup time: |
|
1737 |
private static final Function<Class<?>, MethodHandle> MOST = new Function<Class<?>, MethodHandle>() { |
|
1738 |
@Override |
|
1739 |
public MethodHandle apply(Class<?> cl) { |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1740 |
MethodHandle mhObject = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, Object.class); |
35388 | 1741 |
|
1742 |
// We need the additional conversion here, because String.valueOf(Object) may return null. |
|
1743 |
// String conversion rules in Java state we need to produce "null" String in this case. |
|
1744 |
// It can be easily done with applying valueOf the second time. |
|
1745 |
MethodHandle mhObjectNoNulls = MethodHandles.filterReturnValue(mhObject, |
|
1746 |
mhObject.asType(MethodType.methodType(String.class, String.class))); |
|
1747 |
||
1748 |
if (cl == String.class) { |
|
1749 |
return mhObject; |
|
1750 |
} else if (cl == float.class) { |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1751 |
return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, float.class); |
35388 | 1752 |
} else if (cl == double.class) { |
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1753 |
return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, double.class); |
35388 | 1754 |
} else if (!cl.isPrimitive()) { |
1755 |
return mhObjectNoNulls; |
|
1756 |
} |
|
1757 |
||
1758 |
return null; |
|
1759 |
} |
|
1760 |
}; |
|
1761 |
||
1762 |
// This one is deliberately non-lambdified to optimize startup time: |
|
1763 |
private static final Function<Class<?>, MethodHandle> ANY = new Function<Class<?>, MethodHandle>() { |
|
1764 |
@Override |
|
1765 |
public MethodHandle apply(Class<?> cl) { |
|
1766 |
MethodHandle mh = MOST.apply(cl); |
|
1767 |
if (mh != null) { |
|
1768 |
return mh; |
|
1769 |
} |
|
1770 |
||
1771 |
if (cl == byte.class || cl == short.class || cl == int.class) { |
|
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1772 |
return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, int.class); |
35388 | 1773 |
} else if (cl == boolean.class) { |
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1774 |
return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, boolean.class); |
35388 | 1775 |
} else if (cl == char.class) { |
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1776 |
return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, char.class); |
35388 | 1777 |
} else if (cl == long.class) { |
36001
5f0acf0668e0
8149835: StringConcatFactory should emit classes with the same package as the host class
shade
parents:
35776
diff
changeset
|
1778 |
return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, long.class); |
35388 | 1779 |
} else { |
1780 |
throw new IllegalStateException("Unknown class: " + cl); |
|
1781 |
} |
|
1782 |
} |
|
1783 |
}; |
|
1784 |
||
1785 |
private static final ConcurrentMap<Class<?>, MethodHandle> STRINGIFIERS_MOST = new ConcurrentHashMap<>(); |
|
1786 |
private static final ConcurrentMap<Class<?>, MethodHandle> STRINGIFIERS_ANY = new ConcurrentHashMap<>(); |
|
1787 |
||
1788 |
/** |
|
1789 |
* Returns a stringifier for references and floats/doubles only. |
|
1790 |
* Always returns null for other primitives. |
|
1791 |
* |
|
1792 |
* @param t class to stringify |
|
1793 |
* @return stringifier; null, if not available |
|
1794 |
*/ |
|
1795 |
static MethodHandle forMost(Class<?> t) { |
|
1796 |
return STRINGIFIERS_MOST.computeIfAbsent(t, MOST); |
|
1797 |
} |
|
1798 |
||
1799 |
/** |
|
1800 |
* Returns a stringifier for any type. Never returns null. |
|
1801 |
* |
|
1802 |
* @param t class to stringify |
|
1803 |
* @return stringifier |
|
1804 |
*/ |
|
1805 |
static MethodHandle forAny(Class<?> t) { |
|
1806 |
return STRINGIFIERS_ANY.computeIfAbsent(t, ANY); |
|
1807 |
} |
|
1808 |
} |
|
1809 |
||
1810 |
/* ------------------------------- Common utilities ------------------------------------ */ |
|
1811 |
||
1812 |
private static MethodHandle lookupStatic(Lookup lookup, Class<?> refc, String name, Class<?> rtype, Class<?>... ptypes) { |
|
1813 |
try { |
|
1814 |
return lookup.findStatic(refc, name, MethodType.methodType(rtype, ptypes)); |
|
1815 |
} catch (NoSuchMethodException | IllegalAccessException e) { |
|
1816 |
throw new AssertionError(e); |
|
1817 |
} |
|
1818 |
} |
|
1819 |
||
1820 |
private static MethodHandle lookupVirtual(Lookup lookup, Class<?> refc, String name, Class<?> rtype, Class<?>... ptypes) { |
|
1821 |
try { |
|
1822 |
return lookup.findVirtual(refc, name, MethodType.methodType(rtype, ptypes)); |
|
1823 |
} catch (NoSuchMethodException | IllegalAccessException e) { |
|
1824 |
throw new AssertionError(e); |
|
1825 |
} |
|
1826 |
} |
|
1827 |
||
1828 |
private static MethodHandle lookupConstructor(Lookup lookup, Class<?> refc, Class<?> ptypes) { |
|
1829 |
try { |
|
1830 |
return lookup.findConstructor(refc, MethodType.methodType(void.class, ptypes)); |
|
1831 |
} catch (NoSuchMethodException | IllegalAccessException e) { |
|
1832 |
throw new AssertionError(e); |
|
1833 |
} |
|
1834 |
} |
|
1835 |
||
1836 |
private static int estimateSize(Class<?> cl) { |
|
1837 |
if (cl == Integer.TYPE) { |
|
1838 |
return 11; // "-2147483648" |
|
1839 |
} else if (cl == Boolean.TYPE) { |
|
1840 |
return 5; // "false" |
|
1841 |
} else if (cl == Byte.TYPE) { |
|
1842 |
return 4; // "-128" |
|
1843 |
} else if (cl == Character.TYPE) { |
|
1844 |
return 1; // duh |
|
1845 |
} else if (cl == Short.TYPE) { |
|
1846 |
return 6; // "-32768" |
|
1847 |
} else if (cl == Double.TYPE) { |
|
1848 |
return 26; // apparently, no larger than this, see FloatingDecimal.BinaryToASCIIBuffer.buffer |
|
1849 |
} else if (cl == Float.TYPE) { |
|
1850 |
return 26; // apparently, no larger than this, see FloatingDecimal.BinaryToASCIIBuffer.buffer |
|
1851 |
} else if (cl == Long.TYPE) { |
|
1852 |
return 20; // "-9223372036854775808" |
|
1853 |
} else { |
|
1854 |
throw new IllegalArgumentException("Cannot estimate the size for " + cl); |
|
1855 |
} |
|
1856 |
} |
|
1857 |
||
1858 |
private static Class<?> adaptToStringBuilder(Class<?> c) { |
|
1859 |
if (c.isPrimitive()) { |
|
1860 |
if (c == Byte.TYPE || c == Short.TYPE) { |
|
1861 |
return int.class; |
|
1862 |
} |
|
1863 |
} else { |
|
1864 |
if (c != String.class) { |
|
1865 |
return Object.class; |
|
1866 |
} |
|
1867 |
} |
|
1868 |
return c; |
|
1869 |
} |
|
1870 |
||
1871 |
private StringConcatFactory() { |
|
1872 |
// no instantiation |
|
1873 |
} |
|
1874 |
||
1875 |
} |