• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2019 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 #include "tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.h"
17 
18 #include <cstdint>
19 #include <initializer_list>
20 
21 #include "llvm/ADT/DenseMap.h"
22 #include "llvm/ADT/DenseSet.h"
23 #include "llvm/ADT/Optional.h"
24 #include "llvm/ADT/STLExtras.h"
25 #include "llvm/ADT/SmallVector.h"
26 #include "llvm/ADT/iterator_range.h"
27 #include "llvm/Support/Casting.h"
28 #include "llvm/Support/Debug.h"
29 #include "llvm/Support/ErrorHandling.h"
30 #include "mlir/IR/Attributes.h"  // from @llvm-project
31 #include "mlir/IR/Block.h"  // from @llvm-project
32 #include "mlir/IR/Builders.h"  // from @llvm-project
33 #include "mlir/IR/BuiltinOps.h"  // from @llvm-project
34 #include "mlir/IR/BuiltinTypes.h"  // from @llvm-project
35 #include "mlir/IR/Location.h"  // from @llvm-project
36 #include "mlir/IR/Operation.h"  // from @llvm-project
37 #include "mlir/IR/OperationSupport.h"  // from @llvm-project
38 #include "mlir/IR/Value.h"  // from @llvm-project
39 #include "mlir/Interfaces/SideEffectInterfaces.h"  // from @llvm-project
40 #include "mlir/Support/LLVM.h"  // from @llvm-project
41 #include "mlir/Support/LogicalResult.h"  // from @llvm-project
42 #include "tensorflow/compiler/mlir/tensorflow/ir/tf_device.h"
43 #include "tensorflow/compiler/mlir/tensorflow/ir/tf_executor.h"
44 #include "tensorflow/compiler/mlir/tensorflow/ir/tf_ops.h"
45 #include "tensorflow/compiler/mlir/tensorflow/ir/tf_side_effects.h"
46 #include "tensorflow/compiler/mlir/tensorflow/ir/tf_types.h"
47 
48 namespace mlir {
49 namespace TF {
50 namespace {
51 
52 constexpr auto kUnknownResourceId =
53     ResourceAliasAnalysis::Info::kUnknownResourceId;
54 
55 //===----------------------------------------------------------------------===//
56 // SideEffectAnalysisInfo helper functions.
57 //===----------------------------------------------------------------------===//
58 
59 // Returns a set that contains only kUnknownResourceId.
UnknownResourceSet()60 llvm::SmallDenseSet<int64_t, 8> UnknownResourceSet() {
61   llvm::SmallDenseSet<int64_t, 8> unknown_set;
62   unknown_set.insert(kUnknownResourceId);
63   return unknown_set;
64 }
65 
66 // Returns all resources that could be accessed by op, or UnknownResourceSet()
67 // if we cannot find all of them.
FindAccessedResources(Operation * op,const ResourceAliasAnalysis::Info & alias_analysis)68 llvm::SmallDenseSet<int64_t, 8> FindAccessedResources(
69     Operation* op, const ResourceAliasAnalysis::Info& alias_analysis) {
70   llvm::SmallDenseSet<int64_t, 8> resources;
71 
72   for (auto operand : filter_resources(op->getOperands())) {
73     if (alias_analysis.IsUnknownResource(operand)) return UnknownResourceSet();
74     const auto& ids = alias_analysis.GetResourceUniqueIds(operand);
75     resources.insert(ids.begin(), ids.end());
76   }
77   for (auto result : filter_resources(op->getResults())) {
78     if (alias_analysis.IsUnknownResource(result)) return UnknownResourceSet();
79     const auto& ids = alias_analysis.GetResourceUniqueIds(result);
80     resources.insert(ids.begin(), ids.end());
81   }
82   return resources;
83 }
84 
85 // Helper struct defining what memory effects are present for a resource.
86 struct SideEffects {
87   bool alloc = false;
88   bool free = false;
89   bool read = false;
90   bool write = false;
91 
IsAllocOnlymlir::TF::__anondb22c1c60111::SideEffects92   bool IsAllocOnly() const { return alloc && !free && !read && !write; }
IsReadOnlymlir::TF::__anondb22c1c60111::SideEffects93   bool IsReadOnly() const { return !alloc && !free && read && !write; }
94 };
95 
96 using ResourceSideEffectsByValue = llvm::SmallDenseMap<Value, SideEffects>;
97 
MustExecute(const MemoryEffects::EffectInstance & effect)98 bool MustExecute(const MemoryEffects::EffectInstance& effect) {
99   if (llvm::isa<ResourceEffects::TPUEmbedding>(effect.getResource())) {
100     assert(!effect.getValue() && !effect.getParameters() &&
101            isa<MemoryEffects::Write>(effect.getEffect()));
102     return true;
103   }
104   return false;
105 }
106 
107 // Collects memory side effects for an operation by value (operands and
108 // results).
GetResourceInfoForOp(Operation * op,ResourceSideEffectsByValue & resource_info,bool & must_execute)109 void GetResourceInfoForOp(Operation* op,
110                           ResourceSideEffectsByValue& resource_info,
111                           bool& must_execute) {
112   auto interface = dyn_cast<MemoryEffectOpInterface>(op);
113   if (!interface) return;
114 
115   llvm::SmallVector<MemoryEffects::EffectInstance, 4> effects;
116   interface.getEffects(effects);
117 
118   for (auto& effect : effects) {
119     if (MustExecute(effect)) {
120       must_execute = true;
121       continue;
122     }
123     // TODO(lyandy): Support effects with no value defined.
124     if (!effect.getValue()) {
125       resource_info.clear();
126       must_execute = false;
127       return;
128     }
129     auto it = resource_info.try_emplace(effect.getValue());
130     auto& side_effect = it.first->getSecond();
131     auto* resource_effect = effect.getEffect();
132     if (isa<MemoryEffects::Allocate>(resource_effect)) {
133       side_effect.alloc = true;
134     } else if (isa<MemoryEffects::Free>(resource_effect)) {
135       side_effect.free = true;
136     } else if (isa<MemoryEffects::Read>(resource_effect)) {
137       side_effect.read = true;
138     } else if (isa<MemoryEffects::Write>(resource_effect)) {
139       side_effect.write = true;
140     } else {
141       resource_info.clear();
142       must_execute = false;
143       return;
144     }
145   }
146 }
147 
148 // Checks if a value is a result of `op`.
IsOperationResult(Operation * op,Value value)149 bool IsOperationResult(Operation* op, Value value) {
150   return value.getDefiningOp() == op;
151 }
152 
153 // Checks if an operation's resource operands are read only. Operation results
154 // are ignored.
IsResourceOpReadOnly(Operation * op,const ResourceSideEffectsByValue & resource_op_info)155 bool IsResourceOpReadOnly(Operation* op,
156                           const ResourceSideEffectsByValue& resource_op_info) {
157   if (resource_op_info.empty()) return false;
158 
159   for (const auto& resource_info : resource_op_info) {
160     Value value = resource_info.getFirst();
161     if (IsOperationResult(op, value)) continue;
162     const SideEffects& side_effects = resource_info.getSecond();
163     if (!side_effects.IsReadOnly()) return false;
164   }
165 
166   return true;
167 }
168 
169 // Checks if an operation's resource results are alloc only and no side effects
170 // are present for its operands.
IsResourceOpAllocOnly(Operation * op,const ResourceSideEffectsByValue & resource_op_info)171 bool IsResourceOpAllocOnly(Operation* op,
172                            const ResourceSideEffectsByValue& resource_op_info) {
173   if (resource_op_info.empty()) return false;
174 
175   for (const auto& resource_info : resource_op_info) {
176     // Operand with side effect.
177     Value value = resource_info.getFirst();
178     if (!IsOperationResult(op, value)) return false;
179     const SideEffects& side_effects = resource_info.getSecond();
180     if (!side_effects.IsAllocOnly()) return false;
181   }
182 
183   return true;
184 }
185 
186 // Returns if `op` is a resource declaration.
OpIsDeclaration(Operation * op,const ResourceAliasAnalysis::Info & alias_analysis)187 bool OpIsDeclaration(Operation* op,
188                      const ResourceAliasAnalysis::Info& alias_analysis) {
189   return llvm::isa<TF::IdentityNOp, TF::IdentityOp>(op) &&
190          !FindAccessedResources(op, alias_analysis).empty();
191 }
192 
193 // A vector of resource variable id's with their associated resource value.
194 using ResourceIdsByValue =
195     llvm::SmallVector<std::pair<Value, const llvm::SmallSet<int64_t, 8>*>, 4>;
196 
197 // Collects resource id's by resource value. If operation resource side effects
198 // are unknown or a resource is unknown, an empty optional is returned.
GetResourceIdsByValue(Operation * op,const ResourceAliasAnalysis::Info & alias_analysis,const ResourceSideEffectsByValue & resource_op_info)199 llvm::Optional<ResourceIdsByValue> GetResourceIdsByValue(
200     Operation* op, const ResourceAliasAnalysis::Info& alias_analysis,
201     const ResourceSideEffectsByValue& resource_op_info) {
202   ResourceIdsByValue resource_ids_by_value;
203   if (resource_op_info.empty()) return llvm::None;
204 
205   auto collect_ids = [&](ValueRange values) {
206     for (auto value : filter_resources(values)) {
207       if (alias_analysis.IsUnknownResource(value)) return false;
208       const auto& ids = alias_analysis.GetResourceUniqueIds(value);
209       resource_ids_by_value.push_back({value, &ids});
210     }
211     return true;
212   };
213 
214   if (collect_ids(op->getOperands()) && collect_ids(op->getResults()))
215     return resource_ids_by_value;
216   else
217     return llvm::None;
218 }
219 
220 // Returns true if `op` is known to not have any side effect.
OpIsKnownToHaveNoSideEffect(Operation * op)221 bool OpIsKnownToHaveNoSideEffect(Operation* op) {
222   // Note: Identity op is really side-effect free, but it is not marked as such
223   // in the TF dialect (see comments in definition of Identity op in tf_ops.td)
224   // However, for adding control dependencies, its safe to assume
225   // that the Identity op is side-effect free.
226   if (isa<IdentityOp>(op)) return true;
227 
228   // For op's in the Tensorflow dialect, query the dialect.
229   if (op->getName().getDialect() ==
230       TF::TensorFlowDialect::getDialectNamespace())
231     return !TensorFlowDialect::CanHaveSideEffects(op);
232 
233   // Otherwise, conservatively assume that there can be side effects.
234   return false;
235 }
236 
237 }  // namespace
238 
239 namespace detail {
240 //===----------------------------------------------------------------------===//
241 // SideEffectAnalysisInfo
242 //===----------------------------------------------------------------------===//
243 
TrackAccess(int64_t resource_id,Operation * op,bool read_only)244 void SideEffectAnalysisInfo::TrackAccess(int64_t resource_id, Operation* op,
245                                          bool read_only) {
246   if (resource_id == kUnknownResourceId) {
247     if (read_only) {
248       // New unknown read is not tracked by any known resource access.
249       for (auto& entry : per_resource_access_info_) {
250         entry.getSecond().tracked_last_unknown_read = false;
251       }
252     } else {
253       // Unknown write can clear all other tracked information, since it acts
254       // like a barrier.
255       per_resource_access_info_.clear();
256     }
257   }
258   auto& info = per_resource_access_info_[resource_id];
259   if (read_only) {
260     info.reads_since_last_write.push_back(op);
261     // Resource read must have carried control dependencies of unknown write. It
262     // can only avoid adding control edges (from uknown accesses) for a later
263     // write, but not for a later read, because this read can be reordered with
264     // a later read.
265     info.tracked_last_unknown_write_for_write = true;
266   } else {
267     // Resource write must have carried control dependencies of unknown access.
268     info.tracked_last_unknown_write_for_read = true;
269     info.tracked_last_unknown_write_for_write = true;
270     info.tracked_last_unknown_read = true;
271     info.last_write = op;
272     info.reads_since_last_write.clear();
273   }
274 }
275 
AddPredecessorsForAccess(int64_t resource_id,Operation * op,bool read_only)276 void SideEffectAnalysisInfo::AddPredecessorsForAccess(int64_t resource_id,
277                                                       Operation* op,
278                                                       bool read_only) {
279   auto it = per_resource_access_info_.find(resource_id);
280   if (it == per_resource_access_info_.end()) return;
281   const auto& access_info = it->getSecond();
282   auto& control_predecessors = control_predecessors_[op];
283   bool read_tracked = false;
284   if (!read_only) {
285     control_predecessors.insert(access_info.reads_since_last_write.begin(),
286                                 access_info.reads_since_last_write.end());
287     read_tracked = !access_info.reads_since_last_write.empty();
288   }
289   if (access_info.last_write && !read_tracked) {
290     control_predecessors.insert(access_info.last_write);
291   }
292 }
293 
AnalyzeFunction(FuncOp func_op,const TF::ResourceAliasAnalysis::Info & alias_analysis)294 void SideEffectAnalysisInfo::AnalyzeFunction(
295     FuncOp func_op, const TF::ResourceAliasAnalysis::Info& alias_analysis) {
296   // AnalyzeRegion() recursively analyzes the function body, and only populates
297   // control_predecessors_.
298   AnalyzeRegion(&func_op.getBody(), alias_analysis);
299   // Populate sorted_control_predecessors_ and sorted_control_successors_ based
300   // on control_predecessors.
301   for (auto& entry : control_predecessors_) {
302     auto op = entry.getFirst();
303     auto& sorted_predecessors = sorted_control_predecessors_[op];
304     for (auto predecessor : entry.getSecond()) {
305       sorted_predecessors.push_back(predecessor);
306       sorted_control_successors_[predecessor].push_back(op);
307     }
308   }
309   control_predecessors_.clear();
310   for (auto& entry : sorted_control_predecessors_) {
311     llvm::sort(entry.getSecond(), [](Operation* a, Operation* b) {
312       return a->isBeforeInBlock(b);
313     });
314   }
315   for (auto& entry : sorted_control_successors_) {
316     llvm::sort(entry.getSecond(), [](Operation* a, Operation* b) {
317       return a->isBeforeInBlock(b);
318     });
319   }
320 }
321 
AnalyzeRegion(Region * region,const TF::ResourceAliasAnalysis::Info & alias_analysis)322 void SideEffectAnalysisInfo::AnalyzeRegion(
323     Region* region, const TF::ResourceAliasAnalysis::Info& alias_analysis) {
324   // This function populates control_predecessors_ by walking through the
325   // region, and tracking resource accesses in per_resource_access_info_.
326 
327   // Returns whether an access to `resource` can skip control edges from
328   // previous accesses to unknown resources, due to that earlier accesses to
329   // `resource` already indirectly tracked previous accesses to unknown
330   // resources. `read_only` specifies the type of access of the current op being
331   // considered.
332   auto unknown_access_indirectly_tracked_by_resource = [&](int64_t resource,
333                                                            bool read_only) {
334     auto it = per_resource_access_info_.find(resource);
335     if (it == per_resource_access_info_.end()) return false;
336     auto unknown_it = per_resource_access_info_.find(kUnknownResourceId);
337     const bool no_unknown_read =
338         unknown_it == per_resource_access_info_.end() ||
339         unknown_it->getSecond().reads_since_last_write.empty();
340     return read_only
341                ? it->second.tracked_last_unknown_write_for_read
342                : it->second.tracked_last_unknown_write_for_write &&
343                      (it->second.tracked_last_unknown_read || no_unknown_read);
344   };
345 
346   // We explicitly iterates through the regions and blocks, in order to handle
347   // different nested regions separately.
348   for (auto& block : *region) {
349     llvm::SmallPtrSet<Operation*, 8> non_resource_control_predecessors;
350     for (auto& op : block) {
351       for (Region& child : op.getRegions()) {
352         SideEffectAnalysisInfo child_analysis(&child, alias_analysis);
353         // Moves the control_predecessors_ fields in child region to current
354         // region
355         for (auto& entry : child_analysis.control_predecessors_)
356           control_predecessors_[entry.first] = std::move(entry.second);
357       }
358 
359       // We do not need explicit control edges for declaration ops.
360       if (OpIsDeclaration(&op, alias_analysis)) continue;
361 
362       ResourceSideEffectsByValue resource_op_info;
363       bool must_execute = false;
364       GetResourceInfoForOp(&op, resource_op_info, must_execute);
365 
366       if (resource_op_info.empty() && OpIsKnownToHaveNoSideEffect(&op))
367         continue;
368 
369       if (resource_op_info.empty() && must_execute) {
370         // Add unknown resource ops as predecessors of the op that must execute,
371         // to guarantee ordering between unknown resource ops.
372         AddPredecessorsForAccess(kUnknownResourceId, &op, /*read_only=*/false);
373         non_resource_control_predecessors.insert(&op);
374         continue;
375       }
376 
377       if (IsResourceOpAllocOnly(&op, resource_op_info)) continue;
378 
379       auto resource_ids_by_value =
380           GetResourceIdsByValue(&op, alias_analysis, resource_op_info);
381       const bool read_only = IsResourceOpReadOnly(&op, resource_op_info);
382       bool indirectly_tracked_unknown_access = false;
383       // First add edges from known resources.
384       if (!resource_ids_by_value.hasValue()) {
385         for (auto& entry : per_resource_access_info_) {
386           if (entry.getFirst() == kUnknownResourceId) continue;
387           AddPredecessorsForAccess(entry.getFirst(), &op, read_only);
388           indirectly_tracked_unknown_access |=
389               unknown_access_indirectly_tracked_by_resource(entry.getFirst(),
390                                                             read_only);
391         }
392       } else {
393         // Collect all resource id's and whether their side effect is read only.
394         llvm::SmallDenseMap<int64_t, bool> read_only_by_resource_id;
395         for (const auto& resource_ids : *resource_ids_by_value) {
396           const bool is_result = resource_ids.first.getDefiningOp() == &op;
397           auto value_resource_info = resource_op_info.find(resource_ids.first);
398           bool resource_read_only = false;
399           if (value_resource_info != resource_op_info.end()) {
400             if (is_result && value_resource_info->getSecond().IsAllocOnly())
401               continue;
402             resource_read_only = value_resource_info->getSecond().IsReadOnly();
403           }
404 
405           for (const auto& id : *resource_ids.second) {
406             auto it =
407                 read_only_by_resource_id.try_emplace(id, resource_read_only);
408             if (!it.second && !resource_read_only)
409               it.first->getSecond() = resource_read_only;
410           }
411         }
412 
413         for (const auto& resource : read_only_by_resource_id) {
414           const auto& resource_id = resource.getFirst();
415           const auto& resource_read_only = resource.getSecond();
416           AddPredecessorsForAccess(resource_id, &op, resource_read_only);
417           indirectly_tracked_unknown_access |=
418               unknown_access_indirectly_tracked_by_resource(resource_id,
419                                                             resource_read_only);
420           // Update access info for known resources.
421           TrackAccess(resource_id, &op, resource_read_only);
422         }
423       }
424 
425       // If not indirectly tracked, add edges from the unknown resource.
426       if (!indirectly_tracked_unknown_access) {
427         AddPredecessorsForAccess(kUnknownResourceId, &op, read_only);
428       }
429       if (!resource_ids_by_value.hasValue()) {
430         // Update access info for unknown resource.
431         TrackAccess(kUnknownResourceId, &op, read_only);
432         // Add ops that must execute to unknown resource op predecessors.
433         auto& control_predecessors = control_predecessors_[&op];
434         control_predecessors.insert(non_resource_control_predecessors.begin(),
435                                     non_resource_control_predecessors.end());
436         // Ops that must execute currently tracked are cleared as transitively
437         // unknown resource ops will allow for such ops to be transitively
438         // reachable.
439         non_resource_control_predecessors.clear();
440       }
441     }
442   }
443 }
444 
445 llvm::SmallVector<Operation*, 4>
DirectControlPredecessors(Operation * op,llvm::function_ref<bool (Operation *)> filter) const446 SideEffectAnalysisInfo::DirectControlPredecessors(
447     Operation* op, llvm::function_ref<bool(Operation*)> filter) const {
448   llvm::SmallVector<Operation*, 4> result;
449   auto it = sorted_control_predecessors_.find(op);
450   if (it == sorted_control_predecessors_.end()) return result;
451   result.reserve(it->getSecond().size());
452   for (auto predecessor : it->getSecond()) {
453     if (!filter || filter(predecessor)) result.push_back(predecessor);
454   }
455   return result;
456 }
457 
458 llvm::SmallVector<Operation*, 4>
DirectControlSuccessors(Operation * op,llvm::function_ref<bool (Operation *)> filter) const459 SideEffectAnalysisInfo::DirectControlSuccessors(
460     Operation* op, llvm::function_ref<bool(Operation*)> filter) const {
461   llvm::SmallVector<Operation*, 4> result;
462   auto it = sorted_control_successors_.find(op);
463   if (it == sorted_control_successors_.end()) return result;
464   result.reserve(it->getSecond().size());
465   for (auto successor : it->getSecond()) {
466     if (!filter || filter(successor)) result.push_back(successor);
467   }
468   return result;
469 }
470 }  // namespace detail
471 
SideEffectAnalysis(ModuleOp module)472 SideEffectAnalysis::SideEffectAnalysis(ModuleOp module) {
473   // Analyze entire module for alias analysis info.
474   ResourceAliasAnalysis alias_analysis(module);
475 
476   // Analyze all functions.
477   for (auto func : module.getOps<FuncOp>())
478     this->info_map_.try_emplace(func, func,
479                                 alias_analysis.GetAnalysisForFunc(func));
480 }
481 
482 }  // namespace TF
483 }  // namespace mlir
484