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