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