1 /* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 // This transformation pass transforms region bases control flow operations in
17 // the TensorFlow dialect to their functional counterparts, i.e.,
18 // tf.IfRegion -> tf.If and tf.WhileRegion -> tf.While
19
20 #include "llvm/ADT/STLExtras.h"
21 #include "llvm/ADT/SmallVector.h"
22 #include "llvm/Support/Casting.h"
23 #include "mlir/Dialect/StandardOps/IR/Ops.h" // from @llvm-project
24 #include "mlir/IR/Attributes.h" // from @llvm-project
25 #include "mlir/IR/Builders.h" // from @llvm-project
26 #include "mlir/IR/BuiltinOps.h" // from @llvm-project
27 #include "mlir/IR/Operation.h" // from @llvm-project
28 #include "mlir/IR/TypeUtilities.h" // from @llvm-project
29 #include "mlir/IR/Value.h" // from @llvm-project
30 #include "mlir/IR/Verifier.h" // from @llvm-project
31 #include "mlir/IR/Visitors.h" // from @llvm-project
32 #include "mlir/Pass/Pass.h" // from @llvm-project
33 #include "mlir/Pass/PassRegistry.h" // from @llvm-project
34 #include "mlir/Transforms/RegionUtils.h" // from @llvm-project
35 #include "tensorflow/compiler/mlir/op_or_arg_name_mapper.h"
36 #include "tensorflow/compiler/mlir/tensorflow/ir/tf_ops.h"
37 #include "tensorflow/compiler/mlir/tensorflow/ir/tf_types.h"
38 #include "tensorflow/compiler/mlir/tensorflow/transforms/passes.h"
39 #include "tensorflow/compiler/mlir/tensorflow/transforms/passes_detail.h"
40 #include "tensorflow/compiler/mlir/tensorflow/utils/attribute_utils.h"
41
42 #define DEBUG_TYPE "tf-region-cf-to-functional"
43
44 namespace mlir {
45 namespace TF {
46
47 namespace {
48
49 constexpr char kElseFuncNameAttr[] = "_else_func_name";
50 constexpr char kThenFuncNameAttr[] = "_then_func_name";
51
52 struct RegionControlFlowToFunctional
53 : public TF::RegionControlFlowToFunctionalPassBase<
54 RegionControlFlowToFunctional> {
55 void runOnOperation() override;
56
57 private:
58 LogicalResult ConvertIfOp(IfRegionOp if_region);
59 LogicalResult ConvertWhileOp(WhileRegionOp while_region);
60
61 // Get unique name by using the loc to name mapping.
62 std::string GetName(Operation* op, StringRef suffix);
63
64 tensorflow::OpOrArgLocNameMapper mapper;
65 llvm::SmallVector<FuncOp, 4> worklist;
66 };
67
GetName(Operation * op,StringRef suffix)68 std::string RegionControlFlowToFunctional::GetName(Operation* op,
69 StringRef suffix) {
70 return (mapper.GetUniqueName(op) + suffix).str();
71 }
72
73 // Returns all the external values referenced from the given regions. If the
74 // external value is a constant, sink it into the region instead (and do not
75 // add it to the returned vector).
CollectExternValues(Region & first,Region & second)76 llvm::SmallVector<Value, 4> CollectExternValues(Region& first, Region& second) {
77 llvm::SetVector<Value> extern_values;
78
79 for (Region* region : {&first, &second}) {
80 llvm::SetVector<Value> region_extern_values;
81 getUsedValuesDefinedAbove(*region, region_extern_values);
82
83 // Sink down constants into the functions.
84 for (auto extern_value : region_extern_values) {
85 if (!matchPattern(extern_value, m_Constant())) {
86 extern_values.insert(extern_value);
87 continue;
88 }
89 // Add constant at start of region.
90 auto const_builder = OpBuilder::atBlockBegin(®ion->front());
91 auto const_value = const_builder.clone(*extern_value.getDefiningOp());
92 replaceAllUsesInRegionWith(extern_value, const_value->getResult(0),
93 *region);
94 }
95 }
96
97 return llvm::to_vector<4>(extern_values);
98 }
99
100 // Extracts the contents of a region with a single block into a new function.
101 // `extern_values` is the set of external values that the region refers to.
102 //
103 // Inputs to the terminator of the region are converted to return values of
104 // the function. If `extern_values_passthrough` is true, all the extern values
105 // are also added as return values from the function
ExtractSingleBlockRegion(Region & region,StringRef name,llvm::SmallVectorImpl<Value> & extern_values,llvm::SmallVectorImpl<FuncOp> & worklist,bool extern_values_passthrough)106 void ExtractSingleBlockRegion(Region& region, StringRef name,
107 llvm::SmallVectorImpl<Value>& extern_values,
108 llvm::SmallVectorImpl<FuncOp>& worklist,
109 bool extern_values_passthrough) {
110 ModuleOp module = region.getParentOfType<ModuleOp>();
111 auto builder = OpBuilder::atBlockBegin(module.getBody());
112 auto loc = region.getParentOp()->getLoc();
113 Block& entry = region.front();
114 int num_region_arguments = entry.getNumArguments();
115 Operation* terminator = entry.getTerminator();
116
117 // Build the function type. Region arguments and extern values together
118 // become the function arguments, with region arguments going first.
119 auto input_types = llvm::to_vector<4>(entry.getArgumentTypes());
120 for (auto input : extern_values) input_types.push_back(input.getType());
121
122 // Terminator operands and pass through extern values (if enabled) together
123 // become the function return values.
124 auto return_types = llvm::to_vector<4>(terminator->getOperandTypes());
125 if (extern_values_passthrough)
126 for (auto input : extern_values) return_types.push_back(input.getType());
127
128 auto type = FunctionType::get(region.getContext(), input_types, return_types);
129
130 // Create new function and extract region body into the function.
131 auto outlined_func = builder.create<FuncOp>(loc, name, type);
132 Region& func_region = outlined_func.getBody();
133 func_region.takeBody(region);
134 Block& first_block = func_region.front();
135
136 // Replace all external uses with function arguments.
137 for (auto it : llvm::enumerate(extern_values)) {
138 Value arg = first_block.addArgument(it.value().getType());
139 replaceAllUsesInRegionWith(it.value(), arg, func_region);
140 }
141
142 // Function return values are all the terminator operands + pass through
143 // extern values (if enabled).
144 auto return_values = llvm::to_vector<4>(terminator->getOperands());
145 if (extern_values_passthrough)
146 return_values.insert(return_values.end(),
147 first_block.args_begin() + num_region_arguments,
148 first_block.args_end());
149
150 // Replace the existing terminator with a return.
151 terminator = first_block.getTerminator();
152 builder.setInsertionPoint(terminator);
153 builder.create<ReturnOp>(terminator->getLoc(), return_values);
154 terminator->erase();
155
156 outlined_func.setPrivate();
157
158 // Add the outlined function to the worklist in case its body has
159 // IfRegion or WhileRegion ops that need to converted.
160 worklist.push_back(outlined_func);
161 }
162
163 // Returns call for region with single call whose result feeds into the
164 // terminator of the region. if `allow_to_bool` is true, also allows a single
165 // ToBoolOp between the region yield and the call. Returns none if the region
166 // does not conform to this pattern.
IsSingleCallRegion(Region & region,bool allow_to_bool=false)167 llvm::Optional<CallOp> IsSingleCallRegion(Region& region,
168 bool allow_to_bool = false) {
169 if (!llvm::hasSingleElement(region)) return llvm::None;
170
171 Block& block = region.front();
172 auto it = block.rbegin();
173 YieldOp yield = dyn_cast<YieldOp>(*it++);
174
175 if (it == block.rend()) return llvm::None;
176
177 // Operation which is expected to consume all the call results.
178 Operation* call_consumer = yield;
179
180 // Allow a single ToBoolOp between the call and the yield (valid only
181 // when the yield has a single operand)
182 if (allow_to_bool && yield.getNumOperands() == 1 && isa<ToBoolOp>(*it)) {
183 if (it->getResult(0) != yield.getOperand(0)) return llvm::None;
184 call_consumer = cast<ToBoolOp>(*it);
185 it++;
186 }
187
188 // Check if there is a Call before the Yield.
189 CallOp call = dyn_cast<CallOp>(*it++);
190 if (!call) return llvm::None;
191
192 // All call results should feed into expected consumer
193 // All results of the call should feed into the yield.
194 if (call.getNumResults() != call_consumer->getNumOperands())
195 return llvm::None;
196
197 for (auto res_it : llvm::zip(call.getResults(), call_consumer->getOperands()))
198 if (std::get<0>(res_it) != std::get<1>(res_it)) return llvm::None;
199
200 // There can only be non-truncating cast op's prior to the call.
201 for (; it != block.rend(); ++it) {
202 CastOp cast = dyn_cast<CastOp>(*it);
203 if (!cast || cast.Truncate()) return llvm::None;
204 }
205
206 return call;
207 }
208
209 using ArgMatcherFn = function_ref<bool(Value, Region&, Value, Region&)>;
210
211 // Returns whether the arguments of the given 2 calls are match (after looking
212 // through cast ops). `matcher` is the predicate used to check if two arguments
213 // match.
MatchCallArgs(CallOp first,CallOp second,ArgMatcherFn matcher)214 bool MatchCallArgs(CallOp first, CallOp second, ArgMatcherFn matcher) {
215 if (first.getNumOperands() != second.getNumOperands()) return false;
216
217 Region& first_region = *first->getParentRegion();
218 Region& second_region = *second->getParentRegion();
219
220 for (auto it : llvm::zip(first.getArgOperands(), second.getArgOperands())) {
221 // Get the defining Op, skipping over casts.
222 auto get_defining_op = [](Value value) {
223 while (auto cast_op =
224 llvm::dyn_cast_or_null<CastOp>(value.getDefiningOp())) {
225 // Consider cast compatibility in case
226 // %cast = "tf.Cast"(%0) : (tensor<2xi64>) -> tensor<2xf32>
227 // is skipped.
228 if (cast_op.SrcT() != cast_op.DstT()) {
229 break;
230 }
231 value = cast_op.getOperand();
232 }
233 return value;
234 };
235 Value first_arg = get_defining_op(std::get<0>(it));
236 Value second_arg = get_defining_op(std::get<1>(it));
237
238 if (!matcher(first_arg, first_region, second_arg, second_region))
239 return false;
240 }
241 return true;
242 }
243
244 // Summary information for trivially transforming region based op's to
245 // functional ops. A trivial transformation can be done when the regions are
246 // just calls to functions, in which case no outlining is needed.
247 struct TrivialTransformInfo {
248 // Can the op be transformed trivially?
249 bool can_transform = false;
250
251 // List of callee names (one for each region).
252 llvm::SmallVector<StringRef, 2> callee_names;
253
254 // Analyzes the given calls (from regions attached to the same parent op) to
255 // check if the parent op be transformed to functional form trivially (i.e.,
256 // reusing existing functions and without outlining). This is possible when
257 // all the regions are single call regions (checked using matchers outside
258 // this class) and the all the calls match using the given argument matcher.
259 //
260 // If such a trivial transformation is possible, stash the relevant
261 // information needed for the transformation, else indicate that a trivial
262 // transformation is not possible by setting `can_transform` to false.
TrivialTransformInfomlir::TF::__anon5c806bec0111::TrivialTransformInfo263 TrivialTransformInfo(llvm::Optional<CallOp> first_call,
264 llvm::Optional<CallOp> second_call,
265 ArgMatcherFn arg_matcher) {
266 if (!first_call || !second_call) return;
267
268 if (!MatchCallArgs(first_call.getValue(), second_call.getValue(),
269 arg_matcher))
270 return;
271
272 can_transform = true;
273 callee_names = {first_call.getValue().getCallee(),
274 second_call.getValue().getCallee()};
275 }
276 };
277
278 // Transform IfRegionOp to IfOp.
ConvertIfOp(IfRegionOp if_region)279 LogicalResult RegionControlFlowToFunctional::ConvertIfOp(IfRegionOp if_region) {
280 llvm::SmallVector<Value, 4> extern_values;
281
282 // For IfOp, arguments of calls in the then and else regions match if they
283 // are the same value.
284 auto if_arg_matcher = [&](Value first, Region&, Value second, Region&) {
285 if (first != second) return false;
286
287 // collect the call arguments post lookup through cast Op's
288 extern_values.push_back(first);
289 return true;
290 };
291
292 const TrivialTransformInfo tti(IsSingleCallRegion(if_region.then_branch()),
293 IsSingleCallRegion(if_region.else_branch()),
294 if_arg_matcher);
295
296 std::string then_name, else_name;
297
298 if (tti.can_transform) {
299 // We can transform to functional form trivially without outlining.
300 then_name = tti.callee_names[0].str();
301 else_name = tti.callee_names[1].str();
302 } else {
303 // Collect external values that are used within the else and then bodies.
304 extern_values =
305 CollectExternValues(if_region.then_branch(), if_region.else_branch());
306
307 // These external values need to be added as inputs to the generated If. The
308 // order is determined by the order of these values the `extern_vales`.
309
310 // Create 2 new functions with the input signature matching this order,
311 // and outline the `then` and `else` regions by moving the bodies of these
312 // regions into these functions. Replace tf.yield with a regular return.
313 if (if_region->hasAttrOfType<StringAttr>(kThenFuncNameAttr) &&
314 !if_region._then_func_nameAttr().getValue().empty()) {
315 then_name =
316 mapper.GetUniqueName(if_region._then_func_nameAttr().getValue())
317 .str();
318 } else {
319 then_name = GetName(if_region, "_then");
320 }
321 ExtractSingleBlockRegion(if_region.then_branch(), then_name, extern_values,
322 worklist, /*extern_values_passthrough=*/false);
323
324 if (if_region->hasAttrOfType<StringAttr>(kElseFuncNameAttr) &&
325 !if_region._else_func_nameAttr().getValue().empty()) {
326 else_name =
327 mapper.GetUniqueName(if_region._else_func_nameAttr().getValue())
328 .str();
329 } else {
330 else_name = GetName(if_region, "_else");
331 }
332 ExtractSingleBlockRegion(if_region.else_branch(), else_name, extern_values,
333 worklist, /*extern_values_passthrough=*/false);
334 }
335
336 // Look through ToBool operations for the condition.
337 Value cond = if_region.cond();
338 auto to_bool = dyn_cast_or_null<ToBoolOp>(cond.getDefiningOp());
339 if (to_bool) cond = to_bool.getOperand();
340
341 // Once we have the `then` and `else` functions ready (either outlined or
342 // existing ones), replace the region based op with a functional control flow
343 // op.
344 OpBuilder builder(if_region);
345 auto if_op = builder.create<IfOp>(
346 if_region.getLoc(), if_region.getResultTypes(), cond, extern_values,
347 then_name, else_name, if_region.is_stateless());
348 CopyDeviceAndUnderscoredAttributes(if_region, if_op);
349 if_region.replaceAllUsesWith(if_op.getResults());
350 if_region.erase();
351
352 if (to_bool && to_bool.use_empty()) to_bool.erase();
353 return success();
354 }
355
356 // Transform WhileRegion to WhileOp.
ConvertWhileOp(WhileRegionOp while_region)357 LogicalResult RegionControlFlowToFunctional::ConvertWhileOp(
358 WhileRegionOp while_region) {
359 // For While, the arguments of the calls in the body and cond regions match
360 // if they are region arguments with the same region argument numbers. If the
361 // 2 calls have the same value (an extern value) used as an argument, we
362 // cannot do a trivial transformation because post transform, we will need to
363 // pass this extern value as an argument to the function, so we cannot use the
364 // existing function as is.
365 auto while_arg_matcher = [](Value first, Region& first_region, Value second,
366 Region& second_region) {
367 if (!first.isa<BlockArgument>() || !second.isa<BlockArgument>())
368 return false;
369 BlockArgument first_block_arg = first.cast<BlockArgument>();
370 BlockArgument second_block_arg = second.cast<BlockArgument>();
371
372 // 2 block arguments will match if they are the same argument number, and
373 // are block arguments of the corresponding containing regions.
374 return first_block_arg.getArgNumber() == second_block_arg.getArgNumber() &&
375 first_block_arg.getParentBlock() == &first_region.front() &&
376 second_block_arg.getParentBlock() == &second_region.front();
377 };
378
379 const TrivialTransformInfo tti(
380 IsSingleCallRegion(while_region.cond(), /*allow_to_bool=*/true),
381 IsSingleCallRegion(while_region.body()), while_arg_matcher);
382
383 // All existing inputs to while region are inputs to the functional while.
384 auto new_inputs = llvm::to_vector<4>(while_region.getOperands());
385
386 // All existing results will also be generated by the functional while.
387 auto new_result_types = llvm::to_vector<4>(while_region.getResultTypes());
388
389 std::string cond_name, body_name;
390 if (tti.can_transform) {
391 // We can transform to functional form trivially without outlining.
392 cond_name = tti.callee_names[0].str();
393 body_name = tti.callee_names[1].str();
394 } else {
395 // The WhileRegion regions can refer to either arguments of the region, or
396 // external values implicitly captured by the region. When converting to
397 // functional form, all such external values need to become function
398 // arguments of the outlined functions, and become pass through values in
399 // the outlined body function. So when outlining the while body, in addition
400 // to the region arguments, all these external references need to be added
401 // as function arguments.
402 llvm::SmallVector<Value, 4> extern_values =
403 CollectExternValues(while_region.cond(), while_region.body());
404
405 // Outline the `cond` and `body` regions by moving the bodies of these
406 // regions into new functions. Replace tf.yield with a regular return.
407 cond_name = GetName(while_region, "_cond");
408 ExtractSingleBlockRegion(while_region.cond(), cond_name, extern_values,
409 worklist, /*extern_values_passthrough=*/false);
410
411 body_name = GetName(while_region, "_body");
412 ExtractSingleBlockRegion(while_region.body(), body_name, extern_values,
413 worklist, /*extern_values_passthrough=*/true);
414
415 // All extern values become additional inputs and additional output types
416 // for the functional while.
417 new_inputs.append(extern_values.begin(), extern_values.end());
418 for (auto ext : extern_values) new_result_types.push_back(ext.getType());
419 }
420
421 // Once we have the `cond` and `body` functions ready (either outlined or
422 // existing ones), replace the region based op with a functional op.
423 OpBuilder builder(while_region);
424 auto while_op = builder.create<WhileOp>(
425 while_region.getLoc(), new_result_types, new_inputs, cond_name, body_name,
426 while_region.parallel_iterations(), while_region.is_stateless(),
427 while_region.shape_invariant());
428 CopyDeviceAndUnderscoredAttributes(while_region, while_op);
429
430 // Redirect old results to new results.
431 for (auto it : llvm::zip(
432 while_region.getResults(),
433 while_op.getResults().take_front(while_region.getNumResults())))
434 std::get<0>(it).replaceAllUsesWith(std::get<1>(it));
435
436 while_region.erase();
437 return success();
438 }
439
runOnOperation()440 void RegionControlFlowToFunctional::runOnOperation() {
441 ModuleOp module = getOperation();
442
443 // Seed worklist with all functions in the module.
444 worklist = llvm::to_vector<4>(module.getOps<FuncOp>());
445 while (!worklist.empty()) {
446 FuncOp function = worklist.pop_back_val();
447
448 auto result = function.walk([&](Operation* op) {
449 if (auto if_region = llvm::dyn_cast<IfRegionOp>(op)) {
450 if (failed(ConvertIfOp(if_region))) {
451 op->emitOpError() << "failed to convert to functional form";
452 return WalkResult::interrupt();
453 }
454 } else if (auto while_region = llvm::dyn_cast<WhileRegionOp>(op)) {
455 if (failed(ConvertWhileOp(while_region))) {
456 op->emitOpError() << "failed to convert to functional form";
457 return WalkResult::interrupt();
458 }
459 }
460 return WalkResult::advance();
461 });
462
463 if (result.wasInterrupted()) return signalPassFailure();
464 }
465 }
466
467 } // namespace
468
469 std::unique_ptr<OperationPass<ModuleOp>>
CreateTFRegionControlFlowToFunctional()470 CreateTFRegionControlFlowToFunctional() {
471 return std::make_unique<RegionControlFlowToFunctional>();
472 }
473
474 } // namespace TF
475 } // namespace mlir
476