LLZK 3.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
205 virtual void initializeSolver() = 0;
206
209 virtual const Context &getContext() const = 0;
210
214 void constructChildAnalyses(mlir::AnalysisManager &am) {
216 ensure(init.succeeded(), "solver failed to run on module!");
217
218 // The analysis is run at the module level so that lattices are computed
219 // for global functions as well.
221 auto res = solver.initializeAndRun(modOp);
222 ensure(res.succeeded(), "solver failed to run on module!");
223
224 const Context &ctx = getContext();
225 // Force construction of empty results here so `getCurrentResults()` on
226 // a module with no inner structs returns no results rather than an assertion
227 // failure.
228 results[ctx] = {};
229 modOp.walk([this, &am, &ctx](component::StructDefOp s) mutable {
230 auto &childAnalysis = am.getChildAnalysis<StructAnalysisTy>(s);
231 // Don't re-run the analysis if we already have the results.
232 // The analysis may have been run as part of a nested analysis.
233 if (!childAnalysis.constructed(ctx)) {
234 mlir::LogicalResult childAnalysisRes = childAnalysis.runAnalysis(solver, am, ctx);
235
236 if (mlir::failed(childAnalysisRes)) {
237 auto error_message = "StructAnalysis failed to run for " + s.getName();
238 s->emitError(error_message).report();
239 llvm::report_fatal_error(error_message);
240 }
241 }
242
243 auto [_, inserted] = results[ctx].insert(
244 std::make_pair(s, std::reference_wrapper(childAnalysis.getResult(ctx)))
245 );
246 ensure(inserted, "struct location conflict");
247 return mlir::WalkResult::skip();
248 });
249 }
250
251private:
252 mlir::ModuleOp modOp;
253 ResultMap results;
254
257 void ensureResultCreated(component::StructDefOp op) const {
258 ensure(hasResult(op), "Result does not exist for StructDefOp " + mlir::Twine(op.getName()));
259 }
260};
261
262} // 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)
Order named operations by source location, using the symbol name to break ties or when source locatio...
Definition Compare.h:75
An empty struct that is used for convenience for analyses that do not require any context.