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