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