-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathConstraintDependencyGraph.h
More file actions
269 lines (223 loc) · 11.1 KB
/
ConstraintDependencyGraph.h
File metadata and controls
269 lines (223 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
//===-- ConstraintDependencyGraph.h -----------------------------*- C++ -*-===//
//
// Part of the LLZK Project, under the Apache License v2.0.
// See LICENSE.txt for license information.
// Copyright 2025 Veridise Inc.
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#pragma once
#include "llzk/Analysis/AnalysisWrappers.h"
#include "llzk/Analysis/SourceRef.h"
#include "llzk/Analysis/SourceRefLattice.h"
#include "llzk/Dialect/Array/IR/Ops.h"
#include <mlir/Pass/AnalysisManager.h>
#include <llvm/ADT/EquivalenceClasses.h>
namespace mlir {
class DataFlowSolver;
} // namespace mlir
namespace llzk {
using SourceRefRemappings = std::vector<std::pair<SourceRef, SourceRefLatticeValue>>;
/// @brief The dataflow analysis that computes the set of references that
/// LLZK operations use and produce. The analysis is simple: any operation will
/// simply output a union of its input references, regardless of what type of
/// operation it performs, as the analysis is operator-insensitive.
class SourceRefAnalysis : public dataflow::DenseForwardDataFlowAnalysis<SourceRefLattice> {
public:
using dataflow::DenseForwardDataFlowAnalysis<SourceRefLattice>::DenseForwardDataFlowAnalysis;
void visitCallControlFlowTransfer(
mlir::CallOpInterface call, dataflow::CallControlFlowAction action,
const SourceRefLattice &before, SourceRefLattice *after
) override;
/// @brief Propagate `SourceRef` lattice values from operands to results.
/// @param op
/// @param before
/// @param after
mlir::LogicalResult visitOperation(
mlir::Operation *op, const SourceRefLattice &before, SourceRefLattice *after
) override;
protected:
void setToEntryState(SourceRefLattice *lattice) override {
// the entry state is empty, so do nothing.
}
// Perform a standard union of operands into the results value.
mlir::ChangeResult fallbackOpUpdate(
mlir::Operation *op, const SourceRefLattice::ValueMap &operandVals,
const SourceRefLattice &before, SourceRefLattice *after
);
// Perform the update for either a array.read op or an array.extract op, which
// operate very similarly: index into the first operand using a variable number
// of provided indices.
void arraySubdivisionOpUpdate(
array::ArrayAccessOpInterface op, const SourceRefLattice::ValueMap &operandVals,
const SourceRefLattice &before, SourceRefLattice *after
);
private:
mlir::SymbolTableCollection tables;
};
/// @brief Parameters and shared objects to pass to child analyses.
struct CDGAnalysisContext {
bool runIntraprocedural = false;
bool runIntraproceduralAnalysis() const { return runIntraprocedural; }
friend bool operator==(const CDGAnalysisContext &a, const CDGAnalysisContext &b) = default;
};
} // namespace llzk
template <> struct std::hash<llzk::CDGAnalysisContext> {
size_t operator()(const llzk::CDGAnalysisContext &c) const {
return std::hash<bool> {}(c.runIntraprocedural);
}
};
namespace llzk {
/// @brief A dependency graph of constraints enforced by an LLZK struct.
///
/// Mathematically speaking, a constraint dependency graph (CDG) is a transitive closure
/// of edges where there is an edge between signals `a` and `b`
/// iff `a` and `b` appear in the same constraint.
///
/// Less formally, a CDG is a set of signals that constrain one another through
/// one or more emit operations (`constrain.in` or `constrain.eq`). The CDG only
/// indicate that signals are connected by constraints, but do not include information
/// about the type of computation that binds them together.
///
/// For example, a CDG of the form: {
/// {%arg1, %arg2, %arg3[@foo]}
/// }
/// Means that %arg1, %arg2, and member @foo of %arg3, are connected
/// via some constraints. These constraints could take the form of (in Circom notation):
/// %arg1 + %arg3[@foo] === %arg2
/// Or
/// %arg2 === %arg2 / %arg3[@foo]
/// Or any other form of constraint including those values.
///
/// The CDG also records information about constant values (e.g., felt.const) that
/// are included in constraints, but does not compute a transitive closure over
/// constant values, as constant value usage in constraints does not imply any
/// dependency between signal values (e.g., constraints a + b === 0 and c + d === 0 both use
/// constant 0, but does not enforce a dependency between a, b, c, and d).
class ConstraintDependencyGraph {
public:
/// @brief Compute a ConstraintDependencyGraph (CDG)
/// @param mod The LLZK-complaint module that is the parent of struct `s`.
/// @param s The struct to compute the CDG for.
/// @param solver A pre-configured DataFlowSolver. The liveness of the struct must
/// already be computed in this solver in order for the constraint analysis to run.
/// @param am A module-level analysis manager. This analysis manager needs to originate
/// from a module-level analysis (i.e., for the `mod` module) so that analyses
/// for other constraints can be queried via the getChildAnalysis method.
/// @param ctx The analysis context.
/// @return
static mlir::FailureOr<ConstraintDependencyGraph> compute(
mlir::ModuleOp mod, component::StructDefOp s, mlir::DataFlowSolver &solver,
mlir::AnalysisManager &am, const CDGAnalysisContext &ctx
);
/// @brief Dumps the CDG to stderr.
void dump() const;
/// @brief Print the CDG to the specified output stream.
/// @param os The LLVM/MLIR output stream.
void print(mlir::raw_ostream &os) const;
/// @brief Translate the SourceRefs in this CDG to that of a different
/// context. Used to translate a CDG of a struct to a CDG for a called subcomponent.
/// @param translation A vector of mappings of current reference prefix -> translated reference
/// prefix.
/// @return A CDG that contains only translated references. Non-constant references with
/// no translation are omitted. This omissions allows calling components to ignore internal
/// references within subcomponents that are inaccessible to the caller.
ConstraintDependencyGraph translate(SourceRefRemappings translation) const;
/// @brief Get the values that are connected to the given ref via emitted constraints.
/// This method looks for constraints to the value in the ref and constraints to any
/// prefix of this value.
/// For example, if ref is an array element (foo[2]), this looks for constraints on
/// foo[2] as well as foo, as arrays may be constrained in their entirety via constrain.in
/// operations.
/// @param ref
/// @return The set of references that are connected to ref via constraints.
SourceRefSet getConstrainingValues(const SourceRef &ref) const;
const SourceRefLattice::Ref2Val &getRef2Val() const { return ref2Val; }
/*
Rule of three, needed for the mlir::SymbolTableCollection, which has no copy constructor.
Since the mlir::SymbolTableCollection is a caching mechanism, we simply allow default, empty
construction for copies.
*/
ConstraintDependencyGraph(const ConstraintDependencyGraph &other)
: mod(other.mod), structDef(other.structDef), ctx(other.ctx), signalSets(other.signalSets),
constantSets(other.constantSets), ref2Val(other.ref2Val), tables() {}
ConstraintDependencyGraph &operator=(const ConstraintDependencyGraph &other) {
mod = other.mod;
structDef = other.structDef;
ctx = other.ctx;
signalSets = other.signalSets;
constantSets = other.constantSets;
ref2Val = other.ref2Val;
return *this;
}
virtual ~ConstraintDependencyGraph() = default;
private:
mlir::ModuleOp mod;
// Using mutable because many operations are not const by default, even for "const"-like
// operations, like "getName()", and this reduces const_casts.
mutable component::StructDefOp structDef;
CDGAnalysisContext ctx;
// Transitive closure only over signals.
llvm::EquivalenceClasses<SourceRef> signalSets;
// A simple set mapping of constants, as we do not want to compute a transitive closure over
// constants.
std::unordered_map<SourceRef, SourceRefSet, SourceRef::Hash> constantSets;
// Maps references to the values where they are found.
SourceRefLattice::Ref2Val ref2Val;
// Also mutable for caching within otherwise const lookup operations.
mutable mlir::SymbolTableCollection tables;
/// @brief Constructs an empty CDG. The CDG is populated using computeConstraints.
/// @param m The parent LLZK-compliant module.
/// @param s The struct to analyze.
/// @param intraprocedural Whether the CDG represents and intra-procedural or inter-procedural
/// analysis
ConstraintDependencyGraph(mlir::ModuleOp m, component::StructDefOp s, const CDGAnalysisContext &c)
: mod(m), structDef(s), ctx(c) {}
/// @brief Runs the constraint analysis to compute a transitive closure over SourceRefs
/// as operated over by emit operations.
/// @param solver The pre-configured solver.
/// @param am The module-level AnalysisManager.
/// @return mlir::success() if no issues were encountered, mlir::failure() otherwise
mlir::LogicalResult computeConstraints(mlir::DataFlowSolver &solver, mlir::AnalysisManager &am);
/// @brief Update the signalSets EquivalenceClasses based on the given
/// emit operation. Relies on the caller to verify that `emitOp` is either
/// an EmitEqualityOp or an EmitContainmentOp, as the logic for both is currently
/// the same.
/// @param solver The pre-configured solver.
/// @param emitOp The emit operation that is creating a constraint.
void walkConstrainOp(mlir::DataFlowSolver &solver, mlir::Operation *emitOp);
};
/// @brief An analysis wrapper around the ConstraintDependencyGraph for a given struct.
/// This analysis is a StructDefOp-level analysis that should not be directly
/// interacted with---rather, it is a utility used by the ConstraintDependencyGraphModuleAnalysis
/// that helps use MLIR's AnalysisManager to cache dependencies for sub-components.
class ConstraintDependencyGraphStructAnalysis
: public StructAnalysis<ConstraintDependencyGraph, CDGAnalysisContext> {
public:
using StructAnalysis::StructAnalysis;
virtual ~ConstraintDependencyGraphStructAnalysis() = default;
/// @brief Construct a CDG, using the module's analysis manager to query
/// ConstraintDependencyGraph objects for nested components.
mlir::LogicalResult runAnalysis(
mlir::DataFlowSolver &solver, mlir::AnalysisManager &moduleAnalysisManager,
const CDGAnalysisContext &ctx
) override;
};
/// @brief A module-level analysis for constructing ConstraintDependencyGraph objects for
/// all structs in the given LLZK module.
class ConstraintDependencyGraphModuleAnalysis
: public ModuleAnalysis<
ConstraintDependencyGraph, CDGAnalysisContext, ConstraintDependencyGraphStructAnalysis> {
public:
using ModuleAnalysis::ModuleAnalysis;
virtual ~ConstraintDependencyGraphModuleAnalysis() = default;
void setIntraprocedural(bool runIntraprocedural) {
ctx = {.runIntraprocedural = runIntraprocedural};
}
protected:
void initializeSolver() override { (void)solver.load<SourceRefAnalysis>(); }
const CDGAnalysisContext &getContext() const override { return ctx; }
private:
CDGAnalysisContext ctx = {.runIntraprocedural = false};
};
} // namespace llzk