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.ConstantDesc;
 29 import java.lang.constant.DirectMethodHandleDesc;
 30 import java.lang.constant.MethodTypeDesc;
 31 import java.lang.invoke.LambdaMetafactory;
 32 import java.lang.invoke.MethodHandles;
 33 import java.lang.reflect.Method;
 34 import java.lang.reflect.Modifier;
 35 import java.util.ArrayList;
 36 import java.util.Arrays;
 37 import java.util.HashMap;
 38 import java.util.LinkedHashMap;
 39 import java.util.LinkedHashSet;
 40 import java.util.List;
 41 import java.util.Map;
 42 import java.util.SequencedMap;
 43 import java.util.Set;
 44 import java.util.stream.Collectors;
 45 import java.util.stream.Stream;
 46 
 47 import jdk.incubator.code.Block;
 48 import jdk.incubator.code.CodeTransformer;
 49 import jdk.incubator.code.CodeType;
 50 import jdk.incubator.code.Op;
 51 import jdk.incubator.code.Quoted;
 52 import jdk.incubator.code.Value;
 53 import jdk.incubator.code.dialect.core.CoreOp;
 54 import jdk.incubator.code.dialect.core.CoreOp.FuncOp;
 55 import jdk.incubator.code.dialect.core.CoreType;
 56 import jdk.incubator.code.dialect.core.VarType;
 57 import jdk.incubator.code.dialect.java.FieldRef;
 58 import jdk.incubator.code.dialect.java.JavaOp;
 59 import jdk.incubator.code.dialect.java.JavaType;
 60 import jdk.incubator.code.dialect.java.MethodRef;
 61 import jdk.incubator.code.extern.DialectFactory;
 62 import jdk.incubator.code.internal.OpBuilder;
 63 import jdk.incubator.code.internal.ConstantValueAnalysis;
 64 import jdk.incubator.code.runtime.ReflectableLambdaMetafactory;
 65 
 66 import static java.lang.constant.ConstantDescs.*;
 67 
 68 /**
 69  * Lambda expansion transformer generates a module with lambda operations replaced
 70  * by dynamic function calls and with synthetic functions for lambda bodies and
 71  * reflectable lambda model builders.
 72  */
 73 final class LambdaExpansionTransformer implements CodeTransformer {
 74 
 75     private static final DirectMethodHandleDesc DMHD_LAMBDA_METAFACTORY = ofCallsiteBootstrap(
 76             LambdaMetafactory.class.describeConstable().orElseThrow(),
 77             "metafactory",
 78             CD_CallSite, CD_MethodType, CD_MethodHandle, CD_MethodType);
 79 
 80     private static final DirectMethodHandleDesc DMHD_LAMBDA_ALT_METAFACTORY = ofCallsiteBootstrap(
 81             LambdaMetafactory.class.describeConstable().orElseThrow(),
 82             "altMetafactory",
 83             CD_CallSite, CD_Object.arrayType());
 84 
 85     private static final DirectMethodHandleDesc DMHD_REFLECTABLE_LAMBDA_METAFACTORY = ofCallsiteBootstrap(
 86             ReflectableLambdaMetafactory.class.describeConstable().orElseThrow(),
 87             "metafactory",
 88             CD_CallSite, CD_MethodType, CD_MethodHandle, CD_MethodType);
 89 
 90     private static final DirectMethodHandleDesc DMHD_REFLECTABLE_LAMBDA_ALT_METAFACTORY = ofCallsiteBootstrap(
 91             ReflectableLambdaMetafactory.class.describeConstable().orElseThrow(),
 92             "altMetafactory",
 93             CD_CallSite, CD_Object.arrayType());
 94 
 95     private final MethodHandles.Lookup lookup;
 96     private final Set<String> names;
 97     private final List<FuncOp> functions = new ArrayList<>();
 98     private final LinkedHashMap<String, FuncOp> modelsToBuild = new LinkedHashMap<>();
 99     private int nextLambdaIndex;
100     // used for expression constants evaluation only
101     private final ConstantValueAnalysis constants = new ConstantValueAnalysis(_ -> List.of());
102 
103     private LambdaExpansionTransformer(MethodHandles.Lookup lookup, Set<String> names) {
104         this.lookup = lookup;
105         this.names = new LinkedHashSet<>(names);
106     }
107 
108     static <O extends Op & Op.Invokable> CoreOp.ModuleOp transform(MethodHandles.Lookup lookup,
109                                                                    SequencedMap<String, ? extends O> ops) {
110         return new LambdaExpansionTransformer(lookup, ops.sequencedKeySet()).transform(ops);
111     }
112 
113     private <O extends Op & Op.Invokable> CoreOp.ModuleOp transform(SequencedMap<String, ? extends O> ops) {
114         for (var e : ops.sequencedEntrySet()) {
115             functions.add(switch (e.getValue()) {
116                 case FuncOp fop -> fop.transform(e.getKey(), this);
117                 case JavaOp.LambdaOp lop -> quotationToImpl(e.getKey(), lambdaToQuotation(lop));
118                 default -> throw new IllegalArgumentException("Unsupported invokable operation: " + e.getValue());
119             });
120         }
121         if (!modelsToBuild.isEmpty()) {
122             CoreOp.ModuleOp module = OpBuilder.createBuilderFunctions(
123                     modelsToBuild,
124                     b -> b.add(JavaOp.fieldLoad(
125                             FieldRef.field(JavaOp.class, "JAVA_DIALECT_FACTORY", DialectFactory.class))));
126             names.addAll(module.functionTable().sequencedKeySet());
127             for (FuncOp builder : module.functionTable().sequencedValues()) {
128                 // module may contain chunked large lambdas, which must also be expanded
129                 functions.add(builder.transform(this));
130             }
131         }
132         return CoreOp.module(functions);
133     }
134 
135     // constructs lambda quotation function
136     private FuncOp lambdaToQuotation(JavaOp.LambdaOp lop) {
137         Map<Value, Object> capturedConstants = new HashMap<>();
138         lop.capturedValues().stream().forEach(v -> constants.evaluate(v).ifPresent(c -> capturedConstants.put(v, c)));
139         // embed captured constants into quotation function
140         return Quoted.embedOp(lop, capturedConstants);
141     }
142 
143     // transforms lambda quotation function to its implemetnation
144     private FuncOp quotationToImpl(String name, FuncOp quotation) {
145         // @@@ extract or pass the lambda body yield type
146         CodeType retType = quotation.elements().filter(JavaOp.LambdaOp.class::isInstance).findFirst()
147                 .map(e -> ((JavaOp.LambdaOp)e).body().yieldType()).orElseThrow();
148         return CoreOp.func(name, CoreType.functionType(retType)).body(b ->
149                 b.transformBody(quotation.body(),
150                                 quotation.parameters().stream().map(p -> b.parameter(p.type())).toList(),
151                                 (block, op) -> {
152                                     switch (op) {
153                                         case CoreOp.QuotedOp qo when qo.quotedOp() instanceof JavaOp.LambdaOp lambda ->
154                                             block.transformBody(lambda.body(),
155                                                                 lambda.parameters().stream().map(p -> b.parameter(p.type())).toList(),
156                                                                 this);
157                                         // discard quotation return
158                                         case CoreOp.ReturnOp _ -> {}
159                                         default -> block.add(op);
160                                     }
161                                     return block;
162                                 }));
163 
164     }
165 
166     private static String uniqueName(Set<String> names, String name, boolean reflectable) {
167         if (names.add(name) && (!reflectable || names.add("op$" + name))) {
168             return name;
169         }
170         for (int i = 0; ; i++) {
171             String n = name + "$" + i;
172             if (names.add(n) && (!reflectable || names.add("op$" + n))) {
173                 return n;
174             }
175         }
176     }
177 
178     @Override
179     public Block.Builder acceptOp(Block.Builder block, Op op) {
180         if (!(op instanceof JavaOp.LambdaOp lop)) {
181             block.add(op);
182             return block;
183         }
184         JavaType intfType = (JavaType) lop.functionalInterface();
185         List<CodeType> additionalIntfTypes = lop.additionalInterfaces();
186         MethodTypeDesc mtd = MethodRef.toNominalDescriptor(lop.invokableSignature());
187         try {
188             Class<?> intfClass = (Class<?>) intfType.erasure().resolve(lookup);
189             List<Class<?>> additionalIntfClasses = new ArrayList<>(additionalIntfTypes.size());
190             for (CodeType additionalIntfType : additionalIntfTypes) {
191                 additionalIntfClasses.add((Class<?>) ((JavaType) additionalIntfType).erasure().resolve(lookup));
192             }
193             Method intfMethod = funcIntfMethod(intfClass, additionalIntfClasses, mtd);
194             // filter out constant captures
195             List<Value> captures = lop.capturedValues().stream().filter(v -> constants.evaluate(v).isEmpty()).toList();
196             int i = nextLambdaIndex++;
197             String implName = uniqueName(names, "lambda$" + i, lop.isReflectable());
198             String intfMethodName = intfMethod.getName();
199             List<ConstantDesc> additionalBootstrapArgs = new ArrayList<>();
200             if (!additionalIntfClasses.isEmpty()) {
201                 // @@@ SeralizableLambda and bridges are currently unsupported
202                 additionalBootstrapArgs.add(LambdaMetafactory.FLAG_MARKERS);
203                 additionalBootstrapArgs.add(additionalIntfClasses.size());
204                 for (Class<?> cls : additionalIntfClasses) {
205                     additionalBootstrapArgs.add(cls.describeConstable().get());
206                 }
207             }
208             DirectMethodHandleDesc lambdaMetafactory = additionalBootstrapArgs.isEmpty()
209                     ? DMHD_LAMBDA_METAFACTORY : DMHD_LAMBDA_ALT_METAFACTORY;
210             FuncOp quotation = lambdaToQuotation(lop);
211             if (lop.isReflectable()) {
212                 String modelName = "op$" + implName;
213                 modelsToBuild.put(modelName, quotation);
214                 lambdaMetafactory = additionalBootstrapArgs.isEmpty()
215                         ? DMHD_REFLECTABLE_LAMBDA_METAFACTORY
216                         : DMHD_REFLECTABLE_LAMBDA_ALT_METAFACTORY;
217             }
218             functions.add(quotationToImpl(implName, quotation));
219 
220             ClassDesc[] captureTypes = captures.stream()
221                     .map(Value::type).map(LambdaExpansionTransformer::toClassDesc).toArray(ClassDesc[]::new);
222             Op.Result r = block.add(new DynamicFuncCallOp(
223                     lop.functionalInterface(),
224                     block.context().getValues(captures),
225                     implName,
226                     lambdaMetafactory,
227                     intfMethodName,
228                     MethodTypeDesc.of(intfType.toNominalDescriptor(), captureTypes),
229                     MethodTypeDesc.of(
230                             intfMethod.getReturnType().describeConstable().get(),
231                             Stream.of(intfMethod.getParameterTypes()).map(t -> t.describeConstable().get()).toList()),
232                     mtd,
233                     additionalBootstrapArgs));
234             block.context().mapValue(lop.result(), r);
235             return block;
236         } catch (ReflectiveOperationException e) {
237             throw new IllegalArgumentException(e);
238         }
239     }
240 
241     private static ClassDesc toClassDesc(CodeType t) {
242         return switch (t) {
243             case VarType vt -> toClassDesc(vt.valueType());
244             case JavaType jt -> jt.toNominalDescriptor();
245             default -> throw new IllegalArgumentException("Bad type: " + t);
246         };
247     }
248 
249     private static Method funcIntfMethod(Class<?> intfc, List<Class<?>> addintfcs, MethodTypeDesc mtd) {
250         Map<String, Set<Method>> methods = Stream.concat(Stream.of(intfc), addintfcs.stream())
251                 .flatMap(c -> Stream.of(c.getMethods()))
252                 .filter(m -> !Modifier.isStatic(m.getModifiers()) && !Modifier.isAbstract(m.getModifiers()))
253                 .collect(Collectors.groupingBy(Method::getName, Collectors.toSet()));
254         Method candidate = null;
255         for (Method m : intfc.getMethods()) {
256             String methodName = m.getName();
257             if (Modifier.isAbstract(m.getModifiers())
258                     && (m.getReturnType() != String.class
259                     || m.getParameterCount() != 0
260                     || !methodName.equals("toString"))
261                     && (m.getReturnType() != int.class
262                     || m.getParameterCount() != 0
263                     || !methodName.equals("hashCode"))
264                     && (m.getReturnType() != boolean.class
265                     || m.getParameterCount() != 1
266                     || m.getParameterTypes()[0] != Object.class
267                     || !methodName.equals("equals"))
268                     && methods.getOrDefault(methodName, Set.of()).stream().noneMatch(mth -> {
269                         return mth.getDeclaringClass() != m.getDeclaringClass()
270                                 && m.getDeclaringClass().isAssignableFrom(mth.getDeclaringClass())
271                                 && Arrays.equals(mth.getParameterTypes(), m.getParameterTypes());
272                     })) {
273                 if (candidate == null) {
274                     candidate = m;
275                 } else if (!m.getName().equals(candidate.getName())) {
276                     throw new IllegalArgumentException("Not a single-method interface: " + intfc.getName());
277                 }
278             }
279         }
280         if (candidate == null) {
281             throw new IllegalArgumentException("No method in: " + intfc.getName() + " matching: " + mtd);
282         }
283         return candidate;
284     }
285 }