1 package jdk.incubator.code.runtime;
2
3 import java.lang.classfile.ClassBuilder;
4 import java.lang.classfile.CodeBuilder;
5 import java.lang.classfile.Opcode;
6 import java.lang.classfile.TypeKind;
7 import java.lang.classfile.constantpool.ConstantPoolBuilder;
8 import java.lang.classfile.constantpool.MethodRefEntry;
9 import java.lang.constant.ClassDesc;
10 import java.lang.constant.MethodTypeDesc;
11 import java.lang.invoke.CallSite;
12 import java.lang.invoke.LambdaConversionException;
13 import java.lang.invoke.LambdaMetafactory;
14 import java.lang.invoke.MethodHandle;
15 import java.lang.invoke.MethodHandles;
16 import java.lang.invoke.MethodHandles.Lookup;
17 import java.lang.invoke.MethodType;
18 import java.util.List;
19
20 import jdk.incubator.code.Op;
21 import jdk.incubator.code.Quoted;
22 import jdk.incubator.code.dialect.core.CoreOp.FuncOp;
23 import jdk.internal.access.JavaLangInvokeAccess;
24 import jdk.internal.access.SharedSecrets;
25
26 import static java.lang.classfile.ClassFile.ACC_PRIVATE;
27 import static java.lang.classfile.ClassFile.ACC_STATIC;
28 import static java.lang.classfile.ClassFile.ACC_SYNCHRONIZED;
29 import static java.lang.constant.ConstantDescs.*;
30 import java.lang.constant.DynamicConstantDesc;
31 import java.util.function.Function;
32
33 /**
34 * Provides runtime support for creating reflectable lambdas. A reflectable lambda is a lambda whose
35 * code model can be inspected using {@link Op#ofLambda(Object)}.
36 * @see LambdaMetafactory
37 * @see Op#ofLambda(Object)
38 */
39 public class ReflectableLambdaMetafactory {
40
41 private static final String NAME_METHOD_QUOTED = "__internal_quoted";
42 private static final String QUOTED_FIELD_NAME = "quoted";
43 private static final String MODEL_FIELD_NAME = "model";
44
45 static final ClassDesc CD_Quoted = Quoted.class.describeConstable().get();
46 static final ClassDesc CD_FuncOp = FuncOp.class.describeConstable().get();
47 static final ClassDesc CD_Op = Op.class.describeConstable().get();
48 static final MethodTypeDesc MTD_extractOp = MethodTypeDesc.of(CD_Quoted, CD_FuncOp, CD_Object.arrayType());
49 static final DynamicConstantDesc<?> DCD_CLASS_DATA = DynamicConstantDesc.ofNamed(BSM_CLASS_DATA, DEFAULT_NAME, CD_List);
50
51 private ReflectableLambdaMetafactory() {
52 // nope
53 }
54
55 /**
56 * Metafactory used to create a reflectable lambda.
57 * <p>
58 * The functionality provided by this metafactory is identical to that in
59 * {@link LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)}
60 * with one important difference: this metafactory expects the provided name to be encoded in the following form:
61 * <code>
62 * lambdaName=opMethodName
63 * </code>
64 * The {@code lambdaName} part of the name is passed to the regular metafactory method, along with all the other
65 * parameters unchanged. The {@code opMethod} part of the name is used to locate a method in the
66 * {@linkplain Lookup#lookupClass() lookup class} associated with the provided lookup. This method is expected
67 * to accept no parameters and return a {@link Op}, namely the model of the reflectable lambda.
68 * <p>
69 * This means the clients can pass the lambda returned by this factory to the {@link Op#ofLambda(Object)} method,
70 * to access the code model of the lambda expression dynamically.
71 *
72 * @param caller The lookup
73 * @param interfaceMethodName The name of the method to implement.
74 * This is encoded in the format described above.
75 * @param factoryType The expected signature of the {@code CallSite}.
76 * @param interfaceMethodType Signature and return type of method to be
77 * implemented by the function object.
78 * @param implementation A direct method handle describing the implementation
79 * method which should be called at invocation time.
80 * @param dynamicMethodType The signature and return type that should
81 * be enforced dynamically at invocation time.
82 * @return a CallSite whose target can be used to perform capture, generating
83 * a reflectable lambda instance implementing the interface named by {@code factoryType}.
84 * The code model for such instance can be inspected using {@link Op#ofLambda(Object)}.
85 *
86 * @throws LambdaConversionException If, after the lambda name is decoded,
87 * the parameters of the call are invalid for
88 * {@link LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)}
89 * @throws NullPointerException If any argument is {@code null}.
90 *
91 * @see LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)
92 * @see Op#ofLambda(Object)
93 */
94 public static CallSite metafactory(MethodHandles.Lookup caller,
95 String interfaceMethodName,
96 MethodType factoryType,
97 MethodType interfaceMethodType,
98 MethodHandle implementation,
99 MethodType dynamicMethodType)
100 throws LambdaConversionException {
101 DecodedName decodedName = findReflectableOpGetter(caller, interfaceMethodName);
102 LambdaFinisher finisher = new LambdaFinisher(caller.lookupClass(), factoryType.parameterList(), decodedName.opHandle);
103 return JLI_ACCESS.metafactoryInternal(caller, decodedName.name, factoryType, interfaceMethodType,
104 implementation, dynamicMethodType, finisher);
105 }
106
107 /**
108 * Metafactory used to create a reflectable lambda.
109 * <p>
110 * The functionality provided by this metafactory is identical to that in
111 * {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
112 * with one important difference: this metafactory expects the provided name to be encoded in the following form:
113 * <code>
114 * lambdaName=opMethodName
115 * </code>
116 * The {@code lambdaName} part of the name is passed to the regular metafactory method, along with all the other
117 * parameters unchanged. The {@code opMethod} part of the name is used to locate a method in the
118 * {@linkplain Lookup#lookupClass() lookup class} associated with the provided lookup. This method is expected
119 * to accept no parameters and return a {@link Op}, namely the model of the reflectable lambda.
120 * <p>
121 * This means the clients can pass the lambda returned by this factory to the {@link Op#ofLambda(Object)} method,
122 * to access the code model of the lambda expression dynamically.
123 *
124 * @param caller The lookup
125 * @param interfaceMethodName The name of the method to implement.
126 * This is encoded in the format described above.
127 * @param factoryType The expected signature of the {@code CallSite}.
128 * @param args An array of {@code Object} containing the required
129 * arguments {@code interfaceMethodType}, {@code implementation},
130 * {@code dynamicMethodType}, {@code flags}, and any
131 * optional arguments, as required by {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
132 * @return a CallSite whose target can be used to perform capture, generating
133 * a reflectable lambda instance implementing the interface named by {@code factoryType}.
134 * The code model for such instance can be inspected using {@link Op#ofLambda(Object)}.
135 *
136 * @throws LambdaConversionException If, after the lambda name is decoded,
137 * the parameters of the call are invalid for
138 * {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
139 * @throws NullPointerException If any argument, or any component of {@code args},
140 * is {@code null}.
141 * @throws IllegalArgumentException If {@code args} are invalid for
142 * {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
143 *
144 * @see LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)
145 * @see Op#ofLambda(Object)
146 */
147 public static CallSite altMetafactory(MethodHandles.Lookup caller,
148 String interfaceMethodName,
149 MethodType factoryType,
150 Object... args)
151 throws LambdaConversionException {
152 DecodedName decodedName = findReflectableOpGetter(caller, interfaceMethodName);
153 LambdaFinisher finisher = new LambdaFinisher(caller.lookupClass(), factoryType.parameterList(), decodedName.opHandle);
154 return JLI_ACCESS.altMetafactoryInternal(caller, decodedName.name, factoryType, finisher, args);
155 }
156
157 static final JavaLangInvokeAccess JLI_ACCESS = SharedSecrets.getJavaLangInvokeAccess();
158
159 record DecodedName(String name, MethodHandle opHandle) { }
160
161 private static DecodedName findReflectableOpGetter(MethodHandles.Lookup lookup, String interfaceMethodName) throws LambdaConversionException {
162 String[] implNameParts = interfaceMethodName.split("=");
163 if (implNameParts.length != 2) {
164 throw new LambdaConversionException("Bad method name: " + interfaceMethodName);
165 }
166 try {
167 return new DecodedName(
168 implNameParts[0],
169 lookup.findStatic(lookup.lookupClass(), implNameParts[1], MethodType.methodType(Op.class)));
170 } catch (ReflectiveOperationException ex) {
171 throw new LambdaConversionException(ex);
172 }
173 }
174
175 static class LambdaFinisher implements Function<ClassBuilder, Object> {
176
177 final ClassDesc lambdaClassSymbol;
178 final ClassDesc[] argDescs;
179 final MethodHandle opHandle;
180
181 public LambdaFinisher(Class<?> callerClass, List<Class<?>> parameterTypes, MethodHandle opHandle) throws LambdaConversionException {
182 this.lambdaClassSymbol = ClassDesc.ofInternalName(sanitizedTargetClassName(callerClass).concat("$$Lambda"));
183 this.argDescs = parameterTypes.stream().map(cls -> cls.describeConstable().get()).toArray(ClassDesc[]::new);
184 this.opHandle = opHandle;
185 }
186
187 @Override
188 public Object apply(ClassBuilder clb) {
189 // the field that will hold the quoted instance
190 clb.withField(QUOTED_FIELD_NAME, CD_Quoted, ACC_PRIVATE);
191 // the field that will hold the model
192 clb.withField(MODEL_FIELD_NAME, CD_FuncOp,
193 ACC_PRIVATE | ACC_STATIC);
194 // Generate method #__internal_quoted()
195 clb.withMethodBody(NAME_METHOD_QUOTED, MethodTypeDesc.of(CD_Quoted), ACC_PRIVATE, (cob) ->
196 cob.aload(0)
197 .invokevirtual(lambdaClassSymbol, "getQuoted", MethodTypeDesc.of(CD_Quoted))
198 .areturn());
199 // generate helper methods
200 /*
201 synchronized Quoted getQuoted() {
202 Quoted v = quoted;
203 if (v == null) {
204 v = quoted = Quoted.extractOp(getModel(), captures);
205 }
206 return v;
207 }
208 */
209 clb.withMethodBody("getQuoted", MethodTypeDesc.of(CD_Quoted),
210 ACC_PRIVATE + ACC_SYNCHRONIZED, cob ->
211 cob.aload(0)
212 .getfield(lambdaClassSymbol, QUOTED_FIELD_NAME, CD_Quoted)
213 .astore(1)
214 .aload(1)
215 .ifThen(Opcode.IFNULL, bcb -> {
216 bcb.aload(0) // will be used by putfield
217 .invokestatic(lambdaClassSymbol, "getModel", MethodTypeDesc.of(CD_FuncOp))
218 // load captured args in array
219 .loadConstant(argDescs.length)
220 .anewarray(CD_Object);
221 for (int i = 0; i < argDescs.length; i++) {
222 bcb.dup()
223 .loadConstant(i)
224 .aload(0)
225 .getfield(lambdaClassSymbol, "arg$" + (i + 1), argDescs[i]);
226 boxIfTypePrimitive(bcb, TypeKind.from(argDescs[i]));
227 bcb.aastore();
228 }
229 // invoke Quoted.extractOp
230 bcb.invokestatic(CD_Quoted, "extractOp", MTD_extractOp)
231 .dup_x1()
232 .putfield(lambdaClassSymbol, QUOTED_FIELD_NAME, CD_Quoted)
233 .astore(1);
234 })
235 .aload(1)
236 .areturn());
237 /*
238 private static synchronized CoreOp.FuncOp getModel() {
239 FuncOp v = model;
240 if (v == null) {
241 v = model = ...invoke lambda op building method...
242 }
243 return v;
244 }
245 */
246 clb.withMethodBody("getModel", MethodTypeDesc.of(CD_FuncOp),
247 ACC_PRIVATE + ACC_STATIC + ACC_SYNCHRONIZED, cob ->
248 cob.getstatic(lambdaClassSymbol, MODEL_FIELD_NAME, CD_FuncOp)
249 .astore(0)
250 .aload(0)
251 .ifThen(Opcode.IFNULL, bcb ->
252 // last item in the class data list is a method handle to get the op
253 bcb.ldc(DCD_CLASS_DATA)
254 .invokeinterface(CD_List, "getLast", MethodTypeDesc.of(CD_Object))
255 .checkcast(CD_MethodHandle)
256 .invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDesc.of(CD_Op))
257 .checkcast(CD_FuncOp)
258 .dup()
259 .putstatic(lambdaClassSymbol, MODEL_FIELD_NAME, CD_FuncOp)
260 .astore(0))
261 .aload(0)
262 .areturn());
263 // return opHandle as additional class data
264 return opHandle;
265 }
266
267 static void boxIfTypePrimitive(CodeBuilder cob, TypeKind tk) {
268 var cp = cob.constantPool();
269 switch (tk) {
270 case BOOLEAN -> cob.invokestatic(box(cp, CD_boolean, CD_Boolean));
271 case BYTE -> cob.invokestatic(box(cp, CD_byte, CD_Byte));
272 case CHAR -> cob.invokestatic(box(cp, CD_char, CD_Character));
273 case DOUBLE -> cob.invokestatic(box(cp, CD_double, CD_Double));
274 case FLOAT -> cob.invokestatic(box(cp, CD_float, CD_Float));
275 case INT -> cob.invokestatic(box(cp, CD_int, CD_Integer));
276 case LONG -> cob.invokestatic(box(cp, CD_long, CD_Long));
277 case SHORT -> cob.invokestatic(box(cp, CD_short, CD_Short));
278 }
279 }
280
281 private static MethodRefEntry box(ConstantPoolBuilder cp, ClassDesc primitive, ClassDesc target) {
282 return cp.methodRefEntry(target, "valueOf", MethodTypeDesc.of(target, primitive));
283 }
284
285 private static String sanitizedTargetClassName(Class<?> targetClass) {
286 String name = targetClass.getName();
287 if (targetClass.isHidden()) {
288 // use the original class name
289 name = name.replace('/', '_');
290 }
291 return name.replace('.', '/');
292 }
293 }
294 }