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