1 package jdk.incubator.code.analysis; 2 3 import jdk.incubator.code.*; 4 import jdk.incubator.code.dialect.core.CoreOp; 5 6 import java.util.HashMap; 7 import java.util.List; 8 import java.util.Map; 9 import java.util.function.BiConsumer; 10 11 import static jdk.incubator.code.dialect.core.CoreOp.branch; 12 import static jdk.incubator.code.dialect.core.CoreOp.return_; 13 14 /** 15 * Functionality for inlining code. 16 */ 17 public final class Inliner { 18 19 private Inliner() {} 20 21 /** 22 * An inline consumer that inserts a return operation with a value, if non-null. 23 */ 24 public static final BiConsumer<Block.Builder, Value> INLINE_RETURN = (block, value) -> { 25 block.op(value != null ? return_(value) : CoreOp.return_()); 26 }; 27 28 /** 29 * Inlines the invokable operation into the given block builder and returns the block builder from which to 30 * continue building. 31 * <p> 32 * This method {@link Block.Builder#transformBody(Body, List, CopyContext, OpTransformer) transforms} the 33 * body of the invokable operation with the given arguments, a new context, and an operation transformer that 34 * replaces return operations by applying the given consumer to a block builder and a return value. 35 * <p> 36 * The operation transformer copies all operations except return operations whose nearest invokable operation 37 * ancestor is the given the invokable operation. When such a return operation is encountered, then on 38 * first encounter of its grandparent body a return block builder is computed and used for this return operation 39 * and encounters of subsequent return operations with the same grandparent body. 40 * <p> 41 * If the grandparent body has only one block then operation transformer's block builder is the return 42 * block builder. Otherwise, if the grandparent body has one or more blocks then the return block builder is 43 * created from the operation transformer's block builder. The created return block builder will have a block 44 * parameter whose type corresponds to the return type, or will have no parameter for void return. 45 * The computation finishes by applying the return block builder and a return value to the inlining consumer. 46 * If the grandparent body has only one block then the return value is the value mapped from the return 47 * operation's operand, or is null for void return. Otherwise, if the grandparent body has one or more blocks 48 * then the value is the block parameter of the created return block builder, or is null for void return. 49 * <p> 50 * For every encounter of a return operation the associated return block builder is compared against the 51 * operation transformer's block builder. If they are not equal then a branch operation is added to the 52 * operation transformer's block builder whose successor is the return block builder with a block argument 53 * that is the value mapped from the return operation's operand, or with no block argument for void return. 54 * @apiNote 55 * It is easier to inline an invokable op if its body is in lowered form (there are no operations in the blocks 56 * of the body that are lowerable). This ensures a single exit point can be created (paired with the single 57 * entry point). If there are one or more nested return operations, then there is unlikely to be a single exit. 58 * Transforming the model to create a single exit point while preserving nested structure is in general 59 * non-trivial and outside the scope of this method. In such cases the invokable operation can be transformed 60 * with a lowering transformation after which it can then be inlined. 61 * 62 * @param _this the block builder 63 * @param invokableOp the invokable operation 64 * @param args the arguments to map to the invokable operation's parameters 65 * @param inlineConsumer the consumer applied to process the return from the invokable operation. 66 * This is called once for each grandparent body of a return operation, with a block to 67 * build replacement operations and the return value, or null for void return. 68 * @return the block builder to continue building from 69 * @param <O> The invokable type 70 */ 71 public static <O extends Op & Op.Invokable> 72 Block.Builder inline(Block.Builder _this, O invokableOp, List<? extends Value> args, 73 BiConsumer<Block.Builder, Value> inlineConsumer) { 74 Map<Body, Block.Builder> returnBlocks = new HashMap<>(); 75 // Create new context, ensuring inlining is isolated 76 _this.transformBody(invokableOp.body(), args, CopyContext.create(), (block, op) -> { 77 // If the return operation is associated with the invokable operation 78 if (op instanceof CoreOp.ReturnOp rop && getNearestInvokeableAncestorOp(op) == invokableOp) { 79 // Compute the return block 80 Block.Builder returnBlock = returnBlocks.computeIfAbsent(rop.ancestorBody(), _body -> { 81 Block.Builder rb; 82 // If the body has one block we know there is just one return op declared, otherwise there may 83 // one or more. If so, create a new block that joins all the returns. 84 // Note: we could count all return op in a body to avoid creating a new block for a body 85 // with two or more blocks with only one returnOp is declared. 86 Value r; 87 if (rop.ancestorBody().blocks().size() != 1) { 88 List<TypeElement> param = rop.returnValue() != null 89 ? List.of(invokableOp.invokableType().returnType()) 90 : List.of(); 91 rb = block.block(param); 92 r = !param.isEmpty() 93 ? rb.parameters().get(0) 94 : null; 95 } else { 96 r = rop.returnValue() != null 97 ? block.context().getValue(rop.returnValue()) 98 : null; 99 rb = block; 100 } 101 102 // Inline the return 103 inlineConsumer.accept(rb, r); 104 105 return rb; 106 }); 107 108 // Replace the return op with a branch to the return block, if needed 109 if (!returnBlock.equals(block)) { 110 // Replace return op with branch to return block, with given return value 111 List<Value> arg = rop.returnValue() != null 112 ? List.of(block.context().getValue(rop.returnValue())) 113 : List.of(); 114 block.op(branch(returnBlock.successor(arg))); 115 } 116 117 return block; 118 } 119 120 block.op(op); 121 return block; 122 }); 123 124 125 Block.Builder builder = returnBlocks.get(invokableOp.body()); 126 return builder != null ? builder : _this; 127 } 128 129 private static Op getNearestInvokeableAncestorOp(Op op) { 130 do { 131 op = op.ancestorOp(); 132 } while (!(op instanceof Op.Invokable)); 133 return op; 134 } 135 }