blob: d4f0e99943e993d4fed71fb8d789efd14ed76c5a [file] [edit]
//===- StackArrays.h ------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
//
//===----------------------------------------------------------------------===//
//
// This header exposes the reusable pieces of the StackArrays pass: the
// analysis that determines which fir.allocmem operations can safely be moved
// to the stack (and where the replacement fir.alloca should be inserted), and
// the pattern that performs the heap-to-stack rewrite. They are shared so that
// other passes can reuse the same "is this heap allocation safely stackifiable,
// and where" logic.
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_OPTIMIZER_TRANSFORMS_STACKARRAYS_H
#define FORTRAN_OPTIMIZER_TRANSFORMS_STACKARRAYS_H
#include "flang/Optimizer/Dialect/FIROps.h"
#include "flang/Optimizer/Dialect/Support/KindMapping.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Interfaces/DataLayoutInterfaces.h"
#include "mlir/Support/TypeID.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Casting.h"
#include <optional>
namespace fir {
/// Stores where an alloca should be inserted. If the PointerUnion is an
/// Operation the alloca should be inserted /after/ the operation. If it is a
/// block, the alloca can be placed anywhere in that block.
class InsertionPoint {
llvm::PointerUnion<mlir::Operation *, mlir::Block *> location;
bool saveRestoreStack;
/// Get contained pointer type or nullptr
template <class T>
T *tryGetPtr() const {
// Use llvm::dyn_cast_if_present because location may be null here.
if (T *ptr = llvm::dyn_cast_if_present<T *>(location))
return ptr;
return nullptr;
}
public:
template <class T>
InsertionPoint(T *ptr, bool saveRestoreStack = false)
: location(ptr), saveRestoreStack{saveRestoreStack} {}
InsertionPoint(std::nullptr_t null)
: location(null), saveRestoreStack{false} {}
/// Get contained operation, or nullptr
mlir::Operation *tryGetOperation() const {
return tryGetPtr<mlir::Operation>();
}
/// Get contained block, or nullptr
mlir::Block *tryGetBlock() const { return tryGetPtr<mlir::Block>(); }
/// Get whether the stack should be saved/restored. If yes, an llvm.stacksave
/// intrinsic should be added before the alloca, and an llvm.stackrestore
/// intrinsic should be added where the freemem is
bool shouldSaveRestoreStack() const { return saveRestoreStack; }
operator bool() const { return tryGetOperation() || tryGetBlock(); }
bool operator==(const InsertionPoint &rhs) const {
return (location == rhs.location) &&
(saveRestoreStack == rhs.saveRestoreStack);
}
bool operator!=(const InsertionPoint &rhs) const { return !(*this == rhs); }
};
/// Drives analysis to find candidate fir.allocmem operations which could be
/// moved to the stack. Intended to be used with mlir::Pass::getAnalysis
class StackArraysAnalysisWrapper {
public:
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(StackArraysAnalysisWrapper)
// Maps fir.allocmem -> place to insert alloca
using AllocMemMap = llvm::DenseMap<mlir::Operation *, InsertionPoint>;
StackArraysAnalysisWrapper(mlir::Operation *op) {}
// Returns nullptr if analysis failed.
// Note: the returned pointer points into funcMaps and is invalidated if
// funcMaps grows (i.e. when a not-yet-analysed function is queried). This
// does not happen currently because each StackArraysAnalysisWrapper instance
// is only used to analyse a single function.
const AllocMemMap *getCandidateOps(mlir::Operation *func);
private:
llvm::DenseMap<mlir::Operation *, AllocMemMap> funcMaps;
llvm::LogicalResult analyseFunction(mlir::Operation *func);
};
/// Converts a fir.allocmem to a fir.alloca
class AllocMemConversion : public mlir::OpRewritePattern<fir::AllocMemOp> {
public:
explicit AllocMemConversion(
mlir::MLIRContext *ctx,
const StackArraysAnalysisWrapper::AllocMemMap &candidateOps,
std::optional<mlir::DataLayout> &dl,
std::optional<fir::KindMapping> &kindMap)
: OpRewritePattern(ctx), candidateOps{candidateOps}, dl{dl},
kindMap{kindMap} {}
llvm::LogicalResult
matchAndRewrite(fir::AllocMemOp allocmem,
mlir::PatternRewriter &rewriter) const override;
/// Determine where to insert the alloca operation. The returned value should
/// be checked to see if it is inside a loop
static InsertionPoint
findAllocaInsertionPoint(fir::AllocMemOp &oldAlloc,
const llvm::SmallVector<mlir::Operation *> &freeOps);
private:
/// Handle to the DFA (already run)
const StackArraysAnalysisWrapper::AllocMemMap &candidateOps;
const std::optional<mlir::DataLayout> &dl;
const std::optional<fir::KindMapping> &kindMap;
/// If we failed to find an insertion point not inside a loop, see if it would
/// be safe to use an llvm.stacksave/llvm.stackrestore inside the loop
static InsertionPoint findAllocaLoopInsertionPoint(
fir::AllocMemOp &oldAlloc,
const llvm::SmallVector<mlir::Operation *> &freeOps);
/// Returns the alloca if it was successfully inserted, otherwise {}
std::optional<fir::AllocaOp>
insertAlloca(fir::AllocMemOp &oldAlloc,
mlir::PatternRewriter &rewriter) const;
/// Inserts a stacksave before oldAlloc and a stackrestore after each freemem
void insertStackSaveRestore(fir::AllocMemOp oldAlloc,
mlir::PatternRewriter &rewriter) const;
/// Emit lifetime markers for newAlloc between oldAlloc and each freemem.
/// If the allocation is dynamic, no life markers are emitted.
void insertLifetimeMarkers(fir::AllocMemOp oldAlloc, fir::AllocaOp newAlloc,
mlir::PatternRewriter &rewriter) const;
};
} // namespace fir
#endif // FORTRAN_OPTIMIZER_TRANSFORMS_STACKARRAYS_H