[Mlir-commits] [mlir] [mlir][scf] support front peeling for unsigned loop (PR #233238)
Matthias Springer
llvmlistbot at llvm.org
Tue Oct 20 17:04:49 PDT 2026
================
@@ -233,51 +233,63 @@ LogicalResult mlir::scf::peelForLoopAndSimplifyBounds(RewriterBase &rewriter,
/// original lower bound plus the step (i.e. skips the first iteration).
LogicalResult mlir::scf::peelForLoopFirstIteration(RewriterBase &b, ForOp forOp,
ForOp &firstIteration) {
- // TODO: Add support for unsigned loops. The current logic
- // uses `affine.min` and `arith.index_cast`, which is incorrect for
- // unsigned comparisons.
- if (forOp.getUnsignedCmp())
- return failure();
-
RewriterBase::InsertionGuard guard(b);
- auto lbInt = getConstantIntValue(forOp.getLowerBound());
- auto ubInt = getConstantIntValue(forOp.getUpperBound());
- auto stepInt = getConstantIntValue(forOp.getStep());
+ Value lbVal = forOp.getLowerBound();
+ Value ubVal = forOp.getUpperBound();
+ Value stepVal = forOp.getStep();
+ auto loc = forOp.getLoc();
- // Peeling is not needed if there is at most one iteration.
- // Per the scf.for semantics, the trip count is
- // max(0, ceil((UB - LB) / Step)), so for a positive step there is more than
- // one iteration exactly when UB - LB > Step.
- if (lbInt && ubInt && stepInt && *ubInt - *lbInt <= *stepInt)
- return failure();
+ Value splitBound;
+ b.setInsertionPoint(forOp);
- // The peeling bound (%lb + %step) is computed with affine.apply, which
- // accepts only index operands. %ub does not feed into this bound, so only
- // %lb and %step need to be constant to guarantee the affine.apply (see below)
- // folds away before its (non-index) operand types matter.
- if ((!lbInt || !stepInt) && !forOp.getInductionVar().getType().isIndex())
- return failure();
+ // Check whether we have a unsigned loop
+ if (forOp.getUnsignedCmp()) {
+ IntegerAttr lbAttr, ubAttr, stepAttr;
+ if (matchPattern(forOp.getLowerBound(), m_Constant(&lbAttr)) &&
+ matchPattern(forOp.getUpperBound(), m_Constant(&ubAttr)) &&
+ matchPattern(forOp.getStep(), m_Constant(&stepAttr))) {
- AffineExpr lbSymbol, stepSymbol;
- bindSymbols(b.getContext(), lbSymbol, stepSymbol);
+ // Get the constants as APInt at the loop’s own bit width
+ APInt lb = lbAttr.getValue();
+ APInt ub = ubAttr.getValue();
+ APInt step = stepAttr.getValue();
- // New lower bound for main loop: min(%lb + %step, %ub). Without the minimum
- // the peeled first iteration spans [%lb, %lb + %step) and executes even when
- // the source loop is empty (%lb >= %ub).
- AffineExpr ubSymbol;
- bindSymbols(b.getContext(), lbSymbol, stepSymbol, ubSymbol);
- auto ubMap =
- AffineMap::get(0, 3, {lbSymbol + stepSymbol, ubSymbol}, b.getContext());
- b.setInsertionPoint(forOp);
- auto loc = forOp.getLoc();
- Value splitBound = b.createOrFold<AffineMinOp>(
- loc, ubMap,
- ValueRange{forOp.getLowerBound(), forOp.getStep(),
- forOp.getUpperBound()});
- if (splitBound.getType() != forOp.getUpperBound().getType())
- splitBound = b.createOrFold<arith::IndexCastOp>(
- loc, forOp.getUpperBound().getType(), splitBound);
+ // bail out if ub <= lb or ub - lb <= step (unsigned constants)
+ if (ub.ule(lb) || (ub - lb).ule(step))
+ return failure();
+ }
+ // split = max(lb, min(lb + step, ub)): the first loop runs one iteration,
+ // or none if the loop is empty. If lb + step wraps, the loop has at most
+ // one iteration and split falls back to lb, so the main loop does the work.
+ // use unsigned ops (MinUIOp, MaxUIOp)
+ Value sum = b.createOrFold<arith::AddIOp>(loc, lbVal, stepVal);
+ Value capped = b.createOrFold<arith::MinUIOp>(loc, sum, ubVal);
+ splitBound = b.createOrFold<arith::MaxUIOp>(loc, lbVal, capped);
+ } else {
+ auto lbInt = getConstantIntValue(lbVal);
+ auto ubInt = getConstantIntValue(ubVal);
+ auto stepInt = getConstantIntValue(stepVal);
+
+ // Peeling is not needed if there is at most one iteration. scf.for requires
+ // a positive step, so the trip count is ceil((UB - LB) / Step) when UB > LB
+ // and 0 otherwise. With a positive step, "UB - LB <= Step" therefore covers
+ // both the empty loop and the single-iteration loop. Bail out if the
+ // subtraction overflows, since we can't reason about the trip count then.
+ if (lbInt && ubInt && stepInt) {
+ int64_t diff;
+ if (llvm::SubOverflow(*ubInt, *lbInt, diff) || diff <= *stepInt)
+ return failure();
+ }
+
+ // split = max(lb, min(lb + step, ub)): the first loop runs one iteration,
+ // or none if the loop is empty. If lb + step wraps, the loop has at most
+ // one iteration and split falls back to lb, so the main loop does the work.
+ // use signed ops (MinSIOp, MaxSIOp)
----------------
matthias-springer wrote:
You can drop this line.
https://github.com/llvm/llvm-project/pull/233238
More information about the Mlir-commits
mailing list