LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
SourceRefLattice.cpp
Go to the documentation of this file.
1//===-- SourceRefLattice.cpp - SourceRef lattice & utils --*- 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//===----------------------------------------------------------------------===//
9
11
17#include "llzk/Util/Hash.h"
19
20#include <mlir/Analysis/DataFlow/DeadCodeAnalysis.h>
21#include <mlir/IR/Value.h>
22
23#include <llvm/Support/Debug.h>
24
25#include <numeric>
26#include <unordered_set>
27
28#define DEBUG_TYPE "llzk-constrain-ref-lattice"
29
30using namespace mlir;
31
32namespace llzk {
33
34using namespace array;
35using namespace component;
36using namespace felt;
37using namespace pod;
38using namespace polymorphic;
39
40/* SourceRefLatticeValue */
41
42mlir::ChangeResult SourceRefLatticeValue::insert(const SourceRef &rhs) {
43 auto rhsVal = SourceRefLatticeValue(rhs);
44 if (isScalar()) {
45 return updateScalar(rhsVal.getScalarValue());
46 } else {
47 return foldAndUpdate(rhsVal);
48 }
49}
50
51std::pair<SourceRefLatticeValue, mlir::ChangeResult>
53 auto newVal = *this;
54 auto res = mlir::ChangeResult::NoChange;
55 if (newVal.isScalar()) {
56 res = newVal.translateScalar(translation);
57 } else {
58 for (auto &elem : newVal.getArrayValue()) {
59 auto [newElem, elemRes] = elem->translate(translation);
60 (*elem) = newElem;
61 res |= elemRes;
62 }
63 }
64 return {newVal, res};
65}
66
67mlir::FailureOr<std::pair<SourceRefLatticeValue, mlir::ChangeResult>>
69 SourceRefIndex idx(std::move(memberRef));
70 auto transform = [&idx](const SourceRef &r) -> mlir::FailureOr<SourceRef> {
71 return r.createChild(idx);
72 };
73 return elementwiseTransform(transform);
74}
75
76mlir::FailureOr<std::pair<SourceRefLatticeValue, mlir::ChangeResult>>
77SourceRefLatticeValue::referencePodRecord(mlir::StringAttr recordName) const {
78 SourceRefIndex idx(recordName);
79 auto transform = [&idx](const SourceRef &r) -> mlir::FailureOr<SourceRef> {
80 return r.createChild(idx);
81 };
82 return elementwiseTransform(transform);
83}
84
85mlir::FailureOr<std::pair<SourceRefLatticeValue, mlir::ChangeResult>>
86SourceRefLatticeValue::extract(const std::vector<SourceRefIndex> &indices) const {
87 if (isArray()) {
88 ensure(indices.size() <= getNumArrayDims(), "invalid extract array operands");
89
90 // First, compute what chunk(s) to index
91 std::vector<size_t> currIdxs {0};
92 for (unsigned i = 0; i < indices.size(); i++) {
93 const auto &idx = indices[i];
94 auto currDim = getArrayDim(i);
95
96 std::vector<size_t> newIdxs;
97 ensure(idx.isIndex() || idx.isIndexRange(), "wrong type of index for array");
98 if (idx.isIndex()) {
99 int64_t idxVal(idx.getIndex());
100 std::transform(
101 currIdxs.begin(), currIdxs.end(), std::back_inserter(newIdxs),
102 [&currDim, &idxVal](size_t j) { return j * currDim + idxVal; }
103 );
104 } else {
105 auto [low, high] = idx.getIndexRange();
106 int64_t lowInt(low), highInt(high);
107 for (int64_t idxVal = lowInt; idxVal < highInt; idxVal++) {
108 std::transform(
109 currIdxs.begin(), currIdxs.end(), std::back_inserter(newIdxs),
110 [&currDim, &idxVal](size_t j) { return j * currDim + idxVal; }
111 );
112 }
113 }
114
115 currIdxs = newIdxs;
116 }
117 std::vector<int64_t> newArrayDims;
118 size_t chunkSz = 1;
119 for (size_t i = indices.size(); i < getNumArrayDims(); i++) {
120 auto dim = getArrayDim(i);
121 newArrayDims.push_back(dim);
122 chunkSz *= dim;
123 }
124 if (newArrayDims.empty()) {
125 // read case, where the return value is a scalar (single element)
126 SourceRefLatticeValue extractedVal;
127 for (auto idx : currIdxs) {
128 (void)extractedVal.update(getElemFlatIdx(idx));
129 }
130 return std::make_pair(extractedVal, mlir::ChangeResult::Change);
131 } else {
132 // extract case, where the return value is an array of fewer dimensions.
133 SourceRefLatticeValue extractedVal(newArrayDims);
134 for (auto chunkStart : currIdxs) {
135 for (size_t i = 0; i < chunkSz; i++) {
136 (void)extractedVal.getElemFlatIdx(i).update(getElemFlatIdx(chunkStart + i));
137 }
138 }
139 return std::make_pair(extractedVal, mlir::ChangeResult::Change);
140 }
141 } else {
142 auto currVal = *this;
143 auto res = mlir::ChangeResult::NoChange;
144 for (const auto &idx : indices) {
145 auto transform = [&idx](const SourceRef &r) -> mlir::FailureOr<SourceRef> {
146 return r.createChild(idx);
147 };
148 auto transformedVal = currVal.elementwiseTransform(transform);
149 if (failed(transformedVal)) {
150 return mlir::failure();
151 }
152 auto [newVal, transformRes] = *transformedVal;
153 currVal = std::move(newVal);
154 res |= transformRes;
155 }
156 return std::make_pair(currVal, res);
157 }
158}
159
160mlir::ChangeResult SourceRefLatticeValue::translateScalar(const TranslationMap &translation) {
161 auto res = mlir::ChangeResult::NoChange;
162 // copy the current value
163 auto currVal = getScalarValue();
164 // reset this value
165 getValue() = ScalarTy();
166 // For each current element, see if the translation map contains a valid prefix.
167 // If so, translate the current element with all replacement prefixes indicated
168 // by the translation value.
169 for (const SourceRef &currRef : currVal) {
170 for (const auto &[prefix, replacementVal] : translation) {
171 if (currRef.isValidPrefix(prefix)) {
172 for (const SourceRef &replacementPrefix : replacementVal.foldToScalar()) {
173 auto translatedRefRes = currRef.translate(prefix, replacementPrefix);
174 if (succeeded(translatedRefRes)) {
175 res |= insert(*translatedRefRes);
176 }
177 }
178 }
179 }
180 }
181 return res;
182}
183
184mlir::FailureOr<std::pair<SourceRefLatticeValue, mlir::ChangeResult>>
186 llvm::function_ref<mlir::FailureOr<SourceRef>(const SourceRef &)> transform
187) const {
188 auto newVal = *this;
189 auto res = mlir::ChangeResult::NoChange;
190 if (newVal.isScalar()) {
191 ScalarTy indexed;
192 for (const auto &ref : newVal.getScalarValue()) {
193 auto transformedRef = transform(ref);
194 if (failed(transformedRef)) {
195 return mlir::failure();
196 }
197 auto [_, inserted] = indexed.insert(*transformedRef);
198 if (inserted) {
199 res |= mlir::ChangeResult::Change;
200 }
201 }
202 newVal.getScalarValue() = indexed;
203 } else {
204 for (auto &elem : newVal.getArrayValue()) {
205 auto transformedElem = elem->elementwiseTransform(transform);
206 if (failed(transformedElem)) {
207 return mlir::failure();
208 }
209 auto [newElem, elemRes] = *transformedElem;
210 (*elem) = std::move(newElem);
211 res |= elemRes;
212 }
213 }
214 return std::make_pair(newVal, res);
215}
216
217mlir::raw_ostream &operator<<(mlir::raw_ostream &os, const SourceRefLatticeValue &v) {
218 v.print(os);
219 return os;
220}
221
222/* SourceRefLattice */
223
224mlir::FailureOr<SourceRef> SourceRefLattice::getSourceRef(mlir::Value val) {
225 if (auto blockArg = llvm::dyn_cast<mlir::BlockArgument>(val)) {
226 return SourceRef(blockArg);
227 } else if (auto *defOp = val.getDefiningOp()) {
228 if (auto feltConst = llvm::dyn_cast<FeltConstantOp>(defOp)) {
229 return SourceRef(feltConst);
230 } else if (auto constIdx = llvm::dyn_cast<mlir::arith::ConstantIndexOp>(defOp)) {
231 return SourceRef(constIdx);
232 } else if (auto readConst = llvm::dyn_cast<ConstReadOp>(defOp)) {
233 return SourceRef(readConst);
234 } else if (auto structNew = llvm::dyn_cast<CreateStructOp>(defOp)) {
235 return SourceRef(structNew);
236 } else if (auto nonDet = llvm::dyn_cast<NonDetOp>(defOp)) {
237 return SourceRef(nonDet);
238 } else if (auto createArray = llvm::dyn_cast<CreateArrayOp>(defOp)) {
239 return SourceRef(createArray->getResult(0));
240 } else if (auto newPod = llvm::dyn_cast<NewPodOp>(defOp)) {
241 return SourceRef(newPod->getResult(0));
242 } else if (llvm::isa<function::CallOp>(defOp)) {
243 auto callResult = llvm::dyn_cast<mlir::OpResult>(val);
244 ensure(callResult != nullptr, "function.call value should be an OpResult");
245 return SourceRef(callResult);
246 }
247 }
248 return mlir::failure();
249}
250
252 if (auto asVal = llvm::dyn_cast_if_present<Value>(v)) {
253 auto sourceRef = getSourceRef(asVal);
254 if (mlir::succeeded(sourceRef)) {
255 return SourceRefLatticeValue(*sourceRef);
256 }
257 }
258 return SourceRefLatticeValue();
259}
260
261ChangeResult SourceRefLattice::join(const AbstractSparseLattice &rhs) {
262 return value.update(static_cast<const SourceRefLattice &>(rhs).value);
263}
264
265ChangeResult SourceRefLattice::meet(const AbstractSparseLattice & /*rhs*/) {
266 llvm::report_fatal_error("meet operation is not supported for SourceRefLattice");
267 return ChangeResult::NoChange;
268}
269
270void SourceRefLattice::print(mlir::raw_ostream &os) const {
271 os << "SourceRefLattice { " << value << " }";
272}
273
274ChangeResult SourceRefLattice::setValue(const LatticeValue &newValue) {
275 return value.setValue(newValue);
276}
277
278ChangeResult SourceRefLattice::setValue(const SourceRef &ref) {
279 return value.setValue(LatticeValue(ref));
280}
281
282} // namespace llzk
283
284namespace llvm {
285
286raw_ostream &operator<<(raw_ostream &os, llvm::PointerUnion<mlir::Value, mlir::Operation *> ptr) {
287 if (auto asVal = llvm::dyn_cast_if_present<Value>(ptr)) {
288 os << asVal;
289 } else if (auto *asOp = llvm::dyn_cast_if_present<Operation *>(ptr)) {
290 os << *asOp;
291 } else {
292 os << "<<null PointerUnion>>";
293 }
294 return os;
295}
296} // namespace llvm
Defines an index into an LLZK object.
Definition SourceRef.h:43
A value at a given point of the SourceRefLattice.
mlir::FailureOr< std::pair< SourceRefLatticeValue, mlir::ChangeResult > > referencePodRecord(mlir::StringAttr recordName) const
Add the given pod recordName to the SourceRefs contained within this value.
mlir::FailureOr< std::pair< SourceRefLatticeValue, mlir::ChangeResult > > referenceMember(SymbolLookupResult< component::MemberDefOp > memberRef) const
Add the given memberRef to the SourceRefs contained within this value.
virtual mlir::FailureOr< std::pair< SourceRefLatticeValue, mlir::ChangeResult > > elementwiseTransform(llvm::function_ref< mlir::FailureOr< SourceRef >(const SourceRef &)> transform) const
Perform a recursive transformation over all elements of this value and return a new value with the mo...
mlir::ChangeResult insert(const SourceRef &rhs)
Directly insert the ref into this value.
std::pair< SourceRefLatticeValue, mlir::ChangeResult > translate(const TranslationMap &translation) const
For the refs contained in this value, translate them given the translation map and return the transfo...
mlir::FailureOr< std::pair< SourceRefLatticeValue, mlir::ChangeResult > > extract(const std::vector< SourceRefIndex > &indices) const
Perform an array.extract or array.read operation, depending on how many indices are provided.
mlir::ChangeResult translateScalar(const TranslationMap &translation)
Translate this value using the translation map, assuming this value is a scalar.
Sparse SSA-value lattice for SourceRef propagation.
mlir::ChangeResult join(const AbstractSparseLattice &rhs) override
mlir::ChangeResult setValue(const LatticeValue &newValue)
mlir::ChangeResult meet(const AbstractSparseLattice &rhs) override
static SourceRefLatticeValue getDefaultValue(ValueTy v)
void print(mlir::raw_ostream &os) const override
static mlir::FailureOr< SourceRef > getSourceRef(mlir::Value val)
If val is the source of other values (i.e., a block argument, an allocation-like op result,...
llvm::PointerUnion< mlir::Value, mlir::Operation * > ValueTy
SourceRefLatticeValue LatticeValue
A reference to a "source", which is the base value from which other SSA values are derived.
Definition SourceRef.h:146
mlir::ChangeResult update(const Derived &rhs)
Union this value with that of rhs.
void print(mlir::raw_ostream &os) const
raw_ostream & operator<<(raw_ostream &os, llvm::PointerUnion< mlir::Value, mlir::Operation * > ptr)
void ensure(bool condition, const llvm::Twine &errMsg)
Interval operator<<(const Interval &lhs, const Interval &rhs)
std::unordered_map< SourceRef, SourceRefLatticeValue, SourceRef::Hash > TranslationMap