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#body(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.body(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 }