blob: 32fd3957894fb16ddf0480efa5f6a6f04f10490d [file] [edit]
//===- OpenACCUtilsTiling.cpp - OpenACC Loop Tiling 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 tiling OpenACC loops.
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/OpenACC/OpenACCUtilsTiling.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Arith/Utils/Utils.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/Dialect/Utils/StaticValueUtils.h"
#include "mlir/Transforms/RegionUtils.h"
#include "llvm/ADT/STLExtras.h"
// Resolve unknown tile sizes (represented as -1 for tile(*)) to the default.
// Returns a value with the same type as targetType.
static mlir::Value resolveAndCastTileSize(mlir::Value tileSize,
int32_t defaultTileSize,
mlir::Type targetType,
mlir::RewriterBase &rewriter,
mlir::Location loc) {
auto constVal = mlir::getConstantIntValue(tileSize);
if (constVal && *constVal < 0) {
// Create constant with the target type directly
return mlir::arith::ConstantOp::create(
rewriter, loc, targetType,
rewriter.getIntegerAttr(targetType, defaultTileSize));
}
return mlir::getValueOrCreateCastToIndexLike(rewriter, loc, targetType,
tileSize);
}
// Remove vector/worker attributes from loop
static void removeWorkerVectorFromLoop(mlir::acc::LoopOp loop) {
if (loop.hasVector() || loop.getVectorValue()) {
loop.removeVectorAttr();
loop.removeVectorOperandsDeviceTypeAttr();
} else if (loop.hasWorker() || loop.getWorkerValue()) {
loop.removeWorkerAttr();
loop.removeWorkerNumOperandsDeviceTypeAttr();
}
}
// Create a new ACC loop with new steps, lb, ub from original loop
static mlir::acc::LoopOp createACCLoopFromOriginal(
mlir::acc::LoopOp origLoop, mlir::RewriterBase &rewriter,
mlir::ValueRange lb, mlir::ValueRange ub, mlir::ValueRange step,
mlir::DenseBoolArrayAttr inclusiveUBAttr,
mlir::acc::CombinedConstructsTypeAttr combinedAttr, mlir::Location loc) {
mlir::ArrayAttr collapseAttr = mlir::ArrayAttr{};
mlir::ArrayAttr collapseDeviceTypeAttr = mlir::ArrayAttr{};
auto newLoop = mlir::acc::LoopOp::create(
rewriter, loc, origLoop->getResultTypes(), lb, ub, step, inclusiveUBAttr,
collapseAttr, collapseDeviceTypeAttr, origLoop.getGangOperands(),
origLoop.getGangOperandsArgTypeAttr(),
origLoop.getGangOperandsSegmentsAttr(),
origLoop.getGangOperandsDeviceTypeAttr(), origLoop.getWorkerNumOperands(),
origLoop.getWorkerNumOperandsDeviceTypeAttr(),
origLoop.getVectorOperands(), origLoop.getVectorOperandsDeviceTypeAttr(),
origLoop.getSeqAttr(), origLoop.getIndependentAttr(),
origLoop.getAuto_Attr(), origLoop.getGangAttr(), origLoop.getWorkerAttr(),
origLoop.getVectorAttr(), mlir::ValueRange{}, mlir::DenseI32ArrayAttr{},
mlir::ArrayAttr{}, origLoop.getCacheOperands(),
origLoop.getPrivateOperands(), origLoop.getFirstprivateOperands(),
origLoop.getReductionOperands(), combinedAttr);
return newLoop;
}
// Move ops from source to target Loop and replace uses of IVs
static void moveOpsAndReplaceIVs(mlir::acc::LoopOp sourceLoop,
mlir::acc::LoopOp targetLoop,
llvm::ArrayRef<mlir::Value> newIVs,
llvm::ArrayRef<mlir::Value> origIVs,
size_t nOps, mlir::RewriterBase &rewriter) {
// nOps includes the terminator; move all ops except the terminator:
// [begin, begin + nOps - 1)
mlir::Block::iterator begin = sourceLoop.getBody().begin();
mlir::Block::iterator end = std::next(begin, nOps - 1);
// Notify the rewriter about all ops being moved (and their nested ops).
// Directly moved ops have their parent block changed (rewriter fingerprint
// tracking invalidated). Nested ops may have operands replaced by
// replaceAllUsesInRegionWith below.
llvm::SmallVector<mlir::Operation *> movedOps;
for (mlir::Block::iterator it = begin; it != end; ++it)
it->walk([&](mlir::Operation *op) {
movedOps.push_back(op);
rewriter.startOpModification(op);
});
targetLoop.getBody().getOperations().splice(
targetLoop.getBody().getOperations().begin(),
sourceLoop.getBody().getOperations(), begin, end);
// Replace uses of origIV with newIV
for (auto [i, newIV] : llvm::enumerate(newIVs))
mlir::replaceAllUsesInRegionWith(origIVs[i], newIV, targetLoop.getRegion());
for (mlir::Operation *op : movedOps)
rewriter.finalizeOpModification(op);
}
// Create a single "element group" loop nested in `tileLoop`, carrying
// `ivTypes.size()` induction variables so the whole element space is one
// multi-IV loop. The element group carries vector or worker but not gang.
static mlir::acc::LoopOp
createElementGroupLoop(mlir::acc::LoopOp tileLoop, mlir::RewriterBase &rewriter,
mlir::ValueRange lbs, mlir::ValueRange ubs,
mlir::ValueRange steps,
mlir::DenseBoolArrayAttr inclusiveUBAttr,
llvm::ArrayRef<mlir::Type> ivTypes, mlir::Location loc) {
mlir::acc::LoopOp elementLoop = createACCLoopFromOriginal(
tileLoop, rewriter, lbs, ubs, steps, inclusiveUBAttr,
mlir::acc::CombinedConstructsTypeAttr{}, loc);
// Drop gang from the element group, keeping vector/worker. The operand
// values must be cleared too, not just the attributes.
rewriter.startOpModification(elementLoop);
if (tileLoop.hasGang() ||
tileLoop.getGangValue(mlir::acc::GangArgType::Num) ||
tileLoop.getGangValue(mlir::acc::GangArgType::Dim) ||
tileLoop.getGangValue(mlir::acc::GangArgType::Static)) {
elementLoop.removeGangAttr();
elementLoop.removeGangOperandsArgTypeAttr();
elementLoop.removeGangOperandsSegmentsAttr();
elementLoop.removeGangOperandsDeviceTypeAttr();
elementLoop.getGangOperandsMutable().clear();
}
if (tileLoop.hasVector() || tileLoop.getVectorValue()) {
elementLoop.removeWorkerAttr();
elementLoop.removeWorkerNumOperandsDeviceTypeAttr();
elementLoop.getWorkerNumOperandsMutable().clear();
}
rewriter.finalizeOpModification(elementLoop);
// Create the element loop body: one block argument per IV plus a terminator.
mlir::Block *blk = rewriter.createBlock(&elementLoop.getRegion(),
elementLoop.getRegion().begin());
rewriter.setInsertionPointToEnd(blk);
mlir::acc::YieldOp::create(rewriter, loc);
for (mlir::Type ivType : ivTypes)
elementLoop.getBody().addArgument(ivType, loc);
return elementLoop;
}
mlir::acc::LoopOp
mlir::acc::tileACCLoops(mlir::acc::LoopOp tileLoop,
const llvm::SmallVector<mlir::Value> &tileSizes,
int32_t defaultTileSize, mlir::RewriterBase &rewriter) {
// Tile a single fused acc.loop that carries all associated induction
// variables. This keeps the tile iterations as one multi-IV "tile group"
// loop and the in-tile iterations as one multi-IV "element group" loop, each
// spanning all of its induction variables.
const mlir::Location loc = tileLoop.getLoc();
const unsigned tileCount = tileSizes.size();
llvm::SmallVector<mlir::Value, 3> origIVs(tileLoop.getBody().getArguments());
llvm::SmallVector<mlir::Value, 3> origUBs(tileLoop.getUpperbound());
llvm::SmallVector<mlir::Value, 3> origSteps(tileLoop.getStep());
const unsigned numIVs = origIVs.size();
const size_t nOps = tileLoop.getBody().getOperations().size();
// Original inclusive-UB flags (default false when the attribute is absent).
llvm::SmallVector<bool> inclusiveUBs;
for (unsigned i = 0; i < numIVs; ++i) {
if (tileLoop.getInclusiveUpperboundAttr())
inclusiveUBs.push_back(
tileLoop.getInclusiveUpperboundAttr().asArrayRef()[i]);
else
inclusiveUBs.push_back(false);
}
// Scale each tiled dimension's step by its tile size to form the tile group
// loop steps.
rewriter.setInsertionPoint(tileLoop);
llvm::SmallVector<mlir::Value, 3> scaledSteps;
llvm::SmallVector<mlir::Value, 3> tileLoopSteps;
for (unsigned i = 0; i < numIVs; ++i) {
if (i < tileCount) {
mlir::Value tileSize = resolveAndCastTileSize(
tileSizes[i], defaultTileSize, origSteps[i].getType(), rewriter, loc);
mlir::Value scaled =
mlir::arith::MulIOp::create(rewriter, loc, origSteps[i], tileSize);
scaledSteps.push_back(scaled);
tileLoopSteps.push_back(scaled);
} else {
tileLoopSteps.push_back(origSteps[i]);
}
}
// Compute the element-loop upper bounds min(origUB, origIV + scaledStep).
rewriter.setInsertionPoint(tileLoop.getBody().getTerminator());
llvm::SmallVector<mlir::Value, 3> elemLBs, elemUBs, elemSteps;
llvm::SmallVector<mlir::Type, 3> elemIVTypes;
llvm::SmallVector<bool> elemInclusiveUBs;
for (unsigned i = 0; i < tileCount; ++i) {
mlir::Value stepped =
mlir::arith::AddIOp::create(rewriter, loc, origIVs[i], scaledSteps[i]);
mlir::Value newUB = stepped;
if (inclusiveUBs[i]) {
// Inclusive UB: min(origUB, origIV + (scaledStep - 1)).
mlir::Value c1 = mlir::arith::ConstantOp::create(
rewriter, loc, scaledSteps[i].getType(),
rewriter.getIntegerAttr(scaledSteps[i].getType(), 1));
newUB = mlir::arith::SubIOp::create(rewriter, loc, stepped, c1);
}
elemUBs.push_back(
mlir::arith::MinSIOp::create(rewriter, loc, origUBs[i], newUB));
elemLBs.push_back(origIVs[i]);
elemSteps.push_back(origSteps[i]);
elemIVTypes.push_back(origIVs[i].getType());
elemInclusiveUBs.push_back(inclusiveUBs[i]);
}
// Only attach an inclusiveUpperbound attribute if at least one element
// dimension is inclusive.
mlir::DenseBoolArrayAttr elemInclAttr = mlir::DenseBoolArrayAttr{};
if (llvm::is_contained(elemInclusiveUBs, true))
elemInclAttr = rewriter.getDenseBoolArrayAttr(elemInclusiveUBs);
// Create the element group loop from the unmodified tile loop.
mlir::acc::LoopOp elementLoop =
createElementGroupLoop(tileLoop, rewriter, elemLBs, elemUBs, elemSteps,
elemInclAttr, elemIVTypes, loc);
// Move the original body into the element loop and remap the tiled IVs to the
// element IVs.
llvm::SmallVector<mlir::Value, 3> newIVs(
elementLoop.getBody().getArguments());
llvm::SmallVector<mlir::Value, 3> tiledOrigIVs(origIVs.begin(),
origIVs.begin() + tileCount);
moveOpsAndReplaceIVs(tileLoop, elementLoop, newIVs, tiledOrigIVs, nOps,
rewriter);
// Turn the fused loop into the tile group: scaled steps, gang only.
rewriter.startOpModification(tileLoop);
tileLoop.getStepMutable().clear();
tileLoop.getStepMutable().append(tileLoopSteps);
removeWorkerVectorFromLoop(tileLoop);
rewriter.finalizeOpModification(tileLoop);
return tileLoop;
}