LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
SourceRef.h
Go to the documentation of this file.
1//===-- SourceRef.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//===----------------------------------------------------------------------===//
9
10#pragma once
11
22#include "llzk/Util/Hash.h"
23
24#include <mlir/Analysis/DataFlowFramework.h>
25#include <mlir/Dialect/Arith/IR/Arith.h>
26#include <mlir/Pass/AnalysisManager.h>
27
28#include <llvm/ADT/ArrayRef.h>
29#include <llvm/ADT/DynamicAPInt.h>
30#include <llvm/ADT/EquivalenceClasses.h>
31#include <llvm/ADT/TypeSwitch.h>
32
33#include <compare>
34#include <unordered_set>
35#include <variant>
36#include <vector>
37
38namespace llzk {
39
44 using IndexRange = std::pair<llvm::DynamicAPInt, llvm::DynamicAPInt>;
45
46public:
47 explicit SourceRefIndex(component::MemberDefOp f) : index(f) {}
49 explicit SourceRefIndex(mlir::StringAttr recordName) : index(recordName) {}
50 explicit SourceRefIndex(const llvm::DynamicAPInt &i) : index(i) {}
51 explicit SourceRefIndex(int64_t i) : index(llvm::DynamicAPInt(i)) {}
53 SourceRefIndex(const llvm::DynamicAPInt &low, const llvm::DynamicAPInt &high)
54 : index(IndexRange {low, high}) {}
55 explicit SourceRefIndex(IndexRange r) : index(r) {}
56
58 static SourceRefIndex forArrayDimension(int64_t size);
59
61 bool hasUnboundedUpperBound() const;
62
63 bool isMember() const {
64 return std::holds_alternative<SymbolLookupResult<component::MemberDefOp>>(index) ||
65 std::holds_alternative<component::MemberDefOp>(index);
66 }
68 ensure(isMember(), "SourceRefIndex: member requested but not contained");
69 if (std::holds_alternative<component::MemberDefOp>(index)) {
70 return std::get<component::MemberDefOp>(index);
71 }
72 return std::get<SymbolLookupResult<component::MemberDefOp>>(index).get();
73 }
74
75 bool isPodRecord() const { return std::holds_alternative<mlir::StringAttr>(index); }
76 mlir::StringAttr getPodRecordNameAttr() const {
77 ensure(isPodRecord(), "SourceRefIndex: pod record requested but not contained");
78 return std::get<mlir::StringAttr>(index);
79 }
80 llvm::StringRef getPodRecordName() const { return getPodRecordNameAttr().getValue(); }
81
82 bool isIndex() const { return std::holds_alternative<llvm::DynamicAPInt>(index); }
83 llvm::DynamicAPInt getIndex() const {
84 ensure(isIndex(), "SourceRefIndex: index requested but not contained");
85 return std::get<llvm::DynamicAPInt>(index);
86 }
87
88 bool isIndexRange() const { return std::holds_alternative<IndexRange>(index); }
89 IndexRange getIndexRange() const {
90 ensure(isIndexRange(), "SourceRefIndex: index range requested but not contained");
91 return std::get<IndexRange>(index);
92 }
93
94 inline void dump() const { print(llvm::errs()); }
95 void print(mlir::raw_ostream &os) const;
96
97 inline bool operator==(const SourceRefIndex &rhs) const {
98 if (isMember() && rhs.isMember()) {
99 // We compare the underlying members, since the member could be in a symbol
100 // lookup or not.
101 return getMember() == rhs.getMember();
102 }
103 if (isIndex() && rhs.isIndex()) {
104 return getIndex() == rhs.getIndex();
105 }
106 return index == rhs.index;
107 }
108
111 bool overlaps(const SourceRefIndex &rhs) const;
112
113 std::strong_ordering operator<=>(const SourceRefIndex &rhs) const;
114
115 struct Hash {
116 size_t operator()(const SourceRefIndex &c) const;
117 };
118
119 inline size_t getHash() const { return Hash {}(*this); }
120
121private:
129 std::variant<
131 llvm::DynamicAPInt, IndexRange>
132 index;
133};
134
135static inline mlir::raw_ostream &operator<<(mlir::raw_ostream &os, const SourceRefIndex &rhs) {
136 rhs.print(os);
137 return os;
138}
139
152class SourceRef {
153public:
154 using Path = std::vector<SourceRefIndex>;
155
156private:
157 // Sort in the following order:
158 // block arg < struct.new < nondet < other rooted result < template const < const index <
159 // const felt.
160 enum class SortCategory : std::uint8_t {
161 BlockArgument,
162 CreateStruct,
163 NonDet,
164 RootResult,
165 TemplateConstant,
166 ConstantIndex,
167 ConstantFelt,
168 };
169
170 template <typename OpT> static mlir::Value getSingleResultValue(OpT op) {
171 ensure(op, "SourceRef requires a non-null operation");
172 ensure(op->getNumResults() == 1, "SourceRef expects a single-result operation");
173 return op->getResult(0);
174 }
175
176 static mlir::Value getRootResultValue(mlir::OpResult result) {
177 ensure(
178 !llvm::isa<
179 felt::FeltConstantOp, mlir::arith::ConstantIndexOp, mlir::arith::ConstantIntOp,
180 polymorphic::ConstReadOp>(result.getOwner()),
181 "SourceRef rooted OpResult constructors must not be used for constant values"
182 );
183 return result;
184 }
185
186 template <typename OpT> mlir::FailureOr<OpT> getDefiningOp() const {
187 if (auto op = llvm::dyn_cast_if_present<OpT>(value.getDefiningOp())) {
188 return op;
189 }
190 return mlir::failure();
191 }
192
193 SourceRef(mlir::Value sourceValue, bool isConstantStorage, Path sourcePath = {})
194 : value(sourceValue), path(std::move(sourcePath)), constant(isConstantStorage) {
195 ensure(value != nullptr, "SourceRef requires a non-null value");
196 ensure(!constant || this->path.empty(), "constant SourceRef cannot have a path");
197 }
198
199 Path &getPathMut() { return path; }
200 const void *getAsOpaquePointer() const { return value.getAsOpaquePointer(); }
201 SortCategory getSortCategory() const;
204 llvm::DynamicAPInt getConstantIdentityValue() const;
205 llvm::StringRef getTemplateConstantName() const;
206 std::strong_ordering compareWithinCategory(const SourceRef &rhs, SortCategory category) const;
207
208public:
210 static std::vector<SourceRef>
211 getAllSourceRefs(mlir::SymbolTableCollection &tables, mlir::ModuleOp mod, const SourceRef &root);
212
214 static std::vector<SourceRef>
216
219 static std::vector<SourceRef>
221
222 /* Rooted constructors */
223
224 SourceRef(mlir::BlockArgument b, Path p = {})
225 : SourceRef(b, /*isConstantStorage=*/false, std::move(p)) {}
227 : SourceRef(getSingleResultValue(createOp), /*isConstantStorage=*/false, std::move(p)) {}
228 SourceRef(NonDetOp nondet, Path p = {})
229 : SourceRef(getSingleResultValue(nondet), /*isConstantStorage=*/false, std::move(p)) {}
230 SourceRef(mlir::OpResult rootResult, Path p = {})
231 : SourceRef(getRootResultValue(rootResult), /*isConstantStorage=*/false, std::move(p)) {}
232
233 /* Constant constructors */
234
236 : SourceRef(getSingleResultValue(c), /*isConstantStorage=*/true) {}
237 explicit SourceRef(mlir::arith::ConstantIndexOp c)
238 : SourceRef(getSingleResultValue(c), /*isConstantStorage=*/true) {}
240 : SourceRef(getSingleResultValue(c), /*isConstantStorage=*/true) {}
241
242 mlir::Type getType() const;
243
244 bool isConstantFelt() const {
245 return isConstant() && llvm::isa_and_present<felt::FeltConstantOp>(value.getDefiningOp());
246 }
247 bool isConstantIndex() const {
248 return isConstant() &&
249 llvm::isa_and_present<mlir::arith::ConstantIndexOp>(value.getDefiningOp());
250 }
251
252 bool isTemplateConstant() const {
253 return isConstant() && llvm::isa_and_present<polymorphic::ConstReadOp>(value.getDefiningOp());
254 }
255
256 bool isConstant() const { return constant; }
257 bool isConstantInt() const { return isConstantFelt() || isConstantIndex(); }
258
259 bool isFeltVal() const { return llvm::isa<felt::FeltType>(getType()); }
260 bool isIndexVal() const { return llvm::isa<mlir::IndexType>(getType()); }
261 bool isIntegerVal() const { return llvm::isa<mlir::IntegerType>(getType()); }
262 bool isTypeVarVal() const { return llvm::isa<polymorphic::TypeVarType>(getType()); }
263 bool isScalar() const {
264 return isConstant() || isFeltVal() || isIndexVal() || isIntegerVal() || isTypeVarVal();
265 }
266
267 bool isRooted() const { return !constant; }
268
269 bool isBlockArgument() const { return isRooted() && llvm::isa<mlir::BlockArgument>(value); }
270 mlir::FailureOr<mlir::Value> getRoot() const {
271 if (isRooted()) {
272 return value;
273 }
274 return mlir::failure();
275 }
276 mlir::FailureOr<mlir::Value> getConstant() const {
277 if (isConstant()) {
278 return value;
279 }
280 return mlir::failure();
281 }
282 mlir::FailureOr<mlir::BlockArgument> getBlockArgument() const {
283 if (auto blockArg = llvm::dyn_cast<mlir::BlockArgument>(value)) {
284 return blockArg;
285 }
286 return mlir::failure();
287 }
288 mlir::FailureOr<unsigned> getInputNum() const {
289 auto blockArg = getBlockArgument();
290 if (succeeded(blockArg)) {
291 return blockArg->getArgNumber();
292 }
293 return mlir::failure();
294 }
295
296 bool isCreateStructOp() const { return succeeded(getCreateStructOp()); }
297 mlir::FailureOr<component::CreateStructOp> getCreateStructOp() const {
298 return getDefiningOp<component::CreateStructOp>();
299 }
300
301 bool isNonDetOp() const { return succeeded(getNonDetOp()); }
302 mlir::FailureOr<NonDetOp> getNonDetOp() const { return getDefiningOp<NonDetOp>(); }
303
304 bool isCallResult() const { return succeeded(getCallOp()); }
305 mlir::FailureOr<function::CallOp> getCallOp() const { return getDefiningOp<function::CallOp>(); }
306
308 mlir::FailureOr<llvm::DynamicAPInt> getConstantFeltValue() const {
309 auto feltConst = getDefiningOp<felt::FeltConstantOp>();
310 if (succeeded(feltConst)) {
311 if (auto reduced = feltConst->getValue().getReducedValue()) {
312 return *reduced;
313 }
314 }
315 return mlir::failure();
316 }
317 mlir::FailureOr<llvm::DynamicAPInt> getConstantIndexValue() const {
318 auto indexConst = getDefiningOp<mlir::arith::ConstantIndexOp>();
319 if (succeeded(indexConst)) {
320 return llvm::DynamicAPInt(indexConst->value());
321 }
322 return mlir::failure();
323 }
324
326 mlir::FailureOr<llvm::DynamicAPInt> getConstantValue(const Field &field) const;
327
329 mlir::FailureOr<llvm::DynamicAPInt> getConstantValue() const {
330 auto feltVal = getConstantFeltValue();
331 if (succeeded(feltVal)) {
332 return *feltVal;
333 }
334 auto indexVal = getConstantIndexValue();
335 if (succeeded(indexVal)) {
336 return *indexVal;
337 }
338 return mlir::failure();
339 }
340
342 bool isValidPrefix(const SourceRef &prefix) const;
343
345 bool overlaps(const SourceRef &rhs) const;
346
354 SourceRef narrowRanges(const SourceRef &rhs) const;
355
360 mlir::FailureOr<std::vector<SourceRefIndex>> getSuffix(const SourceRef &prefix) const;
361
368 mlir::FailureOr<SourceRef> translate(const SourceRef &prefix, const SourceRef &other) const;
369
371 mlir::FailureOr<SourceRef> getParentPrefix() const {
372 if (!isRooted() || getPath().empty()) {
373 return mlir::failure();
374 }
375 auto copy = *this;
376 copy.getPathMut().pop_back();
377 return copy;
378 }
379
381 std::vector<SourceRef>
382 getAllChildren(mlir::SymbolTableCollection &tables, mlir::ModuleOp mod) const;
383
384 mlir::FailureOr<SourceRef> createChild(const SourceRefIndex &r) const {
385 if (!isRooted()) {
386 return mlir::failure();
387 }
388 auto copy = *this;
389 copy.getPathMut().push_back(r);
390 return copy;
391 }
392
393 mlir::FailureOr<SourceRef> createChild(const SourceRef &other) const {
394 auto idxVal = other.getConstantIndexValue();
395 if (failed(idxVal)) {
396 return mlir::failure();
397 }
398 return createChild(SourceRefIndex(*idxVal));
399 }
400
401 [[deprecated("Use getPath() instead")]]
402 // NOTE: When this function is removed, do not delete it, rewrite as `... = delete`.
403 llvm::ArrayRef<SourceRefIndex> getPieces() const {
404 return path;
405 }
406 llvm::ArrayRef<SourceRefIndex> getPath() const { return path; }
407
410 void print(mlir::raw_ostream &os) const;
411 void dump() const { print(llvm::errs()); }
412
413 bool operator==(const SourceRef &rhs) const;
414
415 bool operator!=(const SourceRef &rhs) const { return !(*this == rhs); }
416
417 // required for EquivalenceClasses usage
418 std::strong_ordering operator<=>(const SourceRef &rhs) const;
419
420 struct Hash {
421 size_t operator()(const SourceRef &val) const;
422 };
423
424 friend struct llvm::DenseMapInfo<SourceRef>;
425
426private:
427 mlir::Value value;
428 Path path;
429 bool constant;
430};
431
432mlir::raw_ostream &operator<<(mlir::raw_ostream &os, const SourceRef &rhs);
433
434/* SourceRefSet */
435
436class SourceRefSet : public std::unordered_set<SourceRef, SourceRef::Hash> {
437 using Base = std::unordered_set<SourceRef, SourceRef::Hash>;
438
439public:
440 using Base::Base;
441
442 SourceRefSet &join(const SourceRefSet &rhs);
443
444 friend mlir::raw_ostream &operator<<(mlir::raw_ostream &os, const SourceRefSet &rhs);
445};
446
447static_assert(
449 "SourceRefSet must satisfy the ScalarLatticeValue requirements"
450);
451
452} // namespace llzk
453
454namespace llvm {
455
456template <> struct DenseMapInfo<llzk::SourceRef> {
457 static unsigned getHashValue(const llzk::SourceRef &ref) { return llzk::SourceRef::Hash {}(ref); }
458 static bool isEqual(const llzk::SourceRef &lhs, const llzk::SourceRef &rhs) { return lhs == rhs; }
459};
460
461} // namespace llvm
This file implements helpers for DynamicAPInt operations and conversions that LLVM does not provide.
Information about the prime finite field used for the interval analysis.
Definition Field.h:36
void print(llvm::raw_ostream &os) const
Defines an index into an LLZK object.
Definition SourceRef.h:43
std::strong_ordering operator<=>(const SourceRefIndex &rhs) const
bool operator==(const SourceRefIndex &rhs) const
Definition SourceRef.h:97
bool isIndexRange() const
Definition SourceRef.h:88
size_t getHash() const
Definition SourceRef.h:119
bool isIndex() const
Definition SourceRef.h:82
mlir::StringAttr getPodRecordNameAttr() const
Definition SourceRef.h:76
bool isMember() const
Definition SourceRef.h:63
bool isPodRecord() const
Definition SourceRef.h:75
SourceRefIndex(const llvm::DynamicAPInt &i)
Definition SourceRef.h:50
static SourceRefIndex forArrayDimension(int64_t size)
Select every nonnegative index in a static or dynamic array dimension.
bool hasUnboundedUpperBound() const
Whether this range has no finite upper bound.
void dump() const
Definition SourceRef.h:94
llvm::DynamicAPInt getIndex() const
Definition SourceRef.h:83
void print(mlir::raw_ostream &os) const
IndexRange getIndexRange() const
Definition SourceRef.h:89
bool overlaps(const SourceRefIndex &rhs) const
Return true when these path components select any common storage.
component::MemberDefOp getMember() const
Definition SourceRef.h:67
SourceRefIndex(mlir::StringAttr recordName)
Definition SourceRef.h:49
SourceRefIndex(const llvm::DynamicAPInt &low, const llvm::DynamicAPInt &high)
Construct a half-open range; a high bound of ShapedType::kDynamic means unbounded.
Definition SourceRef.h:53
SourceRefIndex(SymbolLookupResult< component::MemberDefOp > f)
Definition SourceRef.h:48
SourceRefIndex(IndexRange r)
Definition SourceRef.h:55
SourceRefIndex(int64_t i)
Definition SourceRef.h:51
SourceRefIndex(component::MemberDefOp f)
Definition SourceRef.h:47
llvm::StringRef getPodRecordName() const
Definition SourceRef.h:80
SourceRefSet & join(const SourceRefSet &rhs)
friend mlir::raw_ostream & operator<<(mlir::raw_ostream &os, const SourceRefSet &rhs)
A reference to a "source", which is the base value from which other SSA values are derived.
Definition SourceRef.h:152
bool isIntegerVal() const
Definition SourceRef.h:261
bool isBlockArgument() const
Definition SourceRef.h:269
bool overlaps(const SourceRef &rhs) const
Return true when both references select overlapping storage at the same path depth.
mlir::FailureOr< SourceRef > createChild(const SourceRefIndex &r) const
Definition SourceRef.h:384
std::vector< SourceRef > getAllChildren(mlir::SymbolTableCollection &tables, mlir::ModuleOp mod) const
Get all direct children of this SourceRef, assuming this ref is not a scalar.
mlir::FailureOr< std::vector< SourceRefIndex > > getSuffix(const SourceRef &prefix) const
If prefix is a valid prefix of this reference, return the suffix that remains after removing the pref...
mlir::FailureOr< SourceRef > getParentPrefix() const
Create a new reference that is the immediate prefix of this reference if possible.
Definition SourceRef.h:371
mlir::FailureOr< function::CallOp > getCallOp() const
Definition SourceRef.h:305
void print(mlir::raw_ostream &os) const
Print this reference using source-style names.
bool isCallResult() const
Definition SourceRef.h:304
bool isScalar() const
Definition SourceRef.h:263
bool operator==(const SourceRef &rhs) const
mlir::FailureOr< component::CreateStructOp > getCreateStructOp() const
Definition SourceRef.h:297
bool isConstantFelt() const
Definition SourceRef.h:244
bool isRooted() const
Definition SourceRef.h:267
SourceRef(felt::FeltConstantOp c)
Definition SourceRef.h:235
SourceRef(component::CreateStructOp createOp, Path p={})
Definition SourceRef.h:226
llvm::ArrayRef< SourceRefIndex > getPath() const
Definition SourceRef.h:406
bool isValidPrefix(const SourceRef &prefix) const
Returns true iff prefix is a valid prefix of this reference.
std::strong_ordering operator<=>(const SourceRef &rhs) const
SourceRef(mlir::BlockArgument b, Path p={})
Definition SourceRef.h:224
mlir::FailureOr< llvm::DynamicAPInt > getConstantFeltValue() const
Return the field representative, or failure for a nonconstant or unspecified field.
Definition SourceRef.h:308
bool isConstantIndex() const
Definition SourceRef.h:247
std::vector< SourceRefIndex > Path
Definition SourceRef.h:154
static std::vector< SourceRef > getAllSourceRefs(mlir::SymbolTableCollection &tables, mlir::ModuleOp mod, const SourceRef &root)
Produce all possible SourceRefs that are present starting from the given root.
mlir::FailureOr< llvm::DynamicAPInt > getConstantValue() const
Return a known numeric value, failing for fieldless felt constants.
Definition SourceRef.h:329
mlir::FailureOr< unsigned > getInputNum() const
Definition SourceRef.h:288
SourceRef narrowRanges(const SourceRef &rhs) const
Return a copy with ranged array indices narrowed by concrete indices from rhs.
mlir::FailureOr< NonDetOp > getNonDetOp() const
Definition SourceRef.h:302
void dump() const
Definition SourceRef.h:411
llvm::ArrayRef< SourceRefIndex > getPieces() const
Definition SourceRef.h:403
SourceRef(mlir::arith::ConstantIndexOp c)
Definition SourceRef.h:237
mlir::FailureOr< mlir::BlockArgument > getBlockArgument() const
Definition SourceRef.h:282
bool isIndexVal() const
Definition SourceRef.h:260
SourceRef(NonDetOp nondet, Path p={})
Definition SourceRef.h:228
SourceRef(mlir::OpResult rootResult, Path p={})
Definition SourceRef.h:230
mlir::FailureOr< SourceRef > createChild(const SourceRef &other) const
Definition SourceRef.h:393
mlir::FailureOr< llvm::DynamicAPInt > getConstantIndexValue() const
Definition SourceRef.h:317
mlir::FailureOr< SourceRef > translate(const SourceRef &prefix, const SourceRef &other) const
Create a new reference with prefix replaced with other iff prefix is a valid prefix for this referenc...
bool isNonDetOp() const
Definition SourceRef.h:301
mlir::FailureOr< mlir::Value > getConstant() const
Definition SourceRef.h:276
SourceRef(polymorphic::ConstReadOp c)
Definition SourceRef.h:239
bool isTemplateConstant() const
Return whether this reference originates from a template constant read.
Definition SourceRef.h:252
bool isTypeVarVal() const
Definition SourceRef.h:262
bool isConstant() const
Definition SourceRef.h:256
bool operator!=(const SourceRef &rhs) const
Definition SourceRef.h:415
mlir::FailureOr< mlir::Value > getRoot() const
Definition SourceRef.h:270
bool isFeltVal() const
Definition SourceRef.h:259
bool isConstantInt() const
Definition SourceRef.h:257
bool isCreateStructOp() const
Definition SourceRef.h:296
mlir::Type getType() const
ExpressionValue mod(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
void ensure(bool condition, const llvm::Twine &errMsg)
Interval operator<<(const Interval &lhs, const Interval &rhs)
static bool isEqual(const llzk::SourceRef &lhs, const llzk::SourceRef &rhs)
Definition SourceRef.h:458
static unsigned getHashValue(const llzk::SourceRef &ref)
Definition SourceRef.h:457
size_t operator()(const SourceRefIndex &c) const
size_t operator()(const SourceRef &val) const