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
18#include "llzk/Util/Hash.h"
20
21#include <mlir/Analysis/DataFlow/DeadCodeAnalysis.h>
22#include <mlir/IR/Value.h>
23
24#include <llvm/Support/Debug.h>
25
26#include <numeric>
27#include <unordered_set>
28
29#define DEBUG_TYPE "llzk-constrain-ref-lattice"
30
31using namespace mlir;
32
33namespace llzk {
34
35using namespace array;
36using namespace component;
37using namespace felt;
38using namespace pod;
39using namespace polymorphic;
40
41/* SourceRefLatticeValue */
42
43mlir::ChangeResult SourceRefLatticeValue::insert(const SourceRef &rhs) {
44 auto rhsVal = SourceRefLatticeValue(rhs);
45 if (isScalar()) {
46 return updateScalar(rhsVal.getScalarValue());
47 } else {
48 return foldAndUpdate(rhsVal);
49 }
50}
51
52mlir::ChangeResult SourceRefLatticeValue::remove(const SourceRef &ref) {
53 if (isScalar()) {
54 return getScalarValue().erase(ref) != 0 ? mlir::ChangeResult::Change
55 : mlir::ChangeResult::NoChange;
56 }
57
58 mlir::ChangeResult changed = mlir::ChangeResult::NoChange;
59 for (auto &element : getArrayValue()) {
60 changed |= element->remove(ref);
61 }
62 return changed;
63}
64
65std::pair<SourceRefLatticeValue, mlir::ChangeResult>
67 auto newVal = *this;
68 auto res = mlir::ChangeResult::NoChange;
69 if (newVal.isScalar()) {
70 res = newVal.translateScalar(translation);
71 } else {
72 for (auto &elem : newVal.getArrayValue()) {
73 auto [newElem, elemRes] = elem->translate(translation);
74 (*elem) = newElem;
75 res |= elemRes;
76 }
77 }
78 return {newVal, res};
79}
80
81std::pair<SourceRefLatticeValue, mlir::ChangeResult>
83 auto newVal = *this;
84 auto res = mlir::ChangeResult::NoChange;
85 if (newVal.isScalar()) {
86 res = newVal.replacePrefixesScalar(translation);
87 } else {
88 for (auto &elem : newVal.getArrayValue()) {
89 auto [newElem, elemRes] = elem->replacePrefixes(translation);
90 *elem = std::move(newElem);
91 res |= elemRes;
92 }
93 }
94 return {newVal, res};
95}
96
98 const std::vector<SourceRefIndex> &indices, const SourceRefLatticeValue &rhs,
99 bool joinWithExisting
100) {
101 ensure(isArray(), "SourceRef array write requires an array-shaped value");
102 ensure(indices.size() <= getNumArrayDims(), "invalid SourceRef array write indices");
103
104 std::vector<size_t> selected {0};
105 bool hasRangedIndex = false;
106 for (unsigned dimIdx = 0; dimIdx < indices.size(); ++dimIdx) {
107 const SourceRefIndex &idx = indices[dimIdx];
108 const int64_t dim = getArrayDim(dimIdx);
109 std::vector<size_t> next;
110 if (idx.isIndex()) {
111 const int64_t indexValue(idx.getIndex());
112 for (size_t prefix : selected) {
113 next.push_back(prefix * dim + indexValue);
114 }
115 } else {
116 ensure(idx.isIndexRange(), "wrong type of index for SourceRef array write");
117 hasRangedIndex = true;
118 auto [low, high] = idx.getIndexRange();
119 const int64_t lowValue(low), highValue(high);
120 for (int64_t indexValue = lowValue; indexValue < highValue; ++indexValue) {
121 for (size_t prefix : selected) {
122 next.push_back(prefix * dim + indexValue);
123 }
124 }
125 }
126 selected = std::move(next);
127 }
128
129 ArrayRef<int64_t> selectedShape(getArrayShape());
130 selectedShape = selectedShape.drop_front(indices.size());
131 size_t chunkSize = 1;
132 for (int64_t dim : selectedShape) {
133 chunkSize *= static_cast<size_t>(dim);
134 }
135 ensure(
136 (selectedShape.empty() && rhs.isScalar()) ||
137 (rhs.isArray() && ArrayRef<int64_t>(rhs.getArrayShape()) == selectedShape),
138 "SourceRef array write value shape does not match selected storage"
139 );
140
141 ChangeResult changed = ChangeResult::NoChange;
142 const bool join = joinWithExisting || hasRangedIndex || selected.size() > 1;
143 for (size_t chunkStart : selected) {
144 for (size_t offset = 0; offset < chunkSize; ++offset) {
145 SourceRefLatticeValue &dest = getElemFlatIdx(chunkStart * chunkSize + offset);
146 const SourceRefLatticeValue &source = rhs.isScalar() ? rhs : rhs.getElemFlatIdx(offset);
147 changed |= join ? dest.update(source) : dest.setValue(source);
148 }
149 }
150 return changed;
151}
152
153mlir::FailureOr<std::pair<SourceRefLatticeValue, mlir::ChangeResult>>
155 SourceRefIndex idx(std::move(memberRef));
156 auto transform = [&idx](const SourceRef &r) -> mlir::FailureOr<SourceRef> {
157 return r.createChild(idx);
158 };
159 return elementwiseTransform(transform);
160}
161
162mlir::FailureOr<std::pair<SourceRefLatticeValue, mlir::ChangeResult>>
163SourceRefLatticeValue::referencePodRecord(mlir::StringAttr recordName) const {
164 SourceRefIndex idx(recordName);
165 auto transform = [&idx](const SourceRef &r) -> mlir::FailureOr<SourceRef> {
166 return r.createChild(idx);
167 };
168 return elementwiseTransform(transform);
169}
170
171mlir::FailureOr<std::pair<SourceRefLatticeValue, mlir::ChangeResult>>
172SourceRefLatticeValue::extract(const std::vector<SourceRefIndex> &indices) const {
173 if (isArray()) {
174 ensure(indices.size() <= getNumArrayDims(), "invalid extract array operands");
175
176 // First, compute what chunk(s) to index
177 std::vector<size_t> currIdxs {0};
178 for (unsigned i = 0; i < indices.size(); i++) {
179 const auto &idx = indices[i];
180 auto currDim = getArrayDim(i);
181
182 std::vector<size_t> newIdxs;
183 ensure(idx.isIndex() || idx.isIndexRange(), "wrong type of index for array");
184 if (idx.isIndex()) {
185 int64_t idxVal(idx.getIndex());
186 std::transform(
187 currIdxs.begin(), currIdxs.end(), std::back_inserter(newIdxs),
188 [&currDim, &idxVal](size_t j) { return j * currDim + idxVal; }
189 );
190 } else {
191 auto [low, high] = idx.getIndexRange();
192 int64_t lowInt(low), highInt(high);
193 for (int64_t idxVal = lowInt; idxVal < highInt; idxVal++) {
194 std::transform(
195 currIdxs.begin(), currIdxs.end(), std::back_inserter(newIdxs),
196 [&currDim, &idxVal](size_t j) { return j * currDim + idxVal; }
197 );
198 }
199 }
200
201 currIdxs = newIdxs;
202 }
203 std::vector<int64_t> newArrayDims;
204 size_t chunkSz = 1;
205 for (size_t i = indices.size(); i < getNumArrayDims(); i++) {
206 auto dim = getArrayDim(i);
207 newArrayDims.push_back(dim);
208 chunkSz *= dim;
209 }
210 if (newArrayDims.empty()) {
211 // read case, where the return value is a scalar (single element)
212 SourceRefLatticeValue extractedVal;
213 for (auto idx : currIdxs) {
214 (void)extractedVal.update(getElemFlatIdx(idx));
215 }
216 return std::make_pair(extractedVal, mlir::ChangeResult::Change);
217 } else {
218 // extract case, where the return value is an array of fewer dimensions.
219 SourceRefLatticeValue extractedVal(newArrayDims);
220 for (auto chunkStart : currIdxs) {
221 for (size_t i = 0; i < chunkSz; i++) {
222 (void)extractedVal.getElemFlatIdx(i).update(getElemFlatIdx(chunkStart * chunkSz + i));
223 }
224 }
225 return std::make_pair(extractedVal, mlir::ChangeResult::Change);
226 }
227 } else {
228 auto currVal = *this;
229 auto res = mlir::ChangeResult::NoChange;
230 for (const auto &idx : indices) {
231 auto transform = [&idx](const SourceRef &r) -> mlir::FailureOr<SourceRef> {
232 return r.createChild(idx);
233 };
234 auto transformedVal = currVal.elementwiseTransform(transform);
235 if (failed(transformedVal)) {
236 return mlir::failure();
237 }
238 auto [newVal, transformRes] = *transformedVal;
239 currVal = std::move(newVal);
240 res |= transformRes;
241 }
242 return std::make_pair(currVal, res);
243 }
244}
245
246mlir::ChangeResult SourceRefLatticeValue::translateScalar(const TranslationMap &translation) {
247 auto res = mlir::ChangeResult::NoChange;
248 // copy the current value
249 auto currVal = getScalarValue();
250 // reset this value
251 getValue() = ScalarTy();
252 // For each current element, see if the translation map contains a valid prefix.
253 // If so, translate the current element with all replacement prefixes indicated
254 // by the translation value.
255 for (const SourceRef &currRef : currVal) {
256 for (const auto &[prefix, replacementVal] : translation) {
257 if (currRef.isValidPrefix(prefix)) {
258 for (const SourceRef &replacementPrefix : replacementVal.foldToScalar()) {
259 auto translatedRefRes = currRef.translate(prefix, replacementPrefix);
260 if (succeeded(translatedRefRes)) {
261 res |= insert(*translatedRefRes);
262 }
263 }
264 }
265 }
266 }
267 return res;
268}
269
270mlir::ChangeResult SourceRefLatticeValue::replacePrefixesScalar(const TranslationMap &translation) {
271 const ScalarTy current = getScalarValue();
272 ScalarTy replaced;
273 for (const SourceRef &currentRef : current) {
274 bool matched = false;
275 for (const auto &[prefix, replacementVal] : translation) {
276 if (!currentRef.isValidPrefix(prefix)) {
277 continue;
278 }
279 matched = true;
280 for (const SourceRef &replacementPrefix : replacementVal.foldToScalar()) {
281 auto translated = currentRef.translate(prefix, replacementPrefix);
282 if (succeeded(translated)) {
283 replaced.insert(*translated);
284 }
285 }
286 }
287 if (!matched) {
288 replaced.insert(currentRef);
289 }
290 }
291 if (replaced == current) {
292 return ChangeResult::NoChange;
293 }
294 getValue() = std::move(replaced);
295 return ChangeResult::Change;
296}
297
298mlir::FailureOr<std::pair<SourceRefLatticeValue, mlir::ChangeResult>>
300 llvm::function_ref<mlir::FailureOr<SourceRef>(const SourceRef &)> transform
301) const {
302 auto newVal = *this;
303 auto res = mlir::ChangeResult::NoChange;
304 if (newVal.isScalar()) {
305 ScalarTy indexed;
306 for (const auto &ref : newVal.getScalarValue()) {
307 auto transformedRef = transform(ref);
308 if (failed(transformedRef)) {
309 return mlir::failure();
310 }
311 auto [_, inserted] = indexed.insert(*transformedRef);
312 if (inserted) {
313 res |= mlir::ChangeResult::Change;
314 }
315 }
316 newVal.getScalarValue() = indexed;
317 } else {
318 for (auto &elem : newVal.getArrayValue()) {
319 auto transformedElem = elem->elementwiseTransform(transform);
320 if (failed(transformedElem)) {
321 return mlir::failure();
322 }
323 auto [newElem, elemRes] = *transformedElem;
324 (*elem) = std::move(newElem);
325 res |= elemRes;
326 }
327 }
328 return std::make_pair(newVal, res);
329}
330
331mlir::raw_ostream &operator<<(mlir::raw_ostream &os, const SourceRefLatticeValue &v) {
332 v.print(os);
333 return os;
334}
335
336/* SourceRefLattice */
337
338mlir::FailureOr<SourceRef> SourceRefLattice::getSourceRef(mlir::Value val) {
339 if (auto blockArg = llvm::dyn_cast<mlir::BlockArgument>(val)) {
340 return SourceRef(blockArg);
341 } else if (auto *defOp = val.getDefiningOp()) {
342 if (auto feltConst = llvm::dyn_cast<FeltConstantOp>(defOp)) {
343 return SourceRef(feltConst);
344 } else if (auto constIdx = llvm::dyn_cast<mlir::arith::ConstantIndexOp>(defOp)) {
345 return SourceRef(constIdx);
346 } else if (auto readConst = llvm::dyn_cast<ConstReadOp>(defOp)) {
347 return SourceRef(readConst);
348 } else if (auto structNew = llvm::dyn_cast<CreateStructOp>(defOp)) {
349 return SourceRef(structNew);
350 } else if (auto nonDet = llvm::dyn_cast<NonDetOp>(defOp)) {
351 return SourceRef(nonDet);
352 } else if (llvm::isa<global::GlobalReadOp>(defOp)) {
353 return SourceRef(llvm::cast<mlir::OpResult>(val));
354 } else if (auto createArray = llvm::dyn_cast<CreateArrayOp>(defOp)) {
355 return SourceRef(createArray->getResult(0));
356 } else if (auto newPod = llvm::dyn_cast<NewPodOp>(defOp)) {
357 return SourceRef(newPod->getResult(0));
358 } else if (llvm::isa<function::CallOp>(defOp)) {
359 auto callResult = llvm::dyn_cast<mlir::OpResult>(val);
360 ensure(callResult != nullptr, "function.call value should be an OpResult");
361 return SourceRef(callResult);
362 }
363 }
364 return mlir::failure();
365}
366
368 if (auto asVal = llvm::dyn_cast_if_present<Value>(v)) {
369 auto sourceRef = getSourceRef(asVal);
370 if (mlir::succeeded(sourceRef)) {
371 return SourceRefLatticeValue(*sourceRef);
372 }
373 }
374 return SourceRefLatticeValue();
375}
376
377ChangeResult SourceRefLattice::join(const AbstractSparseLattice &rhs) {
378 const auto &rhsValue = static_cast<const SourceRefLattice &>(rhs).value;
379 // Region-branch block arguments and results start with an empty scalar state (bottom) so their
380 // identity can come from incoming values. Do not let a join with bottom fold an array to a
381 // scalar, regardless of which side was initialized first. Preserving the shape is necessary
382 // for element-sensitive reads after an scf.for/scf.while join.
383 if (rhsValue.isScalar() && rhsValue.getScalarValue().empty()) {
384 return ChangeResult::NoChange;
385 }
386 if (value.isScalar() && value.getScalarValue().empty()) {
387 return value.setValue(rhsValue);
388 }
389 return value.update(rhsValue);
390}
391
392ChangeResult SourceRefLattice::meet(const AbstractSparseLattice & /*rhs*/) {
393 llvm::report_fatal_error("meet operation is not supported for SourceRefLattice");
394 return ChangeResult::NoChange;
395}
396
397void SourceRefLattice::print(mlir::raw_ostream &os) const {
398 os << "SourceRefLattice { " << value << " }";
399}
400
401ChangeResult SourceRefLattice::setValue(const LatticeValue &newValue) {
402 return value.setValue(newValue);
403}
404
405ChangeResult SourceRefLattice::setValue(const SourceRef &ref) {
406 return value.setValue(LatticeValue(ref));
407}
408
409} // namespace llzk
410
411namespace llvm {
412
413raw_ostream &operator<<(raw_ostream &os, llvm::PointerUnion<mlir::Value, mlir::Operation *> ptr) {
414 if (auto asVal = llvm::dyn_cast_if_present<Value>(ptr)) {
415 os << asVal;
416 } else if (auto *asOp = llvm::dyn_cast_if_present<Operation *>(ptr)) {
417 os << *asOp;
418 } else {
419 os << "<<null PointerUnion>>";
420 }
421 return os;
422}
423} // namespace llvm
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation source
Definition LICENSE.txt:28
Defines an index into an LLZK object.
Definition SourceRef.h:43
bool isIndexRange() const
Definition SourceRef.h:82
bool isIndex() const
Definition SourceRef.h:76
llvm::DynamicAPInt getIndex() const
Definition SourceRef.h:77
IndexRange getIndexRange() const
Definition SourceRef.h:83
A value at a given point of the SourceRefLattice.
mlir::ChangeResult write(const std::vector< SourceRefIndex > &indices, const SourceRefLatticeValue &rhs, bool joinWithExisting=false)
Update the element or subarray selected by indices.
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::ChangeResult remove(const SourceRef &ref)
Remove ref from this value's reference set or, for an array, from every element.
mlir::ChangeResult replacePrefixesScalar(const TranslationMap &translation)
Replace matching prefixes in a scalar value without dropping unmatched references.
const std::vector< int64_t > & getArrayShape() const
Return the dimensions of this array-shaped 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
Translate contained references using translation and return the transformed value.
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.
std::pair< SourceRefLatticeValue, mlir::ChangeResult > replacePrefixes(const TranslationMap &translation) const
Replace matching SourceRef prefixes and leave unmatched references unchanged.
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 setValue(const AbstractLatticeValue &rhs)
Sets this value to be equal to rhs.
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