LLZK 2.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
AnalysisWrappers.h
Go to the documentation of this file.
1//===-- AnalysisWrappers.h --------------------------------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
21//===----------------------------------------------------------------------===//
22
23#pragma once
24
27#include "llzk/Util/Compare.h"
30
31#include <mlir/IR/BuiltinOps.h>
32#include <mlir/Pass/AnalysisManager.h>
33
34#include <map>
35
36namespace llzk {
37
42template <typename Result, typename Context> class StructAnalysis {
43public:
47 StructAnalysis(mlir::Operation *op) {
48 structDefOp = llvm::dyn_cast<component::StructDefOp>(op);
49 if (!structDefOp) {
50 const char *error_message = "StructAnalysis expects provided op to be a StructDefOp!";
51 op->emitError(error_message).report();
52 llvm::report_fatal_error(error_message);
53 }
54 auto maybeModOp = getRootModule(op);
55 if (mlir::failed(maybeModOp)) {
56 const char *error_message = "StructAnalysis could not find root module from StructDefOp!";
57 op->emitError(error_message).report();
58 llvm::report_fatal_error(error_message);
59 }
60 modOp = *maybeModOp;
61 }
62 virtual ~StructAnalysis() = default;
63
75 virtual mlir::LogicalResult runAnalysis(
76 mlir::DataFlowSolver &solver, mlir::AnalysisManager &moduleAnalysisManager, const Context &ctx
77 ) = 0;
78
80 bool constructed(const Context &ctx) const { return res.contains(ctx); }
81
83 const Result &getResult(const Context &ctx) const {
84 ensure(
85 constructed(ctx), mlir::Twine(__FUNCTION__) +
86 ": result has not been constructed for struct " +
87 mlir::Twine(getStruct().getName())
88 );
89 return *res.at(ctx);
90 }
91
92protected:
94 mlir::ModuleOp getModule() const { return modOp; }
95
97 component::StructDefOp getStruct() const { return structDefOp; }
98
100 void setResult(const Context &ctx, Result &&r) {
101 auto [_, inserted] = res.insert(std::make_pair(ctx, std::make_unique<Result>(r)));
102 ensure(inserted, "Result already initialized");
103 }
104
105private:
106 mlir::ModuleOp modOp;
107 component::StructDefOp structDefOp;
108 std::unordered_map<Context, std::unique_ptr<Result>> res;
109};
110
111template <typename Context>
112concept ContextType = requires(const Context &a, const Context &b) {
113 { a == b } -> std::convertible_to<bool>;
114 { std::hash<Context> {}(a) } -> std::convertible_to<std::size_t>;
115};
116
119struct NoContext {};
120
123template <typename Analysis, typename Result, typename Context>
124concept StructAnalysisType = requires {
125 requires std::is_base_of<StructAnalysis<Result, Context>, Analysis>::value;
126 requires ContextType<Context>;
127};
128
135template <typename Result, typename Context, StructAnalysisType<Result, Context> StructAnalysisTy>
137
139 using StructResults = std::map<
140 component::StructDefOp, std::reference_wrapper<const Result>,
142
147 using ResultMap = std::unordered_map<Context, StructResults>;
148
149public:
154 ModuleAnalysis(mlir::Operation *op, const mlir::DataFlowConfig &config = mlir::DataFlowConfig())
155 : solver(config) {
156 if (modOp = llvm::dyn_cast<mlir::ModuleOp>(op); !modOp) {
157 const auto *error_message = "ModuleAnalysis expects provided op to be an mlir::ModuleOp!";
158 op->emitError(error_message).report();
159 llvm::report_fatal_error(error_message);
160 }
161 }
162 virtual ~ModuleAnalysis() = default;
163
168 virtual void runAnalysis(mlir::AnalysisManager &am) { constructChildAnalyses(am); }
169
171 void ensureAnalysisRun(mlir::AnalysisManager &am) {
172 if (!constructed()) {
173 runAnalysis(am);
174 }
175 }
176
179 bool constructed() const { return results.contains(getContext()); }
180
183 return constructed() && results.at(getContext()).contains(op);
184 }
185
187 const Result &getResult(component::StructDefOp op) const {
188 ensureResultCreated(op);
189 return results.at(getContext()).at(op).get();
190 }
191
193 const StructResults &getCurrentResults() const {
194 ensure(constructed(), "results are not yet constructed for the current context");
195 return results.at(getContext());
196 }
197
198 mlir::DataFlowSolver &getSolver() { return solver; }
199
200protected:
201 mlir::DataFlowSolver solver;
202
206 virtual void initializeSolver() = 0;
207
210 virtual const Context &getContext() const = 0;
211
215 void constructChildAnalyses(mlir::AnalysisManager &am) {
217 ensure(init.succeeded(), "solver failed to run on module!");
218
219 // The analysis is run at the module level so that lattices are computed
220 // for global functions as well.
222 auto res = solver.initializeAndRun(modOp);
223 ensure(res.succeeded(), "solver failed to run on module!");
224
225 const Context &ctx = getContext();
226 // Force construction of empty results here so `getCurrentResults()` on
227 // a module with no inner structs returns no results rather than an assertion
228 // failure.
229 results[ctx] = {};
230 modOp.walk([this, &am, &ctx](component::StructDefOp s) mutable {
231 auto &childAnalysis = am.getChildAnalysis<StructAnalysisTy>(s);
232 // Don't re-run the analysis if we already have the results.
233 // The analysis may have been run as part of a nested analysis.
234 if (!childAnalysis.constructed(ctx)) {
235 mlir::LogicalResult childAnalysisRes = childAnalysis.runAnalysis(solver, am, ctx);
236
237 if (mlir::failed(childAnalysisRes)) {
238 auto error_message = "StructAnalysis failed to run for " + s.getName();
239 s->emitError(error_message).report();
240 llvm::report_fatal_error(error_message);
241 }
242 }
243
244 auto [_, inserted] = results[ctx].insert(
245 std::make_pair(s, std::reference_wrapper(childAnalysis.getResult(ctx)))
246 );
247 ensure(inserted, "struct location conflict");
248 return mlir::WalkResult::skip();
249 });
250 }
251
252private:
253 mlir::ModuleOp modOp;
254 ResultMap results;
255
258 void ensureResultCreated(component::StructDefOp op) const {
259 ensure(hasResult(op), "Result does not exist for StructDefOp " + mlir::Twine(op.getName()));
260 }
261};
262
263} // namespace llzk
mlir::DataFlowSolver solver
virtual ~ModuleAnalysis()=default
mlir::DataFlowSolver & getSolver()
void ensureAnalysisRun(mlir::AnalysisManager &am)
Runs the analysis if the results do not already exist.
bool constructed() const
Check if the results of this analysis have been created for the currently available context.
virtual void initializeSolver()=0
Initialize the shared dataflow solver with any common analyses required by the contained struct analy...
virtual void runAnalysis(mlir::AnalysisManager &am)
Run the StructAnalysisTy struct analysis on all child structs.
const StructResults & getCurrentResults() const
Get the results for the current context.
virtual const Context & getContext() const =0
Return the current Context object.
const Result & getResult(component::StructDefOp op) const
Asserts that op has a result and returns it.
void constructChildAnalyses(mlir::AnalysisManager &am)
Construct and run the StructAnalysisTy analyses on each StructDefOp contained in the ModuleOp that is...
bool hasResult(component::StructDefOp op) const
Checks if op has a result contained in the current result map.
ModuleAnalysis(mlir::Operation *op, const mlir::DataFlowConfig &config=mlir::DataFlowConfig())
Asserts that the analysis is being run on a ModuleOp.
StructAnalysis(mlir::Operation *op)
Assert that this analysis is being run on a StructDefOp and initializes the analysis with the current...
component::StructDefOp getStruct() const
Get the current StructDefOp that is under analysis.
virtual mlir::LogicalResult runAnalysis(mlir::DataFlowSolver &solver, mlir::AnalysisManager &moduleAnalysisManager, const Context &ctx)=0
Perform the analysis and construct the Result output.
void setResult(const Context &ctx, Result &&r)
Initialize the final Result object.
const Result & getResult(const Context &ctx) const
Access the result iff it has been created for the given Context object ctx.
mlir::ModuleOp getModule() const
Get the ModuleOp that is the parent of the StructDefOp that is under analysis.
bool constructed(const Context &ctx) const
Query if the analysis has constructed a Result object for the given Context.
virtual ~StructAnalysis()=default
Any type that is a subclass of StructAnalysis and provided a Context that matches ContextType.
LogicalResult loadAndRunRequiredAnalyses(DataFlowSolver &solver, Operation *op)
void ensure(bool condition, const llvm::Twine &errMsg)
FailureOr< ModuleOp > getRootModule(Operation *from)
An empty struct that is used for convenience for analyses that do not require any context.