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