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