blob: 52f97e0867ee686c6ee48d0bfe97e5be08ae8de6 [file] [edit]
//===- OpenACCUtilsLoop.cpp - OpenACC Loop Utilities ----------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains utility functions for converting OpenACC loops to SCF.
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/OpenACC/OpenACCUtilsLoop.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Arith/Utils/Utils.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Dialect/SCF/Utils/Utils.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/Transforms/RegionUtils.h"
#include "llvm/Support/ErrorHandling.h"
using namespace mlir;
namespace {
/// Calculate trip count for a loop: (ub - lb + step) / step
/// If inclusiveUpperbound is false, subtracts 1 from ub first.
static Value calculateTripCount(OpBuilder &b, Location loc, Value lb, Value ub,
Value step, bool inclusiveUpperbound) {
Type type = b.getIndexType();
// Convert original loop arguments to index type
lb = getValueOrCreateCastToIndexLike(b, loc, type, lb);
ub = getValueOrCreateCastToIndexLike(b, loc, type, ub);
step = getValueOrCreateCastToIndexLike(b, loc, type, step);
if (!inclusiveUpperbound) {
Value one = arith::ConstantIndexOp::create(b, loc, 1);
ub = b.createOrFold<arith::SubIOp>(loc, ub, one,
arith::IntegerOverflowFlags::nsw);
}
Value sub = b.createOrFold<arith::SubIOp>(loc, ub, lb,
arith::IntegerOverflowFlags::nsw);
Value add = b.createOrFold<arith::AddIOp>(loc, sub, step,
arith::IntegerOverflowFlags::nsw);
return b.createOrFold<arith::DivSIOp>(loc, add, step);
}
/// Handle differing types between SCF (index) and ACC loops.
/// Creates casts from the new SCF IVs to the original ACC IV types and updates
/// the mapping. The newIVs should correspond 1:1 with the ACC loop's IVs.
static void mapACCLoopIVsToSCFIVs(acc::LoopOp accLoop, ValueRange newIVs,
OpBuilder &b, IRMapping &mapping) {
for (auto [origIV, newIV] :
llvm::zip(accLoop.getBody().getArguments(), newIVs)) {
Value replacementIV = getValueOrCreateCastToIndexLike(
b, accLoop->getLoc(), origIV.getType(), newIV);
mapping.map(origIV, replacementIV);
}
}
/// Normalize IV uses after converting to normalized loop form.
/// For normalized loops (lb=0, step=1), we need to denormalize the IV:
/// original_iv = new_iv * orig_step + orig_lb
static void normalizeIVUses(OpBuilder &b, Location loc, Value iv, Value origLB,
Value origStep) {
Type indexType = b.getIndexType();
Value lb = getValueOrCreateCastToIndexLike(b, loc, indexType, origLB);
Value step = getValueOrCreateCastToIndexLike(b, loc, indexType, origStep);
// new_iv * step + lb
Value scaled =
arith::MulIOp::create(b, loc, iv, step, arith::IntegerOverflowFlags::nsw);
Value denormalized = arith::AddIOp::create(b, loc, scaled, lb,
arith::IntegerOverflowFlags::nsw);
// Replace uses of iv with denormalized value, except for the ops that
// compute the denormalized value itself (muli and addi)
llvm::SmallPtrSet<Operation *, 2> exceptions;
exceptions.insert(scaled.getDefiningOp());
exceptions.insert(denormalized.getDefiningOp());
iv.replaceAllUsesExcept(denormalized, exceptions);
}
/// Helper used by loop conversion: clone region and return insertion point
/// only.
static Block::iterator cloneACCRegionIntoForLoop(Region *src, Block *dest,
Block::iterator insertionPoint,
IRMapping &mapping,
RewriterBase &rewriter) {
auto [replacements, ip] =
acc::cloneACCRegionInto(src, dest, insertionPoint, mapping, ValueRange{});
(void)replacements;
return ip;
}
} // namespace
namespace mlir {
namespace acc {
std::pair<SmallVector<Value>, Block::iterator>
cloneACCRegionInto(Region *src, Block *dest, Block::iterator inlinePoint,
IRMapping &mapping, ValueRange resultsToReplace) {
if (!src->hasOneBlock())
llvm_unreachable("cloneACCRegionInto: multi-block region not supported "
"(requires scf.execute_region)");
Region *insertRegion = dest->getParent();
Block *postInsertBlock = dest->splitBlock(inlinePoint);
src->cloneInto(insertRegion, postInsertBlock->getIterator(), mapping);
SmallVector<Value> replacements;
Block *lastNewBlock = &*std::prev(postInsertBlock->getIterator());
Block::iterator ip;
if (auto yieldOp = dyn_cast<acc::YieldOp>(lastNewBlock->getTerminator())) {
for (auto [replacement, orig] :
llvm::zip(yieldOp.getOperands(), resultsToReplace)) {
replaceAllUsesInRegionWith(orig, replacement, *dest->getParent());
replacements.push_back(replacement);
}
ip = std::prev(yieldOp->getIterator());
yieldOp.erase();
} else {
auto terminatorOp =
dyn_cast<acc::TerminatorOp>(lastNewBlock->getTerminator());
if (!terminatorOp)
llvm_unreachable(
"cloneACCRegionInto: expected acc.yield or acc.terminator");
ip = std::prev(terminatorOp->getIterator());
terminatorOp.erase();
}
lastNewBlock->getOperations().splice(lastNewBlock->end(),
postInsertBlock->getOperations());
postInsertBlock->erase();
Block *firstNewBlock = &*std::next(dest->getIterator());
dest->getOperations().splice(dest->end(), firstNewBlock->getOperations());
firstNewBlock->erase();
return {replacements, ip};
}
/// Wrap a multi-block region with scf.execute_region.
scf::ExecuteRegionOp
wrapMultiBlockRegionWithSCFExecuteRegion(Region &region, IRMapping &mapping,
Location loc, RewriterBase &rewriter) {
SmallVector<Operation *> terminators;
for (Block &block : region.getBlocks()) {
if (block.empty())
continue;
Operation *term = block.getTerminator();
if (term->getNumSuccessors() == 0)
terminators.push_back(term);
}
SmallVector<Type> resultTypes;
if (!terminators.empty())
for (Value operand : terminators.front()->getOperands())
resultTypes.push_back(operand.getType());
auto exeRegionOp =
scf::ExecuteRegionOp::create(rewriter, loc, TypeRange(resultTypes));
rewriter.cloneRegionBefore(region, exeRegionOp.getRegion(),
exeRegionOp.getRegion().end(), mapping);
for (Operation *term : terminators) {
Operation *blockTerminator = mapping.lookup(term);
assert(blockTerminator && "expected terminator to be in mapping");
rewriter.setInsertionPoint(blockTerminator);
(void)scf::YieldOp::create(rewriter, blockTerminator->getLoc(),
blockTerminator->getOperands());
rewriter.eraseOp(blockTerminator);
}
return exeRegionOp;
}
scf::ForOp convertACCLoopToSCFFor(LoopOp loopOp, RewriterBase &rewriter,
bool enableCollapse) {
assert(!loopOp.getUnstructured() &&
"use convertUnstructuredACCLoopToSCFExecuteRegion for unstructured "
"loops");
Location loc = loopOp->getLoc();
IRMapping mapping;
SmallVector<scf::ForOp> forOps;
OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPoint(loopOp);
// Normalize all loops: lb=0, step=1, ub=tripCount.
// scf.for requires a positive step, but acc.loop may have arbitrary steps
// (including negative). Normalizing unconditionally keeps this consistent
// with convertACCLoopToSCFParallel and lets later passes fold constants.
Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
Value one = arith::ConstantIndexOp::create(rewriter, loc, 1);
SmallVector<Value> tripCounts;
for (auto [idx, iv] : llvm::enumerate(loopOp.getBody().getArguments())) {
bool inclusiveUpperbound = false;
if (loopOp.getInclusiveUpperbound().has_value())
inclusiveUpperbound =
loopOp.getInclusiveUpperboundAttr().asArrayRef()[idx];
Value tc = calculateTripCount(rewriter, loc, loopOp.getLowerbound()[idx],
loopOp.getUpperbound()[idx],
loopOp.getStep()[idx], inclusiveUpperbound);
tripCounts.push_back(tc);
}
for (auto [idx, iv] : llvm::enumerate(loopOp.getBody().getArguments())) {
// For nested loops, insert inside the previous loop's body
if (idx > 0)
rewriter.setInsertionPointToStart(forOps.back().getBody());
scf::ForOp forOp =
scf::ForOp::create(rewriter, loc, zero, tripCounts[idx], one);
forOps.push_back(forOp);
mapping.map(iv, forOp.getInductionVar());
}
// Set insertion point inside the innermost loop for IV casts and body cloning
rewriter.setInsertionPointToStart(forOps.back().getBody());
// Handle IV type conversion (index -> original type)
SmallVector<Value> scfIVs;
for (scf::ForOp forOp : forOps)
scfIVs.push_back(forOp.getInductionVar());
mapACCLoopIVsToSCFIVs(loopOp, scfIVs, rewriter, mapping);
// Clone the loop body into the innermost scf.for
cloneACCRegionIntoForLoop(&loopOp.getRegion(), forOps.back().getBody(),
rewriter.getInsertionPoint(), mapping, rewriter);
// Denormalize IV uses: original_iv = normalized_iv * orig_step + orig_lb
for (size_t idx = 0; idx < forOps.size(); ++idx) {
Value iv = forOps[idx].getInductionVar();
if (!iv.use_empty()) {
rewriter.setInsertionPointToStart(forOps[idx].getBody());
normalizeIVUses(rewriter, loc, iv, loopOp.getLowerbound()[idx],
loopOp.getStep()[idx]);
}
}
// Optionally collapse nested loops
if (enableCollapse && forOps.size() > 1) {
unsigned numCollapsed = forOps.size();
if (failed(coalesceLoops(rewriter, forOps)))
loopOp.emitError("failed to collapse acc.loop");
else
setCollapseCountAttr(forOps.front(), numCollapsed);
}
return forOps.front();
}
scf::ParallelOp convertACCLoopToSCFParallel(LoopOp loopOp,
RewriterBase &rewriter) {
assert(!loopOp.getUnstructured() &&
"use convertUnstructuredACCLoopToSCFExecuteRegion for unstructured "
"loops");
assert(
rewriter.getInsertionBlock() &&
!loopOp->isProperAncestor(rewriter.getInsertionBlock()->getParentOp()) &&
"builder insertion point must not be inside the loop being converted");
Location loc = loopOp->getLoc();
SmallVector<Value> lowerBounds, upperBounds, steps;
// Normalize all loops: lb=0, step=1, ub=tripCount
Value lb = arith::ConstantIndexOp::create(rewriter, loc, 0);
Value step = arith::ConstantIndexOp::create(rewriter, loc, 1);
for (auto [idx, iv] : llvm::enumerate(loopOp.getBody().getArguments())) {
bool inclusiveUpperbound = false;
if (loopOp.getInclusiveUpperbound().has_value())
inclusiveUpperbound = loopOp.getInclusiveUpperbound().value()[idx];
Value ub = calculateTripCount(rewriter, loc, loopOp.getLowerbound()[idx],
loopOp.getUpperbound()[idx],
loopOp.getStep()[idx], inclusiveUpperbound);
lowerBounds.push_back(lb);
upperBounds.push_back(ub);
steps.push_back(step);
}
auto parallelOp =
scf::ParallelOp::create(rewriter, loc, lowerBounds, upperBounds, steps);
// Create IV type conversions
IRMapping mapping;
rewriter.setInsertionPointToStart(parallelOp.getBody());
mapACCLoopIVsToSCFIVs(loopOp, parallelOp.getInductionVars(), rewriter,
mapping);
if (!loopOp.getRegion().hasOneBlock()) {
auto exeRegion = wrapMultiBlockRegionWithSCFExecuteRegion(
loopOp.getRegion(), mapping, loc, rewriter);
if (!exeRegion) {
rewriter.eraseOp(parallelOp);
return nullptr;
}
} else {
cloneACCRegionIntoForLoop(&loopOp.getRegion(), parallelOp.getBody(),
rewriter.getInsertionPoint(), mapping, rewriter);
}
// Denormalize IV uses
rewriter.setInsertionPointToStart(parallelOp.getBody());
for (auto [idx, iv] : llvm::enumerate(parallelOp.getBody()->getArguments()))
if (!iv.use_empty())
normalizeIVUses(rewriter, loc, iv, loopOp.getLowerbound()[idx],
loopOp.getStep()[idx]);
setCollapseCountAttr(parallelOp, parallelOp.getNumLoops());
return parallelOp;
}
scf::ExecuteRegionOp
convertUnstructuredACCLoopToSCFExecuteRegion(LoopOp loopOp,
RewriterBase &rewriter) {
assert(loopOp.getUnstructured() &&
"use convertACCLoopToSCFFor for structured loops");
assert(
rewriter.getInsertionBlock() &&
!loopOp->isProperAncestor(rewriter.getInsertionBlock()->getParentOp()) &&
"builder insertion point must not be inside the loop being converted");
IRMapping mapping;
return wrapMultiBlockRegionWithSCFExecuteRegion(loopOp.getRegion(), mapping,
loopOp->getLoc(), rewriter);
}
void setCollapseCountAttr(Operation *op, uint64_t count) {
op->setAttr(getCollapseCountAttrName(),
IntegerAttr::get(IntegerType::get(op->getContext(), 64), count));
}
uint64_t getCollapseCount(Operation *op) {
if (auto attr = op->getAttrOfType<IntegerAttr>(getCollapseCountAttrName()))
return attr.getValue().getZExtValue();
return 1;
}
} // namespace acc
} // namespace mlir