[Mlir-commits] [flang] [mlir] [mlir][Analysis] Add dependency declaration for dataflow analyses (PR #193112)
Ivan Butygin
llvmlistbot at llvm.org
Wed Apr 22 04:04:39 PDT 2026
https://github.com/Hardcode84 updated https://github.com/llvm/llvm-project/pull/193112
>From cd036280f85695529cc0e52f5c5e0628ab32e188 Mon Sep 17 00:00:00 2001
From: Ivan Butygin <ivan.butygin at gmail.com>
Date: Tue, 21 Apr 2026 00:54:11 +0200
Subject: [PATCH 1/3] [mlir][Analysis] Add dependency declaration for dataflow
analyses
DataFlowAnalysis subclasses can now declare required sibling analyses
via getDependentAnalyses(AnalysisDependencies &). The solver validates
them by TypeID at initializeAndRun and emits a consolidated diagnostic
listing every missing dep with its requester. Dependencies are not
auto-loaded because load<T> accepts arbitrary constructor arguments the
framework cannot synthesize; callers keep control of instantiation, the
solver just enforces the contract.
The four abstract driver bases (Sparse/Dense x Forward/Backward) declare
DeadCodeAnalysis as a hard dependency, and additionally assert it in
initialize() as a debug-only check against subclasses that override
getDependentAnalyses without chaining to the base.
Also:
- New DataFlowSolver::lookupAnalysis<T>() (exact-TypeID match).
- load<T>() asserts no duplicate load.
- Consolidate the existing debugName (gated on LLVM_ENABLE_ABI_BREAKING_CHECKS)
with the new analysisID/analysisName into always-present members
populated by load() after construction.
- Explicit TypeIDs (MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID) for
analyses defined in anonymous namespaces.
- Document the soft DeadCodeAnalysis <-> SparseConstantPropagation
circular co-dependency in DeadCodeAnalysis.h and Utils.h.
- Two lit tests covering single-requester and multi-requester
missing-dep diagnostics.
Made-with: Cursor
---
.../mlir/Analysis/DataFlow/DeadCodeAnalysis.h | 9 ++
.../mlir/Analysis/DataFlow/DenseAnalysis.h | 8 ++
.../mlir/Analysis/DataFlow/SparseAnalysis.h | 8 ++
mlir/include/mlir/Analysis/DataFlow/Utils.h | 11 +-
.../include/mlir/Analysis/DataFlowFramework.h | 113 ++++++++++++++++--
mlir/lib/Analysis/DataFlow/DenseAnalysis.cpp | 14 +++
mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp | 21 ++++
mlir/lib/Analysis/DataFlowFramework.cpp | 47 +++++++-
.../XeGPU/Transforms/XeGPUPropagateLayout.cpp | 2 +
.../test-analysis-deps-multi-requester.mlir | 11 ++
.../Analysis/DataFlow/test-analysis-deps.mlir | 12 ++
.../DataFlow/TestDeadCodeAnalysis.cpp | 2 +
.../TestDenseBackwardDataFlowAnalysis.cpp | 2 +
.../TestDenseForwardDataFlowAnalysis.cpp | 2 +
.../TestSparseBackwardDataFlowAnalysis.cpp | 4 +-
.../lib/Analysis/TestDataFlowFramework.cpp | 77 ++++++++++++
mlir/tools/mlir-opt/mlir-opt.cpp | 6 +-
17 files changed, 331 insertions(+), 18 deletions(-)
create mode 100644 mlir/test/Analysis/DataFlow/test-analysis-deps-multi-requester.mlir
create mode 100644 mlir/test/Analysis/DataFlow/test-analysis-deps.mlir
diff --git a/mlir/include/mlir/Analysis/DataFlow/DeadCodeAnalysis.h b/mlir/include/mlir/Analysis/DataFlow/DeadCodeAnalysis.h
index 3b2914cdd4c98..b05a323b2b3ec 100644
--- a/mlir/include/mlir/Analysis/DataFlow/DeadCodeAnalysis.h
+++ b/mlir/include/mlir/Analysis/DataFlow/DeadCodeAnalysis.h
@@ -173,6 +173,15 @@ class CFGEdge
/// for region entry blocks and region control-flow operations. For the
/// callgraph, this analysis determines the callsites and live returns of every
/// function.
+///
+/// This analysis reads `Lattice<ConstantValue>` state to refine branches on
+/// constant conditions. The producer of that lattice is *not* declared as a
+/// hard dependency: if no producer is loaded, operand constants remain
+/// uninitialized and `DeadCodeAnalysis` conservatively marks all successors
+/// live (still correct, just less precise). Pair with
+/// `SparseConstantPropagation` (see `loadBaselineAnalyses` in
+/// `mlir/Analysis/DataFlow/Utils.h`) or a custom `Lattice<ConstantValue>`
+/// producer for best precision.
class DeadCodeAnalysis : public DataFlowAnalysis {
public:
explicit DeadCodeAnalysis(DataFlowSolver &solver);
diff --git a/mlir/include/mlir/Analysis/DataFlow/DenseAnalysis.h b/mlir/include/mlir/Analysis/DataFlow/DenseAnalysis.h
index 82f86d06886b6..36a3b439dc2e1 100644
--- a/mlir/include/mlir/Analysis/DataFlow/DenseAnalysis.h
+++ b/mlir/include/mlir/Analysis/DataFlow/DenseAnalysis.h
@@ -73,6 +73,10 @@ class AbstractDenseForwardDataFlowAnalysis : public DataFlowAnalysis {
/// may modify the program state; that is, every operation and block.
LogicalResult initialize(Operation *top) override;
+ /// Dense forward analyses use `DeadCodeAnalysis` to skip dead blocks and
+ /// control-flow edges during propagation.
+ void getDependentAnalyses(AnalysisDependencies &deps) const override;
+
/// Initialize lattice anchor equivalence class from the provided top-level
/// operation.
///
@@ -367,6 +371,10 @@ class AbstractDenseBackwardDataFlowAnalysis : public DataFlowAnalysis {
/// may modify the program state; that is, every operation and block.
LogicalResult initialize(Operation *top) override;
+ /// Dense backward analyses use `DeadCodeAnalysis` to skip dead blocks and
+ /// control-flow edges during propagation.
+ void getDependentAnalyses(AnalysisDependencies &deps) const override;
+
/// Initialize lattice anchor equivalence class from the provided top-level
/// operation.
///
diff --git a/mlir/include/mlir/Analysis/DataFlow/SparseAnalysis.h b/mlir/include/mlir/Analysis/DataFlow/SparseAnalysis.h
index df50d8d193aeb..555efe8f802e9 100644
--- a/mlir/include/mlir/Analysis/DataFlow/SparseAnalysis.h
+++ b/mlir/include/mlir/Analysis/DataFlow/SparseAnalysis.h
@@ -193,6 +193,10 @@ class AbstractSparseForwardDataFlowAnalysis : public DataFlowAnalysis {
/// accordingly. Otherwise, the operation transfer function is invoked.
LogicalResult visit(ProgramPoint *point) override;
+ /// Sparse forward analyses use `DeadCodeAnalysis` to skip dead blocks and
+ /// control-flow edges during propagation.
+ void getDependentAnalyses(AnalysisDependencies &deps) const override;
+
protected:
explicit AbstractSparseForwardDataFlowAnalysis(DataFlowSolver &solver);
@@ -413,6 +417,10 @@ class AbstractSparseBackwardDataFlowAnalysis : public DataFlowAnalysis {
/// Otherwise, invokes the operation transfer function (`visitOperationImpl`).
LogicalResult visit(ProgramPoint *point) override;
+ /// Sparse backward analyses use `DeadCodeAnalysis` to skip dead blocks and
+ /// control-flow edges during propagation.
+ void getDependentAnalyses(AnalysisDependencies &deps) const override;
+
protected:
explicit AbstractSparseBackwardDataFlowAnalysis(
DataFlowSolver &solver, SymbolTableCollection &symbolTable);
diff --git a/mlir/include/mlir/Analysis/DataFlow/Utils.h b/mlir/include/mlir/Analysis/DataFlow/Utils.h
index e97f2f70f609c..43fd7810e2682 100644
--- a/mlir/include/mlir/Analysis/DataFlow/Utils.h
+++ b/mlir/include/mlir/Analysis/DataFlow/Utils.h
@@ -23,8 +23,15 @@ namespace dataflow {
/// Populates a DataFlowSolver with analyses that are required to ensure
/// user-defined analyses are run properly.
///
+/// `DeadCodeAnalysis` and `SparseConstantPropagation` have a circular
+/// co-dependency: SCP only propagates along live CFG edges (produced by DCA),
+/// and DCA refines branches using `Lattice<ConstantValue>` (produced by SCP).
+/// Only SCP's dep on DCA is declared (inherited from the sparse forward driver
+/// base); DCA works without SCP at reduced precision, so clients are free to
+/// substitute another `Lattice<ConstantValue>` producer.
+///
/// This helper is intended to be an interim fix until a more robust solution
-/// can be implemented in the DataFlow framework directly. Cf.
+/// can be implemented in the DataFlow framework directly. See
/// https://discourse.llvm.org/t/mlir-dead-code-analysis/67568
inline void loadBaselineAnalyses(DataFlowSolver &solver) {
solver.load<dataflow::DeadCodeAnalysis>();
@@ -34,4 +41,4 @@ inline void loadBaselineAnalyses(DataFlowSolver &solver) {
} // end namespace dataflow
} // end namespace mlir
-#endif // MLIR_ANALYSIS_DATAFLOW_INTEGERANGEANALYSIS_H
+#endif // MLIR_ANALYSIS_DATAFLOW_UTILS_H
diff --git a/mlir/include/mlir/Analysis/DataFlowFramework.h b/mlir/include/mlir/Analysis/DataFlowFramework.h
index 87ec01a918d90..0d766dffc77e9 100644
--- a/mlir/include/mlir/Analysis/DataFlowFramework.h
+++ b/mlir/include/mlir/Analysis/DataFlowFramework.h
@@ -28,6 +28,52 @@
namespace mlir {
+class DataFlowAnalysis;
+
+//===----------------------------------------------------------------------===//
+// AnalysisDependencies
+//===----------------------------------------------------------------------===//
+
+/// Helper used by `DataFlowAnalysis::getDependentAnalyses` to declare the
+/// analyses that must be loaded into the solver for an analysis to run
+/// correctly. Declared analyses are not auto-loaded by the solver; callers
+/// are expected to load them explicitly with the appropriate constructor
+/// arguments. `DataFlowSolver::initializeAndRun` fails with a diagnostic if
+/// any declared dependency is missing.
+///
+/// Dependencies are matched by `TypeID`. Analyses defined inside an
+/// anonymous namespace must therefore provide an explicit TypeID via
+/// `MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID` (see
+/// `mlir/Support/TypeID.h`).
+class AnalysisDependencies {
+public:
+ /// A declared dependency. `typeID` is used for matching; `name` is
+ /// purely informational and is used in diagnostics.
+ struct Dependency {
+ TypeID typeID;
+ StringRef name;
+ };
+
+ /// Declare that the enclosing analysis depends on `AnalysisT`. Repeated
+ /// declarations of the same dependency are deduplicated so validation
+ /// diagnostics do not carry redundant notes.
+ template <typename AnalysisT>
+ void insert() {
+ static_assert(std::is_base_of_v<DataFlowAnalysis, AnalysisT>,
+ "dependency must derive from DataFlowAnalysis");
+ TypeID id = TypeID::get<AnalysisT>();
+ for (const Dependency &dep : deps)
+ if (dep.typeID == id)
+ return;
+ deps.push_back({id, llvm::getTypeName<AnalysisT>()});
+ }
+
+ ArrayRef<Dependency> getDependencies() const { return deps; }
+
+private:
+ SmallVector<Dependency> deps;
+};
+
//===----------------------------------------------------------------------===//
// ChangeResult
//===----------------------------------------------------------------------===//
@@ -261,9 +307,6 @@ struct LatticeAnchor
Location getLoc() const;
};
-/// Forward declaration of the data-flow analysis class.
-class DataFlowAnalysis;
-
} // namespace mlir
template <>
@@ -331,8 +374,17 @@ class DataFlowSolver {
template <typename AnalysisT, typename... Args>
AnalysisT *load(Args &&...args);
+ /// Return the loaded analysis of type `AnalysisT`, or nullptr if no such
+ /// analysis is loaded. Matches are by exact TypeID: subclasses of
+ /// `AnalysisT` will not be returned when querying for `AnalysisT`.
+ template <typename AnalysisT>
+ AnalysisT *lookupAnalysis() const;
+
/// Initialize the children analyses starting from the provided top-level
/// operation and run the analysis until fixpoint.
+ ///
+ /// Fails with a diagnostic if any child analysis declares a dependency via
+ /// `DataFlowAnalysis::getDependentAnalyses` that has not been loaded.
LogicalResult initializeAndRun(Operation *top);
/// Lookup an analysis state for the given lattice anchor. Returns null if one
@@ -627,9 +679,29 @@ class DataFlowAnalysis {
///
/// This function will union lattice anchor to same equivalent class if the
/// analysis can determine the lattice content of lattice anchor is
- /// necessarily identical under the corrensponding lattice type.
+ /// necessarily identical under the corresponding lattice type.
virtual void initializeEquivalentLatticeAnchor(Operation *top) {}
+ /// Register the analyses that this analysis depends on. Each dependency
+ /// must be loaded into the solver before `initializeAndRun` is called.
+ ///
+ /// Dependencies are not auto-loaded, because `DataFlowSolver::load` accepts
+ /// arbitrary constructor arguments that the framework cannot synthesize.
+ /// Subclasses that override this method should call the parent's
+ /// `getDependentAnalyses` first to preserve inherited dependencies.
+ virtual void getDependentAnalyses(AnalysisDependencies &deps) const {}
+
+ /// Return the TypeID of this analysis. Valid only after
+ /// `DataFlowSolver::load<AnalysisT>` has returned; must not be read from
+ /// the analysis constructor body. Used by the solver to match declared
+ /// dependencies.
+ TypeID getTypeID() const { return analysisID; }
+
+ /// Return a printable name for the analysis. Valid only after
+ /// `DataFlowSolver::load<AnalysisT>` has returned; must not be read from
+ /// the analysis constructor body. Used for diagnostics and debug logging.
+ StringRef getName() const { return analysisName; }
+
protected:
/// Create a dependency between the given analysis state and lattice anchor
/// on this analysis.
@@ -696,28 +768,47 @@ class DataFlowAnalysis {
/// Return the configuration of the solver used for this analysis.
const DataFlowConfig &getSolverConfig() const { return solver.getConfig(); }
-#if LLVM_ENABLE_ABI_BREAKING_CHECKS
- /// When compiling with debugging, keep a name for the analyis.
- StringRef debugName;
-#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
+ /// Return the parent solver.
+ const DataFlowSolver &getSolver() const { return solver; }
private:
/// The parent data-flow solver.
DataFlowSolver &solver;
+ /// The TypeID of the concrete analysis type. Set by `DataFlowSolver::load`,
+ /// used to match declared dependencies.
+ TypeID analysisID;
+
+ /// Printable name of the analysis. Set by `DataFlowSolver::load`. Used for
+ /// diagnostics when reporting missing dependencies and for debug logging.
+ StringRef analysisName;
+
/// Allow the data-flow solver to access the internals of this class.
friend class DataFlowSolver;
};
template <typename AnalysisT, typename... Args>
AnalysisT *DataFlowSolver::load(Args &&...args) {
+ assert(lookupAnalysis<AnalysisT>() == nullptr &&
+ "analysis of this type already loaded into the solver");
childAnalyses.emplace_back(new AnalysisT(*this, std::forward<Args>(args)...));
-#if LLVM_ENABLE_ABI_BREAKING_CHECKS
- childAnalyses.back()->debugName = llvm::getTypeName<AnalysisT>();
-#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
+ DataFlowAnalysis &analysis = *childAnalyses.back();
+ analysis.analysisID = TypeID::get<AnalysisT>();
+ analysis.analysisName = llvm::getTypeName<AnalysisT>();
return static_cast<AnalysisT *>(childAnalyses.back().get());
}
+template <typename AnalysisT>
+AnalysisT *DataFlowSolver::lookupAnalysis() const {
+ // Linear search over `childAnalyses`. Solvers typically hold a handful of
+ // analyses, so a hash map would cost more than it saves.
+ TypeID id = TypeID::get<AnalysisT>();
+ for (const std::unique_ptr<DataFlowAnalysis> &analysis : childAnalyses)
+ if (analysis->getTypeID() == id)
+ return static_cast<AnalysisT *>(analysis.get());
+ return nullptr;
+}
+
template <typename StateT>
LatticeAnchor
DataFlowSolver::getLeaderAnchorOrSelf(LatticeAnchor latticeAnchor) const {
diff --git a/mlir/lib/Analysis/DataFlow/DenseAnalysis.cpp b/mlir/lib/Analysis/DataFlow/DenseAnalysis.cpp
index 22bc0b32a9bd1..37d7ce76e47ce 100644
--- a/mlir/lib/Analysis/DataFlow/DenseAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/DenseAnalysis.cpp
@@ -30,6 +30,11 @@ using namespace mlir::dataflow;
// AbstractDenseForwardDataFlowAnalysis
//===----------------------------------------------------------------------===//
+void AbstractDenseForwardDataFlowAnalysis::getDependentAnalyses(
+ AnalysisDependencies &deps) const {
+ deps.insert<DeadCodeAnalysis>();
+}
+
void AbstractDenseForwardDataFlowAnalysis::initializeEquivalentLatticeAnchor(
Operation *top) {
LDBG() << "initializeEquivalentLatticeAnchor: "
@@ -48,6 +53,8 @@ void AbstractDenseForwardDataFlowAnalysis::initializeEquivalentLatticeAnchor(
}
LogicalResult AbstractDenseForwardDataFlowAnalysis::initialize(Operation *top) {
+ assert(getSolver().lookupAnalysis<DeadCodeAnalysis>() &&
+ "DeadCodeAnalysis must be loaded alongside a dense forward analysis");
LDBG() << "initialize (forward): "
<< OpWithFlags(top, OpPrintingFlags().skipRegions());
// Visit every operation and block.
@@ -356,6 +363,11 @@ void AbstractDenseForwardDataFlowAnalysis::visitRegionBranchOperation(
// AbstractDenseBackwardDataFlowAnalysis
//===----------------------------------------------------------------------===//
+void AbstractDenseBackwardDataFlowAnalysis::getDependentAnalyses(
+ AnalysisDependencies &deps) const {
+ deps.insert<DeadCodeAnalysis>();
+}
+
void AbstractDenseBackwardDataFlowAnalysis::initializeEquivalentLatticeAnchor(
Operation *top) {
LDBG() << "initializeEquivalentLatticeAnchor (backward): "
@@ -375,6 +387,8 @@ void AbstractDenseBackwardDataFlowAnalysis::initializeEquivalentLatticeAnchor(
LogicalResult
AbstractDenseBackwardDataFlowAnalysis::initialize(Operation *top) {
+ assert(getSolver().lookupAnalysis<DeadCodeAnalysis>() &&
+ "DeadCodeAnalysis must be loaded alongside a dense backward analysis");
LDBG() << "initialize (backward): "
<< OpWithFlags(top, OpPrintingFlags().skipRegions());
// Visit every operation and block.
diff --git a/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp b/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
index 90f2a588d1ca4..30f0a9c739533 100644
--- a/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
@@ -51,8 +51,21 @@ AbstractSparseForwardDataFlowAnalysis::AbstractSparseForwardDataFlowAnalysis(
registerAnchorKind<CFGEdge>();
}
+void AbstractSparseForwardDataFlowAnalysis::getDependentAnalyses(
+ AnalysisDependencies &deps) const {
+ deps.insert<DeadCodeAnalysis>();
+}
+
LogicalResult
AbstractSparseForwardDataFlowAnalysis::initialize(Operation *top) {
+ // Sparse forward analyses require `DeadCodeAnalysis` to be loaded. This is
+ // normally enforced by the solver's dependency validator, but a concrete
+ // subclass can silently drop the base's declared dependency by overriding
+ // `getDependentAnalyses` without calling the parent. Assert here as a
+ // second line of defense.
+ assert(getSolver().lookupAnalysis<DeadCodeAnalysis>() &&
+ "DeadCodeAnalysis must be loaded alongside a sparse forward analysis");
+
// Mark the entry block arguments as having reached their pessimistic
// fixpoints.
for (Region ®ion : top->getRegions()) {
@@ -371,8 +384,16 @@ AbstractSparseBackwardDataFlowAnalysis::AbstractSparseBackwardDataFlowAnalysis(
registerAnchorKind<CFGEdge>();
}
+void AbstractSparseBackwardDataFlowAnalysis::getDependentAnalyses(
+ AnalysisDependencies &deps) const {
+ deps.insert<DeadCodeAnalysis>();
+}
+
LogicalResult
AbstractSparseBackwardDataFlowAnalysis::initialize(Operation *top) {
+ assert(getSolver().lookupAnalysis<DeadCodeAnalysis>() &&
+ "DeadCodeAnalysis must be loaded alongside a sparse backward "
+ "analysis");
return initializeRecursively(top);
}
diff --git a/mlir/lib/Analysis/DataFlowFramework.cpp b/mlir/lib/Analysis/DataFlowFramework.cpp
index 258bcf312afc5..a1a97097c36d9 100644
--- a/mlir/lib/Analysis/DataFlowFramework.cpp
+++ b/mlir/lib/Analysis/DataFlowFramework.cpp
@@ -7,10 +7,12 @@
//===----------------------------------------------------------------------===//
#include "mlir/Analysis/DataFlowFramework.h"
+#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/Location.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/SymbolTable.h"
#include "mlir/IR/Value.h"
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/iterator.h"
#include "llvm/Config/abi-breaking.h"
@@ -109,7 +111,48 @@ Location LatticeAnchor::getLoc() const {
// DataFlowSolver
//===----------------------------------------------------------------------===//
+/// Emit a diagnostic listing all analyses whose declared dependencies are not
+/// loaded in the solver. Returns failure if any dependency is missing.
+static LogicalResult
+validateDependencies(Operation *top,
+ ArrayRef<std::unique_ptr<DataFlowAnalysis>> analyses) {
+ llvm::SmallDenseSet<TypeID, 8> loaded;
+ for (const std::unique_ptr<DataFlowAnalysis> &analysis : analyses)
+ loaded.insert(analysis->getTypeID());
+
+ struct Missing {
+ StringRef requester;
+ AnalysisDependencies::Dependency dep;
+ };
+ SmallVector<Missing> missing;
+
+ for (const std::unique_ptr<DataFlowAnalysis> &analysis : analyses) {
+ AnalysisDependencies deps;
+ analysis->getDependentAnalyses(deps);
+ for (const AnalysisDependencies::Dependency &dep : deps.getDependencies()) {
+ if (!loaded.contains(dep.typeID))
+ missing.push_back({analysis->getName(), dep});
+ }
+ }
+
+ if (missing.empty())
+ return success();
+
+ InFlightDiagnostic err =
+ emitError(top->getLoc(),
+ "DataFlowSolver: missing required analyses; load each missing "
+ "analysis via `solver.load<T>()` before `initializeAndRun`");
+ for (const Missing &m : missing) {
+ err.attachNote() << "analysis '" << m.requester << "' requires '"
+ << m.dep.name << "' (not loaded)";
+ }
+ return err;
+}
+
LogicalResult DataFlowSolver::initializeAndRun(Operation *top) {
+ if (failed(validateDependencies(top, childAnalyses)))
+ return failure();
+
// Enable enqueue to the worklist.
isRunning = true;
llvm::scope_exit guard([&]() { isRunning = false; });
@@ -127,7 +170,7 @@ LogicalResult DataFlowSolver::initializeAndRun(Operation *top) {
// Initialize the analyses.
for (DataFlowAnalysis &analysis : llvm::make_pointee_range(childAnalyses)) {
- DATAFLOW_DEBUG(LDBG() << "Priming analysis: " << analysis.debugName);
+ DATAFLOW_DEBUG(LDBG() << "Priming analysis: " << analysis.getName());
if (failed(analysis.initialize(top)))
return failure();
}
@@ -139,7 +182,7 @@ LogicalResult DataFlowSolver::initializeAndRun(Operation *top) {
auto [point, analysis] = worklist.front();
worklist.pop();
- DATAFLOW_DEBUG(LDBG() << "Invoking '" << analysis->debugName
+ DATAFLOW_DEBUG(LDBG() << "Invoking '" << analysis->getName()
<< "' on: " << *point);
if (failed(analysis->visit(point)))
return failure();
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
index 686cb20e1976e..776b31efc167c 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
@@ -390,6 +390,8 @@ class LayoutInfoPropagation
bool hasParamsOfLayoutKind(xegpu::DistributeLayoutAttr anchorLayout);
public:
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LayoutInfoPropagation)
+
LayoutInfoPropagation(DataFlowSolver &solver,
SymbolTableCollection &symbolTable,
xegpu::LayoutKind layoutKind, unsigned indexBitWidth)
diff --git a/mlir/test/Analysis/DataFlow/test-analysis-deps-multi-requester.mlir b/mlir/test/Analysis/DataFlow/test-analysis-deps-multi-requester.mlir
new file mode 100644
index 0000000000000..cf7366e23d753
--- /dev/null
+++ b/mlir/test/Analysis/DataFlow/test-analysis-deps-multi-requester.mlir
@@ -0,0 +1,11 @@
+// RUN: mlir-opt %s -test-analysis-deps-multi-requester -verify-diagnostics
+
+// When multiple loaded analyses all depend on the same missing analysis, the
+// solver should emit one error with one note per requester.
+
+// expected-error @below {{DataFlowSolver: missing required analyses}}
+// expected-note-re @below {{FooDependentAnalysis' requires '{{.*}}FooAnalysis' (not loaded)}}
+// expected-note-re @below {{BazDependentAnalysis' requires '{{.*}}FooAnalysis' (not loaded)}}
+func.func @missing_dep_multi_requester() {
+ return
+}
diff --git a/mlir/test/Analysis/DataFlow/test-analysis-deps.mlir b/mlir/test/Analysis/DataFlow/test-analysis-deps.mlir
new file mode 100644
index 0000000000000..dae8cf522a0dc
--- /dev/null
+++ b/mlir/test/Analysis/DataFlow/test-analysis-deps.mlir
@@ -0,0 +1,12 @@
+// RUN: mlir-opt %s -test-analysis-deps -verify-diagnostics
+
+// The `FooDependentAnalysis` test analysis declares a dependency on
+// `FooAnalysis` but the test pass only loads `FooDependentAnalysis`, so
+// `initializeAndRun` must fail with a diagnostic naming both the requester and
+// the missing dep.
+
+// expected-error @below {{DataFlowSolver: missing required analyses}}
+// expected-note-re @below {{FooDependentAnalysis' requires '{{.*}}FooAnalysis' (not loaded)}}
+func.func @missing_dep() {
+ return
+}
diff --git a/mlir/test/lib/Analysis/DataFlow/TestDeadCodeAnalysis.cpp b/mlir/test/lib/Analysis/DataFlow/TestDeadCodeAnalysis.cpp
index 2dc77c9705d35..327e807873714 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestDeadCodeAnalysis.cpp
+++ b/mlir/test/lib/Analysis/DataFlow/TestDeadCodeAnalysis.cpp
@@ -67,6 +67,8 @@ namespace {
/// This is a simple analysis that implements a transfer function for constant
/// operations.
struct ConstantAnalysis : public DataFlowAnalysis {
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ConstantAnalysis)
+
using DataFlowAnalysis::DataFlowAnalysis;
LogicalResult initialize(Operation *top) override {
diff --git a/mlir/test/lib/Analysis/DataFlow/TestDenseBackwardDataFlowAnalysis.cpp b/mlir/test/lib/Analysis/DataFlow/TestDenseBackwardDataFlowAnalysis.cpp
index 232bf14827556..f09df47e5f65e 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestDenseBackwardDataFlowAnalysis.cpp
+++ b/mlir/test/lib/Analysis/DataFlow/TestDenseBackwardDataFlowAnalysis.cpp
@@ -51,6 +51,8 @@ class NextAccess : public AbstractDenseLattice, public AccessLatticeBase {
class NextAccessAnalysis : public DenseBackwardDataFlowAnalysis<NextAccess> {
public:
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(NextAccessAnalysis)
+
NextAccessAnalysis(DataFlowSolver &solver, SymbolTableCollection &symbolTable,
bool assumeFuncReads = false)
: DenseBackwardDataFlowAnalysis(solver, symbolTable),
diff --git a/mlir/test/lib/Analysis/DataFlow/TestDenseForwardDataFlowAnalysis.cpp b/mlir/test/lib/Analysis/DataFlow/TestDenseForwardDataFlowAnalysis.cpp
index 9236e98168883..f2384f32948a2 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestDenseForwardDataFlowAnalysis.cpp
+++ b/mlir/test/lib/Analysis/DataFlow/TestDenseForwardDataFlowAnalysis.cpp
@@ -49,6 +49,8 @@ class LastModification : public AbstractDenseLattice, public AccessLatticeBase {
class LastModifiedAnalysis
: public DenseForwardDataFlowAnalysis<LastModification> {
public:
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LastModifiedAnalysis)
+
explicit LastModifiedAnalysis(DataFlowSolver &solver, bool assumeFuncWrites)
: DenseForwardDataFlowAnalysis(solver),
assumeFuncWrites(assumeFuncWrites) {}
diff --git a/mlir/test/lib/Analysis/DataFlow/TestSparseBackwardDataFlowAnalysis.cpp b/mlir/test/lib/Analysis/DataFlow/TestSparseBackwardDataFlowAnalysis.cpp
index 4f19cc7144afc..8b26816745a64 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestSparseBackwardDataFlowAnalysis.cpp
+++ b/mlir/test/lib/Analysis/DataFlow/TestSparseBackwardDataFlowAnalysis.cpp
@@ -1,4 +1,4 @@
-//===- TestBackwardDataFlowAnalysis.cpp - Test dead code analysis ---------===//
+//===- TestSparseBackwardDataFlowAnalysis.cpp - Test backward sparse ------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -70,6 +70,8 @@ struct WrittenTo : public Lattice<WrittenToLatticeValue> {
/// is eventually written to.
class WrittenToAnalysis : public SparseBackwardDataFlowAnalysis<WrittenTo> {
public:
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(WrittenToAnalysis)
+
WrittenToAnalysis(DataFlowSolver &solver, SymbolTableCollection &symbolTable,
bool assumeFuncWrites)
: SparseBackwardDataFlowAnalysis(solver, symbolTable),
diff --git a/mlir/test/lib/Analysis/TestDataFlowFramework.cpp b/mlir/test/lib/Analysis/TestDataFlowFramework.cpp
index 4267fb42266ce..add1c8696b5a6 100644
--- a/mlir/test/lib/Analysis/TestDataFlowFramework.cpp
+++ b/mlir/test/lib/Analysis/TestDataFlowFramework.cpp
@@ -90,6 +90,77 @@ struct TestFooAnalysisPass
void runOnOperation() override;
};
+
+/// An analysis that declares a dependency on `FooAnalysis` but does nothing
+/// else. Used to test that `DataFlowSolver::initializeAndRun` reports missing
+/// dependencies via diagnostic.
+class FooDependentAnalysis : public DataFlowAnalysis {
+public:
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(FooDependentAnalysis)
+
+ using DataFlowAnalysis::DataFlowAnalysis;
+
+ LogicalResult initialize(Operation *) override { return success(); }
+ LogicalResult visit(ProgramPoint *) override { return success(); }
+
+ void getDependentAnalyses(AnalysisDependencies &deps) const override {
+ deps.insert<FooAnalysis>();
+ }
+};
+
+/// A second analysis that also declares a dependency on `FooAnalysis`. Paired
+/// with `FooDependentAnalysis` to test that the solver emits a separate
+/// diagnostic note per requester when multiple analyses share an unsatisfied
+/// dependency. The name is intentionally *not* a prefix/suffix of
+/// `FooDependentAnalysis` so lit-test regex matches stay disjoint.
+class BazDependentAnalysis : public DataFlowAnalysis {
+public:
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(BazDependentAnalysis)
+
+ using DataFlowAnalysis::DataFlowAnalysis;
+
+ LogicalResult initialize(Operation *) override { return success(); }
+ LogicalResult visit(ProgramPoint *) override { return success(); }
+
+ void getDependentAnalyses(AnalysisDependencies &deps) const override {
+ deps.insert<FooAnalysis>();
+ }
+};
+
+struct TestAnalysisDepsPass
+ : public PassWrapper<TestAnalysisDepsPass, OperationPass<func::FuncOp>> {
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestAnalysisDepsPass)
+
+ StringRef getArgument() const override { return "test-analysis-deps"; }
+
+ void runOnOperation() override {
+ DataFlowSolver solver;
+ solver.load<FooDependentAnalysis>();
+ if (failed(solver.initializeAndRun(getOperation())))
+ return signalPassFailure();
+ }
+};
+
+/// Loads two analyses that both depend on `FooAnalysis` (which is not loaded).
+/// The solver should emit one error with two notes, one per requester.
+struct TestAnalysisDepsMultiRequesterPass
+ : public PassWrapper<TestAnalysisDepsMultiRequesterPass,
+ OperationPass<func::FuncOp>> {
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(
+ TestAnalysisDepsMultiRequesterPass)
+
+ StringRef getArgument() const override {
+ return "test-analysis-deps-multi-requester";
+ }
+
+ void runOnOperation() override {
+ DataFlowSolver solver;
+ solver.load<FooDependentAnalysis>();
+ solver.load<BazDependentAnalysis>();
+ if (failed(solver.initializeAndRun(getOperation())))
+ return signalPassFailure();
+ }
+};
} // namespace
LogicalResult FooAnalysis::initialize(Operation *top) {
@@ -182,5 +253,11 @@ void TestFooAnalysisPass::runOnOperation() {
namespace mlir {
namespace test {
void registerTestFooAnalysisPass() { PassRegistration<TestFooAnalysisPass>(); }
+void registerTestAnalysisDepsPass() {
+ PassRegistration<TestAnalysisDepsPass>();
+}
+void registerTestAnalysisDepsMultiRequesterPass() {
+ PassRegistration<TestAnalysisDepsMultiRequesterPass>();
+}
} // namespace test
} // namespace mlir
diff --git a/mlir/tools/mlir-opt/mlir-opt.cpp b/mlir/tools/mlir-opt/mlir-opt.cpp
index 48b8c179bd1b0..b4c3939399782 100644
--- a/mlir/tools/mlir-opt/mlir-opt.cpp
+++ b/mlir/tools/mlir-opt/mlir-opt.cpp
@@ -98,8 +98,9 @@ void registerTestDominancePass();
void registerTestDynamicPipelinePass();
void registerTestRemarkPass();
void registerTestEmulateNarrowTypePass();
+void registerTestAnalysisDepsMultiRequesterPass();
+void registerTestAnalysisDepsPass();
void registerTestFooAnalysisPass();
-void registerTestComposeSubView();
void registerTestMultiBuffering();
void registerTestIRVisitorsPass();
void registerTestGenericIRVisitorsPass();
@@ -246,8 +247,9 @@ static void registerTestPasses() {
mlir::test::registerTestDynamicPipelinePass();
mlir::test::registerTestRemarkPass();
mlir::test::registerTestEmulateNarrowTypePass();
+ mlir::test::registerTestAnalysisDepsMultiRequesterPass();
+ mlir::test::registerTestAnalysisDepsPass();
mlir::test::registerTestFooAnalysisPass();
- mlir::test::registerTestComposeSubView();
mlir::test::registerTestMultiBuffering();
mlir::test::registerTestIRVisitorsPass();
mlir::test::registerTestGenericIRVisitorsPass();
>From 69f2695f09f6333e5cf0d26ebd388f2ae62e9698 Mon Sep 17 00:00:00 2001
From: Ivan Butygin <ivan.butygin at gmail.com>
Date: Tue, 21 Apr 2026 01:17:24 +0200
Subject: [PATCH 2/3] fix flang
---
flang/lib/Optimizer/Transforms/StackArrays.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/flang/lib/Optimizer/Transforms/StackArrays.cpp b/flang/lib/Optimizer/Transforms/StackArrays.cpp
index 3f49f60089dbd..70fdc2dca50ce 100644
--- a/flang/lib/Optimizer/Transforms/StackArrays.cpp
+++ b/flang/lib/Optimizer/Transforms/StackArrays.cpp
@@ -156,6 +156,7 @@ class LatticePoint : public mlir::dataflow::AbstractDenseLattice {
class AllocationAnalysis
: public mlir::dataflow::DenseForwardDataFlowAnalysis<LatticePoint> {
public:
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(AllocationAnalysis)
using DenseForwardDataFlowAnalysis::DenseForwardDataFlowAnalysis;
mlir::LogicalResult visitOperation(mlir::Operation *op,
>From e606f4b888bc73087e996eadcd38e1e312bb76d6 Mon Sep 17 00:00:00 2001
From: Ivan Butygin <ivan.butygin at gmail.com>
Date: Wed, 22 Apr 2026 12:50:31 +0200
Subject: [PATCH 3/3] address review doc feedback on dataflow analysis
dependencies
- Reframe AnalysisDependencies as the collection consulted at
initializeAndRun, warn against overusing hard deps for precision.
- Broaden initializeAndRun failure-mode description (init, visit,
and dependency validation).
- DataFlowAnalysis::getDependentAnalyses: say "chain to the parent's"
and note declarations are deduplicated.
- Rewrite the four driver-base getDependentAnalyses override comments
to describe the subclass contract (chain to preserve DeadCodeAnalysis,
add extra deps) rather than restating what the base does.
- validateDependencies docstring: "Emit" -> "Emits".
Made-with: Cursor
---
.../mlir/Analysis/DataFlow/DenseAnalysis.h | 10 +++--
.../mlir/Analysis/DataFlow/SparseAnalysis.h | 10 +++--
.../include/mlir/Analysis/DataFlowFramework.h | 38 ++++++++++++-------
mlir/lib/Analysis/DataFlowFramework.cpp | 4 +-
4 files changed, 38 insertions(+), 24 deletions(-)
diff --git a/mlir/include/mlir/Analysis/DataFlow/DenseAnalysis.h b/mlir/include/mlir/Analysis/DataFlow/DenseAnalysis.h
index 36a3b439dc2e1..4583ca8051095 100644
--- a/mlir/include/mlir/Analysis/DataFlow/DenseAnalysis.h
+++ b/mlir/include/mlir/Analysis/DataFlow/DenseAnalysis.h
@@ -73,8 +73,9 @@ class AbstractDenseForwardDataFlowAnalysis : public DataFlowAnalysis {
/// may modify the program state; that is, every operation and block.
LogicalResult initialize(Operation *top) override;
- /// Dense forward analyses use `DeadCodeAnalysis` to skip dead blocks and
- /// control-flow edges during propagation.
+ /// Chain to this implementation from subclass overrides (so the
+ /// `DeadCodeAnalysis` dependency is preserved) and add any additional
+ /// dependencies of the concrete analysis.
void getDependentAnalyses(AnalysisDependencies &deps) const override;
/// Initialize lattice anchor equivalence class from the provided top-level
@@ -371,8 +372,9 @@ class AbstractDenseBackwardDataFlowAnalysis : public DataFlowAnalysis {
/// may modify the program state; that is, every operation and block.
LogicalResult initialize(Operation *top) override;
- /// Dense backward analyses use `DeadCodeAnalysis` to skip dead blocks and
- /// control-flow edges during propagation.
+ /// Chain to this implementation from subclass overrides (so the
+ /// `DeadCodeAnalysis` dependency is preserved) and add any additional
+ /// dependencies of the concrete analysis.
void getDependentAnalyses(AnalysisDependencies &deps) const override;
/// Initialize lattice anchor equivalence class from the provided top-level
diff --git a/mlir/include/mlir/Analysis/DataFlow/SparseAnalysis.h b/mlir/include/mlir/Analysis/DataFlow/SparseAnalysis.h
index 555efe8f802e9..ef0b2becd1d02 100644
--- a/mlir/include/mlir/Analysis/DataFlow/SparseAnalysis.h
+++ b/mlir/include/mlir/Analysis/DataFlow/SparseAnalysis.h
@@ -193,8 +193,9 @@ class AbstractSparseForwardDataFlowAnalysis : public DataFlowAnalysis {
/// accordingly. Otherwise, the operation transfer function is invoked.
LogicalResult visit(ProgramPoint *point) override;
- /// Sparse forward analyses use `DeadCodeAnalysis` to skip dead blocks and
- /// control-flow edges during propagation.
+ /// Chain to this implementation from subclass overrides (so the
+ /// `DeadCodeAnalysis` dependency is preserved) and add any additional
+ /// dependencies of the concrete analysis.
void getDependentAnalyses(AnalysisDependencies &deps) const override;
protected:
@@ -417,8 +418,9 @@ class AbstractSparseBackwardDataFlowAnalysis : public DataFlowAnalysis {
/// Otherwise, invokes the operation transfer function (`visitOperationImpl`).
LogicalResult visit(ProgramPoint *point) override;
- /// Sparse backward analyses use `DeadCodeAnalysis` to skip dead blocks and
- /// control-flow edges during propagation.
+ /// Chain to this implementation from subclass overrides (so the
+ /// `DeadCodeAnalysis` dependency is preserved) and add any additional
+ /// dependencies of the concrete analysis.
void getDependentAnalyses(AnalysisDependencies &deps) const override;
protected:
diff --git a/mlir/include/mlir/Analysis/DataFlowFramework.h b/mlir/include/mlir/Analysis/DataFlowFramework.h
index 0d766dffc77e9..0dff75f0f488f 100644
--- a/mlir/include/mlir/Analysis/DataFlowFramework.h
+++ b/mlir/include/mlir/Analysis/DataFlowFramework.h
@@ -34,12 +34,18 @@ class DataFlowAnalysis;
// AnalysisDependencies
//===----------------------------------------------------------------------===//
-/// Helper used by `DataFlowAnalysis::getDependentAnalyses` to declare the
-/// analyses that must be loaded into the solver for an analysis to run
-/// correctly. Declared analyses are not auto-loaded by the solver; callers
-/// are expected to load them explicitly with the appropriate constructor
-/// arguments. `DataFlowSolver::initializeAndRun` fails with a diagnostic if
-/// any declared dependency is missing.
+/// Collection of sibling analyses a `DataFlowAnalysis` subclass declares it
+/// requires via `DataFlowAnalysis::getDependentAnalyses`. The solver consults
+/// it at `initializeAndRun` time and errors out if any declared analysis is
+/// missing from the solver.
+///
+/// Declared analyses are *not* auto-loaded by the solver. `DataFlowSolver::
+/// load<T>` can take arbitrary constructor arguments that the framework
+/// cannot synthesize, so the caller retains control of how each analysis is
+/// instantiated; this class only captures the contract. Use hard
+/// dependencies sparingly, and only for analyses that are strictly required
+/// for correctness; analyses that would merely improve precision should be
+/// loaded explicitly by the pipeline author instead.
///
/// Dependencies are matched by `TypeID`. Analyses defined inside an
/// anonymous namespace must therefore provide an explicit TypeID via
@@ -383,8 +389,10 @@ class DataFlowSolver {
/// Initialize the children analyses starting from the provided top-level
/// operation and run the analysis until fixpoint.
///
- /// Fails with a diagnostic if any child analysis declares a dependency via
- /// `DataFlowAnalysis::getDependentAnalyses` that has not been loaded.
+ /// Returns failure if any child analysis fails during initialization or
+ /// fixpoint iteration, or if dependency validation fails (a child analysis
+ /// declares a dependency via `DataFlowAnalysis::getDependentAnalyses` that
+ /// has not been loaded).
LogicalResult initializeAndRun(Operation *top);
/// Lookup an analysis state for the given lattice anchor. Returns null if one
@@ -682,13 +690,15 @@ class DataFlowAnalysis {
/// necessarily identical under the corresponding lattice type.
virtual void initializeEquivalentLatticeAnchor(Operation *top) {}
- /// Register the analyses that this analysis depends on. Each dependency
- /// must be loaded into the solver before `initializeAndRun` is called.
+ /// Register the analyses that this analysis depends on. Each declared
+ /// dependency must be loaded into the solver (via `DataFlowSolver::load`)
+ /// before `initializeAndRun`; otherwise `initializeAndRun` fails with a
+ /// diagnostic listing the missing dependencies.
///
- /// Dependencies are not auto-loaded, because `DataFlowSolver::load` accepts
- /// arbitrary constructor arguments that the framework cannot synthesize.
- /// Subclasses that override this method should call the parent's
- /// `getDependentAnalyses` first to preserve inherited dependencies.
+ /// Subclasses that override this method should chain to the parent's
+ /// `getDependentAnalyses` so that dependencies inherited from base classes
+ /// are preserved. Declarations are deduplicated, so the order of calls
+ /// does not matter.
virtual void getDependentAnalyses(AnalysisDependencies &deps) const {}
/// Return the TypeID of this analysis. Valid only after
diff --git a/mlir/lib/Analysis/DataFlowFramework.cpp b/mlir/lib/Analysis/DataFlowFramework.cpp
index a1a97097c36d9..667a2a3485bc7 100644
--- a/mlir/lib/Analysis/DataFlowFramework.cpp
+++ b/mlir/lib/Analysis/DataFlowFramework.cpp
@@ -111,8 +111,8 @@ Location LatticeAnchor::getLoc() const {
// DataFlowSolver
//===----------------------------------------------------------------------===//
-/// Emit a diagnostic listing all analyses whose declared dependencies are not
-/// loaded in the solver. Returns failure if any dependency is missing.
+/// Emits a diagnostic listing all analyses whose declared dependencies are
+/// not loaded in the solver. Returns failure if any dependency is missing.
static LogicalResult
validateDependencies(Operation *top,
ArrayRef<std::unique_ptr<DataFlowAnalysis>> analyses) {
More information about the Mlir-commits
mailing list