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