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