blob: 597c86397e73a5f0f65847d62f9b5f2c66126eeb [file] [edit]
// Copyright 2024 The IREE Authors
//
// Licensed 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 "iree/compiler/GlobalOptimization/Passes.h"
#include "llvm/ADT/TypeSwitch.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Dialect/SCF/Transforms/Transforms.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/PatternMatch.h"
#include "llvm/Support/Debug.h"
using namespace mlir;
#define DEBUG_TYPE "global-loop-invariant-code-motion"
#define LICM_DBGS() (llvm::dbgs() << '[' << DEBUG_TYPE << "] ")
// Check if the op and its producers are loop invariants and hoistable. Using
// `hoistableOps` to check if producers are hoistable. It expects the op's
// producers have been checked and the results exist in `hoistableOps`.
static bool isHoistableOp(LoopLikeOpInterface loopOp, Operation *op,
const llvm::SetVector<Operation *> &hoistableOps) {
// Currently we don't handle implicit captures, so don't hoist ops with
// regions.
if (op->getNumRegions() > 0) {
return false;
}
// Check if the op type is hoistable.
if (!isa<tensor::EmptyOp, linalg::PackOp>(op)) {
return false;
}
// Check if all producers are hoistable.
for (OpOperand &operand : op->getOpOperands()) {
Value value = operand.get();
// Ignore values defined outside the loop.
if (loopOp.isDefinedOutsideOfLoop(value)) {
continue;
}
Operation *producer = value.getDefiningOp();
// If the producer is not an operation, can't hoist it.
if (!producer) {
return false;
}
if (!hoistableOps.contains(producer)) {
return false;
}
}
return true;
}
static LogicalResult hoistLoopInvariants(LoopLikeOpInterface loopOp,
RewriterBase &rewriter) {
// Find hoistable ops in the loop.
llvm::SetVector<Operation *> hoistableOps;
for (Region *region : loopOp.getLoopRegions()) {
// Skip loops with multi-block regions to simplify op's dependency.
if (!region->hasOneBlock()) {
return failure();
}
// Consider only the top-level ops in the region. The forward visiting in a
// single block ensures we are check and add ops in topological order.
for (Operation &op : region->front()) {
if (isHoistableOp(loopOp, &op, hoistableOps)) {
LLVM_DEBUG(LICM_DBGS() << "Found hoistable op: " << op << "\n");
hoistableOps.insert(&op);
}
}
}
if (hoistableOps.empty()) {
return success();
}
// Wrap the loop in zero-trip-check so the hoisted ops will only run when the
// loop condition is ever satisfied.
// Assume the transformation only moves ops around in loop instead of clone
// them.
FailureOr<LoopLikeOpInterface> wrappedLoop =
TypeSwitch<Operation *, FailureOr<LoopLikeOpInterface>>(
loopOp.getOperation())
.Case([&](scf::WhileOp op) {
return scf::wrapWhileLoopInZeroTripCheck(op, rewriter);
})
.Default(failure());
if (failed(wrappedLoop)) {
return failure();
}
// Hoist ops out of the loop in topological order.
for (Operation *op : hoistableOps) {
assert(wrappedLoop->getOperation()->isAncestor(op) &&
"assume hoistable ops still stay in the loop after transformation");
wrappedLoop->moveOutOfLoop(op);
}
return success();
}
namespace mlir::iree_compiler::GlobalOptimization {
#define GEN_PASS_DEF_GLOBALLOOPINVARIANTCODEMOTIONPASS
#include "iree/compiler/GlobalOptimization/Passes.h.inc"
namespace {
struct GlobalLoopInvariantCodeMotionPass
: impl::GlobalLoopInvariantCodeMotionPassBase<
GlobalLoopInvariantCodeMotionPass> {
void runOnOperation() override {
MLIRContext *context = &getContext();
FunctionOpInterface funcOp = getOperation();
SmallVector<LoopLikeOpInterface> candidateLoops;
// Candidate loops are visited in post-order so a loop invariant has chances
// to move across multiple loop levels.
funcOp.walk([&](LoopLikeOpInterface op) {
// Check if the loop type is supported.
if (isa<scf::WhileOp>(op)) {
candidateLoops.push_back(op);
}
return;
});
IRRewriter rewriter(context);
for (auto loopOp : candidateLoops) {
if (failed(hoistLoopInvariants(loopOp, rewriter))) {
return signalPassFailure();
}
}
}
};
} // namespace
} // namespace mlir::iree_compiler::GlobalOptimization