xref: /llvm-project/mlir/include/mlir/Analysis/DataFlowFramework.h (revision 3a439e2caf0bb545ee451df1de5b02ea068140f7)
1 //===- DataFlowFramework.h - A generic framework for data-flow analysis ---===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file defines a generic framework for writing data-flow analysis in MLIR.
10 // The framework consists of a solver, which runs the fixed-point iteration and
11 // manages analysis dependencies, and a data-flow analysis class used to
12 // implement specific analyses.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #ifndef MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H
17 #define MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H
18 
19 #include "mlir/IR/Operation.h"
20 #include "mlir/Support/StorageUniquer.h"
21 #include "llvm/ADT/Hashing.h"
22 #include "llvm/ADT/SetVector.h"
23 #include "llvm/Support/Compiler.h"
24 #include "llvm/Support/TypeName.h"
25 #include <queue>
26 #include <tuple>
27 
28 namespace mlir {
29 
30 //===----------------------------------------------------------------------===//
31 // ChangeResult
32 //===----------------------------------------------------------------------===//
33 
34 /// A result type used to indicate if a change happened. Boolean operations on
35 /// ChangeResult behave as though `Change` is truth.
36 enum class [[nodiscard]] ChangeResult {
37   NoChange,
38   Change,
39 };
40 inline ChangeResult operator|(ChangeResult lhs, ChangeResult rhs) {
41   return lhs == ChangeResult::Change ? lhs : rhs;
42 }
43 inline ChangeResult &operator|=(ChangeResult &lhs, ChangeResult rhs) {
44   lhs = lhs | rhs;
45   return lhs;
46 }
47 inline ChangeResult operator&(ChangeResult lhs, ChangeResult rhs) {
48   return lhs == ChangeResult::NoChange ? lhs : rhs;
49 }
50 
51 /// Forward declare the analysis state class.
52 class AnalysisState;
53 
54 /// Program point represents a specific location in the execution of a program.
55 /// A sequence of program points can be combined into a control flow graph.
56 struct ProgramPoint : public StorageUniquer::BaseStorage {
57   /// Creates a new program point at the given location.
58   ProgramPoint(Block *parentBlock, Block::iterator pp)
59       : block(parentBlock), point(pp) {}
60 
61   /// Creates a new program point at the given operation.
62   ProgramPoint(Operation *op) : op(op) {}
63 
64   /// The concrete key type used by the storage uniquer. This class is uniqued
65   /// by its contents.
66   using KeyTy = std::tuple<Block *, Block::iterator, Operation *>;
67 
68   /// Create a empty program point.
69   ProgramPoint() {}
70 
71   /// Create a new program point from the given program point.
72   ProgramPoint(const ProgramPoint &point) {
73     this->block = point.getBlock();
74     this->point = point.getPoint();
75     this->op = point.getOperation();
76   }
77 
78   static ProgramPoint *construct(StorageUniquer::StorageAllocator &alloc,
79                                  KeyTy &&key) {
80     if (std::get<0>(key)) {
81       return new (alloc.allocate<ProgramPoint>())
82           ProgramPoint(std::get<0>(key), std::get<1>(key));
83     }
84     return new (alloc.allocate<ProgramPoint>()) ProgramPoint(std::get<2>(key));
85   }
86 
87   /// Returns true if this program point is set.
88   bool isNull() const { return block == nullptr && op == nullptr; }
89 
90   /// Two program points are equal if their block and iterator are equal.
91   bool operator==(const KeyTy &key) const {
92     return block == std::get<0>(key) && point == std::get<1>(key) &&
93            op == std::get<2>(key);
94   }
95 
96   bool operator==(const ProgramPoint &pp) const {
97     return block == pp.block && point == pp.point && op == pp.op;
98   }
99 
100   /// Get the block contains this program point.
101   Block *getBlock() const { return block; }
102 
103   /// Get the the iterator this program point refers to.
104   Block::iterator getPoint() const { return point; }
105 
106   /// Get the the iterator this program point refers to.
107   Operation *getOperation() const { return op; }
108 
109   /// Get the next operation of this program point.
110   Operation *getNextOp() const {
111     assert(!isBlockEnd());
112     // If the current program point has no parent block, both the next op and
113     // the previous op point to the op corresponding to the current program
114     // point.
115     if (block == nullptr) {
116       return op;
117     }
118     return &*point;
119   }
120 
121   /// Get the previous operation of this program point.
122   Operation *getPrevOp() const {
123     assert(!isBlockStart());
124     // If the current program point has no parent block, both the next op and
125     // the previous op point to the op corresponding to the current program
126     // point.
127     if (block == nullptr) {
128       return op;
129     }
130     return &*(--Block::iterator(point));
131   }
132 
133   bool isBlockStart() const { return block && block->begin() == point; }
134 
135   bool isBlockEnd() const { return block && block->end() == point; }
136 
137   /// Print the program point.
138   void print(raw_ostream &os) const;
139 
140 private:
141   Block *block = nullptr;
142   Block::iterator point;
143 
144   /// For operations without a parent block, we record the operation itself as
145   /// its program point.
146   Operation *op = nullptr;
147 };
148 
149 inline raw_ostream &operator<<(raw_ostream &os, ProgramPoint point) {
150   point.print(os);
151   return os;
152 }
153 
154 //===----------------------------------------------------------------------===//
155 // GenericLatticeAnchor
156 //===----------------------------------------------------------------------===//
157 
158 /// Abstract class for generic lattice anchor. In classical data-flow analysis,
159 /// lattice anchor represent positions in a program to which lattice elements
160 /// are attached. In sparse data-flow analysis, these can be SSA values, and in
161 /// dense data-flow analysis, these are the program points before and after
162 /// every operation.
163 ///
164 /// Lattice anchor are implemented using MLIR's storage uniquer framework and
165 /// type ID system to provide RTTI.
166 class GenericLatticeAnchor : public StorageUniquer::BaseStorage {
167 public:
168   virtual ~GenericLatticeAnchor();
169 
170   /// Get the abstract lattice anchor's type identifier.
171   TypeID getTypeID() const { return typeID; }
172 
173   /// Get a derived source location for the lattice anchor.
174   virtual Location getLoc() const = 0;
175 
176   /// Print the lattice anchor.
177   virtual void print(raw_ostream &os) const = 0;
178 
179 protected:
180   /// Create an abstract lattice anchor with type identifier.
181   explicit GenericLatticeAnchor(TypeID typeID) : typeID(typeID) {}
182 
183 private:
184   /// The type identifier of the lattice anchor.
185   TypeID typeID;
186 };
187 
188 //===----------------------------------------------------------------------===//
189 // GenericLatticeAnchorBase
190 //===----------------------------------------------------------------------===//
191 
192 /// Base class for generic lattice anchor based on a concrete lattice anchor
193 /// type and a content key. This class defines the common methods required for
194 /// operability with the storage uniquer framework.
195 ///
196 /// The provided key type uniquely identifies the concrete lattice anchor
197 /// instance and are the data members of the class.
198 template <typename ConcreteT, typename Value>
199 class GenericLatticeAnchorBase : public GenericLatticeAnchor {
200 public:
201   /// The concrete key type used by the storage uniquer. This class is uniqued
202   /// by its contents.
203   using KeyTy = Value;
204   /// Alias for the base class.
205   using Base = GenericLatticeAnchorBase<ConcreteT, Value>;
206 
207   /// Construct an instance of the lattice anchor using the provided value and
208   /// the type ID of the concrete type.
209   template <typename ValueT>
210   explicit GenericLatticeAnchorBase(ValueT &&value)
211       : GenericLatticeAnchor(TypeID::get<ConcreteT>()),
212         value(std::forward<ValueT>(value)) {}
213 
214   /// Get a uniqued instance of this lattice anchor class with the given
215   /// arguments.
216   template <typename... Args>
217   static ConcreteT *get(StorageUniquer &uniquer, Args &&...args) {
218     return uniquer.get<ConcreteT>(/*initFn=*/{}, std::forward<Args>(args)...);
219   }
220 
221   /// Allocate space for a lattice anchor and construct it in-place.
222   template <typename ValueT>
223   static ConcreteT *construct(StorageUniquer::StorageAllocator &alloc,
224                               ValueT &&value) {
225     return new (alloc.allocate<ConcreteT>())
226         ConcreteT(std::forward<ValueT>(value));
227   }
228 
229   /// Two lattice anchors are equal if their values are equal.
230   bool operator==(const Value &value) const { return this->value == value; }
231 
232   /// Provide LLVM-style RTTI using type IDs.
233   static bool classof(const GenericLatticeAnchor *point) {
234     return point->getTypeID() == TypeID::get<ConcreteT>();
235   }
236 
237   /// Get the contents of the lattice anchor.
238   const Value &getValue() const { return value; }
239 
240 private:
241   /// The lattice anchor value.
242   Value value;
243 };
244 
245 //===----------------------------------------------------------------------===//
246 // LatticeAnchor
247 //===----------------------------------------------------------------------===//
248 
249 /// Fundamental IR components are supported as first-class lattice anchor.
250 struct LatticeAnchor
251     : public PointerUnion<GenericLatticeAnchor *, ProgramPoint *, Value> {
252   using ParentTy = PointerUnion<GenericLatticeAnchor *, ProgramPoint *, Value>;
253   /// Inherit constructors.
254   using ParentTy::PointerUnion;
255   /// Allow implicit conversion from the parent type.
256   LatticeAnchor(ParentTy point = nullptr) : ParentTy(point) {}
257 
258   /// Print the lattice anchor.
259   void print(raw_ostream &os) const;
260 
261   /// Get the source location of the lattice anchor.
262   Location getLoc() const;
263 };
264 
265 /// Forward declaration of the data-flow analysis class.
266 class DataFlowAnalysis;
267 
268 //===----------------------------------------------------------------------===//
269 // DataFlowConfig
270 //===----------------------------------------------------------------------===//
271 
272 /// Configuration class for data flow solver and child analyses. Follows the
273 /// fluent API pattern.
274 class DataFlowConfig {
275 public:
276   DataFlowConfig() = default;
277 
278   /// Set whether the solver should operate interpocedurally, i.e. enter the
279   /// callee body when available. Interprocedural analyses may be more precise,
280   /// but also more expensive as more states need to be computed and the
281   /// fixpoint convergence takes longer.
282   DataFlowConfig &setInterprocedural(bool enable) {
283     interprocedural = enable;
284     return *this;
285   }
286 
287   /// Return `true` if the solver operates interprocedurally, `false` otherwise.
288   bool isInterprocedural() const { return interprocedural; }
289 
290 private:
291   bool interprocedural = true;
292 };
293 
294 //===----------------------------------------------------------------------===//
295 // DataFlowSolver
296 //===----------------------------------------------------------------------===//
297 
298 /// The general data-flow analysis solver. This class is responsible for
299 /// orchestrating child data-flow analyses, running the fixed-point iteration
300 /// algorithm, managing analysis state and lattice anchor memory, and tracking
301 /// dependencies between analyses, lattice anchor, and analysis states.
302 ///
303 /// Steps to run a data-flow analysis:
304 ///
305 /// 1. Load and initialize children analyses. Children analyses are instantiated
306 ///    in the solver and initialized, building their dependency relations.
307 /// 2. Configure and run the analysis. The solver invokes the children analyses
308 ///    according to their dependency relations until a fixed point is reached.
309 /// 3. Query analysis state results from the solver.
310 ///
311 /// Steps to re-run a data-flow analysis when IR changes:
312 /// 1. Erase all analysis states as they are no longer valid.
313 /// 2. Re-run the analysis using `initializeAndRun`.
314 ///
315 /// TODO: Optimize the internal implementation of the solver.
316 class DataFlowSolver {
317 public:
318   explicit DataFlowSolver(const DataFlowConfig &config = DataFlowConfig())
319       : config(config) {
320     uniquer.registerParametricStorageType<ProgramPoint>();
321   }
322 
323   /// Load an analysis into the solver. Return the analysis instance.
324   template <typename AnalysisT, typename... Args>
325   AnalysisT *load(Args &&...args);
326 
327   /// Initialize the children analyses starting from the provided top-level
328   /// operation and run the analysis until fixpoint.
329   LogicalResult initializeAndRun(Operation *top);
330 
331   /// Lookup an analysis state for the given lattice anchor. Returns null if one
332   /// does not exist.
333   template <typename StateT, typename AnchorT>
334   const StateT *lookupState(AnchorT anchor) const {
335     const auto &mapIt = analysisStates.find(LatticeAnchor(anchor));
336     if (mapIt == analysisStates.end())
337       return nullptr;
338     auto it = mapIt->second.find(TypeID::get<StateT>());
339     if (it == mapIt->second.end())
340       return nullptr;
341     return static_cast<const StateT *>(it->second.get());
342   }
343 
344   /// Erase any analysis state associated with the given lattice anchor.
345   template <typename AnchorT>
346   void eraseState(AnchorT anchor) {
347     LatticeAnchor la(anchor);
348     analysisStates.erase(LatticeAnchor(anchor));
349   }
350 
351   // Erase all analysis states
352   void eraseAllStates() { analysisStates.clear(); }
353 
354   /// Get a uniqued lattice anchor instance. If one is not present, it is
355   /// created with the provided arguments.
356   template <typename AnchorT, typename... Args>
357   AnchorT *getLatticeAnchor(Args &&...args) {
358     return AnchorT::get(uniquer, std::forward<Args>(args)...);
359   }
360 
361   /// Get a uniqued program point instance.
362   ProgramPoint *getProgramPointBefore(Operation *op) {
363     if (op->getBlock())
364       return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(),
365                                        Block::iterator(op), nullptr);
366     else
367       return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr,
368                                        Block::iterator(), op);
369   }
370 
371   ProgramPoint *getProgramPointBefore(Block *block) {
372     return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->begin(),
373                                      nullptr);
374   }
375 
376   ProgramPoint *getProgramPointAfter(Operation *op) {
377     if (op->getBlock())
378       return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(),
379                                        ++Block::iterator(op), nullptr);
380     else
381       return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr,
382                                        Block::iterator(), op);
383   }
384 
385   ProgramPoint *getProgramPointAfter(Block *block) {
386     return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->end(),
387                                      nullptr);
388   }
389 
390   /// A work item on the solver queue is a program point, child analysis pair.
391   /// Each item is processed by invoking the child analysis at the program
392   /// point.
393   using WorkItem = std::pair<ProgramPoint *, DataFlowAnalysis *>;
394   /// Push a work item onto the worklist.
395   void enqueue(WorkItem item) { worklist.push(std::move(item)); }
396 
397   /// Get the state associated with the given lattice anchor. If it does not
398   /// exist, create an uninitialized state.
399   template <typename StateT, typename AnchorT>
400   StateT *getOrCreateState(AnchorT anchor);
401 
402   /// Propagate an update to an analysis state if it changed by pushing
403   /// dependent work items to the back of the queue.
404   /// This should only be used when DataFlowSolver is running.
405   /// Otherwise, the solver won't process the work items.
406   void propagateIfChanged(AnalysisState *state, ChangeResult changed);
407 
408   /// Get the configuration of the solver.
409   const DataFlowConfig &getConfig() const { return config; }
410 
411 private:
412   /// Configuration of the dataflow solver.
413   DataFlowConfig config;
414 
415   /// The solver is working on the worklist.
416   bool isRunning = false;
417 
418   /// The solver's work queue. Work items can be inserted to the front of the
419   /// queue to be processed greedily, speeding up computations that otherwise
420   /// quickly degenerate to quadratic due to propagation of state updates.
421   std::queue<WorkItem> worklist;
422 
423   /// Type-erased instances of the children analyses.
424   SmallVector<std::unique_ptr<DataFlowAnalysis>> childAnalyses;
425 
426   /// The storage uniquer instance that owns the memory of the allocated lattice
427   /// anchors
428   StorageUniquer uniquer;
429 
430   /// A type-erased map of lattice anchors to associated analysis states for
431   /// first-class lattice anchors.
432   DenseMap<LatticeAnchor, DenseMap<TypeID, std::unique_ptr<AnalysisState>>,
433            DenseMapInfo<LatticeAnchor::ParentTy>>
434       analysisStates;
435 
436   /// Allow the base child analysis class to access the internals of the solver.
437   friend class DataFlowAnalysis;
438 };
439 
440 //===----------------------------------------------------------------------===//
441 // AnalysisState
442 //===----------------------------------------------------------------------===//
443 
444 /// Base class for generic analysis states. Analysis states contain data-flow
445 /// information that are attached to lattice anchors and which evolve as the
446 /// analysis iterates.
447 ///
448 /// This class places no restrictions on the semantics of analysis states beyond
449 /// these requirements.
450 ///
451 /// 1. Querying the state of a lattice anchor prior to visiting that anchor
452 ///    results in uninitialized state. Analyses must be aware of unintialized
453 ///    states.
454 /// 2. Analysis states can reach fixpoints, where subsequent updates will never
455 ///    trigger a change in the state.
456 /// 3. Analysis states that are uninitialized can be forcefully initialized to a
457 ///    default value.
458 class AnalysisState {
459 public:
460   virtual ~AnalysisState();
461 
462   /// Create the analysis state on the given lattice anchor.
463   AnalysisState(LatticeAnchor anchor) : anchor(anchor) {}
464 
465   /// Returns the lattice anchor this state is located at.
466   LatticeAnchor getAnchor() const { return anchor; }
467 
468   /// Print the contents of the analysis state.
469   virtual void print(raw_ostream &os) const = 0;
470   LLVM_DUMP_METHOD void dump() const;
471 
472   /// Add a dependency to this analysis state on a lattice anchor and an
473   /// analysis. If this state is updated, the analysis will be invoked on the
474   /// given lattice anchor again (in onUpdate()).
475   void addDependency(ProgramPoint *point, DataFlowAnalysis *analysis);
476 
477 protected:
478   /// This function is called by the solver when the analysis state is updated
479   /// to enqueue more work items. For example, if a state tracks dependents
480   /// through the IR (e.g. use-def chains), this function can be implemented to
481   /// push those dependents on the worklist.
482   virtual void onUpdate(DataFlowSolver *solver) const {
483     for (const DataFlowSolver::WorkItem &item : dependents)
484       solver->enqueue(item);
485   }
486 
487   /// The lattice anchor to which the state belongs.
488   LatticeAnchor anchor;
489 
490 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
491   /// When compiling with debugging, keep a name for the analysis state.
492   StringRef debugName;
493 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
494 
495 private:
496   /// The dependency relations originating from this analysis state. An entry
497   /// `state -> (analysis, anchor)` is created when `analysis` queries `state`
498   /// when updating `anchor`.
499   ///
500   /// When this state is updated, all dependent child analysis invocations are
501   /// pushed to the back of the queue. Use a `SetVector` to keep the analysis
502   /// deterministic.
503   ///
504   /// Store the dependents on the analysis state for efficiency.
505   SetVector<DataFlowSolver::WorkItem> dependents;
506 
507   /// Allow the framework to access the dependents.
508   friend class DataFlowSolver;
509 };
510 
511 //===----------------------------------------------------------------------===//
512 // DataFlowAnalysis
513 //===----------------------------------------------------------------------===//
514 
515 /// Base class for all data-flow analyses. A child analysis is expected to build
516 /// an initial dependency graph (and optionally provide an initial state) when
517 /// initialized and define transfer functions when visiting program points.
518 ///
519 /// In classical data-flow analysis, the dependency graph is fixed and analyses
520 /// define explicit transfer functions between input states and output states.
521 /// In this framework, however, the dependency graph can change during the
522 /// analysis, and transfer functions are opaque such that the solver doesn't
523 /// know what states calling `visit` on an analysis will be updated. This allows
524 /// multiple analyses to plug in and provide values for the same state.
525 ///
526 /// Generally, when an analysis queries an uninitialized state, it is expected
527 /// to "bail out", i.e., not provide any updates. When the value is initialized,
528 /// the solver will re-invoke the analysis. If the solver exhausts its worklist,
529 /// however, and there are still uninitialized states, the solver "nudges" the
530 /// analyses by default-initializing those states.
531 class DataFlowAnalysis {
532 public:
533   virtual ~DataFlowAnalysis();
534 
535   /// Create an analysis with a reference to the parent solver.
536   explicit DataFlowAnalysis(DataFlowSolver &solver);
537 
538   /// Initialize the analysis from the provided top-level operation by building
539   /// an initial dependency graph between all lattice anchors of interest. This
540   /// can be implemented by calling `visit` on all program points of interest
541   /// below the top-level operation.
542   ///
543   /// An analysis can optionally provide initial values to certain analysis
544   /// states to influence the evolution of the analysis.
545   virtual LogicalResult initialize(Operation *top) = 0;
546 
547   /// Visit the given program point. This function is invoked by the solver on
548   /// this analysis with a given program point when a dependent analysis state
549   /// is updated. The function is similar to a transfer function; it queries
550   /// certain analysis states and sets other states.
551   ///
552   /// The function is expected to create dependencies on queried states and
553   /// propagate updates on changed states. A dependency can be created by
554   /// calling `addDependency` between the input state and a program point,
555   /// indicating that, if the state is updated, the solver should invoke `solve`
556   /// on the program point. The dependent point does not have to be the same as
557   /// the provided point. An update to a state is propagated by calling
558   /// `propagateIfChange` on the state. If the state has changed, then all its
559   /// dependents are placed on the worklist.
560   ///
561   /// The dependency graph does not need to be static. Each invocation of
562   /// `visit` can add new dependencies, but these dependencies will not be
563   /// dynamically added to the worklist because the solver doesn't know what
564   /// will provide a value for then.
565   virtual LogicalResult visit(ProgramPoint *point) = 0;
566 
567 protected:
568   /// Create a dependency between the given analysis state and lattice anchor
569   /// on this analysis.
570   void addDependency(AnalysisState *state, ProgramPoint *point);
571 
572   /// Propagate an update to a state if it changed.
573   void propagateIfChanged(AnalysisState *state, ChangeResult changed);
574 
575   /// Register a custom lattice anchor class.
576   template <typename AnchorT>
577   void registerAnchorKind() {
578     solver.uniquer.registerParametricStorageType<AnchorT>();
579   }
580 
581   /// Get or create a custom lattice anchor.
582   template <typename AnchorT, typename... Args>
583   AnchorT *getLatticeAnchor(Args &&...args) {
584     return solver.getLatticeAnchor<AnchorT>(std::forward<Args>(args)...);
585   }
586 
587   /// Get the analysis state associated with the lattice anchor. The returned
588   /// state is expected to be "write-only", and any updates need to be
589   /// propagated by `propagateIfChanged`.
590   template <typename StateT, typename AnchorT>
591   StateT *getOrCreate(AnchorT anchor) {
592     return solver.getOrCreateState<StateT>(anchor);
593   }
594 
595   /// Get a read-only analysis state for the given point and create a dependency
596   /// on `dependent`. If the return state is updated elsewhere, this analysis is
597   /// re-invoked on the dependent.
598   template <typename StateT, typename AnchorT>
599   const StateT *getOrCreateFor(ProgramPoint *dependent, AnchorT anchor) {
600     StateT *state = getOrCreate<StateT>(anchor);
601     addDependency(state, dependent);
602     return state;
603   }
604 
605   /// Get a uniqued program point instance.
606   ProgramPoint *getProgramPointBefore(Operation *op) {
607     return solver.getProgramPointBefore(op);
608   }
609 
610   ProgramPoint *getProgramPointBefore(Block *block) {
611     return solver.getProgramPointBefore(block);
612   }
613 
614   ProgramPoint *getProgramPointAfter(Operation *op) {
615     return solver.getProgramPointAfter(op);
616   }
617 
618   ProgramPoint *getProgramPointAfter(Block *block) {
619     return solver.getProgramPointAfter(block);
620   }
621 
622   /// Return the configuration of the solver used for this analysis.
623   const DataFlowConfig &getSolverConfig() const { return solver.getConfig(); }
624 
625 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
626   /// When compiling with debugging, keep a name for the analyis.
627   StringRef debugName;
628 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
629 
630 private:
631   /// The parent data-flow solver.
632   DataFlowSolver &solver;
633 
634   /// Allow the data-flow solver to access the internals of this class.
635   friend class DataFlowSolver;
636 };
637 
638 template <typename AnalysisT, typename... Args>
639 AnalysisT *DataFlowSolver::load(Args &&...args) {
640   childAnalyses.emplace_back(new AnalysisT(*this, std::forward<Args>(args)...));
641 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
642   childAnalyses.back().get()->debugName = llvm::getTypeName<AnalysisT>();
643 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
644   return static_cast<AnalysisT *>(childAnalyses.back().get());
645 }
646 
647 template <typename StateT, typename AnchorT>
648 StateT *DataFlowSolver::getOrCreateState(AnchorT anchor) {
649   std::unique_ptr<AnalysisState> &state =
650       analysisStates[LatticeAnchor(anchor)][TypeID::get<StateT>()];
651   if (!state) {
652     state = std::unique_ptr<StateT>(new StateT(anchor));
653 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
654     state->debugName = llvm::getTypeName<StateT>();
655 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
656   }
657   return static_cast<StateT *>(state.get());
658 }
659 
660 inline raw_ostream &operator<<(raw_ostream &os, const AnalysisState &state) {
661   state.print(os);
662   return os;
663 }
664 
665 inline raw_ostream &operator<<(raw_ostream &os, LatticeAnchor anchor) {
666   anchor.print(os);
667   return os;
668 }
669 
670 } // end namespace mlir
671 
672 namespace llvm {
673 /// Allow hashing of lattice anchors and program points.
674 template <>
675 struct DenseMapInfo<mlir::ProgramPoint> {
676   static mlir::ProgramPoint getEmptyKey() {
677     void *pointer = llvm::DenseMapInfo<void *>::getEmptyKey();
678     return mlir::ProgramPoint(
679         (mlir::Block *)pointer,
680         mlir::Block::iterator((mlir::Operation *)pointer));
681   }
682   static mlir::ProgramPoint getTombstoneKey() {
683     void *pointer = llvm::DenseMapInfo<void *>::getTombstoneKey();
684     return mlir::ProgramPoint(
685         (mlir::Block *)pointer,
686         mlir::Block::iterator((mlir::Operation *)pointer));
687   }
688   static unsigned getHashValue(mlir::ProgramPoint pp) {
689     return hash_combine(pp.getBlock(), pp.getPoint().getNodePtr());
690   }
691   static bool isEqual(mlir::ProgramPoint lhs, mlir::ProgramPoint rhs) {
692     return lhs == rhs;
693   }
694 };
695 
696 // Allow llvm::cast style functions.
697 template <typename To>
698 struct CastInfo<To, mlir::LatticeAnchor>
699     : public CastInfo<To, mlir::LatticeAnchor::PointerUnion> {};
700 
701 template <typename To>
702 struct CastInfo<To, const mlir::LatticeAnchor>
703     : public CastInfo<To, const mlir::LatticeAnchor::PointerUnion> {};
704 
705 } // end namespace llvm
706 
707 #endif // MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H
708