1 /*
   2  * Copyright (c) 2024, 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 java.lang.reflect.code.bytecode;
  27 
  28 import java.lang.classfile.ClassFile;
  29 import java.lang.classfile.CodeBuilder;
  30 import java.lang.classfile.Label;
  31 import java.lang.constant.*;
  32 import java.lang.reflect.code.op.CoreOp;
  33 import java.lang.reflect.code.op.CoreOp.*;
  34 
  35 import java.lang.classfile.ClassBuilder;
  36 import java.lang.classfile.Opcode;
  37 import java.lang.classfile.TypeKind;
  38 import java.lang.classfile.attribute.ConstantValueAttribute;
  39 import java.lang.invoke.LambdaMetafactory;
  40 import java.lang.reflect.code.Block;
  41 import java.lang.reflect.code.Op;
  42 import java.lang.invoke.MethodHandle;
  43 import java.lang.invoke.MethodHandles;
  44 import java.lang.invoke.MethodType;
  45 import java.lang.reflect.code.Value;
  46 import java.lang.reflect.code.analysis.Liveness;
  47 import java.lang.reflect.code.type.ArrayType;
  48 import java.lang.reflect.code.type.FieldRef;
  49 import java.lang.reflect.code.type.MethodRef;
  50 import java.lang.reflect.code.type.FunctionType;
  51 import java.lang.reflect.code.type.JavaType;
  52 import java.lang.reflect.code.TypeElement;
  53 import java.lang.reflect.code.type.VarType;
  54 import java.util.ArrayList;
  55 import java.util.BitSet;
  56 import java.util.HashMap;
  57 import java.util.List;
  58 import java.util.Map;
  59 import java.util.Set;
  60 
  61 import static java.lang.constant.ConstantDescs.*;
  62 import java.lang.reflect.Method;
  63 import java.lang.reflect.Modifier;
  64 import java.lang.reflect.code.Quotable;
  65 import java.util.stream.Stream;
  66 
  67 /**
  68  * Transformer of code models to bytecode.
  69  */
  70 public final class BytecodeGenerator {
  71 
  72     private static final DirectMethodHandleDesc DMHD_LAMBDA_METAFACTORY = ofCallsiteBootstrap(
  73             LambdaMetafactory.class.describeConstable().orElseThrow(),
  74             "metafactory",
  75             CD_CallSite, CD_MethodType, CD_MethodHandle, CD_MethodType);
  76 
  77     private static final DirectMethodHandleDesc DMHD_LAMBDA_ALT_METAFACTORY = ofCallsiteBootstrap(
  78             LambdaMetafactory.class.describeConstable().orElseThrow(),
  79             "altMetafactory",
  80             CD_CallSite, CD_Object.arrayType());
  81 
  82     /**
  83      * Transforms the invokable operation to bytecode encapsulated in a method of hidden class and exposed
  84      * for invocation via a method handle.
  85      *
  86      * @param l the lookup
  87      * @param iop the invokable operation to transform to bytecode
  88      * @return the invoking method handle
  89      * @param <O> the type of the invokable operation
  90      */
  91     public static <O extends Op & Op.Invokable> MethodHandle generate(MethodHandles.Lookup l, O iop) {
  92         String name = iop instanceof FuncOp fop ? fop.funcName() : "m";
  93         byte[] classBytes = generateClassData(l, name, iop);
  94 
  95         MethodHandles.Lookup hcl;
  96         try {
  97             hcl = l.defineHiddenClass(classBytes, true);
  98         } catch (IllegalAccessException e) {
  99             throw new RuntimeException(e);
 100         }
 101 
 102         try {
 103             FunctionType ft = iop.invokableType();
 104             MethodType mt = MethodRef.toNominalDescriptor(ft).resolveConstantDesc(hcl);
 105             return hcl.findStatic(hcl.lookupClass(), name, mt);
 106         } catch (ReflectiveOperationException e) {
 107             throw new RuntimeException(e);
 108         }
 109     }
 110 
 111     /**
 112      * Transforms the function operation to bytecode encapsulated in a method of a class file.
 113      * <p>
 114      * The name of the method is the function operation's {@link FuncOp#funcName() function name}.
 115      *
 116      * @param lookup the lookup
 117      * @param fop the function operation to transform to bytecode
 118      * @return the class file bytes
 119      */
 120     public static byte[] generateClassData(MethodHandles.Lookup lookup, FuncOp fop) {
 121         return generateClassData(lookup, fop.funcName(), fop);
 122     }
 123 
 124     /**
 125      * Transforms the invokable operation to bytecode encapsulated in a method of a class file.
 126      *
 127      * @param lookup the lookup
 128      * @param name the name to use for the method of the class file
 129      * @param iop the invokable operation to transform to bytecode
 130      * @return the class file bytes
 131      * @param <O> the type of the invokable operation
 132      */
 133     public static <O extends Op & Op.Invokable> byte[] generateClassData(MethodHandles.Lookup lookup,
 134                                                                          String name,
 135                                                                          O iop) {
 136         if (!iop.capturedValues().isEmpty()) {
 137             throw new UnsupportedOperationException("Operation captures values");
 138         }
 139 
 140         String packageName = lookup.lookupClass().getPackageName();
 141         ClassDesc className = ClassDesc.of(packageName.isEmpty()
 142                 ? name
 143                 : packageName + "." + name);
 144         byte[] classBytes = ClassFile.of().build(className, clb -> {
 145             List<LambdaOp> lambdaSink = new ArrayList<>();
 146             BitSet quotable = new BitSet();
 147             generateMethod(lookup, className, name, iop, clb, lambdaSink, quotable);
 148             for (int i = 0; i < lambdaSink.size(); i++) {
 149                 LambdaOp lop = lambdaSink.get(i);
 150                 if (quotable.get(i)) {
 151                     clb.withField("lambda$" + i + "$op", CD_String, fb -> fb
 152                             .withFlags(ClassFile.ACC_STATIC)
 153                             .with(ConstantValueAttribute.of(quote(lop).toText())));
 154                 }
 155                 generateMethod(lookup, className, "lambda$" + i, lop, clb, lambdaSink, quotable);
 156             }
 157         });
 158         return classBytes;
 159     }
 160 
 161     private static <O extends Op & Op.Invokable> void generateMethod(MethodHandles.Lookup lookup,
 162                                                                      ClassDesc className,
 163                                                                      String methodName,
 164                                                                      O iop,
 165                                                                      ClassBuilder clb,
 166                                                                      List<LambdaOp> lambdaSink,
 167                                                                      BitSet quotable) {
 168 
 169         List<Value> capturedValues = iop instanceof LambdaOp lop ? lop.capturedValues() : List.of();
 170         MethodTypeDesc mtd = MethodRef.toNominalDescriptor(
 171                 iop.invokableType()).insertParameterTypes(0, capturedValues.stream()
 172                         .map(Value::type).map(BytecodeGenerator::toClassDesc).toArray(ClassDesc[]::new));
 173         clb.withMethodBody(methodName, mtd, ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
 174                 cb -> cb.transforming(new BranchCompactor(), cob ->
 175                     new BytecodeGenerator(lookup, className, capturedValues, new Liveness(iop),
 176                                           iop.body().blocks(), cob, lambdaSink, quotable).generate()));
 177     }
 178 
 179     private record Slot(int slot, TypeKind typeKind) {}
 180     private record ExceptionRegionWithBlocks(CoreOp.ExceptionRegionEnter ere, BitSet blocks) {}
 181 
 182     private final MethodHandles.Lookup lookup;
 183     private final ClassDesc className;
 184     private final List<Value> capturedValues;
 185     private final List<Block> blocks;
 186     private final CodeBuilder cob;
 187     private final Label[] blockLabels;
 188     private final List<ExceptionRegionWithBlocks> allExceptionRegions;
 189     private final BitSet[] blocksRegionStack;
 190     private final BitSet blocksToVisit, catchingBlocks;
 191     private final Map<Value, Slot> slots;
 192     private final List<LambdaOp> lambdaSink;
 193     private final BitSet quotable;
 194     private Op.Result oprOnStack;
 195 
 196     private BytecodeGenerator(MethodHandles.Lookup lookup,
 197                               ClassDesc className,
 198                               List<Value> capturedValues,
 199                               Liveness liveness,
 200                               List<Block> blocks,
 201                               CodeBuilder cob,
 202                               List<LambdaOp> lambdaSink,
 203                               BitSet quotable) {
 204         this.lookup = lookup;
 205         this.className = className;
 206         this.capturedValues = capturedValues;
 207         this.blocks = blocks;
 208         this.cob = cob;
 209         this.blockLabels = new Label[blocks.size()];
 210         this.allExceptionRegions = new ArrayList<>();
 211         this.blocksRegionStack = new BitSet[blocks.size()];
 212         this.blocksToVisit = new BitSet(blocks.size());
 213         this.catchingBlocks = new BitSet();
 214         this.slots = new HashMap<>();
 215         this.lambdaSink = lambdaSink;
 216         this.quotable = quotable;
 217     }
 218 
 219     private void setExceptionRegionStack(Block.Reference target, BitSet activeRegionStack) {
 220         setExceptionRegionStack(target.targetBlock().index(), activeRegionStack);
 221     }
 222 
 223     private void setExceptionRegionStack(int blockIndex, BitSet activeRegionStack) {
 224         if (blocksRegionStack[blockIndex] == null) {
 225             blocksToVisit.set(blockIndex);
 226             blocksRegionStack[blockIndex] = activeRegionStack;
 227             activeRegionStack.stream().forEach(r -> allExceptionRegions.get(r).blocks.set(blockIndex));
 228         }
 229     }
 230 
 231     private Label getLabel(Block.Reference target) {
 232         return getLabel(target.targetBlock().index());
 233     }
 234 
 235     private Label getLabel(int blockIndex) {
 236         Label l = blockLabels[blockIndex];
 237         if (l == null) {
 238             blockLabels[blockIndex] = l = cob.newLabel();
 239         }
 240         return l;
 241     }
 242 
 243     private Slot allocateSlot(Value v) {
 244         return slots.computeIfAbsent(v, _ -> {
 245             TypeKind tk = toTypeKind(v.type());
 246             return new Slot(cob.allocateLocal(tk), tk);
 247         });
 248     }
 249 
 250     private void storeIfUsed(Value v) {
 251         if (!v.uses().isEmpty()) {
 252             Slot slot = allocateSlot(v);
 253             cob.storeInstruction(slot.typeKind(), slot.slot());
 254         } else {
 255             // Only pop results from stack if the value has no further use (no valid slot)
 256             switch (toTypeKind(v.type()).slotSize()) {
 257                 case 1 -> cob.pop();
 258                 case 2 -> cob.pop2();
 259             }
 260         }
 261     }
 262 
 263     private Slot load(Value v) {
 264         if (v instanceof Op.Result or &&
 265                 or.op() instanceof CoreOp.ConstantOp constantOp &&
 266                 !constantOp.resultType().equals(JavaType.J_L_CLASS)) {
 267             cob.constantInstruction(((Constable)constantOp.value()).describeConstable().orElseThrow());
 268             return null;
 269         } else {
 270             Slot slot = slots.get(v);
 271             cob.loadInstruction(slot.typeKind(), slot.slot());
 272             return slot;
 273         }
 274     }
 275 
 276     private void processFirstOperand(Op op) {
 277         processOperand(op.operands().getFirst());;
 278     }
 279 
 280     private void processOperand(Value operand) {
 281         if (oprOnStack == null) {
 282             load(operand);
 283         } else {
 284             assert oprOnStack == operand;
 285             oprOnStack = null;
 286         }
 287     }
 288 
 289     private void processOperands(Op op) {
 290         processOperands(op.operands());
 291     }
 292 
 293     private void processOperands(List<Value> operands) {
 294         if (oprOnStack == null) {
 295             operands.forEach(this::load);
 296         } else {
 297             assert !operands.isEmpty() && oprOnStack == operands.getFirst();
 298             oprOnStack = null;
 299             for (int i = 1; i < operands.size(); i++) {
 300                 load(operands.get(i));
 301             }
 302         }
 303     }
 304 
 305     // Some of the operations can be deferred
 306     private static boolean canDefer(Op op) {
 307         return switch (op) {
 308             case ConstantOp cop -> canDefer(cop);
 309             case VarOp vop -> canDefer(vop);
 310             case VarAccessOp.VarLoadOp vlop -> canDefer(vlop);
 311             default -> false;
 312         };
 313     }
 314 
 315     // Constant can be deferred, except for loading of a class constant, which  may throw an exception
 316     private static boolean canDefer(ConstantOp op) {
 317         return !op.resultType().equals(JavaType.J_L_CLASS);
 318     }
 319 
 320     // Var with a single-use block parameter operand can be deferred
 321     private static boolean canDefer(VarOp op) {
 322         return op.operands().getFirst() instanceof Block.Parameter bp && bp.uses().size() == 1;
 323     }
 324 
 325     // Var load can be deferred when not used as immediate operand
 326     private static boolean canDefer(VarAccessOp.VarLoadOp op) {
 327         return !isNextUse(op.result());
 328     }
 329 
 330     // This method narrows the first operand inconveniences of some operations
 331     private static boolean isFirstOperand(Op nextOp, Op.Result opr) {
 332         return switch (nextOp) {
 333             // When there is no next operation
 334             case null -> false;
 335             // New object cannot use first operand from stack, new array fall through to the default
 336             case NewOp op when !(op.constructorType().returnType() instanceof ArrayType) ->
 337                 false;
 338             // For lambda the effective operands are captured values
 339             case LambdaOp op ->
 340                 !op.capturedValues().isEmpty() && op.capturedValues().getFirst() == opr;
 341             // Conditional branch may delegate to its binary test operation
 342             case ConditionalBranchOp op when getConditionForCondBrOp(op) instanceof CoreOp.BinaryTestOp bto ->
 343                 isFirstOperand(bto, opr);
 344             // Var store effective first operand is not the first one
 345             case VarAccessOp.VarStoreOp op ->
 346                 op.operands().get(1) == opr;
 347             // regular check of the first operand
 348             default ->
 349                 !nextOp.operands().isEmpty() && nextOp.operands().getFirst() == opr;
 350         };
 351     }
 352 
 353     // Determines if the operation result is immediatelly used by the next operation and so can stay on stack
 354     private static boolean isNextUse(Op.Result opr) {
 355         // Pass over deferred operations
 356         Op nextOp = opr.op();
 357         do {
 358             nextOp = opr.declaringBlock().nextOp(nextOp);
 359         } while (canDefer(nextOp));
 360         return isFirstOperand(nextOp, opr);
 361     }
 362 
 363     private static boolean isConditionForCondBrOp(CoreOp.BinaryTestOp op) {
 364         // Result of op has one use as the operand of a CondBrOp op,
 365         // and both ops are in the same block
 366 
 367         Set<Op.Result> uses = op.result().uses();
 368         if (uses.size() != 1) {
 369             return false;
 370         }
 371         Op.Result use = uses.iterator().next();
 372 
 373         if (use.declaringBlock() != op.parentBlock()) {
 374             return false;
 375         }
 376 
 377         // Check if used in successor
 378         for (Block.Reference s : use.op().successors()) {
 379             if (s.arguments().contains(op.result())) {
 380                 return false;
 381             }
 382         }
 383 
 384         return use.op() instanceof CoreOp.ConditionalBranchOp;
 385     }
 386 
 387     private static ClassDesc toClassDesc(TypeElement t) {
 388         return switch (t) {
 389             case VarType vt -> toClassDesc(vt.valueType());
 390             case JavaType jt -> jt.toNominalDescriptor();
 391             default ->
 392                 throw new IllegalArgumentException("Bad type: " + t);
 393         };
 394     }
 395 
 396     private static TypeKind toTypeKind(TypeElement t) {
 397         return switch (t) {
 398             case VarType vt -> toTypeKind(vt.valueType());
 399             case JavaType jt -> {
 400                 TypeElement bt = jt.toBasicType();
 401                 if (bt.equals(JavaType.VOID)) {
 402                     yield TypeKind.VoidType;
 403                 } else if (bt.equals(JavaType.INT)) {
 404                     yield TypeKind.IntType;
 405                 } else if (bt.equals(JavaType.J_L_OBJECT)) {
 406                     yield TypeKind.ReferenceType;
 407                 } else if (bt.equals(JavaType.LONG)) {
 408                     yield TypeKind.LongType;
 409                 } else if (bt.equals(JavaType.DOUBLE)) {
 410                     yield TypeKind.DoubleType;
 411                 } else if (bt.equals(JavaType.BOOLEAN)) {
 412                     yield TypeKind.BooleanType;
 413                 } else if (bt.equals(JavaType.BYTE)) {
 414                     yield TypeKind.ByteType;
 415                 } else if (bt.equals(JavaType.CHAR)) {
 416                     yield TypeKind.CharType;
 417                 } else if (bt.equals(JavaType.FLOAT)) {
 418                     yield TypeKind.FloatType;
 419                 } else if (bt.equals(JavaType.SHORT)) {
 420                     yield TypeKind.ShortType;
 421                 } else {
 422                     throw new IllegalArgumentException("Bad type: " + t);
 423                 }
 424             }
 425             default ->
 426                 throw new IllegalArgumentException("Bad type: " + t);
 427         };
 428     }
 429 
 430     private void generate() {
 431         // Compute exception region membership
 432         setExceptionRegionStack(0, new BitSet());
 433         int blockIndex;
 434         while ((blockIndex = blocksToVisit.nextSetBit(0)) >= 0) {
 435             blocksToVisit.clear(blockIndex);
 436             BitSet activeRegionStack = blocksRegionStack[blockIndex];
 437             Block b = blocks.get(blockIndex);
 438             Op top = b.terminatingOp();
 439             switch (top) {
 440                 case CoreOp.BranchOp bop ->
 441                     setExceptionRegionStack(bop.branch(), activeRegionStack);
 442                 case CoreOp.ConditionalBranchOp cop -> {
 443                     setExceptionRegionStack(cop.falseBranch(), activeRegionStack);
 444                     setExceptionRegionStack(cop.trueBranch(), activeRegionStack);
 445                 }
 446                 case CoreOp.ExceptionRegionEnter er -> {
 447                     for (Block.Reference catchBlock : er.catchBlocks().reversed()) {
 448                         catchingBlocks.set(catchBlock.targetBlock().index());
 449                         setExceptionRegionStack(catchBlock, activeRegionStack);
 450                     }
 451                     activeRegionStack = (BitSet)activeRegionStack.clone();
 452                     activeRegionStack.set(allExceptionRegions.size());
 453                     ExceptionRegionWithBlocks newNode = new ExceptionRegionWithBlocks(er, new BitSet());
 454                     allExceptionRegions.add(newNode);
 455                     setExceptionRegionStack(er.start(), activeRegionStack);
 456                 }
 457                 case CoreOp.ExceptionRegionExit er -> {
 458                     activeRegionStack = (BitSet)activeRegionStack.clone();
 459                     activeRegionStack.clear(activeRegionStack.length() - 1);
 460                     setExceptionRegionStack(er.end(), activeRegionStack);
 461                 }
 462                 default -> {
 463                 }
 464             }
 465         }
 466 
 467         // Declare the exception regions
 468         for (ExceptionRegionWithBlocks erNode : allExceptionRegions.reversed()) {
 469             int start  = erNode.blocks.nextSetBit(0);
 470             while (start >= 0) {
 471                 int end = erNode.blocks.nextClearBit(start);
 472                 Label startLabel = getLabel(start);
 473                 Label endLabel = getLabel(end);
 474                 for (Block.Reference cbr : erNode.ere.catchBlocks()) {
 475                     List<Block.Parameter> params = cbr.targetBlock().parameters();
 476                     if (!params.isEmpty()) {
 477                         JavaType jt = (JavaType) params.get(0).type();
 478                         cob.exceptionCatch(startLabel, endLabel, getLabel(cbr), jt.toNominalDescriptor());
 479                     } else {
 480                         cob.exceptionCatchAll(startLabel, endLabel, getLabel(cbr));
 481                     }
 482                 }
 483                 start = erNode.blocks.nextSetBit(end);
 484             }
 485         }
 486 
 487         // Process blocks in topological order
 488         // A jump instruction assumes the false successor block is
 489         // immediately after, in sequence, to the predecessor
 490         // since the jump instructions branch on a true condition
 491         // Conditions are inverted when lowered to bytecode
 492         for (Block b : blocks) {
 493             // Ignore any non-entry blocks that have no predecessors
 494             if (!b.isEntryBlock() && b.predecessors().isEmpty()) {
 495                 continue;
 496             }
 497 
 498             Label blockLabel = getLabel(b.index());
 499             cob.labelBinding(blockLabel);
 500 
 501             // If b is the entry block then all its parameters conservatively require slots
 502             // Some unused parameters might be declared before others that are used
 503             if (b.isEntryBlock()) {
 504                 List<Block.Parameter> parameters = b.parameters();
 505                 int i = 0;
 506                 // Captured values prepend parameters in lambda impl methods
 507                 for (Value cv : capturedValues) {
 508                     slots.put(cv, new Slot(cob.parameterSlot(i++), toTypeKind(cv.type())));
 509                 }
 510                 for (Block.Parameter bp : parameters) {
 511                     slots.put(bp, new Slot(cob.parameterSlot(i++), toTypeKind(bp.type())));
 512                 }
 513             }
 514 
 515             // If b is a catch block then the exception argument will be represented on the stack
 516             if (catchingBlocks.get(b.index())) {
 517                 // Retain block argument for exception table generation
 518                 storeIfUsed(b.parameters().get(0));
 519             }
 520 
 521             List<Op> ops = b.ops();
 522             oprOnStack = null;
 523             for (int i = 0; i < ops.size() - 1; i++) {
 524                 final Op o = ops.get(i);
 525                 final TypeElement oprType = o.resultType();
 526                 final TypeKind rvt = toTypeKind(oprType);
 527                 switch (o) {
 528                     case ConstantOp op -> {
 529                         if (!canDefer(op)) {
 530                             // Constant can be deferred, except for a class constant, which  may throw an exception
 531                             cob.ldc(((JavaType)op.value()).toNominalDescriptor());
 532                             push(op.result());
 533                         }
 534                     }
 535                     case VarOp op -> {
 536                         //     %1 : Var<int> = var %0 @"i";
 537                         if (canDefer(op)) {
 538                             // Var with a single-use block parameter operand can be deferred
 539                             slots.put(op.result(), slots.get(op.operands().getFirst()));
 540                         } else {
 541                             processOperand(op.operands().getFirst());
 542                             allocateSlot(op.result());
 543                             push(op.result());
 544                         }
 545                     }
 546                     case VarAccessOp.VarLoadOp op -> {
 547                         if (canDefer(op)) {
 548                             // Var load can be deferred when not used as immediate operand
 549                             slots.computeIfAbsent(op.result(), r -> slots.get(op.operands().getFirst()));
 550                         } else {
 551                             processFirstOperand(op);
 552                             push(op.result());
 553                         }
 554                     }
 555                     case VarAccessOp.VarStoreOp op -> {
 556                         processOperand(op.operands().get(1));
 557                         storeIfUsed(op.operands().get(0));
 558                     }
 559                     case ConvOp op -> {
 560                         Value first = op.operands().getFirst();
 561                         processOperand(first);
 562                         TypeKind tk = toTypeKind(first.type());
 563                         if (tk != rvt) cob.convertInstruction(tk, rvt);
 564                         push(op.result());
 565                     }
 566                     case NegOp op -> {
 567                         processFirstOperand(op);
 568                         switch (rvt) { //this can be moved to CodeBuilder::neg(TypeKind)
 569                             case IntType, BooleanType, ByteType, ShortType, CharType -> cob.ineg();
 570                             case LongType -> cob.lneg();
 571                             case FloatType -> cob.fneg();
 572                             case DoubleType -> cob.dneg();
 573                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 574                         }
 575                         push(op.result());
 576                     }
 577                     case NotOp op -> {
 578                         processFirstOperand(op);
 579                         cob.ifThenElse(CodeBuilder::iconst_0, CodeBuilder::iconst_1);
 580                         push(op.result());
 581                     }
 582                     case AddOp op -> {
 583                         processOperands(op);
 584                         switch (rvt) { //this can be moved to CodeBuilder::add(TypeKind)
 585                             case IntType, BooleanType, ByteType, ShortType, CharType -> cob.iadd();
 586                             case LongType -> cob.ladd();
 587                             case FloatType -> cob.fadd();
 588                             case DoubleType -> cob.dadd();
 589                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 590                         }
 591                         push(op.result());
 592                     }
 593                     case SubOp op -> {
 594                         processOperands(op);
 595                         switch (rvt) { //this can be moved to CodeBuilder::sub(TypeKind)
 596                             case IntType, BooleanType, ByteType, ShortType, CharType -> cob.isub();
 597                             case LongType -> cob.lsub();
 598                             case FloatType -> cob.fsub();
 599                             case DoubleType -> cob.dsub();
 600                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 601                         }
 602                         push(op.result());
 603                     }
 604                     case MulOp op -> {
 605                         processOperands(op);
 606                         switch (rvt) { //this can be moved to CodeBuilder::mul(TypeKind)
 607                             case IntType, BooleanType, ByteType, ShortType, CharType -> cob.imul();
 608                             case LongType -> cob.lmul();
 609                             case FloatType -> cob.fmul();
 610                             case DoubleType -> cob.dmul();
 611                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 612                         }
 613                         push(op.result());
 614                     }
 615                     case DivOp op -> {
 616                         processOperands(op);
 617                         switch (rvt) { //this can be moved to CodeBuilder::div(TypeKind)
 618                             case IntType, BooleanType, ByteType, ShortType, CharType -> cob.idiv();
 619                             case LongType -> cob.ldiv();
 620                             case FloatType -> cob.fdiv();
 621                             case DoubleType -> cob.ddiv();
 622                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 623                         }
 624                         push(op.result());
 625                     }
 626                     case ModOp op -> {
 627                         processOperands(op);
 628                         switch (rvt) { //this can be moved to CodeBuilder::rem(TypeKind)
 629                             case IntType, BooleanType, ByteType, ShortType, CharType -> cob.irem();
 630                             case LongType -> cob.lrem();
 631                             case FloatType -> cob.frem();
 632                             case DoubleType -> cob.drem();
 633                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 634                         }
 635                         push(op.result());
 636                     }
 637                     case AndOp op -> {
 638                         processOperands(op);
 639                         switch (rvt) { //this can be moved to CodeBuilder::and(TypeKind)
 640                             case IntType, BooleanType, ByteType, ShortType, CharType -> cob.iand();
 641                             case LongType -> cob.land();
 642                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 643                         }
 644                         push(op.result());
 645                     }
 646                     case OrOp op -> {
 647                         processOperands(op);
 648                         switch (rvt) { //this can be moved to CodeBuilder::or(TypeKind)
 649                             case IntType, BooleanType, ByteType, ShortType, CharType -> cob.ior();
 650                             case LongType -> cob.lor();
 651                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 652                         }
 653                         push(op.result());
 654                     }
 655                     case XorOp op -> {
 656                         processOperands(op);
 657                         switch (rvt) { //this can be moved to CodeBuilder::xor(TypeKind)
 658                             case IntType, BooleanType, ByteType, ShortType, CharType -> cob.ixor();
 659                             case LongType -> cob.lxor();
 660                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 661                         }
 662                         push(op.result());
 663                     }
 664                     case LshlOp op -> {
 665                         processOperands(op);
 666                         adjustRightTypeToInt(op);
 667                         switch (rvt) { //this can be moved to CodeBuilder::shl(TypeKind)
 668                             case IntType -> cob.ishl();
 669                             case LongType -> cob.lshl();
 670                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 671                         }
 672                         push(op.result());
 673                     }
 674                     case AshrOp op -> {
 675                         processOperands(op);
 676                         adjustRightTypeToInt(op);
 677                         switch (rvt) { //this can be moved to CodeBuilder::shr(TypeKind)
 678                             case IntType, ByteType, ShortType, CharType -> cob.ishr();
 679                             case LongType -> cob.lshr();
 680                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 681                         }
 682                         push(op.result());
 683                     }
 684                     case LshrOp op -> {
 685                         processOperands(op);
 686                         adjustRightTypeToInt(op);
 687                         switch (rvt) { //this can be moved to CodeBuilder::ushr(TypeKind)
 688                             case IntType, ByteType, ShortType, CharType -> cob.iushr();
 689                             case LongType -> cob.lushr();
 690                             default -> throw new IllegalArgumentException("Bad type: " + op.resultType());
 691                         }
 692                         push(op.result());
 693                     }
 694                     case ArrayAccessOp.ArrayLoadOp op -> {
 695                         processOperands(op);
 696                         cob.arrayLoadInstruction(rvt);
 697                         push(op.result());
 698                     }
 699                     case ArrayAccessOp.ArrayStoreOp op -> {
 700                         processOperands(op);
 701                         cob.arrayStoreInstruction(toTypeKind(op.operands().get(2).type()));
 702                         push(op.result());
 703                     }
 704                     case ArrayLengthOp op -> {
 705                         processFirstOperand(op);
 706                         cob.arraylength();
 707                         push(op.result());
 708                     }
 709                     case BinaryTestOp op -> {
 710                         if (!isConditionForCondBrOp(op)) {
 711                             processOperands(op);
 712                             cob.ifThenElse(prepareReverseCondition(op), CodeBuilder::iconst_0, CodeBuilder::iconst_1);
 713                             push(op.result());
 714                         }
 715                         // Processing is deferred to the CondBrOp, do not process the op result
 716                     }
 717                     case NewOp op -> {
 718                         switch (op.constructorType().returnType()) {
 719                             case ArrayType at -> {
 720                                 processOperands(op);
 721                                 if (at.dimensions() == 1) {
 722                                     ClassDesc ctd = at.componentType().toNominalDescriptor();
 723                                     if (ctd.isPrimitive()) {
 724                                         cob.newarray(TypeKind.from(ctd));
 725                                     } else {
 726                                         cob.anewarray(ctd);
 727                                     }
 728                                 } else {
 729                                     cob.multianewarray(at.toNominalDescriptor(), op.operands().size());
 730                                 }
 731                             }
 732                             case JavaType jt -> {
 733                                 cob.new_(jt.toNominalDescriptor())
 734                                     .dup();
 735                                 processOperands(op);
 736                                 cob.invokespecial(
 737                                         ((JavaType) op.resultType()).toNominalDescriptor(),
 738                                         ConstantDescs.INIT_NAME,
 739                                         MethodRef.toNominalDescriptor(op.constructorType())
 740                                                  .changeReturnType(ConstantDescs.CD_void));
 741                             }
 742                             default ->
 743                                 throw new IllegalArgumentException("Invalid return type: "
 744                                                                     + op.constructorType().returnType());
 745                         }
 746                         push(op.result());
 747                     }
 748                     case InvokeOp op -> {
 749                         processOperands(op);
 750                         // @@@ Enhance method descriptor to include how the method is to be invoked
 751                         // Example result of DirectMethodHandleDesc.toString()
 752                         //   INTERFACE_VIRTUAL/IntBinaryOperator::applyAsInt(IntBinaryOperator,int,int)int
 753                         // This will avoid the need to reflectively operate on the descriptor
 754                         // which may be insufficient in certain cases.
 755                         DirectMethodHandleDesc.Kind descKind;
 756                         try {
 757                             descKind = resolveToMethodHandleDesc(lookup, op.invokeDescriptor()).kind();
 758                         } catch (ReflectiveOperationException e) {
 759                             // @@@ Approximate fallback
 760                             if (op.hasReceiver()) {
 761                                 descKind = DirectMethodHandleDesc.Kind.VIRTUAL;
 762                             } else {
 763                                 descKind = DirectMethodHandleDesc.Kind.STATIC;
 764                             }
 765                         }
 766                         MethodRef md = op.invokeDescriptor();
 767                         cob.invokeInstruction(
 768                                 switch (descKind) {
 769                                     case STATIC, INTERFACE_STATIC   -> Opcode.INVOKESTATIC;
 770                                     case VIRTUAL                    -> Opcode.INVOKEVIRTUAL;
 771                                     case INTERFACE_VIRTUAL          -> Opcode.INVOKEINTERFACE;
 772                                     case SPECIAL, INTERFACE_SPECIAL -> Opcode.INVOKESPECIAL;
 773                                     default ->
 774                                         throw new IllegalStateException("Bad method descriptor resolution: "
 775                                                                         + op.opType() + " > " + op.invokeDescriptor());
 776                                 },
 777                                 ((JavaType) md.refType()).toNominalDescriptor(),
 778                                 md.name(),
 779                                 MethodRef.toNominalDescriptor(md.type()),
 780                                 switch (descKind) {
 781                                     case INTERFACE_STATIC, INTERFACE_VIRTUAL, INTERFACE_SPECIAL -> true;
 782                                     default -> false;
 783                                 });
 784 
 785                         push(op.result());
 786                     }
 787                     case FieldAccessOp.FieldLoadOp op -> {
 788                         processOperands(op);
 789                         FieldRef fd = op.fieldDescriptor();
 790                         if (op.operands().isEmpty()) {
 791                             cob.getstatic(
 792                                     ((JavaType) fd.refType()).toNominalDescriptor(),
 793                                     fd.name(),
 794                                     ((JavaType) fd.type()).toNominalDescriptor());
 795                         } else {
 796                             cob.getfield(
 797                                     ((JavaType) fd.refType()).toNominalDescriptor(),
 798                                     fd.name(),
 799                                     ((JavaType) fd.type()).toNominalDescriptor());
 800                         }
 801                         push(op.result());
 802                     }
 803                     case FieldAccessOp.FieldStoreOp op -> {
 804                         processOperands(op);
 805                         FieldRef fd = op.fieldDescriptor();
 806                         if (op.operands().size() == 1) {
 807                             cob.putstatic(
 808                                     ((JavaType) fd.refType()).toNominalDescriptor(),
 809                                     fd.name(),
 810                                     ((JavaType) fd.type()).toNominalDescriptor());
 811                         } else {
 812                             cob.putfield(
 813                                     ((JavaType) fd.refType()).toNominalDescriptor(),
 814                                     fd.name(),
 815                                     ((JavaType) fd.type()).toNominalDescriptor());
 816                         }
 817                     }
 818                     case InstanceOfOp op -> {
 819                         processFirstOperand(op);
 820                         cob.instanceof_(((JavaType) op.type()).toNominalDescriptor());
 821                         push(op.result());
 822                     }
 823                     case CastOp op -> {
 824                         processFirstOperand(op);
 825                         cob.checkcast(((JavaType) op.type()).toNominalDescriptor());
 826                         push(op.result());
 827                     }
 828                     case LambdaOp op -> {
 829                         JavaType intfType = (JavaType)op.functionalInterface();
 830                         MethodTypeDesc mtd = MethodRef.toNominalDescriptor(op.invokableType());
 831                         try {
 832                             Class<?> intfClass = (Class<?>)intfType.erasure().resolve(lookup);
 833                             processOperands(op.capturedValues());
 834                             ClassDesc[] captureTypes = op.capturedValues().stream()
 835                                     .map(Value::type).map(BytecodeGenerator::toClassDesc).toArray(ClassDesc[]::new);
 836                             int lambdaIndex = lambdaSink.size();
 837                             if (Quotable.class.isAssignableFrom(intfClass)) {
 838                                 // @@@ double the captured values to enable LambdaMetafactory.FLAG_QUOTABLE
 839                                 for (Value cv : op.capturedValues()) {
 840                                     load(cv);
 841                                 }
 842                                 cob.invokedynamic(DynamicCallSiteDesc.of(
 843                                         DMHD_LAMBDA_ALT_METAFACTORY,
 844                                         funcIntfMethodName(intfClass),
 845                                         // @@@ double the descriptor parameters
 846                                         MethodTypeDesc.of(intfType.toNominalDescriptor(),
 847                                                           Stream.concat(Stream.of(captureTypes),
 848                                                                         Stream.of(captureTypes)).toList()),
 849                                         mtd,
 850                                         MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
 851                                                                   className,
 852                                                                   "lambda$" + lambdaIndex,
 853                                                                   mtd.insertParameterTypes(0, captureTypes)),
 854                                         mtd,
 855                                         LambdaMetafactory.FLAG_QUOTABLE,
 856                                         MethodHandleDesc.ofField(DirectMethodHandleDesc.Kind.STATIC_GETTER,
 857                                                                  className,
 858                                                                  "lambda$" + lambdaIndex + "$op",
 859                                                                  CD_String)));
 860                                 quotable.set(lambdaSink.size());
 861                             } else {
 862                                 cob.invokedynamic(DynamicCallSiteDesc.of(
 863                                         DMHD_LAMBDA_METAFACTORY,
 864                                         funcIntfMethodName(intfClass),
 865                                         MethodTypeDesc.of(intfType.toNominalDescriptor(), captureTypes),
 866                                         mtd,
 867                                         MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC,
 868                                                                   className,
 869                                                                   "lambda$" + lambdaIndex,
 870                                                                   mtd.insertParameterTypes(0, captureTypes)),
 871                                         mtd));
 872                             }
 873                             lambdaSink.add(op);
 874                         } catch (ReflectiveOperationException e) {
 875                             throw new IllegalArgumentException(e);
 876                         }
 877                         push(op.result());
 878                     }
 879                     default ->
 880                         throw new UnsupportedOperationException("Unsupported operation: " + ops.get(i));
 881                 }
 882             }
 883             Op top = b.terminatingOp();
 884             switch (top) {
 885                 case CoreOp.ReturnOp op -> {
 886                     Value a = op.returnValue();
 887                     if (a == null) {
 888                         cob.return_();
 889                     } else {
 890                         processFirstOperand(op);
 891                         cob.returnInstruction(toTypeKind(a.type()));
 892                     }
 893                 }
 894                 case ThrowOp op -> {
 895                     processFirstOperand(op);
 896                     cob.athrow();
 897                 }
 898                 case BranchOp op -> {
 899                     assignBlockArguments(op.branch());
 900                     cob.goto_(getLabel(op.branch()));
 901                 }
 902                 case ConditionalBranchOp op -> {
 903                     if (getConditionForCondBrOp(op) instanceof CoreOp.BinaryTestOp btop) {
 904                         // Processing of the BinaryTestOp was deferred, so it can be merged with CondBrOp
 905                         processOperands(btop);
 906                         conditionalBranch(btop, op.trueBranch(), op.falseBranch());
 907                     } else {
 908                         processOperands(op);
 909                         conditionalBranch(Opcode.IFEQ, op, op.trueBranch(), op.falseBranch());
 910                     }
 911                 }
 912                 case ExceptionRegionEnter op -> {
 913                     assignBlockArguments(op.start());
 914                 }
 915                 case ExceptionRegionExit op -> {
 916                     assignBlockArguments(op.end());
 917                     cob.goto_(getLabel(op.end()));
 918                 }
 919                 default ->
 920                     throw new UnsupportedOperationException("Terminating operation not supported: " + top);
 921             }
 922         }
 923     }
 924 
 925     private boolean inBlockArgs(Op.Result res) {
 926         // Check if used in successor
 927         for (Block.Reference s : res.declaringBlock().successors()) {
 928             if (s.arguments().contains(res)) {
 929                 return true;
 930             }
 931         }
 932         return false;
 933     }
 934 
 935     private void push(Op.Result res) {
 936         assert oprOnStack == null;
 937         if (res.type().equals(JavaType.VOID)) return;
 938         if (isNextUse(res)) {
 939             if (res.uses().size() > 1 || inBlockArgs(res)) {
 940                 switch (toTypeKind(res.type()).slotSize()) {
 941                     case 1 -> cob.dup();
 942                     case 2 -> cob.dup2();
 943                 }
 944                 storeIfUsed(res);
 945             }
 946             oprOnStack = res;
 947         } else {
 948             storeIfUsed(res);
 949             oprOnStack = null;
 950         }
 951     }
 952 
 953     // the rhs of any shift instruction must be int or smaller -> convert longs
 954     private void adjustRightTypeToInt(Op op) {
 955         TypeElement right = op.operands().getLast().type();
 956         if (right.equals(JavaType.LONG)) {
 957             cob.convertInstruction(toTypeKind(right), TypeKind.IntType);
 958         }
 959     }
 960 
 961     private static Op getConditionForCondBrOp(CoreOp.ConditionalBranchOp op) {
 962         Value p = op.predicate();
 963         if (p.uses().size() != 1) {
 964             return null;
 965         }
 966 
 967         if (p.declaringBlock() != op.parentBlock()) {
 968             return null;
 969         }
 970 
 971         // Check if used in successor
 972         for (Block.Reference s : op.successors()) {
 973             if (s.arguments().contains(p)) {
 974                 return null;
 975             }
 976         }
 977 
 978         if (p instanceof Op.Result or) {
 979             return or.op();
 980         } else {
 981             return null;
 982         }
 983     }
 984 
 985     private String funcIntfMethodName(Class<?> intfc) {
 986         String uniqueName = null;
 987         for (Method m : intfc.getMethods()) {
 988             // ensure it's SAM interface
 989             String methodName = m.getName();
 990             if (Modifier.isAbstract(m.getModifiers())
 991                     && (m.getReturnType() != String.class
 992                         || m.getParameterCount() != 0
 993                         || !methodName.equals("toString"))
 994                     && (m.getReturnType() != int.class
 995                         || m.getParameterCount() != 0
 996                         || !methodName.equals("hashCode"))
 997                     && (m.getReturnType() != boolean.class
 998                         || m.getParameterCount() != 1
 999                         || m.getParameterTypes()[0] != Object.class
1000                         || !methodName.equals("equals"))) {
1001                 if (uniqueName == null) {
1002                     uniqueName = methodName;
1003                 } else if (!uniqueName.equals(methodName)) {
1004                     // too many abstract methods
1005                     throw new IllegalArgumentException("Not a single-method interface: " + intfc.getName());
1006                 }
1007             }
1008         }
1009         if (uniqueName == null) {
1010             throw new IllegalArgumentException("No method in: " + intfc.getName());
1011         }
1012         return uniqueName;
1013     }
1014 
1015     private void conditionalBranch(BinaryTestOp op, Block.Reference trueBlock, Block.Reference falseBlock) {
1016         conditionalBranch(prepareReverseCondition(op), op, trueBlock, falseBlock);
1017     }
1018 
1019     private void conditionalBranch(Opcode reverseOpcode, Op op, Block.Reference trueBlock, Block.Reference falseBlock) {
1020         if (!needToAssignBlockArguments(falseBlock)) {
1021             cob.branchInstruction(reverseOpcode, getLabel(falseBlock));
1022         } else {
1023             cob.ifThen(reverseOpcode,
1024                 bb -> {
1025                     assignBlockArguments(falseBlock);
1026                     bb.goto_(getLabel(falseBlock));
1027                 });
1028         }
1029         assignBlockArguments(trueBlock);
1030         cob.goto_(getLabel(trueBlock));
1031     }
1032 
1033     private Opcode prepareReverseCondition(BinaryTestOp op) {
1034         return switch (toTypeKind(op.operands().get(0).type())) {
1035             case IntType ->
1036                 switch (op) {
1037                     case EqOp _ -> Opcode.IF_ICMPNE;
1038                     case NeqOp _ -> Opcode.IF_ICMPEQ;
1039                     case GtOp _ -> Opcode.IF_ICMPLE;
1040                     case GeOp _ -> Opcode.IF_ICMPLT;
1041                     case LtOp _ -> Opcode.IF_ICMPGE;
1042                     case LeOp _ -> Opcode.IF_ICMPGT;
1043                     default ->
1044                         throw new UnsupportedOperationException(op.opName() + " on int");
1045                 };
1046             case ReferenceType ->
1047                 switch (op) {
1048                     case EqOp _ -> Opcode.IF_ACMPNE;
1049                     case NeqOp _ -> Opcode.IF_ACMPEQ;
1050                     default ->
1051                         throw new UnsupportedOperationException(op.opName() + " on Object");
1052                 };
1053             case FloatType -> {
1054                 cob.fcmpg(); // FCMPL?
1055                 yield reverseIfOpcode(op);
1056             }
1057             case LongType -> {
1058                 cob.lcmp();
1059                 yield reverseIfOpcode(op);
1060             }
1061             case DoubleType -> {
1062                 cob.dcmpg(); //CMPL?
1063                 yield reverseIfOpcode(op);
1064             }
1065             default ->
1066                 throw new UnsupportedOperationException(op.opName() + " on " + op.operands().get(0).type());
1067         };
1068     }
1069 
1070     private static Opcode reverseIfOpcode(BinaryTestOp op) {
1071         return switch (op) {
1072             case EqOp _ -> Opcode.IFNE;
1073             case NeqOp _ -> Opcode.IFEQ;
1074             case GtOp _ -> Opcode.IFLE;
1075             case GeOp _ -> Opcode.IFLT;
1076             case LtOp _ -> Opcode.IFGE;
1077             case LeOp _ -> Opcode.IFGT;
1078             default ->
1079                 throw new UnsupportedOperationException(op.opName());
1080         };
1081     }
1082 
1083     private boolean needToAssignBlockArguments(Block.Reference ref) {
1084         List<Value> sargs = ref.arguments();
1085         List<Block.Parameter> bargs = ref.targetBlock().parameters();
1086         boolean need = false;
1087         for (int i = 0; i < bargs.size(); i++) {
1088             Block.Parameter barg = bargs.get(i);
1089             if (!barg.uses().isEmpty() && !barg.equals(sargs.get(i))) {
1090                 need = true;
1091                 allocateSlot(barg);
1092             }
1093         }
1094         return need;
1095     }
1096 
1097     private void assignBlockArguments(Block.Reference ref) {
1098         List<Value> sargs = ref.arguments();
1099         List<Block.Parameter> bargs = ref.targetBlock().parameters();
1100         // First push successor arguments on the stack, then pop and assign
1101         // so as not to overwrite slots that are reused slots at different argument positions
1102         for (int i = 0; i < bargs.size(); i++) {
1103             Block.Parameter barg = bargs.get(i);
1104             Value value = sargs.get(i);
1105             if (!barg.uses().isEmpty() && !barg.equals(value)) {
1106                 if (oprOnStack == value) {
1107                     oprOnStack = null;
1108                 } else {
1109                     load(value);
1110                 }
1111                 storeIfUsed(barg);
1112             }
1113         }
1114     }
1115 
1116     static DirectMethodHandleDesc resolveToMethodHandleDesc(MethodHandles.Lookup l,
1117                                                             MethodRef d) throws ReflectiveOperationException {
1118         MethodHandle mh = d.resolveToHandle(l);
1119 
1120         if (mh.describeConstable().isEmpty()) {
1121             throw new NoSuchMethodException();
1122         }
1123 
1124         MethodHandleDesc mhd = mh.describeConstable().get();
1125         if (!(mhd instanceof DirectMethodHandleDesc dmhd)) {
1126             throw new NoSuchMethodException();
1127         }
1128 
1129         return dmhd;
1130     }
1131 
1132     static CoreOp.FuncOp quote(CoreOp.LambdaOp lop) {
1133         List<Value> captures = lop.capturedValues();
1134 
1135         // Build the function type
1136         List<TypeElement> params = captures.stream()
1137                 .map(v -> v.type() instanceof VarType vt ? vt.valueType() : v.type())
1138                 .toList();
1139         FunctionType ft = FunctionType.functionType(CoreOp.QuotedOp.QUOTED_TYPE, params);
1140 
1141         // Build the function that quotes the lambda
1142         return CoreOp.func("q", ft).body(b -> {
1143             // Create variables as needed and obtain the captured values
1144             // for the copied lambda
1145             List<Value> outputCaptures = new ArrayList<>();
1146             for (int i = 0; i < captures.size(); i++) {
1147                 Value c = captures.get(i);
1148                 Block.Parameter p = b.parameters().get(i);
1149                 if (c.type() instanceof VarType _) {
1150                     Value var = b.op(CoreOp.var(String.valueOf(i), p));
1151                     outputCaptures.add(var);
1152                 } else {
1153                     outputCaptures.add(p);
1154                 }
1155             }
1156 
1157             // Quoted the lambda expression
1158             Value q = b.op(CoreOp.quoted(b.parentBody(), qb -> {
1159                 // Map the lambda's parent block to the quoted block
1160                 // We are copying lop in the context of the quoted block
1161                 qb.context().mapBlock(lop.parentBlock(), qb);
1162                 // Map the lambda's captured values
1163                 qb.context().mapValues(captures, outputCaptures);
1164                 // Return the lambda to be copied in the quoted operation
1165                 return lop;
1166             }));
1167             b.op(CoreOp._return(q));
1168         });
1169     }
1170 }