LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
SourceRef.cpp
Go to the documentation of this file.
1//===-- SourceRef.cpp - SourceRef implementation ----------------*- 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
16#include "llzk/Util/Compare.h"
17#include "llzk/Util/Debug.h"
21
22#include <mlir/IR/AsmState.h>
23
24using namespace mlir;
25
26namespace llzk {
27
28using namespace array;
29using namespace component;
30using namespace felt;
31using namespace function;
32using namespace polymorphic;
33using namespace string;
34
35namespace {
36
38static inline std::strong_ordering comparePointers(const void *lhs, const void *rhs) {
39 return std::compare_three_way {}(lhs, rhs);
40}
41
42std::strong_ordering
43compareDynamicAPInt(const llvm::DynamicAPInt &lhs, const llvm::DynamicAPInt &rhs) {
44 if (lhs < rhs) {
45 return std::strong_ordering::less;
46 }
47 if (rhs < lhs) {
48 return std::strong_ordering::greater;
49 }
50 return std::strong_ordering::equal;
51}
52
53std::strong_ordering compareStringRef(llvm::StringRef lhs, llvm::StringRef rhs) {
54 int cmp = lhs.compare(rhs);
55 if (cmp < 0) {
56 return std::strong_ordering::less;
57 }
58 if (cmp > 0) {
59 return std::strong_ordering::greater;
60 }
61 return std::strong_ordering::equal;
62}
63
64// Prints SourceRef path using source-style syntax, i.e. `.` for struct members
65// and pod records and `[...]` for array indices.
66// Returns failure if an unexpected `SourceRefIndex` type is encountered.
67LogicalResult printSourceStylePath(raw_ostream &os, llvm::ArrayRef<SourceRefIndex> path) {
68 for (const auto &idx : path) {
69 if (idx.isMember()) {
70 os << '.' << idx.getMember().getName();
71 continue;
72 }
73 if (idx.isPodRecord()) {
74 os << '.' << idx.getPodRecordName();
75 continue;
76 }
77 if (idx.isIndex() || idx.isIndexRange()) {
78 os << '[' << idx << ']';
79 continue;
80 }
81 return failure();
82 }
83 return success();
84}
85
86std::strong_ordering
87compareSourceRefPaths(llvm::ArrayRef<SourceRefIndex> lhs, llvm::ArrayRef<SourceRefIndex> rhs) {
88 for (size_t i = 0; i < lhs.size() && i < rhs.size(); i++) {
89 if (auto cmp = lhs[i] <=> rhs[i]; cmp != std::strong_ordering::equal) {
90 return cmp;
91 }
92 }
93 return lhs.size() <=> rhs.size();
94}
95
96} // namespace
97
98/* SourceRefIndex */
99
101 assert((size >= 0 || ShapedType::isDynamic(size)) && "invalid array dimension");
102 return SourceRefIndex(llvm::DynamicAPInt(0), llvm::DynamicAPInt(size));
103}
104
106 return isIndexRange() && getIndexRange().second == ShapedType::kDynamic;
107}
108
109void SourceRefIndex::print(raw_ostream &os) const {
110 if (isMember()) {
111 os << '@' << getMember().getName();
112 } else if (isPodRecord()) {
113 os << '@' << getPodRecordName();
114 } else if (isIndex()) {
115 os << getIndex();
116 } else {
117 auto [low, high] = getIndexRange();
119 os << "<dynamic>";
120 } else {
121 os << low << ':' << high;
122 }
123 }
124}
125
126std::strong_ordering SourceRefIndex::operator<=>(const SourceRefIndex &rhs) const {
127 if (isMember() && rhs.isMember()) {
129 return std::strong_ordering::less;
130 }
132 return std::strong_ordering::greater;
133 }
134 return std::strong_ordering::equal;
135 }
136 if (isPodRecord() && rhs.isPodRecord()) {
137 return compareStringRef(getPodRecordName(), rhs.getPodRecordName());
138 }
139 if (isIndex() && rhs.isIndex()) {
140 return compareDynamicAPInt(getIndex(), rhs.getIndex());
141 }
142 if (isIndexRange() && rhs.isIndexRange()) {
143 auto [ll, lu] = getIndexRange();
144 auto [rl, ru] = rhs.getIndexRange();
145 if (auto cmp = compareDynamicAPInt(ll, rl); cmp != std::strong_ordering::equal) {
146 return cmp;
147 }
148 return compareDynamicAPInt(lu, ru);
149 }
150
151 if (isMember()) {
152 return std::strong_ordering::less;
153 }
154 if (rhs.isMember()) {
155 return std::strong_ordering::greater;
156 }
157 if (isPodRecord()) {
158 return std::strong_ordering::less;
159 }
160 if (rhs.isPodRecord()) {
161 return std::strong_ordering::greater;
162 }
163 if (isIndex()) {
164 return std::strong_ordering::less;
165 }
166 return std::strong_ordering::greater;
167}
168
170 if (c.isIndex()) {
171 return hashDynamicAPInt(c.getIndex());
172 } else if (c.isIndexRange()) {
173 auto r = c.getIndexRange();
174 return llvm::hash_combine(hashDynamicAPInt(std::get<0>(r)), hashDynamicAPInt(std::get<1>(r)));
175 } else if (c.isPodRecord()) {
176 return llvm::hash_value(c.getPodRecordName());
177 } else {
179 }
180}
181
183 if (isIndex() && rhs.isIndexRange()) {
184 auto [low, high] = rhs.getIndexRange();
185 return low <= getIndex() && (rhs.hasUnboundedUpperBound() || getIndex() < high);
186 }
187 if (isIndexRange() && rhs.isIndex()) {
188 return rhs.overlaps(*this);
189 }
190 if (isIndexRange() && rhs.isIndexRange()) {
191 auto [lhsLow, lhsHigh] = getIndexRange();
192 auto [rhsLow, rhsHigh] = rhs.getIndexRange();
193 if ((!hasUnboundedUpperBound() && lhsLow >= lhsHigh) ||
194 (!rhs.hasUnboundedUpperBound() && rhsLow >= rhsHigh)) {
195 return false;
196 }
197 return (rhs.hasUnboundedUpperBound() || lhsLow < rhsHigh) &&
198 (hasUnboundedUpperBound() || rhsLow < lhsHigh);
199 }
200 return *this == rhs;
201}
202
203/* SourceRef */
204
205SourceRef::SortCategory SourceRef::getSortCategory() const {
206 if (isBlockArgument()) {
207 return SortCategory::BlockArgument;
208 }
209 if (isCreateStructOp()) {
210 return SortCategory::CreateStruct;
211 }
212 if (isNonDetOp()) {
213 return SortCategory::NonDet;
214 }
215 if (isRooted()) {
216 return SortCategory::RootResult;
217 }
218 if (isTemplateConstant()) {
219 return SortCategory::TemplateConstant;
220 }
221 if (isConstantIndex()) {
222 return SortCategory::ConstantIndex;
223 }
224 if (isConstantFelt()) {
225 return SortCategory::ConstantFelt;
226 }
227
228 llvm::errs() << *this << '\n';
229 llvm_unreachable("unhandled SourceRef sort category");
230}
231
232StringRef SourceRef::getTemplateConstantName() const {
233 auto constantVal = getConstant();
234 ensure(succeeded(constantVal), "template constant must be constant");
235 auto constRead = llvm::dyn_cast<ConstReadOp>(constantVal->getDefiningOp());
236 ensure(constRead, "template constant must be backed by const.read");
237 return constRead.getConstName();
238}
239
240std::strong_ordering
241SourceRef::compareWithinCategory(const SourceRef &rhs, SortCategory category) const {
242 switch (category) {
243 case SortCategory::BlockArgument: {
244 if (auto cmp = *getInputNum() <=> *rhs.getInputNum(); cmp != std::strong_ordering::equal) {
245 return cmp;
246 }
247 if (auto cmp = comparePointers(getAsOpaquePointer(), rhs.getAsOpaquePointer());
248 cmp != std::strong_ordering::equal) {
249 return cmp;
250 }
251 return compareSourceRefPaths(getPath(), rhs.getPath());
252 }
253 case SortCategory::CreateStruct:
254 case SortCategory::NonDet:
255 case SortCategory::RootResult: {
256 if (auto cmp = comparePointers(getAsOpaquePointer(), rhs.getAsOpaquePointer());
257 cmp != std::strong_ordering::equal) {
258 return cmp;
259 }
260 return compareSourceRefPaths(getPath(), rhs.getPath());
261 }
262 case SortCategory::TemplateConstant: {
263 if (auto cmp = compareStringRef(getTemplateConstantName(), rhs.getTemplateConstantName());
264 cmp != std::strong_ordering::equal) {
265 return cmp;
266 }
267 return comparePointers(getAsOpaquePointer(), rhs.getAsOpaquePointer());
268 }
269 case SortCategory::ConstantIndex:
270 return compareDynamicAPInt(*getConstantIndexValue(), *rhs.getConstantIndexValue());
271 case SortCategory::ConstantFelt:
272 // Constant identity includes the type as well as the field representative.
273 if (auto cmp =
274 comparePointers(getType().getAsOpaquePointer(), rhs.getType().getAsOpaquePointer());
275 cmp != std::strong_ordering::equal) {
276 return cmp;
277 }
278 return compareDynamicAPInt(getConstantIdentityValue(), rhs.getConstantIdentityValue());
279 }
280
281 llvm_unreachable("unhandled SourceRef category compare");
282}
283
291SymbolLookupResult<StructDefOp>
292getStructDef(SymbolTableCollection &tables, ModuleOp mod, StructType ty) {
293 auto sDef = ty.getDefinition(tables, mod);
294 ensure(
295 succeeded(sDef),
296 "could not find '" + StructDefOp::getOperationName() + "' op from struct type"
297 );
298
299 return std::move(*sDef);
300}
301
302std::vector<SourceRef>
303SourceRef::getAllSourceRefs(SymbolTableCollection &tables, ModuleOp mod, const SourceRef &root) {
304 std::vector<SourceRef> res = {root};
305 for (const SourceRef &child : root.getAllChildren(tables, mod)) {
306 auto recursiveChildren = getAllSourceRefs(tables, mod, child);
307 res.insert(res.end(), recursiveChildren.begin(), recursiveChildren.end());
308 }
309 return res;
310}
311
312std::vector<SourceRef> SourceRef::getAllSourceRefs(StructDefOp structDef, FuncDefOp fnOp) {
313 std::vector<SourceRef> res;
314
315 ensure(
316 structDef == fnOp->getParentOfType<StructDefOp>(), "function must be within the given struct"
317 );
318
319 FailureOr<ModuleOp> modOp = getRootModule(structDef);
320 ensure(succeeded(modOp), "could not lookup module from struct " + Twine(structDef.getName()));
321
322 SymbolTableCollection tables;
323 for (auto a : fnOp.getArguments()) {
324 auto argRes = getAllSourceRefs(tables, modOp.value(), SourceRef(a));
325 res.insert(res.end(), argRes.begin(), argRes.end());
326 }
327
328 // For compute functions, the "self" member is not arg0 like for constrain, but
329 // rather the struct value returned from the function.
330 if (fnOp.isStructCompute()) {
331 Value selfVal = fnOp.getSelfValueFromCompute();
332 auto createOp = dyn_cast_if_present<CreateStructOp>(selfVal.getDefiningOp());
333 ensure(createOp, "self value should originate from struct.new operation");
334 auto selfRes = getAllSourceRefs(tables, modOp.value(), SourceRef(createOp));
335 res.insert(res.end(), selfRes.begin(), selfRes.end());
336 }
337
338 return res;
339}
340
341std::vector<SourceRef> SourceRef::getAllSourceRefs(StructDefOp structDef, MemberDefOp memberDef) {
342 std::vector<SourceRef> res;
343 FuncDefOp constrainFnOp = structDef.getConstrainFuncOp();
344 ensure(
345 memberDef->getParentOfType<StructDefOp>() == structDef,
346 "Member " + Twine(memberDef.getName()) + " is not a member of struct " +
347 Twine(structDef.getName())
348 );
349 FailureOr<ModuleOp> modOp = getRootModule(structDef);
350 ensure(succeeded(modOp), "could not lookup module from struct " + Twine(structDef.getName()));
351
352 // Get the self argument (like `FuncDefOp::getSelfValueFromConstrain()`)
353 BlockArgument self = constrainFnOp.getArguments().front();
354 SourceRef memberRef = SourceRef(self, {SourceRefIndex(memberDef)});
355
356 SymbolTableCollection tables;
357 return getAllSourceRefs(tables, modOp.value(), memberRef);
358}
359
360Type SourceRef::getType() const {
361 Type currTy = value.getType();
362 for (const auto &idx : getPath()) {
363 if (idx.isMember()) {
364 currTy = idx.getMember().getType();
365 continue;
366 }
367 if (idx.isPodRecord()) {
368 auto podTy = dyn_cast<pod::PodType>(currTy);
369 ensure(static_cast<bool>(podTy), "SourceRef pod record requires a pod-typed base");
370 auto lookup = podTy.getRecord(idx.getPodRecordName(), [ctx = value.getContext()]() {
371 return mlir::emitError(
372 mlir::UnknownLoc::get(ctx), "SourceRef references a missing pod record"
373 );
374 });
375 ensure(succeeded(lookup), "SourceRef references a missing pod record");
376 currTy = *lookup;
377 continue;
378 }
379
380 auto arrTy = dyn_cast<ArrayType>(currTy);
381 ensure(static_cast<bool>(arrTy), "SourceRef array index requires an array-typed base");
382 currTy = arrTy.getSelectionType(1);
383 }
384 return currTy;
385}
386
387bool SourceRef::isValidPrefix(const SourceRef &prefix) const {
388 if (isConstant() || prefix.isConstant()) {
389 return false;
390 }
391
392 auto pathRef = getPath();
393 auto prefixPath = prefix.getPath();
394 if (value != prefix.value || pathRef.size() < prefixPath.size()) {
395 return false;
396 }
397 for (size_t i = 0; i < prefixPath.size(); i++) {
398 if (pathRef[i] != prefixPath[i]) {
399 return false;
400 }
401 }
402 return true;
403}
404
405bool SourceRef::overlaps(const SourceRef &rhs) const {
406 auto getSelfStruct = [](const SourceRef &ref) -> StructDefOp {
407 if (auto createOp = dyn_cast_if_present<CreateStructOp>(ref.value.getDefiningOp())) {
408 auto func = createOp->getParentOfType<FuncDefOp>();
409 if (!func || !func.isStructCompute() || func.getSelfValueFromCompute() != ref.value) {
410 return nullptr;
411 }
412 return func->getParentOfType<StructDefOp>();
413 }
414 auto blockArg = ref.getBlockArgument();
415 if (failed(blockArg)) {
416 return nullptr;
417 }
418 auto func = dyn_cast_if_present<FuncDefOp>(blockArg->getOwner()->getParentOp());
419 return func && func.isStructConstrain() && func.getSelfValueFromConstrain() == *blockArg
420 ? func->getParentOfType<StructDefOp>()
421 : nullptr;
422 };
423 bool sameRoot = value == rhs.value;
424 if (!sameRoot) {
425 StructDefOp lhsStruct = getSelfStruct(*this);
426 StructDefOp rhsStruct = getSelfStruct(rhs);
427 sameRoot = lhsStruct && lhsStruct == rhsStruct;
428 }
429 if (isConstant() || rhs.isConstant() || !sameRoot || path.size() != rhs.path.size()) {
430 return false;
431 }
432 return llvm::all_of(llvm::zip(path, rhs.path), [](const auto &indices) {
433 return std::get<0>(indices).overlaps(std::get<1>(indices));
434 });
435}
436
437SourceRef SourceRef::narrowRanges(const SourceRef &rhs) const {
438 llvm::SmallVector<SourceRefIndex> selections;
439 llvm::copy_if(rhs.getPath(), std::back_inserter(selections), [](const SourceRefIndex &index) {
440 return index.isIndex() || index.isIndexRange();
441 });
442
443 SourceRef result = *this;
444 size_t dimension = 0;
445 for (SourceRefIndex &index : result.getPathMut()) {
446 if (!index.isIndex() && !index.isIndexRange()) {
447 continue;
448 }
449 if (dimension < selections.size() && index.isIndexRange() && selections[dimension].isIndex() &&
450 index.overlaps(selections[dimension])) {
451 index = selections[dimension];
452 }
453 ++dimension;
454 }
455 return result;
456}
457
458FailureOr<SourceRef::Path> SourceRef::getSuffix(const SourceRef &prefix) const {
459 if (!isValidPrefix(prefix)) {
460 return failure();
461 }
462 Path suffix;
463 auto pathRef = getPath();
464 auto prefixPath = prefix.getPath();
465 suffix.reserve(pathRef.size() - prefixPath.size());
466 for (size_t i = prefixPath.size(); i < pathRef.size(); i++) {
467 suffix.push_back(pathRef[i]);
468 }
469 return suffix;
470}
471
472FailureOr<SourceRef> SourceRef::translate(const SourceRef &prefix, const SourceRef &other) const {
473 if (isConstant()) {
474 return *this;
475 }
476 auto suffix = getSuffix(prefix);
477 if (failed(suffix)) {
478 return failure();
479 }
480
481 SourceRef newSignalUsage = other; // copy
482 if (newSignalUsage.isRooted()) {
483 SourceRef::Path &pathRef = newSignalUsage.getPathMut();
484 pathRef.insert(pathRef.end(), suffix->begin(), suffix->end());
485 }
486
487 return newSignalUsage;
488}
489
490std::vector<SourceRef> getAllChildren(
491 SymbolTableCollection & /*tables*/, ModuleOp /*mod*/, ArrayType arrayTy, const SourceRef &root
492) {
493 std::vector<SourceRef> res;
494 // Recurse into arrays by iterating over their elements
495 for (int64_t i = 0; i < arrayTy.getDimSize(0); i++) {
496 auto childRef = root.createChild(SourceRefIndex(i));
497 ensure(succeeded(childRef), "array children require a rooted SourceRef");
498 res.push_back(*childRef);
499 }
500
501 return res;
502}
503
504std::vector<SourceRef> getAllChildren(
505 SymbolTableCollection &tables, ModuleOp mod, SymbolLookupResult<StructDefOp> structDefRes,
506 const SourceRef &root
507) {
508 std::vector<SourceRef> res;
509 // Recurse into struct types by iterating over all their member definitions
510 for (auto f : structDefRes.get().getOps<MemberDefOp>()) {
511 // We want to store the MemberDefOp, but without the possibility of accidentally dropping the
512 // reference, so we need to re-lookup the symbol to create a SymbolLookupResult, which will
513 // manage the external module containing the member defs, if needed.
514 // TODO: It would be nice if we could manage module op references differently
515 // so we don't have to do this.
516 auto structDefCopy = structDefRes;
517 auto memberLookup = lookupSymbolIn<MemberDefOp>(
518 tables, SymbolRefAttr::get(f.getContext(), f.getSymNameAttr()), std::move(structDefCopy),
519 mod.getOperation()
520 );
521 ensure(succeeded(memberLookup), "could not get SymbolLookupResult of existing MemberDefOp");
522 auto childRef = root.createChild(SourceRefIndex(memberLookup.value()));
523 ensure(succeeded(childRef), "struct children require a rooted SourceRef");
524 // Make a reference to the current member, regardless of if it is a composite
525 // type or not.
526 res.push_back(*childRef);
527 }
528 return res;
529}
530
531std::vector<SourceRef> getAllChildren(pod::PodType podTy, const SourceRef &root) {
532 std::vector<SourceRef> res;
533 for (auto record : podTy.getRecords()) {
534 auto childRef = root.createChild(SourceRefIndex(record.getName()));
535 ensure(succeeded(childRef), "pod children require a rooted SourceRef");
536 res.push_back(*childRef);
537 }
538 return res;
539}
540
541std::vector<SourceRef>
542SourceRef::getAllChildren(SymbolTableCollection &tables, ModuleOp mod) const {
543 auto ty = getType();
544 if (auto structTy = dyn_cast<StructType>(ty)) {
545 return llzk::getAllChildren(tables, mod, getStructDef(tables, mod, structTy), *this);
546 } else if (auto podTy = dyn_cast<pod::PodType>(ty)) {
547 return llzk::getAllChildren(podTy, *this);
548 } else if (auto arrayType = dyn_cast<ArrayType>(ty)) {
549 return llzk::getAllChildren(tables, mod, arrayType, *this);
550 }
551 // Scalar type, no children
552 return {};
553}
554
555static void printCallResultFallback(raw_ostream &os, function::CallOp callOp, Value value) {
556 os << "<call " << callOp.getCallee();
557 os << ' ';
558 Operation *printScope = callOp.getOperation();
559 if (auto funcOp = callOp->getParentOfType<FuncDefOp>()) {
560 printScope = funcOp.getOperation();
561 }
562 // Allows us to print the SSA result value of the call to disambiguate
563 // repeated calls in the same function.
564 AsmState state(printScope);
565 value.printAsOperand(os, state);
566 os << '>';
567}
568
569static bool shouldPrintNamedCallResult(
570 function::CallOp callOp, OpResult callResult, function::FuncDefOp calleeFunc
571) {
572 auto resName = calleeFunc.getResNameAttr(callResult.getResultNumber());
573 if (!resName) {
574 return false;
575 }
576
577 auto parentFunc = callOp->getParentOfType<FuncDefOp>();
578 if (!parentFunc) {
579 return true;
580 }
581
582 bool foundThisCall = false;
583 bool foundDuplicate = false;
584 parentFunc.walk([&](function::CallOp otherCall) {
585 if (foundDuplicate) {
586 return WalkResult::interrupt();
587 }
588
589 auto otherFunc = llvm::dyn_cast_if_present<FuncDefOp>(otherCall.resolveCallable());
590 if (!otherFunc) {
591 return WalkResult::advance();
592 }
593 for (Value otherValue : otherCall->getResults()) {
594 auto otherResult = llvm::cast<OpResult>(otherValue);
595 auto otherResName = otherFunc.getResNameAttr(otherResult.getResultNumber());
596 if (!otherResName || otherResName->getValue() != resName->getValue()) {
597 continue;
598 }
599 if (otherResult == callResult) {
600 foundThisCall = true;
601 continue;
602 }
603 foundDuplicate = true;
604 return WalkResult::interrupt();
605 }
606 return WalkResult::advance();
607 });
608
609 return foundThisCall && !foundDuplicate;
610}
611
612void SourceRef::print(raw_ostream &os) const {
613 if (isConstantFelt()) {
614 os << "<felt.const: " << getConstantIdentityValue() << '>';
615 } else if (isConstantIndex()) {
616 os << "<index: " << *getConstantIndexValue() << '>';
617 } else if (isTemplateConstant()) {
618 auto constRead = getDefiningOp<ConstReadOp>();
619 ensure(succeeded(constRead), "template constant should be backed by a const.read op");
620 auto structDefOp = (*constRead)->getParentOfType<StructDefOp>();
621 ensure(structDefOp, "struct template should have a struct parent");
622 os << '@' << structDefOp.getName() << "<[@" << constRead->getConstName() << "]>";
623 } else {
624 if (isCreateStructOp()) {
625 os << "%self";
626 } else if (isBlockArgument()) {
627 auto blockArg = *getBlockArgument();
628 auto funcOp = llvm::dyn_cast<FuncDefOp>(blockArg.getOwner()->getParentOp());
629 // The entry self argument of a struct constrain function matches the struct value
630 // returned by the corresponding compute function.
631 if (funcOp && funcOp.isStructConstrain() && funcOp.getSelfValueFromConstrain() == blockArg) {
632 os << "%self";
633 } else {
634 std::optional<StringAttr> argName;
635 if (funcOp) {
636 argName = funcOp.getArgNameAttr(blockArg.getArgNumber());
637 }
638 if (argName) {
639 os << argName->getValue();
640 } else {
641 os << "%arg" << *getInputNum();
642 }
643 }
644 } else if (isNonDetOp()) {
645 os << '<' << *getNonDetOp() << '>';
646 } else if (isCallResult()) {
647 auto callOp = *getCallOp();
648 auto callResult = llvm::cast<OpResult>(value);
649 auto callee = resolveCallable<FuncDefOp>(callOp);
650 if (succeeded(callee)) {
651 FuncDefOp calleeFunc = (*callee).get();
652 if (calleeFunc && shouldPrintNamedCallResult(callOp, callResult, calleeFunc)) {
653 auto resName = *calleeFunc.getResNameAttr(callResult.getResultNumber());
654 os << resName.getValue();
655 } else {
656 printCallResultFallback(os, callOp, value);
657 }
658 } else {
659 printCallResultFallback(os, callOp, value);
660 }
661 } else {
662 ensure(isRooted(), "unhandled print case");
663 OpPrintingFlags flags;
664 value.printAsOperand(os, flags);
665 }
666
667 auto res = printSourceStylePath(os, getPath());
668 ensure(succeeded(res), "unhandled path print case");
669 }
670}
671
672FailureOr<DynamicAPInt> SourceRef::getConstantValue(const Field &field) const {
673 if (auto feltConst = getDefiningOp<FeltConstantOp>(); succeeded(feltConst)) {
674 // Honor the declared prime first (per semantics of `felt.const`); only fieldless literals
675 // use the raw fallback. Regardless, the value is then reduced via the specified `Field`.
676 return field.reduce(feltConst->getValue().getReducedValueOrRaw());
677 }
678 return getConstantIndexValue();
679}
680
681DynamicAPInt SourceRef::getConstantIdentityValue() const {
682 if (auto feltConst = getDefiningOp<FeltConstantOp>(); succeeded(feltConst)) {
683 // Identity uses representatives for specified fields, but must distinguish
684 // exact raw literals when no field is available to establish congruence.
685 return feltConst->getValue().getReducedValueOrRaw();
686 }
687 auto index = getConstantIndexValue();
688 ensure(succeeded(index), "expected a numeric constant reference");
689 return *index;
690}
691
692bool SourceRef::operator==(const SourceRef &rhs) const {
693 // This way two felt constants can be equal even if the declared in separate ops.
694 if (isConstantInt() && rhs.isConstantInt()) {
695 DynamicAPInt lhsVal = getConstantIdentityValue(), rhsVal = rhs.getConstantIdentityValue();
696 return getType() == rhs.getType() && lhsVal == rhsVal;
697 }
698 return constant == rhs.constant && value == rhs.value && llvm::equal(getPath(), rhs.getPath());
699}
700
701// required for EquivalenceClasses usage
702std::strong_ordering SourceRef::operator<=>(const SourceRef &rhs) const {
703 auto lhsCategory = getSortCategory();
704 auto rhsCategory = rhs.getSortCategory();
705 if (auto cmp = lhsCategory <=> rhsCategory; cmp != std::strong_ordering::equal) {
706 return cmp;
707 }
708 return compareWithinCategory(rhs, lhsCategory);
709}
710
711size_t SourceRef::Hash::operator()(const SourceRef &val) const {
712 if (val.isConstantInt()) {
713 return llvm::hash_combine(val.getType(), hashDynamicAPInt(val.getConstantIdentityValue()));
714 } else if (val.isTemplateConstant()) {
715 return llvm::hash_value(val.getAsOpaquePointer());
716 } else {
717 ensure(
718 val.isBlockArgument() || val.isCreateStructOp() || val.isNonDetOp() || val.isRooted(),
719 "unhandled SourceRef hash case"
720 );
721
722 size_t hash = llvm::hash_value(val.getAsOpaquePointer());
723 for (const auto &f : val.getPath()) {
724 hash = llvm::hash_combine(hash, f.getHash());
725 }
726 return hash;
727 }
728}
729
730raw_ostream &operator<<(raw_ostream &os, const SourceRef &rhs) {
731 rhs.print(os);
732 return os;
733}
734
735/* SourceRefSet */
736
738 insert(rhs.begin(), rhs.end());
739 return *this;
740}
741
742raw_ostream &operator<<(raw_ostream &os, const SourceRefSet &rhs) {
743 os << "{ ";
744 std::vector<SourceRef> sortedRefs(rhs.begin(), rhs.end());
745 std::sort(sortedRefs.begin(), sortedRefs.end());
746 for (auto it = sortedRefs.begin(); it != sortedRefs.end();) {
747 os << *it;
748 it++;
749 if (it != sortedRefs.end()) {
750 os << ", ";
751 } else {
752 os << ' ';
753 }
754 }
755 os << '}';
756 return os;
757}
758
759} // namespace llzk
This file implements helpers for DynamicAPInt operations and conversions that LLVM does not provide.
Shared utility function implementations for LLZK lowering passes.
This file defines methods symbol lookup across LLZK operations and included files.
Information about the prime finite field used for the interval analysis.
Definition Field.h:36
llvm::DynamicAPInt reduce(const llvm::DynamicAPInt &i) const
Returns the canonical representative of i mod p in [0, p).
Defines an index into an LLZK object.
Definition SourceRef.h:43
std::strong_ordering operator<=>(const SourceRefIndex &rhs) const
bool isIndexRange() const
Definition SourceRef.h:88
bool isIndex() const
Definition SourceRef.h:82
bool isMember() const
Definition SourceRef.h:63
bool isPodRecord() const
Definition SourceRef.h:75
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.
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(component::MemberDefOp f)
Definition SourceRef.h:47
llvm::StringRef getPodRecordName() const
Definition SourceRef.h:80
SourceRefSet & join(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 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< 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 operator==(const SourceRef &rhs) const
bool isConstantFelt() const
Definition SourceRef.h:244
bool isRooted() const
Definition SourceRef.h:267
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
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
mlir::FailureOr< mlir::BlockArgument > getBlockArgument() const
Definition SourceRef.h:282
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
bool isTemplateConstant() const
Return whether this reference originates from a template constant read.
Definition SourceRef.h:252
bool isConstant() const
Definition SourceRef.h:256
bool isConstantInt() const
Definition SourceRef.h:257
bool isCreateStructOp() const
Definition SourceRef.h:296
mlir::Type getType() const
static constexpr ::llvm::StringLiteral getOperationName()
Definition Ops.h.inc:1367
::llzk::function::FuncDefOp getConstrainFuncOp()
Gets the FuncDefOp that defines the constrain function in this structure, if present,...
Definition Ops.cpp:470
::mlir::FailureOr< SymbolLookupResult< StructDefOp > > getDefinition(::mlir::SymbolTableCollection &symbolTable, ::mlir::Operation *op, bool reportMissing=true) const
Gets the struct op that defines this struct.
Definition Types.cpp:26
::mlir::SymbolRefAttr getCallee()
Definition Ops.cpp.inc:566
::mlir::Value getSelfValueFromCompute()
Return the "self" value (i.e.
Definition Ops.cpp:502
::std::optional<::mlir::StringAttr > getResNameAttr(unsigned index)
Return the function.res_name attribute for the result at the given index.
Definition Ops.cpp:333
bool isStructCompute()
Return true iff the function is within a StructDefOp and named FUNC_NAME_COMPUTE.
Definition Ops.h.inc:1096
::llvm::ArrayRef<::llzk::pod::RecordAttr > getRecords() const
ExpressionValue mod(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
hash_code hashDynamicAPInt(const DynamicAPInt &value)
void ensure(bool condition, const llvm::Twine &errMsg)
FailureOr< ModuleOp > getRootModule(Operation *from)
ExpressionValue cmp(const llvm::SMTSolverRef &solver, CmpOp op, const ExpressionValue &lhs, const ExpressionValue &rhs)
Interval operator<<(const Interval &lhs, const Interval &rhs)
mlir::FailureOr< SymbolLookupResult< T > > resolveCallable(mlir::SymbolTableCollection &symbolTable, mlir::CallOpInterface call)
Based on mlir::CallOpInterface::resolveCallable, but using LLZK lookup helpers.
std::vector< SourceRef > getAllChildren(SymbolTableCollection &, ModuleOp, ArrayType arrayTy, const SourceRef &root)
mlir::FailureOr< SymbolLookupResultUntyped > lookupSymbolIn(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, Within &&lookupWithin, mlir::Operation *origin, bool reportMissing=true)
SymbolLookupResult< StructDefOp > getStructDef(SymbolTableCollection &tables, ModuleOp mod, StructType ty)
Lookup a StructDefOp from a given StructType.
Order named operations by source location, using the symbol name to break ties or when source locatio...
Definition Compare.h:75
size_t operator()(const SourceRefIndex &c) const
size_t operator()(const SourceRef &val) const