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