1 /*
  2  * Copyright (c) 2026, 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 package jdk.incubator.code.bytecode.impl;
 26 
 27 import java.lang.constant.ClassDesc;
 28 import java.lang.constant.DirectMethodHandleDesc;
 29 import java.lang.constant.MethodTypeDesc;
 30 import java.lang.invoke.LambdaMetafactory;
 31 import java.lang.invoke.MethodHandles;
 32 import java.lang.reflect.Method;
 33 import java.lang.reflect.Modifier;
 34 import java.util.ArrayList;
 35 import java.util.LinkedHashMap;
 36 import java.util.LinkedHashSet;
 37 import java.util.List;
 38 import java.util.SequencedMap;
 39 import java.util.Set;
 40 import java.util.stream.Stream;
 41 
 42 import jdk.incubator.code.Block;
 43 import jdk.incubator.code.CodeTransformer;
 44 import jdk.incubator.code.CodeType;
 45 import jdk.incubator.code.Op;
 46 import jdk.incubator.code.Quoted;
 47 import jdk.incubator.code.Value;
 48 import jdk.incubator.code.dialect.core.CoreOp;
 49 import jdk.incubator.code.dialect.core.CoreOp.FuncOp;
 50 import jdk.incubator.code.dialect.core.CoreType;
 51 import jdk.incubator.code.dialect.core.FunctionType;
 52 import jdk.incubator.code.dialect.core.VarType;
 53 import jdk.incubator.code.dialect.java.FieldRef;
 54 import jdk.incubator.code.dialect.java.JavaOp;
 55 import jdk.incubator.code.dialect.java.JavaType;
 56 import jdk.incubator.code.dialect.java.MethodRef;
 57 import jdk.incubator.code.extern.DialectFactory;
 58 import jdk.incubator.code.internal.OpBuilder;
 59 import jdk.incubator.code.runtime.ReflectableLambdaMetafactory;
 60 
 61 import static java.lang.constant.ConstantDescs.*;
 62 
 63 /**
 64  * Lambda expansion transformer generates a module with lambda operations replaced
 65  * by dynamic function calls and with synthetic functions for lambda bodies and
 66  * reflectable lambda model builders.
 67  */
 68 final class LambdaExpansionTransformer implements CodeTransformer {
 69 
 70     private static final DirectMethodHandleDesc DMHD_LAMBDA_METAFACTORY = ofCallsiteBootstrap(
 71             LambdaMetafactory.class.describeConstable().orElseThrow(),
 72             "metafactory",
 73             CD_CallSite, CD_MethodType, CD_MethodHandle, CD_MethodType);
 74 
 75     private static final DirectMethodHandleDesc DMHD_REFLECTABLE_LAMBDA_METAFACTORY = ofCallsiteBootstrap(
 76             ReflectableLambdaMetafactory.class.describeConstable().orElseThrow(),
 77             "metafactory",
 78             CD_CallSite, CD_MethodType, CD_MethodHandle, CD_MethodType);
 79 
 80     private final MethodHandles.Lookup lookup;
 81     private final Set<String> names;
 82     private final List<FuncOp> functions = new ArrayList<>();
 83     private final LinkedHashMap<String, FuncOp> modelsToBuild = new LinkedHashMap<>();
 84     private int nextLambdaIndex;
 85 
 86     private LambdaExpansionTransformer(MethodHandles.Lookup lookup, Set<String> names) {
 87         this.lookup = lookup;
 88         this.names = new LinkedHashSet<>(names);
 89     }
 90 
 91     static <O extends Op & Op.Invokable> CoreOp.ModuleOp transform(MethodHandles.Lookup lookup,
 92                                                                    SequencedMap<String, ? extends O> ops) {
 93         return new LambdaExpansionTransformer(lookup, ops.sequencedKeySet()).transform(ops);
 94     }
 95 
 96     private <O extends Op & Op.Invokable> CoreOp.ModuleOp transform(SequencedMap<String, ? extends O> ops) {
 97         for (var e : ops.sequencedEntrySet()) {
 98             functions.add(switch (e.getValue()) {
 99                 case FuncOp fop -> fop.transform(e.getKey(), this);
100                 case JavaOp.LambdaOp lop -> lambdaToFuncOp(e.getKey(), lop).transform(this);
101                 default -> throw new IllegalArgumentException("Unsupported invokable operation: " + e.getValue());
102             });
103         }
104         if (!modelsToBuild.isEmpty()) {
105             CoreOp.ModuleOp module = OpBuilder.createBuilderFunctions(
106                     modelsToBuild,
107                     b -> b.add(JavaOp.fieldLoad(
108                             FieldRef.field(JavaOp.class, "JAVA_DIALECT_FACTORY", DialectFactory.class))));
109             names.addAll(module.functionTable().sequencedKeySet());
110             for (FuncOp builder : module.functionTable().sequencedValues()) {
111                 // module may contain chunked large lambdas, which must also be expanded
112                 functions.add(builder.transform(this));
113             }
114         }
115         return CoreOp.module(functions);
116     }
117 
118     // LambdaMetafactory implementation methods take captures before lambda parameters.
119     private static FuncOp lambdaToFuncOp(String name, JavaOp.LambdaOp lop) {
120         List<Value> captures = lop.capturedValues();
121         FunctionType lambdaType = lop.invokableSignature();
122         ArrayList<CodeType> parameterTypes = new ArrayList<>(captures.size() + lambdaType.parameterTypes().size());
123         for (Value v : captures) {
124             parameterTypes.add(v.type() instanceof VarType vt ? vt.valueType() : v.type());
125         }
126         parameterTypes.addAll(lambdaType.parameterTypes());
127         return CoreOp.func(name, CoreType.functionType(lambdaType.returnType(), parameterTypes)).body(b -> {
128             int i = 0;
129             for (Value cv : captures) {
130                 Value v = b.parameters().get(i++);
131                 if (cv.type() instanceof VarType) {
132                     v = b.add(CoreOp.var(v));
133                 }
134                 b.context().mapValue(cv, v);
135             }
136             b.transformBody(lop.body(), b.parameters().subList(i, b.parameters().size()),
137                     CodeTransformer.COPYING_TRANSFORMER);
138         });
139     }
140 
141     private static String uniqueName(Set<String> names, String name) {
142         if (names.add(name)) {
143             return name;
144         }
145         for (int i = 0; ; i++) {
146             String n = name + "$" + i;
147             if (names.add(n)) {
148                 return n;
149             }
150         }
151     }
152 
153     @Override
154     public Block.Builder acceptOp(Block.Builder block, Op op) {
155         if (!(op instanceof JavaOp.LambdaOp lop)) {
156             block.add(op);
157             return block;
158         }
159         JavaType intfType = (JavaType) lop.functionalInterface();
160         MethodTypeDesc mtd = MethodRef.toNominalDescriptor(lop.invokableSignature());
161         try {
162             Class<?> intfClass = (Class<?>) intfType.erasure().resolve(lookup);
163             Method intfMethod = funcIntfMethod(intfClass, mtd);
164             List<Value> captures = lop.capturedValues();
165             int i = nextLambdaIndex++;
166             String implName = uniqueName(names, "lambda$" + i);
167             String intfMethodName = intfMethod.getName();
168             DirectMethodHandleDesc lambdaMetafactory = DMHD_LAMBDA_METAFACTORY;
169             if (lop.isReflectable()) {
170                 String modelName = uniqueName(names, "op$lambda$" + i);
171                 modelsToBuild.put(modelName, Quoted.embedOp(lop));
172                 lambdaMetafactory = DMHD_REFLECTABLE_LAMBDA_METAFACTORY;
173                 intfMethodName = intfMethodName + "=" + modelName;
174             }
175             functions.add(lambdaToFuncOp(implName, lop).transform(this));
176 
177             ClassDesc[] captureTypes = captures.stream()
178                     .map(Value::type).map(LambdaExpansionTransformer::toClassDesc).toArray(ClassDesc[]::new);
179             Op.Result r = block.add(new DynamicFuncCallOp(
180                     lop.functionalInterface(),
181                     block.context().getValues(captures),
182                     implName,
183                     lambdaMetafactory,
184                     intfMethodName,
185                     MethodTypeDesc.of(intfType.toNominalDescriptor(), captureTypes),
186                     MethodTypeDesc.of(
187                             intfMethod.getReturnType().describeConstable().get(),
188                             Stream.of(intfMethod.getParameterTypes()).map(t -> t.describeConstable().get()).toList()),
189                     mtd));
190             block.context().mapValue(lop.result(), r);
191             return block;
192         } catch (ReflectiveOperationException e) {
193             throw new IllegalArgumentException(e);
194         }
195     }
196 
197     private static ClassDesc toClassDesc(CodeType t) {
198         return switch (t) {
199             case VarType vt -> toClassDesc(vt.valueType());
200             case JavaType jt -> jt.toNominalDescriptor();
201             default -> throw new IllegalArgumentException("Bad type: " + t);
202         };
203     }
204 
205     private static Method funcIntfMethod(Class<?> intfc, MethodTypeDesc mtd) {
206         Method intfM = null;
207         for (Method m : intfc.getMethods()) {
208             String methodName = m.getName();
209             if (Modifier.isAbstract(m.getModifiers())
210                     && (m.getReturnType() != String.class
211                     || m.getParameterCount() != 0
212                     || !methodName.equals("toString"))
213                     && (m.getReturnType() != int.class
214                     || m.getParameterCount() != 0
215                     || !methodName.equals("hashCode"))
216                     && (m.getReturnType() != boolean.class
217                     || m.getParameterCount() != 1
218                     || m.getParameterTypes()[0] != Object.class
219                     || !methodName.equals("equals"))) {
220                 if (intfM == null) {
221                     intfM = m;
222                 } else if (!intfM.getName().equals(methodName)) {
223                     throw new IllegalArgumentException("Not a single-method interface: " + intfc.getName());
224                 }
225             }
226         }
227         if (intfM == null) {
228             throw new IllegalArgumentException("No method in: " + intfc.getName() + " matching: " + mtd);
229         }
230         return intfM;
231     }
232 }