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