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 }