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 }