blob: cb19cc2fa485bb77250a88c318a85fe0e5f77390 [file] [log] [blame] [edit]
//===- LoopInvariantCodeMotion.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
//
//===----------------------------------------------------------------------===//
/// \file
/// FIR-specific Loop Invariant Code Motion pass.
/// The pass relies on FIR types and interfaces to prove the safety
/// of hoisting invariant operations out of loop-like operations.
/// It may be run on both HLFIR and FIR representations.
//===----------------------------------------------------------------------===//
#include "flang/Optimizer/Analysis/AliasAnalysis.h"
#include "flang/Optimizer/Dialect/FIROpsSupport.h"
#include "flang/Optimizer/Dialect/FortranVariableInterface.h"
#include "flang/Optimizer/HLFIR/HLFIROps.h"
#include "flang/Optimizer/Transforms/Passes.h"
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/LoopInvariantCodeMotionUtils.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/DebugLog.h"
namespace fir {
#define GEN_PASS_DEF_LOOPINVARIANTCODEMOTION
#include "flang/Optimizer/Transforms/Passes.h.inc"
} // namespace fir
#define DEBUG_TYPE "flang-licm"
// Temporary engineering option for triaging LICM.
static llvm::cl::opt<bool> disableFlangLICM(
"disable-flang-licm", llvm::cl::init(false), llvm::cl::Hidden,
llvm::cl::desc("Disable Flang's loop invariant code motion"));
namespace {
using namespace mlir;
/// The pass tries to hoist loop invariant operations with only
/// MemoryEffects::Read effects (MemoryEffects::Write support
/// may be added later).
/// The safety of hoisting is proven by:
/// * Proving that the loop runs at least one iteration.
/// * Proving that is is always safe to load from this location
/// (see isSafeToHoistLoad() comments below).
struct LoopInvariantCodeMotion
: fir::impl::LoopInvariantCodeMotionBase<LoopInvariantCodeMotion> {
void runOnOperation() override;
};
} // namespace
/// 'location' is a memory reference used by a memory access.
/// The type of 'location' defines the data type of the access
/// (e.g. it is considered to be invalid to access 'i64'
/// data using '!fir.ref<i32>`).
/// For the given location, this function returns true iff
/// the Fortran object being accessed is a scalar that
/// may not be OPTIONAL.
///
/// Note that the '!fir.ref<!fir.box<>>' accesses are considered
/// to be scalar, even if the underlying data is an array.
///
/// Note that an access of '!fir.ref<scalar>' may access
/// an array object. For example:
/// real :: x(:)
/// do i=...
/// = x(10)
/// 'x(10)' accesses array 'x', and it may be unsafe to hoist
/// it without proving that '10' is a valid index for the array.
/// The fact that 'x' is not OPTIONAL does not allow hoisting
/// on its own.
static bool isNonOptionalScalar(Value location) {
while (true) {
LDBG() << "Checking location:\n" << location;
Type dataType = fir::unwrapRefType(location.getType());
if (!isa<fir::BaseBoxType>(location.getType()) &&
(!dataType ||
(!isa<fir::BaseBoxType>(dataType) && !fir::isa_trivial(dataType) &&
!fir::isa_derived(dataType)))) {
LDBG() << "Failure: data access is not scalar";
return false;
}
Operation *defOp = location.getDefiningOp();
if (!defOp) {
// If this is a function argument
auto blockArg = cast<BlockArgument>(location);
Block *block = blockArg.getOwner();
if (block && block->isEntryBlock())
if (auto funcOp =
dyn_cast_if_present<FunctionOpInterface>(block->getParentOp()))
if (!funcOp.getArgAttrOfType<UnitAttr>(blockArg.getArgNumber(),
fir::getOptionalAttrName())) {
LDBG() << "Success: is non optional scalar dummy";
return true;
}
LDBG() << "Failure: no defining operation";
return false;
}
// Scalars "defined" by fir.alloca and fir.address_of
// are present.
if (isa<fir::AllocaOp, fir::AddrOfOp>(defOp)) {
LDBG() << "Success: is non optional scalar";
return true;
}
if (auto varIface = dyn_cast<fir::FortranVariableOpInterface>(defOp)) {
if (varIface.isOptional()) {
// The variable is optional, so do not look further.
// Note that it is possible to deduce that the optional
// is actually present, but we are not doing it now.
LDBG() << "Failure: is optional";
return false;
}
// In case of MLIR inlining and ASSOCIATE an [hl]fir.declare
// may declare a scalar variable that is actually a "view"
// of an array element. Originally, such [hl]fir.declare
// would be located inside the loop preventing the hoisting.
// But if we decide to hoist such [hl]fir.declare in future,
// we cannot rely on their attributes/types.
// Use reliable checks based on the variable storage.
// If the variable has storage specifier (e.g. it is a member
// of COMMON, etc.), we can rely that the storage is present,
// and we can also rely on its FortranVariableOpInterface
// definition type (which is a scalar due to previous checks).
if (auto storageIface =
dyn_cast<fir::FortranVariableStorageOpInterface>(defOp))
if (Value storage = storageIface.getStorage()) {
LDBG() << "Success: is scalar with existing storage";
return true;
}
// TODO: we can probably use FIR AliasAnalysis' getSource()
// method to identify the storage in more cases.
Value memref = llvm::TypeSwitch<Operation *, Value>(defOp)
.Case<fir::DeclareOp, hlfir::DeclareOp>(
[](auto op) { return op.getMemref(); })
.Default([](auto) { return nullptr; });
if (memref)
return isNonOptionalScalar(memref);
LDBG() << "Failure: cannot reason about variable storage";
return false;
}
if (auto viewIface = dyn_cast<fir::FortranObjectViewOpInterface>(defOp)) {
location = viewIface.getViewSource(cast<OpResult>(location));
} else {
LDBG() << "Failure: unknown operation:\n" << *defOp;
return false;
}
}
}
/// Returns true iff it is safe to hoist the given load-like operation 'op',
/// which access given memory 'locations', out of the operation 'loopLike'.
/// The current safety conditions are:
/// * The loop runs at least one iteration, OR
/// * all the accessed locations are inside scalar non-OPTIONAL
/// Fortran objects (Fortran descriptors are considered to be scalars).
static bool isSafeToHoistLoad(Operation *op, ArrayRef<Value> locations,
LoopLikeOpInterface loopLike,
AliasAnalysis &aliasAnalysis) {
for (Value location : locations)
if (aliasAnalysis.getModRef(loopLike.getOperation(), location)
.isModAndRef()) {
LDBG() << "Failure: reads location:\n"
<< location << "\nwhich is modified inside the loop";
return false;
}
// Check that it is safe to read from all the locations before the loop.
std::optional<llvm::APInt> tripCount = loopLike.getStaticTripCount();
if (tripCount && !tripCount->isZero()) {
// Loop executes at least one iteration, so it is safe to hoist.
LDBG() << "Success: loop has non-zero iterations";
return true;
}
// Check whether the access must always be valid.
return llvm::all_of(
locations, [&](Value location) { return isNonOptionalScalar(location); });
// TODO: consider hoisting under condition of the loop's trip count
// being non-zero.
}
/// Returns true iff the given 'op' is a load-like operation,
/// and it can be hoisted out of 'loopLike' operation.
static bool canHoistLoad(Operation *op, LoopLikeOpInterface loopLike,
AliasAnalysis &aliasAnalysis) {
LDBG() << "Checking operation:\n" << *op;
if (auto effectInterface = dyn_cast<MemoryEffectOpInterface>(op)) {
SmallVector<MemoryEffects::EffectInstance> effects;
effectInterface.getEffects(effects);
if (effects.empty()) {
LDBG() << "Failure: not a load";
return false;
}
llvm::SetVector<Value> locations;
for (const MemoryEffects::EffectInstance &effect : effects) {
Value location = effect.getValue();
if (!isa<MemoryEffects::Read>(effect.getEffect())) {
LDBG() << "Failure: has unsupported effects";
return false;
} else if (!location) {
LDBG() << "Failure: reads from unknown location";
return false;
}
locations.insert(location);
}
return isSafeToHoistLoad(op, locations.getArrayRef(), loopLike,
aliasAnalysis);
}
LDBG() << "Failure: has unknown effects";
return false;
}
void LoopInvariantCodeMotion::runOnOperation() {
if (disableFlangLICM) {
LDBG() << "Skipping [HL]FIR LoopInvariantCodeMotion()";
return;
}
LDBG() << "Enter [HL]FIR LoopInvariantCodeMotion()";
auto &aliasAnalysis = getAnalysis<AliasAnalysis>();
aliasAnalysis.addAnalysisImplementation(fir::AliasAnalysis{});
std::function<bool(Operation *, LoopLikeOpInterface loopLike)>
shouldMoveOutOfLoop = [&](Operation *op, LoopLikeOpInterface loopLike) {
if (isPure(op)) {
LDBG() << "Pure operation: " << *op;
return true;
}
// Handle RecursivelySpeculatable operations that have
// RecursiveMemoryEffects by checking if all their
// nested operations can be hoisted.
auto iface = dyn_cast<ConditionallySpeculatable>(op);
if (iface && iface.getSpeculatability() ==
Speculation::RecursivelySpeculatable) {
if (op->hasTrait<OpTrait::HasRecursiveMemoryEffects>()) {
LDBG() << "Checking recursive operation:\n" << *op;
llvm::SmallVector<Operation *> nestedOps;
for (Region &region : op->getRegions())
for (Block &block : region)
for (Operation &nestedOp : block)
nestedOps.push_back(&nestedOp);
bool result = llvm::all_of(nestedOps, [&](Operation *nestedOp) {
return shouldMoveOutOfLoop(nestedOp, loopLike);
});
LDBG() << "Recursive operation can" << (result ? "" : "not")
<< " be hoisted";
// If nested operations cannot be hoisted, there is nothing
// else to check. Also if the operation itself does not have
// any memory effects, we can return the result now.
// Otherwise, we have to check the operation itself below.
if (!result || !isa<MemoryEffectOpInterface>(op))
return result;
}
}
return canHoistLoad(op, loopLike, aliasAnalysis);
};
getOperation()->walk([&](LoopLikeOpInterface loopLike) {
if (isa<omp::OutlineableOpenMPOpInterface>(loopLike.getOperation())) {
LDBG() << "Skipping omp::OutlineableOpenMPOpInterface operation";
return;
}
// We always hoist operations to the parent operation of the loopLike.
// Check that the parent operation allows the hoisting, e.g.
// omp::LoopWrapperInterface operations assume tight nesting
// of the inner maybe loop-like operations, so hoisting
// to such a parent would be invalid.
Operation *parentOp = loopLike->getParentOp();
if (!parentOp) {
LDBG() << "Skipping top-level loop-like operation?";
return;
} else if (isa<omp::LoopWrapperInterface>(parentOp)) {
LDBG() << "Skipping omp::LoopWrapperInterface operation";
return;
}
moveLoopInvariantCode(
loopLike.getLoopRegions(),
/*isDefinedOutsideRegion=*/
[&](Value value, Region *) {
return loopLike.isDefinedOutsideOfLoop(value);
},
/*shouldMoveOutOfRegion=*/
[&](Operation *op, Region *) {
return shouldMoveOutOfLoop(op, loopLike);
},
/*moveOutOfRegion=*/
[&](Operation *op, Region *) { loopLike.moveOutOfLoop(op); });
});
LDBG() << "Exit [HL]FIR LoopInvariantCodeMotion()";
}