Skip to content

Conversation

@linuxlonelyeagle
Copy link
Member

This PR adds the SimplifyTrivialLoops pattern to affine.for, and removes the promote-single-iter functionality from affine-loop-normalize, since scf.for also has the capability to eliminate loops with a trip count of 1.

@llvmbot
Copy link
Member

llvmbot commented Oct 25, 2025

@llvm/pr-subscribers-mlir

@llvm/pr-subscribers-mlir-affine

Author: lonely eagle (linuxlonelyeagle)

Changes

This PR adds the SimplifyTrivialLoops pattern to affine.for, and removes the promote-single-iter functionality from affine-loop-normalize, since scf.for also has the capability to eliminate loops with a trip count of 1.


Full diff: https://github.com/llvm/llvm-project/pull/165091.diff

10 Files Affected:

  • (modified) mlir/include/mlir/Dialect/Affine/IR/AffineOps.td (+1)
  • (modified) mlir/include/mlir/Dialect/Affine/Passes.h (+1-2)
  • (modified) mlir/include/mlir/Dialect/Affine/Passes.td (-4)
  • (modified) mlir/include/mlir/Dialect/Affine/Utils.h (+2-5)
  • (modified) mlir/lib/Dialect/Affine/IR/AffineOps.cpp (+40)
  • (modified) mlir/lib/Dialect/Affine/Transforms/AffineLoopNormalize.cpp (+4-6)
  • (modified) mlir/lib/Dialect/Affine/Utils/Utils.cpp (+1-4)
  • (modified) mlir/test/Dialect/Affine/affine-loop-normalize.mlir (-21)
  • (modified) mlir/test/Dialect/Affine/canonicalize.mlir (+23-9)
  • (modified) mlir/test/Dialect/Affine/raise-memref.mlir (+6-10)
