173c3dff1SAlex Zinenko //===- CheckUses.cpp - Expensive transform value validity checks ----------===// 273c3dff1SAlex Zinenko // 373c3dff1SAlex Zinenko // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 473c3dff1SAlex Zinenko // See https://llvm.org/LICENSE.txt for license information. 573c3dff1SAlex Zinenko // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 673c3dff1SAlex Zinenko // 773c3dff1SAlex Zinenko //===----------------------------------------------------------------------===// 873c3dff1SAlex Zinenko // 973c3dff1SAlex Zinenko // This file defines a pass that performs expensive opt-in checks for Transform 1073c3dff1SAlex Zinenko // dialect values being potentially used after they have been consumed. 1173c3dff1SAlex Zinenko // 1273c3dff1SAlex Zinenko //===----------------------------------------------------------------------===// 1373c3dff1SAlex Zinenko 14039b969bSMichele Scuttari #include "mlir/Dialect/Transform/Transforms/Passes.h" 1567d0d7acSMichele Scuttari 165a9bdd85SOleksandr "Alex" Zinenko #include "mlir/Dialect/Transform/Interfaces/TransformInterfaces.h" 1773c3dff1SAlex Zinenko #include "mlir/Interfaces/SideEffectInterfaces.h" 1873c3dff1SAlex Zinenko #include "mlir/Pass/Pass.h" 1973c3dff1SAlex Zinenko #include "llvm/ADT/SetOperations.h" 2073c3dff1SAlex Zinenko 2167d0d7acSMichele Scuttari namespace mlir { 2267d0d7acSMichele Scuttari namespace transform { 23b512b1c8SMehdi Amini #define GEN_PASS_DEF_CHECKUSESPASS 2467d0d7acSMichele Scuttari #include "mlir/Dialect/Transform/Transforms/Passes.h.inc" 2567d0d7acSMichele Scuttari } // namespace transform 2667d0d7acSMichele Scuttari } // namespace mlir 2767d0d7acSMichele Scuttari 2873c3dff1SAlex Zinenko using namespace mlir; 2973c3dff1SAlex Zinenko 3073c3dff1SAlex Zinenko namespace { 3173c3dff1SAlex Zinenko 3273c3dff1SAlex Zinenko /// Returns a reference to a cached set of blocks that are reachable from the 3373c3dff1SAlex Zinenko /// given block via edges computed by the `getNextNodes` function. For example, 3473c3dff1SAlex Zinenko /// if `getNextNodes` returns successors of a block, this will return the set of 3573c3dff1SAlex Zinenko /// reachable blocks; if it returns predecessors of a block, this will return 3673c3dff1SAlex Zinenko /// the set of blocks from which the given block can be reached. The block is 3773c3dff1SAlex Zinenko /// considered reachable form itself only if there is a cycle. 3873c3dff1SAlex Zinenko template <typename FnTy> 3973c3dff1SAlex Zinenko const llvm::SmallPtrSet<Block *, 4> & 4073c3dff1SAlex Zinenko getReachableImpl(Block *block, FnTy getNextNodes, 4173c3dff1SAlex Zinenko DenseMap<Block *, llvm::SmallPtrSet<Block *, 4>> &cache) { 427d9f9938SKazu Hirata auto [it, inserted] = cache.try_emplace(block); 437d9f9938SKazu Hirata if (!inserted) 4473c3dff1SAlex Zinenko return it->getSecond(); 4573c3dff1SAlex Zinenko 467d9f9938SKazu Hirata llvm::SmallPtrSet<Block *, 4> &reachable = it->second; 4773c3dff1SAlex Zinenko SmallVector<Block *> worklist; 4873c3dff1SAlex Zinenko worklist.push_back(block); 4973c3dff1SAlex Zinenko while (!worklist.empty()) { 5073c3dff1SAlex Zinenko Block *current = worklist.pop_back_val(); 5173c3dff1SAlex Zinenko for (Block *predecessor : getNextNodes(current)) { 5273c3dff1SAlex Zinenko // The block is reachable from its transitive predecessors. Only add 5373c3dff1SAlex Zinenko // them to the worklist if they weren't already visited. 5473c3dff1SAlex Zinenko if (reachable.insert(predecessor).second) 5573c3dff1SAlex Zinenko worklist.push_back(predecessor); 5673c3dff1SAlex Zinenko } 5773c3dff1SAlex Zinenko } 5873c3dff1SAlex Zinenko return reachable; 5973c3dff1SAlex Zinenko } 6073c3dff1SAlex Zinenko 6173c3dff1SAlex Zinenko /// An analysis that identifies whether a value allocated by a Transform op may 6273c3dff1SAlex Zinenko /// be used by another such op after it may have been freed by a third op on 6373c3dff1SAlex Zinenko /// some control flow path. This is conceptually similar to a data flow 6473c3dff1SAlex Zinenko /// analysis, but relies on side effects related to particular values that 6573c3dff1SAlex Zinenko /// currently cannot be modeled by the MLIR data flow analysis framework (also, 6673c3dff1SAlex Zinenko /// the lattice element would be rather expensive as it would need to include 6773c3dff1SAlex Zinenko /// live and/or freed values for each operation). 6873c3dff1SAlex Zinenko /// 6973c3dff1SAlex Zinenko /// This analysis is conservatively pessimisic: it will consider that a value 7073c3dff1SAlex Zinenko /// may be freed if it is freed on any possible control flow path between its 7173c3dff1SAlex Zinenko /// allocation and a relevant use, even if the control never actually flows 7273c3dff1SAlex Zinenko /// through the operation that frees the value. It also does not differentiate 7373c3dff1SAlex Zinenko /// between may- (freed on at least one control flow path) and must-free (freed 7473c3dff1SAlex Zinenko /// on all possible control flow paths) because it would require expensive graph 7573c3dff1SAlex Zinenko /// algorithms. 7673c3dff1SAlex Zinenko /// 7773c3dff1SAlex Zinenko /// It is intended as an additional non-blocking verification or debugging aid 7873c3dff1SAlex Zinenko /// for ops in the Transform dialect. It leverages the requirement for Transform 7973c3dff1SAlex Zinenko /// dialect ops to implement the MemoryEffectsOpInterface, and expects the 8073c3dff1SAlex Zinenko /// values in the Transform IR to have an allocation effect on the 8173c3dff1SAlex Zinenko /// TransformMappingResource when defined. 8273c3dff1SAlex Zinenko class TransformOpMemFreeAnalysis { 8373c3dff1SAlex Zinenko public: 8473c3dff1SAlex Zinenko MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TransformOpMemFreeAnalysis) 8573c3dff1SAlex Zinenko 8673c3dff1SAlex Zinenko /// Computes the analysis for Transform ops nested in the given operation. 8773c3dff1SAlex Zinenko explicit TransformOpMemFreeAnalysis(Operation *root) { 8873c3dff1SAlex Zinenko root->walk([&](Operation *op) { 8973c3dff1SAlex Zinenko if (isa<transform::TransformOpInterface>(op)) { 9073c3dff1SAlex Zinenko collectFreedValues(op); 9173c3dff1SAlex Zinenko return WalkResult::skip(); 9273c3dff1SAlex Zinenko } 9373c3dff1SAlex Zinenko return WalkResult::advance(); 9473c3dff1SAlex Zinenko }); 9573c3dff1SAlex Zinenko } 9673c3dff1SAlex Zinenko 9773c3dff1SAlex Zinenko /// A list of operations that may be deleting a value. Non-empty list 9873c3dff1SAlex Zinenko /// contextually converts to boolean "true" value. 9973c3dff1SAlex Zinenko class PotentialDeleters { 10073c3dff1SAlex Zinenko public: 10173c3dff1SAlex Zinenko /// Creates an empty list that corresponds to the value being live. 10273c3dff1SAlex Zinenko static PotentialDeleters live() { return PotentialDeleters({}); } 10373c3dff1SAlex Zinenko 10473c3dff1SAlex Zinenko /// Creates a list from the operations that may be deleting the value. 10573c3dff1SAlex Zinenko static PotentialDeleters maybeFreed(ArrayRef<Operation *> deleters) { 10673c3dff1SAlex Zinenko return PotentialDeleters(deleters); 10773c3dff1SAlex Zinenko } 10873c3dff1SAlex Zinenko 10973c3dff1SAlex Zinenko /// Converts to "true" if there are operations that may be deleting the 11073c3dff1SAlex Zinenko /// value. 11173c3dff1SAlex Zinenko explicit operator bool() const { return !deleters.empty(); } 11273c3dff1SAlex Zinenko 11373c3dff1SAlex Zinenko /// Concatenates the lists of operations that may be deleting the value. The 11473c3dff1SAlex Zinenko /// value is known to be live if the reuslting list is still empty. 11573c3dff1SAlex Zinenko PotentialDeleters &operator|=(const PotentialDeleters &other) { 11673c3dff1SAlex Zinenko llvm::append_range(deleters, other.deleters); 11773c3dff1SAlex Zinenko return *this; 11873c3dff1SAlex Zinenko } 11973c3dff1SAlex Zinenko 12073c3dff1SAlex Zinenko /// Returns the list of ops that may be deleting the value. 12173c3dff1SAlex Zinenko ArrayRef<Operation *> getOps() const { return deleters; } 12273c3dff1SAlex Zinenko 12373c3dff1SAlex Zinenko private: 12473c3dff1SAlex Zinenko /// Constructs the list from the given operations. 12573c3dff1SAlex Zinenko explicit PotentialDeleters(ArrayRef<Operation *> ops) { 12673c3dff1SAlex Zinenko llvm::append_range(deleters, ops); 12773c3dff1SAlex Zinenko } 12873c3dff1SAlex Zinenko 12973c3dff1SAlex Zinenko /// The list of operations that may be deleting the value. 13073c3dff1SAlex Zinenko SmallVector<Operation *> deleters; 13173c3dff1SAlex Zinenko }; 13273c3dff1SAlex Zinenko 13373c3dff1SAlex Zinenko /// Returns the list of operations that may be deleting the operand value on 13473c3dff1SAlex Zinenko /// any control flow path between the definition of the value and its use as 13573c3dff1SAlex Zinenko /// the given operand. For the purposes of this analysis, the value is 13673c3dff1SAlex Zinenko /// considered to be allocated at its definition point and never re-allocated. 13773c3dff1SAlex Zinenko PotentialDeleters isUseLive(OpOperand &operand) { 13873c3dff1SAlex Zinenko const llvm::SmallPtrSet<Operation *, 2> &deleters = freedBy[operand.get()]; 13973c3dff1SAlex Zinenko if (deleters.empty()) 14073c3dff1SAlex Zinenko return live(); 14173c3dff1SAlex Zinenko 14273c3dff1SAlex Zinenko #ifndef NDEBUG 143d46afeefSAlex Zinenko // Check that the definition point actually allocates the value. If the 144d46afeefSAlex Zinenko // definition is a block argument, it may be just forwarding the operand of 145d46afeefSAlex Zinenko // the parent op without doing a new allocation, allow that. We currently 146d46afeefSAlex Zinenko // don't have the capability to analyze region-based control flow here. 147d46afeefSAlex Zinenko // 148d46afeefSAlex Zinenko // TODO: when this ported to the dataflow analysis infra, we should have 149d46afeefSAlex Zinenko // proper support for region-based control flow. 15073c3dff1SAlex Zinenko Operation *valueSource = 1515550c821STres Popp isa<OpResult>(operand.get()) 15273c3dff1SAlex Zinenko ? operand.get().getDefiningOp() 15373c3dff1SAlex Zinenko : operand.get().getParentBlock()->getParentOp(); 15473c3dff1SAlex Zinenko auto iface = cast<MemoryEffectOpInterface>(valueSource); 15573c3dff1SAlex Zinenko SmallVector<MemoryEffects::EffectInstance> instances; 15673c3dff1SAlex Zinenko iface.getEffectsOnResource(transform::TransformMappingResource::get(), 15773c3dff1SAlex Zinenko instances); 1585550c821STres Popp assert((isa<BlockArgument>(operand.get()) || 159d46afeefSAlex Zinenko hasEffect<MemoryEffects::Allocate>(instances, operand.get())) && 16073c3dff1SAlex Zinenko "expected the op defining the value to have an allocation effect " 16173c3dff1SAlex Zinenko "on it"); 16273c3dff1SAlex Zinenko #endif 16373c3dff1SAlex Zinenko 16473c3dff1SAlex Zinenko // Collect ancestors of the use operation. 16573c3dff1SAlex Zinenko Block *defBlock = operand.get().getParentBlock(); 16673c3dff1SAlex Zinenko SmallVector<Operation *> ancestors; 16773c3dff1SAlex Zinenko Operation *ancestor = operand.getOwner(); 16873c3dff1SAlex Zinenko do { 16973c3dff1SAlex Zinenko ancestors.push_back(ancestor); 17073c3dff1SAlex Zinenko if (ancestor->getParentRegion() == defBlock->getParent()) 17173c3dff1SAlex Zinenko break; 17273c3dff1SAlex Zinenko ancestor = ancestor->getParentOp(); 17373c3dff1SAlex Zinenko } while (true); 17473c3dff1SAlex Zinenko std::reverse(ancestors.begin(), ancestors.end()); 17573c3dff1SAlex Zinenko 17673c3dff1SAlex Zinenko // Consider the control flow from the definition point of the value to its 17773c3dff1SAlex Zinenko // use point. If the use is located in some nested region, consider the path 17873c3dff1SAlex Zinenko // from the entry block of the region to the use. 17973c3dff1SAlex Zinenko for (Operation *ancestor : ancestors) { 18073c3dff1SAlex Zinenko // The block should be considered partially if it is the block that 18173c3dff1SAlex Zinenko // contains the definition (allocation) of the value being used, and the 18273c3dff1SAlex Zinenko // value is defined in the middle of the block, i.e., is not a block 18373c3dff1SAlex Zinenko // argument. 18473c3dff1SAlex Zinenko bool isOutermost = ancestor == ancestors.front(); 1855550c821STres Popp bool isFromBlockPartial = isOutermost && isa<OpResult>(operand.get()); 18673c3dff1SAlex Zinenko 18773c3dff1SAlex Zinenko // Check if the value may be freed by operations between its definition 18873c3dff1SAlex Zinenko // (allocation) point in its block and the terminator of the block or the 18973c3dff1SAlex Zinenko // ancestor of the use if it is located in the same block. This is only 19073c3dff1SAlex Zinenko // done for partial blocks here, full blocks will be considered below 19173c3dff1SAlex Zinenko // similarly to other blocks. 19273c3dff1SAlex Zinenko if (isFromBlockPartial) { 19373c3dff1SAlex Zinenko bool defUseSameBlock = ancestor->getBlock() == defBlock; 19473c3dff1SAlex Zinenko // Consider all ops from the def to its block terminator, except the 19573c3dff1SAlex Zinenko // when the use is in the same block, in which case only consider the 19673c3dff1SAlex Zinenko // ops until the user. 19773c3dff1SAlex Zinenko if (PotentialDeleters potentialDeleters = isFreedInBlockAfter( 19873c3dff1SAlex Zinenko operand.get().getDefiningOp(), operand.get(), 19973c3dff1SAlex Zinenko defUseSameBlock ? ancestor : nullptr)) 20073c3dff1SAlex Zinenko return potentialDeleters; 20173c3dff1SAlex Zinenko } 20273c3dff1SAlex Zinenko 20373c3dff1SAlex Zinenko // Check if the value may be freed by opeations preceding the ancestor in 20473c3dff1SAlex Zinenko // its block. Skip the check for partial blocks that contain both the 20573c3dff1SAlex Zinenko // definition and the use point, as this has been already checked above. 20673c3dff1SAlex Zinenko if (!isFromBlockPartial || ancestor->getBlock() != defBlock) { 20773c3dff1SAlex Zinenko if (PotentialDeleters potentialDeleters = 20873c3dff1SAlex Zinenko isFreedInBlockBefore(ancestor, operand.get())) 20973c3dff1SAlex Zinenko return potentialDeleters; 21073c3dff1SAlex Zinenko } 21173c3dff1SAlex Zinenko 21273c3dff1SAlex Zinenko // Check if the value may be freed by operations in any of the blocks 21373c3dff1SAlex Zinenko // between the definition point (in the outermost region) or the entry 21473c3dff1SAlex Zinenko // block of the region (in other regions) and the operand or its ancestor 21573c3dff1SAlex Zinenko // in the region. This includes the entire "form" block if (1) the block 21673c3dff1SAlex Zinenko // has not been considered as partial above and (2) the block can be 21773c3dff1SAlex Zinenko // reached again through some control-flow loop. This includes the entire 21873c3dff1SAlex Zinenko // "to" block if it can be reached form itself through some control-flow 21973c3dff1SAlex Zinenko // cycle, regardless of whether it has been visited before. 22073c3dff1SAlex Zinenko Block *ancestorBlock = ancestor->getBlock(); 22173c3dff1SAlex Zinenko Block *from = 22273c3dff1SAlex Zinenko isOutermost ? defBlock : &ancestorBlock->getParent()->front(); 22373c3dff1SAlex Zinenko if (PotentialDeleters potentialDeleters = 22473c3dff1SAlex Zinenko isMaybeFreedOnPaths(from, ancestorBlock, operand.get(), 22573c3dff1SAlex Zinenko /*alwaysIncludeFrom=*/!isFromBlockPartial)) 22673c3dff1SAlex Zinenko return potentialDeleters; 22773c3dff1SAlex Zinenko } 22873c3dff1SAlex Zinenko return live(); 22973c3dff1SAlex Zinenko } 23073c3dff1SAlex Zinenko 23173c3dff1SAlex Zinenko private: 23273c3dff1SAlex Zinenko /// Make PotentialDeleters constructors available with shorter names. 23373c3dff1SAlex Zinenko static PotentialDeleters maybeFreed(ArrayRef<Operation *> deleters) { 23473c3dff1SAlex Zinenko return PotentialDeleters::maybeFreed(deleters); 23573c3dff1SAlex Zinenko } 23673c3dff1SAlex Zinenko static PotentialDeleters live() { return PotentialDeleters::live(); } 23773c3dff1SAlex Zinenko 23873c3dff1SAlex Zinenko /// Returns the list of operations that may be deleting the given value betwen 23973c3dff1SAlex Zinenko /// the first and last operations, non-inclusive. `getNext` indicates the 24073c3dff1SAlex Zinenko /// direction of the traversal. 24173c3dff1SAlex Zinenko PotentialDeleters 24273c3dff1SAlex Zinenko isFreedBetween(Value value, Operation *first, Operation *last, 24373c3dff1SAlex Zinenko llvm::function_ref<Operation *(Operation *)> getNext) const { 24473c3dff1SAlex Zinenko auto it = freedBy.find(value); 24573c3dff1SAlex Zinenko if (it == freedBy.end()) 24673c3dff1SAlex Zinenko return live(); 24773c3dff1SAlex Zinenko const llvm::SmallPtrSet<Operation *, 2> &deleters = it->getSecond(); 24873c3dff1SAlex Zinenko for (Operation *op = getNext(first); op != last; op = getNext(op)) { 24973c3dff1SAlex Zinenko if (deleters.contains(op)) 25073c3dff1SAlex Zinenko return maybeFreed(op); 25173c3dff1SAlex Zinenko } 25273c3dff1SAlex Zinenko return live(); 25373c3dff1SAlex Zinenko } 25473c3dff1SAlex Zinenko 25573c3dff1SAlex Zinenko /// Returns the list of operations that may be deleting the given value 25673c3dff1SAlex Zinenko /// between `root` and `before` values. `root` is expected to be in the same 25773c3dff1SAlex Zinenko /// block as `before` and precede it. If `before` is null, consider all 25873c3dff1SAlex Zinenko /// operations until the end of the block including the terminator. 25973c3dff1SAlex Zinenko PotentialDeleters isFreedInBlockAfter(Operation *root, Value value, 26073c3dff1SAlex Zinenko Operation *before = nullptr) const { 26173c3dff1SAlex Zinenko return isFreedBetween(value, root, before, 26273c3dff1SAlex Zinenko [](Operation *op) { return op->getNextNode(); }); 26373c3dff1SAlex Zinenko } 26473c3dff1SAlex Zinenko 26573c3dff1SAlex Zinenko /// Returns the list of operations that may be deleting the given value 26673c3dff1SAlex Zinenko /// between the entry of the block and the `root` operation. 26773c3dff1SAlex Zinenko PotentialDeleters isFreedInBlockBefore(Operation *root, Value value) const { 26873c3dff1SAlex Zinenko return isFreedBetween(value, root, nullptr, 26973c3dff1SAlex Zinenko [](Operation *op) { return op->getPrevNode(); }); 27073c3dff1SAlex Zinenko } 27173c3dff1SAlex Zinenko 27273c3dff1SAlex Zinenko /// Returns the list of operations that may be deleting the given value on 27373c3dff1SAlex Zinenko /// any of the control flow paths between the "form" and the "to" block. The 27473c3dff1SAlex Zinenko /// operations from any block visited on any control flow path are 27573c3dff1SAlex Zinenko /// consdiered. The "from" block is considered if there is a control flow 27673c3dff1SAlex Zinenko /// cycle going through it, i.e., if there is a possibility that all 27773c3dff1SAlex Zinenko /// operations in this block are visited or if the `alwaysIncludeFrom` flag is 27873c3dff1SAlex Zinenko /// set. The "to" block is considered only if there is a control flow cycle 27973c3dff1SAlex Zinenko /// going through it. 28073c3dff1SAlex Zinenko PotentialDeleters isMaybeFreedOnPaths(Block *from, Block *to, Value value, 28173c3dff1SAlex Zinenko bool alwaysIncludeFrom) { 28273c3dff1SAlex Zinenko // Find all blocks that lie on any path between "from" and "to", i.e., the 28373c3dff1SAlex Zinenko // intersection of blocks reachable from "from" and blocks from which "to" 28473c3dff1SAlex Zinenko // is rechable. 28573c3dff1SAlex Zinenko const llvm::SmallPtrSet<Block *, 4> &sources = getReachableFrom(to); 28673c3dff1SAlex Zinenko if (!sources.contains(from)) 28773c3dff1SAlex Zinenko return live(); 28873c3dff1SAlex Zinenko 28973c3dff1SAlex Zinenko llvm::SmallPtrSet<Block *, 4> reachable(getReachable(from)); 29073c3dff1SAlex Zinenko llvm::set_intersect(reachable, sources); 29173c3dff1SAlex Zinenko 29273c3dff1SAlex Zinenko // If requested, include the "from" block that may not be present in the set 29373c3dff1SAlex Zinenko // of visited blocks when there is no cycle going through it. 29473c3dff1SAlex Zinenko if (alwaysIncludeFrom) 29573c3dff1SAlex Zinenko reachable.insert(from); 29673c3dff1SAlex Zinenko 29773c3dff1SAlex Zinenko // Join potential deleters from all blocks as we don't know here which of 29873c3dff1SAlex Zinenko // the paths through the control flow is taken. 29973c3dff1SAlex Zinenko PotentialDeleters potentialDeleters = live(); 30073c3dff1SAlex Zinenko for (Block *block : reachable) { 30173c3dff1SAlex Zinenko for (Operation &op : *block) { 30273c3dff1SAlex Zinenko if (freedBy[value].count(&op)) 30373c3dff1SAlex Zinenko potentialDeleters |= maybeFreed(&op); 30473c3dff1SAlex Zinenko } 30573c3dff1SAlex Zinenko } 30673c3dff1SAlex Zinenko return potentialDeleters; 30773c3dff1SAlex Zinenko } 30873c3dff1SAlex Zinenko 30973c3dff1SAlex Zinenko /// Popualtes `reachable` with the set of blocks that are rechable from the 31073c3dff1SAlex Zinenko /// given block. A block is considered reachable from itself if there is a 31173c3dff1SAlex Zinenko /// cycle in the control-flow graph that invovles the block. 31273c3dff1SAlex Zinenko const llvm::SmallPtrSet<Block *, 4> &getReachable(Block *block) { 31373c3dff1SAlex Zinenko return getReachableImpl( 31473c3dff1SAlex Zinenko block, [](Block *b) { return b->getSuccessors(); }, reachableCache); 31573c3dff1SAlex Zinenko } 31673c3dff1SAlex Zinenko 31773c3dff1SAlex Zinenko /// Populates `sources` with the set of blocks from which the given block is 31873c3dff1SAlex Zinenko /// reachable. 31973c3dff1SAlex Zinenko const llvm::SmallPtrSet<Block *, 4> &getReachableFrom(Block *block) { 32073c3dff1SAlex Zinenko return getReachableImpl( 32173c3dff1SAlex Zinenko block, [](Block *b) { return b->getPredecessors(); }, 32273c3dff1SAlex Zinenko reachableFromCache); 32373c3dff1SAlex Zinenko } 32473c3dff1SAlex Zinenko 32573c3dff1SAlex Zinenko /// Returns true of `instances` contains an effect of `EffectTy` on `value`. 32673c3dff1SAlex Zinenko template <typename EffectTy> 32773c3dff1SAlex Zinenko static bool hasEffect(ArrayRef<MemoryEffects::EffectInstance> instances, 32873c3dff1SAlex Zinenko Value value) { 32973c3dff1SAlex Zinenko return llvm::any_of(instances, 33073c3dff1SAlex Zinenko [&](const MemoryEffects::EffectInstance &instance) { 33173c3dff1SAlex Zinenko return instance.getValue() == value && 33273c3dff1SAlex Zinenko isa<EffectTy>(instance.getEffect()); 33373c3dff1SAlex Zinenko }); 33473c3dff1SAlex Zinenko } 33573c3dff1SAlex Zinenko 33673c3dff1SAlex Zinenko /// Records the values that are being freed by an operation or any of its 33773c3dff1SAlex Zinenko /// children in `freedBy`. 33873c3dff1SAlex Zinenko void collectFreedValues(Operation *root) { 33973c3dff1SAlex Zinenko SmallVector<MemoryEffects::EffectInstance> instances; 34073c3dff1SAlex Zinenko root->walk([&](Operation *child) { 341*938cdd60SOleksandr "Alex" Zinenko if (isa<transform::PatternDescriptorOpInterface>(child)) 342*938cdd60SOleksandr "Alex" Zinenko return; 34373c3dff1SAlex Zinenko // TODO: extend this to conservatively handle operations with undeclared 34473c3dff1SAlex Zinenko // side effects as maybe freeing the operands. 34573c3dff1SAlex Zinenko auto iface = cast<MemoryEffectOpInterface>(child); 34673c3dff1SAlex Zinenko instances.clear(); 34773c3dff1SAlex Zinenko iface.getEffectsOnResource(transform::TransformMappingResource::get(), 34873c3dff1SAlex Zinenko instances); 34973c3dff1SAlex Zinenko for (Value operand : child->getOperands()) { 35073c3dff1SAlex Zinenko if (hasEffect<MemoryEffects::Free>(instances, operand)) { 35173c3dff1SAlex Zinenko // All parents of the operation that frees a value should be 35273c3dff1SAlex Zinenko // considered as potentially freeing the value as well. 35373c3dff1SAlex Zinenko // 35473c3dff1SAlex Zinenko // TODO: differentiate between must-free/may-free as well as between 35573c3dff1SAlex Zinenko // this op having the effect and children having the effect. This may 35673c3dff1SAlex Zinenko // require some analysis of all control flow paths through the nested 35773c3dff1SAlex Zinenko // regions as well as a mechanism to separate proper side effects from 35873c3dff1SAlex Zinenko // those obtained by nesting. 35973c3dff1SAlex Zinenko Operation *parent = child; 36073c3dff1SAlex Zinenko do { 36173c3dff1SAlex Zinenko freedBy[operand].insert(parent); 36273c3dff1SAlex Zinenko if (parent == root) 36373c3dff1SAlex Zinenko break; 36473c3dff1SAlex Zinenko parent = parent->getParentOp(); 36573c3dff1SAlex Zinenko } while (true); 36673c3dff1SAlex Zinenko } 36773c3dff1SAlex Zinenko } 36873c3dff1SAlex Zinenko }); 36973c3dff1SAlex Zinenko } 37073c3dff1SAlex Zinenko 37173c3dff1SAlex Zinenko /// The mapping from a value to operations that have a Free memory effect on 37273c3dff1SAlex Zinenko /// the TransformMappingResource and associated with this value, or to 37373c3dff1SAlex Zinenko /// Transform operations transitively containing such operations. 37473c3dff1SAlex Zinenko DenseMap<Value, llvm::SmallPtrSet<Operation *, 2>> freedBy; 37573c3dff1SAlex Zinenko 37673c3dff1SAlex Zinenko /// Caches for sets of reachable blocks. 37773c3dff1SAlex Zinenko DenseMap<Block *, llvm::SmallPtrSet<Block *, 4>> reachableCache; 37873c3dff1SAlex Zinenko DenseMap<Block *, llvm::SmallPtrSet<Block *, 4>> reachableFromCache; 37973c3dff1SAlex Zinenko }; 38073c3dff1SAlex Zinenko 38173c3dff1SAlex Zinenko //// A simple pass that warns about any use of a value by a transform operation 38273c3dff1SAlex Zinenko // that may be using the value after it has been freed. 383b512b1c8SMehdi Amini class CheckUsesPass : public transform::impl::CheckUsesPassBase<CheckUsesPass> { 38473c3dff1SAlex Zinenko public: 38573c3dff1SAlex Zinenko void runOnOperation() override { 38673c3dff1SAlex Zinenko auto &analysis = getAnalysis<TransformOpMemFreeAnalysis>(); 38773c3dff1SAlex Zinenko 38873c3dff1SAlex Zinenko getOperation()->walk([&](Operation *child) { 38973c3dff1SAlex Zinenko for (OpOperand &operand : child->getOpOperands()) { 39073c3dff1SAlex Zinenko TransformOpMemFreeAnalysis::PotentialDeleters deleters = 39173c3dff1SAlex Zinenko analysis.isUseLive(operand); 39273c3dff1SAlex Zinenko if (!deleters) 39373c3dff1SAlex Zinenko continue; 39473c3dff1SAlex Zinenko 39573c3dff1SAlex Zinenko InFlightDiagnostic diag = child->emitWarning() 39673c3dff1SAlex Zinenko << "operand #" << operand.getOperandNumber() 39773c3dff1SAlex Zinenko << " may be used after free"; 39873c3dff1SAlex Zinenko diag.attachNote(operand.get().getLoc()) << "allocated here"; 39973c3dff1SAlex Zinenko for (Operation *d : deleters.getOps()) { 40073c3dff1SAlex Zinenko diag.attachNote(d->getLoc()) << "freed here"; 40173c3dff1SAlex Zinenko } 40273c3dff1SAlex Zinenko } 40373c3dff1SAlex Zinenko }); 40473c3dff1SAlex Zinenko } 40573c3dff1SAlex Zinenko }; 40673c3dff1SAlex Zinenko 40773c3dff1SAlex Zinenko } // namespace 408039b969bSMichele Scuttari 409