| // 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 |