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