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