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 }