//===- Dataflow.h - Generic Dataflow Analysis Framework --------*- C++ -*-===// // // 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, policy-based driver for dataflow analyses. // It provides a flexible framework that combines the dataflow runner and // transfer functions, allowing derived classes to implement specific analyses // by defining their lattice, join, and transfer functions. // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H #define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/FlowSensitive/DataflowWorklist.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/TimeProfiler.h" #include namespace clang::lifetimes::internal { enum class Direction { Forward, Backward }; /// A `ProgramPoint` identifies a location in the CFG by pointing to a specific /// `Fact`. identified by a lifetime-related event (`Fact`). /// /// A `ProgramPoint` has "after" semantics: it represents the location /// immediately after its corresponding `Fact`. using ProgramPoint = const Fact *; /// A generic, policy-based driver for dataflow analyses. It combines /// the dataflow runner and the transferer logic into a single class hierarchy. /// /// The derived class is expected to provide: /// - A `Lattice` type. /// - `StringRef getAnalysisName() const` /// - `Lattice getInitialState();` The initial state of the analysis. /// - `Lattice join(Lattice, Lattice);` Merges states from multiple CFG paths. /// - `Lattice transfer(Lattice, const FactType&);` Defines how a single /// lifetime-relevant `Fact` transforms the lattice state. Only overloads /// for facts relevant to the analysis need to be implemented. /// /// \tparam Derived The CRTP derived class that implements the specific /// analysis. /// \tparam LatticeType The dataflow lattice used by the analysis. /// \tparam Dir The direction of the analysis (Forward or Backward). /// TODO: Maybe use the dataflow framework! The framework might need changes /// to support the current comparison done at block-entry. template class DataflowAnalysis { public: using Lattice = LatticeType; using Base = DataflowAnalysis; private: const CFG &Cfg; AnalysisDeclContext &AC; /// The dataflow state before a basic block is processed. llvm::DenseMap InStates; /// The dataflow state after a basic block is processed. llvm::DenseMap OutStates; /// Dataflow state at each program point, indexed by Fact ID. /// In a forward analysis, this is the state after the Fact at that point has /// been applied, while in a backward analysis, it is the state before. llvm::SmallVector PointToState; static constexpr bool isForward() { return Dir == Direction::Forward; } protected: FactManager &FactMgr; explicit DataflowAnalysis(const CFG &Cfg, AnalysisDeclContext &AC, FactManager &FactMgr) : Cfg(Cfg), AC(AC), FactMgr(FactMgr) {} public: void run() { Derived &D = static_cast(*this); llvm::TimeTraceScope Time(D.getAnalysisName()); PointToState.resize(FactMgr.getNumFacts()); using Worklist = std::conditional_t; Worklist W(Cfg, AC); const CFGBlock *Start = isForward() ? &Cfg.getEntry() : &Cfg.getExit(); InStates[Start] = D.getInitialState(); W.enqueueBlock(Start); while (const CFGBlock *B = W.dequeue()) { Lattice StateIn = *getInState(B); Lattice StateOut = transferBlock(B, StateIn); OutStates[B] = StateOut; for (const CFGBlock *AdjacentB : isForward() ? B->succs() : B->preds()) { if (!AdjacentB) continue; std::optional OldInState = getInState(AdjacentB); Lattice NewInState = !OldInState ? StateOut : D.join(*OldInState, StateOut); // Enqueue the adjacent block if its in-state has changed or if we have // never seen it. if (!OldInState || NewInState != *OldInState) { InStates[AdjacentB] = NewInState; W.enqueueBlock(AdjacentB); } } } } protected: Lattice getState(ProgramPoint P) const { return PointToState[P->getID().Value]; } std::optional getInState(const CFGBlock *B) const { auto It = InStates.find(B); if (It == InStates.end()) return std::nullopt; return It->second; } Lattice getOutState(const CFGBlock *B) const { return OutStates.lookup(B); } void dump() const { const Derived *D = static_cast(this); llvm::dbgs() << "==========================================\n"; llvm::dbgs() << D->getAnalysisName() << " results:\n"; llvm::dbgs() << "==========================================\n"; const CFGBlock &B = isForward() ? Cfg.getExit() : Cfg.getEntry(); getOutState(&B).dump(llvm::dbgs()); } private: /// Computes the state at one end of a block by applying all its facts /// sequentially to a given state from the other end. Lattice transferBlock(const CFGBlock *Block, Lattice State) { auto Facts = FactMgr.getFacts(Block); if constexpr (isForward()) { for (const Fact *F : Facts) { State = transferFact(State, F); PointToState[F->getID().Value] = State; } } else { for (const Fact *F : llvm::reverse(Facts)) { // In backward analysis, capture the state before applying the fact. PointToState[F->getID().Value] = State; State = transferFact(State, F); } } return State; } Lattice transferFact(Lattice In, const Fact *F) { assert(F); Derived *D = static_cast(this); switch (F->getKind()) { case Fact::Kind::Issue: return D->transfer(In, *F->getAs()); case Fact::Kind::Expire: return D->transfer(In, *F->getAs()); case Fact::Kind::OriginFlow: return D->transfer(In, *F->getAs()); case Fact::Kind::OriginEscapes: return D->transfer(In, *F->getAs()); case Fact::Kind::Use: return D->transfer(In, *F->getAs()); case Fact::Kind::TestPoint: return D->transfer(In, *F->getAs()); } llvm_unreachable("Unknown fact kind"); } public: Lattice transfer(Lattice In, const IssueFact &) { return In; } Lattice transfer(Lattice In, const ExpireFact &) { return In; } Lattice transfer(Lattice In, const OriginFlowFact &) { return In; } Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; } Lattice transfer(Lattice In, const UseFact &) { return In; } Lattice transfer(Lattice In, const TestPointFact &) { return In; } }; } // namespace clang::lifetimes::internal #endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H