MLIR  22.0.0git
LocalAliasAnalysis.cpp
Go to the documentation of this file.
1 //===- LocalAliasAnalysis.cpp - Local stateless alias Analysis for MLIR ---===//
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 
10 
12 #include "mlir/IR/Attributes.h"
13 #include "mlir/IR/Block.h"
14 #include "mlir/IR/Matchers.h"
15 #include "mlir/IR/OpDefinition.h"
16 #include "mlir/IR/Operation.h"
17 #include "mlir/IR/Region.h"
18 #include "mlir/IR/Value.h"
19 #include "mlir/IR/ValueRange.h"
24 #include "mlir/Support/LLVM.h"
25 #include "llvm/Support/Casting.h"
26 #include <cassert>
27 #include <optional>
28 #include <utility>
29 
30 using namespace mlir;
31 
32 //===----------------------------------------------------------------------===//
33 // Underlying Address Computation
34 //===----------------------------------------------------------------------===//
35 
36 /// The maximum depth that will be searched when trying to find an underlying
37 /// value.
38 static constexpr unsigned maxUnderlyingValueSearchDepth = 10;
39 
40 /// Given a value, collect all of the underlying values being addressed.
41 static void collectUnderlyingAddressValues(Value value, unsigned maxDepth,
42  DenseSet<Value> &visited,
43  SmallVectorImpl<Value> &output);
44 
45 /// Given a successor (`region`) of a RegionBranchOpInterface, collect all of
46 /// the underlying values being addressed by one of the successor inputs. If the
47 /// provided `region` is null, as per `RegionBranchOpInterface` this represents
48 /// the parent operation.
49 static void collectUnderlyingAddressValues(RegionBranchOpInterface branch,
50  Region *region, Value inputValue,
51  unsigned inputIndex,
52  unsigned maxDepth,
53  DenseSet<Value> &visited,
54  SmallVectorImpl<Value> &output) {
55  // Given the index of a region of the branch (`predIndex`), or std::nullopt to
56  // represent the parent operation, try to return the index into the outputs of
57  // this region predecessor that correspond to the input values of `region`. If
58  // an index could not be found, std::nullopt is returned instead.
59  auto getOperandIndexIfPred =
60  [&](RegionBranchPoint pred) -> std::optional<unsigned> {
62  branch.getSuccessorRegions(pred, successors);
63  for (RegionSuccessor &successor : successors) {
64  if (successor.getSuccessor() != region)
65  continue;
66  // Check that the successor inputs map to the given input value.
67  ValueRange inputs = successor.getSuccessorInputs();
68  if (inputs.empty()) {
69  output.push_back(inputValue);
70  break;
71  }
72  unsigned firstInputIndex, lastInputIndex;
73  if (region) {
74  firstInputIndex = cast<BlockArgument>(inputs[0]).getArgNumber();
75  lastInputIndex = cast<BlockArgument>(inputs.back()).getArgNumber();
76  } else {
77  firstInputIndex = cast<OpResult>(inputs[0]).getResultNumber();
78  lastInputIndex = cast<OpResult>(inputs.back()).getResultNumber();
79  }
80  if (firstInputIndex > inputIndex || lastInputIndex < inputIndex) {
81  output.push_back(inputValue);
82  break;
83  }
84  return inputIndex - firstInputIndex;
85  }
86  return std::nullopt;
87  };
88 
89  // Check branches from the parent operation.
90  auto branchPoint = RegionBranchPoint::parent();
91  if (region)
92  branchPoint = region;
93 
94  if (std::optional<unsigned> operandIndex =
95  getOperandIndexIfPred(/*predIndex=*/RegionBranchPoint::parent())) {
97  branch.getEntrySuccessorOperands(branchPoint)[*operandIndex], maxDepth,
98  visited, output);
99  }
100  // Check branches from each child region.
101  Operation *op = branch.getOperation();
102  for (Region &region : op->getRegions()) {
103  if (std::optional<unsigned> operandIndex = getOperandIndexIfPred(region)) {
104  for (Block &block : region) {
105  // Try to determine possible region-branch successor operands for the
106  // current region.
107  if (auto term = dyn_cast<RegionBranchTerminatorOpInterface>(
108  block.getTerminator())) {
110  term.getSuccessorOperands(branchPoint)[*operandIndex], maxDepth,
111  visited, output);
112  } else if (block.getNumSuccessors()) {
113  // Otherwise, if this terminator may exit the region we can't make
114  // any assumptions about which values get passed.
115  output.push_back(inputValue);
116  return;
117  }
118  }
119  }
120  }
121 }
122 
123 /// Given a result, collect all of the underlying values being addressed.
124 static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
125  DenseSet<Value> &visited,
126  SmallVectorImpl<Value> &output) {
127  Operation *op = result.getOwner();
128 
129  // If this is a view, unwrap to the source.
130  if (ViewLikeOpInterface view = dyn_cast<ViewLikeOpInterface>(op)) {
131  if (result == view.getViewDest()) {
132  return collectUnderlyingAddressValues(view.getViewSource(), maxDepth,
133  visited, output);
134  }
135  }
136  // Check to see if we can reason about the control flow of this op.
137  if (auto branch = dyn_cast<RegionBranchOpInterface>(op)) {
138  return collectUnderlyingAddressValues(branch, /*region=*/nullptr, result,
139  result.getResultNumber(), maxDepth,
140  visited, output);
141  }
142 
143  output.push_back(result);
144 }
145 
146 /// Given a block argument, collect all of the underlying values being
147 /// addressed.
148 static void collectUnderlyingAddressValues(BlockArgument arg, unsigned maxDepth,
149  DenseSet<Value> &visited,
150  SmallVectorImpl<Value> &output) {
151  Block *block = arg.getOwner();
152  unsigned argNumber = arg.getArgNumber();
153 
154  // Handle the case of a non-entry block.
155  if (!block->isEntryBlock()) {
156  for (auto it = block->pred_begin(), e = block->pred_end(); it != e; ++it) {
157  auto branch = dyn_cast<BranchOpInterface>((*it)->getTerminator());
158  if (!branch) {
159  // We can't analyze the control flow, so bail out early.
160  output.push_back(arg);
161  return;
162  }
163 
164  // Try to get the operand passed for this argument.
165  unsigned index = it.getSuccessorIndex();
166  Value operand = branch.getSuccessorOperands(index)[argNumber];
167  if (!operand) {
168  // We can't analyze the control flow, so bail out early.
169  output.push_back(arg);
170  return;
171  }
172  collectUnderlyingAddressValues(operand, maxDepth, visited, output);
173  }
174  return;
175  }
176 
177  // Otherwise, check to see if we can reason about the control flow of this op.
178  Region *region = block->getParent();
179  Operation *op = region->getParentOp();
180  if (auto branch = dyn_cast<RegionBranchOpInterface>(op)) {
181  return collectUnderlyingAddressValues(branch, region, arg, argNumber,
182  maxDepth, visited, output);
183  }
184 
185  // We can't reason about the underlying address of this argument.
186  output.push_back(arg);
187 }
188 
189 /// Given a value, collect all of the underlying values being addressed.
190 static void collectUnderlyingAddressValues(Value value, unsigned maxDepth,
191  DenseSet<Value> &visited,
192  SmallVectorImpl<Value> &output) {
193  // Check that we don't infinitely recurse.
194  if (!visited.insert(value).second)
195  return;
196  if (maxDepth == 0) {
197  output.push_back(value);
198  return;
199  }
200  --maxDepth;
201 
202  if (BlockArgument arg = dyn_cast<BlockArgument>(value))
203  return collectUnderlyingAddressValues(arg, maxDepth, visited, output);
204  collectUnderlyingAddressValues(cast<OpResult>(value), maxDepth, visited,
205  output);
206 }
207 
208 /// Given a value, collect all of the underlying values being addressed.
210  SmallVectorImpl<Value> &output) {
211  DenseSet<Value> visited;
213  output);
214 }
215 
216 //===----------------------------------------------------------------------===//
217 // LocalAliasAnalysis: alias
218 //===----------------------------------------------------------------------===//
219 
220 /// Given a value, try to get an allocation effect attached to it. If
221 /// successful, `allocEffect` is populated with the effect. If an effect was
222 /// found, `allocScopeOp` is also specified if a parent operation of `value`
223 /// could be identified that bounds the scope of the allocated value; i.e. if
224 /// non-null it specifies the parent operation that the allocation does not
225 /// escape. If no scope is found, `allocScopeOp` is set to nullptr.
226 static LogicalResult
228  std::optional<MemoryEffects::EffectInstance> &effect,
229  Operation *&allocScopeOp) {
230  // Try to get a memory effect interface for the parent operation.
231  Operation *op;
232  if (BlockArgument arg = dyn_cast<BlockArgument>(value))
233  op = arg.getOwner()->getParentOp();
234  else
235  op = cast<OpResult>(value).getOwner();
236  MemoryEffectOpInterface interface = dyn_cast<MemoryEffectOpInterface>(op);
237  if (!interface)
238  return failure();
239 
240  // Try to find an allocation effect on the resource.
241  if (!(effect = interface.getEffectOnValue<MemoryEffects::Allocate>(value)))
242  return failure();
243 
244  // If we found an allocation effect, try to find a scope for the allocation.
245  // If the resource of this allocation is automatically scoped, find the parent
246  // operation that bounds the allocation scope.
247  if (llvm::isa<SideEffects::AutomaticAllocationScopeResource>(
248  effect->getResource())) {
250  return success();
251  }
252 
253  // TODO: Here we could look at the users to see if the resource is either
254  // freed on all paths within the region, or is just not captured by anything.
255  // For now assume allocation scope to the function scope (we don't care if
256  // pointer escape outside function).
257  allocScopeOp = op->getParentOfType<FunctionOpInterface>();
258  return success();
259 }
260 
262  if (op && op->hasTrait<OpTrait::DistinctObjectsTrait>())
263  return op;
264 
265  return nullptr;
266 }
267 
269  unsigned argNumber = cast<OpResult>(value).getResultNumber();
270  return op->getOperand(argNumber);
271 }
272 
273 static std::optional<AliasResult> checkDistinctObjects(Value lhs, Value rhs) {
274  // We should already checked that lhs and rhs are different.
275  assert(lhs != rhs && "lhs and rhs must be different");
276 
277  // Result and corresponding operand must alias.
278  auto lhsOp = isDistinctObjectsOp(lhs.getDefiningOp());
279  if (lhsOp && getDistinctObjectsOperand(lhsOp, lhs) == rhs)
280  return AliasResult::MustAlias;
281 
282  auto rhsOp = isDistinctObjectsOp(rhs.getDefiningOp());
283  if (rhsOp && getDistinctObjectsOperand(rhsOp, rhs) == lhs)
284  return AliasResult::MustAlias;
285 
286  // If two different values come from the same `DistinctObjects` operation,
287  // they don't alias.
288  if (lhsOp && lhsOp == rhsOp)
289  return AliasResult::NoAlias;
290 
291  return std::nullopt;
292 }
293 
294 /// Given the two values, return their aliasing behavior.
296  if (lhs == rhs)
297  return AliasResult::MustAlias;
298  Operation *lhsAllocScope = nullptr, *rhsAllocScope = nullptr;
299  std::optional<MemoryEffects::EffectInstance> lhsAlloc, rhsAlloc;
300 
301  // Handle the case where lhs is a constant.
302  Attribute lhsAttr, rhsAttr;
303  if (matchPattern(lhs, m_Constant(&lhsAttr))) {
304  // TODO: This is overly conservative. Two matching constants don't
305  // necessarily map to the same address. For example, if the two values
306  // correspond to different symbols that both represent a definition.
307  if (matchPattern(rhs, m_Constant(&rhsAttr)))
308  return AliasResult::MayAlias;
309 
310  // Try to find an alloc effect on rhs. If an effect was found we can't
311  // alias, otherwise we might.
312  return succeeded(getAllocEffectFor(rhs, rhsAlloc, rhsAllocScope))
315  }
316  // Handle the case where rhs is a constant.
317  if (matchPattern(rhs, m_Constant(&rhsAttr))) {
318  // Try to find an alloc effect on lhs. If an effect was found we can't
319  // alias, otherwise we might.
320  return succeeded(getAllocEffectFor(lhs, lhsAlloc, lhsAllocScope))
323  }
324 
325  if (std::optional<AliasResult> result = checkDistinctObjects(lhs, rhs))
326  return *result;
327 
328  // Otherwise, neither of the values are constant so check to see if either has
329  // an allocation effect.
330  bool lhsHasAlloc = succeeded(getAllocEffectFor(lhs, lhsAlloc, lhsAllocScope));
331  bool rhsHasAlloc = succeeded(getAllocEffectFor(rhs, rhsAlloc, rhsAllocScope));
332  if (lhsHasAlloc == rhsHasAlloc) {
333  // If both values have an allocation effect we know they don't alias, and if
334  // neither have an effect we can't make an assumptions.
335  return lhsHasAlloc ? AliasResult::NoAlias : AliasResult::MayAlias;
336  }
337 
338  // When we reach this point we have one value with a known allocation effect,
339  // and one without. Move the one with the effect to the lhs to make the next
340  // checks simpler.
341  if (rhsHasAlloc) {
342  std::swap(lhs, rhs);
343  lhsAlloc = rhsAlloc;
344  lhsAllocScope = rhsAllocScope;
345  }
346 
347  // If the effect has a scoped allocation region, check to see if the
348  // non-effect value is defined above that scope.
349  if (lhsAllocScope) {
350  // If the parent operation of rhs is an ancestor of the allocation scope, or
351  // if rhs is an entry block argument of the allocation scope we know the two
352  // values can't alias.
353  Operation *rhsParentOp = rhs.getParentRegion()->getParentOp();
354  if (rhsParentOp->isProperAncestor(lhsAllocScope))
355  return AliasResult::NoAlias;
356  if (rhsParentOp == lhsAllocScope) {
357  BlockArgument rhsArg = dyn_cast<BlockArgument>(rhs);
358  if (rhsArg && rhs.getParentBlock()->isEntryBlock())
359  return AliasResult::NoAlias;
360  }
361  }
362 
363  // If we couldn't reason about the relationship between the two values,
364  // conservatively assume they might alias.
365  return AliasResult::MayAlias;
366 }
367 
368 /// Given the two values, return their aliasing behavior.
370  if (lhs == rhs)
371  return AliasResult::MustAlias;
372 
373  // Get the underlying values being addressed.
374  SmallVector<Value, 8> lhsValues, rhsValues;
375  collectUnderlyingAddressValues(lhs, lhsValues);
376  collectUnderlyingAddressValues(rhs, rhsValues);
377 
378  // If we failed to collect for either of the values somehow, conservatively
379  // assume they may alias.
380  if (lhsValues.empty() || rhsValues.empty())
381  return AliasResult::MayAlias;
382 
383  // Check the alias results against each of the underlying values.
384  std::optional<AliasResult> result;
385  for (Value lhsVal : lhsValues) {
386  for (Value rhsVal : rhsValues) {
387  AliasResult nextResult = aliasImpl(lhsVal, rhsVal);
388  result = result ? result->merge(nextResult) : nextResult;
389  }
390  }
391 
392  // We should always have a valid result here.
393  return *result;
394 }
395 
396 //===----------------------------------------------------------------------===//
397 // LocalAliasAnalysis: getModRef
398 //===----------------------------------------------------------------------===//
399 
401  // Check to see if this operation relies on nested side effects.
403  // TODO: To check recursive operations we need to check all of the nested
404  // operations, which can result in a quadratic number of queries. We should
405  // introduce some caching of some kind to help alleviate this, especially as
406  // this caching could be used in other areas of the codebase (e.g. when
407  // checking `wouldOpBeTriviallyDead`).
409  }
410 
411  // Otherwise, check to see if this operation has a memory effect interface.
412  MemoryEffectOpInterface interface = dyn_cast<MemoryEffectOpInterface>(op);
413  if (!interface)
415 
416  // Build a ModRefResult by merging the behavior of the effects of this
417  // operation.
419  interface.getEffects(effects);
420 
422  for (const MemoryEffects::EffectInstance &effect : effects) {
423  if (isa<MemoryEffects::Allocate, MemoryEffects::Free>(effect.getEffect()))
424  continue;
425 
426  // Check for an alias between the effect and our memory location.
427  // TODO: Add support for checking an alias with a symbol reference.
428  AliasResult aliasResult = AliasResult::MayAlias;
429  if (Value effectValue = effect.getValue())
430  aliasResult = alias(effectValue, location);
431 
432  // If we don't alias, ignore this effect.
433  if (aliasResult.isNo())
434  continue;
435 
436  // Merge in the corresponding mod or ref for this effect.
437  if (isa<MemoryEffects::Read>(effect.getEffect())) {
438  result = result.merge(ModRefResult::getRef());
439  } else {
440  assert(isa<MemoryEffects::Write>(effect.getEffect()));
441  result = result.merge(ModRefResult::getMod());
442  }
443  if (result.isModAndRef())
444  break;
445  }
446  return result;
447 }
static Operation * isDistinctObjectsOp(Operation *op)
static LogicalResult getAllocEffectFor(Value value, std::optional< MemoryEffects::EffectInstance > &effect, Operation *&allocScopeOp)
Given a value, try to get an allocation effect attached to it.
static std::optional< AliasResult > checkDistinctObjects(Value lhs, Value rhs)
static void collectUnderlyingAddressValues(Value value, unsigned maxDepth, DenseSet< Value > &visited, SmallVectorImpl< Value > &output)
Given a value, collect all of the underlying values being addressed.
static constexpr unsigned maxUnderlyingValueSearchDepth
The maximum depth that will be searched when trying to find an underlying value.
static Value getDistinctObjectsOperand(Operation *op, Value value)
The possible results of an alias query.
Definition: AliasAnalysis.h:26
bool isNo() const
Returns if this result indicates no possibility of aliasing.
Definition: AliasAnalysis.h:59
@ MustAlias
The two locations precisely alias each other.
Definition: AliasAnalysis.h:41
@ MayAlias
The two locations may or may not alias.
Definition: AliasAnalysis.h:37
@ NoAlias
The two locations do not alias at all.
Definition: AliasAnalysis.h:34
Attributes are known-constant values of operations.
Definition: Attributes.h:25
This class represents an argument of a Block.
Definition: Value.h:309
Block * getOwner() const
Returns the block that owns this argument.
Definition: Value.h:318
unsigned getArgNumber() const
Returns the number of this argument.
Definition: Value.h:321
Block represents an ordered list of Operations.
Definition: Block.h:33
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition: Block.cpp:27
pred_iterator pred_begin()
Definition: Block.h:236
bool isEntryBlock()
Return if this block is the entry block in the parent region.
Definition: Block.cpp:36
pred_iterator pred_end()
Definition: Block.h:239
ModRefResult getModRef(Operation *op, Value location)
Return the modify-reference behavior of op on location.
virtual AliasResult aliasImpl(Value lhs, Value rhs)
Given the two values, return their aliasing behavior.
AliasResult alias(Value lhs, Value rhs)
Given two values, return their aliasing behavior.
The possible results of whether a memory access modifies or references a memory location.
Definition: AliasAnalysis.h:90
bool isModAndRef() const
Returns if this result modifies and references memory.
ModRefResult merge(const ModRefResult &other)
Merge this ModRef result with other and return the result.
static ModRefResult getRef()
Return a new result that indicates that the memory access may reference the value stored in memory.
static ModRefResult getNoModRef()
Return a new result that indicates that the memory access neither references nor modifies the value s...
static ModRefResult getModAndRef()
Return a new result that indicates that the memory access may reference and may modify the value stor...
static ModRefResult getMod()
Return a new result that indicates that the memory access may modify the value stored in memory.
This is a value defined by a result of an operation.
Definition: Value.h:447
Operation * getOwner() const
Returns the operation that owns this result.
Definition: Value.h:456
unsigned getResultNumber() const
Returns the number of this result.
Definition: Value.h:459
A trait of region holding operations that define a new scope for automatic allocations,...
This trai indicates that pointer-like objects (such as memrefs) returned from this operation will nev...
This trait indicates that the memory effects of an operation includes the effects of operations neste...
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
Value getOperand(unsigned idx)
Definition: Operation.h:350
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition: Operation.h:749
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition: Operation.h:234
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition: Operation.h:238
Operation * getParentWithTrait()
Returns the closest surrounding parent operation with trait Trait.
Definition: Operation.h:248
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition: Operation.h:677
bool isProperAncestor(Operation *other)
Return true if this operation is a proper ancestor of the other operation.
Definition: Operation.cpp:219
This class represents a point being branched from in the methods of the RegionBranchOpInterface.
static constexpr RegionBranchPoint parent()
Returns an instance of RegionBranchPoint representing the parent operation.
This class represents a successor of a region.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition: Region.h:26
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition: Region.h:200
This class represents a specific instance of an effect.
This class provides an abstraction over the different types of ranges over Values.
Definition: ValueRange.h:387
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
Block * getParentBlock()
Return the Block in which this Value is defined.
Definition: Value.cpp:46
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition: Value.cpp:18
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition: Value.cpp:39
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition: Matchers.h:490
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition: Matchers.h:369
The following effect indicates that the operation allocates from some resource.