diff --git a/mlir/include/mlir/Dialect/Affine/IR/AffineOps.td b/mlir/include/mlir/Dialect/Affine/IR/AffineOps.td --- a/mlir/include/mlir/Dialect/Affine/IR/AffineOps.td +++ b/mlir/include/mlir/Dialect/Affine/IR/AffineOps.td @@ -110,7 +110,9 @@ [AutomaticAllocationScope, ImplicitAffineTerminator, RecursiveSideEffects, DeclareOpInterfaceMethods]> { + "getSingleUpperBound"]>, + DeclareOpInterfaceMethods]> { let summary = "for operation"; let description = [{ Syntax: diff --git a/mlir/lib/Dialect/Affine/IR/AffineOps.cpp b/mlir/lib/Dialect/Affine/IR/AffineOps.cpp --- a/mlir/lib/Dialect/Affine/IR/AffineOps.cpp +++ b/mlir/lib/Dialect/Affine/IR/AffineOps.cpp @@ -1723,6 +1723,57 @@ results.add(context); } +/// Return operands used when entering the region at 'index'. These operands +/// correspond to the loop iterator operands, i.e., those excluding the +/// induction variable. AffineForOp only has one region, so zero is the only +/// valid value for `index`. +OperandRange AffineForOp::getSuccessorEntryOperands(unsigned index) { + assert(index == 0 && "invalid region index"); + + // The initial operands map to the loop arguments after the induction + // variable. + return getIterOperands(); +} + +/// Given the region at `index`, or the parent operation if `index` is None, +/// return the successor regions. These are the regions that may be selected +/// during the flow of control. `operands` is a set of optional attributes that +/// correspond to a constant value for each operand, or null if that operand is +/// not a constant. +void AffineForOp::getSuccessorRegions( + Optional index, ArrayRef operands, + SmallVectorImpl ®ions) { + assert((!index.hasValue() || index.getValue() == 0) && + "expected loop region"); + // The loop may typically branch back to its body or to the parent operation. + // If the predecessor is the parent op and the trip count is known to be at + // least one, branch into the body using the iterator arguments. And in cases + // we know the trip count is zero, it can only branch back to its parent. + Optional tripCount = getTrivialConstantTripCount(*this); + if (!index.hasValue() && tripCount.hasValue()) { + if (tripCount.getValue() > 0) { + regions.push_back(RegionSuccessor(&getLoopBody(), getRegionIterArgs())); + return; + } + if (tripCount.getValue() == 0) { + regions.push_back(RegionSuccessor(getResults())); + return; + } + } + + // From the loop body, if the trip count is one, we can only branch back to + // the parent. + if (index.hasValue() && tripCount.hasValue() && tripCount.getValue() == 1) { + regions.push_back(RegionSuccessor(getResults())); + return; + } + + // In all other cases, the loop may branch back to itself or the parent + // operation. + regions.push_back(RegionSuccessor(&getLoopBody(), getRegionIterArgs())); + regions.push_back(RegionSuccessor(getResults())); +} + /// Returns true if the affine.for has zero iterations in trivial cases. static bool hasTrivialZeroTripCount(AffineForOp op) { Optional tripCount = getTrivialConstantTripCount(op); diff --git a/mlir/test/Dialect/Bufferization/Transforms/buffer-deallocation.mlir b/mlir/test/Dialect/Bufferization/Transforms/buffer-deallocation.mlir --- a/mlir/test/Dialect/Bufferization/Transforms/buffer-deallocation.mlir +++ b/mlir/test/Dialect/Bufferization/Transforms/buffer-deallocation.mlir @@ -1092,6 +1092,24 @@ // ----- +// CHECK-LABEL: func @affine_loop +func @affine_loop() { + %buffer = memref.alloc() : memref<1024xf32> + %sum_init_0 = arith.constant 0.0 : f32 + %res = affine.for %i = 0 to 10 step 2 iter_args(%sum_iter = %sum_init_0) -> f32 { + %t = affine.load %buffer[%i] : memref<1024xf32> + %sum_next = arith.addf %sum_iter, %t : f32 + affine.yield %sum_next : f32 + } + // CHECK: %[[M:.*]] = memref.alloc + // CHECK: affine.for + // CHECK: } + // CHECK-NEXT: memref.dealloc %[[M]] + return +} + +// ----- + // Test Case: explicit control-flow loop with a dynamically allocated buffer. // The BufferDeallocation transformation should fail on this explicit // control-flow loop since they are not supported. diff --git a/mlir/test/Transforms/sccp-structured.mlir b/mlir/test/Transforms/sccp-structured.mlir --- a/mlir/test/Transforms/sccp-structured.mlir +++ b/mlir/test/Transforms/sccp-structured.mlir @@ -149,3 +149,33 @@ } return } + +/// Check that propgation happens for affine.for -- tests its region branch op +/// interface as well. + +// CHECK-LABEL: func @affine_loop_one_iter( +func @affine_loop_one_iter(%arg0 : index, %arg1 : index, %arg2 : index) -> i32 { + // CHECK: %[[C1:.*]] = arith.constant 1 : i32 + %s0 = arith.constant 0 : i32 + %s1 = arith.constant 1 : i32 + %result = affine.for %i = 0 to 1 iter_args(%si = %s0) -> (i32) { + %sn = arith.addi %si, %s1 : i32 + affine.yield %sn : i32 + } + // CHECK: return %[[C1]] : i32 + return %result : i32 +} + +// CHECK-LABEL: func @affine_loop_zero_iter( +func @affine_loop_zero_iter(%arg0 : index, %arg1 : index, %arg2 : index) -> i32 { + // This exposes a crash in sccp/forward data flow analysis: https://github.com/llvm/llvm-project/issues/54928 + // CHECK: %[[C0:.*]] = arith.constant 0 : i32 + %s0 = arith.constant 0 : i32 + // %result = affine.for %i = 0 to 0 iter_args(%si = %s0) -> (i32) { + // %sn = arith.addi %si, %si : i32 + // affine.yield %sn : i32 + // } + // return %result : i32 + // CHECK: return %[[C0]] : i32 + return %s0 : i32 +}