blob: f9e62cccdc6e27d570836803653aca8dec826ad9 [file] [edit]
//===-- CUFAllocDelay.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
//
//===----------------------------------------------------------------------===//
//
// Delay cuf.alloc of descriptor (box) types from function entry to just before
// their first use. This defers cudaMallocManaged calls so that users can call
// cudaSetDevice before any CUDA context is created.
//
//===----------------------------------------------------------------------===//
#include "flang/Optimizer/Dialect/CUF/CUFOps.h"
#include "flang/Optimizer/Dialect/FIRDialect.h"
#include "flang/Optimizer/Dialect/FIROps.h"
#include "flang/Optimizer/Dialect/FIRType.h"
#include "mlir/IR/Block.h"
#include "mlir/Pass/Pass.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
namespace fir {
#define GEN_PASS_DEF_CUFALLOCDELAY
#include "flang/Optimizer/Transforms/Passes.h.inc"
} // namespace fir
namespace {
/// Find the earliest use of the descriptor and return the op before which the
/// cuf.alloc group should be placed. Uses in nested regions (fir.if,
/// fir.do_loop, ...) resolve to the enclosing entry-block op; uses confined to
/// a single successor block resolve to that block.
static mlir::Operation *findDelayTarget(fir::DeclareOp declareOp,
mlir::Block *entryBlock) {
mlir::Operation *earliest = nullptr;
mlir::Region *funcRegion = entryBlock->getParent();
// Uses per successor block, with the earliest op in each.
llvm::SmallDenseMap<mlir::Block *, mlir::Operation *> successorEarliest;
// Resolve a use in a nested region or successor block to a target in/after
// the entry block.
auto recordRealUse = [&](mlir::Operation *user) {
mlir::Operation *target = user;
while (target->getBlock() != entryBlock) {
// User in another block of the same function.
if (target->getBlock() && target->getBlock()->getParent() == funcRegion) {
mlir::Block *blk = target->getBlock();
auto it = successorEarliest.find(blk);
if (it == successorEarliest.end() ||
target->isBeforeInBlock(it->second))
successorEarliest[blk] = target;
return;
}
target = target->getParentOp();
if (!target)
return;
}
if (!earliest || target->isBeforeInBlock(earliest))
earliest = target;
};
for (mlir::Value result : declareOp->getResults()) {
for (mlir::Operation *user : result.getUsers())
recordRealUse(user);
}
if (earliest)
return earliest;
// No entry-block uses. If all successor uses are in a single block,
// delay directly into that block (before the earliest use there).
// Otherwise fall back to the entry block's terminator.
if (successorEarliest.size() == 1)
return successorEarliest.begin()->second;
if (!successorEarliest.empty())
return entryBlock->getTerminator();
return nullptr;
}
struct CUFAllocDelay : public fir::impl::CUFAllocDelayBase<CUFAllocDelay> {
void runOnOperation() override {
mlir::func::FuncOp func = getOperation();
if (func.empty())
return;
mlir::Block &entryBlock = func.front();
// Collect box-type cuf.alloc ops in the entry block.
llvm::SmallVector<cuf::AllocOp> boxAllocOps;
for (auto &op : entryBlock)
if (auto allocOp = mlir::dyn_cast<cuf::AllocOp>(op))
if (mlir::isa<fir::BaseBoxType>(allocOp.getInType()))
boxAllocOps.push_back(allocOp);
for (cuf::AllocOp allocOp : boxAllocOps) {
// Find the fir.declare and fir.store using this cuf.alloc; bail on any
// unexpected user.
fir::DeclareOp declareOp = nullptr;
fir::StoreOp storeOp = nullptr;
bool hasUnknownUser = false;
for (mlir::Operation *user : allocOp->getUsers()) {
if (auto decl = mlir::dyn_cast<fir::DeclareOp>(user))
declareOp = decl;
else if (auto store = mlir::dyn_cast<fir::StoreOp>(user))
storeOp = store;
else
hasUnknownUser = true;
}
if (!declareOp || hasUnknownUser)
continue;
mlir::Operation *delayTarget = findDelayTarget(declareOp, &entryBlock);
if (!delayTarget)
continue;
// Skip if the target is at or before the alloc, or is the declare.
if (delayTarget->getBlock() == allocOp->getBlock() &&
(delayTarget->isBeforeInBlock(allocOp) || delayTarget == allocOp))
continue;
if (delayTarget == declareOp)
continue;
// Sink {cuf.alloc, fir.store, fir.declare} before the target; the
// embox/shape/constants stay put and still dominate the new position.
allocOp->moveBefore(delayTarget);
if (storeOp)
storeOp->moveAfter(allocOp);
if (storeOp)
declareOp->moveAfter(storeOp);
else
declareOp->moveAfter(allocOp);
}
}
};
} // end anonymous namespace