diff --git a/mlir/include/mlir/Dialect/Affine/IR/AffineOps.td b/mlir/include/mlir/Dialect/Affine/IR/AffineOps.td index 12a79358d42f1..e52b7d2090d53 100644 --- a/mlir/include/mlir/Dialect/Affine/IR/AffineOps.td +++ b/mlir/include/mlir/Dialect/Affine/IR/AffineOps.td @@ -330,6 +330,7 @@ def AffineForOp : Affine_Op<"for", Speculation::Speculatability getSpeculatability(); }]; + let hasCanonicalizer = 1; let hasCustomAssemblyFormat = 1; let hasFolder = 1; let hasRegionVerifier = 1; diff --git a/mlir/include/mlir/Dialect/Affine/Passes.h b/mlir/include/mlir/Dialect/Affine/Passes.h index 2f70f24dd3ef2..0d88ecc462afd 100644 --- a/mlir/include/mlir/Dialect/Affine/Passes.h +++ b/mlir/include/mlir/Dialect/Affine/Passes.h @@ -58,8 +58,7 @@ std::unique_ptr<OperationPass<func::FuncOp>> createRaiseMemrefToAffine(); /// Apply normalization transformations to affine loop-like ops. If /// `promoteSingleIter` is true, single iteration loops are promoted (i.e., the /// loop is replaced by its loop body). -std::unique_ptr<OperationPass<func::FuncOp>> -createAffineLoopNormalizePass(bool promoteSingleIter = false); +std::unique_ptr<OperationPass<func::FuncOp>> createAffineLoopNormalizePass(); /// Performs packing (or explicit copying) of accessed memref regions into /// buffers in the specified faster memory space through either pointwise copies diff --git a/mlir/include/mlir/Dialect/Affine/Passes.td b/mlir/include/mlir/Dialect/Affine/Passes.td index 6ad45b828f657..74081772a8441 100644 --- a/mlir/include/mlir/Dialect/Affine/Passes.td +++ b/mlir/include/mlir/Dialect/Affine/Passes.td @@ -383,10 +383,6 @@ def AffineParallelize : Pass<"affine-parallelize", "func::FuncOp"> { def AffineLoopNormalize : Pass<"affine-loop-normalize", "func::FuncOp"> { let summary = "Apply normalization transformations to affine loop-like ops"; let constructor = "mlir::affine::createAffineLoopNormalizePass()"; - let options = [ - Option<"promoteSingleIter", "promote-single-iter", "bool", - /*default=*/"true", "Promote single iteration loops">, - ]; } def LoopCoalescing : Pass<"affine-loop-coalescing", "func::FuncOp"> { diff --git a/mlir/include/mlir/Dialect/Affine/Utils.h b/mlir/include/mlir/Dialect/Affine/Utils.h index ac11f5a7c24c7..f46f40de4bcc7 100644 --- a/mlir/include/mlir/Dialect/Affine/Utils.h +++ b/mlir/include/mlir/Dialect/Affine/Utils.h @@ -171,11 +171,8 @@ void normalizeAffineParallel(AffineParallelOp op); /// lower bound to zero and loop step to one. The upper bound is set to the trip /// count of the loop. Original loops must have a lower bound with only a single /// result. There is no such restriction on upper bounds. Returns success if the -/// loop has been normalized (or is already in the normal form). If -/// `promoteSingleIter` is true, the loop is simply promoted if it has a single -/// iteration. -LogicalResult normalizeAffineFor(AffineForOp op, - bool promoteSingleIter = false); +/// loop has been normalized (or is already in the normal form). +LogicalResult normalizeAffineFor(AffineForOp op); /// Traverse `e` and return an AffineExpr where all occurrences of `dim` have /// been replaced by either: diff --git a/mlir/lib/Dialect/Affine/IR/AffineOps.cpp b/mlir/lib/Dialect/Affine/IR/AffineOps.cpp index e0a53cd52f143..1aea482a5bcea 100644 --- a/mlir/lib/Dialect/Affine/IR/AffineOps.cpp +++ b/mlir/lib/Dialect/Affine/IR/AffineOps.cpp @@ -2716,6 +2716,46 @@ LogicalResult AffineForOp::fold(FoldAdaptor adaptor, return success(folded); } +/// Replaces the given op with the contents of the given single-block region, +/// using the operands of the block terminator to replace operation results. +static void replaceOpWithRegion(PatternRewriter &rewriter, Operation *op, + Region &region, ValueRange blockArgs = {}) { + assert(region.hasOneBlock() && "expected single-block region"); + Block *block = &region.front(); + Operation *terminator = block->getTerminator(); + ValueRange results = terminator->getOperands(); + rewriter.inlineBlockBefore(block, op, blockArgs); + rewriter.replaceOp(op, results); + rewriter.eraseOp(terminator); +} + +struct SimplifyTrivialLoops : public OpRewritePattern<AffineForOp> { + using OpRewritePattern<AffineForOp>::OpRewritePattern; + LogicalResult matchAndRewrite(AffineForOp forOp, + PatternRewriter &rewriter) const override { + std::optional<uint64_t> tripCount = getTrivialConstantTripCount(forOp); + if (!tripCount.has_value() || tripCount != 1) + return failure(); + + SmallVector<Value> blockArgs; + blockArgs.reserve(forOp.getInits().size() + 1); + rewriter.setInsertionPointToStart(forOp.getBody()); + Value lower = + rewriter.create<AffineApplyOp>(forOp.getLoc(), forOp.getLowerBoundMap(), + forOp.getLowerBoundOperands()); + forOp.getInductionVar().replaceAllUsesWith(lower); + blockArgs.push_back(lower); + llvm::append_range(blockArgs, forOp.getInits()); + replaceOpWithRegion(rewriter, forOp, forOp.getRegion(), blockArgs); + return success(); + } +}; + +void AffineForOp::getCanonicalizationPatterns(RewritePatternSet &results, + MLIRContext *context) { + results.add<SimplifyTrivialLoops>(context); +} + OperandRange AffineForOp::getEntrySuccessorOperands(RegionBranchPoint point) { assert((point.isParent() || point == getRegion()) && "invalid region point"); diff --git a/mlir/lib/Dialect/Affine/Transforms/AffineLoopNormalize.cpp b/mlir/lib/Dialect/Affine/Transforms/AffineLoopNormalize.cpp index 5cc38f7051726..4312a733c95c5 100644 --- a/mlir/lib/Dialect/Affine/Transforms/AffineLoopNormalize.cpp +++ b/mlir/lib/Dialect/Affine/Transforms/AffineLoopNormalize.cpp @@ -33,16 +33,14 @@ namespace { /// that are already in a normalized form. struct AffineLoopNormalizePass : public affine::impl::AffineLoopNormalizeBase<AffineLoopNormalizePass> { - explicit AffineLoopNormalizePass(bool promoteSingleIter) { - this->promoteSingleIter = promoteSingleIter; - } + using Base::Base; void runOnOperation() override { getOperation().walk([&](Operation *op) { if (auto affineParallel = dyn_cast<AffineParallelOp>(op)) normalizeAffineParallel(affineParallel); else if (auto affineFor = dyn_cast<AffineForOp>(op)) - (void)normalizeAffineFor(affineFor, promoteSingleIter); + (void)normalizeAffineFor(affineFor); }); } }; @@ -50,6 +48,6 @@ struct AffineLoopNormalizePass } // namespace std::unique_ptr<OperationPass<func::FuncOp>> -mlir::affine::createAffineLoopNormalizePass(bool promoteSingleIter) { - return std::make_unique<AffineLoopNormalizePass>(promoteSingleIter); +mlir::affine::createAffineLoopNormalizePass() { + return std::make_unique<AffineLoopNormalizePass>(); } diff --git a/mlir/lib/Dialect/Affine/Utils/Utils.cpp b/mlir/lib/Dialect/Affine/Utils/Utils.cpp index 845be20d15b69..445617c8a0e52 100644 --- a/mlir/lib/Dialect/Affine/Utils/Utils.cpp +++ b/mlir/lib/Dialect/Affine/Utils/Utils.cpp @@ -556,10 +556,7 @@ void mlir::affine::normalizeAffineParallel(AffineParallelOp op) { op.setUpperBounds(ranges.getOperands(), newUpperMap); } -LogicalResult mlir::affine::normalizeAffineFor(AffineForOp op, - bool promoteSingleIter) { - if (promoteSingleIter && succeeded(promoteIfSingleIteration(op))) - return success(); +LogicalResult mlir::affine::normalizeAffineFor(AffineForOp op) { // Check if the forop is already normalized. if (op.hasConstantLowerBound() && (op.getConstantLowerBound() == 0) && diff --git a/mlir/test/Dialect/Affine/affine-loop-normalize.mlir b/mlir/test/Dialect/Affine/affine-loop-normalize.mlir index 7d90efec0c21b..a25888d50e6b9 100644 --- a/mlir/test/Dialect/Affine/affine-loop-normalize.mlir +++ b/mlir/test/Dialect/Affine/affine-loop-normalize.mlir @@ -1,5 +1,4 @@ // RUN: mlir-opt %s -affine-loop-normalize -split-input-file | FileCheck %s -// RUN: mlir-opt %s -affine-loop-normalize='promote-single-iter=1' -split-input-file | FileCheck %s --check-prefix=PROMOTE-SINGLE-ITER // Normalize steps to 1 and lower bounds to 0. @@ -37,26 +36,6 @@ func.func @relative_bounds(%arg: index) { // ----- -// Check that single iteration loop is removed and its body is promoted to the -// parent block. - -// CHECK-LABEL: func @promote_single_iter_loop -// PROMOTE-SINGLE-ITER-LABEL: func @promote_single_iter_loop -func.func @promote_single_iter_loop(%in: memref<1xf32>, %out: memref<1xf32>) { - affine.for %i = 0 to 1 { - %1 = affine.load %in[%i] : memref<1xf32> - affine.store %1, %out[%i] : memref<1xf32> - } - return -} - -// PROMOTE-SINGLE-ITER-NEXT: arith.constant -// PROMOTE-SINGLE-ITER-NEXT: affine.load -// PROMOTE-SINGLE-ITER-NEXT: affine.store -// PROMOTE-SINGLE-ITER-NEXT: return - -// ----- - // CHECK-DAG: [[$IV0:#map[0-9]*]] = affine_map<(d0) -> (d0 * 2 + 2)> // CHECK-DAG: [[$IV1:#map[0-9]*]] = affine_map<(d0) -> (d0 * 3)> diff --git a/mlir/test/Dialect/Affine/canonicalize.mlir b/mlir/test/Dialect/Affine/canonicalize.mlir index 1169cd1c29d74..c99d3772f1648 100644 --- a/mlir/test/Dialect/Affine/canonicalize.mlir +++ b/mlir/test/Dialect/Affine/canonicalize.mlir @@ -1323,12 +1323,10 @@ func.func @simplify_bounds_tiled() { } } } - // CHECK: affine.for - // CHECK-NEXT: affine.for + // CHECK: affine.for %{{.*}} 0 to 2 + // CHECK-NEXT: affine.for %{{.*}} = 0 to 32 step 16 // CHECK-NEXT: affine.for %{{.*}} = 0 to 32 step 16 - // CHECK-NEXT: affine.for %{{.*}} = 0 to 32 step 16 - // CHECK-NEXT: affine.for %{{.*}} = 0 to 2 - // CHECK-NEXT: affine.for %{{.*}} = 0 to 16 step 16 + // CHECK-NEXT: affine.for %{{.*}} = 0 to 2 return } @@ -1348,9 +1346,7 @@ func.func @simplify_min_max_multi_expr() { // CHECK: affine.for affine.for %i = 0 to 2 { // CHECK: affine.for - affine.for %j = 0 to 4 { - // The first upper bound expression will not be lower than -9. So, it's redundant. - // CHECK-NEXT: affine.for %{{.*}} = -10 to -9 + affine.for %j = 0 to 4 {  affine.for %k = -10 to min affine_map<(d0, d1) -> (4 * d0 - 3 * d1, -9)>(%i, %j) { "test.foo"() : () -> () } @@ -1370,7 +1366,6 @@ func.func @simplify_min_max_multi_expr() { } } - // CHECK: affine.for %{{.*}} = 0 to 1 affine.for %i = 0 to 2 { affine.for %j = max affine_map<(d0) -> (d0 floordiv 2, 0)>(%i) to 1 { "test.foo"() : () -> () @@ -2401,3 +2396,22 @@ func.func @for_empty_body_folder_iv_yield() -> index { } return %10 : index } + +// ----- + +// Check that single iteration loop is removed and its body is promoted to the +// parent block. + +// CHECK-LABEL: func @promote_single_iter_loop +func.func @promote_single_iter_loop(%in: memref<1xf32>, %out: memref<1xf32>) { + affine.for %i = 0 to 1 { + %1 = affine.load %in[%i] : memref<1xf32> + affine.store %1, %out[%i] : memref<1xf32> + } + return +} + +// CHECK-NEXT: arith.constant +// CHECK-NEXT: affine.load +// CHECK-NEXT: affine.store +// CHECK-NEXT: return diff --git a/mlir/test/Dialect/Affine/raise-memref.mlir b/mlir/test/Dialect/Affine/raise-memref.mlir index 8dc24d99db3e2..c9777de2718ae 100644 --- a/mlir/test/Dialect/Affine/raise-memref.mlir +++ b/mlir/test/Dialect/Affine/raise-memref.mlir @@ -51,21 +51,17 @@ func.func @reduce_window_max() { // CHECK: affine.for %[[arg0:.*]] = // CHECK: affine.for %[[arg1:.*]] = // CHECK: affine.for %[[arg2:.*]] = -// CHECK: affine.for %[[arg3:.*]] = -// CHECK: affine.store %[[cst]], %[[v0]][%[[arg0]], %[[arg1]], %[[arg2]], %[[arg3]]] : +// CHECK: affine.store %[[cst]], %[[v0]][0, %[[arg0]], %[[arg1]], %[[arg2]]] : // CHECK: affine.for %[[a0:.*]] = // CHECK: affine.for %[[a1:.*]] = // CHECK: affine.for %[[a2:.*]] = // CHECK: affine.for %[[a3:.*]] = // CHECK: affine.for %[[a4:.*]] = -// CHECK: affine.for %[[a5:.*]] = -// CHECK: affine.for %[[a6:.*]] = -// CHECK: affine.for %[[a7:.*]] = -// CHECK: %[[lhs:.*]] = affine.load %[[v0]][%[[a0]], %[[a1]], %[[a2]], %[[a3]]] : -// CHECK: %[[rhs:.*]] = affine.load %[[v1]][%[[a0]] + %[[a4]], %[[a1]] * 2 + %[[a5]], %[[a2]] * 2 + %[[a6]], %[[a3]] + %[[a7]]] : -// CHECK: %[[res:.*]] = arith.cmpf ogt, %[[lhs]], %[[rhs]] : f32 -// CHECK: %[[sel:.*]] = arith.select %[[res]], %[[lhs]], %[[rhs]] : f32 -// CHECK: affine.store %[[sel]], %[[v0]][%[[a0]], %[[a1]], %[[a2]], %[[a3]]] : +// CHECK: %[[lhs:.*]] = affine.load %[[v0]][0, %[[a0]], %[[a1]], %[[a2]]] : +// CHECK: %[[rhs:.*]] = affine.load %[[v1]][0, %[[a0]] * 2 + %[[a3]], %[[a1]] * 2 + %[[a4]], %[[a2]]] : +// CHECK: %[[res:.*]] = arith.cmpf ogt, %[[lhs]], %[[rhs]] : f32 +// CHECK: %[[sel:.*]] = arith.select %[[res]], %[[lhs]], %[[rhs]] : f32 +// CHECK: affine.store %[[sel]], %[[v0]][0, %[[a0]], %[[a1]], %[[a2]]] : // CHECK-LABEL: func @symbols( func.func @symbols(%N : index) { 
@linuxlonelyeagle linuxlonelyeagle requested review from bondhugula, ftynse, joker-eph, krzysz00 and kuhar and removed request for kuhar October 25, 2025 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

2 participants