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 }