[Mlir-commits] [mlir] [mlir][linalg] Remove abandoned `Detensorize` pass (PR #177579)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Fri Jan 23 05:09:11 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-linalg
@llvm/pr-subscribers-mlir
Author: Matthias Springer (matthias-springer)
<details>
<summary>Changes</summary>
---
Patch is 50.85 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/177579.diff
11 Files Affected:
- (modified) mlir/include/mlir/Dialect/Linalg/Passes.td (-37)
- (modified) mlir/lib/Dialect/Linalg/Transforms/CMakeLists.txt (-1)
- (removed) mlir/lib/Dialect/Linalg/Transforms/Detensorize.cpp (-569)
- (removed) mlir/test/Dialect/Linalg/detensorize_0d.mlir (-102)
- (removed) mlir/test/Dialect/Linalg/detensorize_br_operands.mlir (-45)
- (removed) mlir/test/Dialect/Linalg/detensorize_entry_block.mlir (-21)
- (removed) mlir/test/Dialect/Linalg/detensorize_if.mlir (-177)
- (removed) mlir/test/Dialect/Linalg/detensorize_trivial.mlir (-44)
- (removed) mlir/test/Dialect/Linalg/detensorize_while.mlir (-71)
- (removed) mlir/test/Dialect/Linalg/detensorize_while_impure_cf.mlir (-104)
- (removed) mlir/test/Dialect/Linalg/detensorize_while_pure_cf.mlir (-58)
``````````diff
diff --git a/mlir/include/mlir/Dialect/Linalg/Passes.td b/mlir/include/mlir/Dialect/Linalg/Passes.td
index 42d4d2083fc1c..f48ea9849e237 100644
--- a/mlir/include/mlir/Dialect/Linalg/Passes.td
+++ b/mlir/include/mlir/Dialect/Linalg/Passes.td
@@ -178,43 +178,6 @@ def LinalgFoldIntoElementwisePass : Pass<"linalg-fold-into-elementwise"> {
}];
}
-def LinalgDetensorizePass : InterfacePass<"linalg-detensorize", "FunctionOpInterface"> {
- let summary = "Detensorize linalg ops";
- let dependentDialects = [];
-
- let description = [{
- Detensoring is the process through which a tensor value is converted to one
- or potentially more primitive value(s). During this process, operations with
- such detensored operands are also converted to an equivalent form that works
- on primitives.
-
- The detensoring process is driven by linalg-on-tensor ops. In particular, a
- linalg-on-tensor op is checked to see whether *all* its operands can be
- detensored. If so, those operands are converted to their primitive
- counterparts and the linalg op is replaced by an equivalent op that takes
- those new primitive values as operands. Therefore, detensoring an op can be
- divided into 2 main logical phases:
-
- 1. Detect/match an op that can be detensored.
- 2. Detensor the operands of the op and replace it with a primitive
- equivalent.
-
- In addition to detensoring individual ops, this pass detensors internal
- control flow inside a function. All blocks except for the entry block are
- detensored by converting their arguments whenever possible.
-
- This can be run on any FunctionOpInterface op and must not be
- run on others. This is because it performs specific legalization of the
- blocks that make up the body, which it assumes has is a FunctionOpInterface.
- }];
- let options = [
- Option<"aggressiveMode", "aggressive-mode", "bool", /*default=*/"false",
- "Detensorize all ops that qualify for detensoring along with branch"
- " operands and basic-block arguments.">
-
- ];
-}
-
def LinalgBlockPackMatmul : Pass<"linalg-block-pack-matmul"> {
let summary = "Convert linalg matmul ops to block layout and back";
let description = [{
diff --git a/mlir/lib/Dialect/Linalg/Transforms/CMakeLists.txt b/mlir/lib/Dialect/Linalg/Transforms/CMakeLists.txt
index fb39e18691e03..a2149478e4c2d 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/Linalg/Transforms/CMakeLists.txt
@@ -7,7 +7,6 @@ add_mlir_dialect_library(MLIRLinalgTransforms
ConvertConv2DToImg2Col.cpp
DataLayoutPropagation.cpp
DecomposeLinalgOps.cpp
- Detensorize.cpp
DropUnitDims.cpp
ElementwiseOpFusion.cpp
ElementwiseToLinalg.cpp
diff --git a/mlir/lib/Dialect/Linalg/Transforms/Detensorize.cpp b/mlir/lib/Dialect/Linalg/Transforms/Detensorize.cpp
deleted file mode 100644
index 830905495e759..0000000000000
--- a/mlir/lib/Dialect/Linalg/Transforms/Detensorize.cpp
+++ /dev/null
@@ -1,569 +0,0 @@
-//===- Detensorize.cpp - Linalg transformations as patterns ----------===//
-//
-// 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/Dialect/Linalg/Passes.h"
-
-#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
-#include "mlir/Dialect/Func/Transforms/FuncConversions.h"
-#include "mlir/Dialect/Linalg/IR/Linalg.h"
-#include "mlir/Dialect/Tensor/IR/Tensor.h"
-#include "mlir/IR/OpDefinition.h"
-#include "mlir/Transforms/DialectConversion.h"
-#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
-#include <utility>
-
-namespace mlir {
-#define GEN_PASS_DEF_LINALGDETENSORIZEPASS
-#include "mlir/Dialect/Linalg/Passes.h.inc"
-} // namespace mlir
-
-using namespace mlir;
-using namespace mlir::linalg;
-
-static Value sourceMaterializationCallback(OpBuilder &builder, Type type,
- ValueRange inputs, Location loc) {
- assert(inputs.size() == 1);
- auto inputType = inputs[0].getType();
- if (isa<TensorType>(inputType))
- return nullptr;
-
- // A detensored value is converted back by creating a new tensor from its
- // element(s).
- return tensor::FromElementsOp::create(
- builder, loc, RankedTensorType::get({}, inputType), inputs[0]);
-}
-
-namespace {
-/// Defines the criteria a TensorType must follow in order to be considered
-/// "detensorable".
-///
-/// NOTE: For now, only 0-D tensors are supported.
-///
-/// Returns true if tensorType can be detensored.
-bool canBeDetensored(TensorType tensorType) {
- return tensorType.hasRank() && tensorType.getRank() == 0;
-}
-
-bool shouldBeDetensored(Operation *op, TypeConverter typeConverter) {
- GenericOp genericOp = dyn_cast_or_null<GenericOp>(op);
- return genericOp &&
- llvm::all_of(genericOp->getOpOperands(), [&](OpOperand &opOperand) {
- return !typeConverter.isLegal(opOperand.get().getType());
- });
-}
-
-/// A conversion pattern for detensoring `linalg.generic` ops.
-class DetensorizeGenericOp : public OpConversionPattern<GenericOp> {
-public:
- using OpConversionPattern::OpConversionPattern;
- LogicalResult
- matchAndRewrite(GenericOp op, OpAdaptor adaptor,
- ConversionPatternRewriter &rewriter) const override {
- Block *originalBlock = op->getBlock();
-
- // Gather some information about the op before inlining its region.
- Block *opEntryBlock = &*op.getRegion().begin();
- YieldOp yieldOp = dyn_cast<YieldOp>(op.getRegion().back().getTerminator());
-
- // Split the op's region before the op. This way, we have a clear insertion
- // point in which the op can be inlined.
- Block *newBlock = rewriter.splitBlock(originalBlock, Block::iterator(op));
- rewriter.inlineRegionBefore(op.getRegion(), newBlock);
- // Now that op's region is inlined, the operands of its YieldOp are mapped
- // to the materialized target values. Therefore, we can replace the op's
- // uses with those of its YielOp's operands.
- rewriter.replaceOp(op, yieldOp->getOperands());
-
- // No need for these intermediate blocks, merge them into 1.
- rewriter.mergeBlocks(opEntryBlock, originalBlock, adaptor.getOperands());
- rewriter.mergeBlocks(newBlock, originalBlock, {});
-
- rewriter.eraseOp(&*Block::iterator(yieldOp));
-
- return success();
- }
-};
-
-/// A conversion pattern for detensoring internal (non-entry) blocks within a
-/// function.
-struct FunctionNonEntryBlockConversion
- : public OpInterfaceConversionPattern<FunctionOpInterface> {
- FunctionNonEntryBlockConversion(MLIRContext *ctx, TypeConverter &converter,
- DenseSet<BlockArgument> blockArgsToDetensor)
- : OpInterfaceConversionPattern(converter, ctx),
- blockArgsToDetensor(std::move(blockArgsToDetensor)) {}
-
- LogicalResult
- matchAndRewrite(FunctionOpInterface op, ArrayRef<Value> operands,
- ConversionPatternRewriter &rewriter) const override {
- rewriter.startOpModification(op);
- Region ®ion = op.getFunctionBody();
-
- for (Block &block :
- llvm::make_early_inc_range(llvm::drop_begin(region, 1))) {
- TypeConverter::SignatureConversion conversion(
- /*numOrigInputs=*/block.getNumArguments());
-
- for (BlockArgument blockArgument : block.getArguments()) {
- int idx = blockArgument.getArgNumber();
-
- if (blockArgsToDetensor.count(blockArgument))
- conversion.addInputs(idx, {getTypeConverter()->convertType(
- block.getArgumentTypes()[idx])});
- else
- conversion.addInputs(idx, {block.getArgumentTypes()[idx]});
- }
-
- rewriter.applySignatureConversion(&block, conversion, getTypeConverter());
- }
-
- rewriter.finalizeOpModification(op);
- return success();
- }
-
-private:
- const DenseSet<BlockArgument> blockArgsToDetensor;
-};
-
-class DetensorizeTypeConverter : public TypeConverter {
-public:
- DetensorizeTypeConverter() {
- addConversion([](Type type) { return type; });
-
- // A TensorType that can be detensored, is converted to the underlying
- // element type.
- addConversion([](TensorType tensorType) -> Type {
- if (canBeDetensored(tensorType))
- return tensorType.getElementType();
-
- return tensorType;
- });
-
- // A tensor value is detensoried by extracting its element(s).
- addTargetMaterialization([](OpBuilder &builder, Type type,
- ValueRange inputs, Location loc) -> Value {
- return tensor::ExtractOp::create(builder, loc, inputs[0], ValueRange{});
- });
-
- addSourceMaterialization(sourceMaterializationCallback);
- }
-};
-
-/// @see LinalgDetensorize in Linalg/Passes.td for more details.
-struct LinalgDetensorize
- : public impl::LinalgDetensorizePassBase<LinalgDetensorize> {
- using impl::LinalgDetensorizePassBase<
- LinalgDetensorize>::LinalgDetensorizePassBase;
- LinalgDetensorize() = default;
-
- class CostModel {
- public:
- virtual ~CostModel() = default;
-
- /// A cost model algorithm computes the following outputs:
- ///
- /// - opsToDetensor: the list of linalg ops that should be
- /// detensored.
- ///
- /// - blockArgsToDetensor: since the operands and results of detensored
- /// linalg ops can cross the BB boundary (e.g. a linalg op's input can come
- /// from a BB argument and a linalg op's output can be passed to successor
- /// BBs), we need to maintain the sub-set of arguments that should be
- /// detensored (i.e. converted by typeConverter) for each affected BB.
- ///
- /// Example:
- ///
- /// For the following snippet:
- /// ...
- /// ^bb1(%6: tensor<i32>, %9: tensor<i32>):
- /// %7 = tensor.empty() : tensor<i32>
- /// %8 = linalg.generic #attrs
- /// ins(%6, %6 : tensor<i32>, tensor<i32>)
- /// outs(%7 : tensor<i32>) {
- /// ^bb0(%arg0: i32, %arg1: i32, %arg2: i32):
- /// %9 = arith.addi %arg0, %arg1 : i32
- /// linalg.yield %9 : i32
- /// } -> tensor<i32>
- /// %10 = "some.op"(%9)
- /// br ^bb2(%8 : tensor<i32>)
- /// ...
- ///
- /// if the cost model decides that the linalg.generic op should be
- /// detensored, then:
- /// - opsToDetensor should be = {linalg.generic{add}}.
- /// - blockArgsToDetensor should be = {bb1 -> {0}, bb2 -> {0}}.
- virtual void compute(FunctionOpInterface func,
- DetensorizeTypeConverter typeConverter,
- DenseSet<Operation *> &opsToDetensor,
- DenseSet<BlockArgument> &blockArgsToDetensor) = 0;
-
- /// From the blockArgsToDetensor set computed by a CostModel
- /// implementation, this method computes the corresponding branch op
- /// detensoring. The result is a map from a branch op to a subset of indices
- /// of its operands. The indices specify which of the branch op's operands
- /// should be detensored.
- ///
- /// For the previous example, this method would compute: {bb2 -> {0}}.
- static DenseMap<Operation *, DenseSet<int>> computeBranchOpDetensoring(
- const DenseSet<BlockArgument> &blockArgsToDetensor) {
- DenseMap<Operation *, DenseSet<int>> detensorableBranchOps;
-
- for (auto blockArgumentElem : blockArgsToDetensor) {
- Block *block = blockArgumentElem.getOwner();
-
- for (PredecessorIterator pred = block->pred_begin();
- pred != block->pred_end(); ++pred) {
- BranchOpInterface terminator =
- dyn_cast<BranchOpInterface>((*pred)->getTerminator());
- auto blockOperands =
- terminator.getSuccessorOperands(pred.getSuccessorIndex());
-
- if (blockOperands.empty() ||
- blockOperands.isOperandProduced(blockArgumentElem.getArgNumber()))
- continue;
-
- detensorableBranchOps[terminator].insert(
- blockOperands.getOperandIndex(blockArgumentElem.getArgNumber()));
- }
- }
-
- return detensorableBranchOps;
- }
- };
-
- /// Detensorize linalg ops involved in control-flow within a function.
- ///
- /// This model starts from BranchOps and CondBranchOps within a function. For
- /// each such branch, the model then walks the use-def chain for the branch's
- /// condition backwards in order to understand where the condition's value
- /// comes from. If the condition value is (indirectly) computed by a linalg op
- /// that can be detensored, the model then continues walking the use-def chain
- /// in order to understand where the linalg op's operands come from. This
- /// leads to discovering a "detensoring component". A detensoring component is
- /// the set of operations + block arguments that are involved in control-flow
- /// AND can be detensored.
- class ControlFlowDetectionModel : public CostModel {
- public:
- void compute(FunctionOpInterface func,
- DetensorizeTypeConverter typeConverter,
- DenseSet<Operation *> &opsToDetensor,
- DenseSet<BlockArgument> &blockArgsToDetensor) override {
- SmallVector<Value> workList;
-
- func->walk([&](cf::CondBranchOp condBr) {
- llvm::append_range(workList, condBr.getOperands());
- });
-
- func->walk([&](cf::BranchOp br) {
- llvm::append_range(workList, br.getOperands());
- });
-
- DenseSet<Value> visitedValues;
- DenseSet<Operation *> visitedOps;
-
- // For a (to-be-detesored) value, check if it "escapes" the block by being
- // passed to terminator. If it does, then workList is updated with the
- // corresponding argument to the successor block.
- auto updateWorkListWithSuccessorArguments =
- [&](Value value, BranchOpInterface terminator) {
- if (!terminator)
- return;
-
- for (auto operandIdx :
- llvm::seq<unsigned>(0, terminator->getOperands().size())) {
- Value operand = terminator->getOperand(operandIdx);
-
- if (operand == value) {
- auto succBlockArg =
- terminator.getSuccessorBlockArgument(operandIdx);
-
- if (succBlockArg && !blockArgsToDetensor.count(*succBlockArg))
- workList.push_back(*succBlockArg);
- }
- }
- };
-
- while (!workList.empty()) {
- Value currentItem = workList.pop_back_val();
-
- if (!visitedValues.insert(currentItem).second)
- continue;
-
- // 1 - Look forward:
- // 1.1 - If currentItem escapes to one or more successors, add
- // the corresponding successor arguments to workList.
- updateWorkListWithSuccessorArguments(
- currentItem, dyn_cast<BranchOpInterface>(
- currentItem.getParentBlock()->getTerminator()));
-
- // 1.2 - For each user of currentItem, add the defined values to
- // workList. This way, the user ops can be inspected later if they are
- // detensorable and if so, their operands will be added to workList to
- // potentially discover other parts of the detensorable component.
- for (auto *user : currentItem.getUsers())
- llvm::append_range(workList, user->getResults());
-
- // 2 - Look backward:
- // 2.1 - The current item is defined by a block argument. If the owner
- // block is a non-entry one, then:
- // * Add the argument to blockArgsToDetensor.
- // * Walk the use-def chain backwards to add each predecessor's
- // terminator-operands corresponding to currentItem to workList.
- if (auto currentItemBlockArgument =
- dyn_cast<BlockArgument>(currentItem)) {
- Block *ownerBlock = currentItemBlockArgument.getOwner();
-
- // Function arguments are not detensored/converted.
- if (&*ownerBlock->getParent()->begin() == ownerBlock)
- continue;
-
- // This inner-block argument is involved in control-flow, it should be
- // detensored.
- blockArgsToDetensor.insert(currentItemBlockArgument);
-
- for (PredecessorIterator pred = ownerBlock->pred_begin();
- pred != ownerBlock->pred_end(); ++pred) {
- BranchOpInterface predTerminator =
- dyn_cast<BranchOpInterface>((*pred)->getTerminator());
-
- // TODO: For now, we give up if any of the control-flow components
- // in a function is not detensorable. Fix that.
- if (!predTerminator) {
- opsToDetensor.clear();
- blockArgsToDetensor.clear();
- return;
- }
-
- auto ownerBlockOperands =
- predTerminator.getSuccessorOperands(pred.getSuccessorIndex());
-
- if (ownerBlockOperands.empty() ||
- ownerBlockOperands.isOperandProduced(
- currentItemBlockArgument.getArgNumber()))
- continue;
-
- // For each predecessor, add the value it passes to that argument to
- // workList to find out how it's computed.
- workList.push_back(
- ownerBlockOperands[currentItemBlockArgument.getArgNumber()]);
- }
-
- continue;
- }
-
- Operation *currentItemDefiningOp = currentItem.getDefiningOp();
-
- if (!visitedOps.insert(currentItemDefiningOp).second)
- continue;
-
- // 2.2 - The current item is computed by a GenericOp. If the op should
- // be detensored, then:
- // * Add it to opsToDetensor.
- // * Add its operands to workList to discover other parts of the
- // potentially detensorable component.
- if (auto genericOp = dyn_cast<GenericOp>(currentItemDefiningOp)) {
- // The op was encountered already, no need to inspect it again.
- if (opsToDetensor.count(genericOp))
- continue;
-
- // The op should not be detensored, give up on it but continue with
- // discovering the rest of the control-flow component.
- if (!shouldBeDetensored(genericOp, typeConverter)) {
- continue;
- }
-
- opsToDetensor.insert(genericOp);
- llvm::append_range(workList, genericOp.getInputs());
- continue;
- }
-
- // 2.3 - The current item is the result of a FromElementsOp, it will be
- // trivially detensored later as part of canonicalization patterns
- // applied at the end of detensoring.
- //
- // Note: No need to check whether the result type of this op is
- // detensorable since if it wasn't we wouldn't reach that point in the
- // work list.
- if (isa<tensor::FromElementsOp>(currentItemDefiningOp))
- continue;
-
- // 2.4 - The current item is the result of a scalar op, add all its
- // operands to the work list.
- if (llvm::all_of(
- currentItemDefiningOp->getResultTypes(),
- [&](Type resultType) { return resultType.isIntOrFloat(); }))
- llvm::append_range(workList, currentItemDefiningOp->getOperands());
- }
-
- // Since the cost model gives up on some ops (see the details of step 2.2
- // above), block arguments that correspond to the values produced by those
- // ops should not be detensored as well.
-
- DenseSet<BlockArgument> blockArgsToRemove;
-
- for (auto &blockArg : blockArgsToDetensor) {
- Block *block = blockArg.getParentBlock();
-
- // For the potentially detensorable block argument, find the
- // corresponding operands in predecessor blocks.
- for (PredecessorIterator pred = block->pred_begin();
- pred != block->pred_end(); ++pred) {
- BranchOpInterface terminator =
- dyn_cast<BranchOpInterface>((*pred)->getTerminator());
- auto blockOperands =
- terminator.getSuccessorOperands(pred.getSuccessorIndex());
-
- if (blockOperands.empty() ||
- blockOperands.isOperandProduced(blockArg.getArgNumber()))
- ...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/177579
More information about the Mlir-commits
mailing list