1 /*
2 * Copyright (c) 2024, 2025, 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
26 import jdk.incubator.code.*;
27 import jdk.incubator.code.dialect.core.CoreType;
28 import jdk.incubator.code.dialect.core.FunctionType;
29 import jdk.incubator.code.dialect.core.VarType;
30 import jdk.incubator.code.dialect.java.*;
31
32 import java.lang.classfile.Attributes;
33 import java.lang.classfile.ClassFile;
34 import java.lang.classfile.ClassModel;
35 import java.lang.classfile.CodeElement;
36 import java.lang.classfile.CodeModel;
37 import java.lang.classfile.Instruction;
38 import java.lang.classfile.Label;
39 import java.lang.classfile.MethodModel;
40 import java.lang.classfile.Opcode;
41 import java.lang.classfile.PseudoInstruction;
42 import java.lang.classfile.TypeKind;
43 import java.lang.classfile.attribute.StackMapFrameInfo;
44 import java.lang.classfile.instruction.*;
45 import java.lang.constant.ClassDesc;
46 import java.lang.constant.ConstantDesc;
47 import java.lang.constant.ConstantDescs;
48 import java.lang.constant.DirectMethodHandleDesc;
49 import java.lang.constant.DynamicConstantDesc;
50 import java.lang.constant.MethodTypeDesc;
51 import java.lang.invoke.CallSite;
52 import java.lang.invoke.MethodHandle;
53 import java.lang.reflect.AccessFlag;
54
55 import jdk.incubator.code.dialect.core.CoreOp;
56 import jdk.incubator.code.dialect.core.NormalizeBlocksTransformer;
57
58 import java.util.ArrayDeque;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.BitSet;
62 import java.util.Collections;
63 import java.util.Deque;
64 import java.util.HashMap;
65 import java.util.IdentityHashMap;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.stream.Collectors;
69 import java.util.stream.IntStream;
70 import java.util.stream.Stream;
71
72 import static java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo.*;
73
74 public final class BytecodeLift {
75
76 private static final ClassDesc CD_LambdaMetafactory = ClassDesc.ofDescriptor("Ljava/lang/invoke/LambdaMetafactory;");
77 private static final ClassDesc CD_StringConcatFactory = ClassDesc.ofDescriptor("Ljava/lang/invoke/StringConcatFactory;");
78 private static final JavaType MHS_LOOKUP = JavaType.type(ConstantDescs.CD_MethodHandles_Lookup);
79 private static final JavaType MH = JavaType.type(ConstantDescs.CD_MethodHandle);
80 private static final JavaType MT = JavaType.type(ConstantDescs.CD_MethodType);
81 private static final JavaType CLASS_ARRAY = JavaType.array(JavaType.J_L_CLASS);
82 private static final MethodRef LCMP = MethodRef.method(JavaType.J_L_LONG, "compare", JavaType.INT, JavaType.LONG, JavaType.LONG);
83 private static final MethodRef FCMP = MethodRef.method(JavaType.J_L_FLOAT, "compare", JavaType.INT, JavaType.FLOAT, JavaType.FLOAT);
84 private static final MethodRef DCMP = MethodRef.method(JavaType.J_L_DOUBLE, "compare", JavaType.INT, JavaType.DOUBLE, JavaType.DOUBLE);
85 private static final MethodRef LOOKUP = MethodRef.method(JavaType.type(ConstantDescs.CD_MethodHandles), "lookup", MHS_LOOKUP);
86 private static final MethodRef FIND_STATIC = MethodRef.method(MHS_LOOKUP, "findStatic", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, MT);
87 private static final MethodRef FIND_VIRTUAL = MethodRef.method(MHS_LOOKUP, "findVirtual", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, MT);
88 private static final MethodRef FIND_CONSTRUCTOR = MethodRef.method(MHS_LOOKUP, "findConstructor", MH, JavaType.J_L_CLASS, MT);
89 private static final MethodRef FIND_GETTER = MethodRef.method(MHS_LOOKUP, "findGetter", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, JavaType.J_L_CLASS);
90 private static final MethodRef FIND_STATIC_GETTER = MethodRef.method(MHS_LOOKUP, "findStaticGetter", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, JavaType.J_L_CLASS);
91 private static final MethodRef FIND_SETTER = MethodRef.method(MHS_LOOKUP, "findSetter", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, JavaType.J_L_CLASS);
92 private static final MethodRef FIND_STATIC_SETTER = MethodRef.method(MHS_LOOKUP, "findStaticSetter", MH, JavaType.J_L_CLASS, JavaType.J_L_STRING, JavaType.J_L_CLASS);
93 private static final MethodRef METHOD_TYPE_0 = MethodRef.method(MT, "methodType", MT, JavaType.J_L_CLASS);
94 private static final MethodRef METHOD_TYPE_1 = MethodRef.method(MT, "methodType", MT, JavaType.J_L_CLASS, JavaType.J_L_CLASS);
95 private static final MethodRef METHOD_TYPE_L = MethodRef.method(MT, "methodType", MT, JavaType.J_L_CLASS, CLASS_ARRAY);
96
97 private final Block.Builder entryBlock;
98 private final List<Value> initialValues;
99 private final ClassModel classModel;
100 private final List<Label> exceptionHandlers;
101 private final Map<Integer, Block.Builder> exceptionHandlerBlocks;
102 private final BitSet actualEreStack;
103 private final Map<Label, BitSet> exceptionHandlersMap;
104 private final Map<Label, Block.Builder> blockMap;
105 private final List<CodeElement> elements;
106 private final Deque<Value> stack;
107 private final Deque<ClassDesc> newStack;
108 private final List<ExceptionCatch> ecs;
109 private Block.Builder currentBlock;
110
111 private BytecodeLift(Block.Builder entryBlock, ClassModel classModel, CodeModel codeModel, Value... capturedValues) {
112 this.entryBlock = entryBlock;
113 this.initialValues = Stream.concat(Stream.of(capturedValues), entryBlock.parameters().stream()).toList();
114 this.currentBlock = entryBlock;
115 this.classModel = classModel;
116 this.exceptionHandlers = new ArrayList<>();
117 this.exceptionHandlerBlocks = new HashMap<>();
118 this.actualEreStack = new BitSet();
119 this.newStack = new ArrayDeque<>();
120 this.elements = codeModel.elementList();
121 this.stack = new ArrayDeque<>();
122 this.exceptionHandlersMap = new IdentityHashMap<>();
123 this.blockMap = codeModel.findAttribute(Attributes.stackMapTable()).map(sma ->
124 sma.entries().stream().collect(Collectors.toUnmodifiableMap(
125 StackMapFrameInfo::target,
126 smfi -> entryBlock.block(toBlockParams(smfi.stack()))))).orElseGet(Map::of);
127 this.ecs = codeModel.exceptionHandlers();
128 for (var ec : ecs.reversed()) {
129 if (exceptionHandlers.indexOf(ec.handler()) < 0) {
130 exceptionHandlers.add(ec.handler());
131 }
132 }
133 }
134
135 private List<CodeType> toBlockParams(List<StackMapFrameInfo.VerificationTypeInfo> vtis) {
136 ArrayList<CodeType> params = new ArrayList<>(vtis.size());
137 for (int i = vtis.size() - 1; i >= 0; i--) {
138 var vti = vtis.get(i);
139 switch (vti) {
140 case INTEGER -> params.add(UnresolvedType.unresolvedInt());
141 case FLOAT -> params.add(JavaType.FLOAT);
142 case DOUBLE -> params.add(JavaType.DOUBLE);
143 case LONG -> params.add(JavaType.LONG);
144 case NULL -> params.add(UnresolvedType.unresolvedRef());
145 case UNINITIALIZED_THIS ->
146 params.add(JavaType.type(classModel.thisClass().asSymbol()));
147 case StackMapFrameInfo.ObjectVerificationTypeInfo ovti ->
148 params.add(JavaType.type(ovti.classSymbol()));
149
150 // Unitialized entry (a new object before its constructor is called)
151 // must be skipped from block parameters because they do not exist in code reflection model
152 case StackMapFrameInfo.UninitializedVerificationTypeInfo _ -> {}
153 default ->
154 throw new IllegalArgumentException("Unexpected VTI: " + vti);
155 }
156 }
157 return params;
158 }
159
160 private Op.Result op(Op op) {
161 return currentBlock.op(op);
162 }
163
164 // Lift to core dialect
165 public static CoreOp.FuncOp lift(byte[] classdata, String methodName) {
166 return lift(classdata, methodName, null);
167 }
168
169 public static CoreOp.FuncOp lift(byte[] classdata, String methodName, MethodTypeDesc methodType) {
170 return lift(ClassFile.of(
171 ClassFile.DebugElementsOption.DROP_DEBUG,
172 ClassFile.LineNumbersOption.DROP_LINE_NUMBERS).parse(classdata).methods().stream()
173 .filter(mm -> mm.methodName().equalsString(methodName) && (methodType == null || mm.methodTypeSymbol().equals(methodType)))
174 .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown method: " + methodName)));
175 }
176
177 public static CoreOp.FuncOp lift(MethodModel methodModel) {
178 ClassModel classModel = methodModel.parent().orElseThrow();
179 MethodTypeDesc mDesc = methodModel.methodTypeSymbol();
180 if (!methodModel.flags().has(AccessFlag.STATIC)) {
181 mDesc = mDesc.insertParameterTypes(0, classModel.thisClass().asSymbol());
182 }
183 return NormalizeBlocksTransformer.transform(
184 UnresolvedTypesTransformer.transform(
185 SlotToVarTransformer.transform(
186 CoreOp.func(methodModel.methodName().stringValue(),
187 MethodRef.ofNominalDescriptor(mDesc)).body(entryBlock ->
188 new BytecodeLift(entryBlock,
189 classModel,
190 methodModel.code().orElseThrow()).liftBody()))));
191 }
192
193 private void liftBody() {
194 // store entry block
195 int slot = 0;
196 for (var ep : initialValues) {
197 op(SlotOp.store(slot, ep));
198 slot += ep.type().equals(JavaType.LONG) || ep.type().equals(JavaType.DOUBLE) ? 2 : 1;
199 }
200
201 // fill exceptionHandlersMap
202 BitSet eStack = new BitSet();
203 for (var e : elements) {
204 if (e instanceof LabelTarget lt) {
205 BitSet newEreStack = null;
206 for (var er : ecs) {
207 if (lt.label() == er.tryStart() || lt.label() == er.tryEnd()) {
208 if (newEreStack == null) newEreStack = (BitSet)eStack.clone();
209
210 newEreStack.set(exceptionHandlers.indexOf(er.handler()), lt.label() == er.tryStart());
211 }
212 }
213 if (newEreStack != null || blockMap.containsKey(lt.label())) {
214 if (newEreStack != null) eStack = newEreStack;
215 exceptionHandlersMap.put(lt.label(), eStack);
216 }
217 }
218 }
219
220 for (int i = 0; i < elements.size(); i++) {
221 switch (elements.get(i)) {
222 case ExceptionCatch _ -> {
223 // Exception blocks are inserted by label target (below)
224 }
225 case LabelTarget lt -> {
226 BitSet newEreStack = exceptionHandlersMap.get(lt.label());
227 if (newEreStack != null) {
228 Block.Builder target = blockMap.get(lt.label());
229 if (target != null) {
230 if (currentBlock != null) {
231 // Transition to a branch target or a handler
232 ereTransit(actualEreStack, newEreStack, currentBlock, target, stackValues(target), exceptionHandlers.indexOf(lt.label()));
233 }
234 currentBlock = target;
235 stack.clear();
236 stack.addAll(target.parameters());
237 } else if (currentBlock != null && !actualEreStack.equals(newEreStack)) {
238 // Transition to a block with a different ERE stack
239 Block.Builder next = entryBlock.block();
240 ereTransit(actualEreStack, newEreStack, currentBlock, next, List.of(), -1);
241 currentBlock = next;
242 }
243 actualEreStack.clear();
244 actualEreStack.or(newEreStack);
245 }
246 }
247 case BranchInstruction inst when isUnconditionalBranch(inst.opcode()) -> {
248 Block.Builder target = blockMap.get(inst.target());
249 ereTransit(actualEreStack, exceptionHandlersMap.get(inst.target()), currentBlock, target, stackValues(target), exceptionHandlers.indexOf(inst.target()));
250 endOfFlow();
251 }
252 case BranchInstruction inst -> {
253 // Conditional branch
254 Value operand = stack.pop();
255 Op cop = switch (inst.opcode()) {
256 case IFNE -> JavaOp.eq(operand, liftConstant(0));
257 case IFEQ -> JavaOp.neq(operand, liftConstant(0));
258 case IFGE -> JavaOp.lt(operand, liftConstant(0));
259 case IFLE -> JavaOp.gt(operand, liftConstant(0));
260 case IFGT -> JavaOp.le(operand, liftConstant(0));
261 case IFLT -> JavaOp.ge(operand, liftConstant(0));
262 case IFNULL -> JavaOp.neq(operand, liftConstant(null));
263 case IFNONNULL -> JavaOp.eq(operand, liftConstant(null));
264 case IF_ICMPNE -> JavaOp.eq(stack.pop(), operand);
265 case IF_ICMPEQ -> JavaOp.neq(stack.pop(), operand);
266 case IF_ICMPGE -> JavaOp.lt(stack.pop(), operand);
267 case IF_ICMPLE -> JavaOp.gt(stack.pop(), operand);
268 case IF_ICMPGT -> JavaOp.le(stack.pop(), operand);
269 case IF_ICMPLT -> JavaOp.ge(stack.pop(), operand);
270 case IF_ACMPEQ -> JavaOp.neq(stack.pop(), operand);
271 case IF_ACMPNE -> JavaOp.eq(stack.pop(), operand);
272 default -> throw new UnsupportedOperationException("Unsupported branch instruction: " + inst);
273 };
274 Block.Builder branch = targetBlockForBranch(inst.target());
275 Block.Builder next = entryBlock.block();
276 op(CoreOp.conditionalBranch(op(cop),
277 next.reference(),
278 successorWithStack(branch)));
279 currentBlock = next;
280 }
281 case LookupSwitchInstruction si -> {
282 liftSwitch(si.defaultTarget(), si.cases());
283 }
284 case TableSwitchInstruction si -> {
285 liftSwitch(si.defaultTarget(), si.cases());
286 }
287 case ReturnInstruction inst when inst.typeKind() == TypeKind.VOID -> {
288 op(CoreOp.return_());
289 endOfFlow();
290 }
291 case ReturnInstruction _ -> {
292 op(CoreOp.return_(stack.pop()));
293 endOfFlow();
294 }
295 case ThrowInstruction _ -> {
296 op(JavaOp.throw_(stack.pop()));
297 endOfFlow();
298 }
299 case LoadInstruction inst -> {
300 stack.push(op(SlotOp.load(inst.slot(), inst.typeKind())));
301 }
302 case StoreInstruction inst -> {
303 op(SlotOp.store(inst.slot(), stack.pop()));
304 }
305 case IncrementInstruction inst -> {
306 op(SlotOp.store(inst.slot(), op(JavaOp.add(op(SlotOp.load(inst.slot(), TypeKind.INT)), liftConstant(inst.constant())))));
307 }
308 case ConstantInstruction inst -> {
309 stack.push(liftConstant(inst.constantValue()));
310 }
311 case ConvertInstruction inst -> {
312 stack.push(op(JavaOp.conv(switch (inst.toType()) {
313 case BYTE -> JavaType.BYTE;
314 case SHORT -> JavaType.SHORT;
315 case INT -> JavaType.INT;
316 case FLOAT -> JavaType.FLOAT;
317 case LONG -> JavaType.LONG;
318 case DOUBLE -> JavaType.DOUBLE;
319 case CHAR -> JavaType.CHAR;
320 case BOOLEAN -> JavaType.BOOLEAN;
321 default ->
322 throw new IllegalArgumentException("Unsupported conversion target: " + inst.toType());
323 }, stack.pop())));
324 }
325 case OperatorInstruction inst -> {
326 TypeKind tk = inst.typeKind();
327 Value operand = stack.pop();
328 stack.push(op(switch (inst.opcode()) {
329 case IADD, LADD, FADD, DADD ->
330 JavaOp.add(stack.pop(), operand);
331 case ISUB, LSUB, FSUB, DSUB ->
332 JavaOp.sub(stack.pop(), operand);
333 case IMUL, LMUL, FMUL, DMUL ->
334 JavaOp.mul(stack.pop(), operand);
335 case IDIV, LDIV, FDIV, DDIV ->
336 JavaOp.div(stack.pop(), operand);
337 case IREM, LREM, FREM, DREM ->
338 JavaOp.mod(stack.pop(), operand);
339 case INEG, LNEG, FNEG, DNEG ->
340 JavaOp.neg(operand);
341 case ARRAYLENGTH ->
342 JavaOp.arrayLength(operand);
343 case IAND, LAND ->
344 JavaOp.and(stack.pop(), operand);
345 case IOR, LOR ->
346 JavaOp.or(stack.pop(), operand);
347 case IXOR, LXOR ->
348 JavaOp.xor(stack.pop(), operand);
349 case ISHL, LSHL ->
350 JavaOp.lshl(stack.pop(), operand);
351 case ISHR, LSHR ->
352 JavaOp.ashr(stack.pop(), operand);
353 case IUSHR, LUSHR ->
354 JavaOp.lshr(stack.pop(), operand);
355 case LCMP ->
356 JavaOp.invoke(LCMP, stack.pop(), operand);
357 case FCMPL, FCMPG ->
358 JavaOp.invoke(FCMP, stack.pop(), operand);
359 case DCMPL, DCMPG ->
360 JavaOp.invoke(DCMP, stack.pop(), operand);
361 default ->
362 throw new IllegalArgumentException("Unsupported operator opcode: " + inst.opcode());
363 }));
364 }
365 case FieldInstruction inst -> {
366 FieldRef fd = FieldRef.field(
367 JavaType.type(inst.owner().asSymbol()),
368 inst.name().stringValue(),
369 JavaType.type(inst.typeSymbol()));
370 switch (inst.opcode()) {
371 case GETFIELD ->
372 stack.push(op(JavaOp.fieldLoad(fd, stack.pop())));
373 case GETSTATIC ->
374 stack.push(op(JavaOp.fieldLoad(fd)));
375 case PUTFIELD -> {
376 Value value = stack.pop();
377 op(JavaOp.fieldStore(fd, stack.pop(), value));
378 }
379 case PUTSTATIC ->
380 op(JavaOp.fieldStore(fd, stack.pop()));
381 default ->
382 throw new IllegalArgumentException("Unsupported field opcode: " + inst.opcode());
383 }
384 }
385 case ArrayStoreInstruction _ -> {
386 Value value = stack.pop();
387 Value index = stack.pop();
388 op(JavaOp.arrayStoreOp(stack.pop(), index, value));
389 }
390 case ArrayLoadInstruction ali -> {
391 Value index = stack.pop();
392 Value array = stack.pop();
393 if (array.type() instanceof UnresolvedType) {
394 stack.push(op(JavaOp.arrayLoadOp(array, index, switch (ali.typeKind()) {
395 case BYTE -> UnresolvedType.unresolvedInt(); // @@@ Create UnresolvedType.unresolvedByteOrBoolean();
396 case CHAR -> JavaType.CHAR;
397 case DOUBLE -> JavaType.DOUBLE;
398 case FLOAT -> JavaType.FLOAT;
399 case INT -> JavaType.INT;
400 case LONG -> JavaType.LONG;
401 case SHORT -> JavaType.SHORT;
402 case REFERENCE -> UnresolvedType.unresolvedRef();
403 case BOOLEAN, VOID -> throw new IllegalArgumentException("Unexpected array load instruction type");
404 })));
405 } else {
406 stack.push(op(JavaOp.arrayLoadOp(array, index)));
407 }
408 }
409 case InvokeInstruction inst -> {
410 FunctionType mType = MethodRef.ofNominalDescriptor(inst.typeSymbol());
411 List<Value> operands = new ArrayList<>();
412 for (var _ : mType.parameterTypes()) {
413 operands.add(stack.pop());
414 }
415 MethodRef mDesc = MethodRef.method(
416 JavaType.type(inst.owner().asSymbol()),
417 inst.name().stringValue(),
418 mType);
419 Op.Result result = switch (inst.opcode()) {
420 case INVOKEVIRTUAL, INVOKEINTERFACE -> {
421 operands.add(stack.pop());
422 yield op(JavaOp.invoke(JavaOp.InvokeOp.InvokeKind.INSTANCE, false,
423 mDesc.signature().returnType(), mDesc, operands.reversed()));
424 }
425 case INVOKESTATIC ->
426 op(JavaOp.invoke(JavaOp.InvokeOp.InvokeKind.STATIC, false,
427 mDesc.signature().returnType(), mDesc, operands.reversed()));
428 case INVOKESPECIAL -> {
429 if (inst.owner().asSymbol().equals(newStack.peek()) && inst.name().equalsString(ConstantDescs.INIT_NAME)) {
430 newStack.pop();
431 yield op(JavaOp.new_(
432 MethodRef.constructor(
433 mDesc.refType(),
434 mType.parameterTypes()),
435 operands.reversed()));
436 } else {
437 operands.add(stack.pop());
438 yield op(JavaOp.invoke(JavaOp.InvokeOp.InvokeKind.SUPER, false,
439 mDesc.signature().returnType(), mDesc, operands.reversed()));
440 }
441 }
442 default ->
443 throw new IllegalArgumentException("Unsupported invocation opcode: " + inst.opcode());
444 };
445 if (!result.type().equals(JavaType.VOID)) {
446 stack.push(result);
447 }
448 }
449 case InvokeDynamicInstruction inst when inst.bootstrapMethod().kind() == DirectMethodHandleDesc.Kind.STATIC -> {
450 DirectMethodHandleDesc bsm = inst.bootstrapMethod();
451 ClassDesc bsmOwner = bsm.owner();
452 if (bsmOwner.equals(CD_LambdaMetafactory)
453 && inst.bootstrapArgs().get(0) instanceof MethodTypeDesc mtd
454 && inst.bootstrapArgs().get(1) instanceof DirectMethodHandleDesc dmhd) {
455
456 var capturedValues = new Value[dmhd.invocationType().parameterCount() - mtd.parameterCount()];
457 for (int ci = capturedValues.length - 1; ci >= 0; ci--) {
458 capturedValues[ci] = stack.pop();
459 }
460 for (int ci = capturedValues.length; ci < inst.typeSymbol().parameterCount(); ci++) {
461 stack.pop();
462 }
463 MethodTypeDesc mt = dmhd.invocationType();
464 if (capturedValues.length > 0) {
465 mt = mt.dropParameterTypes(0, capturedValues.length);
466 }
467 FunctionType lambdaFunc = CoreType.functionType(JavaType.type(mt.returnType()),
468 mt.parameterList().stream().map(JavaType::type).toList());
469 JavaOp.LambdaOp.Builder lambda = JavaOp.lambda(currentBlock.parentBody(),
470 lambdaFunc,
471 JavaType.type(inst.typeSymbol().returnType()));
472 // if ReflectableLambdaMetafactory is used, the lambda is reflectable
473 if (bsm.owner().displayName().equals("jdk.incubator.code.runtime.ReflectableLambdaMetafactory")) {
474 lambda = lambda.reflectable();
475 }
476
477 if (dmhd.methodName().startsWith("lambda$") && dmhd.owner().equals(classModel.thisClass().asSymbol())) {
478 // inline lambda impl method
479 MethodModel implMethod = classModel.methods().stream().filter(m -> m.methodName().equalsString(dmhd.methodName())).findFirst().orElseThrow();
480 stack.push(op(lambda.body(eb -> new BytecodeLift(eb,
481 classModel,
482 implMethod.code().orElseThrow(),
483 capturedValues).liftBody())));
484 } else {
485 // lambda call to a MH
486 stack.push(op(lambda.body(eb -> {
487 Op.Result ret = eb.op(JavaOp.invoke(
488 MethodRef.method(JavaType.type(dmhd.owner()),
489 dmhd.methodName(),
490 lambdaFunc.returnType(),
491 lambdaFunc.parameterTypes()),
492 Stream.concat(Arrays.stream(capturedValues), eb.parameters().stream()).toArray(Value[]::new)));
493 eb.op(ret.type().equals(JavaType.VOID) ? CoreOp.return_() : CoreOp.return_(ret));
494 })));
495 }
496 } else if (bsmOwner.equals(CD_StringConcatFactory)) {
497 int argsCount = inst.typeSymbol().parameterCount();
498 Deque<Value> args = new ArrayDeque<>(argsCount);
499 for (int ai = 0; ai < argsCount; ai++) {
500 args.push(stack.pop());
501 }
502 Value res = null;
503 if (bsm.methodName().equals("makeConcat")) {
504 for (Value argVal : args) {
505 res = res == null ? argVal : op(JavaOp.concat(res, argVal));
506 }
507 } else {
508 assert bsm.methodName().equals("makeConcatWithConstants");
509 var bsmArgs = inst.bootstrapArgs();
510 String recipe = (String)(bsmArgs.getFirst());
511 int bsmArg = 1;
512 for (int ri = 0; ri < recipe.length(); ri++) {
513 Value argVal = switch (recipe.charAt(ri)) {
514 case '\u0001' -> args.pop();
515 case '\u0002' -> liftConstant(bsmArgs.get(bsmArg++));
516 default -> {
517 char c;
518 int start = ri;
519 while (ri < recipe.length() && (c = recipe.charAt(ri)) != '\u0001' && c != '\u0002') ri++;
520 yield liftConstant(recipe.substring(start, ri--));
521 }
522 };
523 res = res == null ? argVal : op(JavaOp.concat(res, argVal));
524 }
525 }
526 if (res != null) stack.push(res);
527 } else {
528 MethodTypeDesc mtd = inst.typeSymbol();
529
530 //bootstrap
531 MethodTypeDesc bsmDesc = bsm.invocationType();
532 MethodRef bsmRef = MethodRef.method(JavaType.type(bsmOwner),
533 bsm.methodName(),
534 JavaType.type(bsmDesc.returnType()),
535 bsmDesc.parameterList().stream().map(JavaType::type).toArray(CodeType[]::new));
536
537 Value[] bootstrapArgs = liftBootstrapArgs(bsmDesc, inst.name().toString(), mtd, inst.bootstrapArgs());
538 Value methodHandle = op(JavaOp.invoke(MethodRef.method(CallSite.class, "dynamicInvoker", MethodHandle.class),
539 op(JavaOp.invoke(JavaType.type(ConstantDescs.CD_CallSite), bsmRef, bootstrapArgs))));
540
541 //invocation
542 List<Value> operands = new ArrayList<>();
543 for (int c = 0; c < mtd.parameterCount(); c++) {
544 operands.add(stack.pop());
545 }
546 operands.add(methodHandle);
547 MethodRef mDesc = MethodRef.method(JavaType.type(ConstantDescs.CD_MethodHandle),
548 "invokeExact",
549 MethodRef.ofNominalDescriptor(mtd));
550 Op.Result result = op(JavaOp.invoke(mDesc, operands.reversed()));
551 if (!result.type().equals(JavaType.VOID)) {
552 stack.push(result);
553 }
554 }
555 }
556 case NewObjectInstruction inst -> {
557 // Skip over this and the dup to process the invoke special
558 if (i + 2 < elements.size() - 1
559 && elements.get(i + 1) instanceof StackInstruction dup
560 && dup.opcode() == Opcode.DUP) {
561 i++;
562 newStack.push(inst.className().asSymbol());
563 } else {
564 throw new UnsupportedOperationException("New must be followed by dup");
565 }
566 }
567 case NewPrimitiveArrayInstruction inst -> {
568 stack.push(op(JavaOp.newArray(
569 switch (inst.typeKind()) {
570 case BOOLEAN -> JavaType.BOOLEAN_ARRAY;
571 case BYTE -> JavaType.BYTE_ARRAY;
572 case CHAR -> JavaType.CHAR_ARRAY;
573 case DOUBLE -> JavaType.DOUBLE_ARRAY;
574 case FLOAT -> JavaType.FLOAT_ARRAY;
575 case INT -> JavaType.INT_ARRAY;
576 case LONG -> JavaType.LONG_ARRAY;
577 case SHORT -> JavaType.SHORT_ARRAY;
578 default ->
579 throw new UnsupportedOperationException("Unsupported new primitive array type: " + inst.typeKind());
580 },
581 stack.pop())));
582 }
583 case NewReferenceArrayInstruction inst -> {
584 stack.push(op(JavaOp.newArray(
585 JavaType.type(inst.componentType().asSymbol().arrayType()),
586 stack.pop())));
587 }
588 case NewMultiArrayInstruction inst -> {
589 stack.push(op(JavaOp.new_(
590 MethodRef.constructor(
591 JavaType.type(inst.arrayType().asSymbol()),
592 Collections.nCopies(inst.dimensions(), JavaType.INT)),
593 IntStream.range(0, inst.dimensions()).mapToObj(_ -> stack.pop()).toList().reversed())));
594 }
595 case TypeCheckInstruction inst when inst.opcode() == Opcode.CHECKCAST -> {
596 stack.push(op(JavaOp.cast(JavaType.type(inst.type().asSymbol()), stack.pop())));
597 }
598 case TypeCheckInstruction inst -> {
599 stack.push(op(JavaOp.instanceOf(JavaType.type(inst.type().asSymbol()), stack.pop())));
600 }
601 case StackInstruction inst -> {
602 switch (inst.opcode()) {
603 case POP -> {
604 stack.pop();
605 }
606 case POP2 -> {
607 if (isCategory1(stack.pop())) {
608 stack.pop();
609 }
610 }
611 case DUP -> {
612 stack.push(stack.peek());
613 }
614 case DUP_X1 -> {
615 var value1 = stack.pop();
616 var value2 = stack.pop();
617 stack.push(value1);
618 stack.push(value2);
619 stack.push(value1);
620 }
621 case DUP_X2 -> {
622 var value1 = stack.pop();
623 var value2 = stack.pop();
624 if (isCategory1(value2)) {
625 var value3 = stack.pop();
626 stack.push(value1);
627 stack.push(value3);
628 } else {
629 stack.push(value1);
630 }
631 stack.push(value2);
632 stack.push(value1);
633 }
634 case DUP2 -> {
635 var value1 = stack.peek();
636 if (isCategory1(value1)) {
637 stack.pop();
638 var value2 = stack.peek();
639 stack.push(value1);
640 stack.push(value2);
641 }
642 stack.push(value1);
643 }
644 case DUP2_X1 -> {
645 var value1 = stack.pop();
646 var value2 = stack.pop();
647 if (isCategory1(value1)) {
648 var value3 = stack.pop();
649 stack.push(value2);
650 stack.push(value1);
651 stack.push(value3);
652 } else {
653 stack.push(value1);
654 }
655 stack.push(value2);
656 stack.push(value1);
657 }
658 case DUP2_X2 -> {
659 var value1 = stack.pop();
660 var value2 = stack.pop();
661 if (isCategory1(value1)) {
662 var value3 = stack.pop();
663 if (isCategory1(value3)) {
664 var value4 = stack.pop();
665 stack.push(value2);
666 stack.push(value1);
667 stack.push(value4);
668 } else {
669 stack.push(value2);
670 stack.push(value1);
671 }
672 stack.push(value3);
673 } else {
674 if (isCategory1(value2)) {
675 var value3 = stack.pop();
676 stack.push(value1);
677 stack.push(value3);
678 } else {
679 stack.push(value1);
680 }
681 }
682 stack.push(value2);
683 stack.push(value1);
684 }
685 case SWAP -> {
686 var value1 = stack.pop();
687 var value2 = stack.pop();
688 stack.push(value1);
689 stack.push(value2);
690 }
691 default ->
692 throw new UnsupportedOperationException("Unsupported stack instruction: " + inst);
693 }
694 }
695 case MonitorInstruction inst -> {
696 var monitor = stack.pop();
697 switch (inst.opcode()) {
698 case MONITORENTER -> op(JavaOp.monitorEnter(monitor));
699 case MONITOREXIT -> op(JavaOp.monitorExit(monitor));
700 default ->
701 throw new UnsupportedOperationException("Unsupported stack instruction: " + inst);
702 }
703 }
704 case NopInstruction _ -> {}
705 case PseudoInstruction _ -> {}
706 case Instruction inst ->
707 throw new UnsupportedOperationException("Unsupported instruction: " + inst.opcode().name());
708 default ->
709 throw new UnsupportedOperationException("Unsupported code element: " + elements.get(i));
710 }
711 }
712 assert newStack.isEmpty();
713 }
714
715 private Op.Result liftConstantsIntoArray(CodeType arrayType, Object... constants) {
716 Op.Result array = op(JavaOp.newArray(arrayType, liftConstant(constants.length)));
717 for (int i = 0; i < constants.length; i++) {
718 op(JavaOp.arrayStoreOp(array, liftConstant(i), liftConstant(constants[i])));
719 }
720 return array;
721 }
722
723 private Op.Result liftDefaultValue(ClassDesc type) {
724 return liftConstant(switch (TypeKind.from(type)) {
725 case BOOLEAN -> false;
726 case BYTE -> (byte)0;
727 case CHAR -> (char)0;
728 case DOUBLE -> 0d;
729 case FLOAT -> 0f;
730 case INT -> 0;
731 case LONG -> 0l;
732 case REFERENCE -> null;
733 case SHORT -> (short)0;
734 default -> throw new IllegalStateException("Invalid type " + type.displayName());
735 });
736 }
737
738 private Op.Result liftConstant(Object c) {
739 return switch (c) {
740 case null -> op(CoreOp.constant(UnresolvedType.unresolvedRef(), null));
741 case ClassDesc cd -> op(CoreOp.constant(JavaType.J_L_CLASS, JavaType.type(cd)));
742 case Double d -> op(CoreOp.constant(JavaType.DOUBLE, d));
743 case Float f -> op(CoreOp.constant(JavaType.FLOAT, f));
744 case Integer ii -> op(CoreOp.constant(UnresolvedType.unresolvedInt(), ii));
745 case Long l -> op(CoreOp.constant(JavaType.LONG, l));
746 case String s -> op(CoreOp.constant(JavaType.J_L_STRING, s));
747 case DirectMethodHandleDesc dmh -> {
748 Op.Result lookup = op(JavaOp.invoke(LOOKUP));
749 Op.Result owner = liftConstant(dmh.owner());
750 Op.Result name = liftConstant(dmh.methodName());
751 MethodTypeDesc invDesc = dmh.invocationType();
752 yield op(switch (dmh.kind()) {
753 case STATIC, INTERFACE_STATIC ->
754 JavaOp.invoke(FIND_STATIC, lookup, owner, name, liftConstant(invDesc));
755 case VIRTUAL, INTERFACE_VIRTUAL ->
756 JavaOp.invoke(FIND_VIRTUAL, lookup, owner, name, liftConstant(invDesc.dropParameterTypes(0, 1)));
757 case SPECIAL, INTERFACE_SPECIAL ->
758 //CoreOp.invoke(MethodRef.method(e), "findSpecial", owner, name, liftConstant(invDesc.dropParameterTypes(0, 1)), lookup.lookupClass());
759 throw new UnsupportedOperationException(dmh.toString());
760 case CONSTRUCTOR ->
761 JavaOp.invoke(FIND_CONSTRUCTOR, lookup, owner, liftConstant(invDesc.changeReturnType(ConstantDescs.CD_Void)));
762 case GETTER ->
763 JavaOp.invoke(FIND_GETTER, lookup, owner, name, liftConstant(invDesc.returnType()));
764 case STATIC_GETTER ->
765 JavaOp.invoke(FIND_STATIC_GETTER, lookup, owner, name, liftConstant(invDesc.returnType()));
766 case SETTER ->
767 JavaOp.invoke(FIND_SETTER, lookup, owner, name, liftConstant(invDesc.parameterType(1)));
768 case STATIC_SETTER ->
769 JavaOp.invoke(FIND_STATIC_SETTER, lookup, owner, name, liftConstant(invDesc.parameterType(0)));
770 });
771 }
772 case MethodTypeDesc mt -> op(switch (mt.parameterCount()) {
773 case 0 -> JavaOp.invoke(METHOD_TYPE_0, liftConstant(mt.returnType()));
774 case 1 -> JavaOp.invoke(METHOD_TYPE_1, liftConstant(mt.returnType()), liftConstant(mt.parameterType(0)));
775 default -> JavaOp.invoke(METHOD_TYPE_L, liftConstant(mt.returnType()), liftConstantsIntoArray(CLASS_ARRAY, (Object[])mt.parameterArray()));
776 });
777 case DynamicConstantDesc<?> v when v.bootstrapMethod().owner().equals(ConstantDescs.CD_ConstantBootstraps)
778 && v.bootstrapMethod().methodName().equals("nullConstant")
779 -> {
780 c = null;
781 yield liftConstant(null);
782 }
783 case DynamicConstantDesc<?> dcd -> {
784 DirectMethodHandleDesc bsm = dcd.bootstrapMethod();
785 MethodTypeDesc bsmDesc = bsm.invocationType();
786 Value[] bootstrapArgs = liftBootstrapArgs(bsmDesc, dcd.constantName(), dcd.constantType(), dcd.bootstrapArgsList());
787 MethodRef bsmRef = MethodRef.method(JavaType.type(bsm.owner()),
788 bsm.methodName(),
789 JavaType.type(bsmDesc.returnType()),
790 bsmDesc.parameterList().stream().map(JavaType::type).toArray(CodeType[]::new));
791 yield op(JavaOp.invoke(bsmRef, bootstrapArgs));
792 }
793 case Boolean b -> op(CoreOp.constant(JavaType.BOOLEAN, b));
794 case Byte b -> op(CoreOp.constant(JavaType.BYTE, b));
795 case Short s -> op(CoreOp.constant(JavaType.SHORT, s));
796 case Character ch -> op(CoreOp.constant(JavaType.CHAR, ch));
797 default -> throw new UnsupportedOperationException(c.getClass().toString());
798 };
799 }
800
801 private Value[] liftBootstrapArgs(MethodTypeDesc bsmDesc, String name, ConstantDesc desc, List<ConstantDesc> bsmArgs) {
802 Value[] bootstrapArgs = new Value[bsmDesc.parameterCount()];
803 bootstrapArgs[0] = op(JavaOp.invoke(LOOKUP));
804 bootstrapArgs[1] = liftConstant(name);
805 bootstrapArgs[2] = liftConstant(desc);
806 ClassDesc lastArgType = bsmDesc.parameterType(bsmDesc.parameterCount() - 1);
807 if (lastArgType.isArray()) {
808 for (int ai = 0; ai < bootstrapArgs.length - 4; ai++) {
809 bootstrapArgs[ai + 3] = liftConstant(bsmArgs.get(ai));
810 }
811 // Vararg tail of the bootstrap method parameters
812 bootstrapArgs[bootstrapArgs.length - 1] =
813 liftConstantsIntoArray(JavaType.type(lastArgType),
814 bsmArgs.subList(bootstrapArgs.length - 4, bsmArgs.size()).toArray());
815 } else {
816 for (int ai = 0; ai < bootstrapArgs.length - 3; ai++) {
817 bootstrapArgs[ai + 3] = liftConstant(bsmArgs.get(ai));
818 }
819 }
820 return bootstrapArgs;
821 }
822
823 private void liftSwitch(Label defaultTarget, List<SwitchCase> cases) {
824 Value v = stack.pop();
825 if (!valueType(v).equals(PrimitiveType.INT)) {
826 v = op(JavaOp.conv(PrimitiveType.INT, v));
827 }
828 SwitchCase last = cases.getLast();
829 Block.Builder def = targetBlockForBranch(defaultTarget);
830 for (SwitchCase sc : cases) {
831 if (sc == last) {
832 op(CoreOp.conditionalBranch(
833 op(JavaOp.eq(v, liftConstant(sc.caseValue()))),
834 successorWithStack(targetBlockForBranch(sc.target())),
835 successorWithStack(def)));
836 } else {
837 Block.Builder next = entryBlock.block();
838 op(CoreOp.conditionalBranch(
839 op(JavaOp.eq(v, liftConstant(sc.caseValue()))),
840 successorWithStack(targetBlockForBranch(sc.target())),
841 next.reference()));
842 currentBlock = next;
843 }
844 }
845 endOfFlow();
846 }
847
848 private Block.Builder newBlock(List<Block.Parameter> otherBlockParams) {
849 return entryBlock.block(otherBlockParams.stream().map(Block.Parameter::type).toList());
850 }
851
852 private void endOfFlow() {
853 currentBlock = null;
854 // Flow discontinued, stack cleared to be ready for the next label target
855 stack.clear();
856 }
857
858 private Block.Builder targetBlockForExceptionHandler(BitSet initialEreStack, int exceptionHandlerIndex) {
859 Block.Builder target = exceptionHandlerBlocks.get(exceptionHandlerIndex);
860 if (target == null) { // Avoid ConcurrentModificationException
861 Label ehLabel = exceptionHandlers.get(exceptionHandlerIndex);
862 target = transitionBlockForTarget(initialEreStack, exceptionHandlersMap.get(ehLabel), blockMap.get(ehLabel), exceptionHandlerIndex);
863 exceptionHandlerBlocks.put(exceptionHandlerIndex, target);
864 }
865 return target;
866 }
867
868 private Block.Builder targetBlockForBranch(Label targetLabel) {
869 return transitionBlockForTarget(actualEreStack, exceptionHandlersMap.get(targetLabel), blockMap.get(targetLabel), -1);
870 }
871
872 private Block.Builder transitionBlockForTarget(BitSet initialEreStack, BitSet targetEreStack, Block.Builder targetBlock, int targetExceptionHandlerIndex) {
873 if (targetBlock == null) return null;
874 Block.Builder transitionBlock = newBlock(targetBlock.parameters());
875 ereTransit(initialEreStack, targetEreStack, transitionBlock, targetBlock, transitionBlock.parameters(), targetExceptionHandlerIndex);
876 return transitionBlock;
877 }
878
879 record EreT(boolean enter, int ehi) {}
880
881 private void ereTransit(BitSet initialEreStack, BitSet targetEreStack, Block.Builder initialBlock, Block.Builder targetBlock, List<? extends Value> values, int targetExceptionHandlerIndex) {
882 List<EreT> transits = new ArrayList<>();
883 BitSet ereStack = (BitSet)initialEreStack.clone();
884 ereStack.andNot(targetEreStack);
885 // Split region exits by handler stack
886 for (int ehi = ereStack.previousSetBit(Integer.MAX_VALUE); ehi >= 0; ehi = ereStack.previousSetBit(ehi - 1)) {
887 transits.add(new EreT(false, ehi));
888 }
889 ereStack = (BitSet)targetEreStack.clone();
890 ereStack.andNot(initialEreStack);
891 // Split region enters by handler stack
892 for (int ehi = ereStack.nextSetBit(0); ehi >= 0; ehi = ereStack.nextSetBit(ehi + 1)) {
893 transits.add(new EreT(true, ehi));
894 }
895
896 if (transits.isEmpty()) {
897 // Join with branch
898 initialBlock.op(CoreOp.branch(targetBlock.reference(values)));
899 } else {
900 // Insert ERE transitions
901 Block.Builder currentBlock = initialBlock;
902 ereStack = (BitSet)initialEreStack.clone();
903 for (int i = 0; i < transits.size() - 1; i++) {
904 EreT t = transits.get(i);
905 Block.Builder next = entryBlock.block();
906 ereTransit(initialBlock, currentBlock, t.enter(), next, List.of(), t.ehi(), targetExceptionHandlerIndex, ereStack);
907 currentBlock = next;
908 ereStack.set(t.ehi(), t.enter());
909 }
910 EreT t = transits.getLast();
911 ereTransit(initialBlock, currentBlock, t.enter(), targetBlock, values, t.ehi(), targetExceptionHandlerIndex, ereStack);
912 }
913 }
914
915 private void ereTransit(Block.Builder initialBlock, Block.Builder currentBlock, boolean enter, Block.Builder targetBlock, List<? extends Value> values, int ehi, int targetExceptionHandlerIndex, BitSet handlerEreStack) {
916 Block.Reference ref = targetBlock.reference(values);
917 Block.Reference catcher = (ehi == targetExceptionHandlerIndex
918 ? initialBlock
919 : targetBlockForExceptionHandler(handlerEreStack, ehi)).reference();
920 currentBlock.op(enter ? JavaOp.exceptionRegionEnter(ref, catcher) : JavaOp.exceptionRegionExit(ref, catcher));
921 }
922
923 Block.Reference successorWithStack(Block.Builder next) {
924 return next.reference(stackValues(next));
925 }
926
927 private List<Value> stackValues(Block.Builder limit) {
928 return stack.stream().limit(limit.parameters().size()).toList();
929 }
930
931 private static CodeType valueType(Value v) {
932 var t = v.type();
933 while (t instanceof VarType vt) t = vt.valueType();
934 return t;
935 }
936
937 private static boolean isCategory1(Value v) {
938 CodeType t = v.type();
939 return !t.equals(JavaType.LONG) && !t.equals(JavaType.DOUBLE);
940 }
941
942 private static boolean isUnconditionalBranch(Opcode opcode) {
943 return switch (opcode) {
944 case GOTO, ATHROW, GOTO_W, LOOKUPSWITCH, TABLESWITCH -> true;
945 default -> opcode.kind() == Opcode.Kind.RETURN;
946 };
947 }
948 }