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(const llvm::APInt &i) : index(toDynamicAPInt(i)) {}
52 explicit SourceRefIndex(int64_t i) : index(llvm::DynamicAPInt(i)) {}
53 SourceRefIndex(const llvm::APInt &low, const llvm::APInt &high)
54 : index(IndexRange {toDynamicAPInt(low), toDynamicAPInt(high)}) {}
55 explicit SourceRefIndex(IndexRange r) : index(r) {}
56
57 bool isMember() const {
58 return std::holds_alternative<SymbolLookupResult<component::MemberDefOp>>(index) ||
59 std::holds_alternative<component::MemberDefOp>(index);
60 }
62 ensure(isMember(), "SourceRefIndex: member requested but not contained");
63 if (std::holds_alternative<component::MemberDefOp>(index)) {
64 return std::get<component::MemberDefOp>(index);
65 }
66 return std::get<SymbolLookupResult<component::MemberDefOp>>(index).get();
67 }
68
69 bool isPodRecord() const { return std::holds_alternative<mlir::StringAttr>(index); }
70 mlir::StringAttr getPodRecordNameAttr() const {
71 ensure(isPodRecord(), "SourceRefIndex: pod record requested but not contained");
72 return std::get<mlir::StringAttr>(index);
73 }
74 llvm::StringRef getPodRecordName() const { return getPodRecordNameAttr().getValue(); }
75
76 bool isIndex() const { return std::holds_alternative<llvm::DynamicAPInt>(index); }
77 llvm::DynamicAPInt getIndex() const {
78 ensure(isIndex(), "SourceRefIndex: index requested but not contained");
79 return std::get<llvm::DynamicAPInt>(index);
80 }
81
82 bool isIndexRange() const { return std::holds_alternative<IndexRange>(index); }
83 IndexRange getIndexRange() const {
84 ensure(isIndexRange(), "SourceRefIndex: index range requested but not contained");
85 return std::get<IndexRange>(index);
86 }
87
88 inline void dump() const { print(llvm::errs()); }
89 void print(mlir::raw_ostream &os) const;
90
91 inline bool operator==(const SourceRefIndex &rhs) const {
92 if (isMember() && rhs.isMember()) {
93 // We compare the underlying members, since the member could be in a symbol
94 // lookup or not.
95 return getMember() == rhs.getMember();
96 }
97 if (isIndex() && rhs.isIndex()) {
98 return getIndex() == rhs.getIndex();
99 }
100 return index == rhs.index;
101 }
102
105 bool overlaps(const SourceRefIndex &rhs) const;
106
107 std::strong_ordering operator<=>(const SourceRefIndex &rhs) const;
108
109 struct Hash {
110 size_t operator()(const SourceRefIndex &c) const;
111 };
112
113 inline size_t getHash() const { return Hash {}(*this); }
114
115private:
123 std::variant<
125 llvm::DynamicAPInt, IndexRange>
126 index;
127};
128
129static inline mlir::raw_ostream &operator<<(mlir::raw_ostream &os, const SourceRefIndex &rhs) {
130 rhs.print(os);
131 return os;
132}
133
146class SourceRef {
147public:
148 using Path = std::vector<SourceRefIndex>;
149
150private:
151 // Sort in the following order:
152 // block arg < struct.new < nondet < other rooted result < template const < const index <
153 // const felt.
154 enum class SortCategory : std::uint8_t {
155 BlockArgument,
156 CreateStruct,
157 NonDet,
158 RootResult,
159 TemplateConstant,
160 ConstantIndex,
161 ConstantFelt,
162 };
163
164 template <typename OpT> static mlir::Value getSingleResultValue(OpT op) {
165 ensure(op, "SourceRef requires a non-null operation");
166 ensure(op->getNumResults() == 1, "SourceRef expects a single-result operation");
167 return op->getResult(0);
168 }
169
170 static mlir::Value getRootResultValue(mlir::OpResult result) {
171 ensure(
172 !llvm::isa<
173 felt::FeltConstantOp, mlir::arith::ConstantIndexOp, mlir::arith::ConstantIntOp,
174 polymorphic::ConstReadOp>(result.getOwner()),
175 "SourceRef rooted OpResult constructors must not be used for constant values"
176 );
177 return result;
178 }
179
180 template <typename OpT> mlir::FailureOr<OpT> getDefiningOp() const {
181 if (auto op = llvm::dyn_cast_if_present<OpT>(value.getDefiningOp())) {
182 return op;
183 }
184 return mlir::failure();
185 }
186
187 SourceRef(mlir::Value sourceValue, bool isConstantStorage, Path sourcePath = {})
188 : value(sourceValue), path(std::move(sourcePath)), constant(isConstantStorage) {
189 ensure(value != nullptr, "SourceRef requires a non-null value");
190 ensure(!constant || this->path.empty(), "constant SourceRef cannot have a path");
191 }
192
193 Path &getPathMut() { return path; }
194 const void *getAsOpaquePointer() const { return value.getAsOpaquePointer(); }
195 SortCategory getSortCategory() const;
196 llvm::StringRef getTemplateConstantName() const;
197 std::strong_ordering compareWithinCategory(const SourceRef &rhs, SortCategory category) const;
198
199public:
201 static std::vector<SourceRef>
202 getAllSourceRefs(mlir::SymbolTableCollection &tables, mlir::ModuleOp mod, const SourceRef &root);
203
205 static std::vector<SourceRef>
207
210 static std::vector<SourceRef>
212
213 /* Rooted constructors */
214
215 SourceRef(mlir::BlockArgument b, Path p = {})
216 : SourceRef(b, /*isConstantStorage=*/false, std::move(p)) {}
218 : SourceRef(getSingleResultValue(createOp), /*isConstantStorage=*/false, std::move(p)) {}
219 SourceRef(NonDetOp nondet, Path p = {})
220 : SourceRef(getSingleResultValue(nondet), /*isConstantStorage=*/false, std::move(p)) {}
221 SourceRef(mlir::OpResult rootResult, Path p = {})
222 : SourceRef(getRootResultValue(rootResult), /*isConstantStorage=*/false, std::move(p)) {}
223
224 /* Constant constructors */
225
227 : SourceRef(getSingleResultValue(c), /*isConstantStorage=*/true) {}
228 explicit SourceRef(mlir::arith::ConstantIndexOp c)
229 : SourceRef(getSingleResultValue(c), /*isConstantStorage=*/true) {}
231 : SourceRef(getSingleResultValue(c), /*isConstantStorage=*/true) {}
232
233 mlir::Type getType() const;
234
235 bool isConstantFelt() const {
236 return isConstant() && llvm::isa_and_present<felt::FeltConstantOp>(value.getDefiningOp());
237 }
238 bool isConstantIndex() const {
239 return isConstant() &&
240 llvm::isa_and_present<mlir::arith::ConstantIndexOp>(value.getDefiningOp());
241 }
242
243 bool isTemplateConstant() const {
244 return isConstant() && llvm::isa_and_present<polymorphic::ConstReadOp>(value.getDefiningOp());
245 }
246
247 bool isConstant() const { return constant; }
248 bool isConstantInt() const { return isConstantFelt() || isConstantIndex(); }
249
250 bool isFeltVal() const { return llvm::isa<felt::FeltType>(getType()); }
251 bool isIndexVal() const { return llvm::isa<mlir::IndexType>(getType()); }
252 bool isIntegerVal() const { return llvm::isa<mlir::IntegerType>(getType()); }
253 bool isTypeVarVal() const { return llvm::isa<polymorphic::TypeVarType>(getType()); }
254 bool isScalar() const {
255 return isConstant() || isFeltVal() || isIndexVal() || isIntegerVal() || isTypeVarVal();
256 }
257
258 bool isRooted() const { return !constant; }
259
260 bool isBlockArgument() const { return isRooted() && llvm::isa<mlir::BlockArgument>(value); }
261 mlir::FailureOr<mlir::Value> getRoot() const {
262 if (isRooted()) {
263 return value;
264 }
265 return mlir::failure();
266 }
267 mlir::FailureOr<mlir::Value> getConstant() const {
268 if (isConstant()) {
269 return value;
270 }
271 return mlir::failure();
272 }
273 mlir::FailureOr<mlir::BlockArgument> getBlockArgument() const {
274 if (auto blockArg = llvm::dyn_cast<mlir::BlockArgument>(value)) {
275 return blockArg;
276 }
277 return mlir::failure();
278 }
279 mlir::FailureOr<unsigned> getInputNum() const {
280 auto blockArg = getBlockArgument();
281 if (succeeded(blockArg)) {
282 return blockArg->getArgNumber();
283 }
284 return mlir::failure();
285 }
286
287 bool isCreateStructOp() const { return succeeded(getCreateStructOp()); }
288 mlir::FailureOr<component::CreateStructOp> getCreateStructOp() const {
289 return getDefiningOp<component::CreateStructOp>();
290 }
291
292 bool isNonDetOp() const { return succeeded(getNonDetOp()); }
293 mlir::FailureOr<NonDetOp> getNonDetOp() const { return getDefiningOp<NonDetOp>(); }
294
295 bool isCallResult() const { return succeeded(getCallOp()); }
296 mlir::FailureOr<function::CallOp> getCallOp() const { return getDefiningOp<function::CallOp>(); }
297
298 mlir::FailureOr<llvm::DynamicAPInt> getConstantFeltValue() const {
299 auto feltConst = getDefiningOp<felt::FeltConstantOp>();
300 if (succeeded(feltConst)) {
301 llvm::APInt i = feltConst->getValue();
302 return toDynamicAPInt(i);
303 }
304 return mlir::failure();
305 }
306 mlir::FailureOr<llvm::DynamicAPInt> getConstantIndexValue() const {
307 auto indexConst = getDefiningOp<mlir::arith::ConstantIndexOp>();
308 if (succeeded(indexConst)) {
309 return llvm::DynamicAPInt(indexConst->value());
310 }
311 return mlir::failure();
312 }
313 mlir::FailureOr<llvm::DynamicAPInt> getConstantValue() const {
314 auto feltVal = getConstantFeltValue();
315 if (succeeded(feltVal)) {
316 return *feltVal;
317 }
318 auto indexVal = getConstantIndexValue();
319 if (succeeded(indexVal)) {
320 return *indexVal;
321 }
322 return mlir::failure();
323 }
324
326 bool isValidPrefix(const SourceRef &prefix) const;
327
329 bool overlaps(const SourceRef &rhs) const;
330
338 SourceRef narrowRanges(const SourceRef &rhs) const;
339
344 mlir::FailureOr<std::vector<SourceRefIndex>> getSuffix(const SourceRef &prefix) const;
345
352 mlir::FailureOr<SourceRef> translate(const SourceRef &prefix, const SourceRef &other) const;
353
355 mlir::FailureOr<SourceRef> getParentPrefix() const {
356 if (!isRooted() || getPath().empty()) {
357 return mlir::failure();
358 }
359 auto copy = *this;
360 copy.getPathMut().pop_back();
361 return copy;
362 }
363
365 std::vector<SourceRef>
366 getAllChildren(mlir::SymbolTableCollection &tables, mlir::ModuleOp mod) const;
367
368 mlir::FailureOr<SourceRef> createChild(const SourceRefIndex &r) const {
369 if (!isRooted()) {
370 return mlir::failure();
371 }
372 auto copy = *this;
373 copy.getPathMut().push_back(r);
374 return copy;
375 }
376
377 mlir::FailureOr<SourceRef> createChild(const SourceRef &other) const {
378 auto idxVal = other.getConstantIndexValue();
379 if (failed(idxVal)) {
380 return mlir::failure();
381 }
382 return createChild(SourceRefIndex(*idxVal));
383 }
384
385 [[deprecated("Use getPath() instead")]]
386 // NOTE: When this function is removed, do not delete it, rewrite as `... = delete`.
387 llvm::ArrayRef<SourceRefIndex> getPieces() const {
388 return path;
389 }
390 llvm::ArrayRef<SourceRefIndex> getPath() const { return path; }
391
394 void print(mlir::raw_ostream &os) const;
395 void dump() const { print(llvm::errs()); }
396
397 bool operator==(const SourceRef &rhs) const;
398
399 bool operator!=(const SourceRef &rhs) const { return !(*this == rhs); }
400
401 // required for EquivalenceClasses usage
402 std::strong_ordering operator<=>(const SourceRef &rhs) const;
403
404 struct Hash {
405 size_t operator()(const SourceRef &val) const;
406 };
407
408 friend struct llvm::DenseMapInfo<SourceRef>;
409
410private:
411 mlir::Value value;
412 Path path;
413 bool constant;
414};
415
416mlir::raw_ostream &operator<<(mlir::raw_ostream &os, const SourceRef &rhs);
417
418/* SourceRefSet */
419
420class SourceRefSet : public std::unordered_set<SourceRef, SourceRef::Hash> {
421 using Base = std::unordered_set<SourceRef, SourceRef::Hash>;
422
423public:
424 using Base::Base;
425
426 SourceRefSet &join(const SourceRefSet &rhs);
427
428 friend mlir::raw_ostream &operator<<(mlir::raw_ostream &os, const SourceRefSet &rhs);
429};
430
431static_assert(
433 "SourceRefSet must satisfy the ScalarLatticeValue requirements"
434);
435
436} // namespace llzk
437
438namespace llvm {
439
440template <> struct DenseMapInfo<llzk::SourceRef> {
442 return llzk::SourceRef(mlir::BlockArgument(reinterpret_cast<mlir::detail::ValueImpl *>(1)));
443 }
445 return llzk::SourceRef(mlir::BlockArgument(reinterpret_cast<mlir::detail::ValueImpl *>(2)));
446 }
447 static unsigned getHashValue(const llzk::SourceRef &ref) {
448 if (ref == getEmptyKey() || ref == getTombstoneKey()) {
449 return llvm::hash_value(ref.getAsOpaquePointer());
450 }
451 return llzk::SourceRef::Hash {}(ref);
452 }
453 static bool isEqual(const llzk::SourceRef &lhs, const llzk::SourceRef &rhs) { return lhs == rhs; }
454};
455
456} // namespace llvm
This file implements helper methods for constructing DynamicAPInts.
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:91
bool isIndexRange() const
Definition SourceRef.h:82
size_t getHash() const
Definition SourceRef.h:113
bool isIndex() const
Definition SourceRef.h:76
mlir::StringAttr getPodRecordNameAttr() const
Definition SourceRef.h:70
bool isMember() const
Definition SourceRef.h:57
bool isPodRecord() const
Definition SourceRef.h:69
SourceRefIndex(const llvm::DynamicAPInt &i)
Definition SourceRef.h:50
SourceRefIndex(const llvm::APInt &low, const llvm::APInt &high)
Definition SourceRef.h:53
SourceRefIndex(const llvm::APInt &i)
Definition SourceRef.h:51
void dump() const
Definition SourceRef.h:88
llvm::DynamicAPInt getIndex() const
Definition SourceRef.h:77
void print(mlir::raw_ostream &os) const
Definition SourceRef.cpp:95
IndexRange getIndexRange() const
Definition SourceRef.h:83
bool overlaps(const SourceRefIndex &rhs) const
Return true when these path components select any common storage.
component::MemberDefOp getMember() const
Definition SourceRef.h:61
SourceRefIndex(mlir::StringAttr recordName)
Definition SourceRef.h:49
SourceRefIndex(SymbolLookupResult< component::MemberDefOp > f)
Definition SourceRef.h:48
SourceRefIndex(IndexRange r)
Definition SourceRef.h:55
SourceRefIndex(int64_t i)
Definition SourceRef.h:52
SourceRefIndex(component::MemberDefOp f)
Definition SourceRef.h:47
llvm::StringRef getPodRecordName() const
Definition SourceRef.h:74
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:146
bool isIntegerVal() const
Definition SourceRef.h:252
bool isBlockArgument() const
Definition SourceRef.h:260
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:368
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:355
mlir::FailureOr< function::CallOp > getCallOp() const
Definition SourceRef.h:296
void print(mlir::raw_ostream &os) const
Print this reference using source-style names.
bool isCallResult() const
Definition SourceRef.h:295
bool isScalar() const
Definition SourceRef.h:254
bool operator==(const SourceRef &rhs) const
mlir::FailureOr< component::CreateStructOp > getCreateStructOp() const
Definition SourceRef.h:288
bool isConstantFelt() const
Definition SourceRef.h:235
bool isRooted() const
Definition SourceRef.h:258
SourceRef(felt::FeltConstantOp c)
Definition SourceRef.h:226
SourceRef(component::CreateStructOp createOp, Path p={})
Definition SourceRef.h:217
llvm::ArrayRef< SourceRefIndex > getPath() const
Definition SourceRef.h:390
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:215
mlir::FailureOr< llvm::DynamicAPInt > getConstantFeltValue() const
Definition SourceRef.h:298
bool isConstantIndex() const
Definition SourceRef.h:238
std::vector< SourceRefIndex > Path
Definition SourceRef.h:148
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
Definition SourceRef.h:313
mlir::FailureOr< unsigned > getInputNum() const
Definition SourceRef.h:279
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:293
void dump() const
Definition SourceRef.h:395
llvm::ArrayRef< SourceRefIndex > getPieces() const
Definition SourceRef.h:387
SourceRef(mlir::arith::ConstantIndexOp c)
Definition SourceRef.h:228
mlir::FailureOr< mlir::BlockArgument > getBlockArgument() const
Definition SourceRef.h:273
bool isIndexVal() const
Definition SourceRef.h:251
SourceRef(NonDetOp nondet, Path p={})
Definition SourceRef.h:219
SourceRef(mlir::OpResult rootResult, Path p={})
Definition SourceRef.h:221
mlir::FailureOr< SourceRef > createChild(const SourceRef &other) const
Definition SourceRef.h:377
mlir::FailureOr< llvm::DynamicAPInt > getConstantIndexValue() const
Definition SourceRef.h:306
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:292
mlir::FailureOr< mlir::Value > getConstant() const
Definition SourceRef.h:267
SourceRef(polymorphic::ConstReadOp c)
Definition SourceRef.h:230
bool isTemplateConstant() const
Return whether this reference originates from a template constant read.
Definition SourceRef.h:243
bool isTypeVarVal() const
Definition SourceRef.h:253
bool isConstant() const
Definition SourceRef.h:247
bool operator!=(const SourceRef &rhs) const
Definition SourceRef.h:399
mlir::FailureOr< mlir::Value > getRoot() const
Definition SourceRef.h:261
bool isFeltVal() const
Definition SourceRef.h:250
bool isConstantInt() const
Definition SourceRef.h:248
bool isCreateStructOp() const
Definition SourceRef.h:287
mlir::Type getType() const
ExpressionValue mod(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
void ensure(bool condition, const llvm::Twine &errMsg)
DynamicAPInt toDynamicAPInt(StringRef str)
Interval operator<<(const Interval &lhs, const Interval &rhs)
static bool isEqual(const llzk::SourceRef &lhs, const llzk::SourceRef &rhs)
Definition SourceRef.h:453
static unsigned getHashValue(const llzk::SourceRef &ref)
Definition SourceRef.h:447
static llzk::SourceRef getTombstoneKey()
Definition SourceRef.h:444
static llzk::SourceRef getEmptyKey()
Definition SourceRef.h:441
size_t operator()(const SourceRefIndex &c) const
size_t operator()(const SourceRef &val) const