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 }