//===- DataFlowFramework.h - A generic framework for data-flow analysis ---===// // // 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 // //===----------------------------------------------------------------------===// // // This file defines a generic framework for writing data-flow analysis in MLIR. // The framework consists of a solver, which runs the fixed-point iteration and // manages analysis dependencies, and a data-flow analysis class used to // implement specific analyses. // //===----------------------------------------------------------------------===// #ifndef MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H #define MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H #include "mlir/IR/Operation.h" #include "mlir/Support/StorageUniquer.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/SetVector.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/TypeName.h" #include <queue> #include <tuple> namespace mlir { //===----------------------------------------------------------------------===// // ChangeResult //===----------------------------------------------------------------------===// /// A result type used to indicate if a change happened. Boolean operations on /// ChangeResult behave as though `Change` is truth. enum class [[nodiscard]] ChangeResult { … }; inline ChangeResult operator|(ChangeResult lhs, ChangeResult rhs) { … } inline ChangeResult &operator|=(ChangeResult &lhs, ChangeResult rhs) { … } inline ChangeResult operator&(ChangeResult lhs, ChangeResult rhs) { … } /// Forward declare the analysis state class. class AnalysisState; /// Program point represents a specific location in the execution of a program. /// A sequence of program points can be combined into a control flow graph. struct ProgramPoint : public StorageUniquer::BaseStorage { … }; inline raw_ostream &operator<<(raw_ostream &os, ProgramPoint point) { … } //===----------------------------------------------------------------------===// // GenericLatticeAnchor //===----------------------------------------------------------------------===// /// Abstract class for generic lattice anchor. In classical data-flow analysis, /// lattice anchor represent positions in a program to which lattice elements /// are attached. In sparse data-flow analysis, these can be SSA values, and in /// dense data-flow analysis, these are the program points before and after /// every operation. /// /// Lattice anchor are implemented using MLIR's storage uniquer framework and /// type ID system to provide RTTI. class GenericLatticeAnchor : public StorageUniquer::BaseStorage { … }; //===----------------------------------------------------------------------===// // GenericLatticeAnchorBase //===----------------------------------------------------------------------===// /// Base class for generic lattice anchor based on a concrete lattice anchor /// type and a content key. This class defines the common methods required for /// operability with the storage uniquer framework. /// /// The provided key type uniquely identifies the concrete lattice anchor /// instance and are the data members of the class. template <typename ConcreteT, typename Value> class GenericLatticeAnchorBase : public GenericLatticeAnchor { … }; //===----------------------------------------------------------------------===// // LatticeAnchor //===----------------------------------------------------------------------===// /// Fundamental IR components are supported as first-class lattice anchor. struct LatticeAnchor : public PointerUnion<GenericLatticeAnchor *, ProgramPoint *, Value> { … }; /// Forward declaration of the data-flow analysis class. class DataFlowAnalysis; //===----------------------------------------------------------------------===// // DataFlowConfig //===----------------------------------------------------------------------===// /// Configuration class for data flow solver and child analyses. Follows the /// fluent API pattern. class DataFlowConfig { … }; //===----------------------------------------------------------------------===// // DataFlowSolver //===----------------------------------------------------------------------===// /// The general data-flow analysis solver. This class is responsible for /// orchestrating child data-flow analyses, running the fixed-point iteration /// algorithm, managing analysis state and lattice anchor memory, and tracking /// dependencies between analyses, lattice anchor, and analysis states. /// /// Steps to run a data-flow analysis: /// /// 1. Load and initialize children analyses. Children analyses are instantiated /// in the solver and initialized, building their dependency relations. /// 2. Configure and run the analysis. The solver invokes the children analyses /// according to their dependency relations until a fixed point is reached. /// 3. Query analysis state results from the solver. /// /// TODO: Optimize the internal implementation of the solver. class DataFlowSolver { … }; //===----------------------------------------------------------------------===// // AnalysisState //===----------------------------------------------------------------------===// /// Base class for generic analysis states. Analysis states contain data-flow /// information that are attached to lattice anchors and which evolve as the /// analysis iterates. /// /// This class places no restrictions on the semantics of analysis states beyond /// these requirements. /// /// 1. Querying the state of a lattice anchor prior to visiting that anchor /// results in uninitialized state. Analyses must be aware of unintialized /// states. /// 2. Analysis states can reach fixpoints, where subsequent updates will never /// trigger a change in the state. /// 3. Analysis states that are uninitialized can be forcefully initialized to a /// default value. class AnalysisState { … }; //===----------------------------------------------------------------------===// // DataFlowAnalysis //===----------------------------------------------------------------------===// /// Base class for all data-flow analyses. A child analysis is expected to build /// an initial dependency graph (and optionally provide an initial state) when /// initialized and define transfer functions when visiting program points. /// /// In classical data-flow analysis, the dependency graph is fixed and analyses /// define explicit transfer functions between input states and output states. /// In this framework, however, the dependency graph can change during the /// analysis, and transfer functions are opaque such that the solver doesn't /// know what states calling `visit` on an analysis will be updated. This allows /// multiple analyses to plug in and provide values for the same state. /// /// Generally, when an analysis queries an uninitialized state, it is expected /// to "bail out", i.e., not provide any updates. When the value is initialized, /// the solver will re-invoke the analysis. If the solver exhausts its worklist, /// however, and there are still uninitialized states, the solver "nudges" the /// analyses by default-initializing those states. class DataFlowAnalysis { … }; template <typename AnalysisT, typename... Args> AnalysisT *DataFlowSolver::load(Args &&...args) { … } template <typename StateT, typename AnchorT> StateT *DataFlowSolver::getOrCreateState(AnchorT anchor) { … } inline raw_ostream &operator<<(raw_ostream &os, const AnalysisState &state) { … } inline raw_ostream &operator<<(raw_ostream &os, LatticeAnchor anchor) { … } } // end namespace mlir namespace llvm { /// Allow hashing of lattice anchors and program points. template <> struct DenseMapInfo<mlir::ProgramPoint> { … }; template <> struct DenseMapInfo<mlir::LatticeAnchor> : public DenseMapInfo<mlir::LatticeAnchor::ParentTy> { … }; // Allow llvm::cast style functions. CastInfo<To, mlir::LatticeAnchor>; CastInfo<To, const mlir::LatticeAnchor>; } // end namespace llvm #endif // MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H