This patch refactors the OpenACC dialect to attach recipe symbols
directly to data operations (acc.private, acc.firstprivate,
acc.reduction)
rather than to compute constructs (acc.parallel, acc.serial, acc.loop).
Motivation:
The previous design required compute constructs to carry both the recipe
symbol and the variable reference, leading to complexity. Additionally,
recipes were required even when they could be generated automatically
through MappableType interfaces.
Changes:
- Data operations (acc.private, acc.firstprivate, acc.reduction) now
require a 'recipe' attribute referencing their respective recipe
operations
- Verifier enforces recipe attribute presence for non-MappableType
operands; MappableType operands can generate recipes on demand
- Compute constructs (acc.parallel, acc.serial, acc.loop) no longer
carry recipe symbols in their operands
- Updated flang lowering to attach recipes to data operations instead
of passing them to compute constructs
Format Migration:
Old format:
```
acc.parallel private(@recipe -> %var : !fir.ref<i32>) { ... }
```
New format:
```
%private = acc.private varPtr(%var : !fir.ref<i32>)
recipe(@recipe) -> !fir.ref<i32>
acc.parallel private(%private : !fir.ref<i32>) { ... }
```
Test Updates:
- Updated all CIR and Flang OpenACC tests to new format
- Fixed CHECK lines to verify recipe attributes on data operations
185 lines
7.0 KiB
C++
185 lines
7.0 KiB
C++
//===- ACCRecipeBufferization.cpp -----------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Bufferize OpenACC recipes that yield fir.box<T> to operate on
|
|
// fir.ref<fir.box<T>> and update uses accordingly.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "flang/Optimizer/Dialect/FIROps.h"
|
|
#include "flang/Optimizer/OpenACC/Passes.h"
|
|
#include "mlir/Dialect/OpenACC/OpenACC.h"
|
|
#include "mlir/IR/Block.h"
|
|
#include "mlir/IR/Builders.h"
|
|
#include "mlir/IR/BuiltinOps.h"
|
|
#include "mlir/IR/SymbolTable.h"
|
|
#include "mlir/IR/Value.h"
|
|
#include "mlir/IR/Visitors.h"
|
|
#include "llvm/ADT/TypeSwitch.h"
|
|
|
|
namespace fir::acc {
|
|
#define GEN_PASS_DEF_ACCRECIPEBUFFERIZATION
|
|
#include "flang/Optimizer/OpenACC/Passes.h.inc"
|
|
} // namespace fir::acc
|
|
|
|
namespace {
|
|
|
|
class BufferizeInterface {
|
|
public:
|
|
static std::optional<mlir::Type> mustBufferize(mlir::Type recipeType) {
|
|
if (auto boxTy = llvm::dyn_cast<fir::BaseBoxType>(recipeType))
|
|
return fir::ReferenceType::get(boxTy);
|
|
return std::nullopt;
|
|
}
|
|
|
|
static mlir::Operation *load(mlir::OpBuilder &builder, mlir::Location loc,
|
|
mlir::Value value) {
|
|
return fir::LoadOp::create(builder, loc, value);
|
|
}
|
|
|
|
static mlir::Value placeInMemory(mlir::OpBuilder &builder, mlir::Location loc,
|
|
mlir::Value value) {
|
|
auto alloca = fir::AllocaOp::create(builder, loc, value.getType());
|
|
fir::StoreOp::create(builder, loc, value, alloca);
|
|
return alloca;
|
|
}
|
|
};
|
|
|
|
static void bufferizeRegionArgsAndYields(mlir::Region ®ion,
|
|
mlir::Location loc, mlir::Type oldType,
|
|
mlir::Type newType) {
|
|
if (region.empty())
|
|
return;
|
|
|
|
mlir::OpBuilder builder(®ion);
|
|
for (mlir::BlockArgument arg : region.getArguments()) {
|
|
if (arg.getType() == oldType) {
|
|
arg.setType(newType);
|
|
if (!arg.use_empty()) {
|
|
mlir::Operation *loadOp = BufferizeInterface::load(builder, loc, arg);
|
|
arg.replaceAllUsesExcept(loadOp->getResult(0), loadOp);
|
|
}
|
|
}
|
|
}
|
|
if (auto yield =
|
|
llvm::dyn_cast<mlir::acc::YieldOp>(region.back().getTerminator())) {
|
|
llvm::SmallVector<mlir::Value> newOperands;
|
|
newOperands.reserve(yield.getNumOperands());
|
|
bool changed = false;
|
|
for (mlir::Value oldYieldArg : yield.getOperands()) {
|
|
if (oldYieldArg.getType() == oldType) {
|
|
builder.setInsertionPoint(yield);
|
|
mlir::Value alloca =
|
|
BufferizeInterface::placeInMemory(builder, loc, oldYieldArg);
|
|
newOperands.push_back(alloca);
|
|
changed = true;
|
|
} else {
|
|
newOperands.push_back(oldYieldArg);
|
|
}
|
|
}
|
|
if (changed)
|
|
yield->setOperands(newOperands);
|
|
}
|
|
}
|
|
|
|
template <typename OpTy>
|
|
static void updateRecipeUse(mlir::ValueRange operands,
|
|
llvm::StringRef recipeSymName,
|
|
mlir::Operation *computeOp) {
|
|
for (auto operand : operands) {
|
|
auto op = operand.getDefiningOp<OpTy>();
|
|
if (!op || !op.getRecipe().has_value() ||
|
|
op.getRecipeAttr().getLeafReference() != recipeSymName)
|
|
continue;
|
|
|
|
mlir::Location loc = op->getLoc();
|
|
|
|
mlir::OpBuilder builder(op);
|
|
builder.setInsertionPointAfterValue(op.getVar());
|
|
mlir::Value alloca =
|
|
BufferizeInterface::placeInMemory(builder, loc, op.getVar());
|
|
op.getVarMutable().assign(alloca);
|
|
op.getAccVar().setType(alloca.getType());
|
|
|
|
mlir::Value oldRes = op.getAccVar();
|
|
llvm::SmallVector<mlir::Operation *> users(oldRes.getUsers().begin(),
|
|
oldRes.getUsers().end());
|
|
for (mlir::Operation *useOp : users) {
|
|
if (useOp == computeOp)
|
|
continue;
|
|
builder.setInsertionPoint(useOp);
|
|
mlir::Operation *load = BufferizeInterface::load(builder, loc, oldRes);
|
|
useOp->replaceUsesOfWith(oldRes, load->getResult(0));
|
|
}
|
|
}
|
|
}
|
|
|
|
class ACCRecipeBufferization
|
|
: public fir::acc::impl::ACCRecipeBufferizationBase<
|
|
ACCRecipeBufferization> {
|
|
public:
|
|
void runOnOperation() override {
|
|
mlir::ModuleOp module = getOperation();
|
|
|
|
llvm::SmallVector<llvm::StringRef> recipeNames;
|
|
module.walk([&](mlir::Operation *recipe) {
|
|
llvm::TypeSwitch<mlir::Operation *, void>(recipe)
|
|
.Case<mlir::acc::PrivateRecipeOp, mlir::acc::FirstprivateRecipeOp,
|
|
mlir::acc::ReductionRecipeOp>([&](auto recipe) {
|
|
mlir::Type oldType = recipe.getType();
|
|
auto bufferizedType =
|
|
BufferizeInterface::mustBufferize(recipe.getType());
|
|
if (!bufferizedType)
|
|
return;
|
|
recipe.setTypeAttr(mlir::TypeAttr::get(*bufferizedType));
|
|
mlir::Location loc = recipe.getLoc();
|
|
using RecipeOp = decltype(recipe);
|
|
bufferizeRegionArgsAndYields(recipe.getInitRegion(), loc, oldType,
|
|
*bufferizedType);
|
|
if constexpr (std::is_same_v<RecipeOp,
|
|
mlir::acc::FirstprivateRecipeOp>)
|
|
bufferizeRegionArgsAndYields(recipe.getCopyRegion(), loc, oldType,
|
|
*bufferizedType);
|
|
if constexpr (std::is_same_v<RecipeOp,
|
|
mlir::acc::ReductionRecipeOp>)
|
|
bufferizeRegionArgsAndYields(recipe.getCombinerRegion(), loc,
|
|
oldType, *bufferizedType);
|
|
bufferizeRegionArgsAndYields(recipe.getDestroyRegion(), loc,
|
|
oldType, *bufferizedType);
|
|
recipeNames.push_back(recipe.getSymName());
|
|
});
|
|
});
|
|
if (recipeNames.empty())
|
|
return;
|
|
|
|
module.walk([&](mlir::Operation *op) {
|
|
llvm::TypeSwitch<mlir::Operation *, void>(op)
|
|
.Case<mlir::acc::LoopOp, mlir::acc::ParallelOp, mlir::acc::SerialOp>(
|
|
[&](auto computeOp) {
|
|
for (llvm::StringRef recipeName : recipeNames) {
|
|
if (!computeOp.getPrivateOperands().empty())
|
|
updateRecipeUse<mlir::acc::PrivateOp>(
|
|
computeOp.getPrivateOperands(), recipeName, op);
|
|
if (!computeOp.getFirstprivateOperands().empty())
|
|
updateRecipeUse<mlir::acc::FirstprivateOp>(
|
|
computeOp.getFirstprivateOperands(), recipeName, op);
|
|
if (!computeOp.getReductionOperands().empty())
|
|
updateRecipeUse<mlir::acc::ReductionOp>(
|
|
computeOp.getReductionOperands(), recipeName, op);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<mlir::Pass> fir::acc::createACCRecipeBufferizationPass() {
|
|
return std::make_unique<ACCRecipeBufferization>();
|
|
}
|