Skip to content

Commit

Permalink
[mlir] Add function for checking if a block is inside a loop
Browse files Browse the repository at this point in the history
This function returns whether a block is nested inside of a loop. There
can be three kinds of loop:
  1) The block is nested inside of a LoopLikeOpInterface
  2) The block is nested inside another block which is in a loop
  3) There is a cycle in the control flow graph

This will be useful for Flang's stack arrays pass, which moves array
allocations from the heap to the stack. Special handling is needed when
allocations occur inside of loops to ensure additional stack space is
not allocated on each loop iteration.

Differential Revision: https://reviews.llvm.org/D141401
  • Loading branch information
tblah committed Feb 9, 2023
1 parent de4321c commit dcee187
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 0 deletions.
8 changes: 8 additions & 0 deletions mlir/include/mlir/Interfaces/LoopLikeInterface.td
Expand Up @@ -97,6 +97,14 @@ def LoopLikeOpInterface : OpInterface<"LoopLikeOpInterface"> {
}]
>,
];

let extraClassDeclaration = [{
/// Returns if a block is inside a loop (within the current function). This
/// can be either because the block is nested inside a LoopLikeInterface or
/// because the block is nested inside a LoopLikeInterface or because
/// the control flow graph is cyclic
static bool blockIsInLoop(Block *block);
}];
}

#endif // MLIR_INTERFACES_LOOPLIKEINTERFACE
40 changes: 40 additions & 0 deletions mlir/lib/Interfaces/LoopLikeInterface.cpp
Expand Up @@ -7,8 +7,48 @@
//===----------------------------------------------------------------------===//

#include "mlir/Interfaces/LoopLikeInterface.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "llvm/ADT/DenseSet.h"

using namespace mlir;

/// Include the definitions of the loop-like interfaces.
#include "mlir/Interfaces/LoopLikeInterface.cpp.inc"

bool LoopLikeOpInterface::blockIsInLoop(Block *block) {
Operation *parent = block->getParentOp();

// The block could be inside a loop-like operation
if (isa<LoopLikeOpInterface>(parent) ||
parent->getParentOfType<LoopLikeOpInterface>())
return true;

// This block might be nested inside another block, which is in a loop
if (!isa<FunctionOpInterface>(parent)) {
if (blockIsInLoop(parent->getBlock())) {
return true;
}
}

// Or the block could be inside a control flow graph loop:
// A block is in a control flow graph loop if it can reach itself in a graph
// traversal
DenseSet<Block *> visited;
SmallVector<Block *> stack;
stack.push_back(block);
while (!stack.empty()) {
Block *current = stack.pop_back_val();
auto [it, inserted] = visited.insert(current);
if (!inserted) {
// loop detected
if (current == block)
return true;
continue;
}

stack.reserve(stack.size() + current->getNumSuccessors());
for (Block *successor : current->getSuccessors())
stack.push_back(successor);
}
return false;
}
143 changes: 143 additions & 0 deletions mlir/test/Interfaces/LoopLikeInterface/test-block-loop.mlir
@@ -0,0 +1,143 @@
// RUN: mlir-opt %s --mlir-disable-threading -test-block-is-in-loop 2>&1 | FileCheck %s

