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