forked from openjdk/jdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLambdaForm.java
1752 lines (1613 loc) · 70.2 KB
/
LambdaForm.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.invoke;
import java.lang.classfile.TypeKind;
import jdk.internal.perf.PerfCounter;
import jdk.internal.vm.annotation.DontInline;
import jdk.internal.vm.annotation.Hidden;
import jdk.internal.vm.annotation.Stable;
import sun.invoke.util.Wrapper;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import static java.lang.invoke.LambdaForm.BasicType.*;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.MethodHandleStatics.*;
/**
* The symbolic, non-executable form of a method handle's invocation semantics.
* It consists of a series of names.
* The first N (N=arity) names are parameters,
* while any remaining names are temporary values.
* Each temporary specifies the application of a function to some arguments.
* The functions are method handles, while the arguments are mixes of
* constant values and local names.
* The result of the lambda is defined as one of the names, often the last one.
* <p>
* Here is an approximate grammar:
* <blockquote><pre>{@code
* LambdaForm = "(" ArgName* ")=>{" TempName* Result "}"
* ArgName = "a" N ":" T
* TempName = "t" N ":" T "=" Function "(" Argument* ");"
* Function = ConstantValue
* Argument = NameRef | ConstantValue
* Result = NameRef | "void"
* NameRef = "a" N | "t" N
* N = (any whole number)
* T = "L" | "I" | "J" | "F" | "D" | "V"
* }</pre></blockquote>
* Names are numbered consecutively from left to right starting at zero.
* (The letters are merely a taste of syntax sugar.)
* Thus, the first temporary (if any) is always numbered N (where N=arity).
* Every occurrence of a name reference in an argument list must refer to
* a name previously defined within the same lambda.
* A lambda has a void result if and only if its result index is -1.
* If a temporary has the type "V", it cannot be the subject of a NameRef,
* even though possesses a number.
* Note that all reference types are erased to "L", which stands for {@code Object}.
* All subword types (boolean, byte, short, char) are erased to "I" which is {@code int}.
* The other types stand for the usual primitive types.
* <p>
* Function invocation closely follows the static rules of the Java verifier.
* Arguments and return values must exactly match when their "Name" types are
* considered.
* Conversions are allowed only if they do not change the erased type.
* <ul>
* <li>L = Object: casts are used freely to convert into and out of reference types
* <li>I = int: subword types are forcibly narrowed when passed as arguments (see {@code explicitCastArguments})
* <li>J = long: no implicit conversions
* <li>F = float: no implicit conversions
* <li>D = double: no implicit conversions
* <li>V = void: a function result may be void if and only if its Name is of type "V"
* </ul>
* Although implicit conversions are not allowed, explicit ones can easily be
* encoded by using temporary expressions which call type-transformed identity functions.
* <p>
* Examples:
* <blockquote><pre>{@code
* (a0:J)=>{ a0 }
* == identity(long)
* (a0:I)=>{ t1:V = System.out#println(a0); void }
* == System.out#println(int)
* (a0:L)=>{ t1:V = System.out#println(a0); a0 }
* == identity, with printing side-effect
* (a0:L, a1:L)=>{ t2:L = BoundMethodHandle#argument(a0);
* t3:L = BoundMethodHandle#target(a0);
* t4:L = MethodHandle#invoke(t3, t2, a1); t4 }
* == general invoker for unary insertArgument combination
* (a0:L, a1:L)=>{ t2:L = FilterMethodHandle#filter(a0);
* t3:L = MethodHandle#invoke(t2, a1);
* t4:L = FilterMethodHandle#target(a0);
* t5:L = MethodHandle#invoke(t4, t3); t5 }
* == general invoker for unary filterArgument combination
* (a0:L, a1:L)=>{ ...(same as previous example)...
* t5:L = MethodHandle#invoke(t4, t3, a1); t5 }
* == general invoker for unary/unary foldArgument combination
* (a0:L, a1:I)=>{ t2:I = identity(long).asType((int)->long)(a1); t2 }
* == invoker for identity method handle which performs i2l
* (a0:L, a1:L)=>{ t2:L = BoundMethodHandle#argument(a0);
* t3:L = Class#cast(t2,a1); t3 }
* == invoker for identity method handle which performs cast
* }</pre></blockquote>
* <p>
* @author John Rose, JSR 292 EG
*/
class LambdaForm {
final int arity;
final int result;
final boolean forceInline;
final MethodHandle customized;
@Stable final Name[] names;
final Kind kind;
MemberName vmentry; // low-level behavior, or null if not yet prepared
private boolean isCompiled;
// Either a LambdaForm cache (managed by LambdaFormEditor) or a link to uncustomized version (for customized LF)
volatile Object transformCache;
public static final int VOID_RESULT = -1, LAST_RESULT = -2;
enum BasicType {
L_TYPE('L', Object.class, Wrapper.OBJECT, TypeKind.REFERENCE), // all reference types
I_TYPE('I', int.class, Wrapper.INT, TypeKind.INT),
J_TYPE('J', long.class, Wrapper.LONG, TypeKind.LONG),
F_TYPE('F', float.class, Wrapper.FLOAT, TypeKind.FLOAT),
D_TYPE('D', double.class, Wrapper.DOUBLE, TypeKind.DOUBLE), // all primitive types
V_TYPE('V', void.class, Wrapper.VOID, TypeKind.VOID); // not valid in all contexts
static final @Stable BasicType[] ALL_TYPES = BasicType.values();
static final @Stable BasicType[] ARG_TYPES = Arrays.copyOf(ALL_TYPES, ALL_TYPES.length-1);
static final int ARG_TYPE_LIMIT = ARG_TYPES.length;
static final int TYPE_LIMIT = ALL_TYPES.length;
final char btChar;
final Class<?> btClass;
final Wrapper btWrapper;
final TypeKind btKind;
private BasicType(char btChar, Class<?> btClass, Wrapper wrapper, TypeKind typeKind) {
this.btChar = btChar;
this.btClass = btClass;
this.btWrapper = wrapper;
this.btKind = typeKind;
}
char basicTypeChar() {
return btChar;
}
Class<?> basicTypeClass() {
return btClass;
}
Wrapper basicTypeWrapper() {
return btWrapper;
}
TypeKind basicTypeKind() {
return btKind;
}
int basicTypeSlots() {
return btWrapper.stackSlots();
}
static BasicType basicType(byte type) {
return ALL_TYPES[type];
}
static BasicType basicType(char type) {
return switch (type) {
case 'L' -> L_TYPE;
case 'I' -> I_TYPE;
case 'J' -> J_TYPE;
case 'F' -> F_TYPE;
case 'D' -> D_TYPE;
case 'V' -> V_TYPE;
// all subword types are represented as ints
case 'Z', 'B', 'S', 'C' -> I_TYPE;
default -> throw newInternalError("Unknown type char: '" + type + "'");
};
}
static BasicType basicType(Class<?> type) {
return basicType(Wrapper.basicTypeChar(type));
}
static int[] basicTypeOrds(BasicType[] types) {
if (types == null) {
return null;
}
int[] a = new int[types.length];
for(int i = 0; i < types.length; ++i) {
a[i] = types[i].ordinal();
}
return a;
}
static char basicTypeChar(Class<?> type) {
return basicType(type).btChar;
}
static int[] basicTypesOrd(Class<?>[] types) {
int[] ords = new int[types.length];
for (int i = 0; i < ords.length; i++) {
ords[i] = basicType(types[i]).ordinal();
}
return ords;
}
static boolean isBasicTypeChar(char c) {
return "LIJFDV".indexOf(c) >= 0;
}
static boolean isArgBasicTypeChar(char c) {
return "LIJFD".indexOf(c) >= 0;
}
static { assert(checkBasicType()); }
private static boolean checkBasicType() {
for (int i = 0; i < ARG_TYPE_LIMIT; i++) {
assert ARG_TYPES[i].ordinal() == i;
assert ARG_TYPES[i] == ALL_TYPES[i];
}
for (int i = 0; i < TYPE_LIMIT; i++) {
assert ALL_TYPES[i].ordinal() == i;
}
assert ALL_TYPES[TYPE_LIMIT - 1] == V_TYPE;
assert !Arrays.asList(ARG_TYPES).contains(V_TYPE);
return true;
}
}
enum Kind {
GENERIC("invoke"),
IDENTITY("identity"),
CONSTANT("constant"),
BOUND_REINVOKER("BMH.reinvoke", "reinvoke"),
REINVOKER("MH.reinvoke", "reinvoke"),
DELEGATE("MH.delegate", "delegate"),
EXACT_LINKER("MH.invokeExact_MT", "invokeExact_MT"),
EXACT_INVOKER("MH.exactInvoker", "exactInvoker"),
GENERIC_LINKER("MH.invoke_MT", "invoke_MT"),
GENERIC_INVOKER("MH.invoker", "invoker"),
LINK_TO_TARGET_METHOD("linkToTargetMethod"),
LINK_TO_CALL_SITE("linkToCallSite"),
DIRECT_INVOKE_VIRTUAL("DMH.invokeVirtual", "invokeVirtual"),
DIRECT_INVOKE_SPECIAL("DMH.invokeSpecial", "invokeSpecial"),
DIRECT_INVOKE_SPECIAL_IFC("DMH.invokeSpecialIFC", "invokeSpecialIFC"),
DIRECT_INVOKE_STATIC("DMH.invokeStatic", "invokeStatic"),
DIRECT_NEW_INVOKE_SPECIAL("DMH.newInvokeSpecial", "newInvokeSpecial"),
DIRECT_INVOKE_INTERFACE("DMH.invokeInterface", "invokeInterface"),
DIRECT_INVOKE_STATIC_INIT("DMH.invokeStaticInit", "invokeStaticInit"),
GET_REFERENCE("getReference"),
PUT_REFERENCE("putReference"),
GET_REFERENCE_VOLATILE("getReferenceVolatile"),
PUT_REFERENCE_VOLATILE("putReferenceVolatile"),
GET_INT("getInt"),
PUT_INT("putInt"),
GET_INT_VOLATILE("getIntVolatile"),
PUT_INT_VOLATILE("putIntVolatile"),
GET_BOOLEAN("getBoolean"),
PUT_BOOLEAN("putBoolean"),
GET_BOOLEAN_VOLATILE("getBooleanVolatile"),
PUT_BOOLEAN_VOLATILE("putBooleanVolatile"),
GET_BYTE("getByte"),
PUT_BYTE("putByte"),
GET_BYTE_VOLATILE("getByteVolatile"),
PUT_BYTE_VOLATILE("putByteVolatile"),
GET_CHAR("getChar"),
PUT_CHAR("putChar"),
GET_CHAR_VOLATILE("getCharVolatile"),
PUT_CHAR_VOLATILE("putCharVolatile"),
GET_SHORT("getShort"),
PUT_SHORT("putShort"),
GET_SHORT_VOLATILE("getShortVolatile"),
PUT_SHORT_VOLATILE("putShortVolatile"),
GET_LONG("getLong"),
PUT_LONG("putLong"),
GET_LONG_VOLATILE("getLongVolatile"),
PUT_LONG_VOLATILE("putLongVolatile"),
GET_FLOAT("getFloat"),
PUT_FLOAT("putFloat"),
GET_FLOAT_VOLATILE("getFloatVolatile"),
PUT_FLOAT_VOLATILE("putFloatVolatile"),
GET_DOUBLE("getDouble"),
PUT_DOUBLE("putDouble"),
GET_DOUBLE_VOLATILE("getDoubleVolatile"),
PUT_DOUBLE_VOLATILE("putDoubleVolatile"),
TRY_FINALLY("tryFinally"),
TABLE_SWITCH("tableSwitch"),
COLLECTOR("collector"),
LOOP("loop"),
GUARD("guard"),
GUARD_WITH_CATCH("guardWithCatch"),
VARHANDLE_EXACT_INVOKER("VH.exactInvoker"),
VARHANDLE_INVOKER("VH.invoker", "invoker"),
VARHANDLE_LINKER("VH.invoke_MT", "invoke_MT");
final String defaultLambdaName;
final String methodName;
private Kind(String defaultLambdaName) {
this(defaultLambdaName, defaultLambdaName);
}
private Kind(String defaultLambdaName, String methodName) {
this.defaultLambdaName = defaultLambdaName;
this.methodName = methodName;
}
}
// private version that doesn't do checks or defensive copies
private LambdaForm(int arity, int result, boolean forceInline, MethodHandle customized, Name[] names, Kind kind) {
this.arity = arity;
this.result = result;
this.forceInline = forceInline;
this.customized = customized;
this.names = names;
this.kind = kind;
this.vmentry = null;
this.isCompiled = false;
}
// root factory pre/post processing and calls simple constructor
private static LambdaForm create(int arity, Name[] names, int result, boolean forceInline, MethodHandle customized, Kind kind) {
names = names.clone();
assert(namesOK(arity, names));
result = fixResult(result, names);
boolean canInterpret = normalizeNames(arity, names);
LambdaForm form = new LambdaForm(arity, result, forceInline, customized, names, kind);
assert(form.nameRefsAreLegal());
if (!canInterpret) {
form.compileToBytecode();
}
return form;
}
// derived factories with defaults
private static final int DEFAULT_RESULT = LAST_RESULT;
private static final boolean DEFAULT_FORCE_INLINE = true;
private static final MethodHandle DEFAULT_CUSTOMIZED = null;
private static final Kind DEFAULT_KIND = Kind.GENERIC;
static LambdaForm create(int arity, Name[] names, int result) {
return create(arity, names, result, DEFAULT_FORCE_INLINE, DEFAULT_CUSTOMIZED, DEFAULT_KIND);
}
static LambdaForm create(int arity, Name[] names, int result, Kind kind) {
return create(arity, names, result, DEFAULT_FORCE_INLINE, DEFAULT_CUSTOMIZED, kind);
}
static LambdaForm create(int arity, Name[] names) {
return create(arity, names, DEFAULT_RESULT, DEFAULT_FORCE_INLINE, DEFAULT_CUSTOMIZED, DEFAULT_KIND);
}
static LambdaForm create(int arity, Name[] names, Kind kind) {
return create(arity, names, DEFAULT_RESULT, DEFAULT_FORCE_INLINE, DEFAULT_CUSTOMIZED, kind);
}
static LambdaForm create(int arity, Name[] names, boolean forceInline, Kind kind) {
return create(arity, names, DEFAULT_RESULT, forceInline, DEFAULT_CUSTOMIZED, kind);
}
private static int fixResult(int result, Name[] names) {
if (result == LAST_RESULT)
result = names.length - 1; // might still be void
if (result >= 0 && names[result].type == V_TYPE)
result = VOID_RESULT;
return result;
}
static boolean debugNames() {
return DEBUG_NAME_COUNTERS != null;
}
static void associateWithDebugName(LambdaForm form, String name) {
assert (debugNames());
synchronized (DEBUG_NAMES) {
DEBUG_NAMES.put(form, name);
}
}
String lambdaName() {
if (DEBUG_NAMES != null) {
synchronized (DEBUG_NAMES) {
String name = DEBUG_NAMES.get(this);
if (name == null) {
name = generateDebugName();
}
return name;
}
}
return kind.defaultLambdaName;
}
private String generateDebugName() {
assert (debugNames());
String debugNameStem = kind.defaultLambdaName;
Integer ctr = DEBUG_NAME_COUNTERS.getOrDefault(debugNameStem, 0);
DEBUG_NAME_COUNTERS.put(debugNameStem, ctr + 1);
StringBuilder buf = new StringBuilder(debugNameStem);
int leadingZero = buf.length();
buf.append((int) ctr);
for (int i = buf.length() - leadingZero; i < 3; i++) {
buf.insert(leadingZero, '0');
}
buf.append('_');
buf.append(basicTypeSignature());
String name = buf.toString();
associateWithDebugName(this, name);
return name;
}
private static boolean namesOK(int arity, Name[] names) {
for (int i = 0; i < names.length; i++) {
Name n = names[i];
assert(n != null) : "n is null";
if (i < arity)
assert( n.isParam()) : n + " is not param at " + i;
else
assert(!n.isParam()) : n + " is param at " + i;
}
return true;
}
/** Customize LambdaForm for a particular MethodHandle */
LambdaForm customize(MethodHandle mh) {
if (customized == mh) {
return this;
}
LambdaForm customForm = LambdaForm.create(arity, names, result, forceInline, mh, kind);
if (COMPILE_THRESHOLD >= 0 && isCompiled) {
// If shared LambdaForm has been compiled, compile customized version as well.
customForm.compileToBytecode();
}
customForm.transformCache = this; // LambdaFormEditor should always use uncustomized form.
return customForm;
}
/** Get uncustomized flavor of the LambdaForm */
LambdaForm uncustomize() {
if (customized == null) {
return this;
}
assert(transformCache != null); // Customized LambdaForm should always has a link to uncustomized version.
LambdaForm uncustomizedForm = (LambdaForm)transformCache;
if (COMPILE_THRESHOLD >= 0 && isCompiled) {
// If customized LambdaForm has been compiled, compile uncustomized version as well.
uncustomizedForm.compileToBytecode();
}
return uncustomizedForm;
}
/** Renumber and/or replace params so that they are interned and canonically numbered.
* @return true if we can interpret
*/
private static boolean normalizeNames(int arity, Name[] names) {
Name[] oldNames = names.clone();
int maxOutArity = 0;
for (int i = 0; i < names.length; i++) {
Name n = names[i];
names[i] = n.withIndex(i);
if (n.arguments != null && maxOutArity < n.arguments.length)
maxOutArity = n.arguments.length;
}
if (oldNames != null) {
for (int i = Math.max(1, arity); i < names.length; i++) {
Name fixed = names[i].replaceNames(oldNames, names, 0, i);
names[i] = fixed.withIndex(i);
}
}
int maxInterned = Math.min(arity, INTERNED_ARGUMENT_LIMIT);
boolean needIntern = false;
for (int i = 0; i < maxInterned; i++) {
Name n = names[i], n2 = internArgument(n);
if (n != n2) {
names[i] = n2;
needIntern = true;
}
}
if (needIntern) {
for (int i = arity; i < names.length; i++) {
names[i].internArguments();
}
}
// return true if we can interpret
if (maxOutArity > MethodType.MAX_MH_INVOKER_ARITY) {
// Cannot use LF interpreter on very high arity expressions.
assert(maxOutArity <= MethodType.MAX_JVM_ARITY);
return false;
}
return true;
}
/**
* Check that all embedded Name references are localizable to this lambda,
* and are properly ordered after their corresponding definitions.
* <p>
* Note that a Name can be local to multiple lambdas, as long as
* it possesses the same index in each use site.
* This allows Name references to be freely reused to construct
* fresh lambdas, without confusion.
*/
boolean nameRefsAreLegal() {
assert(arity >= 0 && arity <= names.length);
assert(result >= -1 && result < names.length);
// Do all names possess an index consistent with their local definition order?
for (int i = 0; i < arity; i++) {
Name n = names[i];
assert(n.index() == i) : Arrays.asList(n.index(), i);
assert(n.isParam());
}
// Also, do all local name references
for (int i = arity; i < names.length; i++) {
Name n = names[i];
assert(n.index() == i);
for (Object arg : n.arguments) {
if (arg instanceof Name n2) {
int i2 = n2.index;
assert(0 <= i2 && i2 < names.length) : n.debugString() + ": 0 <= i2 && i2 < names.length: 0 <= " + i2 + " < " + names.length;
assert(names[i2] == n2) : Arrays.asList("-1-", i, "-2-", n.debugString(), "-3-", i2, "-4-", n2.debugString(), "-5-", names[i2].debugString(), "-6-", this);
assert(i2 < i); // ref must come after def!
}
}
}
return true;
}
// /** Invoke this form on the given arguments. */
// final Object invoke(Object... args) throws Throwable {
// // NYI: fit this into the fast path?
// return interpretWithArguments(args);
// }
/** Report the return type. */
BasicType returnType() {
if (result < 0) return V_TYPE;
Name n = names[result];
return n.type;
}
/** Report the N-th argument type. */
BasicType parameterType(int n) {
return parameter(n).type;
}
/** Report the N-th argument name. */
Name parameter(int n) {
Name param = names[n];
assert(n < arity && param.isParam());
return param;
}
/** Report the N-th argument type constraint. */
Object parameterConstraint(int n) {
return parameter(n).constraint;
}
/** Report the arity. */
int arity() {
return arity;
}
/** Report the number of expressions (non-parameter names). */
int expressionCount() {
return names.length - arity;
}
/** Return the method type corresponding to my basic type signature. */
MethodType methodType() {
Class<?>[] ptypes = new Class<?>[arity];
for (int i = 0; i < arity; ++i) {
ptypes[i] = parameterType(i).btClass;
}
return MethodType.methodType(returnType().btClass, ptypes, true);
}
/** Return ABC_Z, where the ABC are parameter type characters, and Z is the return type character. */
final String basicTypeSignature() {
StringBuilder buf = new StringBuilder(arity() + 3);
for (int i = 0, a = arity(); i < a; i++)
buf.append(parameterType(i).basicTypeChar());
return buf.append('_').append(returnType().basicTypeChar()).toString();
}
static int signatureArity(String sig) {
assert(isValidSignature(sig));
return sig.indexOf('_');
}
static boolean isValidSignature(String sig) {
int arity = sig.indexOf('_');
if (arity < 0) return false; // must be of the form *_*
int siglen = sig.length();
if (siglen != arity + 2) return false; // *_X
for (int i = 0; i < siglen; i++) {
if (i == arity) continue; // skip '_'
char c = sig.charAt(i);
if (c == 'V')
return (i == siglen - 1 && arity == siglen - 2);
if (!isArgBasicTypeChar(c)) return false; // must be [LIJFD]
}
return true; // [LIJFD]*_[LIJFDV]
}
/**
* Check if i-th name is a call to MethodHandleImpl.selectAlternative.
*/
boolean isSelectAlternative(int pos) {
// selectAlternative idiom:
// t_{n}:L=MethodHandleImpl.selectAlternative(...)
// t_{n+1}:?=MethodHandle.invokeBasic(t_{n}, ...)
if (pos+1 >= names.length) return false;
Name name0 = names[pos];
Name name1 = names[pos+1];
return name0.refersTo(MethodHandleImpl.class, "selectAlternative") &&
name1.isInvokeBasic() &&
name1.lastUseIndex(name0) == 0 && // t_{n+1}:?=MethodHandle.invokeBasic(t_{n}, ...)
lastUseIndex(name0) == pos+1; // t_{n} is local: used only in t_{n+1}
}
private boolean isMatchingIdiom(int pos, String idiomName, int nArgs) {
if (pos+2 >= names.length) return false;
Name name0 = names[pos];
Name name1 = names[pos+1];
Name name2 = names[pos+2];
return name1.refersTo(MethodHandleImpl.class, idiomName) &&
name0.isInvokeBasic() &&
name2.isInvokeBasic() &&
name1.lastUseIndex(name0) == nArgs && // t_{n+1}:L=MethodHandleImpl.<invoker>(<args>, t_{n});
lastUseIndex(name0) == pos+1 && // t_{n} is local: used only in t_{n+1}
name2.lastUseIndex(name1) == 1 && // t_{n+2}:?=MethodHandle.invokeBasic(*, t_{n+1})
lastUseIndex(name1) == pos+2; // t_{n+1} is local: used only in t_{n+2}
}
/**
* Check if i-th name is a start of GuardWithCatch idiom.
*/
boolean isGuardWithCatch(int pos) {
// GuardWithCatch idiom:
// t_{n}:L=MethodHandle.invokeBasic(...)
// t_{n+1}:L=MethodHandleImpl.guardWithCatch(*, *, *, t_{n});
// t_{n+2}:?=MethodHandle.invokeBasic(*, t_{n+1})
return isMatchingIdiom(pos, "guardWithCatch", 3);
}
/**
* Check if i-th name is a start of the tryFinally idiom.
*/
boolean isTryFinally(int pos) {
// tryFinally idiom:
// t_{n}:L=MethodHandle.invokeBasic(...)
// t_{n+1}:L=MethodHandleImpl.tryFinally(*, *, t_{n})
// t_{n+2}:?=MethodHandle.invokeBasic(*, t_{n+1})
return isMatchingIdiom(pos, "tryFinally", 2);
}
/**
* Check if i-th name is a start of the tableSwitch idiom.
*/
boolean isTableSwitch(int pos) {
// tableSwitch idiom:
// t_{n}:L=MethodHandle.invokeBasic(...) // args
// t_{n+1}:L=MethodHandleImpl.tableSwitch(*, *, *, t_{n})
// t_{n+2}:?=MethodHandle.invokeBasic(*, t_{n+1})
if (pos + 2 >= names.length) return false;
final int POS_COLLECT_ARGS = pos;
final int POS_TABLE_SWITCH = pos + 1;
final int POS_UNBOX_RESULT = pos + 2;
Name collectArgs = names[POS_COLLECT_ARGS];
Name tableSwitch = names[POS_TABLE_SWITCH];
Name unboxResult = names[POS_UNBOX_RESULT];
return tableSwitch.refersTo(MethodHandleImpl.class, "tableSwitch") &&
collectArgs.isInvokeBasic() &&
unboxResult.isInvokeBasic() &&
tableSwitch.lastUseIndex(collectArgs) == 3 && // t_{n+1}:L=MethodHandleImpl.<invoker>(*, *, *, t_{n});
lastUseIndex(collectArgs) == POS_TABLE_SWITCH && // t_{n} is local: used only in t_{n+1}
unboxResult.lastUseIndex(tableSwitch) == 1 && // t_{n+2}:?=MethodHandle.invokeBasic(*, t_{n+1})
lastUseIndex(tableSwitch) == POS_UNBOX_RESULT; // t_{n+1} is local: used only in t_{n+2}
}
/**
* Check if i-th name is a start of the loop idiom.
*/
boolean isLoop(int pos) {
// loop idiom:
// t_{n}:L=MethodHandle.invokeBasic(...)
// t_{n+1}:L=MethodHandleImpl.loop(types, *, t_{n})
// t_{n+2}:?=MethodHandle.invokeBasic(*, t_{n+1})
return isMatchingIdiom(pos, "loop", 2);
}
/*
* Code generation issues:
*
* Compiled LFs should be reusable in general.
* The biggest issue is how to decide when to pull a name into
* the bytecode, versus loading a reified form from the MH data.
*
* For example, an asType wrapper may require execution of a cast
* after a call to a MH. The target type of the cast can be placed
* as a constant in the LF itself. This will force the cast type
* to be compiled into the bytecodes and native code for the MH.
* Or, the target type of the cast can be erased in the LF, and
* loaded from the MH data. (Later on, if the MH as a whole is
* inlined, the data will flow into the inlined instance of the LF,
* as a constant, and the end result will be an optimal cast.)
*
* This erasure of cast types can be done with any use of
* reference types. It can also be done with whole method
* handles. Erasing a method handle might leave behind
* LF code that executes correctly for any MH of a given
* type, and load the required MH from the enclosing MH's data.
* Or, the erasure might even erase the expected MT.
*
* Also, for direct MHs, the MemberName of the target
* could be erased, and loaded from the containing direct MH.
* As a simple case, a LF for all int-valued non-static
* field getters would perform a cast on its input argument
* (to non-constant base type derived from the MemberName)
* and load an integer value from the input object
* (at a non-constant offset also derived from the MemberName).
* Such MN-erased LFs would be inlinable back to optimized
* code, whenever a constant enclosing DMH is available
* to supply a constant MN from its data.
*
* The main problem here is to keep LFs reasonably generic,
* while ensuring that hot spots will inline good instances.
* "Reasonably generic" means that we don't end up with
* repeated versions of bytecode or machine code that do
* not differ in their optimized form. Repeated versions
* of machine would have the undesirable overheads of
* (a) redundant compilation work and (b) extra I$ pressure.
* To control repeated versions, we need to be ready to
* erase details from LFs and move them into MH data,
* whenever those details are not relevant to significant
* optimization. "Significant" means optimization of
* code that is actually hot.
*
* Achieving this may require dynamic splitting of MHs, by replacing
* a generic LF with a more specialized one, on the same MH,
* if (a) the MH is frequently executed and (b) the MH cannot
* be inlined into a containing caller, such as an invokedynamic.
*
* Compiled LFs that are no longer used should be GC-able.
* If they contain non-BCP references, they should be properly
* interlinked with the class loader(s) that their embedded types
* depend on. This probably means that reusable compiled LFs
* will be tabulated (indexed) on relevant class loaders,
* or else that the tables that cache them will have weak links.
*/
/**
* Make this LF directly executable, as part of a MethodHandle.
* Invariant: Every MH which is invoked must prepare its LF
* before invocation.
* (In principle, the JVM could do this very lazily,
* as a sort of pre-invocation linkage step.)
*/
public void prepare() {
if (COMPILE_THRESHOLD == 0 && !forceInterpretation() && !isCompiled) {
compileToBytecode();
}
if (this.vmentry != null) {
// already prepared (e.g., a primitive DMH invoker form)
return;
}
MethodType mtype = methodType();
MethodTypeForm form = mtype.form();
MemberName entry = form.cachedInterpretEntry();
if (entry == null) {
assert (isValidSignature(basicTypeSignature()));
entry = InvokerBytecodeGenerator.generateLambdaFormInterpreterEntryPoint(mtype);
entry = form.setCachedInterpretEntry(entry);
}
this.vmentry = entry;
// TO DO: Maybe add invokeGeneric, invokeWithArguments
}
private static @Stable PerfCounter LF_FAILED;
private static PerfCounter failedCompilationCounter() {
if (LF_FAILED == null) {
LF_FAILED = PerfCounter.newPerfCounter("java.lang.invoke.failedLambdaFormCompilations");
}
return LF_FAILED;
}
/** Generate optimizable bytecode for this form. */
void compileToBytecode() {
if (forceInterpretation()) {
return; // this should not be compiled
}
if (vmentry != null && isCompiled) {
return; // already compiled somehow
}
// Obtain the invoker MethodType outside of the following try block.
// This ensures that an IllegalArgumentException is directly thrown if the
// type would have 256 or more parameters
MethodType invokerType = methodType();
assert(vmentry == null || vmentry.getMethodType().basicType().equals(invokerType));
try {
vmentry = InvokerBytecodeGenerator.generateCustomizedCode(this, invokerType);
if (TRACE_INTERPRETER)
traceInterpreter("compileToBytecode", this);
isCompiled = true;
} catch (InvokerBytecodeGenerator.BytecodeGenerationException bge) {
// bytecode generation failed - mark this LambdaForm as to be run in interpretation mode only
invocationCounter = -1;
failedCompilationCounter().increment();
if (LOG_LF_COMPILATION_FAILURE) {
System.out.println("LambdaForm compilation failed: " + this);
bge.printStackTrace(System.out);
}
} catch (Error e) {
// Pass through any error
throw e;
} catch (Exception e) {
// Wrap any exception
throw newInternalError(this.toString(), e);
}
}
// The next few routines are called only from assert expressions
// They verify that the built-in invokers process the correct raw data types.
private static boolean argumentTypesMatch(String sig, Object[] av) {
int arity = signatureArity(sig);
assert(av.length == arity) : "av.length == arity: av.length=" + av.length + ", arity=" + arity;
assert(av[0] instanceof MethodHandle) : "av[0] not instance of MethodHandle: " + av[0];
MethodHandle mh = (MethodHandle) av[0];
MethodType mt = mh.type();
assert(mt.parameterCount() == arity-1);
for (int i = 0; i < av.length; i++) {
Class<?> pt = (i == 0 ? MethodHandle.class : mt.parameterType(i-1));
assert(valueMatches(basicType(sig.charAt(i)), pt, av[i]));
}
return true;
}
private static boolean valueMatches(BasicType tc, Class<?> type, Object x) {
// The following line is needed because (...)void method handles can use non-void invokers
if (type == void.class) tc = V_TYPE; // can drop any kind of value
assert tc == basicType(type) : tc + " == basicType(" + type + ")=" + basicType(type);
switch (tc) {
case I_TYPE: assert checkInt(type, x) : "checkInt(" + type + "," + x +")"; break;
case J_TYPE: assert x instanceof Long : "instanceof Long: " + x; break;
case F_TYPE: assert x instanceof Float : "instanceof Float: " + x; break;
case D_TYPE: assert x instanceof Double : "instanceof Double: " + x; break;
case L_TYPE: assert checkRef(type, x) : "checkRef(" + type + "," + x + ")"; break;
case V_TYPE: break; // allow anything here; will be dropped
default: assert(false);
}
return true;
}
private static boolean checkInt(Class<?> type, Object x) {
assert(x instanceof Integer);
if (type == int.class) return true;
Wrapper w = Wrapper.forBasicType(type);
assert(w.isSubwordOrInt());
Object x1 = Wrapper.INT.wrap(w.wrap(x));
return x.equals(x1);
}
private static boolean checkRef(Class<?> type, Object x) {
assert(!type.isPrimitive());
if (x == null) return true;
if (type.isInterface()) return true;
return type.isInstance(x);
}
/** If the invocation count hits the threshold we spin bytecodes and call that subsequently. */
private static final int COMPILE_THRESHOLD;
static {
COMPILE_THRESHOLD = Math.max(-1, MethodHandleStatics.COMPILE_THRESHOLD);
}
private int invocationCounter = 0; // a value of -1 indicates LambdaForm interpretation mode forever
private boolean forceInterpretation() {
return invocationCounter == -1;
}
/** Interpretively invoke this form on the given arguments. */
@Hidden
@DontInline
Object interpretWithArguments(Object... argumentValues) throws Throwable {
if (TRACE_INTERPRETER)
return interpretWithArgumentsTracing(argumentValues);
checkInvocationCounter();
assert(arityCheck(argumentValues));
Object[] values = Arrays.copyOf(argumentValues, names.length);
for (int i = argumentValues.length; i < values.length; i++) {
values[i] = interpretName(names[i], values);
}
Object rv = (result < 0) ? null : values[result];
assert(resultCheck(argumentValues, rv));
return rv;
}
/** Evaluate a single Name within this form, applying its function to its arguments. */
@Hidden
@DontInline
Object interpretName(Name name, Object[] values) throws Throwable {
if (TRACE_INTERPRETER)
traceInterpreter("| interpretName", name.debugString(), (Object[]) null);
Object[] arguments = Arrays.copyOf(name.arguments, name.arguments.length, Object[].class);
for (int i = 0; i < arguments.length; i++) {
Object a = arguments[i];
if (a instanceof Name n) {
int i2 = n.index();
assert(names[i2] == a);
a = values[i2];
arguments[i] = a;
}
}
return name.function.invokeWithArguments(arguments);
}
private void checkInvocationCounter() {
if (COMPILE_THRESHOLD != 0 &&
!forceInterpretation() && invocationCounter < COMPILE_THRESHOLD) {
invocationCounter++; // benign race
if (invocationCounter >= COMPILE_THRESHOLD) {
// Replace vmentry with a bytecode version of this LF.
compileToBytecode();
}
}
}
Object interpretWithArgumentsTracing(Object... argumentValues) throws Throwable {
traceInterpreter("[ interpretWithArguments", this, argumentValues);
if (!forceInterpretation() && invocationCounter < COMPILE_THRESHOLD) {
int ctr = invocationCounter++; // benign race
traceInterpreter("| invocationCounter", ctr);
if (invocationCounter >= COMPILE_THRESHOLD) {
compileToBytecode();
}
}
Object rval;
try {
assert(arityCheck(argumentValues));
Object[] values = Arrays.copyOf(argumentValues, names.length);
for (int i = argumentValues.length; i < values.length; i++) {
values[i] = interpretName(names[i], values);
}
rval = (result < 0) ? null : values[result];
} catch (Throwable ex) {
traceInterpreter("] throw =>", ex);
throw ex;
}
traceInterpreter("] return =>", rval);
return rval;
}
static void traceInterpreter(String event, Object obj, Object... args) {
if (TRACE_INTERPRETER) {
System.out.println("LFI: "+event+" "+(obj != null ? obj : "")+(args != null && args.length != 0 ? Arrays.asList(args) : ""));
}
}
static void traceInterpreter(String event, Object obj) {
traceInterpreter(event, obj, (Object[])null);
}
private boolean arityCheck(Object[] argumentValues) {
assert(argumentValues.length == arity) : arity+"!="+Arrays.asList(argumentValues)+".length";
// also check that the leading (receiver) argument is somehow bound to this LF:
assert(argumentValues[0] instanceof MethodHandle) : "not MH: " + argumentValues[0];
MethodHandle mh = (MethodHandle) argumentValues[0];
assert(mh.internalForm() == this);
// note: argument #0 could also be an interface wrapper, in the future
argumentTypesMatch(basicTypeSignature(), argumentValues);
return true;
}
private boolean resultCheck(Object[] argumentValues, Object result) {
MethodHandle mh = (MethodHandle) argumentValues[0];
MethodType mt = mh.type();
assert(valueMatches(returnType(), mt.returnType(), result));
return true;
}
public String toString() {
return debugString(-1);
}
String debugString(int indentLevel) {
String prefix = MethodHandle.debugPrefix(indentLevel);
String lambdaName = lambdaName();
StringBuilder buf = new StringBuilder(lambdaName);
buf.append("=Lambda(");
for (int i = 0; i < names.length; i++) {
if (i == arity) buf.append(")=>{");
Name n = names[i];