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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.incubator.code.bytecode;
  27 
  28 import jdk.incubator.code.Block;
  29 import jdk.incubator.code.CodeType;
  30 import jdk.incubator.code.Op;
  31 import jdk.incubator.code.Value;
  32 import jdk.incubator.code.bytecode.impl.BytecodeCompactor;
  33 import jdk.incubator.code.bytecode.impl.ConstantLabelSwitchOp;
  34 import jdk.incubator.code.bytecode.impl.DynamicFuncCallOp;
  35 import jdk.incubator.code.bytecode.impl.LoweringTransformer;
  36 import jdk.incubator.code.dialect.core.CoreOp.*;
  37 import jdk.incubator.code.dialect.core.FunctionType;
  38 import jdk.incubator.code.dialect.core.TupleType;
  39 import jdk.incubator.code.dialect.core.VarType;
  40 import jdk.incubator.code.dialect.java.*;
  41 
  42 import java.lang.classfile.*;
  43 import java.lang.classfile.instruction.SwitchCase;
  44 import java.lang.constant.*;
  45 import java.lang.invoke.MethodHandle;
  46 import java.lang.invoke.MethodHandles;
  47 import java.lang.invoke.MethodType;
  48 import java.lang.invoke.StringConcatFactory;
  49 import java.lang.reflect.Member;
  50 import java.lang.reflect.Modifier;
  51 import java.util.*;
  52 import java.util.stream.Stream;
  53 
  54 import static java.lang.classfile.Opcode.*;
  55 import static java.lang.constant.ConstantDescs.*;
  56 import static jdk.incubator.code.dialect.java.JavaOp.*;
  57 
  58 /**
  59  * Transformer of code models to bytecode.
  60  */
  61 public final class BytecodeGenerator {
  62 
  63     private static final DirectMethodHandleDesc DMHD_STRING_CONCAT = ofCallsiteBootstrap(
  64             StringConcatFactory.class.describeConstable().orElseThrow(),
  65             "makeConcat",
  66             CD_CallSite);
  67 
  68     private static final MethodTypeDesc MTD_FIND_FIELD =
  69             MethodTypeDesc.of(CD_MethodHandle, CD_Class, CD_String, CD_Class);
  70     private static final MethodTypeDesc MTD_FIND_METHOD =
  71             MethodTypeDesc.of(CD_MethodHandle, CD_Class, CD_String, CD_MethodType);
  72     private static final MethodTypeDesc MTD_FIND_SPECIAL =
  73             MethodTypeDesc.of(CD_MethodHandle, CD_Class, CD_String, CD_MethodType, CD_Class);
  74     private static final MethodTypeDesc MTD_PRIVATE_LOOKUP_IN =
  75             MethodTypeDesc.of(CD_MethodHandles_Lookup, CD_Class, CD_MethodHandles_Lookup);
  76 
  77     /**
  78      * Transforms the invokable operation to bytecode encapsulated in a method of hidden class and exposed
  79      * for invocation via a method handle.
  80      *
  81      * @param l the lookup
  82      * @param iop the invokable operation to transform to bytecode
  83      * @return the invoking method handle
  84      * @param <O> the type of the invokable operation
  85      */
  86     public static <O extends Op & Op.Invokable> MethodHandle generate(MethodHandles.Lookup l, O iop) {
  87         String name = iop instanceof FuncOp fop ? fop.funcName() : "m";
  88         byte[] classBytes = generateClassData(l, name, iop);
  89 
  90         MethodHandles.Lookup hcl;
  91         try {
  92             hcl = l.defineHiddenClassWithClassData(classBytes, l, true, MethodHandles.Lookup.ClassOption.NESTMATE);
  93         } catch (IllegalAccessException e) {
  94             throw new RuntimeException(e);
  95         }
  96 
  97         try {
  98             FunctionType ft = iop.invokableSignature();
  99             MethodType mt = MethodRef.toNominalDescriptor(ft).resolveConstantDesc(hcl);
 100             return hcl.findStatic(hcl.lookupClass(), name, mt);
 101         } catch (ReflectiveOperationException e) {
 102             throw new RuntimeException(e);
 103         }
 104     }
 105 
 106     /**
 107      * Transforms the function operation to bytecode encapsulated in a method of a class file.
 108      * <p>
 109      * The name of the method is the function operation's {@link FuncOp#funcName() function name}.
 110      *
 111      * @param lookup the lookup
 112      * @param fop the function operation to transform to bytecode
 113      * @return the class file bytes
 114      */
 115     public static byte[] generateClassData(MethodHandles.Lookup lookup, FuncOp fop) {
 116         return generateClassData(lookup, fop.funcName(), fop);
 117     }
 118 
 119     /**
 120      * Transforms the module operation to bytecode encapsulated in methods of a class file.
 121      *
 122      * @param lookup the lookup
 123      * @param clName the name of the generated class file
 124      * @param mop the module operation to transform to bytecode
 125      * @return the class file bytes
 126      */
 127     public static byte[] generateClassData(MethodHandles.Lookup lookup,
 128                                            ClassDesc clName,
 129                                            ModuleOp mop) {
 130         return generateClassData(lookup, clName, mop.functionTable());
 131     }
 132 
 133     /**
 134      * Transforms the invokable operation to bytecode encapsulated in a method of a class file.
 135      *
 136      * @param lookup the lookup
 137      * @param name the name to use for the method of the class file
 138      * @param iop the invokable operation to transform to bytecode
 139      * @return the class file bytes
 140      * @param <O> the type of the invokable operation
 141      */
 142     public static <O extends Op & Op.Invokable> byte[] generateClassData(MethodHandles.Lookup lookup,
 143                                                                          String name,
 144                                                                          O iop) {
 145         String packageName = lookup.lookupClass().getPackageName();
 146         ClassDesc clsName = ClassDesc.of(packageName.isEmpty()
 147                 ? name
 148                 : packageName + "." + name);
 149         return generateClassData(lookup, clsName, new LinkedHashMap<>(Map.of(name, iop)));
 150     }
 151 
 152     private static <O extends Op & Op.Invokable> byte[] generateClassData(MethodHandles.Lookup lookup,
 153                                                                           ClassDesc clName,
 154                                                                           SequencedMap<String, ? extends O> ops) {
 155         ModuleOp module = LoweringTransformer.transform(lookup, ops);
 156         byte[] classBytes = ClassFile.of().build(clName, clb -> {
 157             for (var e : module.functionTable().sequencedEntrySet()) {
 158                 generateMethod(lookup, clName, e.getKey(), e.getValue(), clb, module.functionTable());
 159             }
 160         });
 161 
 162         // Compact locals of the generated bytecode
 163         return BytecodeCompactor.transform(classBytes);
 164     }
 165 
 166     private static void generateMethod(MethodHandles.Lookup lookup,
 167                                        ClassDesc className,
 168                                        String methodName,
 169                                        FuncOp fop,
 170                                        ClassBuilder clb,
 171                                        SequencedMap<String, FuncOp> functionTable) {
 172         MethodTypeDesc mtd = MethodRef.toNominalDescriptor(fop.invokableSignature());
 173         clb.withMethodBody(methodName, mtd, ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
 174                 cob -> new BytecodeGenerator(lookup, className, List.of(), TypeKind.from(mtd.returnType()),
 175                                              fop.body().blocks(), cob, functionTable).generate());
 176     }
 177 
 178     private record Slot(int slot, TypeKind typeKind) {}
 179 
 180     private final MethodHandles.Lookup lookup;
 181     private final ClassDesc className;
 182     private final List<Value> capturedValues;
 183     private final TypeKind returnType;
 184     private final List<Block> blocks;
 185     private final CodeBuilder cob;
 186     private final Label[] blockLabels;
 187     private final Block[][] blocksCatchMap;
 188     private final CodeType[] catchBlockTypes;
 189     private final Label[] tryStartLabels;
 190     private final Map<Value, Slot> slots;
 191     private final Map<Block.Parameter, Value> singlePredecessorsValues;
 192     private final Map<String, ? extends Op.Invokable> functionMap;
 193     private final Map<Op, Boolean> deferCache;
 194     private final List<Value> oprOnStack;
 195     private Block[] recentCatchBlocks;
 196 
 197     private BytecodeGenerator(MethodHandles.Lookup lookup,
 198                               ClassDesc className,
 199                               List<Value> capturedValues,
 200                               TypeKind returnType,
 201                               List<Block> blocks,
 202                               CodeBuilder cob,
 203                               Map<String, ? extends Op.Invokable> functionMap) {
 204         this.lookup = lookup;
 205         this.className = className;
 206         this.capturedValues = capturedValues;
 207         this.returnType = returnType;
 208         this.blocks = blocks;
 209         this.cob = cob;
 210         this.blockLabels = new Label[blocks.size()];
 211         this.blocksCatchMap = new Block[blocks.size()][];
 212         this.catchBlockTypes = new CodeType[blocks.size()];
 213         this.tryStartLabels = new Label[blocks.size()];
 214         this.slots = new IdentityHashMap<>();
 215         this.singlePredecessorsValues = new IdentityHashMap<>();
 216         this.functionMap = functionMap;
 217         this.deferCache = new IdentityHashMap<>();
 218         this.oprOnStack = new ArrayList<>();
 219     }
 220 
 221     private void setCatchStack(Block.Reference target, Block[] activeCatchBlocks) {
 222         setCatchStack(target.targetBlock().index(), activeCatchBlocks);
 223     }
 224 
 225     private void setCatchStack(int blockIndex, Block[] activeCatchBlocks) {
 226         Block[] prevStack = blocksCatchMap[blockIndex];
 227         if (prevStack == null) {
 228             blocksCatchMap[blockIndex] = activeCatchBlocks;
 229         } else {
 230             assert Arrays.equals(prevStack, activeCatchBlocks);
 231         }
 232     }
 233 
 234     private Label getLabel(Block.Reference target) {
 235         return getLabel(target.targetBlock().index());
 236     }
 237 
 238     private Label getLabel(int blockIndex) {
 239         if (blockIndex == blockLabels.length) {
 240             return cob.endLabel();
 241         }
 242         Label l = blockLabels[blockIndex];
 243         if (l == null) {
 244             blockLabels[blockIndex] = l = cob.newLabel();
 245         }
 246         return l;
 247     }
 248 
 249     private Slot allocateSlot(Value v) {
 250         return slots.computeIfAbsent(v, _ -> {
 251             TypeKind tk = toTypeKind(v.type());
 252             return new Slot(cob.allocateLocal(tk), tk);
 253         });
 254     }
 255 
 256     private void storeIfUsed(Value v) {
 257         if (!v.uses().isEmpty()) {
 258             Slot slot = allocateSlot(v);
 259             cob.storeLocal(slot.typeKind(), slot.slot());
 260         } else {
 261             // Only pop results from stack if the value has no further use (no valid slot)
 262             switch (toTypeKind(v.type()).slotSize()) {
 263                 case 1 -> cob.pop();
 264                 case 2 -> cob.pop2();
 265             }
 266         }
 267     }
 268 
 269     private void storeOperandsOnStack() {
 270         storeOperandsOnStack(0);
 271     }
 272 
 273     private void storeOperandsOnStack(List<Value> values) {
 274         int keep = 0;
 275         // Make sure none of the values remains on stack
 276         while (keep < oprOnStack.size() && !values.contains(oprOnStack.get(keep))) {
 277             keep++;
 278         }
 279         storeOperandsOnStack(keep);
 280     }
 281 
 282     private void storeOperandsOnStack(int keep) {
 283         while (oprOnStack.size() > keep) {
 284             storeIfUsed(oprOnStack.removeLast());
 285         }
 286     }
 287 
 288     // loads a local or a deferred value only
 289     private void load(Value v) {
 290         v = singlePredecessorsValues.getOrDefault(v, v);
 291         if (v instanceof Op.Result or &&
 292                 or.op() instanceof ConstantOp constantOp &&
 293                 canDefer(constantOp)) {
 294             cob.loadConstant(switch (constantOp.value()) {
 295                 case null -> null;
 296                 case Boolean b -> {
 297                     yield b ? 1 : 0;
 298                 }
 299                 case Byte b -> (int)b;
 300                 case Character ch -> (int)ch;
 301                 case Short s -> (int)s;
 302                 case Constable c -> c.describeConstable().orElseThrow();
 303                 default -> throw new IllegalArgumentException("Unexpected constant value: " + constantOp.value());
 304             });
 305         } else {
 306             Slot slot = slots.get(v);
 307             if (slot == null) {
 308                 if (v instanceof Op.Result or) {
 309                     // Handling of deferred variables
 310                     switch (or.op()) {
 311                         case VarOp vop ->
 312                             load(vop.initOperand());
 313                         case VarAccessOp.VarLoadOp vlop ->
 314                             load(vlop.varOperand());
 315                         default ->
 316                             throw new IllegalStateException("Missing slot for: " + or.op());
 317                     }
 318                 } else {
 319                     throw new IllegalStateException("Missing slot for: " + v);
 320                 }
 321             } else {
 322                 cob.loadLocal(slot.typeKind(), slot.slot());
 323             }
 324         }
 325     }
 326 
 327     private void processFirstOperand(Op op) {
 328         processOperand(op.operands().getFirst());
 329     }
 330 
 331     private void processOperand(Value operand) {
 332         processOperands(List.of(operand), true);
 333     }
 334 
 335     private void processOperands(Op op) {
 336         processOperands(op.operands(), true);
 337     }
 338 
 339     private void processOperands(List<Value> operands, boolean keepPrefix) {
 340         int firstToLoad = Math.min(oprOnStack.size(), operands.size());
 341         // match stack suffix against the requested operand prefix
 342         while (firstToLoad > 0 && !oprOnStack.subList(oprOnStack.size() - firstToLoad, oprOnStack.size())
 343                                              .equals(operands.subList(0, firstToLoad))) {
 344             firstToLoad--;
 345         }
 346         int remainingOnStack = oprOnStack.size() - firstToLoad;
 347         if (remainingOnStack == 0 || (keepPrefix && Collections.disjoint(oprOnStack.subList(0, remainingOnStack),
 348                                                                          operands.subList(firstToLoad, operands.size())))) {
 349             // no operand burried in the remaining stack
 350             oprOnStack.subList(remainingOnStack, oprOnStack.size()).clear();
 351         } else if ((keepPrefix || remainingOnStack == 1)
 352                 && operands.size() >= 2 && firstToLoad == 1
 353                 && oprOnStack.get(remainingOnStack - 1) == operands.get(1)
 354                 && Collections.disjoint(oprOnStack, operands.subList(2, operands.size()))) {
 355             // swap special case
 356             int topSize = toTypeKind(operands.get(0).type()).slotSize();
 357             int belowSize = toTypeKind(operands.get(1).type()).slotSize();
 358             if (topSize == 1 && belowSize == 1) {
 359                 cob.swap();
 360             } else if (topSize == 1) {
 361                 cob.dup_x2().pop();
 362             } else if (belowSize == 1) {
 363                 cob.dup2_x1().pop2();
 364             } else {
 365                 cob.dup2_x2().pop2();
 366             }
 367             oprOnStack.subList(remainingOnStack - 1, oprOnStack.size()).clear();
 368             firstToLoad = 2;
 369         } else {
 370             // operands must be digged and made reloadable
 371             if (keepPrefix) {
 372                 storeOperandsOnStack(operands);
 373             } else {
 374                 storeOperandsOnStack();
 375             }
 376             firstToLoad = 0;
 377         }
 378         for (int i = firstToLoad; i < operands.size(); i++) {
 379             load(operands.get(i));
 380         }
 381     }
 382 
 383     // Some of the operations can be deferred
 384     private boolean canDefer(Op op) {
 385         Boolean can = deferCache.get(op);
 386         if (can == null) {
 387             can = switch (op) {
 388                 case ConstantOp cop -> canDefer(cop);
 389                 case VarOp vop -> canDefer(vop);
 390                 case VarAccessOp.VarLoadOp vlop -> canDefer(vlop);
 391                 default -> false;
 392             };
 393             deferCache.put(op, can);
 394         }
 395         return can;
 396     }
 397 
 398     // Class and dynamic constants cannot be deferred, resolution may throw
 399     // and dynamic constant bootstrap methods may have side effects
 400     private static boolean canDefer(ConstantOp op) {
 401         return !op.resultType().equals(JavaType.J_L_CLASS)
 402                 && !(op.value() instanceof DynamicConstantDesc<?>);
 403     }
 404 
 405     private static boolean canDefer(VarOp op) {
 406         if (op.isUninitialized()) {
 407             // Uninitialized var with single store dominating to all its uses can be deferred
 408             var uses = op.result().uses();
 409             var storeUses = uses.stream().filter(u -> u.op() instanceof VarAccessOp.VarStoreOp).toList();
 410             return storeUses.size() == 1 && uses.stream().allMatch(u -> u.isDominatedBy(storeUses.getFirst()));
 411         } else {
 412             // Initialized var used only for loads or var with a single-use entry block parameter operand can be deferred
 413             return op.result().uses().stream().allMatch(u -> u.op() instanceof VarAccessOp.VarLoadOp)
 414                     || op.initOperand() instanceof Block.Parameter bp
 415                         && bp.declaringBlock().isEntryBlock()
 416                         && bp.uses().size() == 1;
 417         }
 418     }
 419 
 420     // Var load can be deferred when do not dominate a var store (conservative deferral refusal)
 421     private boolean canDefer(VarAccessOp.VarLoadOp op) {
 422         return op.varOperand().uses().stream()
 423                  .filter(u -> u.op() instanceof VarAccessOp.VarStoreOp)
 424                  .noneMatch(store -> store.isDominatedBy(op.result()));
 425     }
 426 
 427     private static boolean isConditionForCondBrOp(CompareOp op) {
 428         // Result of op has one use as the operand of a CondBrOp op,
 429         // and both ops are in the same block
 430 
 431         Set<Op.Result> uses = op.result().uses();
 432         if (uses.size() != 1) {
 433             return false;
 434         }
 435         Op.Result use = uses.iterator().next();
 436 
 437         if (use.declaringBlock() != op.ancestorBlock()) {
 438             return false;
 439         }
 440 
 441         // Check if used in successor
 442         for (Block.Reference s : use.op().successors()) {
 443             if (s.arguments().contains(op.result())) {
 444                 return false;
 445             }
 446         }
 447 
 448         return use.op() instanceof ConditionalBranchOp;
 449     }
 450 
 451     static ClassDesc toClassDesc(CodeType t) {
 452         return switch (t) {
 453             case VarType vt -> toClassDesc(vt.valueType());
 454             case JavaType jt -> jt.toNominalDescriptor();
 455             default ->
 456                 throw new IllegalArgumentException("Bad type: " + t);
 457         };
 458     }
 459 
 460     static TypeKind toTypeKind(CodeType t) {
 461         return switch (t) {
 462             case VarType vt -> toTypeKind(vt.valueType());
 463             case PrimitiveType pt -> TypeKind.from(pt.toNominalDescriptor());
 464             case JavaType _ -> TypeKind.REFERENCE;
 465             default ->
 466                 throw new IllegalArgumentException("Bad type: " + t);
 467         };
 468     }
 469 
 470     private void generate() {
 471         recentCatchBlocks = new Block[0];
 472 
 473         Block entryBlock = blocks.getFirst();
 474         assert entryBlock.isEntryBlock();
 475 
 476         // Entry block parameters conservatively require slots
 477         // Some unused parameters might be declared before others that are used
 478         List<Block.Parameter> parameters = entryBlock.parameters();
 479         int paramSlot = 0;
 480         // Captured values prepend parameters in lambda impl methods
 481         for (Value cv : capturedValues) {
 482             slots.put(cv, new Slot(cob.parameterSlot(paramSlot++), toTypeKind(cv.type())));
 483         }
 484         for (Block.Parameter bp : parameters) {
 485             slots.put(bp, new Slot(cob.parameterSlot(paramSlot++), toTypeKind(bp.type())));
 486         }
 487 
 488         blocksCatchMap[entryBlock.index()] = new Block[0];
 489 
 490         // Process blocks in topological order
 491         // A jump instruction assumes the false successor block is
 492         // immediately after, in sequence, to the predecessor
 493         // since the jump instructions branch on a true condition
 494         // Conditions are inverted when lowered to bytecode
 495         for (Block b : blocks) {
 496 
 497             Block[] catchBlocks = blocksCatchMap[b.index()];
 498 
 499             // Ignore inaccessible blocks
 500             if (catchBlocks == null) {
 501                 continue;
 502             }
 503 
 504             Label blockLabel = getLabel(b.index());
 505             cob.labelBinding(blockLabel);
 506 
 507             if (!oprOnStack.isEmpty()) {
 508                 throw new IllegalStateException("stack should be empty here");
 509             }
 510 
 511             exceptionRegionsChange(catchBlocks);
 512 
 513             // If b is a catch block then the exception argument will be represented on the stack
 514             if (catchBlockTypes[b.index()] != null) {
 515                 // Retain block argument for exception table generation
 516                 push(b.parameters().getFirst());
 517             }
 518 
 519             List<Op> ops = b.ops();
 520             for (int i = 0; i < ops.size() - 1; i++) {
 521                 final Op o = ops.get(i);
 522                 final CodeType oprType = o.resultType();
 523                 final TypeKind rvt = toTypeKind(oprType);
 524                 switch (o) {
 525                     case ConstantOp op -> {
 526                         if (!canDefer(op)) {
 527                             // Emit class and dynamic constants at their original position
 528                             Object v = op.value();
 529                             if (v instanceof DynamicConstantDesc<?> dcd) {
 530                                 cob.ldc(dcd);
 531                             } else if (v == null) {
 532                                 cob.aconst_null();
 533                             } else {
 534                                 cob.ldc(((JavaType)v).toNominalDescriptor());
 535                             }
 536                             push(op.result());
 537                         }
 538                     }
 539                     case VarOp op when op.isUninitialized() -> {}
 540                     case VarOp op -> {
 541                         //     %1 : Var<int> = var %0 @"i";
 542                         if (canDefer(op)) {
 543                             Slot s = slots.get(op.operands().getFirst());
 544                             if (s != null) {
 545                                 // Var with a single-use entry block parameter can reuse its slot
 546                                 slots.put(op.result(), s);
 547                             }
 548                         } else {
 549                             processFirstOperand(op);
 550                             storeIfUsed(op.result());
 551                         }
 552                     }
 553                     case VarAccessOp.VarLoadOp op -> {
 554                         if (canDefer(op)) {
 555                             // create alias for the source slot
 556                             slots.computeIfAbsent(op.result(), r -> slots.get(op.operands().getFirst()));
 557                         } else {
 558                             load(op.operands().getFirst());
 559                             push(op.result());
 560                         }
 561                     }
 562                     case VarAccessOp.VarStoreOp op -> {
 563                         processOperand(op.operands().get(1));
 564                         Slot slot = allocateSlot(op.operands().getFirst());
 565                         cob.storeLocal(slot.typeKind(), slot.slot());
 566                     }
 567                     case ConvOp op -> {
 568                         Value first = op.operands().getFirst();
 569                         processOperand(first);
 570                         cob.conversion(toTypeKind(first.type()), rvt);
 571                         push(op.result());
 572                     }
 573                     case NegOp op -> {
 574                         processFirstOperand(op);
 575                         switch (rvt) { //this can be moved to CodeBuilder::neg(TypeKind)
 576                             case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.ineg();
 577                             case LONG -> cob.lneg();
 578                             case FLOAT -> cob.fneg();
 579                             case DOUBLE -> cob.dneg();
 580                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 581                         }
 582                         push(op.result());
 583                     }
 584                     case PosOp op -> {
 585                         processFirstOperand(op);
 586                         push(op.result());
 587                     }
 588                     case ComplOp op -> {
 589                         // Lower to x ^ -1
 590                         processFirstOperand(op);
 591                         switch (rvt) {
 592                             case INT, BOOLEAN, BYTE, SHORT, CHAR -> {
 593                                 cob.iconst_m1();
 594                                 cob.ixor();
 595                             }
 596                             case LONG -> {
 597                                 cob.ldc(-1L);
 598                                 cob.lxor();
 599                             }
 600                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 601                         }
 602                         push(op.result());
 603                     }
 604                     case NotOp op -> {
 605                         processFirstOperand(op);
 606                         cob.ifThenElse(CodeBuilder::iconst_0, CodeBuilder::iconst_1);
 607                         push(op.result());
 608                     }
 609                     case AddOp op -> {
 610                         processOperands(op);
 611                         switch (rvt) { //this can be moved to CodeBuilder::add(TypeKind)
 612                             case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.iadd();
 613                             case LONG -> cob.ladd();
 614                             case FLOAT -> cob.fadd();
 615                             case DOUBLE -> cob.dadd();
 616                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 617                         }
 618                         push(op.result());
 619                     }
 620                     case SubOp op -> {
 621                         processOperands(op);
 622                         switch (rvt) { //this can be moved to CodeBuilder::sub(TypeKind)
 623                             case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.isub();
 624                             case LONG -> cob.lsub();
 625                             case FLOAT -> cob.fsub();
 626                             case DOUBLE -> cob.dsub();
 627                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 628                         }
 629                         push(op.result());
 630                     }
 631                     case MulOp op -> {
 632                         processOperands(op);
 633                         switch (rvt) { //this can be moved to CodeBuilder::mul(TypeKind)
 634                             case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.imul();
 635                             case LONG -> cob.lmul();
 636                             case FLOAT -> cob.fmul();
 637                             case DOUBLE -> cob.dmul();
 638                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 639                         }
 640                         push(op.result());
 641                     }
 642                     case DivOp op -> {
 643                         processOperands(op);
 644                         switch (rvt) { //this can be moved to CodeBuilder::div(TypeKind)
 645                             case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.idiv();
 646                             case LONG -> cob.ldiv();
 647                             case FLOAT -> cob.fdiv();
 648                             case DOUBLE -> cob.ddiv();
 649                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 650                         }
 651                         push(op.result());
 652                     }
 653                     case ModOp op -> {
 654                         processOperands(op);
 655                         switch (rvt) { //this can be moved to CodeBuilder::rem(TypeKind)
 656                             case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.irem();
 657                             case LONG -> cob.lrem();
 658                             case FLOAT -> cob.frem();
 659                             case DOUBLE -> cob.drem();
 660                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 661                         }
 662                         push(op.result());
 663                     }
 664                     case AndOp op -> {
 665                         processOperands(op);
 666                         switch (rvt) { //this can be moved to CodeBuilder::and(TypeKind)
 667                             case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.iand();
 668                             case LONG -> cob.land();
 669                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 670                         }
 671                         push(op.result());
 672                     }
 673                     case OrOp op -> {
 674                         processOperands(op);
 675                         switch (rvt) { //this can be moved to CodeBuilder::or(TypeKind)
 676                             case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.ior();
 677                             case LONG -> cob.lor();
 678                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 679                         }
 680                         push(op.result());
 681                     }
 682                     case XorOp op -> {
 683                         processOperands(op);
 684                         switch (rvt) { //this can be moved to CodeBuilder::xor(TypeKind)
 685                             case INT, BOOLEAN, BYTE, SHORT, CHAR -> cob.ixor();
 686                             case LONG -> cob.lxor();
 687                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 688                         }
 689                         push(op.result());
 690                     }
 691                     case LshlOp op -> {
 692                         processOperands(op);
 693                         adjustRightTypeToInt(op);
 694                         switch (rvt) { //this can be moved to CodeBuilder::shl(TypeKind)
 695                             case BYTE, CHAR, INT, SHORT -> cob.ishl();
 696                             case LONG -> cob.lshl();
 697                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 698                         }
 699                         push(op.result());
 700                     }
 701                     case AshrOp op -> {
 702                         processOperands(op);
 703                         adjustRightTypeToInt(op);
 704                         switch (rvt) { //this can be moved to CodeBuilder::shr(TypeKind)
 705                             case INT, BYTE, SHORT, CHAR -> cob.ishr();
 706                             case LONG -> cob.lshr();
 707                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 708                         }
 709                         push(op.result());
 710                     }
 711                     case LshrOp op -> {
 712                         processOperands(op);
 713                         adjustRightTypeToInt(op);
 714                         switch (rvt) { //this can be moved to CodeBuilder::ushr(TypeKind)
 715                             case INT, BYTE, SHORT, CHAR -> cob.iushr();
 716                             case LONG -> cob.lushr();
 717                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 718                         }
 719                         push(op.result());
 720                     }
 721                     case ArrayAccessOp.ArrayLoadOp op -> {
 722                         processOperands(op);
 723                         cob.arrayLoad(rvt);
 724                         push(op.result());
 725                     }
 726                     case ArrayAccessOp.ArrayStoreOp op -> {
 727                         processOperands(op);
 728                         cob.arrayStore(toTypeKind(((ArrayType)op.operands().getFirst().type()).componentType()));
 729                         push(op.result());
 730                     }
 731                     case ArrayLengthOp op -> {
 732                         processFirstOperand(op);
 733                         cob.arraylength();
 734                         push(op.result());
 735                     }
 736                     case CompareOp op -> {
 737                         if (!isConditionForCondBrOp(op)) {
 738                             Label falseLabel = cob.newLabel();
 739                             Label endLabel = cob.newLabel();
 740                             cob.branch(prepareConditionalBranch(op, true), falseLabel)
 741                                .iconst_1()
 742                                .goto_(endLabel)
 743                                .labelBinding(falseLabel)
 744                                .iconst_0()
 745                                .labelBinding(endLabel);
 746                             push(op.result());
 747                         }
 748                         // Processing is deferred to the CondBrOp, do not process the op result
 749                     }
 750                     case NewOp op -> {
 751                         switch (op.constructorReference().signature().returnType()) {
 752                             case ArrayType at -> {
 753                                 processOperands(op);
 754                                 if (at.dimensions() == 1) {
 755                                     ClassDesc ctd = at.componentType().toNominalDescriptor();
 756                                     if (ctd.isPrimitive()) {
 757                                         cob.newarray(TypeKind.from(ctd));
 758                                     } else {
 759                                         cob.anewarray(ctd);
 760                                     }
 761                                 } else {
 762                                     cob.multianewarray(at.toNominalDescriptor(), op.operands().size());
 763                                 }
 764                             }
 765                             case JavaType jt -> {
 766                                 // new and dup must precede the explicit constructor args
 767                                 storeOperandsOnStack(op.operands());
 768                                 cob.new_(jt.toNominalDescriptor())
 769                                     .dup();
 770                                 if (op.isVarargs()) {
 771                                     int varargIndex = op.constructorReference().signature().parameterTypes().size() - 1;
 772                                     var argOperands = op.operands().subList(0, varargIndex);
 773                                     argOperands.forEach(this::load);
 774                                     var compType = ((ArrayType) op.constructorReference().signature().parameterTypes().getLast()).componentType();
 775                                     var varArgOperands = op.operands().subList(varargIndex, op.operands().size());
 776                                     loadArray(compType, varArgOperands);
 777                                 } else {
 778                                     op.operands().forEach(this::load);
 779                                 }
 780                                 cob.invokespecial(
 781                                         ((JavaType) op.resultType()).toNominalDescriptor(),
 782                                         ConstantDescs.INIT_NAME,
 783                                         MethodRef.toNominalDescriptor(op.constructorReference().signature())
 784                                                  .changeReturnType(ConstantDescs.CD_void));
 785                             }
 786                             default ->
 787                                 throw new IllegalArgumentException("Invalid return type: "
 788                                                                     + op.constructorReference().signature().returnType());
 789                         }
 790                         push(op.result());
 791                     }
 792                     case InvokeOp op -> {
 793                         // loadArray() reloads each element after pushing array/index
 794                         // any stack operand must be made reloadable now
 795                         if (op.isVarArgs()) {
 796                             storeOperandsOnStack(op.varArgOperands());
 797                         }
 798                         // Resolve referenced class to determine if interface
 799                         MethodRef md = op.invokeReference();
 800                         JavaType refType = (JavaType)md.refType();
 801                         ClassDesc specialCaller = lookup.lookupClass().describeConstable().get();
 802                         MethodTypeDesc mDesc = MethodRef.toNominalDescriptor(md.signature());
 803                         MethodHandles.Lookup protectedAccessLookup = null;
 804                         if (op.invokeKind() == InvokeOp.InvokeKind.SUPER) {
 805                             // invokeExact needs the handle below receiver and args
 806                             // any stack operand must be made reloadable now
 807                             storeOperandsOnStack(op.operands());
 808                             // resolve the method handle with lookup.findSpecial using the lookup's class as the specialCaller
 809                             // and cache it in a dynamic constant
 810                             // the original lookup is stored in class data.
 811                             cob.ldc(DynamicConstantDesc.ofNamed(BSM_INVOKE, DEFAULT_NAME, CD_MethodHandle,
 812                                     MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.VIRTUAL,
 813                                             CD_MethodHandles_Lookup, "findSpecial", MTD_FIND_SPECIAL),
 814                                     DynamicConstantDesc.ofNamed(BSM_CLASS_DATA, DEFAULT_NAME, CD_MethodHandles_Lookup),
 815                                     refType.toNominalDescriptor(), md.name(), mDesc, specialCaller));
 816                         } else {
 817                             if (!(refType instanceof ArrayType)) { // array methods (e.g. clone) are excluded
 818                                 protectedAccessLookup = findProtectedAccessLookup(
 819                                         op.invokeKind() == InvokeOp.InvokeKind.STATIC ? null : receiverClass(op.operands()),
 820                                         l -> {
 821                                             var info = l.revealDirect(md.resolveToHandle(l, op.invokeKind()));
 822                                             return Modifier.isProtected(info.getModifiers())
 823                                                     && !info.getDeclaringClass().getPackageName().equals(l.lookupClass().getPackageName());
 824                                         });
 825                                 if (protectedAccessLookup != null) {
 826                                     specialCaller = protectedAccessLookup.lookupClass().describeConstable().get();
 827                                     lookupHandle(op.operands(), protectedAccessLookup, op.invokeKind() == InvokeOp.InvokeKind.STATIC ? refType.toNominalDescriptor() : specialCaller,
 828                                             md.name(), mDesc, op.invokeKind() == InvokeOp.InvokeKind.STATIC ? "findStatic" : "findVirtual");
 829                                 }
 830                             }
 831                         }
 832                         if (op.isVarArgs()) {
 833                             processOperands(op.argOperands(), true);
 834                             var varArgOperands = op.varArgOperands();
 835                             var compType = ((ArrayType) op.invokeReference().signature().parameterTypes().getLast()).componentType();
 836                             loadArray(compType, varArgOperands);
 837                         } else {
 838                             processOperands(op);
 839                         }
 840                         Class<?> refClass;
 841                         try {
 842                              refClass = (Class<?>)refType.erasure().resolve(lookup);
 843                         } catch (ReflectiveOperationException e) {
 844                             throw new IllegalArgumentException(e);
 845                         }
 846                         boolean isInterface = refClass.isInterface();
 847                         switch (op.invokeKind()) {
 848                             case STATIC -> {
 849                                 if (protectedAccessLookup != null) {
 850                                     cob.invokevirtual(CD_MethodHandle, "invokeExact", mDesc);
 851                                 } else {
 852                                     cob.invokestatic(refType.toNominalDescriptor(), md.name(), mDesc, isInterface);
 853                                 }
 854                             }
 855                             case INSTANCE -> {
 856                                 if (protectedAccessLookup != null) {
 857                                     cob.invokevirtual(CD_MethodHandle, "invokeExact", mDesc.insertParameterTypes(0, specialCaller));
 858                                 } else {
 859                                     cob.invoke(isInterface ? INVOKEINTERFACE : INVOKEVIRTUAL,
 860                                                refType.toNominalDescriptor(), md.name(), mDesc, isInterface);
 861                                 }
 862                             }
 863                             case SUPER ->
 864                                     cob.invokevirtual(CD_MethodHandle,
 865                                                       "invokeExact",
 866                                                       mDesc.insertParameterTypes(0, specialCaller));
 867                         }
 868                         ClassDesc ret = toClassDesc(op.resultType());
 869                         if (!ret.isPrimitive() && !ret.equals(mDesc.returnType())) {
 870                             // Explicit cast if method return type differs
 871                             cob.checkcast(ret);
 872                         }
 873                         push(op.result());
 874                     }
 875                     case FuncCallOp op -> {
 876                         Op.Invokable fop = functionMap.get(op.funcName());
 877                         if (fop == null) {
 878                             throw new IllegalArgumentException("Could not resolve function: " + op.funcName());
 879                         }
 880                         processOperands(op);
 881                         MethodTypeDesc mDesc = MethodRef.toNominalDescriptor(fop.invokableSignature());
 882                         cob.invoke(
 883                                 INVOKESTATIC,
 884                                 className,
 885                                 op.funcName(),
 886                                 mDesc,
 887                                 false);
 888                         ClassDesc ret = toClassDesc(op.resultType());
 889                         if (ret.isClassOrInterface() && !ret.equals(mDesc.returnType())) {
 890                             // Explicit cast if method return type differs
 891                             cob.checkcast(ret);
 892                         }
 893                         push(op.result());
 894                     }
 895                     case FieldAccessOp.FieldLoadOp op -> fieldAccess(op);
 896                     case FieldAccessOp.FieldStoreOp op -> fieldAccess(op);
 897                     case InstanceOfOp op -> {
 898                         processFirstOperand(op);
 899                         cob.instanceOf(((JavaType) op.targetType()).toNominalDescriptor());
 900                         push(op.result());
 901                     }
 902                     case CastOp op -> {
 903                         processFirstOperand(op);
 904                         cob.checkcast(((JavaType) op.targetType()).toNominalDescriptor());
 905                         push(op.result());
 906                     }
 907                     case DynamicFuncCallOp op -> {
 908                         Op.Invokable fop = functionMap.get(op.funcName());
 909                         if (fop == null) {
 910                             throw new IllegalArgumentException("Could not resolve function: " + op.funcName());
 911                         }
 912                         processOperands(op);
 913                         List<ConstantDesc> bootstrapArgs = new ArrayList<>();
 914                         bootstrapArgs.add(op.interfaceMethodType());
 915                         bootstrapArgs.add(MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
 916                                 className,
 917                                 op.funcName(),
 918                                 MethodRef.toNominalDescriptor(fop.invokableSignature())));
 919                         bootstrapArgs.add(op.dynamicMethodType());
 920                         bootstrapArgs.addAll(op.additionalBootstapArgs());
 921                         cob.invokedynamic(DynamicCallSiteDesc.of(op.bootstrapMethod(),
 922                                 op.invocationName(),
 923                                 op.invocationType(),
 924                                 bootstrapArgs.toArray(ConstantDesc[]::new)));
 925                         push(op.result());
 926                     }
 927                     case ConcatOp op -> {
 928                         processOperands(op);
 929                         cob.invokedynamic(DynamicCallSiteDesc.of(DMHD_STRING_CONCAT, MethodTypeDesc.of(CD_String,
 930                                 toClassDesc(op.operands().get(0).type()),
 931                                 toClassDesc(op.operands().get(1).type()))));
 932                         push(op.result());
 933                     }
 934                     case MonitorOp.MonitorEnterOp op -> {
 935                         processFirstOperand(op);
 936                         cob.monitorenter();
 937                     }
 938                     case MonitorOp.MonitorExitOp op -> {
 939                         processFirstOperand(op);
 940                         cob.monitorexit();
 941                     }
 942                     default ->
 943                         throw new UnsupportedOperationException("Unsupported operation: " + ops.get(i));
 944                 }
 945             }
 946             Op top = b.terminatingOp();
 947             switch (top) {
 948                 case ReturnOp op -> {
 949                     if (returnType != TypeKind.VOID) {
 950                         processFirstOperand(op);
 951                         // @@@ box, unbox, cast here ?
 952                     }
 953                     cob.return_(returnType);
 954                     oprOnStack.clear();
 955                 }
 956                 case ThrowOp op -> {
 957                     processFirstOperand(op);
 958                     cob.athrow();
 959                     oprOnStack.clear();
 960                 }
 961                 case BranchOp op -> {
 962                     setCatchStack(op.branch(), recentCatchBlocks);
 963 
 964                     assignBlockArguments(op.branch());
 965                     cob.goto_(getLabel(op.branch()));
 966                 }
 967                 case ConditionalBranchOp op -> {
 968                     setCatchStack(op.trueBranch(), recentCatchBlocks);
 969                     setCatchStack(op.falseBranch(), recentCatchBlocks);
 970 
 971                     if (getConditionForCondBrOp(op) instanceof CompareOp cop) {
 972                         // Processing of the BinaryTestOp was deferred, so it can be merged with CondBrOp
 973                         conditionalBranch(prepareConditionalBranch(cop, false), op.trueBranch(), op.falseBranch());
 974                     } else {
 975                         processOperands(op.operands(), false);
 976                         conditionalBranch(IFEQ, op.trueBranch(), op.falseBranch());
 977                     }
 978                 }
 979                 case ConstantLabelSwitchOp op -> {
 980                     op.successors().forEach(t -> setCatchStack(t, recentCatchBlocks));
 981                     var cases = new ArrayList<SwitchCase>();
 982                     int lo = Integer.MAX_VALUE;
 983                     int hi = Integer.MIN_VALUE;
 984                     Label defTarget = null;
 985                     // each switch edge needs its own argument transfer
 986                     Map<Block.Reference, Label> transfers = new LinkedHashMap<>();
 987                     for (int i = 0; i < op.labels().size(); i++) {
 988                         Integer val = op.labels().get(i);
 989                         Block.Reference reference = op.successors().get(i);
 990                         Label target = needToAssignBlockArguments(reference)
 991                                 ? transfers.computeIfAbsent(reference, _ -> cob.newLabel())
 992                                 : getLabel(reference);
 993                         if (val == null) { // default target has null label value
 994                             defTarget = target;
 995                         } else {
 996                             cases.add(SwitchCase.of(val, target));
 997                             if (val < lo) lo = val;
 998                             if (val > hi) hi = val;
 999                         }
1000                     }
1001                     if (defTarget == null) {
1002                         throw new IllegalArgumentException("Missing default target");
1003                     }
1004                     processOperands(op.operands(), false);
1005                     if (tableSwitchOverLookupSwitch(lo, hi, cases.size())) {
1006                         cob.tableswitch(defTarget, cases);
1007                     } else {
1008                         cob.lookupswitch(defTarget, cases);
1009                     }
1010                     for (var transfer : transfers.entrySet()) {
1011                         cob.labelBinding(transfer.getValue());
1012                         assignBlockArguments(transfer.getKey());
1013                         cob.goto_(getLabel(transfer.getKey()));
1014                     }
1015                 }
1016                 case ExceptionRegionEnter op -> {
1017                     List<Block.Reference> enteringCatchBlocks = op.catchReferences();
1018                     List<CodeType> enteringCatchTypes = op.catchTypes();
1019                     Block[] activeCatchBlocks = Arrays.copyOf(recentCatchBlocks, recentCatchBlocks.length + enteringCatchBlocks.size());
1020                     int i = recentCatchBlocks.length;
1021                     int catchIndex = 0;
1022                     for (Block.Reference catchRef : enteringCatchBlocks) {
1023                         catchBlockTypes[catchRef.targetBlock().index()] = enteringCatchTypes.get(catchIndex++);
1024                         activeCatchBlocks[i++] = catchRef.targetBlock();
1025                         setCatchStack(catchRef, recentCatchBlocks);
1026                     }
1027                     setCatchStack(op.startReference(), activeCatchBlocks);
1028 
1029                     assignBlockArguments(op.startReference());
1030                     cob.goto_(getLabel(op.startReference()));
1031                 }
1032                 case ExceptionRegionExit op -> {
1033                     List<Block.Reference> exitingCatchBlocks = op.enterOp().catchReferences().reversed();
1034                     Block[] activeCatchBlocks = Arrays.copyOf(recentCatchBlocks, recentCatchBlocks.length - exitingCatchBlocks.size());
1035                     setCatchStack(op.endReference(), activeCatchBlocks);
1036 
1037                     // Assert block exits in reverse order
1038                     int i = recentCatchBlocks.length;
1039                     for (Block.Reference catchRef : exitingCatchBlocks) {
1040                         assert catchRef.targetBlock() == recentCatchBlocks[--i];
1041                     }
1042 
1043                     assignBlockArguments(op.endReference());
1044                     cob.goto_(getLabel(op.endReference()));
1045                 }
1046                 case UnreachableOp _ -> {
1047                     cob.aconst_null().athrow();
1048                     oprOnStack.clear();
1049                 }
1050                 default ->
1051                     throw new UnsupportedOperationException("Terminating operation not supported: " + top);
1052             }
1053         }
1054         exceptionRegionsChange(new Block[0]);
1055     }
1056 
1057     private interface ProtectedAccessPredicate {
1058         boolean check(MethodHandles.Lookup l) throws ReflectiveOperationException;
1059     }
1060 
1061     private MethodHandles.Lookup findProtectedAccessLookup(Class<?> receiverClass, ProtectedAccessPredicate predicate) {
1062         MethodHandles.Lookup l = lookup;
1063         while (l != null) {
1064             // Resolving a protected instance member may narrow the handle's receiver
1065             // to the lookup class. Skip lookups incompatible with the model's receiver
1066             // and try an enclosing class instead.
1067             if (receiverClass == null || l.lookupClass().isAssignableFrom(receiverClass)) {
1068                 try {
1069                     return predicate.check(l) ? l : null;
1070                 } catch (ReflectiveOperationException | IllegalArgumentException _) {}
1071             }
1072             Class<?> enclClass = l.lookupClass().getEnclosingClass();
1073             try {
1074                 l = enclClass != null ? MethodHandles.privateLookupIn(enclClass, lookup) : null;
1075             } catch (IllegalAccessException _) {
1076                 l = null;
1077             }
1078         }
1079         return null;
1080     }
1081 
1082     private Class<?> receiverClass(List<Value> operands) {
1083         try {
1084             return (Class<?>) ((JavaType) operands.getFirst().type()).erasure().resolve(lookup);
1085         } catch (ReflectiveOperationException ex) {
1086             return null;
1087         }
1088     }
1089 
1090     private void lookupHandle(List<Value> explicitOperands, MethodHandles.Lookup resLookup, ClassDesc owner, String name, ConstantDesc type, String finder) {
1091         // handle must precede any operand
1092         storeOperandsOnStack(explicitOperands);
1093 
1094         // resolve the method handle with the specified finder and lookup and cache it
1095         // in a dynamic constant
1096         cob.ldc(DynamicConstantDesc.ofNamed(BSM_INVOKE, DEFAULT_NAME, CD_MethodHandle,
1097                 MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.VIRTUAL, CD_MethodHandles_Lookup,
1098                         finder, type instanceof MethodTypeDesc ? MTD_FIND_METHOD : MTD_FIND_FIELD),
1099                 resLookup == lookup
1100                         ? DynamicConstantDesc.ofNamed(BSM_CLASS_DATA, DEFAULT_NAME, CD_MethodHandles_Lookup)
1101                         : DynamicConstantDesc.ofNamed(BSM_INVOKE, DEFAULT_NAME, CD_MethodHandles_Lookup,
1102                                 MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
1103                                         CD_MethodHandles, "privateLookupIn", MTD_PRIVATE_LOOKUP_IN),
1104                                 resLookup.lookupClass().describeConstable().orElseThrow(),
1105                                 DynamicConstantDesc.ofNamed(BSM_CLASS_DATA, DEFAULT_NAME, CD_MethodHandles_Lookup)),
1106                 owner, name, type));
1107     }
1108 
1109     private void fieldAccess(FieldAccessOp op) {
1110         FieldRef ref = op.fieldReference();
1111         JavaType refType = (JavaType) ref.refType();
1112         ClassDesc fieldType = ((JavaType) ref.type()).toNominalDescriptor();
1113         boolean store = op instanceof FieldAccessOp.FieldStoreOp;
1114         boolean isStatic = op.operands().size() == (store ? 1 : 0);
1115         ClassDesc caller = lookup.lookupClass().describeConstable().orElseThrow();
1116         MethodHandles.Lookup protectedAccessLookup = findProtectedAccessLookup(
1117                 isStatic ? null : receiverClass(op.operands()),
1118                 l -> {
1119                     Member m = ref.resolveToField(l);
1120                     return Modifier.isProtected(m.getModifiers())
1121                             && !m.getDeclaringClass().getPackageName().equals(lookup.lookupClass().getPackageName());
1122                 });
1123         if (protectedAccessLookup != null) {
1124             caller = protectedAccessLookup.lookupClass().describeConstable().orElseThrow();
1125             lookupHandle(op.operands(), protectedAccessLookup, isStatic ? refType.toNominalDescriptor() : caller, ref.name(),
1126                     fieldType, "find" + (isStatic ? "Static" : "") + (store ? "Setter" : "Getter"));
1127         }
1128         processOperands(op);
1129         if (protectedAccessLookup != null) {
1130             cob.invokevirtual(CD_MethodHandle,
1131                               "invokeExact",
1132                               isStatic ? (store ? MethodTypeDesc.of(CD_void, fieldType)
1133                                                 : MethodTypeDesc.of(fieldType))
1134                                        : (store ? MethodTypeDesc.of(CD_void, caller, fieldType)
1135                                                 : MethodTypeDesc.of(fieldType, caller)));
1136         } else {
1137             cob.fieldAccess(isStatic ? (store ? PUTSTATIC : GETSTATIC)
1138                                      : (store ? PUTFIELD : GETFIELD),
1139                             refType.toNominalDescriptor(),
1140                             ref.name(),
1141                             fieldType);
1142         }
1143         if (!store) {
1144             ClassDesc ret = toClassDesc(op.resultType());
1145             if (!ret.isPrimitive() && !ret.equals(fieldType)) {
1146                 // Explicit cast if field type differs
1147                 cob.checkcast(ret);
1148             }
1149             push(op.result());
1150         }
1151     }
1152 
1153     private void loadArray(JavaType compType, List<Value> array) {
1154         cob.loadConstant(array.size());
1155         var compTypeDesc = compType.toNominalDescriptor();
1156         var typeKind = TypeKind.from(compTypeDesc);
1157         if (compTypeDesc.isPrimitive()) {
1158             cob.newarray(typeKind);
1159         } else {
1160             cob.anewarray(compTypeDesc);
1161         }
1162         for (int j = 0; j < array.size(); j++) {
1163             // we duplicate array value on the stack to be consumed by arrayStore
1164             // after completion of this loop the array value will be on top of the stack
1165             cob.dup();
1166             cob.loadConstant(j);
1167             load(array.get(j));
1168             cob.arrayStore(typeKind);
1169         }
1170     }
1171 
1172     private void exceptionRegionsChange(Block[] newCatchBlocks) {
1173         if (!Arrays.equals(recentCatchBlocks, newCatchBlocks)) {
1174             int i = recentCatchBlocks.length - 1;
1175             Label currentLabel = cob.newBoundLabel();
1176             // Exit catch blocks missing in the newCatchBlocks
1177             while (i >=0 && (i >= newCatchBlocks.length || recentCatchBlocks[i] != newCatchBlocks[i])) {
1178                 Block catchBlock = recentCatchBlocks[i--];
1179                 CodeType catchType = catchBlockTypes[catchBlock.index()];
1180                 Label tryStart = tryStartLabels[catchBlock.index()];
1181                 Label handler = getLabel(catchBlock.index());
1182                 switch (catchType) {
1183                     case TupleType tt ->
1184                         tt.componentTypes().forEach(type ->
1185                                 cob.exceptionCatch(tryStart, currentLabel, handler, ((JavaType) type).toNominalDescriptor()));
1186                     case ClassType ct ->
1187                         cob.exceptionCatch(tryStart, currentLabel, handler, ct.toNominalDescriptor());
1188                     case PrimitiveType pt when pt.equals(JavaType.VOID) ->
1189                         cob.exceptionCatchAll(tryStart, currentLabel, handler);
1190                     default ->
1191                         throw new IllegalArgumentException("Bad catch type: " + catchType);
1192                 }
1193                 tryStartLabels[catchBlock.index()] = null;
1194             }
1195             // Fill tryStartLabels for new entries
1196             while (++i < newCatchBlocks.length) {
1197                 tryStartLabels[newCatchBlocks[i].index()] = currentLabel;
1198             }
1199             recentCatchBlocks = newCatchBlocks;
1200         }
1201     }
1202 
1203     // Determine whether to issue a tableswitch or a lookupswitch
1204     // instruction.
1205     private static boolean tableSwitchOverLookupSwitch(long lo, long hi, long nlabels) {
1206             long table_space_cost = 4 + (hi - lo + 1); // words
1207             long table_time_cost = 3; // comparisons
1208             long lookup_space_cost = 3 + 2 * nlabels;
1209             long lookup_time_cost = nlabels;
1210             return
1211                 nlabels > 0 &&
1212                 table_space_cost + 3 * table_time_cost <=
1213                 lookup_space_cost + 3 * lookup_time_cost;
1214     }
1215 
1216     private void push(Value res) {
1217         if (res.type().equals(JavaType.VOID)) return;
1218         var uses = res.uses();
1219         Op op;
1220         if (uses.size() == 1
1221                 && uses.getFirst().declaringBlock() == res.declaringBlock()
1222                 && !canDefer(op = uses.getFirst().op())
1223                 // one value can be used in mutliple operands of a single op
1224                 && Stream.concat(op.operands().stream(), op.successors().stream()
1225                         .flatMap(r -> r.arguments().stream()))
1226                         .filter(res::equals).limit(2).count() == 1) {
1227             oprOnStack.add(res);
1228         } else {
1229             storeIfUsed(res);
1230         }
1231     }
1232 
1233     // the rhs of any shift instruction must be int or smaller -> convert longs
1234     private void adjustRightTypeToInt(Op op) {
1235         CodeType right = op.operands().getLast().type();
1236         if (right.equals(JavaType.LONG)) {
1237             cob.conversion(toTypeKind(right), TypeKind.INT);
1238         }
1239     }
1240 
1241     private static Op getConditionForCondBrOp(ConditionalBranchOp op) {
1242         Value p = op.predicateOperand();
1243         if (p.uses().size() != 1) {
1244             return null;
1245         }
1246 
1247         if (p.declaringBlock() != op.ancestorBlock()) {
1248             return null;
1249         }
1250 
1251         // Check if used in successor
1252         for (Block.Reference s : op.successors()) {
1253             if (s.arguments().contains(p)) {
1254                 return null;
1255             }
1256         }
1257 
1258         if (p instanceof Op.Result or) {
1259             return or.op();
1260         } else {
1261             return null;
1262         }
1263     }
1264 
1265     private void conditionalBranch(Opcode reverseOpcode, Block.Reference trueBlock, Block.Reference falseBlock) {
1266         if (needToAssignBlockArguments(falseBlock)) {
1267             Label falseLabel = cob.newLabel();
1268             cob.branch(reverseOpcode, falseLabel);
1269             assignBlockArguments(trueBlock);
1270             cob.goto_(getLabel(trueBlock))
1271                .labelBinding(falseLabel);
1272             assignBlockArguments(falseBlock);
1273             cob.goto_(getLabel(falseBlock));
1274         } else {
1275             cob.branch(reverseOpcode, getLabel(falseBlock));
1276             assignBlockArguments(trueBlock);
1277             cob.goto_(getLabel(trueBlock));
1278         }
1279     }
1280 
1281     private Opcode prepareConditionalBranch(CompareOp op, boolean keepPrefix) {
1282         Value firstOperand = op.operands().get(0);
1283         TypeKind typeKind = toTypeKind(firstOperand.type());
1284         Value secondOperand = op.operands().get(1);
1285         if (isZeroIntOrNullConstant(secondOperand)) {
1286             processOperands(List.of(firstOperand), keepPrefix);
1287             return switch (typeKind) {
1288                 case INT, BOOLEAN, BYTE, SHORT, CHAR ->
1289                     switch (op) {
1290                         case EqOp _ -> IFNE;
1291                         case NeqOp _ -> IFEQ;
1292                         case GtOp _ -> IFLE;
1293                         case GeOp _ -> IFLT;
1294                         case LtOp _ -> IFGE;
1295                         case LeOp _ -> IFGT;
1296                         default ->
1297                             throw new UnsupportedOperationException(op + " on int");
1298                     };
1299                 case REFERENCE ->
1300                     switch (op) {
1301                         case EqOp _ -> IFNONNULL;
1302                         case NeqOp _ -> IFNULL;
1303                         default ->
1304                             throw new UnsupportedOperationException(op + " on Object");
1305                     };
1306                 default ->
1307                     throw new UnsupportedOperationException(op + " on " + op.operands().get(0).type());
1308             };
1309         }
1310         processOperands(op.operands(), keepPrefix);
1311         return switch (typeKind) {
1312             case INT, BOOLEAN, BYTE, SHORT, CHAR ->
1313                 switch (op) {
1314                     case EqOp _ -> IF_ICMPNE;
1315                     case NeqOp _ -> IF_ICMPEQ;
1316                     case GtOp _ -> IF_ICMPLE;
1317                     case GeOp _ -> IF_ICMPLT;
1318                     case LtOp _ -> IF_ICMPGE;
1319                     case LeOp _ -> IF_ICMPGT;
1320                     default ->
1321                         throw new UnsupportedOperationException(op + " on int");
1322                 };
1323             case REFERENCE ->
1324                 switch (op) {
1325                     case EqOp _ -> IF_ACMPNE;
1326                     case NeqOp _ -> IF_ACMPEQ;
1327                     default ->
1328                         throw new UnsupportedOperationException(op + " on Object");
1329                 };
1330             case FLOAT -> {
1331                 // the instruction that come after fcmpg/fcmpl, branch for the false cases
1332                 // f < g -> fcmpg + ifge
1333                 // f <= g -> fcmpg + ifgt
1334                 // f == g -> fcmpg/fcmpl + ifne
1335                 // f > g -> fcmpl + ifle
1336                 // f >= g -> fcmpl + iflt
1337                 if (op instanceof LtOp || op instanceof LeOp) {
1338                     cob.fcmpg();
1339                 } else {
1340                     cob.fcmpl();
1341                 }
1342                 yield reverseIfOpcode(op);
1343             }
1344             case LONG -> {
1345                 cob.lcmp();
1346                 yield reverseIfOpcode(op);
1347             }
1348             case DOUBLE -> {
1349                 if (op instanceof LtOp || op instanceof LeOp) {
1350                     cob.dcmpg();
1351                 } else {
1352                     cob.dcmpl();
1353                 }
1354                 yield reverseIfOpcode(op);
1355             }
1356             default ->
1357                 throw new UnsupportedOperationException(op + " on " + op.operands().get(0).type());
1358         };
1359     }
1360 
1361     private boolean isZeroIntOrNullConstant(Value v) {
1362         return v instanceof Op.Result or
1363                 && or.op() instanceof ConstantOp cop
1364                 && switch (cop.value()) {
1365                     case null -> true;
1366                     case Integer i -> i == 0;
1367                     case Boolean b -> !b;
1368                     case Byte b -> b == 0;
1369                     case Short s -> s == 0;
1370                     case Character ch -> ch == 0;
1371                     default -> false;
1372                 };
1373     }
1374 
1375     private static Opcode reverseIfOpcode(CompareOp op) {
1376         return switch (op) {
1377             case EqOp _ -> IFNE;
1378             case NeqOp _ -> IFEQ;
1379             case GtOp _ -> IFLE;
1380             case GeOp _ -> IFLT;
1381             case LtOp _ -> IFGE;
1382             case LeOp _ -> IFGT;
1383             default ->
1384                 throw new UnsupportedOperationException(op.toString());
1385         };
1386     }
1387 
1388     private boolean needToAssignBlockArguments(Block.Reference ref) {
1389         List<Value> sargs = ref.arguments();
1390         List<Block.Parameter> bargs = ref.targetBlock().parameters();
1391         boolean need = false;
1392         for (int i = 0; i < bargs.size(); i++) {
1393             Block.Parameter barg = bargs.get(i);
1394             if (!barg.uses().isEmpty() && !barg.equals(sargs.get(i))) {
1395                 need = true;
1396                 allocateSlot(barg);
1397             }
1398         }
1399         return need;
1400     }
1401 
1402     private void assignBlockArguments(Block.Reference ref) {
1403         Block target = ref.targetBlock();
1404         List<Value> sargs = ref.arguments();
1405         if (catchBlockTypes[target.index()] != null) {
1406             // Jumping to an exception handler, exception parameter is expected on stack
1407             processOperands(List.of(sargs.getFirst()), false);
1408         } else if (target.predecessorReferences().size() > 1) {
1409             List<Block.Parameter> bargs = target.parameters();
1410             // First push successor arguments on the stack, then pop and assign
1411             // so as not to overwrite slots that are reused slots at different argument positions
1412             List<Value> values = new ArrayList<>();
1413             for (int i = 0; i < bargs.size(); i++) {
1414                 Block.Parameter barg = bargs.get(i);
1415                 Value value = sargs.get(i);
1416                 if (!barg.equals(value)) {
1417                     values.add(value);
1418                 }
1419             }
1420             processOperands(values, false);
1421             for (int i = bargs.size() - 1; i >= 0; i--) {
1422                 Block.Parameter barg = bargs.get(i);
1423                 if (!barg.equals(sargs.get(i))) {
1424                     storeIfUsed(barg);
1425                 }
1426             }
1427         } else {
1428             // Single-predecessor block can just map parameter slots
1429             storeOperandsOnStack();
1430             List<Block.Parameter> bargs = ref.targetBlock().parameters();
1431             for (int i = 0; i < bargs.size(); i++) {
1432                 Value value = sargs.get(i);
1433                 // Map slot of the block argument to slot of the value
1434                 singlePredecessorsValues.put(bargs.get(i), singlePredecessorsValues.getOrDefault(value, value));
1435             }
1436         }
1437     }
1438 }