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