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