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 }