| //===- DialectConversion.cpp - MLIR dialect conversion generic pass -------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "mlir/Transforms/DialectConversion.h" | |
| #include "mlir/Config/mlir-config.h" | |
| #include "mlir/IR/Block.h" | |
| #include "mlir/IR/Builders.h" | |
| #include "mlir/IR/BuiltinOps.h" | |
| #include "mlir/IR/Dominance.h" | |
| #include "mlir/IR/IRMapping.h" | |
| #include "mlir/IR/Iterators.h" | |
| #include "mlir/IR/Operation.h" | |
| #include "mlir/Interfaces/FunctionInterfaces.h" | |
| #include "mlir/Rewrite/PatternApplicator.h" | |
| #include "llvm/ADT/ScopeExit.h" | |
| #include "llvm/ADT/SmallPtrSet.h" | |
| #include "llvm/Support/Debug.h" | |
| #include "llvm/Support/DebugLog.h" | |
| #include "llvm/Support/ErrorHandling.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| #include "llvm/Support/SaveAndRestore.h" | |
| #include "llvm/Support/ScopedPrinter.h" | |
| #include <optional> | |
| #include <utility> | |
| using namespace mlir; | |
| using namespace mlir::detail; | |
| #define DEBUG_TYPE "dialect-conversion" | |
| /// A utility function to log a successful result for the given reason. | |
| template <typename... Args> | |
| static void logSuccess(llvm::ScopedPrinter &os, StringRef fmt, Args &&...args) { | |
| LLVM_DEBUG({ | |
| os.unindent(); | |
| os.startLine() << "} -> SUCCESS"; | |
| if (!fmt.empty()) | |
| os.getOStream() << " : " | |
| << llvm::formatv(fmt.data(), std::forward<Args>(args)...); | |
| os.getOStream() << "\n"; | |
| }); | |
| } | |
| /// A utility function to log a failure result for the given reason. | |
| template <typename... Args> | |
| static void logFailure(llvm::ScopedPrinter &os, StringRef fmt, Args &&...args) { | |
| LLVM_DEBUG({ | |
| os.unindent(); | |
| os.startLine() << "} -> FAILURE : " | |
| << llvm::formatv(fmt.data(), std::forward<Args>(args)...) | |
| << "\n"; | |
| }); | |
| } | |
| /// Helper function that computes an insertion point where the given value is | |
| /// defined and can be used without a dominance violation. | |
| static OpBuilder::InsertPoint computeInsertPoint(Value value) { | |
| Block *insertBlock = value.getParentBlock(); | |
| Block::iterator insertPt = insertBlock->begin(); | |
| if (OpResult inputRes = dyn_cast<OpResult>(value)) | |
| insertPt = ++inputRes.getOwner()->getIterator(); | |
| return OpBuilder::InsertPoint(insertBlock, insertPt); | |
| } | |
| /// Helper function that computes an insertion point where the given values are | |
| /// defined and can be used without a dominance violation. | |
| static OpBuilder::InsertPoint computeInsertPoint(ArrayRef<Value> vals) { | |
| assert(!vals.empty() && "expected at least one value"); | |
| DominanceInfo domInfo; | |
| OpBuilder::InsertPoint pt = computeInsertPoint(vals.front()); | |
| for (Value v : vals.drop_front()) { | |
| // Choose the "later" insertion point. | |
| OpBuilder::InsertPoint nextPt = computeInsertPoint(v); | |
| if (domInfo.dominates(pt.getBlock(), pt.getPoint(), nextPt.getBlock(), | |
| nextPt.getPoint())) { | |
| // pt is before nextPt => choose nextPt. | |
| pt = nextPt; | |
| } else { | |
| #ifndef NDEBUG | |
| // nextPt should be before pt => choose pt. | |
| // If pt, nextPt are no dominance relationship, then there is no valid | |
| // insertion point at which all given values are defined. | |
| bool dom = domInfo.dominates(nextPt.getBlock(), nextPt.getPoint(), | |
| pt.getBlock(), pt.getPoint()); | |
| assert(dom && "unable to find valid insertion point"); | |
| #endif // NDEBUG | |
| } | |
| } | |
| return pt; | |
| } | |
| namespace { | |
| enum OpConversionMode { | |
| /// In this mode, the conversion will ignore failed conversions to allow | |
| /// illegal operations to co-exist in the IR. | |
| Partial, | |
| /// In this mode, all operations must be legal for the given target for the | |
| /// conversion to succeed. | |
| Full, | |
| /// In this mode, operations are analyzed for legality. No actual rewrites are | |
| /// applied to the operations on success. | |
| Analysis, | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // ConversionValueMapping | |
| //===----------------------------------------------------------------------===// | |
| /// A vector of SSA values, optimized for the most common case of a single | |
| /// value. | |
| using ValueVector = SmallVector<Value, 1>; | |
| namespace { | |
| /// Helper class to make it possible to use `ValueVector` as a key in DenseMap. | |
| struct ValueVectorMapInfo { | |
| static ValueVector getEmptyKey() { return ValueVector{Value()}; } | |
| static ValueVector getTombstoneKey() { return ValueVector{Value(), Value()}; } | |
| static ::llvm::hash_code getHashValue(const ValueVector &val) { | |
| return ::llvm::hash_combine_range(val); | |
| } | |
| static bool isEqual(const ValueVector &LHS, const ValueVector &RHS) { | |
| return LHS == RHS; | |
| } | |
| }; | |
| /// This class wraps a IRMapping to provide recursive lookup | |
| /// functionality, i.e. we will traverse if the mapped value also has a mapping. | |
| struct ConversionValueMapping { | |
| /// Return "true" if an SSA value is mapped to the given value. May return | |
| /// false positives. | |
| bool isMappedTo(Value value) const { return mappedTo.contains(value); } | |
| /// Lookup a value in the mapping. | |
| ValueVector lookup(const ValueVector &from) const; | |
| template <typename T> | |
| struct IsValueVector : std::is_same<std::decay_t<T>, ValueVector> {}; | |
| /// Map a value vector to the one provided. | |
| template <typename OldVal, typename NewVal> | |
| std::enable_if_t<IsValueVector<OldVal>::value && IsValueVector<NewVal>::value> | |
| map(OldVal &&oldVal, NewVal &&newVal) { | |
| LLVM_DEBUG({ | |
| ValueVector next(newVal); | |
| while (true) { | |
| assert(next != oldVal && "inserting cyclic mapping"); | |
| auto it = mapping.find(next); | |
| if (it == mapping.end()) | |
| break; | |
| next = it->second; | |
| } | |
| }); | |
| mappedTo.insert_range(newVal); | |
| mapping[std::forward<OldVal>(oldVal)] = std::forward<NewVal>(newVal); | |
| } | |
| /// Map a value vector or single value to the one provided. | |
| template <typename OldVal, typename NewVal> | |
| std::enable_if_t<!IsValueVector<OldVal>::value || | |
| !IsValueVector<NewVal>::value> | |
| map(OldVal &&oldVal, NewVal &&newVal) { | |
| if constexpr (IsValueVector<OldVal>{}) { | |
| map(std::forward<OldVal>(oldVal), ValueVector{newVal}); | |
| } else if constexpr (IsValueVector<NewVal>{}) { | |
| map(ValueVector{oldVal}, std::forward<NewVal>(newVal)); | |
| } else { | |
| map(ValueVector{oldVal}, ValueVector{newVal}); | |
| } | |
| } | |
| void map(Value oldVal, SmallVector<Value> &&newVal) { | |
| map(ValueVector{oldVal}, ValueVector(std::move(newVal))); | |
| } | |
| /// Drop the last mapping for the given values. | |
| void erase(const ValueVector &value) { mapping.erase(value); } | |
| private: | |
| /// Current value mappings. | |
| DenseMap<ValueVector, ValueVector, ValueVectorMapInfo> mapping; | |
| /// All SSA values that are mapped to. May contain false positives. | |
| DenseSet<Value> mappedTo; | |
| }; | |
| } // namespace | |
| /// Marker attribute for pure type conversions. I.e., mappings whose only | |
| /// purpose is to resolve a type mismatch. (In contrast, mappings that point to | |
| /// the replacement values of a "replaceOp" call, etc., are not pure type | |
| /// conversions.) | |
| static const StringRef kPureTypeConversionMarker = "__pure_type_conversion__"; | |
| /// Return the operation that defines all values in the vector. Return nullptr | |
| /// if the values are not defined by the same operation. | |
| static Operation *getCommonDefiningOp(const ValueVector &values) { | |
| assert(!values.empty() && "expected non-empty value vector"); | |
| Operation *op = values.front().getDefiningOp(); | |
| for (Value v : llvm::drop_begin(values)) { | |
| if (v.getDefiningOp() != op) | |
| return nullptr; | |
| } | |
| return op; | |
| } | |
| /// A vector of values is a pure type conversion if all values are defined by | |
| /// the same operation and the operation has the `kPureTypeConversionMarker` | |
| /// attribute. | |
| static bool isPureTypeConversion(const ValueVector &values) { | |
| assert(!values.empty() && "expected non-empty value vector"); | |
| Operation *op = getCommonDefiningOp(values); | |
| return op && op->hasAttr(kPureTypeConversionMarker); | |
| } | |
| ValueVector ConversionValueMapping::lookup(const ValueVector &from) const { | |
| auto it = mapping.find(from); | |
| if (it == mapping.end()) { | |
| // No mapping found: The lookup stops here. | |
| return {}; | |
| } | |
| return it->second; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Rewriter and Translation State | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| /// This class contains a snapshot of the current conversion rewriter state. | |
| /// This is useful when saving and undoing a set of rewrites. | |
| struct RewriterState { | |
| RewriterState(unsigned numRewrites, unsigned numIgnoredOperations, | |
| unsigned numReplacedOps) | |
| : numRewrites(numRewrites), numIgnoredOperations(numIgnoredOperations), | |
| numReplacedOps(numReplacedOps) {} | |
| /// The current number of rewrites performed. | |
| unsigned numRewrites; | |
| /// The current number of ignored operations. | |
| unsigned numIgnoredOperations; | |
| /// The current number of replaced ops that are scheduled for erasure. | |
| unsigned numReplacedOps; | |
| }; | |
| //===----------------------------------------------------------------------===// | |
| // IR rewrites | |
| //===----------------------------------------------------------------------===// | |
| static void notifyIRErased(RewriterBase::Listener *listener, Operation &op); | |
| /// Notify the listener that the given block and its contents are being erased. | |
| static void notifyIRErased(RewriterBase::Listener *listener, Block &b) { | |
| for (Operation &op : b) | |
| notifyIRErased(listener, op); | |
| listener->notifyBlockErased(&b); | |
| } | |
| /// Notify the listener that the given operation and its contents are being | |
| /// erased. | |
| static void notifyIRErased(RewriterBase::Listener *listener, Operation &op) { | |
| for (Region &r : op.getRegions()) { | |
| for (Block &b : r) { | |
| notifyIRErased(listener, b); | |
| } | |
| } | |
| listener->notifyOperationErased(&op); | |
| } | |
| /// An IR rewrite that can be committed (upon success) or rolled back (upon | |
| /// failure). | |
| /// | |
| /// The dialect conversion keeps track of IR modifications (requested by the | |
| /// user through the rewriter API) in `IRRewrite` objects. Some kind of rewrites | |
| /// are directly applied to the IR as the rewriter API is used, some are applied | |
| /// partially, and some are delayed until the `IRRewrite` objects are committed. | |
| class IRRewrite { | |
| public: | |
| /// The kind of the rewrite. Rewrites can be undone if the conversion fails. | |
| /// Enum values are ordered, so that they can be used in `classof`: first all | |
| /// block rewrites, then all operation rewrites. | |
| enum class Kind { | |
| // Block rewrites | |
| CreateBlock, | |
| EraseBlock, | |
| InlineBlock, | |
| MoveBlock, | |
| BlockTypeConversion, | |
| // Operation rewrites | |
| MoveOperation, | |
| ModifyOperation, | |
| ReplaceOperation, | |
| CreateOperation, | |
| UnresolvedMaterialization, | |
| // Value rewrites | |
| ReplaceValue | |
| }; | |
| virtual ~IRRewrite() = default; | |
| /// Roll back the rewrite. Operations may be erased during rollback. | |
| virtual void rollback() = 0; | |
| /// Commit the rewrite. At this point, it is certain that the dialect | |
| /// conversion will succeed. All IR modifications, except for operation/block | |
| /// erasure, must be performed through the given rewriter. | |
| /// | |
| /// Instead of erasing operations/blocks, they should merely be unlinked | |
| /// commit phase and finally be erased during the cleanup phase. This is | |
| /// because internal dialect conversion state (such as `mapping`) may still | |
| /// be using them. | |
| /// | |
| /// Any IR modification that was already performed before the commit phase | |
| /// (e.g., insertion of an op) must be communicated to the listener that may | |
| /// be attached to the given rewriter. | |
| virtual void commit(RewriterBase &rewriter) {} | |
| /// Cleanup operations/blocks. Cleanup is called after commit. | |
| virtual void cleanup(RewriterBase &rewriter) {} | |
| Kind getKind() const { return kind; } | |
| static bool classof(const IRRewrite *rewrite) { return true; } | |
| protected: | |
| IRRewrite(Kind kind, ConversionPatternRewriterImpl &rewriterImpl) | |
| : kind(kind), rewriterImpl(rewriterImpl) {} | |
| const ConversionConfig &getConfig() const; | |
| const Kind kind; | |
| ConversionPatternRewriterImpl &rewriterImpl; | |
| }; | |
| /// A block rewrite. | |
| class BlockRewrite : public IRRewrite { | |
| public: | |
| /// Return the block that this rewrite operates on. | |
| Block *getBlock() const { return block; } | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() >= Kind::CreateBlock && | |
| rewrite->getKind() <= Kind::BlockTypeConversion; | |
| } | |
| protected: | |
| BlockRewrite(Kind kind, ConversionPatternRewriterImpl &rewriterImpl, | |
| Block *block) | |
| : IRRewrite(kind, rewriterImpl), block(block) {} | |
| // The block that this rewrite operates on. | |
| Block *block; | |
| }; | |
| /// A value rewrite. | |
| class ValueRewrite : public IRRewrite { | |
| public: | |
| /// Return the value that this rewrite operates on. | |
| Value getValue() const { return value; } | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::ReplaceValue; | |
| } | |
| protected: | |
| ValueRewrite(Kind kind, ConversionPatternRewriterImpl &rewriterImpl, | |
| Value value) | |
| : IRRewrite(kind, rewriterImpl), value(value) {} | |
| // The value that this rewrite operates on. | |
| Value value; | |
| }; | |
| /// Creation of a block. Block creations are immediately reflected in the IR. | |
| /// There is no extra work to commit the rewrite. During rollback, the newly | |
| /// created block is erased. | |
| class CreateBlockRewrite : public BlockRewrite { | |
| public: | |
| CreateBlockRewrite(ConversionPatternRewriterImpl &rewriterImpl, Block *block) | |
| : BlockRewrite(Kind::CreateBlock, rewriterImpl, block) {} | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::CreateBlock; | |
| } | |
| void commit(RewriterBase &rewriter) override { | |
| // The block was already created and inserted. Just inform the listener. | |
| if (auto *listener = rewriter.getListener()) | |
| listener->notifyBlockInserted(block, /*previous=*/{}, /*previousIt=*/{}); | |
| } | |
| void rollback() override { | |
| // Unlink all of the operations within this block, they will be deleted | |
| // separately. | |
| auto &blockOps = block->getOperations(); | |
| while (!blockOps.empty()) | |
| blockOps.remove(blockOps.begin()); | |
| block->dropAllUses(); | |
| if (block->getParent()) | |
| block->erase(); | |
| else | |
| delete block; | |
| } | |
| }; | |
| /// Erasure of a block. Block erasures are partially reflected in the IR. Erased | |
| /// blocks are immediately unlinked, but only erased during cleanup. This makes | |
| /// it easier to rollback a block erasure: the block is simply inserted into its | |
| /// original location. | |
| class EraseBlockRewrite : public BlockRewrite { | |
| public: | |
| EraseBlockRewrite(ConversionPatternRewriterImpl &rewriterImpl, Block *block) | |
| : BlockRewrite(Kind::EraseBlock, rewriterImpl, block), | |
| region(block->getParent()), insertBeforeBlock(block->getNextNode()) {} | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::EraseBlock; | |
| } | |
| ~EraseBlockRewrite() override { | |
| assert(!block && | |
| "rewrite was neither rolled back nor committed/cleaned up"); | |
| } | |
| void rollback() override { | |
| // The block (owned by this rewrite) was not actually erased yet. It was | |
| // just unlinked. Put it back into its original position. | |
| assert(block && "expected block"); | |
| auto &blockList = region->getBlocks(); | |
| Region::iterator before = insertBeforeBlock | |
| ? Region::iterator(insertBeforeBlock) | |
| : blockList.end(); | |
| blockList.insert(before, block); | |
| block = nullptr; | |
| } | |
| void commit(RewriterBase &rewriter) override { | |
| assert(block && "expected block"); | |
| // Notify the listener that the block and its contents are being erased. | |
| if (auto *listener = | |
| dyn_cast_or_null<RewriterBase::Listener>(rewriter.getListener())) | |
| notifyIRErased(listener, *block); | |
| } | |
| void cleanup(RewriterBase &rewriter) override { | |
| // Erase the contents of the block. | |
| for (auto &op : llvm::make_early_inc_range(llvm::reverse(*block))) | |
| rewriter.eraseOp(&op); | |
| assert(block->empty() && "expected empty block"); | |
| // Erase the block. | |
| block->dropAllDefinedValueUses(); | |
| delete block; | |
| block = nullptr; | |
| } | |
| private: | |
| // The region in which this block was previously contained. | |
| Region *region; | |
| // The original successor of this block before it was unlinked. "nullptr" if | |
| // this block was the only block in the region. | |
| Block *insertBeforeBlock; | |
| }; | |
| /// Inlining of a block. This rewrite is immediately reflected in the IR. | |
| /// Note: This rewrite represents only the inlining of the operations. The | |
| /// erasure of the inlined block is a separate rewrite. | |
| class InlineBlockRewrite : public BlockRewrite { | |
| public: | |
| InlineBlockRewrite(ConversionPatternRewriterImpl &rewriterImpl, Block *block, | |
| Block *sourceBlock, Block::iterator before) | |
| : BlockRewrite(Kind::InlineBlock, rewriterImpl, block), | |
| sourceBlock(sourceBlock), | |
| firstInlinedInst(sourceBlock->empty() ? nullptr | |
| : &sourceBlock->front()), | |
| lastInlinedInst(sourceBlock->empty() ? nullptr : &sourceBlock->back()) { | |
| // If a listener is attached to the dialect conversion, ops must be moved | |
| // one-by-one. When they are moved in bulk, notifications cannot be sent | |
| // because the ops that used to be in the source block at the time of the | |
| // inlining (before the "commit" phase) are unknown at the time when | |
| // notifications are sent (which is during the "commit" phase). | |
| assert(!getConfig().listener && | |
| "InlineBlockRewrite not supported if listener is attached"); | |
| } | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::InlineBlock; | |
| } | |
| void rollback() override { | |
| // Put the operations from the destination block (owned by the rewrite) | |
| // back into the source block. | |
| if (firstInlinedInst) { | |
| assert(lastInlinedInst && "expected operation"); | |
| sourceBlock->getOperations().splice(sourceBlock->begin(), | |
| block->getOperations(), | |
| Block::iterator(firstInlinedInst), | |
| ++Block::iterator(lastInlinedInst)); | |
| } | |
| } | |
| private: | |
| // The block that originally contained the operations. | |
| Block *sourceBlock; | |
| // The first inlined operation. | |
| Operation *firstInlinedInst; | |
| // The last inlined operation. | |
| Operation *lastInlinedInst; | |
| }; | |
| /// Moving of a block. This rewrite is immediately reflected in the IR. | |
| class MoveBlockRewrite : public BlockRewrite { | |
| public: | |
| MoveBlockRewrite(ConversionPatternRewriterImpl &rewriterImpl, Block *block, | |
| Region *previousRegion, Region::iterator previousIt) | |
| : BlockRewrite(Kind::MoveBlock, rewriterImpl, block), | |
| region(previousRegion), | |
| insertBeforeBlock(previousIt == previousRegion->end() ? nullptr | |
| : &*previousIt) {} | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::MoveBlock; | |
| } | |
| void commit(RewriterBase &rewriter) override { | |
| // The block was already moved. Just inform the listener. | |
| if (auto *listener = rewriter.getListener()) { | |
| // Note: `previousIt` cannot be passed because this is a delayed | |
| // notification and iterators into past IR state cannot be represented. | |
| listener->notifyBlockInserted(block, /*previous=*/region, | |
| /*previousIt=*/{}); | |
| } | |
| } | |
| void rollback() override { | |
| // Move the block back to its original position. | |
| Region::iterator before = | |
| insertBeforeBlock ? Region::iterator(insertBeforeBlock) : region->end(); | |
| if (Region *currentParent = block->getParent()) { | |
| // Block is still in a region, use cheap splice to move it back. | |
| region->getBlocks().splice(before, currentParent->getBlocks(), block); | |
| return; | |
| } | |
| // Block was orphaned by a prior rollback, can't splice. | |
| region->getBlocks().insert(before, block); | |
| } | |
| private: | |
| // The region in which this block was previously contained. | |
| Region *region; | |
| // The original successor of this block before it was moved. "nullptr" if | |
| // this block was the only block in the region. | |
| Block *insertBeforeBlock; | |
| }; | |
| /// Block type conversion. This rewrite is partially reflected in the IR. | |
| class BlockTypeConversionRewrite : public BlockRewrite { | |
| public: | |
| BlockTypeConversionRewrite(ConversionPatternRewriterImpl &rewriterImpl, | |
| Block *origBlock, Block *newBlock) | |
| : BlockRewrite(Kind::BlockTypeConversion, rewriterImpl, origBlock), | |
| newBlock(newBlock) {} | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::BlockTypeConversion; | |
| } | |
| Block *getOrigBlock() const { return block; } | |
| Block *getNewBlock() const { return newBlock; } | |
| void commit(RewriterBase &rewriter) override; | |
| void rollback() override; | |
| private: | |
| /// The new block that was created as part of this signature conversion. | |
| Block *newBlock; | |
| }; | |
| /// Replacing a value. This rewrite is not immediately reflected in the | |
| /// IR. An internal IR mapping is updated, but the actual replacement is delayed | |
| /// until the rewrite is committed. | |
| class ReplaceValueRewrite : public ValueRewrite { | |
| public: | |
| ReplaceValueRewrite(ConversionPatternRewriterImpl &rewriterImpl, Value value, | |
| const TypeConverter *converter) | |
| : ValueRewrite(Kind::ReplaceValue, rewriterImpl, value), | |
| converter(converter) {} | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::ReplaceValue; | |
| } | |
| void commit(RewriterBase &rewriter) override; | |
| void rollback() override; | |
| private: | |
| /// The current type converter when the value was replaced. | |
| const TypeConverter *converter; | |
| }; | |
| /// An operation rewrite. | |
| class OperationRewrite : public IRRewrite { | |
| public: | |
| /// Return the operation that this rewrite operates on. | |
| Operation *getOperation() const { return op; } | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() >= Kind::MoveOperation && | |
| rewrite->getKind() <= Kind::UnresolvedMaterialization; | |
| } | |
| protected: | |
| OperationRewrite(Kind kind, ConversionPatternRewriterImpl &rewriterImpl, | |
| Operation *op) | |
| : IRRewrite(kind, rewriterImpl), op(op) {} | |
| // The operation that this rewrite operates on. | |
| Operation *op; | |
| }; | |
| /// Moving of an operation. This rewrite is immediately reflected in the IR. | |
| class MoveOperationRewrite : public OperationRewrite { | |
| public: | |
| MoveOperationRewrite(ConversionPatternRewriterImpl &rewriterImpl, | |
| Operation *op, OpBuilder::InsertPoint previous) | |
| : OperationRewrite(Kind::MoveOperation, rewriterImpl, op), | |
| block(previous.getBlock()), | |
| insertBeforeOp(previous.getPoint() == previous.getBlock()->end() | |
| ? nullptr | |
| : &*previous.getPoint()) {} | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::MoveOperation; | |
| } | |
| void commit(RewriterBase &rewriter) override { | |
| // The operation was already moved. Just inform the listener. | |
| if (auto *listener = rewriter.getListener()) { | |
| // Note: `previousIt` cannot be passed because this is a delayed | |
| // notification and iterators into past IR state cannot be represented. | |
| listener->notifyOperationInserted( | |
| op, /*previous=*/OpBuilder::InsertPoint(/*insertBlock=*/block, | |
| /*insertPt=*/{})); | |
| } | |
| } | |
| void rollback() override { | |
| // Move the operation back to its original position. | |
| Block::iterator before = | |
| insertBeforeOp ? Block::iterator(insertBeforeOp) : block->end(); | |
| block->getOperations().splice(before, op->getBlock()->getOperations(), op); | |
| } | |
| private: | |
| // The block in which this operation was previously contained. | |
| Block *block; | |
| // The original successor of this operation before it was moved. "nullptr" | |
| // if this operation was the only operation in the region. | |
| Operation *insertBeforeOp; | |
| }; | |
| /// In-place modification of an op. This rewrite is immediately reflected in | |
| /// the IR. The previous state of the operation is stored in this object. | |
| class ModifyOperationRewrite : public OperationRewrite { | |
| public: | |
| ModifyOperationRewrite(ConversionPatternRewriterImpl &rewriterImpl, | |
| Operation *op) | |
| : OperationRewrite(Kind::ModifyOperation, rewriterImpl, op), | |
| name(op->getName()), loc(op->getLoc()), attrs(op->getAttrDictionary()), | |
| operands(op->operand_begin(), op->operand_end()), | |
| successors(op->successor_begin(), op->successor_end()) { | |
| if (OpaqueProperties prop = op->getPropertiesStorage()) { | |
| // Make a copy of the properties. | |
| propertiesStorage = operator new(op->getPropertiesStorageSize()); | |
| OpaqueProperties propCopy(propertiesStorage); | |
| name.initOpProperties(propCopy, /*init=*/prop); | |
| } | |
| } | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::ModifyOperation; | |
| } | |
| ~ModifyOperationRewrite() override { | |
| assert(!propertiesStorage && | |
| "rewrite was neither committed nor rolled back"); | |
| } | |
| void commit(RewriterBase &rewriter) override { | |
| // Notify the listener that the operation was modified in-place. | |
| if (auto *listener = | |
| dyn_cast_or_null<RewriterBase::Listener>(rewriter.getListener())) | |
| listener->notifyOperationModified(op); | |
| if (propertiesStorage) { | |
| OpaqueProperties propCopy(propertiesStorage); | |
| // Note: The operation may have been erased in the mean time, so | |
| // OperationName must be stored in this object. | |
| name.destroyOpProperties(propCopy); | |
| operator delete(propertiesStorage); | |
| propertiesStorage = nullptr; | |
| } | |
| } | |
| void rollback() override { | |
| op->setLoc(loc); | |
| op->setAttrs(attrs); | |
| op->setOperands(operands); | |
| for (const auto &it : llvm::enumerate(successors)) | |
| op->setSuccessor(it.value(), it.index()); | |
| if (propertiesStorage) { | |
| OpaqueProperties propCopy(propertiesStorage); | |
| op->copyProperties(propCopy); | |
| name.destroyOpProperties(propCopy); | |
| operator delete(propertiesStorage); | |
| propertiesStorage = nullptr; | |
| } | |
| } | |
| private: | |
| OperationName name; | |
| LocationAttr loc; | |
| DictionaryAttr attrs; | |
| SmallVector<Value, 8> operands; | |
| SmallVector<Block *, 2> successors; | |
| void *propertiesStorage = nullptr; | |
| }; | |
| /// Replacing an operation. Erasing an operation is treated as a special case | |
| /// with "null" replacements. This rewrite is not immediately reflected in the | |
| /// IR. An internal IR mapping is updated, but values are not replaced and the | |
| /// original op is not erased until the rewrite is committed. | |
| class ReplaceOperationRewrite : public OperationRewrite { | |
| public: | |
| ReplaceOperationRewrite(ConversionPatternRewriterImpl &rewriterImpl, | |
| Operation *op, const TypeConverter *converter) | |
| : OperationRewrite(Kind::ReplaceOperation, rewriterImpl, op), | |
| converter(converter) {} | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::ReplaceOperation; | |
| } | |
| void commit(RewriterBase &rewriter) override; | |
| void rollback() override; | |
| void cleanup(RewriterBase &rewriter) override; | |
| private: | |
| /// An optional type converter that can be used to materialize conversions | |
| /// between the new and old values if necessary. | |
| const TypeConverter *converter; | |
| }; | |
| class CreateOperationRewrite : public OperationRewrite { | |
| public: | |
| CreateOperationRewrite(ConversionPatternRewriterImpl &rewriterImpl, | |
| Operation *op) | |
| : OperationRewrite(Kind::CreateOperation, rewriterImpl, op) {} | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::CreateOperation; | |
| } | |
| void commit(RewriterBase &rewriter) override { | |
| // The operation was already created and inserted. Just inform the listener. | |
| if (auto *listener = rewriter.getListener()) | |
| listener->notifyOperationInserted(op, /*previous=*/{}); | |
| } | |
| void rollback() override; | |
| }; | |
| /// The type of materialization. | |
| enum MaterializationKind { | |
| /// This materialization materializes a conversion from an illegal type to a | |
| /// legal one. | |
| Target, | |
| /// This materialization materializes a conversion from a legal type back to | |
| /// an illegal one. | |
| Source | |
| }; | |
| /// Helper class that stores metadata about an unresolved materialization. | |
| class UnresolvedMaterializationInfo { | |
| public: | |
| UnresolvedMaterializationInfo() = default; | |
| UnresolvedMaterializationInfo(const TypeConverter *converter, | |
| MaterializationKind kind, Type originalType) | |
| : converterAndKind(converter, kind), originalType(originalType) {} | |
| /// Return the type converter of this materialization (which may be null). | |
| const TypeConverter *getConverter() const { | |
| return converterAndKind.getPointer(); | |
| } | |
| /// Return the kind of this materialization. | |
| MaterializationKind getMaterializationKind() const { | |
| return converterAndKind.getInt(); | |
| } | |
| /// Return the original type of the SSA value. | |
| Type getOriginalType() const { return originalType; } | |
| private: | |
| /// The corresponding type converter to use when resolving this | |
| /// materialization, and the kind of this materialization. | |
| llvm::PointerIntPair<const TypeConverter *, 2, MaterializationKind> | |
| converterAndKind; | |
| /// The original type of the SSA value. Only used for target | |
| /// materializations. | |
| Type originalType; | |
| }; | |
| /// An unresolved materialization, i.e., a "builtin.unrealized_conversion_cast" | |
| /// op. Unresolved materializations fold away or are replaced with | |
| /// source/target materializations at the end of the dialect conversion. | |
| class UnresolvedMaterializationRewrite : public OperationRewrite { | |
| public: | |
| UnresolvedMaterializationRewrite(ConversionPatternRewriterImpl &rewriterImpl, | |
| UnrealizedConversionCastOp op, | |
| ValueVector mappedValues) | |
| : OperationRewrite(Kind::UnresolvedMaterialization, rewriterImpl, op), | |
| mappedValues(std::move(mappedValues)) {} | |
| static bool classof(const IRRewrite *rewrite) { | |
| return rewrite->getKind() == Kind::UnresolvedMaterialization; | |
| } | |
| void rollback() override; | |
| UnrealizedConversionCastOp getOperation() const { | |
| return cast<UnrealizedConversionCastOp>(op); | |
| } | |
| private: | |
| /// The values in the conversion value mapping that are being replaced by the | |
| /// results of this unresolved materialization. | |
| ValueVector mappedValues; | |
| }; | |
| } // namespace | |
| #if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS | |
| /// Return "true" if there is an operation rewrite that matches the specified | |
| /// rewrite type and operation among the given rewrites. | |
| template <typename RewriteTy, typename R> | |
| static bool hasRewrite(R &&rewrites, Operation *op) { | |
| return any_of(std::forward<R>(rewrites), [&](auto &rewrite) { | |
| auto *rewriteTy = dyn_cast<RewriteTy>(rewrite.get()); | |
| return rewriteTy && rewriteTy->getOperation() == op; | |
| }); | |
| } | |
| /// Return "true" if there is a block rewrite that matches the specified | |
| /// rewrite type and block among the given rewrites. | |
| template <typename RewriteTy, typename R> | |
| static bool hasRewrite(R &&rewrites, Block *block) { | |
| return any_of(std::forward<R>(rewrites), [&](auto &rewrite) { | |
| auto *rewriteTy = dyn_cast<RewriteTy>(rewrite.get()); | |
| return rewriteTy && rewriteTy->getBlock() == block; | |
| }); | |
| } | |
| #endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS | |
| //===----------------------------------------------------------------------===// | |
| // ConversionPatternRewriterImpl | |
| //===----------------------------------------------------------------------===// | |
| namespace mlir { | |
| namespace detail { | |
| struct ConversionPatternRewriterImpl : public RewriterBase::Listener { | |
| explicit ConversionPatternRewriterImpl(ConversionPatternRewriter &rewriter, | |
| const ConversionConfig &config, | |
| OperationConverter &opConverter) | |
| : rewriter(rewriter), config(config), opConverter(opConverter), | |
| notifyingRewriter(rewriter.getContext(), config.listener) {} | |
| //===--------------------------------------------------------------------===// | |
| // State Management | |
| //===--------------------------------------------------------------------===// | |
| /// Return the current state of the rewriter. | |
| RewriterState getCurrentState(); | |
| /// Apply all requested operation rewrites. This method is invoked when the | |
| /// conversion process succeeds. | |
| void applyRewrites(); | |
| /// Reset the state of the rewriter to a previously saved point. Optionally, | |
| /// the name of the pattern that triggered the rollback can specified for | |
| /// debugging purposes. | |
| void resetState(RewriterState state, StringRef patternName = ""); | |
| /// Append a rewrite. Rewrites are committed upon success and rolled back upon | |
| /// failure. | |
| template <typename RewriteTy, typename... Args> | |
| void appendRewrite(Args &&...args) { | |
| assert(config.allowPatternRollback && "appending rewrites is not allowed"); | |
| rewrites.push_back( | |
| std::make_unique<RewriteTy>(*this, std::forward<Args>(args)...)); | |
| } | |
| /// Undo the rewrites (motions, splits) one by one in reverse order until | |
| /// "numRewritesToKeep" rewrites remains. Optionally, the name of the pattern | |
| /// that triggered the rollback can specified for debugging purposes. | |
| void undoRewrites(unsigned numRewritesToKeep = 0, StringRef patternName = ""); | |
| /// Remap the given values to those with potentially different types. Returns | |
| /// success if the values could be remapped, failure otherwise. `valueDiagTag` | |
| /// is the tag used when describing a value within a diagnostic, e.g. | |
| /// "operand". | |
| LogicalResult remapValues(StringRef valueDiagTag, | |
| std::optional<Location> inputLoc, ValueRange values, | |
| SmallVector<ValueVector> &remapped); | |
| /// Return "true" if the given operation is ignored, and does not need to be | |
| /// converted. | |
| bool isOpIgnored(Operation *op) const; | |
| /// Return "true" if the given operation was replaced or erased. | |
| bool wasOpReplaced(Operation *op) const; | |
| /// Lookup the most recently mapped values with the desired types in the | |
| /// mapping, taking into account only replacements. Perform a best-effort | |
| /// search for existing materializations with the desired types. | |
| /// | |
| /// If `skipPureTypeConversions` is "true", materializations that are pure | |
| /// type conversions are not considered. | |
| ValueVector lookupOrDefault(Value from, TypeRange desiredTypes = {}, | |
| bool skipPureTypeConversions = false) const; | |
| /// Lookup the given value within the map, or return an empty vector if the | |
| /// value is not mapped. If it is mapped, this follows the same behavior | |
| /// as `lookupOrDefault`. | |
| ValueVector lookupOrNull(Value from, TypeRange desiredTypes = {}) const; | |
| //===--------------------------------------------------------------------===// | |
| // IR Rewrites / Type Conversion | |
| //===--------------------------------------------------------------------===// | |
| /// Convert the types of block arguments within the given region. | |
| FailureOr<Block *> | |
| convertRegionTypes(Region *region, const TypeConverter &converter, | |
| TypeConverter::SignatureConversion *entryConversion); | |
| /// Apply the given signature conversion on the given block. The new block | |
| /// containing the updated signature is returned. If no conversions were | |
| /// necessary, e.g. if the block has no arguments, `block` is returned. | |
| /// `converter` is used to generate any necessary cast operations that | |
| /// translate between the origin argument types and those specified in the | |
| /// signature conversion. | |
| Block *applySignatureConversion( | |
| Block *block, const TypeConverter *converter, | |
| TypeConverter::SignatureConversion &signatureConversion); | |
| /// Replace the results of the given operation with the given values and | |
| /// erase the operation. | |
| /// | |
| /// There can be multiple replacement values for each result (1:N | |
| /// replacement). If the replacement values are empty, the respective result | |
| /// is dropped and a source materialization is built if the result still has | |
| /// uses. | |
| void replaceOp(Operation *op, SmallVector<SmallVector<Value>> &&newValues); | |
| /// Replace the uses of the given value with the given values. The specified | |
| /// converter is used to build materializations (if necessary). If `functor` | |
| /// is specified, only the uses that the functor returns "true" for are | |
| /// replaced. | |
| void replaceValueUses(Value from, ValueRange to, | |
| const TypeConverter *converter, | |
| function_ref<bool(OpOperand &)> functor = nullptr); | |
| /// Erase the given block and its contents. | |
| void eraseBlock(Block *block); | |
| /// Inline the source block into the destination block before the given | |
| /// iterator. | |
| void inlineBlockBefore(Block *source, Block *dest, Block::iterator before); | |
| //===--------------------------------------------------------------------===// | |
| // Materializations | |
| //===--------------------------------------------------------------------===// | |
| /// Build an unresolved materialization operation given a range of output | |
| /// types and a list of input operands. Returns the inputs if they their | |
| /// types match the output types. | |
| /// | |
| /// If a cast op was built, it can optionally be returned with the `castOp` | |
| /// output argument. | |
| /// | |
| /// If `valuesToMap` is set to a non-null Value, then that value is mapped to | |
| /// the results of the unresolved materialization in the conversion value | |
| /// mapping. | |
| /// | |
| /// If `isPureTypeConversion` is "true", the materialization is created only | |
| /// to resolve a type mismatch. That means it is not a regular value | |
| /// replacement issued by the user. (Replacement values that are created | |
| /// "out of thin air" appear like unresolved materializations because they are | |
| /// unrealized_conversion_cast ops. However, they must be treated like | |
| /// regular value replacements.) | |
| ValueRange buildUnresolvedMaterialization( | |
| MaterializationKind kind, OpBuilder::InsertPoint ip, Location loc, | |
| ValueVector valuesToMap, ValueRange inputs, TypeRange outputTypes, | |
| Type originalType, const TypeConverter *converter, | |
| bool isPureTypeConversion = true); | |
| /// Find a replacement value for the given SSA value in the conversion value | |
| /// mapping. The replacement value must have the same type as the given SSA | |
| /// value. If there is no replacement value with the correct type, find the | |
| /// latest replacement value (regardless of the type) and build a source | |
| /// materialization. | |
| Value findOrBuildReplacementValue(Value value, | |
| const TypeConverter *converter); | |
| //===--------------------------------------------------------------------===// | |
| // Rewriter Notification Hooks | |
| //===--------------------------------------------------------------------===// | |
| //// Notifies that an op was inserted. | |
| void notifyOperationInserted(Operation *op, | |
| OpBuilder::InsertPoint previous) override; | |
| /// Notifies that a block was inserted. | |
| void notifyBlockInserted(Block *block, Region *previous, | |
| Region::iterator previousIt) override; | |
| /// Notifies that a pattern match failed for the given reason. | |
| void | |
| notifyMatchFailure(Location loc, | |
| function_ref<void(Diagnostic &)> reasonCallback) override; | |
| //===--------------------------------------------------------------------===// | |
| // IR Erasure | |
| //===--------------------------------------------------------------------===// | |
| /// A rewriter that keeps track of erased ops and blocks. It ensures that no | |
| /// operation or block is erased multiple times. This rewriter assumes that | |
| /// no new IR is created between calls to `eraseOp`/`eraseBlock`. | |
| struct SingleEraseRewriter : public RewriterBase, RewriterBase::Listener { | |
| public: | |
| SingleEraseRewriter( | |
| MLIRContext *context, | |
| std::function<void(Operation *)> opErasedCallback = nullptr) | |
| : RewriterBase(context, /*listener=*/this), | |
| opErasedCallback(std::move(opErasedCallback)) {} | |
| /// Erase the given op (unless it was already erased). | |
| void eraseOp(Operation *op) override { | |
| if (wasErased(op)) | |
| return; | |
| op->dropAllUses(); | |
| RewriterBase::eraseOp(op); | |
| } | |
| /// Erase the given block (unless it was already erased). | |
| void eraseBlock(Block *block) override { | |
| if (wasErased(block)) | |
| return; | |
| assert(block->empty() && "expected empty block"); | |
| block->dropAllDefinedValueUses(); | |
| RewriterBase::eraseBlock(block); | |
| } | |
| bool wasErased(void *ptr) const { return erased.contains(ptr); } | |
| void notifyOperationErased(Operation *op) override { | |
| erased.insert(op); | |
| if (opErasedCallback) | |
| opErasedCallback(op); | |
| } | |
| void notifyBlockErased(Block *block) override { erased.insert(block); } | |
| private: | |
| /// Pointers to all erased operations and blocks. | |
| DenseSet<void *> erased; | |
| /// A callback that is invoked when an operation is erased. | |
| std::function<void(Operation *)> opErasedCallback; | |
| }; | |
| //===--------------------------------------------------------------------===// | |
| // State | |
| //===--------------------------------------------------------------------===// | |
| /// The rewriter that is used to perform the conversion. | |
| ConversionPatternRewriter &rewriter; | |
| // Mapping between replaced values that differ in type. This happens when | |
| // replacing a value with one of a different type. | |
| ConversionValueMapping mapping; | |
| /// Ordered list of block operations (creations, splits, motions). | |
| /// This vector is maintained only if `allowPatternRollback` is set to | |
| /// "true". Otherwise, all IR rewrites are materialized immediately and no | |
| /// bookkeeping is needed. | |
| SmallVector<std::unique_ptr<IRRewrite>> rewrites; | |
| /// A set of operations that should no longer be considered for legalization. | |
| /// E.g., ops that are recursively legal. Ops that were replaced/erased are | |
| /// tracked separately. | |
| SetVector<Operation *> ignoredOps; | |
| /// A set of operations that were replaced/erased. Such ops are not erased | |
| /// immediately but only when the dialect conversion succeeds. In the mean | |
| /// time, they should no longer be considered for legalization and any attempt | |
| /// to modify/access them is invalid rewriter API usage. | |
| SetVector<Operation *> replacedOps; | |
| /// A set of operations that were created by the current pattern. | |
| SetVector<Operation *> patternNewOps; | |
| /// A set of operations that were modified by the current pattern. | |
| SetVector<Operation *> patternModifiedOps; | |
| /// A list of unresolved materializations that were created by the current | |
| /// pattern. | |
| DenseSet<UnrealizedConversionCastOp> patternMaterializations; | |
| /// A mapping for looking up metadata of unresolved materializations. | |
| DenseMap<UnrealizedConversionCastOp, UnresolvedMaterializationInfo> | |
| unresolvedMaterializations; | |
| /// The current type converter, or nullptr if no type converter is currently | |
| /// active. | |
| const TypeConverter *currentTypeConverter = nullptr; | |
| /// A mapping of regions to type converters that should be used when | |
| /// converting the arguments of blocks within that region. | |
| DenseMap<Region *, const TypeConverter *> regionToConverter; | |
| /// Dialect conversion configuration. | |
| const ConversionConfig &config; | |
| /// The operation converter to use for recursive legalization. | |
| OperationConverter &opConverter; | |
| /// A set of erased operations. This set is utilized only if | |
| /// `allowPatternRollback` is set to "false". Conceptually, this set is | |
| /// similar to `replacedOps` (which is maintained when the flag is set to | |
| /// "true"). However, erasing from a DenseSet is more efficient than erasing | |
| /// from a SetVector. | |
| DenseSet<Operation *> erasedOps; | |
| /// A set of erased blocks. This set is utilized only if | |
| /// `allowPatternRollback` is set to "false". | |
| DenseSet<Block *> erasedBlocks; | |
| /// A rewriter that notifies the listener (if any) about all IR | |
| /// modifications. This rewriter is utilized only if `allowPatternRollback` | |
| /// is set to "false". If the flag is set to "true", the listener is notified | |
| /// with a separate mechanism (e.g., in `IRRewrite::commit`). | |
| IRRewriter notifyingRewriter; | |
| #ifndef NDEBUG | |
| /// A set of replaced values. This set is for debugging purposes only and it | |
| /// is maintained only if `allowPatternRollback` is set to "true". | |
| DenseSet<Value> replacedValues; | |
| /// A set of operations that have pending updates. This tracking isn't | |
| /// strictly necessary, and is thus only active during debug builds for extra | |
| /// verification. | |
| SmallPtrSet<Operation *, 1> pendingRootUpdates; | |
| /// A raw output stream used to prefix the debug log. | |
| llvm::impl::raw_ldbg_ostream os{(Twine("[") + DEBUG_TYPE + ":1] ").str(), | |
| llvm::dbgs()}; | |
| /// A logger used to emit diagnostics during the conversion process. | |
| llvm::ScopedPrinter logger{os}; | |
| std::string logPrefix; | |
| #endif | |
| }; | |
| } // namespace detail | |
| } // namespace mlir | |
| const ConversionConfig &IRRewrite::getConfig() const { | |
| return rewriterImpl.config; | |
| } | |
| void BlockTypeConversionRewrite::commit(RewriterBase &rewriter) { | |
| // Inform the listener about all IR modifications that have already taken | |
| // place: References to the original block have been replaced with the new | |
| // block. | |
| if (auto *listener = | |
| dyn_cast_or_null<RewriterBase::Listener>(rewriter.getListener())) | |
| for (Operation *op : getNewBlock()->getUsers()) | |
| listener->notifyOperationModified(op); | |
| } | |
| void BlockTypeConversionRewrite::rollback() { | |
| getNewBlock()->replaceAllUsesWith(getOrigBlock()); | |
| } | |
| /// Replace all uses of `from` with `repl`. | |
| static void | |
| performReplaceValue(RewriterBase &rewriter, Value from, Value repl, | |
| function_ref<bool(OpOperand &)> functor = nullptr) { | |
| if (isa<BlockArgument>(repl)) { | |
| // `repl` is a block argument. Directly replace all uses. | |
| if (functor) { | |
| rewriter.replaceUsesWithIf(from, repl, functor); | |
| } else { | |
| rewriter.replaceAllUsesWith(from, repl); | |
| } | |
| return; | |
| } | |
| // If the replacement value is an operation, only replace those uses that: | |
| // - are in a different block than the replacement operation, or | |
| // - are in the same block but after the replacement operation. | |
| // | |
| // Example: | |
| // ^bb0(%arg0: i32): | |
| // %0 = "consumer"(%arg0) : (i32) -> (i32) | |
| // "another_consumer"(%arg0) : (i32) -> () | |
| // | |
| // In the above example, replaceAllUsesWith(%arg0, %0) will replace the | |
| // use in "another_consumer" but not the use in "consumer". When using the | |
| // normal RewriterBase API, this would typically be done with | |
| // `replaceUsesWithIf` / `replaceAllUsesExcept`. However, that API is not | |
| // supported by the `ConversionPatternRewriter`. Due to the mapping mechanism | |
| // it cannot be supported efficiently with `allowPatternRollback` set to | |
| // "true". Therefore, the conversion driver is trying to be smart and replaces | |
| // only those uses that do not lead to a dominance violation. E.g., the | |
| // FuncToLLVM lowering (`restoreByValRefArgumentType`) relies on this | |
| // behavior. | |
| // | |
| // TODO: As we move more and more towards `allowPatternRollback` set to | |
| // "false", we should remove this special handling, in order to align the | |
| // `ConversionPatternRewriter` API with the normal `RewriterBase` API. | |
| Operation *replOp = repl.getDefiningOp(); | |
| Block *replBlock = replOp->getBlock(); | |
| rewriter.replaceUsesWithIf(from, repl, [&](OpOperand &operand) { | |
| Operation *user = operand.getOwner(); | |
| bool result = | |
| user->getBlock() != replBlock || replOp->isBeforeInBlock(user); | |
| if (result && functor) | |
| result &= functor(operand); | |
| return result; | |
| }); | |
| } | |
| void ReplaceValueRewrite::commit(RewriterBase &rewriter) { | |
| Value repl = rewriterImpl.findOrBuildReplacementValue(value, converter); | |
| if (!repl) | |
| return; | |
| performReplaceValue(rewriter, value, repl); | |
| } | |
| void ReplaceValueRewrite::rollback() { | |
| rewriterImpl.mapping.erase({value}); | |
| #ifndef NDEBUG | |
| rewriterImpl.replacedValues.erase(value); | |
| #endif // NDEBUG | |
| } | |
| void ReplaceOperationRewrite::commit(RewriterBase &rewriter) { | |
| auto *listener = | |
| dyn_cast_or_null<RewriterBase::Listener>(rewriter.getListener()); | |
| // Compute replacement values. | |
| SmallVector<Value> replacements = | |
| llvm::map_to_vector(op->getResults(), [&](OpResult result) { | |
| return rewriterImpl.findOrBuildReplacementValue(result, converter); | |
| }); | |
| // Notify the listener that the operation is about to be replaced. | |
| if (listener) | |
| listener->notifyOperationReplaced(op, replacements); | |
| // Replace all uses with the new values. | |
| for (auto [result, newValue] : | |
| llvm::zip_equal(op->getResults(), replacements)) | |
| if (newValue) | |
| rewriter.replaceAllUsesWith(result, newValue); | |
| // The original op will be erased, so remove it from the set of unlegalized | |
| // ops. | |
| if (getConfig().unlegalizedOps) | |
| getConfig().unlegalizedOps->erase(op); | |
| // Notify the listener that the operation and its contents are being erased. | |
| if (listener) | |
| notifyIRErased(listener, *op); | |
| // Do not erase the operation yet. It may still be referenced in `mapping`. | |
| // Just unlink it for now and erase it during cleanup. | |
| op->getBlock()->getOperations().remove(op); | |
| } | |
| void ReplaceOperationRewrite::rollback() { | |
| for (auto result : op->getResults()) | |
| rewriterImpl.mapping.erase({result}); | |
| } | |
| void ReplaceOperationRewrite::cleanup(RewriterBase &rewriter) { | |
| rewriter.eraseOp(op); | |
| } | |
| void CreateOperationRewrite::rollback() { | |
| for (Region ®ion : op->getRegions()) { | |
| while (!region.getBlocks().empty()) | |
| region.getBlocks().remove(region.getBlocks().begin()); | |
| } | |
| op->dropAllUses(); | |
| op->erase(); | |
| } | |
| void UnresolvedMaterializationRewrite::rollback() { | |
| if (!mappedValues.empty()) | |
| rewriterImpl.mapping.erase(mappedValues); | |
| rewriterImpl.unresolvedMaterializations.erase(getOperation()); | |
| op->erase(); | |
| } | |
| void ConversionPatternRewriterImpl::applyRewrites() { | |
| // Commit all rewrites. Use a new rewriter, so the modifications are not | |
| // tracked for rollback purposes etc. | |
| IRRewriter irRewriter(rewriter.getContext(), config.listener); | |
| // Note: New rewrites may be added during the "commit" phase and the | |
| // `rewrites` vector may reallocate. | |
| for (size_t i = 0; i < rewrites.size(); ++i) | |
| rewrites[i]->commit(irRewriter); | |
| // Clean up all rewrites. | |
| SingleEraseRewriter eraseRewriter( | |
| rewriter.getContext(), /*opErasedCallback=*/[&](Operation *op) { | |
| if (auto castOp = dyn_cast<UnrealizedConversionCastOp>(op)) | |
| unresolvedMaterializations.erase(castOp); | |
| }); | |
| for (auto &rewrite : rewrites) | |
| rewrite->cleanup(eraseRewriter); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // State Management | |
| //===----------------------------------------------------------------------===// | |
| ValueVector ConversionPatternRewriterImpl::lookupOrDefault( | |
| Value from, TypeRange desiredTypes, bool skipPureTypeConversions) const { | |
| // Helper function that looks up a single value. | |
| auto lookup = [&](const ValueVector &values) -> ValueVector { | |
| assert(!values.empty() && "expected non-empty value vector"); | |
| // If the pattern rollback is enabled, use the mapping to look up the | |
| // values. | |
| if (config.allowPatternRollback) | |
| return mapping.lookup(values); | |
| // Otherwise, look up values by examining the IR. All replacements have | |
| // already been materialized in IR. | |
| Operation *op = getCommonDefiningOp(values); | |
| if (!op) | |
| return {}; | |
| auto castOp = dyn_cast<UnrealizedConversionCastOp>(op); | |
| if (!castOp) | |
| return {}; | |
| if (!this->unresolvedMaterializations.contains(castOp)) | |
| return {}; | |
| if (castOp.getOutputs() != values) | |
| return {}; | |
| return castOp.getInputs(); | |
| }; | |
| // Helper function that looks up each value in `values` individually and then | |
| // composes the results. If that fails, it tries to look up the entire vector | |
| // at once. | |
| auto composedLookup = [&](const ValueVector &values) -> ValueVector { | |
| // If possible, replace each value with (one or multiple) mapped values. | |
| ValueVector next; | |
| for (Value v : values) { | |
| ValueVector r = lookup({v}); | |
| if (!r.empty()) { | |
| llvm::append_range(next, r); | |
| } else { | |
| next.push_back(v); | |
| } | |
| } | |
| if (next != values) { | |
| // At least one value was replaced. | |
| return next; | |
| } | |
| // Otherwise: Check if there is a mapping for the entire vector. Such | |
| // mappings are materializations. (N:M mapping are not supported for value | |
| // replacements.) | |
| // | |
| // Note: From a correctness point of view, materializations do not have to | |
| // be stored (and looked up) in the mapping. But for performance reasons, | |
| // we choose to reuse existing IR (when possible) instead of creating it | |
| // multiple times. | |
| ValueVector r = lookup(values); | |
| if (r.empty()) { | |
| // No mapping found: The lookup stops here. | |
| return {}; | |
| } | |
| return r; | |
| }; | |
| // Try to find the deepest values that have the desired types. If there is no | |
| // such mapping, simply return the deepest values. | |
| ValueVector desiredValue; | |
| ValueVector current{from}; | |
| ValueVector lastNonMaterialization{from}; | |
| do { | |
| // Store the current value if the types match. | |
| bool match = TypeRange(ValueRange(current)) == desiredTypes; | |
| if (skipPureTypeConversions) { | |
| // Skip pure type conversions, if requested. | |
| bool pureConversion = isPureTypeConversion(current); | |
| match &= !pureConversion; | |
| // Keep track of the last mapped value that was not a pure type | |
| // conversion. | |
| if (!pureConversion) | |
| lastNonMaterialization = current; | |
| } | |
| if (match) | |
| desiredValue = current; | |
| // Lookup next value in the mapping. | |
| ValueVector next = composedLookup(current); | |
| if (next.empty()) | |
| break; | |
| current = std::move(next); | |
| } while (true); | |
| // If the desired values were found use them, otherwise default to the leaf | |
| // values. (Skip pure type conversions, if requested.) | |
| if (!desiredTypes.empty()) | |
| return desiredValue; | |
| if (skipPureTypeConversions) | |
| return lastNonMaterialization; | |
| return current; | |
| } | |
| ValueVector | |
| ConversionPatternRewriterImpl::lookupOrNull(Value from, | |
| TypeRange desiredTypes) const { | |
| ValueVector result = lookupOrDefault(from, desiredTypes); | |
| if (result == ValueVector{from} || | |
| (!desiredTypes.empty() && TypeRange(ValueRange(result)) != desiredTypes)) | |
| return {}; | |
| return result; | |
| } | |
| RewriterState ConversionPatternRewriterImpl::getCurrentState() { | |
| return RewriterState(rewrites.size(), ignoredOps.size(), replacedOps.size()); | |
| } | |
| void ConversionPatternRewriterImpl::resetState(RewriterState state, | |
| StringRef patternName) { | |
| // Undo any rewrites. | |
| undoRewrites(state.numRewrites, patternName); | |
| // Pop all of the recorded ignored operations that are no longer valid. | |
| while (ignoredOps.size() != state.numIgnoredOperations) | |
| ignoredOps.pop_back(); | |
| while (replacedOps.size() != state.numReplacedOps) | |
| replacedOps.pop_back(); | |
| } | |
| void ConversionPatternRewriterImpl::undoRewrites(unsigned numRewritesToKeep, | |
| StringRef patternName) { | |
| for (auto &rewrite : | |
| llvm::reverse(llvm::drop_begin(rewrites, numRewritesToKeep))) | |
| rewrite->rollback(); | |
| rewrites.resize(numRewritesToKeep); | |
| } | |
| LogicalResult ConversionPatternRewriterImpl::remapValues( | |
| StringRef valueDiagTag, std::optional<Location> inputLoc, ValueRange values, | |
| SmallVector<ValueVector> &remapped) { | |
| remapped.reserve(llvm::size(values)); | |
| for (const auto &it : llvm::enumerate(values)) { | |
| Value operand = it.value(); | |
| Type origType = operand.getType(); | |
| Location operandLoc = inputLoc ? *inputLoc : operand.getLoc(); | |
| if (!currentTypeConverter) { | |
| // The current pattern does not have a type converter. Pass the most | |
| // recently mapped values, excluding materializations. Materializations | |
| // are intentionally excluded because their presence may depend on other | |
| // patterns. Including materializations would make the lookup fragile | |
| // and unpredictable. | |
| remapped.push_back(lookupOrDefault(operand, /*desiredTypes=*/{}, | |
| /*skipPureTypeConversions=*/true)); | |
| continue; | |
| } | |
| // If there is no legal conversion, fail to match this pattern. | |
| SmallVector<Type, 1> legalTypes; | |
| if (failed(currentTypeConverter->convertType(operand, legalTypes))) { | |
| notifyMatchFailure(operandLoc, [=](Diagnostic &diag) { | |
| diag << "unable to convert type for " << valueDiagTag << " #" | |
| << it.index() << ", type was " << origType; | |
| }); | |
| return failure(); | |
| } | |
| // If a type is converted to 0 types, there is nothing to do. | |
| if (legalTypes.empty()) { | |
| remapped.push_back({}); | |
| continue; | |
| } | |
| ValueVector repl = lookupOrDefault(operand, legalTypes); | |
| if (!repl.empty() && TypeRange(ValueRange(repl)) == legalTypes) { | |
| // Mapped values have the correct type or there is an existing | |
| // materialization. Or the operand is not mapped at all and has the | |
| // correct type. | |
| remapped.push_back(std::move(repl)); | |
| continue; | |
| } | |
| // Create a materialization for the most recently mapped values. | |
| repl = lookupOrDefault(operand, /*desiredTypes=*/{}, | |
| /*skipPureTypeConversions=*/true); | |
| ValueRange castValues = buildUnresolvedMaterialization( | |
| MaterializationKind::Target, computeInsertPoint(repl), operandLoc, | |
| /*valuesToMap=*/repl, /*inputs=*/repl, /*outputTypes=*/legalTypes, | |
| /*originalType=*/origType, currentTypeConverter); | |
| remapped.push_back(castValues); | |
| } | |
| return success(); | |
| } | |
| bool ConversionPatternRewriterImpl::isOpIgnored(Operation *op) const { | |
| // Check to see if this operation is ignored or was replaced. | |
| return wasOpReplaced(op) || ignoredOps.count(op); | |
| } | |
| bool ConversionPatternRewriterImpl::wasOpReplaced(Operation *op) const { | |
| // Check to see if this operation was replaced. | |
| return replacedOps.count(op) || erasedOps.count(op); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Type Conversion | |
| //===----------------------------------------------------------------------===// | |
| FailureOr<Block *> ConversionPatternRewriterImpl::convertRegionTypes( | |
| Region *region, const TypeConverter &converter, | |
| TypeConverter::SignatureConversion *entryConversion) { | |
| regionToConverter[region] = &converter; | |
| if (region->empty()) | |
| return nullptr; | |
| // Convert the arguments of each non-entry block within the region. | |
| for (Block &block : | |
| llvm::make_early_inc_range(llvm::drop_begin(*region, 1))) { | |
| // Compute the signature for the block with the provided converter. | |
| std::optional<TypeConverter::SignatureConversion> conversion = | |
| converter.convertBlockSignature(&block); | |
| if (!conversion) | |
| return failure(); | |
| // Convert the block with the computed signature. | |
| applySignatureConversion(&block, &converter, *conversion); | |
| } | |
| // Convert the entry block. If an entry signature conversion was provided, | |
| // use that one. Otherwise, compute the signature with the type converter. | |
| if (entryConversion) | |
| return applySignatureConversion(®ion->front(), &converter, | |
| *entryConversion); | |
| std::optional<TypeConverter::SignatureConversion> conversion = | |
| converter.convertBlockSignature(®ion->front()); | |
| if (!conversion) | |
| return failure(); | |
| return applySignatureConversion(®ion->front(), &converter, *conversion); | |
| } | |
| Block *ConversionPatternRewriterImpl::applySignatureConversion( | |
| Block *block, const TypeConverter *converter, | |
| TypeConverter::SignatureConversion &signatureConversion) { | |
| #if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS | |
| // A block cannot be converted multiple times. | |
| if (hasRewrite<BlockTypeConversionRewrite>(rewrites, block)) | |
| llvm::reportFatalInternalError("block was already converted"); | |
| #endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS | |
| OpBuilder::InsertionGuard g(rewriter); | |
| // If no arguments are being changed or added, there is nothing to do. | |
| unsigned origArgCount = block->getNumArguments(); | |
| auto convertedTypes = signatureConversion.getConvertedTypes(); | |
| if (llvm::equal(block->getArgumentTypes(), convertedTypes)) | |
| return block; | |
| // Compute the locations of all block arguments in the new block. | |
| SmallVector<Location> newLocs(convertedTypes.size(), | |
| rewriter.getUnknownLoc()); | |
| for (unsigned i = 0; i < origArgCount; ++i) { | |
| auto inputMap = signatureConversion.getInputMapping(i); | |
| if (!inputMap || inputMap->replacedWithValues()) | |
| continue; | |
| Location origLoc = block->getArgument(i).getLoc(); | |
| for (unsigned j = 0; j < inputMap->size; ++j) | |
| newLocs[inputMap->inputNo + j] = origLoc; | |
| } | |
| // Insert a new block with the converted block argument types and move all ops | |
| // from the old block to the new block. | |
| Block *newBlock = | |
| rewriter.createBlock(block->getParent(), std::next(block->getIterator()), | |
| convertedTypes, newLocs); | |
| // If a listener is attached to the dialect conversion, ops cannot be moved | |
| // to the destination block in bulk ("fast path"). This is because at the time | |
| // the notifications are sent, it is unknown which ops were moved. Instead, | |
| // ops should be moved one-by-one ("slow path"), so that a separate | |
| // `MoveOperationRewrite` is enqueued for each moved op. Moving ops in bulk is | |
| // a bit more efficient, so we try to do that when possible. | |
| bool fastPath = !config.listener; | |
| if (fastPath) { | |
| if (config.allowPatternRollback) | |
| appendRewrite<InlineBlockRewrite>(newBlock, block, newBlock->end()); | |
| newBlock->getOperations().splice(newBlock->end(), block->getOperations()); | |
| } else { | |
| while (!block->empty()) | |
| rewriter.moveOpBefore(&block->front(), newBlock, newBlock->end()); | |
| } | |
| // Replace all uses of the old block with the new block. | |
| block->replaceAllUsesWith(newBlock); | |
| for (unsigned i = 0; i != origArgCount; ++i) { | |
| BlockArgument origArg = block->getArgument(i); | |
| Type origArgType = origArg.getType(); | |
| std::optional<TypeConverter::SignatureConversion::InputMapping> inputMap = | |
| signatureConversion.getInputMapping(i); | |
| if (!inputMap) { | |
| // This block argument was dropped and no replacement value was provided. | |
| // Materialize a replacement value "out of thin air". | |
| // Note: Materialization must be built here because we cannot find a | |
| // valid insertion point in the new block. (Will point to the old block.) | |
| Value mat = | |
| buildUnresolvedMaterialization( | |
| MaterializationKind::Source, | |
| OpBuilder::InsertPoint(newBlock, newBlock->begin()), | |
| origArg.getLoc(), | |
| /*valuesToMap=*/{}, /*inputs=*/ValueRange(), | |
| /*outputTypes=*/origArgType, /*originalType=*/Type(), converter, | |
| /*isPureTypeConversion=*/false) | |
| .front(); | |
| replaceValueUses(origArg, mat, converter); | |
| continue; | |
| } | |
| if (inputMap->replacedWithValues()) { | |
| // This block argument was dropped and replacement values were provided. | |
| assert(inputMap->size == 0 && | |
| "invalid to provide a replacement value when the argument isn't " | |
| "dropped"); | |
| replaceValueUses(origArg, inputMap->replacementValues, converter); | |
| continue; | |
| } | |
| // This is a 1->1+ mapping. | |
| auto replArgs = | |
| newBlock->getArguments().slice(inputMap->inputNo, inputMap->size); | |
| replaceValueUses(origArg, replArgs, converter); | |
| } | |
| if (config.allowPatternRollback) | |
| appendRewrite<BlockTypeConversionRewrite>(/*origBlock=*/block, newBlock); | |
| // Erase the old block. (It is just unlinked for now and will be erased during | |
| // cleanup.) | |
| rewriter.eraseBlock(block); | |
| return newBlock; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Materializations | |
| //===----------------------------------------------------------------------===// | |
| /// Build an unresolved materialization operation given an output type and set | |
| /// of input operands. | |
| ValueRange ConversionPatternRewriterImpl::buildUnresolvedMaterialization( | |
| MaterializationKind kind, OpBuilder::InsertPoint ip, Location loc, | |
| ValueVector valuesToMap, ValueRange inputs, TypeRange outputTypes, | |
| Type originalType, const TypeConverter *converter, | |
| bool isPureTypeConversion) { | |
| assert((!originalType || kind == MaterializationKind::Target) && | |
| "original type is valid only for target materializations"); | |
| assert(TypeRange(inputs) != outputTypes && | |
| "materialization is not necessary"); | |
| // Create an unresolved materialization. We use a new OpBuilder to avoid | |
| // tracking the materialization like we do for other operations. | |
| OpBuilder builder(outputTypes.front().getContext()); | |
| builder.setInsertionPoint(ip.getBlock(), ip.getPoint()); | |
| UnrealizedConversionCastOp convertOp = | |
| UnrealizedConversionCastOp::create(builder, loc, outputTypes, inputs); | |
| if (config.attachDebugMaterializationKind) { | |
| StringRef kindStr = | |
| kind == MaterializationKind::Source ? "source" : "target"; | |
| convertOp->setAttr("__kind__", builder.getStringAttr(kindStr)); | |
| } | |
| if (isPureTypeConversion) | |
| convertOp->setAttr(kPureTypeConversionMarker, builder.getUnitAttr()); | |
| // Register the materialization. | |
| unresolvedMaterializations[convertOp] = | |
| UnresolvedMaterializationInfo(converter, kind, originalType); | |
| if (config.allowPatternRollback) { | |
| if (!valuesToMap.empty()) | |
| mapping.map(valuesToMap, convertOp.getResults()); | |
| appendRewrite<UnresolvedMaterializationRewrite>(convertOp, | |
| std::move(valuesToMap)); | |
| } else { | |
| patternMaterializations.insert(convertOp); | |
| } | |
| return convertOp.getResults(); | |
| } | |
| Value ConversionPatternRewriterImpl::findOrBuildReplacementValue( | |
| Value value, const TypeConverter *converter) { | |
| assert(config.allowPatternRollback && | |
| "this code path is valid only in rollback mode"); | |
| // Try to find a replacement value with the same type in the conversion value | |
| // mapping. This includes cached materializations. We try to reuse those | |
| // instead of generating duplicate IR. | |
| ValueVector repl = lookupOrNull(value, value.getType()); | |
| if (!repl.empty()) | |
| return repl.front(); | |
| // Check if the value is dead. No replacement value is needed in that case. | |
| // This is an approximate check that may have false negatives but does not | |
| // require computing and traversing an inverse mapping. (We may end up | |
| // building source materializations that are never used and that fold away.) | |
| if (llvm::all_of(value.getUsers(), | |
| [&](Operation *op) { return replacedOps.contains(op); }) && | |
| !mapping.isMappedTo(value)) | |
| return Value(); | |
| // No replacement value was found. Get the latest replacement value | |
| // (regardless of the type) and build a source materialization to the | |
| // original type. | |
| repl = lookupOrNull(value); | |
| // Compute the insertion point of the materialization. | |
| OpBuilder::InsertPoint ip; | |
| if (repl.empty()) { | |
| // The source materialization has no inputs. Insert it right before the | |
| // value that it is replacing. | |
| ip = computeInsertPoint(value); | |
| } else { | |
| // Compute the "earliest" insertion point at which all values in `repl` are | |
| // defined. It is important to emit the materialization at that location | |
| // because the same materialization may be reused in a different context. | |
| // (That's because materializations are cached in the conversion value | |
| // mapping.) The insertion point of the materialization must be valid for | |
| // all future users that may be created later in the conversion process. | |
| ip = computeInsertPoint(repl); | |
| } | |
| Value castValue = buildUnresolvedMaterialization( | |
| MaterializationKind::Source, ip, value.getLoc(), | |
| /*valuesToMap=*/repl, /*inputs=*/repl, | |
| /*outputTypes=*/value.getType(), | |
| /*originalType=*/Type(), converter, | |
| /*isPureTypeConversion=*/!repl.empty()) | |
| .front(); | |
| return castValue; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Rewriter Notification Hooks | |
| //===----------------------------------------------------------------------===// | |
| void ConversionPatternRewriterImpl::notifyOperationInserted( | |
| Operation *op, OpBuilder::InsertPoint previous) { | |
| // If no previous insertion point is provided, the op used to be detached. | |
| bool wasDetached = !previous.isSet(); | |
| LLVM_DEBUG({ | |
| logger.startLine() << "** Insert : '" << op->getName() << "' (" << op | |
| << ")"; | |
| if (wasDetached) | |
| logger.getOStream() << " (was detached)"; | |
| logger.getOStream() << "\n"; | |
| }); | |
| // In rollback mode, it is easier to misuse the API, so perform extra error | |
| // checking. | |
| assert(!(config.allowPatternRollback && wasOpReplaced(op->getParentOp())) && | |
| "attempting to insert into a block within a replaced/erased op"); | |
| // In "no rollback" mode, the listener is always notified immediately. | |
| if (!config.allowPatternRollback && config.listener) | |
| config.listener->notifyOperationInserted(op, previous); | |
| if (wasDetached) { | |
| // If the op was detached, it is most likely a newly created op. Add it the | |
| // set of newly created ops, so that it will be legalized. If this op is | |
| // not a newly created op, it will be legalized a second time, which is | |
| // inefficient but harmless. | |
| patternNewOps.insert(op); | |
| if (config.allowPatternRollback) { | |
| // TODO: If the same op is inserted multiple times from a detached | |
| // state, the rollback mechanism may erase the same op multiple times. | |
| // This is a bug in the rollback-based dialect conversion driver. | |
| appendRewrite<CreateOperationRewrite>(op); | |
| } else { | |
| // In "no rollback" mode, there is an extra data structure for tracking | |
| // erased operations that must be kept up to date. | |
| erasedOps.erase(op); | |
| } | |
| return; | |
| } | |
| // The op was moved from one place to another. | |
| if (config.allowPatternRollback) | |
| appendRewrite<MoveOperationRewrite>(op, previous); | |
| } | |
| /// Given that `fromRange` is about to be replaced with `toRange`, compute | |
| /// replacement values with the types of `fromRange`. | |
| static SmallVector<Value> | |
| getReplacementValues(ConversionPatternRewriterImpl &impl, ValueRange fromRange, | |
| const SmallVector<SmallVector<Value>> &toRange, | |
| const TypeConverter *converter) { | |
| assert(!impl.config.allowPatternRollback && | |
| "this code path is valid only in 'no rollback' mode"); | |
| SmallVector<Value> repls; | |
| for (auto [from, to] : llvm::zip_equal(fromRange, toRange)) { | |
| if (from.use_empty()) { | |
| // The replaced value is dead. No replacement value is needed. | |
| repls.push_back(Value()); | |
| continue; | |
| } | |
| if (to.empty()) { | |
| // The replaced value is dropped. Materialize a replacement value "out of | |
| // thin air". | |
| Value srcMat = impl.buildUnresolvedMaterialization( | |
| MaterializationKind::Source, computeInsertPoint(from), from.getLoc(), | |
| /*valuesToMap=*/{}, /*inputs=*/ValueRange(), | |
| /*outputTypes=*/from.getType(), /*originalType=*/Type(), | |
| converter)[0]; | |
| repls.push_back(srcMat); | |
| continue; | |
| } | |
| if (TypeRange(ValueRange(to)) == TypeRange(from.getType())) { | |
| // The replacement value already has the correct type. Use it directly. | |
| repls.push_back(to[0]); | |
| continue; | |
| } | |
| // The replacement value has the wrong type. Build a source materialization | |
| // to the original type. | |
| // TODO: This is a bit inefficient. We should try to reuse existing | |
| // materializations if possible. This would require an extension of the | |
| // `lookupOrDefault` API. | |
| Value srcMat = impl.buildUnresolvedMaterialization( | |
| MaterializationKind::Source, computeInsertPoint(to), from.getLoc(), | |
| /*valuesToMap=*/{}, /*inputs=*/to, /*outputTypes=*/from.getType(), | |
| /*originalType=*/Type(), converter)[0]; | |
| repls.push_back(srcMat); | |
| } | |
| return repls; | |
| } | |
| void ConversionPatternRewriterImpl::replaceOp( | |
| Operation *op, SmallVector<SmallVector<Value>> &&newValues) { | |
| assert(newValues.size() == op->getNumResults() && | |
| "incorrect number of replacement values"); | |
| LLVM_DEBUG({ | |
| logger.startLine() << "** Replace : '" << op->getName() << "'(" << op | |
| << ")\n"; | |
| if (currentTypeConverter) { | |
| // If the user-provided replacement types are different from the | |
| // legalized types, as per the current type converter, print a note. | |
| // In most cases, the replacement types are expected to match the types | |
| // produced by the type converter, so this could indicate a bug in the | |
| // user code. | |
| for (auto [result, repls] : | |
| llvm::zip_equal(op->getResults(), newValues)) { | |
| Type resultType = result.getType(); | |
| auto logProlog = [&, repls = repls]() { | |
| logger.startLine() << " Note: Replacing op result of type " | |
| << resultType << " with value(s) of type ("; | |
| llvm::interleaveComma(repls, logger.getOStream(), [&](Value v) { | |
| logger.getOStream() << v.getType(); | |
| }); | |
| logger.getOStream() << ")"; | |
| }; | |
| SmallVector<Type> convertedTypes; | |
| if (failed(currentTypeConverter->convertTypes(resultType, | |
| convertedTypes))) { | |
| logProlog(); | |
| logger.getOStream() << ", but the type converter failed to legalize " | |
| "the original type.\n"; | |
| continue; | |
| } | |
| if (TypeRange(convertedTypes) != TypeRange(ValueRange(repls))) { | |
| logProlog(); | |
| logger.getOStream() << ", but the legalized type(s) is/are ("; | |
| llvm::interleaveComma(convertedTypes, logger.getOStream(), | |
| [&](Type t) { logger.getOStream() << t; }); | |
| logger.getOStream() << ")\n"; | |
| } | |
| } | |
| } | |
| }); | |
| if (!config.allowPatternRollback) { | |
| // Pattern rollback is not allowed: materialize all IR changes immediately. | |
| SmallVector<Value> repls = getReplacementValues( | |
| *this, op->getResults(), newValues, currentTypeConverter); | |
| // Update internal data structures, so that there are no dangling pointers | |
| // to erased IR. | |
| op->walk([&](Operation *op) { | |
| erasedOps.insert(op); | |
| ignoredOps.remove(op); | |
| if (auto castOp = dyn_cast<UnrealizedConversionCastOp>(op)) { | |
| unresolvedMaterializations.erase(castOp); | |
| patternMaterializations.erase(castOp); | |
| } | |
| // The original op will be erased, so remove it from the set of | |
| // unlegalized ops. | |
| if (config.unlegalizedOps) | |
| config.unlegalizedOps->erase(op); | |
| }); | |
| op->walk([&](Block *block) { erasedBlocks.insert(block); }); | |
| // Replace the op with the replacement values and notify the listener. | |
| notifyingRewriter.replaceOp(op, repls); | |
| return; | |
| } | |
| assert(!ignoredOps.contains(op) && "operation was already replaced"); | |
| #ifndef NDEBUG | |
| for (Value v : op->getResults()) | |
| assert(!replacedValues.contains(v) && | |
| "attempting to replace a value that was already replaced"); | |
| #endif // NDEBUG | |
| // Check if replaced op is an unresolved materialization, i.e., an | |
| // unrealized_conversion_cast op that was created by the conversion driver. | |
| if (auto castOp = dyn_cast<UnrealizedConversionCastOp>(op)) { | |
| // Make sure that the user does not mess with unresolved materializations | |
| // that were inserted by the conversion driver. We keep track of these | |
| // ops in internal data structures. | |
| assert(!unresolvedMaterializations.contains(castOp) && | |
| "attempting to replace/erase an unresolved materialization"); | |
| } | |
| // Create mappings for each of the new result values. | |
| for (auto [repl, result] : llvm::zip_equal(newValues, op->getResults())) | |
| mapping.map(static_cast<Value>(result), std::move(repl)); | |
| appendRewrite<ReplaceOperationRewrite>(op, currentTypeConverter); | |
| // Mark this operation and all nested ops as replaced. | |
| op->walk([&](Operation *op) { replacedOps.insert(op); }); | |
| } | |
| void ConversionPatternRewriterImpl::replaceValueUses( | |
| Value from, ValueRange to, const TypeConverter *converter, | |
| function_ref<bool(OpOperand &)> functor) { | |
| LLVM_DEBUG({ | |
| logger.startLine() << "** Replace Value : '" << from << "'"; | |
| if (auto blockArg = dyn_cast<BlockArgument>(from)) { | |
| if (Operation *parentOp = blockArg.getOwner()->getParentOp()) { | |
| logger.getOStream() << " (in region of '" << parentOp->getName() | |
| << "' (" << parentOp << ")"; | |
| } else { | |
| logger.getOStream() << " (unlinked block)"; | |
| } | |
| } | |
| if (functor) { | |
| logger.getOStream() << ", conditional replacement"; | |
| } | |
| }); | |
| if (!config.allowPatternRollback) { | |
| SmallVector<Value> toConv = llvm::to_vector(to); | |
| SmallVector<Value> repls = | |
| getReplacementValues(*this, from, {toConv}, converter); | |
| IRRewriter r(from.getContext()); | |
| Value repl = repls.front(); | |
| if (!repl) | |
| return; | |
| performReplaceValue(r, from, repl, functor); | |
| return; | |
| } | |
| #ifndef NDEBUG | |
| // Make sure that a value is not replaced multiple times. In rollback mode, | |
| // `replaceAllUsesWith` replaces not only all current uses of the given value, | |
| // but also all future uses that may be introduced by future pattern | |
| // applications. Therefore, it does not make sense to call | |
| // `replaceAllUsesWith` multiple times with the same value. Doing so would | |
| // overwrite the mapping and mess with the internal state of the dialect | |
| // conversion driver. | |
| assert(!replacedValues.contains(from) && | |
| "attempting to replace a value that was already replaced"); | |
| assert(!wasOpReplaced(from.getDefiningOp()) && | |
| "attempting to replace a op result that was already replaced"); | |
| replacedValues.insert(from); | |
| #endif // NDEBUG | |
| if (functor) | |
| llvm::reportFatalInternalError( | |
| "conditional value replacement is not supported in rollback mode"); | |
| mapping.map(from, to); | |
| appendRewrite<ReplaceValueRewrite>(from, converter); | |
| } | |
| void ConversionPatternRewriterImpl::eraseBlock(Block *block) { | |
| if (!config.allowPatternRollback) { | |
| // Pattern rollback is not allowed: materialize all IR changes immediately. | |
| // Update internal data structures, so that there are no dangling pointers | |
| // to erased IR. | |
| block->walk([&](Operation *op) { | |
| erasedOps.insert(op); | |
| ignoredOps.remove(op); | |
| if (auto castOp = dyn_cast<UnrealizedConversionCastOp>(op)) { | |
| unresolvedMaterializations.erase(castOp); | |
| patternMaterializations.erase(castOp); | |
| } | |
| // The original op will be erased, so remove it from the set of | |
| // unlegalized ops. | |
| if (config.unlegalizedOps) | |
| config.unlegalizedOps->erase(op); | |
| }); | |
| block->walk([&](Block *block) { erasedBlocks.insert(block); }); | |
| // Erase the block and notify the listener. | |
| notifyingRewriter.eraseBlock(block); | |
| return; | |
| } | |
| assert(!wasOpReplaced(block->getParentOp()) && | |
| "attempting to erase a block within a replaced/erased op"); | |
| appendRewrite<EraseBlockRewrite>(block); | |
| // Unlink the block from its parent region. The block is kept in the rewrite | |
| // object and will be actually destroyed when rewrites are applied. This | |
| // allows us to keep the operations in the block live and undo the removal by | |
| // re-inserting the block. | |
| block->getParent()->getBlocks().remove(block); | |
| // Mark all nested ops as erased. | |
| block->walk([&](Operation *op) { replacedOps.insert(op); }); | |
| } | |
| void ConversionPatternRewriterImpl::notifyBlockInserted( | |
| Block *block, Region *previous, Region::iterator previousIt) { | |
| // If no previous insertion point is provided, the block used to be detached. | |
| bool wasDetached = !previous; | |
| Operation *newParentOp = block->getParentOp(); | |
| LLVM_DEBUG( | |
| { | |
| Operation *parent = newParentOp; | |
| if (parent) { | |
| logger.startLine() << "** Insert Block into : '" << parent->getName() | |
| << "' (" << parent << ")"; | |
| } else { | |
| logger.startLine() | |
| << "** Insert Block into detached Region (nullptr parent op)"; | |
| } | |
| if (wasDetached) | |
| logger.getOStream() << " (was detached)"; | |
| logger.getOStream() << "\n"; | |
| }); | |
| // In rollback mode, it is easier to misuse the API, so perform extra error | |
| // checking. | |
| assert(!(config.allowPatternRollback && wasOpReplaced(newParentOp)) && | |
| "attempting to insert into a region within a replaced/erased op"); | |
| (void)newParentOp; | |
| // In "no rollback" mode, the listener is always notified immediately. | |
| if (!config.allowPatternRollback && config.listener) | |
| config.listener->notifyBlockInserted(block, previous, previousIt); | |
| if (wasDetached) { | |
| // If the block was detached, it is most likely a newly created block. | |
| if (config.allowPatternRollback) { | |
| // TODO: If the same block is inserted multiple times from a detached | |
| // state, the rollback mechanism may erase the same block multiple times. | |
| // This is a bug in the rollback-based dialect conversion driver. | |
| appendRewrite<CreateBlockRewrite>(block); | |
| } else { | |
| // In "no rollback" mode, there is an extra data structure for tracking | |
| // erased blocks that must be kept up to date. | |
| erasedBlocks.erase(block); | |
| } | |
| return; | |
| } | |
| // The block was moved from one place to another. | |
| if (config.allowPatternRollback) | |
| appendRewrite<MoveBlockRewrite>(block, previous, previousIt); | |
| } | |
| void ConversionPatternRewriterImpl::inlineBlockBefore(Block *source, | |
| Block *dest, | |
| Block::iterator before) { | |
| appendRewrite<InlineBlockRewrite>(dest, source, before); | |
| } | |
| void ConversionPatternRewriterImpl::notifyMatchFailure( | |
| Location loc, function_ref<void(Diagnostic &)> reasonCallback) { | |
| LLVM_DEBUG({ | |
| Diagnostic diag(loc, DiagnosticSeverity::Remark); | |
| reasonCallback(diag); | |
| logger.startLine() << "** Failure : " << diag.str() << "\n"; | |
| if (config.notifyCallback) | |
| config.notifyCallback(diag); | |
| }); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ConversionPatternRewriter | |
| //===----------------------------------------------------------------------===// | |
| ConversionPatternRewriter::ConversionPatternRewriter( | |
| MLIRContext *ctx, const ConversionConfig &config, | |
| OperationConverter &opConverter) | |
| : PatternRewriter(ctx), impl(new detail::ConversionPatternRewriterImpl( | |
| *this, config, opConverter)) { | |
| setListener(impl.get()); | |
| } | |
| ConversionPatternRewriter::~ConversionPatternRewriter() = default; | |
| const ConversionConfig &ConversionPatternRewriter::getConfig() const { | |
| return impl->config; | |
| } | |
| void ConversionPatternRewriter::replaceOp(Operation *op, Operation *newOp) { | |
| assert(op && newOp && "expected non-null op"); | |
| replaceOp(op, newOp->getResults()); | |
| } | |
| void ConversionPatternRewriter::replaceOp(Operation *op, ValueRange newValues) { | |
| assert(op->getNumResults() == newValues.size() && | |
| "incorrect # of replacement values"); | |
| // If the current insertion point is before the erased operation, we adjust | |
| // the insertion point to be after the operation. | |
| if (getInsertionPoint() == op->getIterator()) | |
| setInsertionPointAfter(op); | |
| SmallVector<SmallVector<Value>> newVals = | |
| llvm::map_to_vector(newValues, [](Value v) -> SmallVector<Value> { | |
| return v ? SmallVector<Value>{v} : SmallVector<Value>(); | |
| }); | |
| impl->replaceOp(op, std::move(newVals)); | |
| } | |
| void ConversionPatternRewriter::replaceOpWithMultiple( | |
| Operation *op, SmallVector<SmallVector<Value>> &&newValues) { | |
| assert(op->getNumResults() == newValues.size() && | |
| "incorrect # of replacement values"); | |
| // If the current insertion point is before the erased operation, we adjust | |
| // the insertion point to be after the operation. | |
| if (getInsertionPoint() == op->getIterator()) | |
| setInsertionPointAfter(op); | |
| impl->replaceOp(op, std::move(newValues)); | |
| } | |
| void ConversionPatternRewriter::eraseOp(Operation *op) { | |
| LLVM_DEBUG({ | |
| impl->logger.startLine() | |
| << "** Erase : '" << op->getName() << "'(" << op << ")\n"; | |
| }); | |
| // If the current insertion point is before the erased operation, we adjust | |
| // the insertion point to be after the operation. | |
| if (getInsertionPoint() == op->getIterator()) | |
| setInsertionPointAfter(op); | |
| SmallVector<SmallVector<Value>> nullRepls(op->getNumResults(), {}); | |
| impl->replaceOp(op, std::move(nullRepls)); | |
| } | |
| void ConversionPatternRewriter::eraseBlock(Block *block) { | |
| impl->eraseBlock(block); | |
| } | |
| Block *ConversionPatternRewriter::applySignatureConversion( | |
| Block *block, TypeConverter::SignatureConversion &conversion, | |
| const TypeConverter *converter) { | |
| assert(!impl->wasOpReplaced(block->getParentOp()) && | |
| "attempting to apply a signature conversion to a block within a " | |
| "replaced/erased op"); | |
| return impl->applySignatureConversion(block, converter, conversion); | |
| } | |
| FailureOr<Block *> ConversionPatternRewriter::convertRegionTypes( | |
| Region *region, const TypeConverter &converter, | |
| TypeConverter::SignatureConversion *entryConversion) { | |
| assert(!impl->wasOpReplaced(region->getParentOp()) && | |
| "attempting to apply a signature conversion to a block within a " | |
| "replaced/erased op"); | |
| return impl->convertRegionTypes(region, converter, entryConversion); | |
| } | |
| void ConversionPatternRewriter::replaceAllUsesWith(Value from, ValueRange to) { | |
| impl->replaceValueUses(from, to, impl->currentTypeConverter); | |
| } | |
| void ConversionPatternRewriter::replaceUsesWithIf( | |
| Value from, ValueRange to, function_ref<bool(OpOperand &)> functor, | |
| bool *allUsesReplaced) { | |
| assert(!allUsesReplaced && | |
| "allUsesReplaced is not supported in a dialect conversion"); | |
| impl->replaceValueUses(from, to, impl->currentTypeConverter, functor); | |
| } | |
| Value ConversionPatternRewriter::getRemappedValue(Value key) { | |
| SmallVector<ValueVector> remappedValues; | |
| if (failed(impl->remapValues("value", /*inputLoc=*/std::nullopt, key, | |
| remappedValues))) | |
| return nullptr; | |
| assert(remappedValues.front().size() == 1 && "1:N conversion not supported"); | |
| return remappedValues.front().front(); | |
| } | |
| LogicalResult | |
| ConversionPatternRewriter::getRemappedValues(ValueRange keys, | |
| SmallVectorImpl<Value> &results) { | |
| if (keys.empty()) | |
| return success(); | |
| SmallVector<ValueVector> remapped; | |
| if (failed(impl->remapValues("value", /*inputLoc=*/std::nullopt, keys, | |
| remapped))) | |
| return failure(); | |
| for (const auto &values : remapped) { | |
| assert(values.size() == 1 && "1:N conversion not supported"); | |
| results.push_back(values.front()); | |
| } | |
| return success(); | |
| } | |
| void ConversionPatternRewriter::inlineBlockBefore(Block *source, Block *dest, | |
| Block::iterator before, | |
| ValueRange argValues) { | |
| #ifndef NDEBUG | |
| assert(argValues.size() == source->getNumArguments() && | |
| "incorrect # of argument replacement values"); | |
| assert(!impl->wasOpReplaced(source->getParentOp()) && | |
| "attempting to inline a block from a replaced/erased op"); | |
| assert(!impl->wasOpReplaced(dest->getParentOp()) && | |
| "attempting to inline a block into a replaced/erased op"); | |
| auto opIgnored = [&](Operation *op) { return impl->isOpIgnored(op); }; | |
| // The source block will be deleted, so it should not have any users (i.e., | |
| // there should be no predecessors). | |
| assert(llvm::all_of(source->getUsers(), opIgnored) && | |
| "expected 'source' to have no predecessors"); | |
| #endif // NDEBUG | |
| // If a listener is attached to the dialect conversion, ops cannot be moved | |
| // to the destination block in bulk ("fast path"). This is because at the time | |
| // the notifications are sent, it is unknown which ops were moved. Instead, | |
| // ops should be moved one-by-one ("slow path"), so that a separate | |
| // `MoveOperationRewrite` is enqueued for each moved op. Moving ops in bulk is | |
| // a bit more efficient, so we try to do that when possible. | |
| bool fastPath = !getConfig().listener; | |
| if (fastPath && impl->config.allowPatternRollback) | |
| impl->inlineBlockBefore(source, dest, before); | |
| // Replace all uses of block arguments. | |
| for (auto it : llvm::zip(source->getArguments(), argValues)) | |
| replaceAllUsesWith(std::get<0>(it), std::get<1>(it)); | |
| if (fastPath) { | |
| // Move all ops at once. | |
| dest->getOperations().splice(before, source->getOperations()); | |
| } else { | |
| // Move op by op. | |
| while (!source->empty()) | |
| moveOpBefore(&source->front(), dest, before); | |
| } | |
| // If the current insertion point is within the source block, adjust the | |
| // insertion point to the destination block. | |
| if (getInsertionBlock() == source) | |
| setInsertionPoint(dest, getInsertionPoint()); | |
| // Erase the source block. | |
| eraseBlock(source); | |
| } | |
| void ConversionPatternRewriter::startOpModification(Operation *op) { | |
| if (!impl->config.allowPatternRollback) { | |
| // Pattern rollback is not allowed: no extra bookkeeping is needed. | |
| PatternRewriter::startOpModification(op); | |
| return; | |
| } | |
| assert(!impl->wasOpReplaced(op) && | |
| "attempting to modify a replaced/erased op"); | |
| #ifndef NDEBUG | |
| impl->pendingRootUpdates.insert(op); | |
| #endif | |
| impl->appendRewrite<ModifyOperationRewrite>(op); | |
| } | |
| void ConversionPatternRewriter::finalizeOpModification(Operation *op) { | |
| impl->patternModifiedOps.insert(op); | |
| if (!impl->config.allowPatternRollback) { | |
| PatternRewriter::finalizeOpModification(op); | |
| if (getConfig().listener) | |
| getConfig().listener->notifyOperationModified(op); | |
| return; | |
| } | |
| // There is nothing to do here, we only need to track the operation at the | |
| // start of the update. | |
| #ifndef NDEBUG | |
| assert(!impl->wasOpReplaced(op) && | |
| "attempting to modify a replaced/erased op"); | |
| assert(impl->pendingRootUpdates.erase(op) && | |
| "operation did not have a pending in-place update"); | |
| #endif | |
| } | |
| void ConversionPatternRewriter::cancelOpModification(Operation *op) { | |
| if (!impl->config.allowPatternRollback) { | |
| PatternRewriter::cancelOpModification(op); | |
| return; | |
| } | |
| #ifndef NDEBUG | |
| assert(impl->pendingRootUpdates.erase(op) && | |
| "operation did not have a pending in-place update"); | |
| #endif | |
| // Erase the last update for this operation. | |
| auto it = llvm::find_if( | |
| llvm::reverse(impl->rewrites), [&](std::unique_ptr<IRRewrite> &rewrite) { | |
| auto *modifyRewrite = dyn_cast<ModifyOperationRewrite>(rewrite.get()); | |
| return modifyRewrite && modifyRewrite->getOperation() == op; | |
| }); | |
| assert(it != impl->rewrites.rend() && "no root update started on op"); | |
| (*it)->rollback(); | |
| int updateIdx = std::prev(impl->rewrites.rend()) - it; | |
| impl->rewrites.erase(impl->rewrites.begin() + updateIdx); | |
| } | |
| detail::ConversionPatternRewriterImpl &ConversionPatternRewriter::getImpl() { | |
| return *impl; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ConversionPattern | |
| //===----------------------------------------------------------------------===// | |
| FailureOr<SmallVector<Value>> ConversionPattern::getOneToOneAdaptorOperands( | |
| ArrayRef<ValueRange> operands) const { | |
| SmallVector<Value> oneToOneOperands; | |
| oneToOneOperands.reserve(operands.size()); | |
| for (ValueRange operand : operands) { | |
| if (operand.size() != 1) | |
| return failure(); | |
| oneToOneOperands.push_back(operand.front()); | |
| } | |
| return std::move(oneToOneOperands); | |
| } | |
| LogicalResult | |
| ConversionPattern::matchAndRewrite(Operation *op, | |
| PatternRewriter &rewriter) const { | |
| auto &dialectRewriter = static_cast<ConversionPatternRewriter &>(rewriter); | |
| auto &rewriterImpl = dialectRewriter.getImpl(); | |
| // Track the current conversion pattern type converter in the rewriter. | |
| llvm::SaveAndRestore currentConverterGuard(rewriterImpl.currentTypeConverter, | |
| getTypeConverter()); | |
| // Remap the operands of the operation. | |
| SmallVector<ValueVector> remapped; | |
| if (failed(rewriterImpl.remapValues("operand", op->getLoc(), | |
| op->getOperands(), remapped))) { | |
| return failure(); | |
| } | |
| SmallVector<ValueRange> remappedAsRange = | |
| llvm::to_vector_of<ValueRange>(remapped); | |
| return matchAndRewrite(op, remappedAsRange, dialectRewriter); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // OperationLegalizer | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| /// A set of rewrite patterns that can be used to legalize a given operation. | |
| using LegalizationPatterns = SmallVector<const Pattern *, 1>; | |
| /// This class defines a recursive operation legalizer. | |
| class OperationLegalizer { | |
| public: | |
| using LegalizationAction = ConversionTarget::LegalizationAction; | |
| OperationLegalizer(ConversionPatternRewriter &rewriter, | |
| const ConversionTarget &targetInfo, | |
| const FrozenRewritePatternSet &patterns); | |
| /// Returns true if the given operation is known to be illegal on the target. | |
| bool isIllegal(Operation *op) const; | |
| /// Attempt to legalize the given operation. Returns success if the operation | |
| /// was legalized, failure otherwise. | |
| LogicalResult legalize(Operation *op); | |
| /// Returns the conversion target in use by the legalizer. | |
| const ConversionTarget &getTarget() { return target; } | |
| private: | |
| /// Attempt to legalize the given operation by folding it. | |
| LogicalResult legalizeWithFold(Operation *op); | |
| /// Attempt to legalize the given operation by applying a pattern. Returns | |
| /// success if the operation was legalized, failure otherwise. | |
| LogicalResult legalizeWithPattern(Operation *op); | |
| /// Return true if the given pattern may be applied to the given operation, | |
| /// false otherwise. | |
| bool canApplyPattern(Operation *op, const Pattern &pattern); | |
| /// Legalize the resultant IR after successfully applying the given pattern. | |
| LogicalResult | |
| legalizePatternResult(Operation *op, const Pattern &pattern, | |
| const RewriterState &curState, | |
| const SetVector<Operation *> &newOps, | |
| const SetVector<Operation *> &modifiedOps); | |
| LogicalResult | |
| legalizePatternCreatedOperations(const SetVector<Operation *> &newOps); | |
| LogicalResult | |
| legalizePatternRootUpdates(const SetVector<Operation *> &modifiedOps); | |
| //===--------------------------------------------------------------------===// | |
| // Cost Model | |
| //===--------------------------------------------------------------------===// | |
| /// Build an optimistic legalization graph given the provided patterns. This | |
| /// function populates 'anyOpLegalizerPatterns' and 'legalizerPatterns' with | |
| /// patterns for operations that are not directly legal, but may be | |
| /// transitively legal for the current target given the provided patterns. | |
| void buildLegalizationGraph( | |
| LegalizationPatterns &anyOpLegalizerPatterns, | |
| DenseMap<OperationName, LegalizationPatterns> &legalizerPatterns); | |
| /// Compute the benefit of each node within the computed legalization graph. | |
| /// This orders the patterns within 'legalizerPatterns' based upon two | |
| /// criteria: | |
| /// 1) Prefer patterns that have the lowest legalization depth, i.e. | |
| /// represent the more direct mapping to the target. | |
| /// 2) When comparing patterns with the same legalization depth, prefer the | |
| /// pattern with the highest PatternBenefit. This allows for users to | |
| /// prefer specific legalizations over others. | |
| void computeLegalizationGraphBenefit( | |
| LegalizationPatterns &anyOpLegalizerPatterns, | |
| DenseMap<OperationName, LegalizationPatterns> &legalizerPatterns); | |
| /// Compute the legalization depth when legalizing an operation of the given | |
| /// type. | |
| unsigned computeOpLegalizationDepth( | |
| OperationName op, DenseMap<OperationName, unsigned> &minOpPatternDepth, | |
| DenseMap<OperationName, LegalizationPatterns> &legalizerPatterns); | |
| /// Apply the conversion cost model to the given set of patterns, and return | |
| /// the smallest legalization depth of any of the patterns. See | |
| /// `computeLegalizationGraphBenefit` for the breakdown of the cost model. | |
| unsigned applyCostModelToPatterns( | |
| LegalizationPatterns &patterns, | |
| DenseMap<OperationName, unsigned> &minOpPatternDepth, | |
| DenseMap<OperationName, LegalizationPatterns> &legalizerPatterns); | |
| /// The current set of patterns that have been applied. | |
| SmallPtrSet<const Pattern *, 8> appliedPatterns; | |
| /// The rewriter to use when converting operations. | |
| ConversionPatternRewriter &rewriter; | |
| /// The legalization information provided by the target. | |
| const ConversionTarget ⌖ | |
| /// The pattern applicator to use for conversions. | |
| PatternApplicator applicator; | |
| }; | |
| } // namespace | |
| OperationLegalizer::OperationLegalizer(ConversionPatternRewriter &rewriter, | |
| const ConversionTarget &targetInfo, | |
| const FrozenRewritePatternSet &patterns) | |
| : rewriter(rewriter), target(targetInfo), applicator(patterns) { | |
| // The set of patterns that can be applied to illegal operations to transform | |
| // them into legal ones. | |
| DenseMap<OperationName, LegalizationPatterns> legalizerPatterns; | |
| LegalizationPatterns anyOpLegalizerPatterns; | |
| buildLegalizationGraph(anyOpLegalizerPatterns, legalizerPatterns); | |
| computeLegalizationGraphBenefit(anyOpLegalizerPatterns, legalizerPatterns); | |
| } | |
| bool OperationLegalizer::isIllegal(Operation *op) const { | |
| return target.isIllegal(op); | |
| } | |
| LogicalResult OperationLegalizer::legalize(Operation *op) { | |
| #ifndef NDEBUG | |
| const char *logLineComment = | |
| "//===-------------------------------------------===//\n"; | |
| auto &logger = rewriter.getImpl().logger; | |
| #endif | |
| // Check to see if the operation is ignored and doesn't need to be converted. | |
| bool isIgnored = rewriter.getImpl().isOpIgnored(op); | |
| LLVM_DEBUG({ | |
| logger.getOStream() << "\n"; | |
| logger.startLine() << logLineComment; | |
| logger.startLine() << "Legalizing operation : "; | |
| // Do not print the operation name if the operation is ignored. Ignored ops | |
| // may have been erased and should not be accessed. The pointer can be | |
| // printed safely. | |
| if (!isIgnored) | |
| logger.getOStream() << "'" << op->getName() << "' "; | |
| logger.getOStream() << "(" << op << ") {\n"; | |
| logger.indent(); | |
| // If the operation has no regions, just print it here. | |
| if (!isIgnored && op->getNumRegions() == 0) { | |
| logger.startLine() << OpWithFlags(op, | |
| OpPrintingFlags().printGenericOpForm()) | |
| << "\n"; | |
| } | |
| }); | |
| if (isIgnored) { | |
| LLVM_DEBUG({ | |
| logSuccess(logger, "operation marked 'ignored' during conversion"); | |
| logger.startLine() << logLineComment; | |
| }); | |
| return success(); | |
| } | |
| // Check if this operation is legal on the target. | |
| if (auto legalityInfo = target.isLegal(op)) { | |
| LLVM_DEBUG({ | |
| logSuccess( | |
| logger, "operation marked legal by the target{0}", | |
| legalityInfo->isRecursivelyLegal | |
| ? "; NOTE: operation is recursively legal; skipping internals" | |
| : ""); | |
| logger.startLine() << logLineComment; | |
| }); | |
| // If this operation is recursively legal, mark its children as ignored so | |
| // that we don't consider them for legalization. | |
| if (legalityInfo->isRecursivelyLegal) { | |
| op->walk([&](Operation *nested) { | |
| if (op != nested) | |
| rewriter.getImpl().ignoredOps.insert(nested); | |
| }); | |
| } | |
| return success(); | |
| } | |
| // If the operation is not legal, try to fold it in-place if the folding mode | |
| // is 'BeforePatterns'. 'Never' will skip this. | |
| const ConversionConfig &config = rewriter.getConfig(); | |
| if (config.foldingMode == DialectConversionFoldingMode::BeforePatterns) { | |
| if (succeeded(legalizeWithFold(op))) { | |
| LLVM_DEBUG({ | |
| logSuccess(logger, "operation was folded"); | |
| logger.startLine() << logLineComment; | |
| }); | |
| return success(); | |
| } | |
| } | |
| // Otherwise, we need to apply a legalization pattern to this operation. | |
| if (succeeded(legalizeWithPattern(op))) { | |
| LLVM_DEBUG({ | |
| logSuccess(logger, ""); | |
| logger.startLine() << logLineComment; | |
| }); | |
| return success(); | |
| } | |
| // If the operation can't be legalized via patterns, try to fold it in-place | |
| // if the folding mode is 'AfterPatterns'. | |
| if (config.foldingMode == DialectConversionFoldingMode::AfterPatterns) { | |
| if (succeeded(legalizeWithFold(op))) { | |
| LLVM_DEBUG({ | |
| logSuccess(logger, "operation was folded"); | |
| logger.startLine() << logLineComment; | |
| }); | |
| return success(); | |
| } | |
| } | |
| LLVM_DEBUG({ | |
| logFailure(logger, "no matched legalization pattern"); | |
| logger.startLine() << logLineComment; | |
| }); | |
| return failure(); | |
| } | |
| /// Helper function that moves and returns the given object. Also resets the | |
| /// original object, so that it is in a valid, empty state again. | |
| template <typename T> | |
| static T moveAndReset(T &obj) { | |
| T result = std::move(obj); | |
| obj = T(); | |
| return result; | |
| } | |
| LogicalResult OperationLegalizer::legalizeWithFold(Operation *op) { | |
| auto &rewriterImpl = rewriter.getImpl(); | |
| LLVM_DEBUG({ | |
| rewriterImpl.logger.startLine() << "* Fold {\n"; | |
| rewriterImpl.logger.indent(); | |
| }); | |
| // Clear pattern state, so that the next pattern application starts with a | |
| // clean slate. (The op/block sets are populated by listener notifications.) | |
| llvm::scope_exit cleanup([&]() { | |
| rewriterImpl.patternNewOps.clear(); | |
| rewriterImpl.patternModifiedOps.clear(); | |
| }); | |
| // Upon failure, undo all changes made by the folder. | |
| RewriterState curState = rewriterImpl.getCurrentState(); | |
| // Try to fold the operation. | |
| StringRef opName = op->getName().getStringRef(); | |
| SmallVector<Value, 2> replacementValues; | |
| SmallVector<Operation *, 2> newOps; | |
| rewriter.setInsertionPoint(op); | |
| rewriter.startOpModification(op); | |
| if (failed(rewriter.tryFold(op, replacementValues, &newOps))) { | |
| LLVM_DEBUG(logFailure(rewriterImpl.logger, "unable to fold")); | |
| rewriter.cancelOpModification(op); | |
| return failure(); | |
| } | |
| rewriter.finalizeOpModification(op); | |
| // An empty list of replacement values indicates that the fold was in-place. | |
| // As the operation changed, a new legalization needs to be attempted. | |
| if (replacementValues.empty()) | |
| return legalize(op); | |
| // Insert a replacement for 'op' with the folded replacement values. | |
| rewriter.replaceOp(op, replacementValues); | |
| // Recursively legalize any new constant operations. | |
| for (Operation *newOp : newOps) { | |
| if (failed(legalize(newOp))) { | |
| LLVM_DEBUG(logFailure(rewriterImpl.logger, | |
| "failed to legalize generated constant '{0}'", | |
| newOp->getName())); | |
| if (!rewriter.getConfig().allowPatternRollback) { | |
| // Rolling back a folder is like rolling back a pattern. | |
| llvm::reportFatalInternalError( | |
| "op '" + opName + | |
| "' folder rollback of IR modifications requested"); | |
| } | |
| rewriterImpl.resetState( | |
| curState, std::string(op->getName().getStringRef()) + " folder"); | |
| return failure(); | |
| } | |
| } | |
| LLVM_DEBUG(logSuccess(rewriterImpl.logger, "")); | |
| return success(); | |
| } | |
| /// Report a fatal error indicating that newly produced or modified IR could | |
| /// not be legalized. | |
| static void | |
| reportNewIrLegalizationFatalError(const Pattern &pattern, | |
| const SetVector<Operation *> &newOps, | |
| const SetVector<Operation *> &modifiedOps) { | |
| auto newOpNames = llvm::map_range( | |
| newOps, [](Operation *op) { return op->getName().getStringRef(); }); | |
| auto modifiedOpNames = llvm::map_range( | |
| modifiedOps, [](Operation *op) { return op->getName().getStringRef(); }); | |
| llvm::reportFatalInternalError("pattern '" + pattern.getDebugName() + | |
| "' produced IR that could not be legalized. " + | |
| "new ops: {" + llvm::join(newOpNames, ", ") + | |
| "}, " + "modified ops: {" + | |
| llvm::join(modifiedOpNames, ", ") + "}"); | |
| } | |
| LogicalResult OperationLegalizer::legalizeWithPattern(Operation *op) { | |
| auto &rewriterImpl = rewriter.getImpl(); | |
| const ConversionConfig &config = rewriter.getConfig(); | |
| #if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS | |
| Operation *checkOp; | |
| std::optional<OperationFingerPrint> topLevelFingerPrint; | |
| if (!rewriterImpl.config.allowPatternRollback) { | |
| // The op may be getting erased, so we have to check the parent op. | |
| // (In rare cases, a pattern may even erase the parent op, which will cause | |
| // a crash here. Expensive checks are "best effort".) Skip the check if the | |
| // op does not have a parent op. | |
| if ((checkOp = op->getParentOp())) { | |
| if (!op->getContext()->isMultithreadingEnabled()) { | |
| topLevelFingerPrint = OperationFingerPrint(checkOp); | |
| } else { | |
| // Another thread may be modifying a sibling operation. Therefore, the | |
| // fingerprinting mechanism of the parent op works only in | |
| // single-threaded mode. | |
| LLVM_DEBUG({ | |
| rewriterImpl.logger.startLine() | |
| << "WARNING: Multi-threadeding is enabled. Some dialect " | |
| "conversion expensive checks are skipped in multithreading " | |
| "mode!\n"; | |
| }); | |
| } | |
| } | |
| } | |
| #endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS | |
| // Functor that returns if the given pattern may be applied. | |
| auto canApply = [&](const Pattern &pattern) { | |
| bool canApply = canApplyPattern(op, pattern); | |
| if (canApply && config.listener) | |
| config.listener->notifyPatternBegin(pattern, op); | |
| return canApply; | |
| }; | |
| // Functor that cleans up the rewriter state after a pattern failed to match. | |
| RewriterState curState = rewriterImpl.getCurrentState(); | |
| auto onFailure = [&](const Pattern &pattern) { | |
| assert(rewriterImpl.pendingRootUpdates.empty() && "dangling root updates"); | |
| if (!rewriterImpl.config.allowPatternRollback) { | |
| // Erase all unresolved materializations. | |
| for (auto op : rewriterImpl.patternMaterializations) { | |
| rewriterImpl.unresolvedMaterializations.erase(op); | |
| op.erase(); | |
| } | |
| rewriterImpl.patternMaterializations.clear(); | |
| #if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS | |
| // Expensive pattern check that can detect API violations. | |
| if (checkOp && topLevelFingerPrint) { | |
| OperationFingerPrint fingerPrintAfterPattern(checkOp); | |
| if (fingerPrintAfterPattern != *topLevelFingerPrint) | |
| llvm::reportFatalInternalError( | |
| "pattern '" + pattern.getDebugName() + | |
| "' returned failure but IR did change"); | |
| } | |
| #endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS | |
| } | |
| rewriterImpl.patternNewOps.clear(); | |
| rewriterImpl.patternModifiedOps.clear(); | |
| LLVM_DEBUG({ | |
| logFailure(rewriterImpl.logger, "pattern failed to match"); | |
| if (rewriterImpl.config.notifyCallback) { | |
| Diagnostic diag(op->getLoc(), DiagnosticSeverity::Remark); | |
| diag << "Failed to apply pattern \"" << pattern.getDebugName() | |
| << "\" on op:\n" | |
| << *op; | |
| rewriterImpl.config.notifyCallback(diag); | |
| } | |
| }); | |
| if (config.listener) | |
| config.listener->notifyPatternEnd(pattern, failure()); | |
| rewriterImpl.resetState(curState, pattern.getDebugName()); | |
| appliedPatterns.erase(&pattern); | |
| }; | |
| // Functor that performs additional legalization when a pattern is | |
| // successfully applied. | |
| auto onSuccess = [&](const Pattern &pattern) { | |
| assert(rewriterImpl.pendingRootUpdates.empty() && "dangling root updates"); | |
| if (!rewriterImpl.config.allowPatternRollback) { | |
| // Eagerly erase unused materializations. | |
| for (auto op : rewriterImpl.patternMaterializations) { | |
| if (op->use_empty()) { | |
| rewriterImpl.unresolvedMaterializations.erase(op); | |
| op.erase(); | |
| } | |
| } | |
| rewriterImpl.patternMaterializations.clear(); | |
| } | |
| SetVector<Operation *> newOps = moveAndReset(rewriterImpl.patternNewOps); | |
| SetVector<Operation *> modifiedOps = | |
| moveAndReset(rewriterImpl.patternModifiedOps); | |
| auto result = | |
| legalizePatternResult(op, pattern, curState, newOps, modifiedOps); | |
| appliedPatterns.erase(&pattern); | |
| if (failed(result)) { | |
| if (!rewriterImpl.config.allowPatternRollback) | |
| reportNewIrLegalizationFatalError(pattern, newOps, modifiedOps); | |
| rewriterImpl.resetState(curState, pattern.getDebugName()); | |
| } | |
| if (config.listener) | |
| config.listener->notifyPatternEnd(pattern, result); | |
| return result; | |
| }; | |
| // Try to match and rewrite a pattern on this operation. | |
| return applicator.matchAndRewrite(op, rewriter, canApply, onFailure, | |
| onSuccess); | |
| } | |
| bool OperationLegalizer::canApplyPattern(Operation *op, | |
| const Pattern &pattern) { | |
| LLVM_DEBUG({ | |
| auto &os = rewriter.getImpl().logger; | |
| os.getOStream() << "\n"; | |
| os.startLine() << "* Pattern : '" << op->getName() << " -> ("; | |
| llvm::interleaveComma(pattern.getGeneratedOps(), os.getOStream()); | |
| os.getOStream() << ")' {\n"; | |
| os.indent(); | |
| }); | |
| // Ensure that we don't cycle by not allowing the same pattern to be | |
| // applied twice in the same recursion stack if it is not known to be safe. | |
| if (!pattern.hasBoundedRewriteRecursion() && | |
| !appliedPatterns.insert(&pattern).second) { | |
| LLVM_DEBUG( | |
| logFailure(rewriter.getImpl().logger, "pattern was already applied")); | |
| return false; | |
| } | |
| return true; | |
| } | |
| LogicalResult OperationLegalizer::legalizePatternResult( | |
| Operation *op, const Pattern &pattern, const RewriterState &curState, | |
| const SetVector<Operation *> &newOps, | |
| const SetVector<Operation *> &modifiedOps) { | |
| [[maybe_unused]] auto &impl = rewriter.getImpl(); | |
| assert(impl.pendingRootUpdates.empty() && "dangling root updates"); | |
| #if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS | |
| if (impl.config.allowPatternRollback) { | |
| // Check that the root was either replaced or updated in place. | |
| auto newRewrites = llvm::drop_begin(impl.rewrites, curState.numRewrites); | |
| auto replacedRoot = [&] { | |
| return hasRewrite<ReplaceOperationRewrite>(newRewrites, op); | |
| }; | |
| auto updatedRootInPlace = [&] { | |
| return hasRewrite<ModifyOperationRewrite>(newRewrites, op); | |
| }; | |
| if (!replacedRoot() && !updatedRootInPlace()) | |
| llvm::reportFatalInternalError( | |
| "expected pattern to replace the root operation " | |
| "or modify it in place"); | |
| } | |
| #endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS | |
| // Legalize each of the actions registered during application. | |
| if (failed(legalizePatternRootUpdates(modifiedOps)) || | |
| failed(legalizePatternCreatedOperations(newOps))) { | |
| return failure(); | |
| } | |
| LLVM_DEBUG(logSuccess(impl.logger, "pattern applied successfully")); | |
| return success(); | |
| } | |
| LogicalResult OperationLegalizer::legalizePatternCreatedOperations( | |
| const SetVector<Operation *> &newOps) { | |
| for (Operation *op : newOps) { | |
| if (failed(legalize(op))) { | |
| LLVM_DEBUG(logFailure(rewriter.getImpl().logger, | |
| "failed to legalize generated operation '{0}'({1})", | |
| op->getName(), op)); | |
| return failure(); | |
| } | |
| } | |
| return success(); | |
| } | |
| LogicalResult OperationLegalizer::legalizePatternRootUpdates( | |
| const SetVector<Operation *> &modifiedOps) { | |
| for (Operation *op : modifiedOps) { | |
| if (failed(legalize(op))) { | |
| LLVM_DEBUG( | |
| logFailure(rewriter.getImpl().logger, | |
| "failed to legalize operation updated in-place '{0}'", | |
| op->getName())); | |
| return failure(); | |
| } | |
| } | |
| return success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Cost Model | |
| //===----------------------------------------------------------------------===// | |
| void OperationLegalizer::buildLegalizationGraph( | |
| LegalizationPatterns &anyOpLegalizerPatterns, | |
| DenseMap<OperationName, LegalizationPatterns> &legalizerPatterns) { | |
| // A mapping between an operation and a set of operations that can be used to | |
| // generate it. | |
| DenseMap<OperationName, SmallPtrSet<OperationName, 2>> parentOps; | |
| // A mapping between an operation and any currently invalid patterns it has. | |
| DenseMap<OperationName, SmallPtrSet<const Pattern *, 2>> invalidPatterns; | |
| // A worklist of patterns to consider for legality. | |
| SetVector<const Pattern *> patternWorklist; | |
| // Build the mapping from operations to the parent ops that may generate them. | |
| applicator.walkAllPatterns([&](const Pattern &pattern) { | |
| std::optional<OperationName> root = pattern.getRootKind(); | |
| // If the pattern has no specific root, we can't analyze the relationship | |
| // between the root op and generated operations. Given that, add all such | |
| // patterns to the legalization set. | |
| if (!root) { | |
| anyOpLegalizerPatterns.push_back(&pattern); | |
| return; | |
| } | |
| // Skip operations that are always known to be legal. | |
| if (target.getOpAction(*root) == LegalizationAction::Legal) | |
| return; | |
| // Add this pattern to the invalid set for the root op and record this root | |
| // as a parent for any generated operations. | |
| invalidPatterns[*root].insert(&pattern); | |
| for (auto op : pattern.getGeneratedOps()) | |
| parentOps[op].insert(*root); | |
| // Add this pattern to the worklist. | |
| patternWorklist.insert(&pattern); | |
| }); | |
| // If there are any patterns that don't have a specific root kind, we can't | |
| // make direct assumptions about what operations will never be legalized. | |
| // Note: Technically we could, but it would require an analysis that may | |
| // recurse into itself. It would be better to perform this kind of filtering | |
| // at a higher level than here anyways. | |
| if (!anyOpLegalizerPatterns.empty()) { | |
| for (const Pattern *pattern : patternWorklist) | |
| legalizerPatterns[*pattern->getRootKind()].push_back(pattern); | |
| return; | |
| } | |
| while (!patternWorklist.empty()) { | |
| auto *pattern = patternWorklist.pop_back_val(); | |
| // Check to see if any of the generated operations are invalid. | |
| if (llvm::any_of(pattern->getGeneratedOps(), [&](OperationName op) { | |
| std::optional<LegalizationAction> action = target.getOpAction(op); | |
| return !legalizerPatterns.count(op) && | |
| (!action || action == LegalizationAction::Illegal); | |
| })) | |
| continue; | |
| // Otherwise, if all of the generated operation are valid, this op is now | |
| // legal so add all of the child patterns to the worklist. | |
| legalizerPatterns[*pattern->getRootKind()].push_back(pattern); | |
| invalidPatterns[*pattern->getRootKind()].erase(pattern); | |
| // Add any invalid patterns of the parent operations to see if they have now | |
| // become legal. | |
| for (auto op : parentOps[*pattern->getRootKind()]) | |
| patternWorklist.set_union(invalidPatterns[op]); | |
| } | |
| } | |
| void OperationLegalizer::computeLegalizationGraphBenefit( | |
| LegalizationPatterns &anyOpLegalizerPatterns, | |
| DenseMap<OperationName, LegalizationPatterns> &legalizerPatterns) { | |
| // The smallest pattern depth, when legalizing an operation. | |
| DenseMap<OperationName, unsigned> minOpPatternDepth; | |
| // For each operation that is transitively legal, compute a cost for it. | |
| for (auto &opIt : legalizerPatterns) | |
| if (!minOpPatternDepth.count(opIt.first)) | |
| computeOpLegalizationDepth(opIt.first, minOpPatternDepth, | |
| legalizerPatterns); | |
| // Apply the cost model to the patterns that can match any operation. Those | |
| // with a specific operation type are already resolved when computing the op | |
| // legalization depth. | |
| if (!anyOpLegalizerPatterns.empty()) | |
| applyCostModelToPatterns(anyOpLegalizerPatterns, minOpPatternDepth, | |
| legalizerPatterns); | |
| // Apply a cost model to the pattern applicator. We order patterns first by | |
| // depth then benefit. `legalizerPatterns` contains per-op patterns by | |
| // decreasing benefit. | |
| applicator.applyCostModel([&](const Pattern &pattern) { | |
| ArrayRef<const Pattern *> orderedPatternList; | |
| if (std::optional<OperationName> rootName = pattern.getRootKind()) | |
| orderedPatternList = legalizerPatterns[*rootName]; | |
| else | |
| orderedPatternList = anyOpLegalizerPatterns; | |
| // If the pattern is not found, then it was removed and cannot be matched. | |
| auto *it = llvm::find(orderedPatternList, &pattern); | |
| if (it == orderedPatternList.end()) | |
| return PatternBenefit::impossibleToMatch(); | |
| // Patterns found earlier in the list have higher benefit. | |
| return PatternBenefit(std::distance(it, orderedPatternList.end())); | |
| }); | |
| } | |
| unsigned OperationLegalizer::computeOpLegalizationDepth( | |
| OperationName op, DenseMap<OperationName, unsigned> &minOpPatternDepth, | |
| DenseMap<OperationName, LegalizationPatterns> &legalizerPatterns) { | |
| // Check for existing depth. | |
| auto depthIt = minOpPatternDepth.find(op); | |
| if (depthIt != minOpPatternDepth.end()) | |
| return depthIt->second; | |
| // If a mapping for this operation does not exist, then this operation | |
| // is always legal. Return 0 as the depth for a directly legal operation. | |
| auto opPatternsIt = legalizerPatterns.find(op); | |
| if (opPatternsIt == legalizerPatterns.end() || opPatternsIt->second.empty()) | |
| return 0u; | |
| // Record this initial depth in case we encounter this op again when | |
| // recursively computing the depth. | |
| minOpPatternDepth.try_emplace(op, std::numeric_limits<unsigned>::max()); | |
| // Apply the cost model to the operation patterns, and update the minimum | |
| // depth. | |
| unsigned minDepth = applyCostModelToPatterns( | |
| opPatternsIt->second, minOpPatternDepth, legalizerPatterns); | |
| minOpPatternDepth[op] = minDepth; | |
| return minDepth; | |
| } | |
| unsigned OperationLegalizer::applyCostModelToPatterns( | |
| LegalizationPatterns &patterns, | |
| DenseMap<OperationName, unsigned> &minOpPatternDepth, | |
| DenseMap<OperationName, LegalizationPatterns> &legalizerPatterns) { | |
| unsigned minDepth = std::numeric_limits<unsigned>::max(); | |
| // Compute the depth for each pattern within the set. | |
| SmallVector<std::pair<const Pattern *, unsigned>, 4> patternsByDepth; | |
| patternsByDepth.reserve(patterns.size()); | |
| for (const Pattern *pattern : patterns) { | |
| unsigned depth = 1; | |
| for (auto generatedOp : pattern->getGeneratedOps()) { | |
| unsigned generatedOpDepth = computeOpLegalizationDepth( | |
| generatedOp, minOpPatternDepth, legalizerPatterns); | |
| depth = std::max(depth, generatedOpDepth + 1); | |
| } | |
| patternsByDepth.emplace_back(pattern, depth); | |
| // Update the minimum depth of the pattern list. | |
| minDepth = std::min(minDepth, depth); | |
| } | |
| // If the operation only has one legalization pattern, there is no need to | |
| // sort them. | |
| if (patternsByDepth.size() == 1) | |
| return minDepth; | |
| // Sort the patterns by those likely to be the most beneficial. | |
| llvm::stable_sort(patternsByDepth, | |
| [](const std::pair<const Pattern *, unsigned> &lhs, | |
| const std::pair<const Pattern *, unsigned> &rhs) { | |
| // First sort by the smaller pattern legalization | |
| // depth. | |
| if (lhs.second != rhs.second) | |
| return lhs.second < rhs.second; | |
| // Then sort by the larger pattern benefit. | |
| auto lhsBenefit = lhs.first->getBenefit(); | |
| auto rhsBenefit = rhs.first->getBenefit(); | |
| return lhsBenefit > rhsBenefit; | |
| }); | |
| // Update the legalization pattern to use the new sorted list. | |
| patterns.clear(); | |
| for (auto &patternIt : patternsByDepth) | |
| patterns.push_back(patternIt.first); | |
| return minDepth; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Reconcile Unrealized Casts | |
| //===----------------------------------------------------------------------===// | |
| /// Try to reconcile all given UnrealizedConversionCastOps and store the | |
| /// left-over ops in `remainingCastOps` (if provided). See documentation in | |
| /// DialectConversion.h for more details. | |
| /// The `isCastOpOfInterestFn` is used to filter the cast ops to proceed: the | |
| /// algorithm may visit an operand (or user) which is a cast op, but will not | |
| /// try to reconcile it if not in the filtered set. | |
| template <typename RangeT> | |
| static void reconcileUnrealizedCastsImpl( | |
| RangeT castOps, | |
| function_ref<bool(UnrealizedConversionCastOp)> isCastOpOfInterestFn, | |
| SmallVectorImpl<UnrealizedConversionCastOp> *remainingCastOps) { | |
| // A worklist of cast ops to process. | |
| SetVector<UnrealizedConversionCastOp> worklist(llvm::from_range, castOps); | |
| // Helper function that return the unrealized_conversion_cast op that | |
| // defines all inputs of the given op (in the same order). Return "nullptr" | |
| // if there is no such op. | |
| auto getInputCast = | |
| [](UnrealizedConversionCastOp castOp) -> UnrealizedConversionCastOp { | |
| if (castOp.getInputs().empty()) | |
| return {}; | |
| auto inputCastOp = | |
| castOp.getInputs().front().getDefiningOp<UnrealizedConversionCastOp>(); | |
| if (!inputCastOp) | |
| return {}; | |
| if (inputCastOp.getOutputs() != castOp.getInputs()) | |
| return {}; | |
| return inputCastOp; | |
| }; | |
| // Process ops in the worklist bottom-to-top. | |
| while (!worklist.empty()) { | |
| UnrealizedConversionCastOp castOp = worklist.pop_back_val(); | |
| // Traverse the chain of input cast ops to see if an op with the same | |
| // input types can be found. | |
| UnrealizedConversionCastOp nextCast = castOp; | |
| while (nextCast) { | |
| if (nextCast.getInputs().getTypes() == castOp.getResultTypes()) { | |
| if (llvm::any_of(nextCast.getInputs(), [&](Value v) { | |
| return v.getDefiningOp() == castOp; | |
| })) { | |
| // Ran into a cycle. | |
| break; | |
| } | |
| // Found a cast where the input types match the output types of the | |
| // matched op. We can directly use those inputs. | |
| castOp.replaceAllUsesWith(nextCast.getInputs()); | |
| break; | |
| } | |
| nextCast = getInputCast(nextCast); | |
| } | |
| } | |
| // A set of all alive cast ops. I.e., ops whose results are (transitively) | |
| // used by an op that is not a cast op. | |
| DenseSet<Operation *> liveOps; | |
| // Helper function that marks the given op and transitively reachable input | |
| // cast ops as alive. | |
| auto markOpLive = [&](Operation *rootOp) { | |
| SmallVector<Operation *> worklist; | |
| worklist.push_back(rootOp); | |
| while (!worklist.empty()) { | |
| Operation *op = worklist.pop_back_val(); | |
| if (liveOps.insert(op).second) { | |
| // Successfully inserted: process reachable input cast ops. | |
| for (Value v : op->getOperands()) | |
| if (auto castOp = v.getDefiningOp<UnrealizedConversionCastOp>()) | |
| if (isCastOpOfInterestFn(castOp)) | |
| worklist.push_back(castOp); | |
| } | |
| } | |
| }; | |
| // Find all alive cast ops. | |
| for (UnrealizedConversionCastOp op : castOps) { | |
| // The op may have been marked live already as being an operand of another | |
| // live cast op. | |
| if (liveOps.contains(op.getOperation())) | |
| continue; | |
| // If any of the users is not a cast op, mark the current op (and its | |
| // input ops) as live. | |
| if (llvm::any_of(op->getUsers(), [&](Operation *user) { | |
| auto castOp = dyn_cast<UnrealizedConversionCastOp>(user); | |
| return !castOp || !isCastOpOfInterestFn(castOp); | |
| })) | |
| markOpLive(op); | |
| } | |
| // Erase all dead cast ops. | |
| for (UnrealizedConversionCastOp op : castOps) { | |
| if (liveOps.contains(op)) { | |
| // Op is alive and was not erased. Add it to the remaining cast ops. | |
| if (remainingCastOps) | |
| remainingCastOps->push_back(op); | |
| continue; | |
| } | |
| // Op is dead. Erase it. | |
| op->dropAllUses(); | |
| op->erase(); | |
| } | |
| } | |
| void mlir::reconcileUnrealizedCasts( | |
| ArrayRef<UnrealizedConversionCastOp> castOps, | |
| SmallVectorImpl<UnrealizedConversionCastOp> *remainingCastOps) { | |
| // Set of all cast ops for faster lookups. | |
| DenseSet<UnrealizedConversionCastOp> castOpSet; | |
| for (UnrealizedConversionCastOp op : castOps) | |
| castOpSet.insert(op); | |
| reconcileUnrealizedCasts(castOpSet, remainingCastOps); | |
| } | |
| void mlir::reconcileUnrealizedCasts( | |
| const DenseSet<UnrealizedConversionCastOp> &castOps, | |
| SmallVectorImpl<UnrealizedConversionCastOp> *remainingCastOps) { | |
| reconcileUnrealizedCastsImpl( | |
| llvm::make_range(castOps.begin(), castOps.end()), | |
| [&](UnrealizedConversionCastOp castOp) { | |
| return castOps.contains(castOp); | |
| }, | |
| remainingCastOps); | |
| } | |
| namespace mlir { | |
| static void reconcileUnrealizedCasts( | |
| const DenseMap<UnrealizedConversionCastOp, UnresolvedMaterializationInfo> | |
| &castOps, | |
| SmallVectorImpl<UnrealizedConversionCastOp> *remainingCastOps) { | |
| reconcileUnrealizedCastsImpl( | |
| castOps.keys(), | |
| [&](UnrealizedConversionCastOp castOp) { | |
| return castOps.contains(castOp); | |
| }, | |
| remainingCastOps); | |
| } | |
| } // namespace mlir | |
| //===----------------------------------------------------------------------===// | |
| // OperationConverter | |
| //===----------------------------------------------------------------------===// | |
| namespace mlir { | |
| // This class converts operations to a given conversion target via a set of | |
| // rewrite patterns. The conversion behaves differently depending on the | |
| // conversion mode. | |
| struct OperationConverter { | |
| explicit OperationConverter(MLIRContext *ctx, const ConversionTarget &target, | |
| const FrozenRewritePatternSet &patterns, | |
| const ConversionConfig &config, | |
| OpConversionMode mode) | |
| : rewriter(ctx, config, *this), opLegalizer(rewriter, target, patterns), | |
| mode(mode) {} | |
| /// Applies the conversion to the given operations (and their nested | |
| /// operations). | |
| LogicalResult applyConversion(ArrayRef<Operation *> ops); | |
| /// Legalizes the given operations (and their nested operations) to the | |
| /// conversion target. | |
| template <typename Fn> | |
| LogicalResult legalizeOperations(ArrayRef<Operation *> ops, Fn onFailure, | |
| bool isRecursiveLegalization = false); | |
| LogicalResult legalizeOperations(ArrayRef<Operation *> ops, | |
| bool isRecursiveLegalization = false) { | |
| return legalizeOperations( | |
| ops, /*onFailure=*/[&]() {}, isRecursiveLegalization); | |
| } | |
| /// Converts a single operation. If `isRecursiveLegalization` is "true", the | |
| /// conversion is a recursive legalization request, triggered from within a | |
| /// pattern. In that case, do not emit errors because there will be another | |
| /// attempt at legalizing the operation later (via the regular pre-order | |
| /// legalization mechanism). | |
| LogicalResult convert(Operation *op, bool isRecursiveLegalization = false); | |
| const ConversionTarget &getTarget() { return opLegalizer.getTarget(); } | |
| private: | |
| /// The rewriter to use when converting operations. | |
| ConversionPatternRewriter rewriter; | |
| /// The legalizer to use when converting operations. | |
| OperationLegalizer opLegalizer; | |
| /// The conversion mode to use when legalizing operations. | |
| OpConversionMode mode; | |
| }; | |
| } // namespace mlir | |
| LogicalResult OperationConverter::convert(Operation *op, | |
| bool isRecursiveLegalization) { | |
| const ConversionConfig &config = rewriter.getConfig(); | |
| // Legalize the given operation. | |
| if (failed(opLegalizer.legalize(op))) { | |
| // Handle the case of a failed conversion for each of the different modes. | |
| // Full conversions expect all operations to be converted. | |
| if (mode == OpConversionMode::Full) { | |
| if (!isRecursiveLegalization) | |
| op->emitError() << "failed to legalize operation '" << op->getName() | |
| << "'"; | |
| return failure(); | |
| } | |
| // Partial conversions allow conversions to fail iff the operation was not | |
| // explicitly marked as illegal. If the user provided a `unlegalizedOps` | |
| // set, non-legalizable ops are added to that set. | |
| if (mode == OpConversionMode::Partial) { | |
| if (opLegalizer.isIllegal(op)) { | |
| if (!isRecursiveLegalization) | |
| op->emitError() << "failed to legalize operation '" << op->getName() | |
| << "' that was explicitly marked illegal"; | |
| return failure(); | |
| } | |
| if (config.unlegalizedOps && !isRecursiveLegalization) | |
| config.unlegalizedOps->insert(op); | |
| } | |
| } else if (mode == OpConversionMode::Analysis) { | |
| // Analysis conversions don't fail if any operations fail to legalize, | |
| // they are only interested in the operations that were successfully | |
| // legalized. | |
| if (config.legalizableOps && !isRecursiveLegalization) | |
| config.legalizableOps->insert(op); | |
| } | |
| return success(); | |
| } | |
| static LogicalResult | |
| legalizeUnresolvedMaterialization(RewriterBase &rewriter, | |
| UnrealizedConversionCastOp op, | |
| const UnresolvedMaterializationInfo &info) { | |
| assert(!op.use_empty() && | |
| "expected that dead materializations have already been DCE'd"); | |
| Operation::operand_range inputOperands = op.getOperands(); | |
| // Try to materialize the conversion. | |
| if (const TypeConverter *converter = info.getConverter()) { | |
| rewriter.setInsertionPoint(op); | |
| SmallVector<Value> newMaterialization; | |
| switch (info.getMaterializationKind()) { | |
| case MaterializationKind::Target: | |
| newMaterialization = converter->materializeTargetConversion( | |
| rewriter, op->getLoc(), op.getResultTypes(), inputOperands, | |
| info.getOriginalType()); | |
| break; | |
| case MaterializationKind::Source: | |
| assert(op->getNumResults() == 1 && "expected single result"); | |
| Value sourceMat = converter->materializeSourceConversion( | |
| rewriter, op->getLoc(), op.getResultTypes().front(), inputOperands); | |
| if (sourceMat) | |
| newMaterialization.push_back(sourceMat); | |
| break; | |
| } | |
| if (!newMaterialization.empty()) { | |
| #ifndef NDEBUG | |
| ValueRange newMaterializationRange(newMaterialization); | |
| assert(TypeRange(newMaterializationRange) == op.getResultTypes() && | |
| "materialization callback produced value of incorrect type"); | |
| #endif // NDEBUG | |
| rewriter.replaceOp(op, newMaterialization); | |
| return success(); | |
| } | |
| } | |
| InFlightDiagnostic diag = op->emitError() | |
| << "failed to legalize unresolved materialization " | |
| "from (" | |
| << inputOperands.getTypes() << ") to (" | |
| << op.getResultTypes() | |
| << ") that remained live after conversion"; | |
| diag.attachNote(op->getUsers().begin()->getLoc()) | |
| << "see existing live user here: " << *op->getUsers().begin(); | |
| return failure(); | |
| } | |
| template <typename Fn> | |
| LogicalResult | |
| OperationConverter::legalizeOperations(ArrayRef<Operation *> ops, Fn onFailure, | |
| bool isRecursiveLegalization) { | |
| const ConversionTarget &target = opLegalizer.getTarget(); | |
| // Compute the set of operations and blocks to convert. | |
| SmallVector<Operation *> toConvert; | |
| for (Operation *op : ops) { | |
| op->walk<WalkOrder::PreOrder, ForwardDominanceIterator<>>( | |
| [&](Operation *op) { | |
| toConvert.push_back(op); | |
| // Don't check this operation's children for conversion if the | |
| // operation is recursively legal. | |
| auto legalityInfo = target.isLegal(op); | |
| if (legalityInfo && legalityInfo->isRecursivelyLegal) | |
| return WalkResult::skip(); | |
| return WalkResult::advance(); | |
| }); | |
| } | |
| for (Operation *op : toConvert) { | |
| if (failed(convert(op, isRecursiveLegalization))) { | |
| // Failed to convert an operation. | |
| onFailure(); | |
| return failure(); | |
| } | |
| } | |
| return success(); | |
| } | |
| LogicalResult ConversionPatternRewriter::legalize(Operation *op) { | |
| return impl->opConverter.legalizeOperations(op, | |
| /*isRecursiveLegalization=*/true); | |
| } | |
| LogicalResult ConversionPatternRewriter::legalize(Region *r) { | |
| // Fast path: If the region is empty, there is nothing to legalize. | |
| if (r->empty()) | |
| return success(); | |
| // Gather a list of all operations to legalize. This is done before | |
| // converting the entry block signature because unrealized_conversion_cast | |
| // ops should not be included. | |
| SmallVector<Operation *> ops; | |
| for (Block &b : *r) | |
| for (Operation &op : b) | |
| ops.push_back(&op); | |
| // If the current pattern runs with a type converter, convert the entry block | |
| // signature. | |
| if (const TypeConverter *converter = impl->currentTypeConverter) { | |
| std::optional<TypeConverter::SignatureConversion> conversion = | |
| converter->convertBlockSignature(&r->front()); | |
| if (!conversion) | |
| return failure(); | |
| applySignatureConversion(&r->front(), *conversion, converter); | |
| } | |
| // Legalize all operations in the region. This includes all nested | |
| // operations. | |
| return impl->opConverter.legalizeOperations(ops, | |
| /*isRecursiveLegalization=*/true); | |
| } | |
| LogicalResult OperationConverter::applyConversion(ArrayRef<Operation *> ops) { | |
| // Convert each operation and discard rewrites on failure. | |
| ConversionPatternRewriterImpl &rewriterImpl = rewriter.getImpl(); | |
| LogicalResult status = legalizeOperations(ops, /*onFailure=*/[&]() { | |
| // Dialect conversion failed. | |
| if (rewriterImpl.config.allowPatternRollback) { | |
| // Rollback is allowed: restore the original IR. | |
| rewriterImpl.undoRewrites(); | |
| } else { | |
| // Rollback is not allowed: apply all modifications that have been | |
| // performed so far. | |
| rewriterImpl.applyRewrites(); | |
| } | |
| }); | |
| if (failed(status)) | |
| return failure(); | |
| // After a successful conversion, apply rewrites. | |
| rewriterImpl.applyRewrites(); | |
| // Reconcile all UnrealizedConversionCastOps that were inserted by the | |
| // dialect conversion frameworks. (Not the ones that were inserted by | |
| // patterns.) | |
| const DenseMap<UnrealizedConversionCastOp, UnresolvedMaterializationInfo> | |
| &materializations = rewriterImpl.unresolvedMaterializations; | |
| SmallVector<UnrealizedConversionCastOp> remainingCastOps; | |
| reconcileUnrealizedCasts(materializations, &remainingCastOps); | |
| // Drop markers. | |
| for (UnrealizedConversionCastOp castOp : remainingCastOps) | |
| castOp->removeAttr(kPureTypeConversionMarker); | |
| // Try to legalize all unresolved materializations. | |
| if (rewriter.getConfig().buildMaterializations) { | |
| // Use a new rewriter, so the modifications are not tracked for rollback | |
| // purposes etc. | |
| IRRewriter irRewriter(rewriterImpl.rewriter.getContext(), | |
| rewriter.getConfig().listener); | |
| for (UnrealizedConversionCastOp castOp : remainingCastOps) { | |
| auto it = materializations.find(castOp); | |
| assert(it != materializations.end() && "inconsistent state"); | |
| if (failed(legalizeUnresolvedMaterialization(irRewriter, castOp, | |
| it->second))) | |
| return failure(); | |
| } | |
| } | |
| return success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Type Conversion | |
| //===----------------------------------------------------------------------===// | |
| void TypeConverter::SignatureConversion::addInputs(unsigned origInputNo, | |
| ArrayRef<Type> types) { | |
| assert(!types.empty() && "expected valid types"); | |
| remapInput(origInputNo, /*newInputNo=*/argTypes.size(), types.size()); | |
| addInputs(types); | |
| } | |
| void TypeConverter::SignatureConversion::addInputs(ArrayRef<Type> types) { | |
| assert(!types.empty() && | |
| "1->0 type remappings don't need to be added explicitly"); | |
| argTypes.append(types.begin(), types.end()); | |
| } | |
| void TypeConverter::SignatureConversion::remapInput(unsigned origInputNo, | |
| unsigned newInputNo, | |
| unsigned newInputCount) { | |
| assert(!remappedInputs[origInputNo] && "input has already been remapped"); | |
| assert(newInputCount != 0 && "expected valid input count"); | |
| remappedInputs[origInputNo] = | |
| InputMapping{newInputNo, newInputCount, /*replacementValues=*/{}}; | |
| } | |
| void TypeConverter::SignatureConversion::remapInput( | |
| unsigned origInputNo, ArrayRef<Value> replacements) { | |
| assert(!remappedInputs[origInputNo] && "input has already been remapped"); | |
| remappedInputs[origInputNo] = InputMapping{ | |
| origInputNo, /*size=*/0, | |
| SmallVector<Value, 1>(replacements.begin(), replacements.end())}; | |
| } | |
| /// Internal implementation of the type conversion. | |
| /// This is used with either a Type or a Value as the first argument. | |
| /// - we can cache the context-free conversions until the last registered | |
| /// context-aware conversion. | |
| /// - we can't cache the result of type conversion happening after context-aware | |
| /// conversions, because the type converter may return different results for the | |
| /// same input type. | |
| LogicalResult | |
| TypeConverter::convertTypeImpl(PointerUnion<Type, Value> typeOrValue, | |
| SmallVectorImpl<Type> &results) const { | |
| assert(typeOrValue && "expected non-null type"); | |
| Type t = (isa<Value>(typeOrValue)) ? cast<Value>(typeOrValue).getType() | |
| : cast<Type>(typeOrValue); | |
| { | |
| std::shared_lock<decltype(cacheMutex)> cacheReadLock(cacheMutex, | |
| std::defer_lock); | |
| if (t.getContext()->isMultithreadingEnabled()) | |
| cacheReadLock.lock(); | |
| auto existingIt = cachedDirectConversions.find(t); | |
| if (existingIt != cachedDirectConversions.end()) { | |
| if (existingIt->second) | |
| results.push_back(existingIt->second); | |
| return success(existingIt->second != nullptr); | |
| } | |
| auto multiIt = cachedMultiConversions.find(t); | |
| if (multiIt != cachedMultiConversions.end()) { | |
| results.append(multiIt->second.begin(), multiIt->second.end()); | |
| return success(); | |
| } | |
| } | |
| // Walk the added converters in reverse order to apply the most recently | |
| // registered first. | |
| size_t currentCount = results.size(); | |
| // We can cache the context-free conversions until the last registered | |
| // context-aware conversion. But only if we're processing a Value right now. | |
| auto isCacheable = [&](int index) { | |
| int numberOfConversionsUntilContextAware = | |
| conversions.size() - 1 - contextAwareTypeConversionsIndex; | |
| return index < numberOfConversionsUntilContextAware; | |
| }; | |
| std::unique_lock<decltype(cacheMutex)> cacheWriteLock(cacheMutex, | |
| std::defer_lock); | |
| for (auto indexedConverter : llvm::enumerate(llvm::reverse(conversions))) { | |
| const ConversionCallbackFn &converter = indexedConverter.value(); | |
| std::optional<LogicalResult> result = converter(typeOrValue, results); | |
| if (!result) { | |
| assert(results.size() == currentCount && | |
| "failed type conversion should not change results"); | |
| continue; | |
| } | |
| if (!isCacheable(indexedConverter.index())) | |
| return success(); | |
| if (t.getContext()->isMultithreadingEnabled()) | |
| cacheWriteLock.lock(); | |
| if (!succeeded(*result)) { | |
| assert(results.size() == currentCount && | |
| "failed type conversion should not change results"); | |
| cachedDirectConversions.try_emplace(t, nullptr); | |
| return failure(); | |
| } | |
| auto newTypes = ArrayRef<Type>(results).drop_front(currentCount); | |
| if (newTypes.size() == 1) | |
| cachedDirectConversions.try_emplace(t, newTypes.front()); | |
| else | |
| cachedMultiConversions.try_emplace(t, llvm::to_vector<2>(newTypes)); | |
| return success(); | |
| } | |
| return failure(); | |
| } | |
| LogicalResult TypeConverter::convertType(Type t, | |
| SmallVectorImpl<Type> &results) const { | |
| return convertTypeImpl(t, results); | |
| } | |
| LogicalResult TypeConverter::convertType(Value v, | |
| SmallVectorImpl<Type> &results) const { | |
| return convertTypeImpl(v, results); | |
| } | |
| Type TypeConverter::convertType(Type t) const { | |
| // Use the multi-type result version to convert the type. | |
| SmallVector<Type, 1> results; | |
| if (failed(convertType(t, results))) | |
| return nullptr; | |
| // Check to ensure that only one type was produced. | |
| return results.size() == 1 ? results.front() : nullptr; | |
| } | |
| Type TypeConverter::convertType(Value v) const { | |
| // Use the multi-type result version to convert the type. | |
| SmallVector<Type, 1> results; | |
| if (failed(convertType(v, results))) | |
| return nullptr; | |
| // Check to ensure that only one type was produced. | |
| return results.size() == 1 ? results.front() : nullptr; | |
| } | |
| LogicalResult | |
| TypeConverter::convertTypes(TypeRange types, | |
| SmallVectorImpl<Type> &results) const { | |
| for (Type type : types) | |
| if (failed(convertType(type, results))) | |
| return failure(); | |
| return success(); | |
| } | |
| LogicalResult | |
| TypeConverter::convertTypes(ValueRange values, | |
| SmallVectorImpl<Type> &results) const { | |
| for (Value value : values) | |
| if (failed(convertType(value, results))) | |
| return failure(); | |
| return success(); | |
| } | |
| bool TypeConverter::isLegal(Type type) const { | |
| return convertType(type) == type; | |
| } | |
| bool TypeConverter::isLegal(Value value) const { | |
| return convertType(value) == value.getType(); | |
| } | |
| bool TypeConverter::isLegal(Operation *op) const { | |
| return isLegal(op->getOperands()) && isLegal(op->getResults()); | |
| } | |
| bool TypeConverter::isLegal(Region *region) const { | |
| return llvm::all_of( | |
| *region, [this](Block &block) { return isLegal(block.getArguments()); }); | |
| } | |
| bool TypeConverter::isSignatureLegal(FunctionType ty) const { | |
| if (!isLegal(ty.getInputs())) | |
| return false; | |
| if (!isLegal(ty.getResults())) | |
| return false; | |
| return true; | |
| } | |
| LogicalResult | |
| TypeConverter::convertSignatureArg(unsigned inputNo, Type type, | |
| SignatureConversion &result) const { | |
| // Try to convert the given input type. | |
| SmallVector<Type, 1> convertedTypes; | |
| if (failed(convertType(type, convertedTypes))) | |
| return failure(); | |
| // If this argument is being dropped, there is nothing left to do. | |
| if (convertedTypes.empty()) | |
| return success(); | |
| // Otherwise, add the new inputs. | |
| result.addInputs(inputNo, convertedTypes); | |
| return success(); | |
| } | |
| LogicalResult | |
| TypeConverter::convertSignatureArgs(TypeRange types, | |
| SignatureConversion &result, | |
| unsigned origInputOffset) const { | |
| for (unsigned i = 0, e = types.size(); i != e; ++i) | |
| if (failed(convertSignatureArg(origInputOffset + i, types[i], result))) | |
| return failure(); | |
| return success(); | |
| } | |
| LogicalResult | |
| TypeConverter::convertSignatureArg(unsigned inputNo, Value value, | |
| SignatureConversion &result) const { | |
| // Try to convert the given input type. | |
| SmallVector<Type, 1> convertedTypes; | |
| if (failed(convertType(value, convertedTypes))) | |
| return failure(); | |
| // If this argument is being dropped, there is nothing left to do. | |
| if (convertedTypes.empty()) | |
| return success(); | |
| // Otherwise, add the new inputs. | |
| result.addInputs(inputNo, convertedTypes); | |
| return success(); | |
| } | |
| LogicalResult | |
| TypeConverter::convertSignatureArgs(ValueRange values, | |
| SignatureConversion &result, | |
| unsigned origInputOffset) const { | |
| for (unsigned i = 0, e = values.size(); i != e; ++i) | |
| if (failed(convertSignatureArg(origInputOffset + i, values[i], result))) | |
| return failure(); | |
| return success(); | |
| } | |
| Value TypeConverter::materializeSourceConversion(OpBuilder &builder, | |
| Location loc, Type resultType, | |
| ValueRange inputs) const { | |
| for (const SourceMaterializationCallbackFn &fn : | |
| llvm::reverse(sourceMaterializations)) | |
| if (Value result = fn(builder, resultType, inputs, loc)) | |
| return result; | |
| return nullptr; | |
| } | |
| Value TypeConverter::materializeTargetConversion(OpBuilder &builder, | |
| Location loc, Type resultType, | |
| ValueRange inputs, | |
| Type originalType) const { | |
| SmallVector<Value> result = materializeTargetConversion( | |
| builder, loc, TypeRange(resultType), inputs, originalType); | |
| if (result.empty()) | |
| return nullptr; | |
| assert(result.size() == 1 && "expected single result"); | |
| return result.front(); | |
| } | |
| SmallVector<Value> TypeConverter::materializeTargetConversion( | |
| OpBuilder &builder, Location loc, TypeRange resultTypes, ValueRange inputs, | |
| Type originalType) const { | |
| for (const TargetMaterializationCallbackFn &fn : | |
| llvm::reverse(targetMaterializations)) { | |
| SmallVector<Value> result = | |
| fn(builder, resultTypes, inputs, loc, originalType); | |
| if (result.empty()) | |
| continue; | |
| assert(TypeRange(ValueRange(result)) == resultTypes && | |
| "callback produced incorrect number of values or values with " | |
| "incorrect types"); | |
| return result; | |
| } | |
| return {}; | |
| } | |
| std::optional<TypeConverter::SignatureConversion> | |
| TypeConverter::convertBlockSignature(Block *block) const { | |
| SignatureConversion conversion(block->getNumArguments()); | |
| if (failed(convertSignatureArgs(block->getArguments(), conversion))) | |
| return std::nullopt; | |
| return conversion; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Type attribute conversion | |
| //===----------------------------------------------------------------------===// | |
| TypeConverter::AttributeConversionResult | |
| TypeConverter::AttributeConversionResult::result(Attribute attr) { | |
| return AttributeConversionResult(attr, resultTag); | |
| } | |
| TypeConverter::AttributeConversionResult | |
| TypeConverter::AttributeConversionResult::na() { | |
| return AttributeConversionResult(nullptr, naTag); | |
| } | |
| TypeConverter::AttributeConversionResult | |
| TypeConverter::AttributeConversionResult::abort() { | |
| return AttributeConversionResult(nullptr, abortTag); | |
| } | |
| bool TypeConverter::AttributeConversionResult::hasResult() const { | |
| return impl.getInt() == resultTag; | |
| } | |
| bool TypeConverter::AttributeConversionResult::isNa() const { | |
| return impl.getInt() == naTag; | |
| } | |
| bool TypeConverter::AttributeConversionResult::isAbort() const { | |
| return impl.getInt() == abortTag; | |
| } | |
| Attribute TypeConverter::AttributeConversionResult::getResult() const { | |
| assert(hasResult() && "Cannot get result from N/A or abort"); | |
| return impl.getPointer(); | |
| } | |
| std::optional<Attribute> | |
| TypeConverter::convertTypeAttribute(Type type, Attribute attr) const { | |
| for (const TypeAttributeConversionCallbackFn &fn : | |
| llvm::reverse(typeAttributeConversions)) { | |
| AttributeConversionResult res = fn(type, attr); | |
| if (res.hasResult()) | |
| return res.getResult(); | |
| if (res.isAbort()) | |
| return std::nullopt; | |
| } | |
| return std::nullopt; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // FunctionOpInterfaceSignatureConversion | |
| //===----------------------------------------------------------------------===// | |
| static LogicalResult convertFuncOpTypes(FunctionOpInterface funcOp, | |
| const TypeConverter &typeConverter, | |
| ConversionPatternRewriter &rewriter) { | |
| FunctionType type = dyn_cast<FunctionType>(funcOp.getFunctionType()); | |
| if (!type) | |
| return failure(); | |
| // Convert the original function types. | |
| TypeConverter::SignatureConversion result(type.getNumInputs()); | |
| SmallVector<Type, 1> newResults; | |
| if (failed(typeConverter.convertSignatureArgs(type.getInputs(), result)) || | |
| failed(typeConverter.convertTypes(type.getResults(), newResults))) | |
| return failure(); | |
| if (!funcOp.getFunctionBody().empty()) | |
| rewriter.applySignatureConversion(&funcOp.getFunctionBody().front(), result, | |
| &typeConverter); | |
| // Update the function signature in-place. | |
| auto newType = FunctionType::get(rewriter.getContext(), | |
| result.getConvertedTypes(), newResults); | |
| rewriter.modifyOpInPlace(funcOp, [&] { funcOp.setType(newType); }); | |
| return success(); | |
| } | |
| /// Create a default conversion pattern that rewrites the type signature of a | |
| /// FunctionOpInterface op. This only supports ops which use FunctionType to | |
| /// represent their type. | |
| namespace { | |
| struct FunctionOpInterfaceSignatureConversion : public ConversionPattern { | |
| FunctionOpInterfaceSignatureConversion(StringRef functionLikeOpName, | |
| MLIRContext *ctx, | |
| const TypeConverter &converter, | |
| PatternBenefit benefit) | |
| : ConversionPattern(converter, functionLikeOpName, benefit, ctx) {} | |
| LogicalResult | |
| matchAndRewrite(Operation *op, ArrayRef<Value> /*operands*/, | |
| ConversionPatternRewriter &rewriter) const override { | |
| FunctionOpInterface funcOp = cast<FunctionOpInterface>(op); | |
| return convertFuncOpTypes(funcOp, *typeConverter, rewriter); | |
| } | |
| }; | |
| struct AnyFunctionOpInterfaceSignatureConversion | |
| : public OpInterfaceConversionPattern<FunctionOpInterface> { | |
| using OpInterfaceConversionPattern::OpInterfaceConversionPattern; | |
| LogicalResult | |
| matchAndRewrite(FunctionOpInterface funcOp, ArrayRef<Value> /*operands*/, | |
| ConversionPatternRewriter &rewriter) const override { | |
| return convertFuncOpTypes(funcOp, *typeConverter, rewriter); | |
| } | |
| }; | |
| } // namespace | |
| FailureOr<Operation *> | |
| mlir::convertOpResultTypes(Operation *op, ValueRange operands, | |
| const TypeConverter &converter, | |
| ConversionPatternRewriter &rewriter) { | |
| assert(op && "Invalid op"); | |
| Location loc = op->getLoc(); | |
| if (converter.isLegal(op)) | |
| return rewriter.notifyMatchFailure(loc, "op already legal"); | |
| OperationState newOp(loc, op->getName()); | |
| newOp.addOperands(operands); | |
| SmallVector<Type> newResultTypes; | |
| if (failed(converter.convertTypes(op->getResults(), newResultTypes))) | |
| return rewriter.notifyMatchFailure(loc, "couldn't convert return types"); | |
| newOp.addTypes(newResultTypes); | |
| newOp.addAttributes(op->getAttrs()); | |
| return rewriter.create(newOp); | |
| } | |
| void mlir::populateFunctionOpInterfaceTypeConversionPattern( | |
| StringRef functionLikeOpName, RewritePatternSet &patterns, | |
| const TypeConverter &converter, PatternBenefit benefit) { | |
| patterns.add<FunctionOpInterfaceSignatureConversion>( | |
| functionLikeOpName, patterns.getContext(), converter, benefit); | |
| } | |
| void mlir::populateAnyFunctionOpInterfaceTypeConversionPattern( | |
| RewritePatternSet &patterns, const TypeConverter &converter, | |
| PatternBenefit benefit) { | |
| patterns.add<AnyFunctionOpInterfaceSignatureConversion>( | |
| converter, patterns.getContext(), benefit); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ConversionTarget | |
| //===----------------------------------------------------------------------===// | |
| void ConversionTarget::setOpAction(OperationName op, | |
| LegalizationAction action) { | |
| legalOperations[op].action = action; | |
| } | |
| void ConversionTarget::setDialectAction(ArrayRef<StringRef> dialectNames, | |
| LegalizationAction action) { | |
| for (StringRef dialect : dialectNames) | |
| legalDialects[dialect] = action; | |
| } | |
| auto ConversionTarget::getOpAction(OperationName op) const | |
| -> std::optional<LegalizationAction> { | |
| std::optional<LegalizationInfo> info = getOpInfo(op); | |
| return info ? info->action : std::optional<LegalizationAction>(); | |
| } | |
| auto ConversionTarget::isLegal(Operation *op) const | |
| -> std::optional<LegalOpDetails> { | |
| std::optional<LegalizationInfo> info = getOpInfo(op->getName()); | |
| if (!info) | |
| return std::nullopt; | |
| // Returns true if this operation instance is known to be legal. | |
| auto isOpLegal = [&] { | |
| // Handle dynamic legality either with the provided legality function. | |
| if (info->action == LegalizationAction::Dynamic) { | |
| std::optional<bool> result = info->legalityFn(op); | |
| if (result) | |
| return *result; | |
| } | |
| // Otherwise, the operation is only legal if it was marked 'Legal'. | |
| return info->action == LegalizationAction::Legal; | |
| }; | |
| if (!isOpLegal()) | |
| return std::nullopt; | |
| // This operation is legal, compute any additional legality information. | |
| LegalOpDetails legalityDetails; | |
| if (info->isRecursivelyLegal) { | |
| auto legalityFnIt = opRecursiveLegalityFns.find(op->getName()); | |
| if (legalityFnIt != opRecursiveLegalityFns.end()) { | |
| legalityDetails.isRecursivelyLegal = | |
| legalityFnIt->second(op).value_or(true); | |
| } else { | |
| legalityDetails.isRecursivelyLegal = true; | |
| } | |
| } | |
| return legalityDetails; | |
| } | |
| bool ConversionTarget::isIllegal(Operation *op) const { | |
| std::optional<LegalizationInfo> info = getOpInfo(op->getName()); | |
| if (!info) | |
| return false; | |
| if (info->action == LegalizationAction::Dynamic) { | |
| std::optional<bool> result = info->legalityFn(op); | |
| if (!result) | |
| return false; | |
| return !(*result); | |
| } | |
| return info->action == LegalizationAction::Illegal; | |
| } | |
| static ConversionTarget::DynamicLegalityCallbackFn composeLegalityCallbacks( | |
| ConversionTarget::DynamicLegalityCallbackFn oldCallback, | |
| ConversionTarget::DynamicLegalityCallbackFn newCallback) { | |
| if (!oldCallback) | |
| return newCallback; | |
| auto chain = [oldCl = std::move(oldCallback), newCl = std::move(newCallback)]( | |
| Operation *op) -> std::optional<bool> { | |
| if (std::optional<bool> result = newCl(op)) | |
| return *result; | |
| return oldCl(op); | |
| }; | |
| return chain; | |
| } | |
| void ConversionTarget::setLegalityCallback( | |
| OperationName name, const DynamicLegalityCallbackFn &callback) { | |
| assert(callback && "expected valid legality callback"); | |
| auto *infoIt = legalOperations.find(name); | |
| assert(infoIt != legalOperations.end() && | |
| infoIt->second.action == LegalizationAction::Dynamic && | |
| "expected operation to already be marked as dynamically legal"); | |
| infoIt->second.legalityFn = | |
| composeLegalityCallbacks(std::move(infoIt->second.legalityFn), callback); | |
| } | |
| void ConversionTarget::markOpRecursivelyLegal( | |
| OperationName name, const DynamicLegalityCallbackFn &callback) { | |
| auto *infoIt = legalOperations.find(name); | |
| assert(infoIt != legalOperations.end() && | |
| infoIt->second.action != LegalizationAction::Illegal && | |
| "expected operation to already be marked as legal"); | |
| infoIt->second.isRecursivelyLegal = true; | |
| if (callback) | |
| opRecursiveLegalityFns[name] = composeLegalityCallbacks( | |
| std::move(opRecursiveLegalityFns[name]), callback); | |
| else | |
| opRecursiveLegalityFns.erase(name); | |
| } | |
| void ConversionTarget::setLegalityCallback( | |
| ArrayRef<StringRef> dialects, const DynamicLegalityCallbackFn &callback) { | |
| assert(callback && "expected valid legality callback"); | |
| for (StringRef dialect : dialects) | |
| dialectLegalityFns[dialect] = composeLegalityCallbacks( | |
| std::move(dialectLegalityFns[dialect]), callback); | |
| } | |
| void ConversionTarget::setLegalityCallback( | |
| const DynamicLegalityCallbackFn &callback) { | |
| assert(callback && "expected valid legality callback"); | |
| unknownLegalityFn = composeLegalityCallbacks(unknownLegalityFn, callback); | |
| } | |
| auto ConversionTarget::getOpInfo(OperationName op) const | |
| -> std::optional<LegalizationInfo> { | |
| // Check for info for this specific operation. | |
| const auto *it = legalOperations.find(op); | |
| if (it != legalOperations.end()) | |
| return it->second; | |
| // Check for info for the parent dialect. | |
| auto dialectIt = legalDialects.find(op.getDialectNamespace()); | |
| if (dialectIt != legalDialects.end()) { | |
| DynamicLegalityCallbackFn callback; | |
| auto dialectFn = dialectLegalityFns.find(op.getDialectNamespace()); | |
| if (dialectFn != dialectLegalityFns.end()) | |
| callback = dialectFn->second; | |
| return LegalizationInfo{dialectIt->second, /*isRecursivelyLegal=*/false, | |
| callback}; | |
| } | |
| // Otherwise, check if we mark unknown operations as dynamic. | |
| if (unknownLegalityFn) | |
| return LegalizationInfo{LegalizationAction::Dynamic, | |
| /*isRecursivelyLegal=*/false, unknownLegalityFn}; | |
| return std::nullopt; | |
| } | |
| #if MLIR_ENABLE_PDL_IN_PATTERNMATCH | |
| //===----------------------------------------------------------------------===// | |
| // PDL Configuration | |
| //===----------------------------------------------------------------------===// | |
| void PDLConversionConfig::notifyRewriteBegin(PatternRewriter &rewriter) { | |
| auto &rewriterImpl = | |
| static_cast<ConversionPatternRewriter &>(rewriter).getImpl(); | |
| rewriterImpl.currentTypeConverter = getTypeConverter(); | |
| } | |
| void PDLConversionConfig::notifyRewriteEnd(PatternRewriter &rewriter) { | |
| auto &rewriterImpl = | |
| static_cast<ConversionPatternRewriter &>(rewriter).getImpl(); | |
| rewriterImpl.currentTypeConverter = nullptr; | |
| } | |
| /// Remap the given value using the rewriter and the type converter in the | |
| /// provided config. | |
| static FailureOr<SmallVector<Value>> | |
| pdllConvertValues(ConversionPatternRewriter &rewriter, ValueRange values) { | |
| SmallVector<Value> mappedValues; | |
| if (failed(rewriter.getRemappedValues(values, mappedValues))) | |
| return failure(); | |
| return std::move(mappedValues); | |
| } | |
| void mlir::registerConversionPDLFunctions(RewritePatternSet &patterns) { | |
| patterns.getPDLPatterns().registerRewriteFunction( | |
| "convertValue", | |
| [](PatternRewriter &rewriter, Value value) -> FailureOr<Value> { | |
| auto results = pdllConvertValues( | |
| static_cast<ConversionPatternRewriter &>(rewriter), value); | |
| if (failed(results)) | |
| return failure(); | |
| return results->front(); | |
| }); | |
| patterns.getPDLPatterns().registerRewriteFunction( | |
| "convertValues", [](PatternRewriter &rewriter, ValueRange values) { | |
| return pdllConvertValues( | |
| static_cast<ConversionPatternRewriter &>(rewriter), values); | |
| }); | |
| patterns.getPDLPatterns().registerRewriteFunction( | |
| "convertType", | |
| [](PatternRewriter &rewriter, Type type) -> FailureOr<Type> { | |
| auto &rewriterImpl = | |
| static_cast<ConversionPatternRewriter &>(rewriter).getImpl(); | |
| if (const TypeConverter *converter = | |
| rewriterImpl.currentTypeConverter) { | |
| if (Type newType = converter->convertType(type)) | |
| return newType; | |
| return failure(); | |
| } | |
| return type; | |
| }); | |
| patterns.getPDLPatterns().registerRewriteFunction( | |
| "convertTypes", | |
| [](PatternRewriter &rewriter, | |
| TypeRange types) -> FailureOr<SmallVector<Type>> { | |
| auto &rewriterImpl = | |
| static_cast<ConversionPatternRewriter &>(rewriter).getImpl(); | |
| const TypeConverter *converter = rewriterImpl.currentTypeConverter; | |
| if (!converter) | |
| return SmallVector<Type>(types); | |
| SmallVector<Type> remappedTypes; | |
| if (failed(converter->convertTypes(types, remappedTypes))) | |
| return failure(); | |
| return std::move(remappedTypes); | |
| }); | |
| } | |
| #endif // MLIR_ENABLE_PDL_IN_PATTERNMATCH | |
| //===----------------------------------------------------------------------===// | |
| // Op Conversion Entry Points | |
| //===----------------------------------------------------------------------===// | |
| /// This is the type of Action that is dispatched when a conversion is applied. | |
| class ApplyConversionAction | |
| : public tracing::ActionImpl<ApplyConversionAction> { | |
| public: | |
| using Base = tracing::ActionImpl<ApplyConversionAction>; | |
| ApplyConversionAction(ArrayRef<IRUnit> irUnits) : Base(irUnits) {} | |
| static constexpr StringLiteral tag = "apply-conversion"; | |
| static constexpr StringLiteral desc = | |
| "Encapsulate the application of a dialect conversion"; | |
| void print(raw_ostream &os) const override { os << tag; } | |
| }; | |
| static LogicalResult applyConversion(ArrayRef<Operation *> ops, | |
| const ConversionTarget &target, | |
| const FrozenRewritePatternSet &patterns, | |
| ConversionConfig config, | |
| OpConversionMode mode) { | |
| if (ops.empty()) | |
| return success(); | |
| MLIRContext *ctx = ops.front()->getContext(); | |
| LogicalResult status = success(); | |
| SmallVector<IRUnit> irUnits(ops.begin(), ops.end()); | |
| ctx->executeAction<ApplyConversionAction>( | |
| [&] { | |
| OperationConverter opConverter(ops.front()->getContext(), target, | |
| patterns, config, mode); | |
| status = opConverter.applyConversion(ops); | |
| }, | |
| irUnits); | |
| return status; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Partial Conversion | |
| //===----------------------------------------------------------------------===// | |
| LogicalResult mlir::applyPartialConversion( | |
| ArrayRef<Operation *> ops, const ConversionTarget &target, | |
| const FrozenRewritePatternSet &patterns, ConversionConfig config) { | |
| return applyConversion(ops, target, patterns, config, | |
| OpConversionMode::Partial); | |
| } | |
| LogicalResult | |
| mlir::applyPartialConversion(Operation *op, const ConversionTarget &target, | |
| const FrozenRewritePatternSet &patterns, | |
| ConversionConfig config) { | |
| return applyPartialConversion(llvm::ArrayRef(op), target, patterns, config); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Full Conversion | |
| //===----------------------------------------------------------------------===// | |
| LogicalResult mlir::applyFullConversion(ArrayRef<Operation *> ops, | |
| const ConversionTarget &target, | |
| const FrozenRewritePatternSet &patterns, | |
| ConversionConfig config) { | |
| return applyConversion(ops, target, patterns, config, OpConversionMode::Full); | |
| } | |
| LogicalResult mlir::applyFullConversion(Operation *op, | |
| const ConversionTarget &target, | |
| const FrozenRewritePatternSet &patterns, | |
| ConversionConfig config) { | |
| return applyFullConversion(llvm::ArrayRef(op), target, patterns, config); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Analysis Conversion | |
| //===----------------------------------------------------------------------===// | |
| /// Find a common IsolatedFromAbove ancestor of the given ops. If at least one | |
| /// op is a top-level module op (which is expected to be isolated from above), | |
| /// return that op. | |
| static Operation *findCommonAncestor(ArrayRef<Operation *> ops) { | |
| // Check if there is a top-level operation within `ops`. If so, return that | |
| // op. | |
| for (Operation *op : ops) { | |
| if (!op->getParentOp()) { | |
| #ifndef NDEBUG | |
| assert(op->hasTrait<OpTrait::IsIsolatedFromAbove>() && | |
| "expected top-level op to be isolated from above"); | |
| for (Operation *other : ops) | |
| assert(op->isAncestor(other) && | |
| "expected ops to have a common ancestor"); | |
| #endif // NDEBUG | |
| return op; | |
| } | |
| } | |
| // No top-level op. Find a common ancestor. | |
| Operation *commonAncestor = | |
| ops.front()->getParentWithTrait<OpTrait::IsIsolatedFromAbove>(); | |
| for (Operation *op : ops.drop_front()) { | |
| while (!commonAncestor->isProperAncestor(op)) { | |
| commonAncestor = | |
| commonAncestor->getParentWithTrait<OpTrait::IsIsolatedFromAbove>(); | |
| assert(commonAncestor && | |
| "expected to find a common isolated from above ancestor"); | |
| } | |
| } | |
| return commonAncestor; | |
| } | |
| LogicalResult mlir::applyAnalysisConversion( | |
| ArrayRef<Operation *> ops, ConversionTarget &target, | |
| const FrozenRewritePatternSet &patterns, ConversionConfig config) { | |
| #ifndef NDEBUG | |
| if (config.legalizableOps) | |
| assert(config.legalizableOps->empty() && "expected empty set"); | |
| #endif // NDEBUG | |
| // Clone closted common ancestor that is isolated from above. | |
| Operation *commonAncestor = findCommonAncestor(ops); | |
| IRMapping mapping; | |
| Operation *clonedAncestor = commonAncestor->clone(mapping); | |
| // Compute inverse IR mapping. | |
| DenseMap<Operation *, Operation *> inverseOperationMap; | |
| for (auto &it : mapping.getOperationMap()) | |
| inverseOperationMap[it.second] = it.first; | |
| // Convert the cloned operations. The original IR will remain unchanged. | |
| SmallVector<Operation *> opsToConvert = llvm::map_to_vector( | |
| ops, [&](Operation *op) { return mapping.lookup(op); }); | |
| LogicalResult status = applyConversion(opsToConvert, target, patterns, config, | |
| OpConversionMode::Analysis); | |
| // Remap `legalizableOps`, so that they point to the original ops and not the | |
| // cloned ops. | |
| if (config.legalizableOps) { | |
| DenseSet<Operation *> originalLegalizableOps; | |
| for (Operation *op : *config.legalizableOps) | |
| originalLegalizableOps.insert(inverseOperationMap[op]); | |
| *config.legalizableOps = std::move(originalLegalizableOps); | |
| } | |
| // Erase the cloned IR. | |
| clonedAncestor->erase(); | |
| return status; | |
| } | |
| LogicalResult | |
| mlir::applyAnalysisConversion(Operation *op, ConversionTarget &target, | |
| const FrozenRewritePatternSet &patterns, | |
| ConversionConfig config) { | |
| return applyAnalysisConversion(llvm::ArrayRef(op), target, patterns, config); | |
| } |