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