Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mlir/include/mlir/Conversion/Passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
#include "mlir/Conversion/OpenMPToLLVM/ConvertOpenMPToLLVM.h"
#include "mlir/Conversion/PDLToPDLInterp/PDLToPDLInterp.h"
#include "mlir/Conversion/ReconcileUnrealizedCasts/ReconcileUnrealizedCasts.h"
#include "mlir/Conversion/SCFToAffine/SCFToAffine.h"
#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
#include "mlir/Conversion/SCFToEmitC/SCFToEmitC.h"
#include "mlir/Conversion/SCFToGPU/SCFToGPUPass.h"
Expand Down
28 changes: 28 additions & 0 deletions mlir/include/mlir/Conversion/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,34 @@ def ReconcileUnrealizedCastsPass : Pass<"reconcile-unrealized-casts"> {
}];
}

//===----------------------------------------------------------------------===//
// SCFToAffine
//===----------------------------------------------------------------------===//

def RaiseSCFToAffinePass : Pass<"raise-scf-to-affine"> {
let summary = "Raise SCF operations to affine operations where possible";
let description = [{
This pass raises SCF operations to affine operations where possible.

Specifically:
- `scf.for` loops with affine-compatible bounds and steps are
converted to `affine.for`.

Converting SCF to affine enables affine-specific optimizations such as
loop tiling, unrolling, vectorization, and memory access analysis.

Note:
- Only loops that are statically affine can be converted;
non-affine loops remain in SCF form.
- This pass does not modify memory accesses; consider using
--affine-raise-from-memref for converting `memref.load`/`store`.
}];
let dependentDialects = [
"affine::AffineDialect",
"scf::SCFDialect",
];
}

//===----------------------------------------------------------------------===//
// SCFToControlFlow
//===----------------------------------------------------------------------===//
Expand Down
26 changes: 26 additions & 0 deletions mlir/include/mlir/Conversion/SCFToAffine/SCFToAffine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//===- SCFToAffine.h - SCF to Affine Pass entrypoint ------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef MLIR_CONVERSION_SCFTOAFFINE_SCFTOAFFINE_H_
#define MLIR_CONVERSION_SCFTOAFFINE_SCFTOAFFINE_H_

#include <memory>

namespace mlir {
class Pass;
class RewritePatternSet;

#define GEN_PASS_DECL_RAISESCFTOAFFINEPASS
#include "mlir/Conversion/Passes.h.inc"

/// Collect a set of patterns to convert SCF operations to Affine operations.
void populateSCFToAffineConversionPatterns(RewritePatternSet &patterns);

} // namespace mlir

#endif // MLIR_CONVERSION_SCFTOAFFINE_SCFTOAFFINE_H_
1 change: 1 addition & 0 deletions mlir/lib/Conversion/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ add_subdirectory(OpenACCToSCF)
add_subdirectory(OpenMPToLLVM)
add_subdirectory(PDLToPDLInterp)
add_subdirectory(ReconcileUnrealizedCasts)
add_subdirectory(SCFToAffine)
add_subdirectory(SCFToControlFlow)
add_subdirectory(SCFToEmitC)
add_subdirectory(SCFToGPU)
Expand Down
17 changes: 17 additions & 0 deletions mlir/lib/Conversion/SCFToAffine/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
add_mlir_conversion_library(MLIRSCFToAffine
SCFToAffine.cpp

ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/Conversion/SCFToAffine

DEPENDS
MLIRConversionPassIncGen

LINK_LIBS PUBLIC
MLIRArithDialect
MLIRAffineDialect
MLIRLLVMDialect
MLIRSCFDialect
MLIRSCFTransforms
MLIRTransforms
)
134 changes: 134 additions & 0 deletions mlir/lib/Conversion/SCFToAffine/SCFToAffine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//===- SCFToAffine.cpp - SCF to Affine conversion -------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements a pass to raise scf.for, scf.if and loop.terminator
// ops into affine ops.
//
//===----------------------------------------------------------------------===//

