| //===- 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 |