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