#include "mlir/Conversion/SCFToAffine/SCFToAffine.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/Verifier.h"
#include "mlir/Transforms/Passes.h"
#include "mlir/Transforms/WalkPatternRewriteDriver.h"
#include "llvm/Support/DebugLog.h"

namespace mlir {
#define GEN_PASS_DEF_RAISESCFTOAFFINEPASS
#include "mlir/Conversion/Passes.h.inc"
} // namespace mlir

#define DEBUG_TYPE "raise-scf-to-affine"

using namespace mlir;

namespace {

struct SCFToAffinePass
: public impl::RaiseSCFToAffinePassBase<SCFToAffinePass> {
void runOnOperation() override;
};

bool canRaiseToAffine(scf::ForOp op) {
return affine::isValidDim(op.getLowerBound()) &&
affine::isValidDim(op.getUpperBound()) &&
affine::isValidSymbol(op.getStep());
}

struct ForOpRewrite : public OpRewritePattern<scf::ForOp> {
using OpRewritePattern<scf::ForOp>::OpRewritePattern;

std::pair<affine::AffineForOp, Value>
createAffineFor(scf::ForOp op, PatternRewriter &rewriter) const {
IntegerAttr constAttr;
if (matchPattern(op.getStep(), m_Constant(&constAttr))) {
int64_t step = constAttr.getInt();
if (step > 0)
return positiveConstantStep(op, step, rewriter);
}
return genericBounds(op, rewriter);
}

std::pair<affine::AffineForOp, Value>
positiveConstantStep(scf::ForOp op, int64_t step,
PatternRewriter &rewriter) const {
auto affineFor = affine::AffineForOp::create(
rewriter, op.getLoc(), ValueRange(op.getLowerBound()),
AffineMap::get(1, 0, rewriter.getAffineDimExpr(0)),
ValueRange(op.getUpperBound()),
AffineMap::get(1, 0, rewriter.getAffineDimExpr(0)), step,
op.getInits());
return std::make_pair(affineFor, affineFor.getInductionVar());
}

std::pair<affine::AffineForOp, Value>
genericBounds(scf::ForOp op, PatternRewriter &rewriter) const {
Value lower = op.getLowerBound();
Value upper = op.getUpperBound();
Value step = op.getStep();
AffineExpr lowerExpr = rewriter.getAffineDimExpr(0);
AffineExpr upperExpr = rewriter.getAffineDimExpr(1);
AffineExpr stepExpr = rewriter.getAffineSymbolExpr(0);
auto affineFor = affine::AffineForOp::create(
rewriter, op.getLoc(), ValueRange(), rewriter.getConstantAffineMap(0),
ValueRange({lower, upper, step}),
AffineMap::get(
2, 1, (upperExpr - lowerExpr + stepExpr - 1).floorDiv(stepExpr)),
1, op.getInits());

rewriter.setInsertionPointToStart(affineFor.getBody());
auto actualIndexMap = AffineMap::get(
2, 1, lowerExpr + rewriter.getAffineDimExpr(1) * stepExpr);
auto actualIndex = affine::AffineApplyOp::create(
rewriter, op.getLoc(), actualIndexMap,
ValueRange({lower, affineFor.getInductionVar(), step}));
return std::make_pair(affineFor, actualIndex.getResult());
}

LogicalResult matchAndRewrite(scf::ForOp op,
PatternRewriter &rewriter) const override {
if (!canRaiseToAffine(op)) {
LDBG() << "[affine] Cannot raise scf op: " << op << "\n";
return failure();
}

auto [affineFor, actualIndex] = createAffineFor(op, rewriter);
Block *affineBody = affineFor.getBody();

if (affineBody->mightHaveTerminator())
rewriter.eraseOp(affineBody->getTerminator());

SmallVector<Value> argValues;
argValues.push_back(actualIndex);
llvm::append_range(argValues, affineFor.getRegionIterArgs());
rewriter.inlineBlockBefore(op.getBody(), affineBody, affineBody->end(),
argValues);

auto scfYieldOp = cast<scf::YieldOp>(affineBody->getTerminator());
rewriter.setInsertionPointToEnd(affineBody);
rewriter.replaceOpWithNewOp<affine::AffineYieldOp>(
scfYieldOp, scfYieldOp->getOperands());

rewriter.replaceOp(op, affineFor);
return success();
}
};

} // namespace