module {
// Test function with only one bb
func.func @simple() {
func.return
}
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb0:

// Test simple loop bb0 -> bb0
func.func @loopForever() {
^bb0:
cf.br ^bb1
^bb1:
cf.br ^bb1
}
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb0:
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb1:

// Test bb0 -> bb1 -> bb2 -> bb1
func.func @loopForever2() {
^bb0:
cf.br ^bb1
^bb1:
cf.br ^bb2
^bb2:
cf.br ^bb1
}
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb0:
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb1:
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb2:

// Test conditional branch without loop
// bb0 -> bb1 -> {bb2, bb3}
func.func @noLoop(%arg0: i1) {
cf.br ^bb1
^bb1:
cf.cond_br %arg0, ^bb2, ^bb3
^bb2:
func.return
^bb3:
func.return
}
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb0(%arg0: i1)
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb1:
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb2:
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb3:

// test multiple loops
// bb0 -> bb1 -> bb2 -> bb3 { -> bb2} -> bb4 { -> bb1 } -> bb5
func.func @multipleLoops(%arg0: i1, %arg1: i1) {
cf.br ^bb1
^bb1:
cf.br ^bb2
^bb2:
cf.br ^bb3
^bb3:
cf.cond_br %arg0, ^bb4, ^bb2
^bb4:
cf.cond_br %arg1, ^bb1, ^bb5
^bb5:
return
}
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb0(%arg0: i1, %arg1: i1)
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb1:
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb2:
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb3:
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb4:
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb5:

// test derived from real Flang output
func.func @_QPblockTest0(%arg0: i1, %arg1: i1) {
cf.br ^bb1
^bb1: // 2 preds: ^bb0, ^bb4
cf.cond_br %arg0, ^bb2, ^bb5
^bb2: // pred: ^bb1
cf.cond_br %arg1, ^bb3, ^bb4
^bb3: // pred: ^bb2
return
^bb4: // pred: ^bb2
cf.br ^bb1
^bb5: // pred: ^bb1
return
}
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb0(%arg0: i1, %arg1: i1)
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb1:
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb2:
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb3:
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb4:
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb5:

// check nested blocks
func.func @check_alloc_in_loop(%counter : i64) {
cf.br ^bb1(%counter: i64)
^bb1(%lv : i64):
%cm1 = arith.constant -1 : i64
%rem = arith.addi %lv, %cm1 : i64
%zero = arith.constant 0 : i64
%p = arith.cmpi eq, %rem, %zero : i64
cf.cond_br %p, ^bb3, ^bb2
^bb2:
scf.execute_region -> () {
%c1 = arith.constant 1 : i64
scf.yield
}
cf.br ^bb1(%rem: i64)
^bb3:
return
}
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb0(%arg0: i64):
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb1(%0: i64)
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb0:
// CHECK-NEXT: %c1_i64
// CHECK: Block is in a loop
// CHECK-NEXT: ^bb2:
// CHECK: Block is not in a loop
// CHECK-NEXT: ^bb3:
}
1 change: 1 addition & 0 deletions mlir/test/lib/Interfaces/CMakeLists.txt
@@ -1 +1,2 @@
add_subdirectory(LoopLikeInterface)
add_subdirectory(TilingInterface)
9 changes: 9 additions & 0 deletions mlir/test/lib/Interfaces/LoopLikeInterface/CMakeLists.txt
@@ -0,0 +1,9 @@
add_mlir_library(MLIRLoopLikeInterfaceTestPasses
TestBlockInLoop.cpp

EXCLUDE_FROM_LIBMLIR

LINK_LIBS PUBLIC
MLIRPass
MLIRLoopLikeInterface
)
47 changes: 47 additions & 0 deletions mlir/test/lib/Interfaces/LoopLikeInterface/TestBlockInLoop.cpp
@@ -0,0 +1,47 @@
//===- TestBlockInLoop.cpp - Pass to test mlir::blockIsInLoop -------------===//
//
// 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/Func/IR/FuncOps.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/Interfaces/LoopLikeInterface.h"
#include "mlir/Pass/Pass.h"
#include "llvm/Support/raw_ostream.h"

using namespace mlir;

namespace {
/// This is a test pass that tests Blocks's isInLoop method by checking if each
/// block in a function is in a loop and outputing if it is
struct IsInLoopPass
: public PassWrapper<IsInLoopPass, OperationPass<func::FuncOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(IsInLoopPass)

StringRef getArgument() const final { return "test-block-is-in-loop"; }
StringRef getDescription() const final {
return "Test mlir::blockIsInLoop()";
}

void runOnOperation() override {
mlir::func::FuncOp func = getOperation();
func.walk([](mlir::Block *block) {
llvm::outs() << "Block is ";
if (LoopLikeOpInterface::blockIsInLoop(block))
llvm::outs() << "in a loop\n";
else
llvm::outs() << "not in a loop\n";
block->print(llvm::outs());
llvm::outs() << "\n";
});
}
};

} // namespace

namespace mlir {
void registerLoopLikeInterfaceTestPasses() { PassRegistration<IsInLoopPass>(); }
} // namespace mlir
1 change: 1 addition & 0 deletions mlir/tools/mlir-opt/CMakeLists.txt
Expand Up @@ -21,6 +21,7 @@ if(MLIR_INCLUDE_TESTS)
MLIRFuncTestPasses
MLIRGPUTestPasses
MLIRLinalgTestPasses
MLIRLoopLikeInterfaceTestPasses
MLIRMathTestPasses
MLIRMemRefTestPasses
MLIRNVGPUTestPasses
Expand Down
2 changes: 2 additions & 0 deletions mlir/tools/mlir-opt/mlir-opt.cpp
Expand Up @@ -33,6 +33,7 @@ void registerConvertToTargetEnvPass();
void registerCloneTestPasses();
void registerPassManagerTestPass();
void registerPrintSpirvAvailabilityPass();
void registerLoopLikeInterfaceTestPasses();
void registerShapeFunctionTestPasses();
void registerSideEffectTestPasses();
void registerSliceAnalysisTestPass();
Expand Down Expand Up @@ -140,6 +141,7 @@ void registerTestPasses() {
registerConvertToTargetEnvPass();
registerPassManagerTestPass();
registerPrintSpirvAvailabilityPass();
registerLoopLikeInterfaceTestPasses();
registerShapeFunctionTestPasses();
registerSideEffectTestPasses();
registerSliceAnalysisTestPass();
Expand Down

0 comments on commit dcee187

Please sign in to comment.