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