void mlir::populateSCFToAffineConversionPatterns(RewritePatternSet &patterns) {
patterns.add<ForOpRewrite>(patterns.getContext());
}

void SCFToAffinePass::runOnOperation() {
MLIRContext &ctx = getContext();
RewritePatternSet patterns(&ctx);
populateSCFToAffineConversionPatterns(patterns);
walkAndApplyPatterns(getOperation(), std::move(patterns));
}
51 changes: 51 additions & 0 deletions mlir/test/Conversion/SCFToAffine/scf-to-affine.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// RUN: mlir-opt -raise-scf-to-affine -split-input-file %s | FileCheck %s

// CHECK: #[[$UB_MAP:.+]] = affine_map<(d0, d1)[s0] -> ((d1 - d0 + s0 - 1) floordiv s0)>
// CHECK: #[[$IV_MAP:.+]] = affine_map<(d0, d1)[s0] -> (d0 + d1 * s0)>
// CHECK-LABEL: @generic_loop
// CHECK-SAME: %[[ARR:.*]]: memref<?xi32>, %[[LOWER:.*]]: index, %[[UPPER:.*]]: index, %[[STEP:.*]]: index
func.func @generic_loop(%arr: memref<?xi32>, %lower: index, %upper: index, %step: index) {
// CHECK: affine.for %[[IV:.*]] = 0 to #[[$UB_MAP]](%[[LOWER]], %[[UPPER]])[%[[STEP]]] {
// CHECK: %[[IDX:.*]] = affine.apply #[[$IV_MAP]](%[[LOWER]], %[[IV]])[%[[STEP]]]
// CHECK: memref.store %{{.*}}, %[[ARR]][%[[IDX]]] : memref<?xi32>
// CHECK: }
%c0_i32 = arith.constant 0 : i32
scf.for %idx = %lower to %upper step %step {
memref.store %c0_i32, %arr[%idx] : memref<?xi32>
}
return
}

// -----

// CHECK: #[[$MAP:.+]] = affine_map<(d0) -> (d0)>
// CHECK-LABEL: @loop_with_constant_step
// CHECK-SAME: %[[ARR:.*]]: memref<?xi32>, %[[LOWER:.*]]: index, %[[UPPER:.*]]: index
func.func @loop_with_constant_step(%arr: memref<?xi32>, %lower: index, %upper: index) {
// CHECK: affine.for %[[IDX:.*]] = #[[$MAP]](%[[LOWER]]) to #[[$MAP]](%[[UPPER]]) step 3 {
// CHECK: memref.store %{{.*}}, %[[ARR]][%[[IDX]]] : memref<?xi32>
// CHECK: }
%c0_i32 = arith.constant 0 : i32
%c3 = arith.constant 3 : index
scf.for %idx = %lower to %upper step %c3 {
memref.store %c0_i32, %arr[%idx] : memref<?xi32>
}
return
}

// -----

// CHECK-LABEL: @nested_loop
func.func @nested_loop(%arg0: memref<?x?xi32>, %upper1: index, %upper2: index) {
// CHECK: affine.for
// CHECK: affine.for
%c0_i32 = arith.constant 0 : i32
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
scf.for %i = %c0 to %upper1 step %c1 {
scf.for %j = %c0 to %upper2 step %c1 {
memref.store %c0_i32, %arg0[%i, %j] : memref<?x?xi32>
}
}
return
}