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