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