MLIR 23.0.0git
DataFlowFramework.h
Go to the documentation of this file.
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"
21#include "llvm/ADT/EquivalenceClasses.h"
22#include "llvm/ADT/Hashing.h"
23#include "llvm/ADT/STLFunctionalExtras.h"
24#include "llvm/ADT/SetVector.h"
25#include "llvm/Support/Compiler.h"
26#include "llvm/Support/TypeName.h"
27#include <queue>
28#include <tuple>
29
30namespace mlir {
31
32//===----------------------------------------------------------------------===//
33// ChangeResult
34//===----------------------------------------------------------------------===//
35
36/// A result type used to indicate if a change happened. Boolean operations on
37/// ChangeResult behave as though `Change` is truth.
38enum class [[nodiscard]] ChangeResult {
41};
46 lhs = lhs | rhs;
47 return lhs;
48}
52
53/// Forward declare the analysis state class.
54class AnalysisState;
55
56/// Program point represents a specific location in the execution of a program.
57/// A sequence of program points can be combined into a control flow graph.
59 /// Creates a new program point at the given location.
61 : block(parentBlock), point(pp) {}
62
63 /// Creates a new program point at the given operation.
64 ProgramPoint(Operation *op) : op(op) {}
65
66 /// The concrete key type used by the storage uniquer. This class is uniqued
67 /// by its contents.
68 using KeyTy = std::tuple<Block *, Block::iterator, Operation *>;
69
70 /// Create a empty program point.
72
73 /// Create a new program point from the given program point.
75 : block(point.getBlock()), point(point.getPoint()),
76 op(point.getOperation()) {}
77
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.
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.
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
140private:
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
149inline raw_ostream &operator<<(raw_ostream &os, const 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.
167public:
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
179protected:
180 /// Create an abstract lattice anchor with type identifier.
181 explicit GenericLatticeAnchor(TypeID typeID) : typeID(typeID) {}
182
183private:
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.
198template <typename ConcreteT, typename Value>
200public:
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.
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>
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
240private:
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.
251 : public 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.
266class DataFlowAnalysis;
267
268} // namespace mlir
269
270template <>
271struct llvm::DenseMapInfo<mlir::LatticeAnchor>
272 : public llvm::DenseMapInfo<mlir::LatticeAnchor::ParentTy> {};
273
274namespace mlir {
275
276//===----------------------------------------------------------------------===//
277// DataFlowConfig
278//===----------------------------------------------------------------------===//
279
280/// Configuration class for data flow solver and child analyses. Follows the
281/// fluent API pattern.
283public:
284 DataFlowConfig() = default;
285
286 /// Set whether the solver should operate interpocedurally, i.e. enter the
287 /// callee body when available. Interprocedural analyses may be more precise,
288 /// but also more expensive as more states need to be computed and the
289 /// fixpoint convergence takes longer.
291 interprocedural = enable;
292 return *this;
293 }
294
295 /// Return `true` if the solver operates interprocedurally, `false` otherwise.
296 bool isInterprocedural() const { return interprocedural; }
297
298private:
299 bool interprocedural = true;
300};
301
302//===----------------------------------------------------------------------===//
303// DataFlowSolver
304//===----------------------------------------------------------------------===//
305
306/// The general data-flow analysis solver. This class is responsible for
307/// orchestrating child data-flow analyses, running the fixed-point iteration
308/// algorithm, managing analysis state and lattice anchor memory, and tracking
309/// dependencies between analyses, lattice anchor, and analysis states.
310///
311/// Steps to run a data-flow analysis:
312///
313/// 1. Load and initialize children analyses. Children analyses are instantiated
314/// in the solver and initialized, building their dependency relations.
315/// 2. Configure and run the analysis. The solver invokes the children analyses
316/// according to their dependency relations until a fixed point is reached.
317/// 3. Query analysis state results from the solver.
318///
319/// Steps to re-run a data-flow analysis when IR changes:
320/// 1. Erase all analysis states as they are no longer valid.
321/// 2. Re-run the analysis using `initializeAndRun`.
322///
323/// TODO: Optimize the internal implementation of the solver.
325public:
326 explicit DataFlowSolver(const DataFlowConfig &config = DataFlowConfig())
327 : config(config) {
328 uniquer.registerParametricStorageType<ProgramPoint>();
329 }
330
331 /// Load an analysis into the solver. Return the analysis instance.
332 template <typename AnalysisT, typename... Args>
333 AnalysisT *load(Args &&...args);
334
335 /// Initialize analyses starting from the provided top-level operation and
336 /// run the analysis until fixpoint.
337 ///
338 /// An optional \p analysisFilter predicate restricts which analyses are
339 /// initialized. When no filter is given every loaded analysis is
340 /// (re-)initialized. The fixpoint loop always processes all enqueued work
341 /// items regardless of the filter.
342 LogicalResult initializeAndRun(
343 Operation *top,
344 llvm::function_ref<bool(DataFlowAnalysis &)> analysisFilter = nullptr);
345
346 /// Lookup an analysis state for the given lattice anchor. Returns null if one
347 /// does not exist.
348 template <typename StateT, typename AnchorT>
349 const StateT *lookupState(AnchorT anchor) const {
350 LatticeAnchor latticeAnchor =
352 const auto &mapIt = analysisStates.find(latticeAnchor);
353 if (mapIt == analysisStates.end())
354 return nullptr;
355 auto it = mapIt->second.find(TypeID::get<StateT>());
356 if (it == mapIt->second.end())
357 return nullptr;
358 return static_cast<const StateT *>(it->second.get());
359 }
360
361 /// Erase any analysis state associated with the given lattice anchor.
362 template <typename AnchorT>
363 void eraseState(AnchorT anchor);
364
365 /// Erase all analysis states.
367 analysisStates.clear();
368 equivalentAnchorMap.clear();
369 }
370
371 /// Get a uniqued lattice anchor instance. If one is not present, it is
372 /// created with the provided arguments.
373 template <typename AnchorT, typename... Args>
374 AnchorT *getLatticeAnchor(Args &&...args) {
375 return AnchorT::get(uniquer, std::forward<Args>(args)...);
376 }
377
378 /// Get a uniqued program point instance.
380 if (op->getBlock())
381 return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(),
382 Block::iterator(op), nullptr);
383 else
384 return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr,
385 Block::iterator(), op);
386 }
387
389 return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->begin(),
390 nullptr);
391 }
392
394 if (op->getBlock())
395 return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(),
396 ++Block::iterator(op), nullptr);
397 else
398 return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr,
399 Block::iterator(), op);
400 }
401
403 return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->end(),
404 nullptr);
405 }
406
407 /// A work item on the solver queue is a program point, child analysis pair.
408 /// Each item is processed by invoking the child analysis at the program
409 /// point.
410 using WorkItem = std::pair<ProgramPoint *, DataFlowAnalysis *>;
411 /// Push a work item onto the worklist.
412 void enqueue(WorkItem item) { worklist.push(std::move(item)); }
413
414 /// Get the state associated with the given lattice anchor. If it does not
415 /// exist, create an uninitialized state.
416 template <typename StateT, typename AnchorT>
417 StateT *getOrCreateState(AnchorT anchor);
418
419 /// Get leader lattice anchor in equivalence lattice anchor group, return
420 /// input lattice anchor if input not found in equivalece lattice anchor
421 /// group.
422 template <typename StateT>
424
425 /// Union input anchors under the given state.
426 template <typename StateT, typename AnchorT>
427 void unionLatticeAnchors(AnchorT anchor, AnchorT other);
428
429 /// Return given lattice is equivalent on given state.
430 template <typename StateT>
432
433 /// Propagate an update to an analysis state if it changed by pushing
434 /// dependent work items to the back of the queue.
435 /// This should only be used when DataFlowSolver is running.
436 /// Otherwise, the solver won't process the work items.
437 void propagateIfChanged(AnalysisState *state, ChangeResult changed);
438
439 /// Get the configuration of the solver.
440 const DataFlowConfig &getConfig() const { return config; }
441
442private:
443 /// Configuration of the dataflow solver.
444 DataFlowConfig config;
445
446 /// The solver is working on the worklist.
447 bool isRunning = false;
448
449 /// The solver's work queue. Work items can be inserted to the front of the
450 /// queue to be processed greedily, speeding up computations that otherwise
451 /// quickly degenerate to quadratic due to propagation of state updates.
452 std::queue<WorkItem> worklist;
453
454 /// Type-erased instances of the children analyses.
456
457 /// The storage uniquer instance that owns the memory of the allocated lattice
458 /// anchors
459 StorageUniquer uniquer;
460
461 /// A type-erased map of lattice anchors to associated analysis states for
462 /// first-class lattice anchors.
464 analysisStates;
465
466 /// A map of Ananlysis state type to the equivalent lattice anchors.
467 /// Lattice anchors are considered equivalent under a certain analysis state
468 /// type if and only if, the analysis states pointed to by these lattice
469 /// anchors necessarily contain identical value.
471
472 /// Allow the base child analysis class to access the internals of the solver.
473 friend class DataFlowAnalysis;
474};
475
476//===----------------------------------------------------------------------===//
477// AnalysisState
478//===----------------------------------------------------------------------===//
479
480/// Base class for generic analysis states. Analysis states contain data-flow
481/// information that are attached to lattice anchors and which evolve as the
482/// analysis iterates.
483///
484/// This class places no restrictions on the semantics of analysis states beyond
485/// these requirements.
486///
487/// 1. Querying the state of a lattice anchor prior to visiting that anchor
488/// results in uninitialized state. Analyses must be aware of uninitialized
489/// states.
490/// 2. Analysis states can reach fixpoints, where subsequent updates will never
491/// trigger a change in the state.
492/// 3. Analysis states that are uninitialized can be forcefully initialized to a
493/// default value.
495public:
496 virtual ~AnalysisState();
497
498 /// Create the analysis state on the given lattice anchor.
500
501 /// Returns the lattice anchor this state is located at.
502 LatticeAnchor getAnchor() const { return anchor; }
503
504 /// Print the contents of the analysis state.
505 virtual void print(raw_ostream &os) const = 0;
506 LLVM_DUMP_METHOD void dump() const;
507
508 /// Add a dependency to this analysis state on a lattice anchor and an
509 /// analysis. If this state is updated, the analysis will be invoked on the
510 /// given lattice anchor again (in onUpdate()).
511 void addDependency(ProgramPoint *point, DataFlowAnalysis *analysis);
512
513protected:
514 /// This function is called by the solver when the analysis state is updated
515 /// to enqueue more work items. For example, if a state tracks dependents
516 /// through the IR (e.g. use-def chains), this function can be implemented to
517 /// push those dependents on the worklist.
518 virtual void onUpdate(DataFlowSolver *solver) const {
519 for (const DataFlowSolver::WorkItem &item : dependents)
520 solver->enqueue(item);
521 }
522
523 /// The lattice anchor to which the state belongs.
525
526#if LLVM_ENABLE_ABI_BREAKING_CHECKS
527 /// When compiling with debugging, keep a name for the analysis state.
528 StringRef debugName;
529#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
530
531private:
532 /// The dependency relations originating from this analysis state. An entry
533 /// `state -> (analysis, anchor)` is created when `analysis` queries `state`
534 /// when updating `anchor`.
535 ///
536 /// When this state is updated, all dependent child analysis invocations are
537 /// pushed to the back of the queue. Use a `SetVector` to keep the analysis
538 /// deterministic.
539 ///
540 /// Store the dependents on the analysis state for efficiency.
542
543 /// Allow the framework to access the dependents.
544 friend class DataFlowSolver;
545};
546
547//===----------------------------------------------------------------------===//
548// DataFlowSolver definition
549//===----------------------------------------------------------------------===//
550// This method is defined outside `DataFlowSolver` and after `AnalysisState`
551// to prevent issues around `AnalysisState` being used before it is defined.
552template <typename AnchorT>
553void DataFlowSolver::eraseState(AnchorT anchor) {
554 LatticeAnchor latticeAnchor(anchor);
555
556 // Update equivalentAnchorMap.
557 for (auto &&[TypeId, eqClass] : equivalentAnchorMap) {
558 if (!eqClass.contains(latticeAnchor)) {
559 continue;
560 }
561 llvm::EquivalenceClasses<LatticeAnchor>::member_iterator leaderIt =
562 eqClass.findLeader(latticeAnchor);
563
564 // Update analysis states with new leader if needed.
565 if (*leaderIt == latticeAnchor && ++leaderIt != eqClass.member_end()) {
566 analysisStates[*leaderIt][TypeId] =
567 std::move(analysisStates[latticeAnchor][TypeId]);
568 }
569
570 eqClass.erase(latticeAnchor);
571 }
572
573 // Update analysis states.
574 analysisStates.erase(latticeAnchor);
575}
576
577//===----------------------------------------------------------------------===//
578// DataFlowAnalysis
579//===----------------------------------------------------------------------===//
580
581/// Base class for all data-flow analyses. A child analysis is expected to build
582/// an initial dependency graph (and optionally provide an initial state) when
583/// initialized and define transfer functions when visiting program points.
584///
585/// Subclasses defined in anonymous namespaces must provide an explicit TypeID
586/// via `MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID` in their class body.
587/// This is required because `DataFlowSolver::load` resolves the analysis
588/// TypeID at load time, and the implicit TypeID fallback is not supported for
589/// classes in anonymous namespaces.
590///
591/// In classical data-flow analysis, the dependency graph is fixed and analyses
592/// define explicit transfer functions between input states and output states.
593/// In this framework, however, the dependency graph can change during the
594/// analysis, and transfer functions are opaque such that the solver doesn't
595/// know what states calling `visit` on an analysis will be updated. This allows
596/// multiple analyses to plug in and provide values for the same state.
597///
598/// Generally, when an analysis queries an uninitialized state, it is expected
599/// to "bail out", i.e., not provide any updates. When the value is initialized,
600/// the solver will re-invoke the analysis. If the solver exhausts its worklist,
601/// however, and there are still uninitialized states, the solver "nudges" the
602/// analyses by default-initializing those states.
604public:
606
607 /// Create an analysis with a reference to the parent solver.
608 explicit DataFlowAnalysis(DataFlowSolver &solver);
609
610 /// Initialize the analysis from the provided top-level operation by building
611 /// an initial dependency graph between all lattice anchors of interest. This
612 /// can be implemented by calling `visit` on all program points of interest
613 /// below the top-level operation.
614 ///
615 /// An analysis can optionally provide initial values to certain analysis
616 /// states to influence the evolution of the analysis.
617 virtual LogicalResult initialize(Operation *top) = 0;
618
619 /// Visit the given program point. This function is invoked by the solver on
620 /// this analysis with a given program point when a dependent analysis state
621 /// is updated. The function is similar to a transfer function; it queries
622 /// certain analysis states and sets other states.
623 ///
624 /// The function is expected to create dependencies on queried states and
625 /// propagate updates on changed states. A dependency can be created by
626 /// calling `addDependency` between the input state and a program point,
627 /// indicating that, if the state is updated, the solver should invoke `solve`
628 /// on the program point. The dependent point does not have to be the same as
629 /// the provided point. An update to a state is propagated by calling
630 /// `propagateIfChange` on the state. If the state has changed, then all its
631 /// dependents are placed on the worklist.
632 ///
633 /// The dependency graph does not need to be static. Each invocation of
634 /// `visit` can add new dependencies, but these dependencies will not be
635 /// dynamically added to the worklist because the solver doesn't know what
636 /// will provide a value for then.
637 virtual LogicalResult visit(ProgramPoint *point) = 0;
638
639 /// Initialize lattice anchor equivalence class from the provided top-level
640 /// operation.
641 ///
642 /// This function will union lattice anchor to same equivalent class if the
643 /// analysis can determine the lattice content of lattice anchor is
644 /// necessarily identical under the corrensponding lattice type.
646
647 /// Return the TypeID of the concrete analysis class. Valid only after
648 /// `DataFlowSolver::load<AnalysisT>` has returned; must not be called from
649 /// the analysis constructor body because the TypeID is set by `load` after
650 /// construction.
651 TypeID getTypeID() const { return analysisTypeID; }
652
653protected:
654 /// Create a dependency between the given analysis state and lattice anchor
655 /// on this analysis.
656 void addDependency(AnalysisState *state, ProgramPoint *point);
657
658 /// Propagate an update to a state if it changed.
659 void propagateIfChanged(AnalysisState *state, ChangeResult changed);
660
661 /// Register a custom lattice anchor class.
662 template <typename AnchorT>
664 solver.uniquer.registerParametricStorageType<AnchorT>();
665 }
666
667 /// Get or create a custom lattice anchor.
668 template <typename AnchorT, typename... Args>
669 AnchorT *getLatticeAnchor(Args &&...args) {
670 return solver.getLatticeAnchor<AnchorT>(std::forward<Args>(args)...);
671 }
672
673 /// Union input anchors under the given state.
674 template <typename StateT, typename AnchorT>
675 void unionLatticeAnchors(AnchorT anchor, AnchorT other) {
676 return solver.unionLatticeAnchors<StateT>(anchor, other);
677 }
678
679 /// Get the analysis state associated with the lattice anchor. The returned
680 /// state is expected to be "write-only", and any updates need to be
681 /// propagated by `propagateIfChanged`.
682 template <typename StateT, typename AnchorT>
683 StateT *getOrCreate(AnchorT anchor) {
684 return solver.getOrCreateState<StateT>(anchor);
685 }
686
687 /// Get a read-only analysis state for the given point and create a dependency
688 /// on `dependent`. If the return state is updated elsewhere, this analysis is
689 /// re-invoked on the dependent.
690 template <typename StateT, typename AnchorT>
691 const StateT *getOrCreateFor(ProgramPoint *dependent, AnchorT anchor) {
692 StateT *state = getOrCreate<StateT>(anchor);
693 if (!solver.isEquivalent<StateT>(LatticeAnchor(anchor),
694 LatticeAnchor(dependent)))
695 addDependency(state, dependent);
696 return state;
697 }
698
699 /// Get a uniqued program point instance.
701 return solver.getProgramPointBefore(op);
702 }
703
705 return solver.getProgramPointBefore(block);
706 }
707
709 return solver.getProgramPointAfter(op);
710 }
711
713 return solver.getProgramPointAfter(block);
714 }
715
716 /// Return the configuration of the solver used for this analysis.
717 const DataFlowConfig &getSolverConfig() const { return solver.getConfig(); }
718
719#if LLVM_ENABLE_ABI_BREAKING_CHECKS
720 /// When compiling with debugging, keep a name for the analyis.
721 StringRef debugName;
722#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
723
724private:
725 /// The parent data-flow solver.
726 DataFlowSolver &solver;
727
728 /// The TypeID of the concrete analysis class. Set by
729 /// `DataFlowSolver::load` after construction; not available during the
730 /// analysis constructor.
731 TypeID analysisTypeID;
732
733 /// Allow the data-flow solver to access the internals of this class.
734 friend class DataFlowSolver;
735};
736
737template <typename AnalysisT, typename... Args>
738AnalysisT *DataFlowSolver::load(Args &&...args) {
739 childAnalyses.emplace_back(new AnalysisT(*this, std::forward<Args>(args)...));
740 childAnalyses.back()->analysisTypeID = TypeID::get<AnalysisT>();
741#if LLVM_ENABLE_ABI_BREAKING_CHECKS
742 childAnalyses.back()->debugName = llvm::getTypeName<AnalysisT>();
743#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
744 return static_cast<AnalysisT *>(childAnalyses.back().get());
745}
746
747template <typename StateT>
750 if (!equivalentAnchorMap.contains(TypeID::get<StateT>())) {
751 return latticeAnchor;
752 }
753 const llvm::EquivalenceClasses<LatticeAnchor> &eqClass =
754 equivalentAnchorMap.at(TypeID::get<StateT>());
755 llvm::EquivalenceClasses<LatticeAnchor>::member_iterator leaderIt =
756 eqClass.findLeader(latticeAnchor);
757 if (leaderIt != eqClass.member_end()) {
758 return *leaderIt;
759 }
760 return latticeAnchor;
761}
762
763template <typename StateT, typename AnchorT>
764StateT *DataFlowSolver::getOrCreateState(AnchorT anchor) {
765 // Replace to leader anchor if found.
766 LatticeAnchor latticeAnchor(anchor);
767 latticeAnchor = getLeaderAnchorOrSelf<StateT>(latticeAnchor);
768 std::unique_ptr<AnalysisState> &state =
769 analysisStates[latticeAnchor][TypeID::get<StateT>()];
770 if (!state) {
771 state = std::unique_ptr<StateT>(new StateT(anchor));
772#if LLVM_ENABLE_ABI_BREAKING_CHECKS
773 state->debugName = llvm::getTypeName<StateT>();
774#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
775 }
776 return static_cast<StateT *>(state.get());
777}
778
779template <typename StateT>
781 if (!equivalentAnchorMap.contains(TypeID::get<StateT>())) {
782 return false;
783 }
784 const llvm::EquivalenceClasses<LatticeAnchor> &eqClass =
785 equivalentAnchorMap.at(TypeID::get<StateT>());
786 if (!eqClass.contains(lhs) || !eqClass.contains(rhs))
787 return false;
788 return eqClass.isEquivalent(lhs, rhs);
789}
790
791template <typename StateT, typename AnchorT>
792void DataFlowSolver::unionLatticeAnchors(AnchorT anchor, AnchorT other) {
793 llvm::EquivalenceClasses<LatticeAnchor> &eqClass =
794 equivalentAnchorMap[TypeID::get<StateT>()];
795 eqClass.unionSets(LatticeAnchor(anchor), LatticeAnchor(other));
796}
797
799 state.print(os);
800 return os;
801}
802
803inline raw_ostream &operator<<(raw_ostream &os, const LatticeAnchor &anchor) {
804 anchor.print(os);
805 return os;
806}
807
808} // end namespace mlir
809
810namespace llvm {
811/// Allow hashing of lattice anchors and program points.
812template <>
813struct DenseMapInfo<mlir::ProgramPoint> {
816 return mlir::ProgramPoint(
817 (mlir::Block *)pointer,
819 }
826 static unsigned getHashValue(mlir::ProgramPoint pp) {
827 return hash_combine(pp.getBlock(), pp.getPoint().getNodePtr());
828 }
830 return lhs == rhs;
831 }
832};
833
834// Allow llvm::cast style functions.
835template <typename To>
836struct CastInfo<To, mlir::LatticeAnchor>
837 : public CastInfo<To, mlir::LatticeAnchor::PointerUnion> {};
838
839template <typename To>
840struct CastInfo<To, const mlir::LatticeAnchor>
841 : public CastInfo<To, const mlir::LatticeAnchor::PointerUnion> {};
842
843} // end namespace llvm
844
845#endif // MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H
lhs
auto load
Base class for generic analysis states.
LLVM_DUMP_METHOD void dump() const
LatticeAnchor getAnchor() const
Returns the lattice anchor this state is located at.
void addDependency(ProgramPoint *point, DataFlowAnalysis *analysis)
Add a dependency to this analysis state on a lattice anchor and an analysis.
virtual void print(raw_ostream &os) const =0
Print the contents of the analysis state.
virtual void onUpdate(DataFlowSolver *solver) const
This function is called by the solver when the analysis state is updated to enqueue more work items.
AnalysisState(LatticeAnchor anchor)
Create the analysis state on the given lattice anchor.
virtual ~AnalysisState()
LatticeAnchor anchor
The lattice anchor to which the state belongs.
friend class DataFlowSolver
Allow the framework to access the dependents.
Block represents an ordered list of Operations.
Definition Block.h:33
OpListType::iterator iterator
Definition Block.h:150
iterator end()
Definition Block.h:154
iterator begin()
Definition Block.h:153
Base class for all data-flow analyses.
void addDependency(AnalysisState *state, ProgramPoint *point)
Create a dependency between the given analysis state and lattice anchor on this analysis.
void unionLatticeAnchors(AnchorT anchor, AnchorT other)
Union input anchors under the given state.
TypeID getTypeID() const
Return the TypeID of the concrete analysis class.
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
void propagateIfChanged(AnalysisState *state, ChangeResult changed)
Propagate an update to a state if it changed.
ProgramPoint * getProgramPointBefore(Block *block)
ProgramPoint * getProgramPointAfter(Block *block)
const DataFlowConfig & getSolverConfig() const
Return the configuration of the solver used for this analysis.
StateT * getOrCreate(AnchorT anchor)
Get the analysis state associated with the lattice anchor.
ProgramPoint * getProgramPointAfter(Operation *op)
virtual void initializeEquivalentLatticeAnchor(Operation *top)
Initialize lattice anchor equivalence class from the provided top-level operation.
DataFlowAnalysis(DataFlowSolver &solver)
Create an analysis with a reference to the parent solver.
virtual LogicalResult initialize(Operation *top)=0
Initialize the analysis from the provided top-level operation by building an initial dependency graph...
virtual LogicalResult visit(ProgramPoint *point)=0
Visit the given program point.
AnchorT * getLatticeAnchor(Args &&...args)
Get or create a custom lattice anchor.
void registerAnchorKind()
Register a custom lattice anchor class.
friend class DataFlowSolver
Allow the data-flow solver to access the internals of this class.
const StateT * getOrCreateFor(ProgramPoint *dependent, AnchorT anchor)
Get a read-only analysis state for the given point and create a dependency on dependent.
Configuration class for data flow solver and child analyses.
DataFlowConfig()=default
DataFlowConfig & setInterprocedural(bool enable)
Set whether the solver should operate interpocedurally, i.e.
bool isInterprocedural() const
Return true if the solver operates interprocedurally, false otherwise.
The general data-flow analysis solver.
LogicalResult initializeAndRun(Operation *top, llvm::function_ref< bool(DataFlowAnalysis &)> analysisFilter=nullptr)
Initialize analyses starting from the provided top-level operation and run the analysis until fixpoin...
void unionLatticeAnchors(AnchorT anchor, AnchorT other)
Union input anchors under the given state.
void enqueue(WorkItem item)
Push a work item onto the worklist.
bool isEquivalent(LatticeAnchor lhs, LatticeAnchor rhs) const
Return given lattice is equivalent on given state.
friend class DataFlowAnalysis
Allow the base child analysis class to access the internals of the solver.
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
ProgramPoint * getProgramPointAfter(Block *block)
void eraseState(AnchorT anchor)
Erase any analysis state associated with the given lattice anchor.
ProgramPoint * getProgramPointAfter(Operation *op)
void propagateIfChanged(AnalysisState *state, ChangeResult changed)
Propagate an update to an analysis state if it changed by pushing dependent work items to the back of...
AnchorT * getLatticeAnchor(Args &&...args)
Get a uniqued lattice anchor instance.
const StateT * lookupState(AnchorT anchor) const
Lookup an analysis state for the given lattice anchor.
ProgramPoint * getProgramPointBefore(Block *block)
void eraseAllStates()
Erase all analysis states.
StateT * getOrCreateState(AnchorT anchor)
Get the state associated with the given lattice anchor.
const DataFlowConfig & getConfig() const
Get the configuration of the solver.
LatticeAnchor getLeaderAnchorOrSelf(LatticeAnchor latticeAnchor) const
Get leader lattice anchor in equivalence lattice anchor group, return input lattice anchor if input n...
AnalysisT * load(Args &&...args)
Load an analysis into the solver. Return the analysis instance.
DataFlowSolver(const DataFlowConfig &config=DataFlowConfig())
std::pair< ProgramPoint *, DataFlowAnalysis * > WorkItem
A work item on the solver queue is a program point, child analysis pair.
bool operator==(const Value &value) const
Two lattice anchors are equal if their values are equal.
GenericLatticeAnchorBase< ConcreteT, Value > Base
Alias for the base class.
const Value & getValue() const
Get the contents of the lattice anchor.
static bool classof(const GenericLatticeAnchor *point)
Provide LLVM-style RTTI using type IDs.
static ConcreteT * construct(StorageUniquer::StorageAllocator &alloc, ValueT &&value)
Allocate space for a lattice anchor and construct it in-place.
static ConcreteT * get(StorageUniquer &uniquer, Args &&...args)
Get a uniqued instance of this lattice anchor class with the given arguments.
GenericLatticeAnchorBase(ValueT &&value)
Construct an instance of the lattice anchor using the provided value and the type ID of the concrete ...
Value KeyTy
The concrete key type used by the storage uniquer.
virtual void print(raw_ostream &os) const =0
Print the lattice anchor.
TypeID getTypeID() const
Get the abstract lattice anchor's type identifier.
virtual Location getLoc() const =0
Get a derived source location for the lattice anchor.
GenericLatticeAnchor(TypeID typeID)
Create an abstract lattice anchor with type identifier.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:231
This class acts as the base storage that all storage classes must derived from.
This is a utility allocator used to allocate memory for instances of derived types.
T * allocate()
Allocate an instance of the provided type.
A utility class to get or create instances of "storage classes".
Storage * get(function_ref< void(Storage *)> initFn, TypeID id, Args &&...args)
Gets a uniqued instance of 'Storage'.
This class provides an efficient unique identifier for a specific C++ type.
Definition TypeID.h:107
static TypeID get()
Construct a type info object for the given type T.
Definition TypeID.h:245
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
The OpAsmOpInterface, see OpAsmInterface.td for more details.
Definition CallGraph.h:229
Include the generated interface declarations.
raw_ostream & operator<<(raw_ostream &os, const AliasResult &result)
ChangeResult
A result type used to indicate if a change happened.
ChangeResult operator&(ChangeResult lhs, ChangeResult rhs)
ChangeResult operator|(ChangeResult lhs, ChangeResult rhs)
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
ChangeResult & operator|=(ChangeResult &lhs, ChangeResult rhs)
static unsigned getHashValue(mlir::ProgramPoint pp)
static mlir::ProgramPoint getTombstoneKey()
static bool isEqual(mlir::ProgramPoint lhs, mlir::ProgramPoint rhs)
Fundamental IR components are supported as first-class lattice anchor.
LatticeAnchor(ParentTy point=nullptr)
Allow implicit conversion from the parent type.
Location getLoc() const
Get the source location of the lattice anchor.
void print(raw_ostream &os) const
Print the lattice anchor.
PointerUnion< GenericLatticeAnchor *, ProgramPoint *, Value > ParentTy
Program point represents a specific location in the execution of a program.
bool isNull() const
Returns true if this program point is set.
ProgramPoint(Block *parentBlock, Block::iterator pp)
Creates a new program point at the given location.
Block::iterator getPoint() const
Get the the iterator this program point refers to.
ProgramPoint()
Create a empty program point.
Block * getBlock() const
Get the block contains this program point.
Operation * getNextOp() const
Get the next operation of this program point.
bool operator==(const ProgramPoint &pp) const
bool operator==(const KeyTy &key) const
Two program points are equal if their block and iterator are equal.
ProgramPoint(const ProgramPoint &point)
Create a new program point from the given program point.
std::tuple< Block *, Block::iterator, Operation * > KeyTy
The concrete key type used by the storage uniquer.
void print(raw_ostream &os) const
Print the program point.
static ProgramPoint * construct(StorageUniquer::StorageAllocator &alloc, KeyTy &&key)
Operation * getPrevOp() const
Get the previous operation of this program point.
Operation * getOperation() const
Get the the iterator this program point refers to.
ProgramPoint(Operation *op)
Creates a new program point at the given operation.