blob: 4e3410d569e8bb93e095d7e11f00169e8195b29f [file] [edit]
//===-- RematerializeFIRBoxOps.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
//
//===----------------------------------------------------------------------===//
// Some NoMemoryEffect FIR operations create allocas as an implementation detail
// of their conversion to the LLVM dialect. These allocas must not be
// accidentally shared across different threads when OpenMP outlining is used.
// This pass rematerializes selected operations into the outlined regions,
// which ensures that the allocas are correctly located inside of the outlined
// function.
//
// Operations rematerialized by this pass are re-created at each use inside of
// the affected regions. LLVM-IR CSE later in the pipeline should merge these
// where possible. However, CSE must not be run between this pass and the
// FIR-to-LLVM conversion because MLIR CSE will completely undo the actions of
// this pass. This is because the side effects on the FIR operations do not
// represent the side effects produced by their implementation in the LLVM
// dialect. This pass makes FIR-to-LLVM descriptor allocation safe for outlined
// regions.
#include "flang/Optimizer/CodeGen/CodeGen.h"
#include "flang/Optimizer/Dialect/FIRDialect.h"
#include "flang/Optimizer/Dialect/FIROps.h"
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
namespace fir {
#define GEN_PASS_DEF_REMATERIALIZEFIRBOXOPSPASS
#include "flang/Optimizer/CodeGen/CGPasses.h.inc"
} // namespace fir
namespace {
/// Returns true if \p op has a region that should be rematerialized into.
static bool isRematerializationRegionOp(mlir::Operation *op) {
return mlir::isa<mlir::omp::OutlineableOpenMPOpInterface>(op);
}
/// Return true for FIR box/class conversions whose operand may need to be
/// rematerialized while preserving the type expected by the use.
static bool isBoxConvert(mlir::Operation *op) {
auto convert = mlir::dyn_cast_or_null<fir::ConvertOp>(op);
return convert && mlir::isa<fir::BaseBoxType>(convert.getValue().getType()) &&
mlir::isa<fir::BaseBoxType>(convert.getType());
}
/// Return true if \p op should be cloned into rematerialization regions.
static bool shouldRematerialize(mlir::Operation *op) {
if (!op)
return false;
if (mlir::isa<fir::EmboxOp, fir::ReboxOp>(op)) {
assert(
mlir::isMemoryEffectFree(op) &&
"This transformation is not safe for operations with memory effects");
// Not all Embox and Rebox operations are speculatable. This should be safe
// because SSA can only express a use of the non-speculatable value inside
// of the region requiring rematerialization if that non-speculatable value
// always dominates the region. Therefore we are not adding any new UB from
// absent boxes/arguments. Furthermore, the newly rematerialized operations
// are created at the use site of the original value as a further guarantee
// that the rematerializations are only executed if the original value was
// executed.
return true;
}
// Rematerializing box-to-box conversions is safe and allows rematerialization
// of emboxes which are only used inside of the region through box-to-box
// conversions. Conversions to !fir.box<none> are common before calls to
// runtime functions.
if (isBoxConvert(op)) {
assert(
mlir::isPure(op) &&
"This transformation is not safe for operations with memory effects");
return shouldRematerialize(
mlir::cast<fir::ConvertOp>(op).getValue().getDefiningOp());
}
return false;
}
/// Return true if \p definingOp can be rematerialized into \p useRegion.
/// The use can be rematerialized if the defining operation is located
/// in an ancestor region of the use and the definition operation is
/// rematerializable.
static bool canRematerializeDefInRegion(mlir::Operation *definingOp,
mlir::Region &useRegion) {
if (!definingOp || !shouldRematerialize(definingOp))
return false;
mlir::Region *opRegion = definingOp->getParentRegion();
for (mlir::Region *ancestor = useRegion.getParentRegion(); ancestor;
ancestor = ancestor->getParentRegion()) {
if (opRegion == ancestor)
return true;
}
return false;
}
static mlir::Value cloneRematerializedValue(
mlir::IRRewriter &rewriter, mlir::Region &useRegion, mlir::Value value,
mlir::IRMapping &mapping,
llvm::SmallVectorImpl<mlir::Operation *> &eraseCandidates) {
if (mlir::Value mappedValue = mapping.lookupOrNull(value))
return mappedValue;
mlir::Operation *definingOp = value.getDefiningOp();
if (!canRematerializeDefInRegion(definingOp, useRegion))
return value;
// Clone rematerializable dependencies first so the cloned operation uses
// cloned operands when possible.
for (mlir::Value operand : definingOp->getOperands())
cloneRematerializedValue(rewriter, useRegion, operand, mapping,
eraseCandidates);
rewriter.clone(*definingOp, mapping);
eraseCandidates.push_back(definingOp);
return mapping.lookup(value);
}
struct RematerializationSite {
/// Operation whose operands will be rewritten. This is the insertion point
/// for the rematerialized operations.
mlir::Operation *user;
/// Operands of \c user that use values needing rematerialization.
llvm::SmallVector<mlir::OpOperand *> uses;
RematerializationSite(mlir::Operation *user,
llvm::SmallVector<mlir::OpOperand *> uses)
: user(user), uses(std::move(uses)) {}
};
/// Walk \p regionOwner and collect all rematerializable operands that use
/// values defined outside the region.
static void collectRematerializableUses(
mlir::Operation *regionOwner,
llvm::SmallVectorImpl<RematerializationSite> &rematSites) {
regionOwner->walk<mlir::WalkOrder::PreOrder>(
[&](mlir::Operation *op) -> mlir::WalkResult {
// Don't walk into nested rematerialization regions. They will be
// processed in their own calls to this function.
if (op != regionOwner && isRematerializationRegionOp(op))
return mlir::WalkResult::skip();
llvm::SmallVector<mlir::OpOperand *> rematerializableUses;
for (mlir::OpOperand &operand : op->getOpOperands()) {
if (canRematerializeDefInRegion(operand.get().getDefiningOp(),
*op->getParentRegion()))
rematerializableUses.push_back(&operand);
}
if (!rematerializableUses.empty())
rematSites.emplace_back(op, std::move(rematerializableUses));
return mlir::WalkResult::advance();
});
}
/// Rematerialize supported values defined outside of \p regionOwner into the
/// region
static void rematerializeInRegion(mlir::IRRewriter &rewriter,
mlir::Operation *regionOwner) {
llvm::SmallVector<RematerializationSite> rematSites;
collectRematerializableUses(regionOwner, rematSites);
if (rematSites.empty())
return;
mlir::OpBuilder::InsertionGuard guard(rewriter);
llvm::SmallVector<mlir::Operation *> eraseCandidates;
for (RematerializationSite &rematSite : rematSites) {
rewriter.setInsertionPoint(rematSite.user);
mlir::IRMapping mapping;
for (mlir::OpOperand *use : rematSite.uses) {
mlir::Region *useRegion = use->getOwner()->getParentRegion();
mlir::Value newValue = cloneRematerializedValue(
rewriter, *useRegion, use->get(), mapping, eraseCandidates);
use->set(newValue);
}
}
llvm::DenseSet<mlir::Operation *> erased;
for (mlir::Operation *op : llvm::reverse(eraseCandidates))
if (erased.insert(op).second && op->use_empty())
rewriter.eraseOp(op);
}
class RematerializeFIRBoxOpsPass
: public fir::impl::RematerializeFIRBoxOpsPassBase<
RematerializeFIRBoxOpsPass> {
public:
using RematerializeFIRBoxOpsPassBase<
RematerializeFIRBoxOpsPass>::RematerializeFIRBoxOpsPassBase;
void runOnOperation() override final {
mlir::Operation *top = getOperation();
llvm::SmallVector<mlir::Operation *> regionOwners;
top->walk([&](mlir::Operation *op) {
if (isRematerializationRegionOp(op))
regionOwners.push_back(op);
});
mlir::IRRewriter rewriter(top->getContext());
for (mlir::Operation *op : regionOwners)
rematerializeInRegion(rewriter, op);
}
};
} // namespace