23#include <mlir/IR/BuiltinOps.h>
24#include <mlir/IR/Dominance.h>
26#include <llvm/ADT/DenseMap.h>
27#include <llvm/ADT/DenseMapInfo.h>
28#include <llvm/ADT/DenseSet.h>
29#include <llvm/ADT/STLExtras.h>
30#include <llvm/ADT/ScopeExit.h>
31#include <llvm/ADT/SmallVector.h>
32#include <llvm/ADT/StringMap.h>
33#include <llvm/Support/Debug.h>
34#include <llvm/Support/raw_ostream.h>
43#define GEN_PASS_DEF_POLYLOWERINGPASS
55#define DEBUG_TYPE "llzk-poly-lowering-pass"
56#define AUXILIARY_MEMBER_PREFIX "__llzk_poly_lowering_pass_aux_member_"
61 std::string auxMemberName;
67struct MutableContainmentElement {
72enum class AuxAssignmentVisitState : uint8_t {
78class DegreeComputationError :
public std::runtime_error {
80 DegreeComputationError(Location errorLoc,
const std::string &message)
81 : std::runtime_error(message), loc(errorLoc) {}
83 Location getLoc()
const {
return loc; }
90 using Base = PolyLoweringPassBase<PassImpl>;
93 unsigned auxCounter = 0;
96 static void addAuxDependency(
97 unsigned dep,
unsigned owner, DenseSet<unsigned> &seenDeps, SmallVectorImpl<unsigned> &deps
102 if (seenDeps.insert(dep).second) {
108 static void collectAuxDependencies(
109 Value val,
unsigned owner,
const DenseMap<Value, unsigned> &auxValueToIndex,
110 const llvm::StringMap<unsigned> &auxNameToIndex, DenseSet<Value> &visitedValues,
111 DenseSet<unsigned> &seenDeps, SmallVectorImpl<unsigned> &deps
115 if (!val || !visitedValues.insert(val).second) {
119 if (
auto it = auxValueToIndex.find(val); it != auxValueToIndex.end()) {
120 addAuxDependency(it->second, owner, seenDeps, deps);
123 if (Operation *defOp = val.getDefiningOp()) {
124 if (
auto readOp = llvm::dyn_cast<MemberReadOp>(defOp)) {
125 auto it = auxNameToIndex.find(readOp.getMemberName());
126 if (it != auxNameToIndex.end()) {
127 addAuxDependency(it->second, owner, seenDeps, deps);
131 for (Value operand : defOp->getOperands()) {
132 collectAuxDependencies(
133 operand, owner, auxValueToIndex, auxNameToIndex, visitedValues, seenDeps, deps
140 static LogicalResult visitAuxAssignment(
141 unsigned idx, ArrayRef<SmallVector<unsigned>> deps,
142 SmallVectorImpl<AuxAssignmentVisitState> &visitState, SmallVectorImpl<unsigned> &ordered,
143 ArrayRef<AuxAssignment> auxAssignments
145 if (visitState[idx] == AuxAssignmentVisitState::Done) {
148 if (visitState[idx] == AuxAssignmentVisitState::Visiting) {
149 return emitError(auxAssignments[idx].computedValue.getLoc())
150 <<
"poly lowering generated cyclic auxiliary dependency involving @"
151 << auxAssignments[idx].auxMemberName;
154 visitState[idx] = AuxAssignmentVisitState::Visiting;
156 for (
unsigned dep : deps[idx]) {
157 if (failed(visitAuxAssignment(dep, deps, visitState, ordered, auxAssignments))) {
161 visitState[idx] = AuxAssignmentVisitState::Done;
162 ordered.push_back(idx);
168 orderAuxAssignments(ArrayRef<AuxAssignment> auxAssignments, SmallVectorImpl<unsigned> &ordered) {
169 DenseMap<Value, unsigned> auxValueToIndex;
170 llvm::StringMap<unsigned> auxNameToIndex;
171 auxValueToIndex.reserve(auxAssignments.size());
172 for (
auto [idx, assign] : llvm::enumerate(auxAssignments)) {
173 if (assign.auxValue) {
174 auxValueToIndex[assign.auxValue] = idx;
176 auxNameToIndex[assign.auxMemberName] = idx;
179 SmallVector<SmallVector<unsigned>> deps(auxAssignments.size());
180 for (
auto [idx, assign] : llvm::enumerate(auxAssignments)) {
181 DenseSet<Value> visitedValues;
182 DenseSet<unsigned> seenDeps;
183 collectAuxDependencies(
184 assign.computedValue, idx, auxValueToIndex, auxNameToIndex, visitedValues, seenDeps,
189 SmallVector<AuxAssignmentVisitState> visitState(
190 auxAssignments.size(), AuxAssignmentVisitState::Unvisited
192 for (
unsigned idx = 0, e = auxAssignments.size(); idx < e; ++idx) {
193 if (failed(visitAuxAssignment(idx, deps, visitState, ordered, auxAssignments))) {
201 unsigned getDegree(Value val, DenseMap<Value, unsigned> &memo) {
202 if (
auto it = memo.find(val); it != memo.end()) {
206 if (llvm::isa<BlockArgument>(val)) {
207 return memo[val] = 1;
209 if (Operation *defOp = val.getDefiningOp()) {
210 if (llvm::isa<FeltConstantOp>(defOp)) {
211 return memo[val] = 0;
213 if (llvm::isa<NonDetOp, MemberReadOp>(defOp)) {
214 return memo[val] = 1;
216 if (
auto add = llvm::dyn_cast<AddFeltOp>(defOp)) {
217 return memo[val] = std::max(getDegree(
add.getLhs(), memo), getDegree(
add.getRhs(), memo));
219 if (
auto sub = llvm::dyn_cast<SubFeltOp>(defOp)) {
220 return memo[val] = std::max(getDegree(
sub.getLhs(), memo), getDegree(
sub.getRhs(), memo));
222 if (
auto mul = llvm::dyn_cast<MulFeltOp>(defOp)) {
223 return memo[val] = getDegree(
mul.getLhs(), memo) + getDegree(
mul.getRhs(), memo);
225 if (
auto div = llvm::dyn_cast<DivFeltOp>(defOp)) {
226 return memo[val] = getDegree(
div.getLhs(), memo) + getDegree(
div.getRhs(), memo);
228 if (
auto neg = llvm::dyn_cast<NegFeltOp>(defOp)) {
229 return memo[val] = getDegree(
neg.getOperand(), memo);
231 if (
auto call = llvm::dyn_cast<CallOp>(defOp)) {
233 llvm::raw_string_ostream(message)
235 <<
"' in degree computation. Try running '-llzk-inline-free-functions' first.";
236 throw DegreeComputationError(val.getLoc(), message);
241 llvm::raw_string_ostream(message) <<
"Unhandled value in degree computation: " << val;
242 throw DegreeComputationError(val.getLoc(), message);
245 Value lowerExpression(
247 DominanceInfo &dominanceInfo, DenseMap<Value, unsigned> °reeMemo,
248 DenseMap<Value, Value> &rewrites, SmallVector<AuxAssignment> &auxAssignments
250 auto rewriteIt = rewrites.find(val);
251 if (rewriteIt != rewrites.end() && dominanceInfo.properlyDominates(rewriteIt->second, useOp)) {
252 return rewriteIt->second;
255 auto cacheIdentityRewriteIfAbsent = [&rewrites, &val]() {
257 if (!rewrites.contains(val)) {
262 unsigned degree = getDegree(val, degreeMemo);
263 if (degree <= maxDegree) {
267 cacheIdentityRewriteIfAbsent();
272 auto lowerBinaryRoot = [&](
auto op) -> Value {
273 Value lhs = lowerExpression(
274 op.getLhs(), structDef, constrainFunc, op.getOperation(), dominanceInfo, degreeMemo,
275 rewrites, auxAssignments
277 Value rhs = lowerExpression(
278 op.getRhs(), structDef, constrainFunc, op.getOperation(), dominanceInfo, degreeMemo,
279 rewrites, auxAssignments
282 if (lhs != op.getLhs()) {
283 op.getLhsMutable().set(lhs);
285 if (rhs != op.getRhs()) {
286 op.getRhsMutable().set(rhs);
288 degreeMemo[val] = std::max(getDegree(lhs, degreeMemo), getDegree(rhs, degreeMemo));
289 cacheIdentityRewriteIfAbsent();
293 Operation *defOp = val.getDefiningOp();
294 if (
auto addOp = llvm::dyn_cast_if_present<AddFeltOp>(defOp)) {
295 return lowerBinaryRoot(addOp);
296 }
else if (
auto subOp = llvm::dyn_cast_if_present<SubFeltOp>(defOp)) {
297 return lowerBinaryRoot(subOp);
298 }
else if (
auto negOp = llvm::dyn_cast_if_present<NegFeltOp>(defOp)) {
299 Value operand = lowerExpression(
300 negOp.getOperand(), structDef, constrainFunc, negOp.getOperation(), dominanceInfo,
301 degreeMemo, rewrites, auxAssignments
304 if (operand != negOp.getOperand()) {
305 negOp.getOperandMutable().set(operand);
307 degreeMemo[val] = getDegree(operand, degreeMemo);
308 cacheIdentityRewriteIfAbsent();
310 }
else if (
auto mulOp = llvm::dyn_cast_if_present<MulFeltOp>(defOp)) {
312 Value lhs = lowerExpression(
313 mulOp.getLhs(), structDef, constrainFunc, mulOp.getOperation(), dominanceInfo, degreeMemo,
314 rewrites, auxAssignments
316 Value rhs = lowerExpression(
317 mulOp.getRhs(), structDef, constrainFunc, mulOp.getOperation(), dominanceInfo, degreeMemo,
318 rewrites, auxAssignments
321 unsigned lhsDeg = getDegree(lhs, degreeMemo);
322 unsigned rhsDeg = getDegree(rhs, degreeMemo);
324 OpBuilder builder(mulOp.getOperation()->getBlock(), ++Block::iterator(mulOp));
326 bool eraseMul = lhsDeg + rhsDeg > maxDegree;
328 if (lhs == rhs && eraseMul) {
333 builder, lhs.getLoc(), lhs.getType(), selfVal, auxMember.getNameAttr()
335 auxAssignments.push_back({auxName, lhs, auxVal});
336 Location loc = builder.getFusedLoc({auxVal.getLoc(), lhs.getLoc()});
340 degreeMemo[auxVal] = 1;
341 rewrites[lhs] = auxVal;
342 rewrites[rhs] = auxVal;
353 while (lhsDeg + rhsDeg > maxDegree) {
354 Value &toFactor = (lhsDeg >= rhsDeg) ? lhs : rhs;
362 builder, toFactor.getLoc(), toFactor.getType(), selfVal, auxMember.getNameAttr()
366 Location loc = builder.getFusedLoc({auxVal.getLoc(), toFactor.getLoc()});
368 auxAssignments.push_back({auxName, toFactor, auxVal});
370 rewrites[toFactor] = auxVal;
371 degreeMemo[auxVal] = 1;
379 lhsDeg = getDegree(lhs, degreeMemo);
380 rhsDeg = getDegree(rhs, degreeMemo);
386 mulOp->replaceAllUsesWith(mulVal);
391 degreeMemo[mulVal] = lhsDeg + rhsDeg;
392 rewrites[val] = mulVal;
398 cacheIdentityRewriteIfAbsent();
402 Value materializeCallArgument(
404 DominanceInfo &dominanceInfo, DenseMap<Value, unsigned> °reeMemo,
405 DenseMap<Value, Value> &rewrites, SmallVector<AuxAssignment> &auxAssignments
407 Value loweredVal = lowerExpression(
408 val, structDef, constrainFunc, callOp.getOperation(), dominanceInfo, degreeMemo, rewrites,
411 DenseMap<Value, unsigned> checkMemo;
412 if (getDegree(loweredVal, checkMemo) <= 1) {
421 OpBuilder builder(callOp);
424 builder, loweredVal.getLoc(), loweredVal.getType(), selfVal, auxMember.getNameAttr()
427 Location loc = builder.getFusedLoc({auxVal.getLoc(), loweredVal.getLoc()});
429 auxAssignments.push_back({auxName, loweredVal, auxVal});
431 degreeMemo[auxVal] = 1;
432 rewrites[loweredVal] = auxVal;
433 rewrites[val] = auxVal;
437 LogicalResult checkEqualityDegrees(
FuncDefOp constrainFunc) {
438 auto res = constrainFunc.walk([
this](
EmitEqualityOp eqOp) -> WalkResult {
439 Value lhs = eqOp.
getLhs();
440 Value rhs = eqOp.
getRhs();
441 if (llvm::isa<FeltType>(lhs.getType()) && llvm::isa<FeltType>(rhs.getType())) {
442 DenseMap<Value, unsigned> checkMemo;
443 unsigned lhsDegree = getDegree(lhs, checkMemo);
444 unsigned rhsDegree = getDegree(rhs, checkMemo);
447 return eqOp.emitOpError().append(
448 "poly lowering postcondition failed: equality operand degree exceeds max-degree ",
449 maxDegree.getValue(),
" (lhs degree ", lhsDegree,
", rhs degree ", rhsDegree,
')'
453 return WalkResult::advance();
455 return failure(res.wasInterrupted());
458 LogicalResult checkStructConstrainCallArguments(FuncDefOp constrainFunc) {
459 auto res = constrainFunc.walk([
this](CallOp callOp) -> WalkResult {
462 if (!llvm::isa<FeltType>(arg.getType())) {
465 DenseMap<Value, unsigned> checkMemo;
466 unsigned argDegree = getDegree(arg, checkMemo);
468 return callOp.emitOpError()
469 <<
"poly lowering postcondition failed: "
470 "struct constrain call argument degree exceeds 1 (argument degree "
475 return WalkResult::advance();
477 return failure(res.wasInterrupted());
481 static bool isFeltArray(Type type) {
482 if (
auto arrayType = llvm::dyn_cast<ArrayType>(type)) {
483 return llvm::isa<FeltType>(arrayType.getElementType());
489 return containOp.emitOpError()
490 <<
"poly lowering cannot resolve containment RHS row write history: " <<
detail;
494 template <
typename IndexRange,
typename PrefixRange>
495 static bool indexStartsWith(
const IndexRange &index,
const PrefixRange &prefix) {
496 auto indexIt = index.begin();
497 for (Attribute attr : prefix) {
498 if (indexIt == index.end() || *indexIt != attr) {
507 template <
typename LhsRange,
typename RhsRange>
508 static bool prefixesCanOverlap(
const LhsRange &lhs,
const RhsRange &rhs) {
509 auto lhsIt = lhs.begin();
510 auto rhsIt = rhs.begin();
511 while (lhsIt != lhs.end() && rhsIt != rhs.end()) {
512 if (*lhsIt != *rhsIt) {
522 static ArrayAttr dropIndexPrefix(MLIRContext *ctx, ArrayAttr index,
size_t prefixSize) {
523 SmallVector<Attribute> attrs;
525 for (Attribute attr : index) {
526 if (idx++ >= prefixSize) {
527 attrs.push_back(attr);
530 return ArrayAttr::get(ctx, attrs);
534 template <
typename PrefixRange>
535 static ArrayAttr appendIndex(MLIRContext *ctx,
const PrefixRange &prefix, ArrayAttr suffix) {
536 SmallVector<Attribute> attrs;
537 for (Attribute attr : prefix) {
538 attrs.push_back(attr);
540 for (Attribute attr : suffix) {
541 attrs.push_back(attr);
543 return ArrayAttr::get(ctx, attrs);
547 static inline ArrayAttr getStaticAccessIndex(Operation *op) {
548 return llvm::cast<ArrayAccessOpInterface>(op).indexOperandsToAttributeArray();
553 static std::optional<SmallVector<ArrayAttr>>
554 getViewIndices(ArrayType arrayType, ArrayRef<Attribute> viewPrefix) {
560 SmallVector<ArrayAttr> viewIndices;
561 MLIRContext *ctx = arrayType.getContext();
562 for (ArrayAttr index : *allIndices) {
563 if (indexStartsWith(index, viewPrefix)) {
564 viewIndices.push_back(dropIndexPrefix(ctx, index, viewPrefix.size()));
573 static LogicalResult collectMutableContainmentElements(
574 Value arrayValue, Operation *boundaryOp, ArrayRef<Attribute> viewPrefix,
575 EmitContainmentOp containOp, DenseSet<Value> &activeArrays,
576 SmallVectorImpl<MutableContainmentElement> &elements
578 auto arrayType = llvm::dyn_cast<ArrayType>(arrayValue.getType());
579 if (!arrayType || !llvm::isa<FeltType>(arrayType.
getElementType())) {
583 DenseMap<Attribute, OpOperand *> finalElements;
584 if (failed(collectMutableContainmentElementMap(
585 arrayValue, boundaryOp, viewPrefix, containOp, activeArrays, finalElements
590 std::optional<SmallVector<ArrayAttr>> viewIndices = getViewIndices(arrayType, viewPrefix);
592 if (finalElements.empty()) {
595 return emitAmbiguousContainmentRhs(containOp,
"array shape is not static");
598 for (ArrayAttr relativeIndex : *viewIndices) {
599 auto elementIt = finalElements.find(relativeIndex);
600 if (elementIt != finalElements.end()) {
601 elements.push_back(MutableContainmentElement {relativeIndex, elementIt->second});
610 static std::optional<std::pair<Value, FlatSymbolRefAttr>> resolveStructReadSource(Value v) {
611 if (
auto readOp = v.getDefiningOp<MemberReadOp>()) {
612 return std::make_pair(readOp.getComponent(), readOp.getMemberNameAttr());
622 static bool mayAliasArraySource(Value a, Value b) {
626 auto srcA = resolveStructReadSource(a);
630 auto srcB = resolveStructReadSource(b);
634 return srcA->first == srcB->first && srcA->second == srcB->second;
640 static LogicalResult collectMutableContainmentElementMap(
641 Value arrayValue, Operation *boundaryOp, ArrayRef<Attribute> viewPrefix,
642 EmitContainmentOp containOp, DenseSet<Value> &activeArrays,
643 DenseMap<Attribute, OpOperand *> &finalElements
647 auto arrayType = llvm::dyn_cast<ArrayType>(arrayValue.getType());
648 if (!arrayType || !llvm::isa<FeltType>(arrayType.
getElementType())) {
651 if (!boundaryOp || !boundaryOp->getBlock()) {
652 return emitAmbiguousContainmentRhs(containOp,
"missing observation block");
654 if (!activeArrays.insert(arrayValue).second) {
655 return emitAmbiguousContainmentRhs(containOp,
"cyclic array update");
657 auto cleanup = llvm::scope_exit([&]() { activeArrays.erase(arrayValue); });
659 MLIRContext *ctx = arrayType.getContext();
661 if (
auto arrayOp = arrayValue.getDefiningOp<CreateArrayOp>()) {
662 MutableOperandRange elementOperands = arrayOp.getElementsMutable();
663 if (!elementOperands.empty()) {
666 return emitAmbiguousContainmentRhs(containOp,
"array.new shape is not static");
668 assert(allIndices->size() == elementOperands.size() &&
"array.new verifier mismatch");
670 auto *indexIt = allIndices->begin();
671 for (OpOperand &elementOperand : elementOperands) {
672 ArrayAttr index = *indexIt++;
673 if (indexStartsWith(index, viewPrefix)) {
674 finalElements[dropIndexPrefix(ctx, index, viewPrefix.size())] = &elementOperand;
678 }
else if (
auto extractOp = arrayValue.getDefiningOp<ExtractArrayOp>()) {
679 ArrayAttr extractIndex = getStaticAccessIndex(extractOp.getOperation());
681 return emitAmbiguousContainmentRhs(containOp,
"array.extract index is not static");
684 SmallVector<Attribute> sourcePrefix;
685 for (Attribute attr : extractIndex) {
686 sourcePrefix.push_back(attr);
688 for (Attribute attr : viewPrefix) {
689 sourcePrefix.push_back(attr);
692 if (failed(collectMutableContainmentElementMap(
693 extractOp.getArrRef(), extractOp.getOperation(), sourcePrefix, containOp,
694 activeArrays, finalElements
700 for (Operation &op : *boundaryOp->getBlock()) {
701 if (&op == boundaryOp) {
705 if (
auto writeOp = llvm::dyn_cast<WriteArrayOp>(&op)) {
706 if (!mayAliasArraySource(writeOp.getArrRef(), arrayValue)) {
710 ArrayAttr writeIndex = getStaticAccessIndex(writeOp.getOperation());
712 return emitAmbiguousContainmentRhs(containOp,
"array.write index is not static");
714 if (indexStartsWith(writeIndex, viewPrefix)) {
715 finalElements[dropIndexPrefix(ctx, writeIndex, viewPrefix.size())] =
716 &writeOp.getRvalueMutable();
721 if (
auto insertOp = llvm::dyn_cast<InsertArrayOp>(&op)) {
722 if (!mayAliasArraySource(insertOp.getArrRef(), arrayValue)) {
726 ArrayAttr insertIndex = getStaticAccessIndex(insertOp.getOperation());
728 return emitAmbiguousContainmentRhs(containOp,
"array.insert index is not static");
730 if (!prefixesCanOverlap(insertIndex, viewPrefix)) {
734 auto rvalueType = llvm::dyn_cast<ArrayType>(insertOp.getRvalue().getType());
735 if (!rvalueType || !llvm::isa<FeltType>(rvalueType.getElementType())) {
739 std::optional<SmallVector<ArrayAttr>> rvalueIndices = rvalueType.getSubelementIndices();
740 if (!rvalueIndices) {
741 return emitAmbiguousContainmentRhs(containOp,
"array.insert rvalue shape is not static");
744 SmallVector<MutableContainmentElement> insertedElements;
745 if (failed(collectMutableContainmentElements(
746 insertOp.getRvalue(), insertOp.getOperation(), ArrayRef<Attribute> {}, containOp,
747 activeArrays, insertedElements
752 DenseMap<Attribute, OpOperand *> insertedElementMap;
753 for (MutableContainmentElement element : insertedElements) {
754 insertedElementMap[element.index] = element.operand;
757 for (ArrayAttr rvalueIndex : *rvalueIndices) {
758 ArrayAttr targetIndex = appendIndex(ctx, insertIndex, rvalueIndex);
759 if (!indexStartsWith(targetIndex, viewPrefix)) {
763 ArrayAttr relativeIndex = dropIndexPrefix(ctx, targetIndex, viewPrefix.size());
764 auto elementIt = insertedElementMap.find(rvalueIndex);
765 if (elementIt == insertedElementMap.end()) {
766 finalElements.erase(relativeIndex);
769 finalElements[relativeIndex] = elementIt->second;
779 LogicalResult lowerContainmentRhsFeltOperand(
780 OpOperand &operand, StructDefOp structDef, FuncDefOp constrainFunc,
781 DominanceInfo &dominanceInfo, DenseMap<Value, unsigned> °reeMemo,
782 DenseMap<Value, Value> &rewrites, SmallVector<AuxAssignment> &auxAssignments
784 Value value = operand.get();
785 if (!llvm::isa<FeltType>(value.getType())) {
789 unsigned degree = getDegree(value, degreeMemo);
790 if (degree > maxDegree) {
791 operand.set(lowerExpression(
792 value, structDef, constrainFunc, operand.getOwner(), dominanceInfo, degreeMemo, rewrites,
801 LogicalResult lowerContainmentRhsValue(
802 OpOperand &operand, StructDefOp structDef, FuncDefOp constrainFunc,
803 DominanceInfo &dominanceInfo, DenseMap<Value, unsigned> °reeMemo,
804 DenseMap<Value, Value> &rewrites, SmallVector<AuxAssignment> &auxAssignments,
805 EmitContainmentOp containOp
807 Value value = operand.get();
808 if (llvm::isa<FeltType>(value.getType())) {
809 return lowerContainmentRhsFeltOperand(
810 operand, structDef, constrainFunc, dominanceInfo, degreeMemo, rewrites, auxAssignments
814 if (!isFeltArray(value.getType())) {
818 DenseSet<Value> activeArrays;
819 SmallVector<MutableContainmentElement> elements;
820 if (failed(collectMutableContainmentElements(
821 value, containOp.getOperation(), ArrayRef<Attribute> {}, containOp, activeArrays,
827 for (MutableContainmentElement element : elements) {
828 if (failed(lowerContainmentRhsFeltOperand(
829 *element.operand, structDef, constrainFunc, dominanceInfo, degreeMemo, rewrites,
841 LogicalResult checkContainmentRhsFeltValue(
842 Value value, EmitContainmentOp containOp, DenseMap<Value, unsigned> &checkMemo
844 if (!llvm::isa<FeltType>(value.getType())) {
848 unsigned valueDegree = getDegree(value, checkMemo);
849 if (valueDegree <= maxDegree) {
853 return containOp.emitOpError()
854 <<
"poly lowering postcondition failed: "
855 "containment RHS element degree exceeds max-degree "
856 << maxDegree.getValue() <<
" (element degree " << valueDegree <<
')';
861 LogicalResult checkContainmentRhsValue(
862 Value value, EmitContainmentOp containOp, DenseMap<Value, unsigned> &checkMemo
864 if (llvm::isa<FeltType>(value.getType())) {
865 return checkContainmentRhsFeltValue(value, containOp, checkMemo);
868 if (!isFeltArray(value.getType())) {
872 DenseSet<Value> activeArrays;
873 SmallVector<MutableContainmentElement> elements;
874 auto res = collectMutableContainmentElements(
875 value, containOp.getOperation(), {}, containOp, activeArrays, elements
881 for (MutableContainmentElement element : elements) {
882 if (failed(checkContainmentRhsFeltValue(element.operand->get(), containOp, checkMemo))) {
891 LogicalResult checkContainmentRhsDegrees(FuncDefOp constrainFunc) {
892 auto res = constrainFunc.walk([&](EmitContainmentOp containOp) -> WalkResult {
893 DenseMap<Value, unsigned> memo;
894 return checkContainmentRhsValue(containOp.
getRhs(), containOp, memo);
896 return failure(res.wasInterrupted());
899 LogicalResult lowerInConstrain(
900 StructDefOp structDef, FuncDefOp constrainFunc, SmallVector<AuxAssignment> &auxAssignments
903 DenseMap<Value, unsigned> degreeMemo;
904 DenseMap<Value, Value> rewrites;
905 DominanceInfo dominanceInfo(constrainFunc);
908 constrainFunc.walk([&](EmitEqualityOp constraintOp) {
909 if (!llvm::isa<FeltType>(constraintOp.
getLhs().getType()) ||
910 !llvm::isa<FeltType>(constraintOp.
getRhs().getType())) {
916 unsigned degreeLhs = getDegree(lhsOperand.get(), degreeMemo);
917 unsigned degreeRhs = getDegree(rhsOperand.get(), degreeMemo);
919 if (degreeLhs > maxDegree) {
920 Value loweredExpr = lowerExpression(
921 lhsOperand.get(), structDef, constrainFunc, constraintOp.getOperation(), dominanceInfo,
922 degreeMemo, rewrites, auxAssignments
924 lhsOperand.set(loweredExpr);
926 if (degreeRhs > maxDegree) {
927 Value loweredExpr = lowerExpression(
928 rhsOperand.get(), structDef, constrainFunc, constraintOp.getOperation(), dominanceInfo,
929 degreeMemo, rewrites, auxAssignments
931 rhsOperand.set(loweredExpr);
936 auto res = constrainFunc.walk([&](EmitContainmentOp containOp) -> WalkResult {
937 return lowerContainmentRhsValue(
938 containOp.
getRhsMutable(), structDef, constrainFunc, dominanceInfo, degreeMemo, rewrites,
939 auxAssignments, containOp
942 if (res.wasInterrupted()) {
947 constrainFunc.walk([&](CallOp callOp) {
949 SmallVector<Value> newOperands = llvm::to_vector(callOp.
getArgOperands());
950 bool modified =
false;
952 for (Value &arg : newOperands) {
953 if (!llvm::isa<FeltType>(arg.getType())) {
957 DenseMap<Value, unsigned> callMemo;
958 if (getDegree(arg, callMemo) > 1) {
959 arg = materializeCallArgument(
960 arg, structDef, constrainFunc, callOp, dominanceInfo, degreeMemo, rewrites,
968 OpBuilder builder(callOp);
970 builder, callOp.getLoc(), callOp.getResultTypes(), callOp.
getCallee(),
984 static LogicalResult rebuildInCompute(
985 FuncDefOp constrainFunc, FuncDefOp computeFunc,
986 const SmallVector<AuxAssignment> &auxAssignments
988 SmallVector<unsigned> orderedAuxAssignments;
989 orderedAuxAssignments.reserve(auxAssignments.size());
990 if (failed(orderAuxAssignments(auxAssignments, orderedAuxAssignments))) {
994 for (Block &computeBlock : computeFunc.
getBody()) {
995 auto returnOp = llvm::dyn_cast<ReturnOp>(computeBlock.getTerminator());
1000 Value selfVal = returnOp.getOperands().front();
1001 OpBuilder builder(returnOp);
1002 DenseMap<Value, Value> rebuildMemo;
1005 for (
unsigned assignIdx : orderedAuxAssignments) {
1006 const auto &assign = auxAssignments[assignIdx];
1013 builder, assign.computedValue.getLoc(), selfVal,
1014 builder.getStringAttr(assign.auxMemberName), rebuiltExpr
1016 if (assign.auxValue) {
1019 rebuildMemo[assign.auxValue] = rebuiltExpr;
1026 void runOnOperation()
override {
1027 ModuleOp moduleOp = getOperation();
1030 if (maxDegree < 2) {
1031 moduleOp.emitError()
1032 .append(
"Invalid max degree: ", maxDegree.getValue(),
". Must be >= 2.")
1034 signalPassFailure();
1038 auto moduleRes = moduleOp.walk([
this](StructDefOp structDef) -> WalkResult {
1041 return WalkResult::interrupt();
1045 if (!constrainFunc) {
1046 return structDef.emitOpError() <<
'"' << structDef.getName() <<
"\" doesn't have a \"@"
1051 return WalkResult::interrupt();
1056 return structDef.emitOpError() <<
'"' << structDef.getName() <<
"\" doesn't have a \"@"
1059 SmallVector<AuxAssignment> auxAssignments;
1060 if (failed(lowerInConstrain(structDef, constrainFunc, auxAssignments))) {
1061 return WalkResult::interrupt();
1063 if (computeFunc.isExternal() && !auxAssignments.empty()) {
1064 return computeFunc.emitOpError()
1065 <<
"poly lowering requires a compute body to materialize auxiliary values";
1068 if (failed(checkEqualityDegrees(constrainFunc))) {
1069 return WalkResult::interrupt();
1072 if (failed(checkContainmentRhsDegrees(constrainFunc))) {
1073 return WalkResult::interrupt();
1076 if (failed(checkStructConstrainCallArguments(constrainFunc))) {
1077 return WalkResult::interrupt();
1080 if (failed(rebuildInCompute(constrainFunc, computeFunc, auxAssignments))) {
1081 return WalkResult::interrupt();
1084 }
catch (
const DegreeComputationError &err) {
1085 mlir::emitError(err.getLoc()) << err.what();
1086 return WalkResult::interrupt();
1089 return WalkResult::advance();
1092 if (moduleRes.wasInterrupted()) {
1093 signalPassFailure();
#define AUXILIARY_MEMBER_PREFIX
Shared utility function implementations for LLZK lowering passes.
std::optional<::llvm::SmallVector<::mlir::ArrayAttr > > getSubelementIndices() const
Return a list of all valid indices for this ArrayType.
::mlir::Type getElementType() const
static MemberReadOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type resultType, ::mlir::Value component, ::mlir::StringAttr member)
static MemberWriteOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Value component, ::mlir::FlatSymbolRefAttr member_name, ::mlir::Value val)
::llzk::function::FuncDefOp getConstrainFuncOp()
Gets the FuncDefOp that defines the constrain function in this structure, if present,...
::llzk::function::FuncDefOp getComputeFuncOp()
Gets the FuncDefOp that defines the compute function in this structure, if present,...
::mlir::TypedValue<::mlir::Type > getRhs()
::mlir::OpOperand & getRhsMutable()
::mlir::OpOperand & getRhsMutable()
::mlir::TypedValue<::mlir::Type > getLhs()
static EmitEqualityOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Value lhs, ::mlir::Value rhs)
::mlir::OpOperand & getLhsMutable()
::mlir::TypedValue<::mlir::Type > getRhs()
static MulFeltOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::Type result, ::mlir::Value lhs, ::mlir::Value rhs)
bool calleeIsStructConstrain()
Return true iff the callee function name is FUNC_NAME_CONSTRAIN within a StructDefOp.
::mlir::SymbolRefAttr getCallee()
::llvm::ArrayRef< int32_t > getNumDimsPerMap()
::mlir::Operation::operand_range getArgOperands()
static CallOp create(::mlir::OpBuilder &builder, ::mlir::Location location, ::mlir::TypeRange resultTypes, ::mlir::SymbolRefAttr callee, ::mlir::ValueRange argOperands={}, ::llvm::ArrayRef<::mlir::Attribute > templateParams={})
static constexpr ::llvm::StringLiteral getOperationName()
::mlir::OperandRangeRange getMapOperands()
::mlir::Value getSelfValueFromConstrain()
Return the "self" value (i.e.
::mlir::Region & getBody()
::mlir::Pass::Option< unsigned > maxDegree
Common interface for constant parameter-like symbol operations that can be defined within a TemplateO...
ExpressionValue add(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
constexpr char FUNC_NAME_COMPUTE[]
Symbol name for the witness generation (and resp.
Value rebuildExprInCompute(Value val, FuncDefOp computeFunc, OpBuilder &builder, DenseMap< Value, Value > &memo)
void replaceSubsequentUsesWith(Value oldVal, Value newVal, Operation *afterOp)
constexpr char FUNC_NAME_CONSTRAIN[]
ExpressionValue neg(const llvm::SMTSolverRef &solver, const ExpressionValue &val)
MemberDefOp addAuxMember(StructDefOp structDef, StringRef name, Type type)
ExpressionValue div(const llvm::SMTSolverRef &solver, Operation *op, const ExpressionValue &lhs, const ExpressionValue &rhs)
ExpressionValue mul(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
LogicalResult checkFuncBodyIsStraightLine(FuncDefOp func, StringRef passName)
ExpressionValue sub(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
LogicalResult checkForAuxMemberConflicts(StructDefOp structDef, StringRef prefix)