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