CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
339 lines
13 KiB
C++
339 lines
13 KiB
C++
//===- 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 ®ion, 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)
|
|
if (failed(coalesceLoops(rewriter, forOps)))
|
|
loopOp.emitError("failed to collapse acc.loop");
|
|
|
|
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]);
|
|
|
|
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);
|
|
}
|
|
|
|
} // namespace acc
|
|
} // namespace mlir
|