1 | //===- InliningUtils.h - Inliner utilities ----------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This header file defines interfaces for various inlining utility methods. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #ifndef MLIR_TRANSFORMS_INLININGUTILS_H |
14 | #define MLIR_TRANSFORMS_INLININGUTILS_H |
15 | |
16 | #include "mlir/IR/BuiltinAttributes.h" |
17 | #include "mlir/IR/DialectInterface.h" |
18 | #include "mlir/IR/Location.h" |
19 | #include "mlir/IR/Region.h" |
20 | #include "mlir/IR/ValueRange.h" |
21 | #include <optional> |
22 | |
23 | namespace mlir { |
24 | |
25 | class Block; |
26 | class IRMapping; |
27 | class CallableOpInterface; |
28 | class CallOpInterface; |
29 | class OpBuilder; |
30 | class Operation; |
31 | class Region; |
32 | class TypeRange; |
33 | class Value; |
34 | class ValueRange; |
35 | |
36 | //===----------------------------------------------------------------------===// |
37 | // InlinerInterface |
38 | //===----------------------------------------------------------------------===// |
39 | |
40 | /// This is the interface that must be implemented by the dialects of operations |
41 | /// to be inlined. This interface should only handle the operations of the |
42 | /// given dialect. |
43 | class DialectInlinerInterface |
44 | : public DialectInterface::Base<DialectInlinerInterface> { |
45 | public: |
46 | DialectInlinerInterface(Dialect *dialect) : Base(dialect) {} |
47 | |
48 | //===--------------------------------------------------------------------===// |
49 | // Analysis Hooks |
50 | //===--------------------------------------------------------------------===// |
51 | |
52 | /// Returns true if the given operation 'callable', that implements the |
53 | /// 'CallableOpInterface', can be inlined into the position given call |
54 | /// operation 'call', that is registered to the current dialect and implements |
55 | /// the `CallOpInterface`. 'wouldBeCloned' is set to true if the region of the |
56 | /// given 'callable' is set to be cloned during the inlining process, or false |
57 | /// if the region is set to be moved in-place(i.e. no duplicates would be |
58 | /// created). |
59 | virtual bool isLegalToInline(Operation *call, Operation *callable, |
60 | bool wouldBeCloned) const { |
61 | return false; |
62 | } |
63 | |
64 | /// Returns true if the given region 'src' can be inlined into the region |
65 | /// 'dest' that is attached to an operation registered to the current dialect. |
66 | /// 'wouldBeCloned' is set to true if the given 'src' region is set to be |
67 | /// cloned during the inlining process, or false if the region is set to be |
68 | /// moved in-place(i.e. no duplicates would be created). 'valueMapping' |
69 | /// contains any remapped values from within the 'src' region. This can be |
70 | /// used to examine what values will replace entry arguments into the 'src' |
71 | /// region for example. |
72 | virtual bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned, |
73 | IRMapping &valueMapping) const { |
74 | return false; |
75 | } |
76 | |
77 | /// Returns true if the given operation 'op', that is registered to this |
78 | /// dialect, can be inlined into the given region, false otherwise. |
79 | /// 'wouldBeCloned' is set to true if the given 'op' is set to be cloned |
80 | /// during the inlining process, or false if the operation is set to be moved |
81 | /// in-place(i.e. no duplicates would be created). 'valueMapping' contains any |
82 | /// remapped values from within the 'src' region. This can be used to examine |
83 | /// what values may potentially replace the operands to 'op'. |
84 | virtual bool isLegalToInline(Operation *op, Region *dest, bool wouldBeCloned, |
85 | IRMapping &valueMapping) const { |
86 | return false; |
87 | } |
88 | |
89 | /// This hook is invoked on an operation that contains regions. It should |
90 | /// return true if the analyzer should recurse within the regions of this |
91 | /// operation when computing legality and cost, false otherwise. The default |
92 | /// implementation returns true. |
93 | virtual bool shouldAnalyzeRecursively(Operation *op) const { return true; } |
94 | |
95 | //===--------------------------------------------------------------------===// |
96 | // Transformation Hooks |
97 | //===--------------------------------------------------------------------===// |
98 | |
99 | /// Handle the given inlined terminator by replacing it with a new operation |
100 | /// as necessary. This overload is called when the inlined region has more |
101 | /// than one block. The 'newDest' block represents the new final branching |
102 | /// destination of blocks within this region, i.e. operations that release |
103 | /// control to the parent operation will likely now branch to this block. |
104 | /// Its block arguments correspond to any values that need to be replaced by |
105 | /// terminators within the inlined region. |
106 | virtual void handleTerminator(Operation *op, Block *newDest) const { |
107 | llvm_unreachable("must implement handleTerminator in the case of multiple " |
108 | "inlined blocks" ); |
109 | } |
110 | |
111 | /// Handle the given inlined terminator by replacing it with a new operation |
112 | /// as necessary. This overload is called when the inlined region only |
113 | /// contains one block. 'valuesToReplace' contains the previously returned |
114 | /// values of the call site before inlining. These values must be replaced by |
115 | /// this callback if they had any users (for example for traditional function |
116 | /// calls, these are directly replaced with the operands of the `return` |
117 | /// operation). The given 'op' will be removed by the caller, after this |
118 | /// function has been called. |
119 | virtual void handleTerminator(Operation *op, |
120 | ValueRange valuesToReplace) const { |
121 | llvm_unreachable( |
122 | "must implement handleTerminator in the case of one inlined block" ); |
123 | } |
124 | |
125 | /// Attempt to materialize a conversion for a type mismatch between a call |
126 | /// from this dialect, and a callable region. This method should generate an |
127 | /// operation that takes 'input' as the only operand, and produces a single |
128 | /// result of 'resultType'. If a conversion can not be generated, nullptr |
129 | /// should be returned. For example, this hook may be invoked in the following |
130 | /// scenarios: |
131 | /// func @foo(i32) -> i32 { ... } |
132 | /// |
133 | /// // Mismatched input operand |
134 | /// ... = foo.call @foo(%input : i16) -> i32 |
135 | /// |
136 | /// // Mismatched result type. |
137 | /// ... = foo.call @foo(%input : i32) -> i16 |
138 | /// |
139 | /// NOTE: This hook may be invoked before the 'isLegal' checks above. |
140 | virtual Operation *materializeCallConversion(OpBuilder &builder, Value input, |
141 | Type resultType, |
142 | Location conversionLoc) const { |
143 | return nullptr; |
144 | } |
145 | |
146 | /// Hook to transform the call arguments before using them to replace the |
147 | /// callee arguments. Returns a value of the same type or the `argument` |
148 | /// itself if nothing changed. The `argumentAttrs` dictionary is non-null even |
149 | /// if no attribute is present. The hook is called after converting the |
150 | /// callsite argument types using the materializeCallConversion callback, and |
151 | /// right before inlining the callee region. Any operations created using the |
152 | /// provided `builder` are inserted right before the inlined callee region. An |
153 | /// example use case is the insertion of copies for by value arguments. |
154 | virtual Value handleArgument(OpBuilder &builder, Operation *call, |
155 | Operation *callable, Value argument, |
156 | DictionaryAttr argumentAttrs) const { |
157 | return argument; |
158 | } |
159 | |
160 | /// Hook to transform the callee results before using them to replace the call |
161 | /// results. Returns a value of the same type or the `result` itself if |
162 | /// nothing changed. The `resultAttrs` dictionary is non-null even if no |
163 | /// attribute is present. The hook is called right before handling |
164 | /// terminators, and obtains the callee result before converting its type |
165 | /// using the `materializeCallConversion` callback. Any operations created |
166 | /// using the provided `builder` are inserted right after the inlined callee |
167 | /// region. An example use case is the insertion of copies for by value |
168 | /// results. NOTE: This hook is invoked after inlining the `callable` region. |
169 | virtual Value handleResult(OpBuilder &builder, Operation *call, |
170 | Operation *callable, Value result, |
171 | DictionaryAttr resultAttrs) const { |
172 | return result; |
173 | } |
174 | |
175 | /// Process a set of blocks that have been inlined for a call. This callback |
176 | /// is invoked before inlined terminator operations have been processed. |
177 | virtual void processInlinedCallBlocks( |
178 | Operation *call, iterator_range<Region::iterator> inlinedBlocks) const {} |
179 | }; |
180 | |
181 | /// This interface provides the hooks into the inlining interface. |
182 | /// Note: this class automatically collects 'DialectInlinerInterface' objects |
183 | /// registered to each dialect within the given context. |
184 | class InlinerInterface |
185 | : public DialectInterfaceCollection<DialectInlinerInterface> { |
186 | public: |
187 | using Base::Base; |
188 | |
189 | /// Process a set of blocks that have been inlined. This callback is invoked |
190 | /// *before* inlined terminator operations have been processed. |
191 | virtual void |
192 | processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks) {} |
193 | |
194 | /// These hooks mirror the hooks for the DialectInlinerInterface, with default |
195 | /// implementations that call the hook on the handler for the dialect 'op' is |
196 | /// registered to. |
197 | |
198 | //===--------------------------------------------------------------------===// |
199 | // Analysis Hooks |
200 | //===--------------------------------------------------------------------===// |
201 | |
202 | virtual bool isLegalToInline(Operation *call, Operation *callable, |
203 | bool wouldBeCloned) const; |
204 | virtual bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned, |
205 | IRMapping &valueMapping) const; |
206 | virtual bool isLegalToInline(Operation *op, Region *dest, bool wouldBeCloned, |
207 | IRMapping &valueMapping) const; |
208 | virtual bool shouldAnalyzeRecursively(Operation *op) const; |
209 | |
210 | //===--------------------------------------------------------------------===// |
211 | // Transformation Hooks |
212 | //===--------------------------------------------------------------------===// |
213 | |
214 | virtual void handleTerminator(Operation *op, Block *newDest) const; |
215 | virtual void handleTerminator(Operation *op, ValueRange valuesToRepl) const; |
216 | |
217 | virtual Value handleArgument(OpBuilder &builder, Operation *call, |
218 | Operation *callable, Value argument, |
219 | DictionaryAttr argumentAttrs) const; |
220 | virtual Value handleResult(OpBuilder &builder, Operation *call, |
221 | Operation *callable, Value result, |
222 | DictionaryAttr resultAttrs) const; |
223 | |
224 | virtual void processInlinedCallBlocks( |
225 | Operation *call, iterator_range<Region::iterator> inlinedBlocks) const; |
226 | }; |
227 | |
228 | //===----------------------------------------------------------------------===// |
229 | // Inline Methods. |
230 | //===----------------------------------------------------------------------===// |
231 | |
232 | /// This function inlines a region, 'src', into another. This function returns |
233 | /// failure if it is not possible to inline this function. If the function |
234 | /// returned failure, then no changes to the module have been made. |
235 | /// |
236 | /// The provided 'inlinePoint' must be within a region, and corresponds to the |
237 | /// location where the 'src' region should be inlined. 'mapping' contains any |
238 | /// remapped operands that are used within the region, and *must* include |
239 | /// remappings for the entry arguments to the region. 'resultsToReplace' |
240 | /// corresponds to any results that should be replaced by terminators within the |
241 | /// inlined region. 'regionResultTypes' specifies the expected return types of |
242 | /// the terminators in the region. 'inlineLoc' is an optional Location that, if |
243 | /// provided, will be used to update the inlined operations' location |
244 | /// information. 'shouldCloneInlinedRegion' corresponds to whether the source |
245 | /// region should be cloned into the 'inlinePoint' or spliced directly. |
246 | LogicalResult inlineRegion(InlinerInterface &interface, Region *src, |
247 | Operation *inlinePoint, IRMapping &mapper, |
248 | ValueRange resultsToReplace, |
249 | TypeRange regionResultTypes, |
250 | std::optional<Location> inlineLoc = std::nullopt, |
251 | bool shouldCloneInlinedRegion = true); |
252 | LogicalResult inlineRegion(InlinerInterface &interface, Region *src, |
253 | Block *inlineBlock, Block::iterator inlinePoint, |
254 | IRMapping &mapper, ValueRange resultsToReplace, |
255 | TypeRange regionResultTypes, |
256 | std::optional<Location> inlineLoc = std::nullopt, |
257 | bool shouldCloneInlinedRegion = true); |
258 | |
259 | /// This function is an overload of the above 'inlineRegion' that allows for |
260 | /// providing the set of operands ('inlinedOperands') that should be used |
261 | /// in-favor of the region arguments when inlining. |
262 | LogicalResult inlineRegion(InlinerInterface &interface, Region *src, |
263 | Operation *inlinePoint, ValueRange inlinedOperands, |
264 | ValueRange resultsToReplace, |
265 | std::optional<Location> inlineLoc = std::nullopt, |
266 | bool shouldCloneInlinedRegion = true); |
267 | LogicalResult inlineRegion(InlinerInterface &interface, Region *src, |
268 | Block *inlineBlock, Block::iterator inlinePoint, |
269 | ValueRange inlinedOperands, |
270 | ValueRange resultsToReplace, |
271 | std::optional<Location> inlineLoc = std::nullopt, |
272 | bool shouldCloneInlinedRegion = true); |
273 | |
274 | /// This function inlines a given region, 'src', of a callable operation, |
275 | /// 'callable', into the location defined by the given call operation. This |
276 | /// function returns failure if inlining is not possible, success otherwise. On |
277 | /// failure, no changes are made to the module. 'shouldCloneInlinedRegion' |
278 | /// corresponds to whether the source region should be cloned into the 'call' or |
279 | /// spliced directly. |
280 | LogicalResult inlineCall(InlinerInterface &interface, CallOpInterface call, |
281 | CallableOpInterface callable, Region *src, |
282 | bool shouldCloneInlinedRegion = true); |
283 | |
284 | } // namespace mlir |
285 | |
286 | #endif // MLIR_TRANSFORMS_INLININGUTILS_H |
287 | |