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             functions.addAll(OpBuilder.createBuilderFunctions(
106                     modelsToBuild,
107                     b -> b.add(JavaOp.fieldLoad(
108                             FieldRef.field(JavaOp.class, "JAVA_DIALECT_FACTORY", DialectFactory.class))))
109                     .functionTable().sequencedValues());
110         }
111         return CoreOp.module(functions);
112     }
113 
114     // LambdaMetafactory implementation methods take captures before lambda parameters.
115     private static FuncOp lambdaToFuncOp(String name, JavaOp.LambdaOp lop) {
116         List<Value> captures = lop.capturedValues();
117         FunctionType lambdaType = lop.invokableSignature();
118         ArrayList<CodeType> parameterTypes = new ArrayList<>(captures.size() + lambdaType.parameterTypes().size());
119         for (Value v : captures) {
120             parameterTypes.add(v.type() instanceof VarType vt ? vt.valueType() : v.type());
121         }
122         parameterTypes.addAll(lambdaType.parameterTypes());
123         return CoreOp.func(name, CoreType.functionType(lambdaType.returnType(), parameterTypes)).body(b -> {
124             int i = 0;
125             for (Value cv : captures) {
126                 Value v = b.parameters().get(i++);
127                 if (cv.type() instanceof VarType) {
128                     v = b.add(CoreOp.var(v));
129                 }
130                 b.context().mapValue(cv, v);
131             }
132             b.transformBody(lop.body(), b.parameters().subList(i, b.parameters().size()),
133                     CodeTransformer.COPYING_TRANSFORMER);
134         });
135     }
136 
137     private static String uniqueName(Set<String> names, String name) {
138         if (names.add(name)) {
139             return name;
140         }
141         for (int i = 0; ; i++) {
142             String n = name + "$" + i;
143             if (names.add(n)) {
144                 return n;
145             }
146         }
147     }
148 
149     @Override
150     public Block.Builder acceptOp(Block.Builder block, Op op) {
151         if (!(op instanceof JavaOp.LambdaOp lop)) {
152             block.add(op);
153             return block;
154         }
155         JavaType intfType = (JavaType) lop.functionalInterface();
156         MethodTypeDesc mtd = MethodRef.toNominalDescriptor(lop.invokableSignature());
157         try {
158             Class<?> intfClass = (Class<?>) intfType.erasure().resolve(lookup);
159             Method intfMethod = funcIntfMethod(intfClass, mtd);
160             List<Value> captures = lop.capturedValues();
161             int i = nextLambdaIndex++;
162             String implName = uniqueName(names, "lambda$" + i);
163             String intfMethodName = intfMethod.getName();
164             DirectMethodHandleDesc lambdaMetafactory = DMHD_LAMBDA_METAFACTORY;
165             if (lop.isReflectable()) {
166                 String modelName = uniqueName(names, "op$lambda$" + i);
167                 modelsToBuild.put(modelName, Quoted.embedOp(lop));
168                 lambdaMetafactory = DMHD_REFLECTABLE_LAMBDA_METAFACTORY;
169                 intfMethodName = intfMethodName + "=" + modelName;
170             }
171             functions.add(lambdaToFuncOp(implName, lop).transform(this));
172 
173             ClassDesc[] captureTypes = captures.stream()
174                     .map(Value::type).map(LambdaExpansionTransformer::toClassDesc).toArray(ClassDesc[]::new);
175             Op.Result r = block.add(new DynamicFuncCallOp(
176                     lop.functionalInterface(),
177                     block.context().getValues(captures),
178                     implName,
179                     lambdaMetafactory,
180                     intfMethodName,
181                     MethodTypeDesc.of(intfType.toNominalDescriptor(), captureTypes),
182                     MethodTypeDesc.of(
183                             intfMethod.getReturnType().describeConstable().get(),
184                             Stream.of(intfMethod.getParameterTypes()).map(t -> t.describeConstable().get()).toList()),
185                     mtd));
186             block.context().mapValue(lop.result(), r);
187             return block;
188         } catch (ReflectiveOperationException e) {
189             throw new IllegalArgumentException(e);
190         }
191     }
192 
193     private static ClassDesc toClassDesc(CodeType t) {
194         return switch (t) {
195             case VarType vt -> toClassDesc(vt.valueType());
196             case JavaType jt -> jt.toNominalDescriptor();
197             default -> throw new IllegalArgumentException("Bad type: " + t);
198         };
199     }
200 
201     private static Method funcIntfMethod(Class<?> intfc, MethodTypeDesc mtd) {
202         Method intfM = null;
203         for (Method m : intfc.getMethods()) {
204             String methodName = m.getName();
205             if (Modifier.isAbstract(m.getModifiers())
206                     && (m.getReturnType() != String.class
207                     || m.getParameterCount() != 0
208                     || !methodName.equals("toString"))
209                     && (m.getReturnType() != int.class
210                     || m.getParameterCount() != 0
211                     || !methodName.equals("hashCode"))
212                     && (m.getReturnType() != boolean.class
213                     || m.getParameterCount() != 1
214                     || m.getParameterTypes()[0] != Object.class
215                     || !methodName.equals("equals"))) {
216                 if (intfM == null) {
217                     intfM = m;
218                 } else if (!intfM.getName().equals(methodName)) {
219                     throw new IllegalArgumentException("Not a single-method interface: " + intfc.getName());
220                 }
221             }
222         }
223         if (intfM == null) {
224             throw new IllegalArgumentException("No method in: " + intfc.getName() + " matching: " + mtd);
225         }
226         return intfM;
227     }
228 }