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