38
38
import static java .lang .invoke .MethodHandles .Lookup ;
39
39
import static org .elasticsearch .painless .Compiler .Loader ;
40
40
import static org .elasticsearch .painless .WriterConstants .CLASS_VERSION ;
41
+ import static org .elasticsearch .painless .WriterConstants .CTOR_METHOD_NAME ;
41
42
import static org .elasticsearch .painless .WriterConstants .DELEGATE_BOOTSTRAP_HANDLE ;
42
43
import static org .objectweb .asm .Opcodes .ACC_FINAL ;
43
44
import static org .objectweb .asm .Opcodes .ACC_PRIVATE ;
89
90
* public static final class $$Lambda0 implements Consumer {
90
91
* private List arg$0;
91
92
*
92
- * public $$Lambda0(List arg$0) {
93
+ * private $$Lambda0(List arg$0) {
93
94
* this.arg$0 = arg$0;
94
95
* }
96
+ *
97
+ * public static Consumer create$lambda(List arg$0) {
98
+ * return new $$Lambda0(arg$0);
99
+ * }
95
100
*
96
101
* public void accept(Object val$0) {
97
102
* Painless$Script.lambda$0(this.arg$0, val$0);
120
125
* on whether or not there are captures. If there are no captures
121
126
* the same instance of the generated lambda class will be
122
127
* returned each time by the factory method as there are no
123
- * changing values other than the arguments. If there are
124
- * captures a new instance of the generated lambda class will
125
- * be returned each time with the captures passed into the
128
+ * changing values other than the arguments, the lambda is a singleton.
129
+ * If there are captures, a new instance of the generated lambda class
130
+ * will be returned each time with the captures passed into the
126
131
* factory method to be stored in the member fields.
132
+ * Instead of calling the ctor, a static factory method is created
133
+ * in the lambda class, because a method handle to the ctor directly
134
+ * is (currently) preventing Hotspot optimizer from correctly doing
135
+ * escape analysis. Escape analysis is important to optimize the
136
+ * code in a way, that a new instance is not created on each lambda
137
+ * invocation with captures, stressing garbage collector (thanks
138
+ * to Rémi Forax for the explanation about this on Jaxcon 2017!).
127
139
*/
128
140
public final class LambdaBootstrap {
129
141
@@ -153,6 +165,11 @@ private Capture(int count, Class<?> type) {
153
165
*/
154
166
private static final String DELEGATED_CTOR_WRAPPER_NAME = "delegate$ctor" ;
155
167
168
+ /**
169
+ * This method name is used to generate the static factory for capturing lambdas.
170
+ */
171
+ private static final String LAMBDA_FACTORY_METHOD_NAME = "create$lambda" ;
172
+
156
173
/**
157
174
* Generates a lambda class for a lambda function/method reference
158
175
* within a Painless script. Variables with the prefix interface are considered
@@ -198,7 +215,8 @@ public static CallSite lambdaBootstrap(
198
215
199
216
// Handles the special case where a method reference refers to a ctor (we need a static wrapper method):
200
217
if (delegateInvokeType == H_NEWINVOKESPECIAL ) {
201
- generateStaticCtorDelegator (cw , delegateClassType , delegateMethodName , delegateMethodType );
218
+ assert CTOR_METHOD_NAME .equals (delegateMethodName );
219
+ generateStaticCtorDelegator (cw , ACC_PRIVATE , DELEGATED_CTOR_WRAPPER_NAME , delegateClassType , delegateMethodType );
202
220
// replace the delegate with our static wrapper:
203
221
delegateMethodName = DELEGATED_CTOR_WRAPPER_NAME ;
204
222
delegateClassType = lambdaClassType ;
@@ -281,16 +299,15 @@ private static void generateLambdaConstructor(
281
299
MethodType factoryMethodType ,
282
300
Capture [] captures ) {
283
301
284
- String conName = "<init>" ;
285
302
String conDesc = factoryMethodType .changeReturnType (void .class ).toMethodDescriptorString ();
286
- Method conMeth = new Method (conName , conDesc );
303
+ Method conMeth = new Method (CTOR_METHOD_NAME , conDesc );
287
304
Type baseConType = Type .getType (Object .class );
288
- Method baseConMeth = new Method (conName ,
305
+ Method baseConMeth = new Method (CTOR_METHOD_NAME ,
289
306
MethodType .methodType (void .class ).toMethodDescriptorString ());
290
- int modifiers = ACC_PUBLIC ;
307
+ int modifiers = ( captures . length > 0 ) ? ACC_PRIVATE : ACC_PUBLIC ;
291
308
292
309
GeneratorAdapter constructor = new GeneratorAdapter (modifiers , conMeth ,
293
- cw .visitMethod (modifiers , conName , conDesc , null , null ));
310
+ cw .visitMethod (modifiers , CTOR_METHOD_NAME , conDesc , null , null ));
294
311
constructor .visitCode ();
295
312
constructor .loadThis ();
296
313
constructor .invokeConstructor (baseConType , baseConMeth );
@@ -304,21 +321,31 @@ private static void generateLambdaConstructor(
304
321
305
322
constructor .returnValue ();
306
323
constructor .endMethod ();
324
+
325
+ // Add a factory method, if lambda takes captures.
326
+ // @uschindler says: I talked with Rémi Forax about this. Technically, a plain ctor
327
+ // and a MethodHandle to the ctor would be enough - BUT: Hotspot is unable to
328
+ // do escape analysis through a MethodHandles.findConstructor generated handle.
329
+ // Because of this we create a factory method. With this factory method, the
330
+ // escape analysis can figure out that everything is final and we don't need
331
+ // an instance, so it can omit object creation on heap!
332
+ if (captures .length > 0 ) {
333
+ generateStaticCtorDelegator (cw , ACC_PUBLIC , LAMBDA_FACTORY_METHOD_NAME , lambdaClassType , factoryMethodType );
334
+ }
307
335
}
308
336
309
337
/**
310
- * Generates a factory method to delegate to constructors using
311
- * {@code INVOKEDYNAMIC} using the {@link #delegateBootstrap} type converter.
338
+ * Generates a factory method to delegate to constructors.
312
339
*/
313
- private static void generateStaticCtorDelegator (ClassWriter cw , Type delegateClassType , String delegateMethodName ,
314
- MethodType delegateMethodType ) {
315
- Method wrapperMethod = new Method (DELEGATED_CTOR_WRAPPER_NAME , delegateMethodType .toMethodDescriptorString ());
340
+ private static void generateStaticCtorDelegator (ClassWriter cw , int access , String delegatorMethodName ,
341
+ Type delegateClassType , MethodType delegateMethodType ) {
342
+ Method wrapperMethod = new Method (delegatorMethodName , delegateMethodType .toMethodDescriptorString ());
316
343
Method constructorMethod =
317
- new Method (delegateMethodName , delegateMethodType .changeReturnType (void .class ).toMethodDescriptorString ());
318
- int modifiers = ACC_PRIVATE | ACC_STATIC ;
344
+ new Method (CTOR_METHOD_NAME , delegateMethodType .changeReturnType (void .class ).toMethodDescriptorString ());
345
+ int modifiers = access | ACC_STATIC ;
319
346
320
347
GeneratorAdapter factory = new GeneratorAdapter (modifiers , wrapperMethod ,
321
- cw .visitMethod (modifiers , DELEGATED_CTOR_WRAPPER_NAME , delegateMethodType .toMethodDescriptorString (), null , null ));
348
+ cw .visitMethod (modifiers , delegatorMethodName , delegateMethodType .toMethodDescriptorString (), null , null ));
322
349
factory .visitCode ();
323
350
factory .newInstance (delegateClassType );
324
351
factory .dup ();
@@ -329,7 +356,8 @@ private static void generateStaticCtorDelegator(ClassWriter cw, Type delegateCla
329
356
}
330
357
331
358
/**
332
- * Generates the interface method that will delegate (call) to the delegate method.
359
+ * Generates the interface method that will delegate (call) to the delegate method
360
+ * with {@code INVOKEDYNAMIC} using the {@link #delegateBootstrap} type converter.
333
361
*/
334
362
private static void generateInterfaceMethod (
335
363
ClassWriter cw ,
@@ -464,8 +492,7 @@ private static CallSite createCaptureCallSite(
464
492
465
493
try {
466
494
return new ConstantCallSite (
467
- lookup .findConstructor (lambdaClass , factoryMethodType .changeReturnType (void .class ))
468
- .asType (factoryMethodType ));
495
+ lookup .findStatic (lambdaClass , LAMBDA_FACTORY_METHOD_NAME , factoryMethodType ));
469
496
} catch (ReflectiveOperationException exception ) {
470
497
throw new IllegalStateException ("unable to create lambda class" , exception );
471
498
}
0 commit comments