21#include <mlir/Analysis/DataFlow/DeadCodeAnalysis.h>
22#include <mlir/Dialect/SCF/IR/SCF.h>
24#include <llvm/ADT/EquivalenceClasses.h>
25#include <llvm/ADT/TypeSwitch.h>
43std::optional<UnreducedInterval> mergeUnreducedIntervals(
44 const std::optional<UnreducedInterval> &lhs,
const std::optional<UnreducedInterval> &rhs
46 if (!lhs.has_value() || !rhs.has_value()) {
49 return lhs->doUnion(*rhs);
53std::optional<UnreducedInterval>
55 if (!lhs.hasUnreducedInterval() || !rhs.hasUnreducedInterval()) {
58 return fn(lhs.getUnreducedInterval(), rhs.getUnreducedInterval());
63 if (expr.getInterval() != newInterval) {
64 refined = refined.dropUnreducedInterval();
69bool isInMaybeSkippedScfRegion(Operation *op) {
70 for (Operation *parent = op->getParentOp(); parent !=
nullptr; parent = parent->getParentOp()) {
71 if (llvm::isa<FuncDefOp>(parent)) {
78 if (llvm::isa<scf::ForOp, scf::IfOp, scf::WhileOp>(parent)) {
85std::optional<UnreducedInterval> getBooleanUnreducedInterval(
const Interval &interval) {
86 return interval.isBoolean() ? std::optional<UnreducedInterval>(interval.firstUnreduced())
90FailureOr<std::vector<SourceRef>>
92 std::vector<SourceRef> refs;
93 for (
const auto &[prefix, vals] : translations) {
94 if (!ref.isValidPrefix(prefix)) {
99 auto suffix = ref.getSuffix(prefix);
100 ensure(succeeded(suffix),
"prefix checked before SourceRef suffix extraction");
102 std::vector<SourceRefIndex> arraySuffix, remainingSuffix;
103 bool suffixIsPastArray =
false;
105 if (!suffixIsPastArray && arraySuffix.size() < vals.getNumArrayDims() &&
106 (idx.isIndex() || idx.isIndexRange())) {
107 arraySuffix.push_back(idx);
110 suffixIsPastArray =
true;
111 remainingSuffix.push_back(idx);
114 auto resolvedValsRes = vals.extract(arraySuffix);
115 ensure(succeeded(resolvedValsRes),
"could not resolve translated SourceRef array child");
116 SourceRefSet folded = resolvedValsRes->first.foldToScalar();
117 if (remainingSuffix.empty()) {
118 refs.insert(refs.end(), folded.begin(), folded.end());
122 for (
const SourceRef &baseRef : folded) {
123 auto translatedRef = mlir::FailureOr<SourceRef>(baseRef);
125 if (failed(translatedRef)) {
128 translatedRef = translatedRef->createChild(idx);
130 if (succeeded(translatedRef)) {
131 refs.push_back(*translatedRef);
135 for (
const SourceRef &replacement : vals.getScalarValue()) {
136 auto translated = ref.translate(prefix, replacement);
137 if (succeeded(translated)) {
138 refs.push_back(*translated);
150bool isDirectSourceRefValue(Value value) {
151 if (llvm::isa<BlockArgument>(value)) {
155 Operation *definingOp = value.getDefiningOp();
156 return llvm::isa_and_present<MemberReadOp, ReadArrayOp, polymorphic::ConstReadOp>(definingOp);
159std::optional<SourceRefLatticeValue>
160getIdentitySourceRefState(DataFlowSolver &solver, Value value) {
161 if (isDirectSourceRefValue(value)) {
163 if (val.isScalar()) {
169 auto createArray = llvm::dyn_cast_if_present<CreateArrayOp>(value.getDefiningOp());
175 for (
auto [idx, element] : llvm::enumerate(createArray.getElements())) {
176 std::optional<SourceRefLatticeValue> elementVal = getIdentitySourceRefState(solver, element);
177 if (!elementVal.has_value()) {
180 (void)arrayVal.getElemFlatIdx(idx).setValue(*elementVal);
185llvm::EquivalenceClasses<SourceRef>
186collectDirectEqualityRefs(DataFlowSolver &solver,
FuncDefOp fn) {
187 llvm::EquivalenceClasses<SourceRef> eqRefs;
189 Operation *op = eqOp.getOperation();
194 Value lhs = eqOp.getLhs();
195 Value rhs = eqOp.getRhs();
196 if (!isDirectSourceRefValue(lhs) || !isDirectSourceRefValue(rhs)) {
202 if (!lhsState.isScalar() || !rhsState.isScalar() || !lhsState.isSingleValue() ||
203 !rhsState.isSingleValue()) {
207 const SourceRef &lhsRef = lhsState.getSingleValue();
208 const SourceRef &rhsRef = rhsState.getSingleValue();
209 if (lhsRef.isConstant() || rhsRef.isConstant()) {
212 eqRefs.unionSets(lhsRef, rhsRef);
222 const llvm::SMTSolverRef &solver, Operation *op,
const ExpressionValue &val,
223 StringRef suffix =
""
228 DynamicAPInt invVal = field.
inv(iv.
lhs());
236 if (!suffix.empty()) {
237 symName += suffix.str();
239 llvm::SMTExprRef invSym = field.
createSymbol(solver, symName.c_str());
240 llvm::SMTExprRef one = solver->mkBitvector(APSInt::get(1), field.
bitWidth());
242 llvm::SMTExprRef mult = solver->mkBVMul(val.
getExpr(), invSym);
243 llvm::SMTExprRef
mod = solver->mkBVURem(mult, prime);
244 llvm::SMTExprRef constraint = solver->mkEqual(
mod, one);
245 solver->addConstraint(constraint);
250 if (expr ==
nullptr && rhs.expr ==
nullptr) {
251 return i == rhs.i && unreduced == rhs.unreduced;
253 if (expr ==
nullptr || rhs.expr ==
nullptr) {
256 return i == rhs.i && unreduced == rhs.unreduced && *expr == *rhs.expr;
261 llvm::SMTExprRef zero = solver->mkBitvector(mlir::APSInt::get(0), bitwidth);
262 llvm::SMTExprRef one = solver->mkBitvector(mlir::APSInt::get(1), bitwidth);
263 llvm::SMTExprRef boolToFeltConv = solver->mkIte(expr.
getExpr(), one, zero);
283 llvm::SMTExprRef resultExpr =
285 std::optional<UnreducedInterval> resultUnreduced;
293 resultUnreduced = mergeUnreducedIntervals(
297 return ExpressionValue(resultExpr, resultInterval, std::move(resultUnreduced));
304 const auto *exprEq = solver->mkEqual(lhs.expr, rhs.expr);
311 res.i = lhs.i + rhs.i;
312 res.expr = solver->mkBVAdd(lhs.expr, rhs.expr);
320 res.i = lhs.i - rhs.i;
321 res.expr = solver->mkBVSub(lhs.expr, rhs.expr);
329 res.i = lhs.i * rhs.i;
330 res.expr = solver->mkBVMul(lhs.expr, rhs.expr);
339 auto divRes =
feltDiv(lhs.i, rhs.i);
340 if (failed(divRes)) {
347 "non-degenerate felt.div divisors are not tracked precisely, and the divisor may "
348 "contain zero. Range of division result will be treated as unbounded."
353 "non-degenerate felt.div divisors are not tracked precisely because precise field "
354 "division over intervals would require enumerating divisor inverses. Range of "
355 "division result will be treated as unbounded."
361 "divisor is zero, leading to a divide-by-zero error. Range of division result will "
362 "be treated as unbounded."
371 res.expr = solver->mkBVMul(lhs.expr, invExpr);
376 const llvm::SMTSolverRef &solver, Operation *op,
const ExpressionValue &lhs,
381 if (failed(divRes)) {
383 "divisor is not restricted to non-zero values, leading to potential divide-by-zero error."
384 " Range of division result will be treated as unbounded."
391 res.expr = solver->mkBVUDiv(lhs.expr, rhs.expr);
396 const llvm::SMTSolverRef &solver, Operation *op,
const ExpressionValue &lhs,
401 if (failed(divRes)) {
403 "divisor is not restricted to non-zero values, leading to potential divide-by-zero error."
404 " Range of division result will be treated as unbounded."
411 res.expr = solver->mkBVSDiv(lhs.expr, rhs.expr);
418 res.i = lhs.i % rhs.i;
419 res.expr = solver->mkBVURem(lhs.expr, rhs.expr);
434 res.i = lhs.i & rhs.i;
435 res.expr = solver->mkBVAnd(lhs.expr, rhs.expr);
442 res.i = lhs.i | rhs.i;
443 res.expr = solver->mkBVOr(lhs.expr, rhs.expr);
450 return boolXor(solver, lhs, rhs);
454 res.i = lhs.i ^ rhs.i;
455 res.expr = solver->mkBVXor(lhs.expr, rhs.expr);
463 res.i = lhs.i << rhs.i;
464 res.expr = solver->mkBVShl(lhs.expr, rhs.expr);
472 res.i = lhs.i >> rhs.i;
473 res.expr = solver->mkBVLshr(lhs.expr, rhs.expr);
485 case FeltCmpPredicate::EQ:
486 res.expr = solver->mkEqual(lhs.expr, rhs.expr);
493 case FeltCmpPredicate::NE:
494 res.expr = solver->mkNot(solver->mkEqual(lhs.expr, rhs.expr));
501 case FeltCmpPredicate::LT:
502 res.expr = solver->mkBVUlt(lhs.expr, rhs.expr);
510 case FeltCmpPredicate::LE:
511 res.expr = solver->mkBVUle(lhs.expr, rhs.expr);
519 case FeltCmpPredicate::GT:
520 res.expr = solver->mkBVUgt(lhs.expr, rhs.expr);
528 case FeltCmpPredicate::GE:
529 res.expr = solver->mkBVUge(lhs.expr, rhs.expr);
546 res.expr = solver->mkAnd(lhs.expr, rhs.expr);
554 res.i =
boolOr(lhs.i, rhs.i);
555 res.expr = solver->mkOr(lhs.expr, rhs.expr);
565 res.expr = solver->mkAnd(
566 solver->mkOr(lhs.expr, rhs.expr), solver->mkNot(solver->mkAnd(lhs.expr, rhs.expr))
575 res.expr = solver->mkBVNeg(val.expr);
585 res.expr = solver->mkBVNot(val.expr);
592 res.expr = solver->mkNot(val.expr);
602 res.expr = TypeSwitch<Operation *, llvm::SMTExprRef>(op)
605 }).Default([](Operation *unsupported) {
606 llvm::report_fatal_error(
607 "no fallback provided for " + mlir::Twine(unsupported->getName().getStringRef())
612 if (llvm::isa<InvFeltOp>(op)) {
626 os <<
"<null expression>";
629 os <<
" ( interval: " << i <<
" )";
630 if (unreduced.has_value()) {
631 os <<
" ( unreduced: " << *unreduced <<
" )";
640 return ChangeResult::NoChange;
646 return ChangeResult::NoChange;
650 os <<
"IntervalAnalysisLattice { " << val <<
" }";
655 return ChangeResult::NoChange;
658 return ChangeResult::Change;
667 if (!constraints.contains(e)) {
668 constraints.insert(e);
669 return ChangeResult::Change;
671 return ChangeResult::NoChange;
680std::vector<SourceRefIndex> IntervalDataFlowAnalysis::getArrayAccessIndices(
681 Operation * , ArrayAccessOpInterface arrayAccessOp
683 std::vector<SourceRefIndex> indices;
684 ArrayType arrayType = arrayAccessOp.getArrRefType();
685 size_t numIndices = arrayAccessOp.getIndices().size();
686 indices.reserve(numIndices);
688 for (
size_t i = 0; i < numIndices; ++i) {
689 Value idxOperand = arrayAccessOp.getIndices()[i];
690 SourceRefLatticeValue idxVals = getSourceRefState(idxOperand);
693 auto constantIndex = idxVals.getConstantValue(field.get());
694 if (succeeded(constantIndex)) {
695 indices.emplace_back(*constantIndex);
704mlir::FailureOr<SourceRef> IntervalDataFlowAnalysis::getArrayAccessRef(
707 std::vector<SourceRefIndex> indices = getArrayAccessIndices(baseOp, arrayAccessOp);
708 Value arrayVal = arrayAccessOp.getArrRef();
709 if (
auto blockArg = llvm::dyn_cast<BlockArgument>(arrayVal)) {
710 return SourceRef(blockArg, std::move(indices));
712 if (
auto result = llvm::dyn_cast<OpResult>(arrayVal)) {
713 return SourceRef(result, std::move(indices));
719 if (
auto it = writeResults.find(ref); it != writeResults.end()) {
720 return it->second.getInterval();
723 if (ref.isConstantInt()) {
724 auto constVal = ref.getConstantValue(field.get());
725 if (succeeded(constVal)) {
730 if (ref.isRooted() && ref.getPath().empty()) {
731 auto rootVal = ref.getRoot();
732 if (succeeded(rootVal) && !llvm::isa<ArrayType, StructType, pod::PodType>(rootVal->getType())) {
734 if (rootExpr.getExpr() !=
nullptr) {
735 return rootExpr.getInterval();
740 return getDefaultIntervalForType(ref.getType());
743std::optional<UnreducedInterval>
744IntervalDataFlowAnalysis::getDefaultUnreducedIntervalForType(mlir::Type ty)
const {
745 if (!trackUnreducedIntervals) {
748 if (isBooleanType(ty)) {
749 return UnreducedInterval(0, 1);
751 return UnreducedInterval(field.get().zero(), field.get().maxVal());
754std::optional<UnreducedInterval>
755IntervalDataFlowAnalysis::getRefUnreducedInterval(
const SourceRef &ref) {
756 if (!trackUnreducedIntervals) {
760 if (
auto it = writeResults.find(ref); it != writeResults.end()) {
761 return it->second.getOptionalUnreducedInterval();
764 if (ref.isConstantInt()) {
765 auto constVal = ref.getConstantValue(field.get());
766 if (succeeded(constVal)) {
767 return UnreducedInterval(*constVal, *constVal);
771 if (ref.isRooted() && ref.getPath().empty()) {
772 auto rootVal = ref.getRoot();
773 if (succeeded(rootVal) && !llvm::isa<ArrayType, StructType, pod::PodType>(rootVal->getType())) {
775 if (rootExpr.hasUnreducedInterval()) {
776 return rootExpr.getUnreducedInterval();
781 return getRefInterval(ref).firstUnreduced();
785 if (
auto it = writeResults.find(ref); it != writeResults.end()) {
788 return createUnknownValue(val)
793void IntervalDataFlowAnalysis::recordRefWrite(
796 auto joinStoredWrite = [
this, &writtenRef](
797 const ExpressionValue &old,
const ExpressionValue &next
798 ) -> ExpressionValue {
799 Interval combinedWrite = old.getInterval().join(next.getInterval());
800 auto combinedUnreduced = mergeUnreducedIntervals(
801 old.getOptionalUnreducedInterval(), next.getOptionalUnreducedInterval()
803 if (old.getExpr() !=
nullptr && next.getExpr() !=
nullptr &&
804 *old.getExpr() == *next.getExpr()) {
805 return old.withInterval(combinedWrite).withOptionalUnreducedInterval(combinedUnreduced);
808 return ExpressionValue(
813 if (
auto it = writeResults.find(writtenRef); it != writeResults.end()) {
814 it->second = joinStoredWrite(it->second, writeVal);
815 }
else if (mayBeSkipped) {
816 ExpressionValue noWrite(
818 getRefUnreducedInterval(writtenRef)
820 writeResults[writtenRef] = joinStoredWrite(noWrite, writeVal);
822 writeResults[writtenRef] = writeVal;
825 const ExpressionValue &readerUpdate = mayBeSkipped ? writeResults[writtenRef] : writeVal;
826 for (Lattice *readerLattice : readResults[writtenRef]) {
827 ExpressionValue prior = readerLattice->getValue().getScalarValue();
829 ExpressionValue newVal = prior.withInterval(
intersection);
830 propagateIfChanged(readerLattice, readerLattice->setValue(newVal));
835 Operation *op, ArrayRef<const Lattice *> operands, ArrayRef<Lattice *> results
844 if (operands.empty() && results.empty()) {
849 llvm::SmallVector<LatticeValue> operandVals;
850 llvm::SmallVector<std::optional<SourceRef>> operandRefs;
851 auto resolveRefStateValue =
853 ensure(refSet.isScalar(),
"should have ruled out array values already");
855 if (refSet.getScalarValue().empty()) {
862 " is empty; defining operation is unsupported by SourceRef analysis"
868 if (!refSet.isSingleValue()) {
870 std::optional<UnreducedInterval> joinedUnreduced = std::nullopt;
871 bool sawFirst =
false;
872 for (
const SourceRef &ref : refSet.getScalarValue()) {
873 joinedInterval = joinedInterval.
join(getRefInterval(ref));
874 auto refUnreduced = getRefUnreducedInterval(ref);
876 joinedUnreduced = refUnreduced;
879 joinedUnreduced = mergeUnreducedIntervals(joinedUnreduced, refUnreduced);
885 return LatticeValue(anyVal);
888 return LatticeValue(getRefValue(refSet.getSingleValue(), value));
890 for (
unsigned opNum = 0; opNum < op->getNumOperands(); ++opNum) {
891 Value val = op->getOperand(opNum);
896 operandRefs.push_back(std::nullopt);
899 auto priorState = operands[opNum]->getValue();
900 if (priorState.getScalarValue().getExpr() !=
nullptr) {
901 operandVals.push_back(priorState);
905 if (
auto readArr = llvm::dyn_cast_if_present<ReadArrayOp>(val.getDefiningOp())) {
906 auto arrayRef = getArrayAccessRef(op, readArr);
907 if (succeeded(arrayRef)) {
908 if (
auto it = writeResults.find(*arrayRef); it != writeResults.end()) {
909 operandVals.emplace_back(it->second);
911 (void)operandLattice->
setValue(it->second);
919 Type valTy = val.getType();
920 if (llvm::isa<ArrayType, StructType, pod::PodType>(valTy)) {
922 operandVals.emplace_back(anyVal);
926 auto resolvedValue = resolveRefStateValue(val, refSet);
927 if (!resolvedValue.has_value()) {
932 operandVals.push_back(*resolvedValue);
938 (void)operandLattice->
setValue(operandVals[opNum]);
941 if (isReadOp(op) && op->getNumResults() == 1) {
942 Value resultVal = op->getResult(0);
943 if (!llvm::isa<ArrayType, StructType, pod::PodType>(resultVal.getType())) {
944 auto resolvedValue = resolveRefStateValue(resultVal, getSourceRefState(resultVal));
945 if (resolvedValue.has_value()) {
946 propagateIfChanged(results[0], results[0]->setValue(*resolvedValue));
954 llvm::DynamicAPInt constVal = getConst(op);
955 llvm::SMTExprRef expr;
956 if (isBoolConstOp(op)) {
957 expr = createConstBoolExpr(constVal != 0);
959 expr = createConstBitvectorExpr(constVal);
963 if (trackUnreducedIntervals) {
966 propagateIfChanged(results[0], results[0]->setValue(latticeVal));
967 }
else if (isArithmeticOp(op)) {
969 if (operands.size() == 2) {
970 result = performBinaryArithmetic(op, operandVals[0], operandVals[1]);
972 result = performUnaryArithmetic(op, operandVals[0]);
976 const ExpressionValue &prior = results[0]->getValue().getScalarValue();
980 propagateIfChanged(results[0], results[0]->setValue(result));
981 }
else if (
auto selectOp = llvm::dyn_cast<arith::SelectOp>(op)) {
983 smtSolver, operandVals[0].getScalarValue(), operandVals[1].getScalarValue(),
984 operandVals[2].getScalarValue()
986 const ExpressionValue &prior = results[0]->getValue().getScalarValue();
990 propagateIfChanged(results[0], results[0]->setValue(result));
991 }
else if (
EmitEqualityOp emitEq = llvm::dyn_cast<EmitEqualityOp>(op)) {
992 Value lhsVal = emitEq.getLhs(), rhsVal = emitEq.getRhs();
998 auto res = getGeneralizedDecompInterval(op, lhsVal, rhsVal);
999 if (succeeded(res)) {
1000 for (Value signalVal : res->first) {
1001 applyInterval(emitEq, signalVal, res->second);
1009 applyInterval(emitEq, lhsVal, constrainInterval);
1010 applyInterval(emitEq, rhsVal, constrainInterval);
1011 }
else if (
auto assertOp = llvm::dyn_cast<AssertOp>(op)) {
1014 Value cond = assertOp.getCondition();
1017 auto assertExpr = operandVals[0].getScalarValue();
1020 }
else if (
auto writePod = llvm::dyn_cast<pod::WritePodOp>(op)) {
1021 const bool maySkipWrite = isInMaybeSkippedScfRegion(op);
1025 ensure(succeeded(recordRefsRes),
"could not create SourceRef child for pod write");
1027 Type valueTy = writePod.
getValue().getType();
1028 if (!llvm::isa<ArrayType, StructType, pod::PodType>(valueTy)) {
1031 recordRefWrite(recordRef, writeVal, maySkipWrite);
1033 }
else if (operandRefs[1].has_value()) {
1034 llvm::SmallVector<std::pair<SourceRef, ExpressionValue>> remappedWrites;
1036 for (
const auto &[writtenRef, writtenVal] : writeResults) {
1037 if (writtenRef.isValidPrefix(*operandRefs[1])) {
1038 auto translated = writtenRef.translate(*operandRefs[1], recordRef);
1039 ensure(succeeded(translated),
"could not translate aggregate pod write");
1040 remappedWrites.emplace_back(*translated, writtenVal);
1044 for (
const auto &[translatedRef, translatedVal] : remappedWrites) {
1045 recordRefWrite(translatedRef, translatedVal, maySkipWrite);
1049 }
else if (
auto writem = llvm::dyn_cast<MemberWriteOp>(op)) {
1050 const bool maySkipWrite = isInMaybeSkippedScfRegion(op);
1053 auto cmp = writem.getComponent();
1057 auto memberDefRes = writem.getMemberDefOp(tables);
1058 if (succeeded(memberDefRes)) {
1061 ensure(succeeded(memberRefRes),
"could not create SourceRef child for member write");
1062 const SourceRef &memberRef = *memberRefRes;
1063 Type memberTy = writem.getVal().
getType();
1064 if (!llvm::isa<ArrayType, StructType, pod::PodType>(memberTy)) {
1066 recordRefWrite(memberRef, writeVal, maySkipWrite);
1069 std::optional<SourceRef> rhsPrefix;
1070 if (operandRefs[1].has_value() && operandRefs[1]->isRooted()) {
1071 rhsPrefix = operandRefs[1];
1072 }
else if (
auto blockArg = llvm::dyn_cast<BlockArgument>(writem.getVal())) {
1074 }
else if (
auto result = llvm::dyn_cast<OpResult>(writem.getVal())) {
1078 if (rhsPrefix.has_value()) {
1079 llvm::SmallVector<std::pair<SourceRef, ExpressionValue>> remappedWrites;
1080 for (
const auto &[writtenRef, writtenVal] : writeResults) {
1081 if (!writtenRef.isValidPrefix(*rhsPrefix)) {
1085 auto translatedRef = writtenRef.translate(*rhsPrefix, memberRef);
1086 ensure(succeeded(translatedRef),
"could not translate composite member write");
1087 remappedWrites.emplace_back(*translatedRef, writtenVal);
1090 for (
const auto &[translatedRef, translatedVal] : remappedWrites) {
1091 recordRefWrite(translatedRef, translatedVal, maySkipWrite);
1097 }
else if (
auto writeArr = llvm::dyn_cast<WriteArrayOp>(op)) {
1098 const bool maySkipWrite = isInMaybeSkippedScfRegion(op);
1100 auto arrayRef = getArrayAccessRef(op, writeArr);
1101 if (succeeded(arrayRef)) {
1102 recordRefWrite(*arrayRef, writeVal, maySkipWrite);
1107 std::vector<SourceRefIndex> indices = getArrayAccessIndices(op, writeArr);
1108 auto targetRefsRes = arrayVals.
extract(indices);
1109 ensure(succeeded(targetRefsRes),
"could not create SourceRef child for array write");
1110 auto [targetRefs, _] = *targetRefsRes;
1111 ensure(targetRefs.isScalar(),
"array write must resolve to scalar references");
1112 for (
const SourceRef &ref : targetRefs.getScalarValue()) {
1113 recordRefWrite(ref, writeVal, maySkipWrite);
1116 }
else if (
auto createArray = llvm::dyn_cast<CreateArrayOp>(op)) {
1117 const auto &elements = createArray.getElements();
1118 ArrayType arrayTy = createArray.getType();
1121 if (!elements.empty() && !llvm::isa<ArrayType, StructType, pod::PodType>(elemTy)) {
1122 ensure(arrayTy.hasStaticShape(),
"array.new with explicit elements must have static shape");
1124 std::cmp_equal(elements.size(), arrayTy.getNumElements()),
1125 "array.new explicit initializer length must match array shape"
1129 auto arrayRes = llvm::cast<OpResult>(createArray->getResult(0));
1130 for (
unsigned i = 0; i < elements.size(); ++i) {
1131 auto maybeIndices = indexGen.
delinearize(i, op->getContext());
1132 ensure(maybeIndices.has_value(),
"could not delinearize array.new element index");
1135 path.reserve(maybeIndices->size());
1136 for (Attribute attr : *maybeIndices) {
1137 auto idxAttr = llvm::dyn_cast<IntegerAttr>(attr);
1138 ensure(idxAttr !=
nullptr,
"array.new delinearize should produce integer attributes");
1142 recordRefWrite(
SourceRef(arrayRes, std::move(path)), operandVals[i].getScalarValue());
1144 }
else if (!elements.empty()) {
1145 ensure(arrayTy.hasStaticShape(),
"aggregate array.new initializer requires static shape");
1147 SourceRef arrayRoot(llvm::cast<OpResult>(createArray.getResult()));
1148 llvm::SmallVector<std::pair<SourceRef, ExpressionValue>> remappedWrites;
1149 for (
auto [i, element] : llvm::enumerate(elements)) {
1155 ensure(maybeIndices.has_value(),
"could not delinearize aggregate array.new index");
1157 for (Attribute attr : *maybeIndices) {
1161 ensure(succeeded(child),
"could not create aggregate array element SourceRef");
1162 elementTarget = *child;
1164 for (
const auto &[writtenRef, writtenVal] : writeResults) {
1166 auto translated = writtenRef.translate(elementRefs.
getSingleValue(), elementTarget);
1167 ensure(succeeded(translated),
"could not translate aggregate array initializer");
1168 remappedWrites.emplace_back(*translated, writtenVal);
1172 for (
const auto &[translatedRef, translatedVal] : remappedWrites) {
1173 recordRefWrite(translatedRef, translatedVal);
1176 }
else if (
auto newPod = llvm::dyn_cast<pod::NewPodOp>(op)) {
1177 SourceRef podRoot(llvm::cast<OpResult>(newPod.getResult()));
1178 for (
auto [idx, record] : llvm::enumerate(newPod.getInitializedRecordValues())) {
1181 ensure(succeeded(recordRef),
"could not create SourceRef child for pod initializer");
1182 if (!llvm::isa<ArrayType, StructType, pod::PodType>(record.value.getType())) {
1183 recordRefWrite(*recordRef, operandVals[idx].getScalarValue());
1191 llvm::SmallVector<std::pair<SourceRef, ExpressionValue>> remappedWrites;
1192 for (
const auto &[writtenRef, writtenVal] : writeResults) {
1194 auto translated = writtenRef.translate(sourceRefs.
getSingleValue(), *recordRef);
1195 ensure(succeeded(translated),
"could not translate aggregate pod initializer");
1196 remappedWrites.emplace_back(*translated, writtenVal);
1199 for (
const auto &[translatedRef, translatedVal] : remappedWrites) {
1200 recordRefWrite(translatedRef, translatedVal);
1204 auto intToFelt = dyn_cast<IntToFeltOp>(op);
1206 !intToFelt.getValue().getType().isInteger(1)
1211 propagateIfChanged(results[0], results[0]->setValue(createUnknownValue(intToFelt.getResult())));
1213 auto feltToIndex = dyn_cast<FeltToIndexOp>(op);
1217 propagateIfChanged(results[0], results[0]->setValue(createUnknownValue(op->getResult(0))));
1218 }
else if (isa<IntToFeltOp, FeltToIndexOp>(op)) {
1225 expr =
boolToFelt(smtSolver, expr, field.get().bitWidth());
1227 propagateIfChanged(results[0], results[0]->setValue(expr));
1228 }
else if (
auto yieldOp = dyn_cast<scf::YieldOp>(op)) {
1229 Operation *parent = op->getParentOp();
1230 ensure(parent,
"yield operation must have parent operation");
1233 for (
unsigned idx = 0; idx < yieldOp.getResults().size(); ++idx) {
1234 Value parentRes = parent->getResult(idx);
1240 if (
auto loopOp = llvm::dyn_cast<LoopLikeOpInterface>(parent)) {
1244 if (exprVal.
getExpr() !=
nullptr) {
1256 propagateIfChanged(resLattice, resLattice->
setValue(newResVal));
1261 for (
unsigned idx = 0; idx < yieldOp.getNumOperands(); ++idx) {
1262 Value parentRes = parent->getResult(idx);
1263 if (!llvm::isa<ArrayType, StructType, pod::PodType>(parentRes.getType())) {
1272 SourceRef resultRef(llvm::cast<OpResult>(parentRes));
1273 llvm::SmallVector<std::pair<SourceRef, ExpressionValue>> remappedWrites;
1275 for (
const auto &[writtenRef, writtenVal] : writeResults) {
1276 if (!writtenRef.isValidPrefix(yieldedRef)) {
1279 auto translatedRef = writtenRef.translate(yieldedRef, resultRef);
1280 ensure(succeeded(translatedRef),
"could not translate aggregate loop yield");
1281 remappedWrites.emplace_back(*translatedRef, writtenVal);
1284 for (
const auto &[translatedRef, translatedVal] : remappedWrites) {
1285 recordRefWrite(translatedRef, translatedVal);
1296 && !isDefinitionOp(op)
1298 && !llvm::isa<CreateArrayOp, CreateStructOp, pod::NewPodOp, NonDetOp>(op)
1300 op->emitWarning(
"unhandled operation, analysis may be incomplete").report();
1307 auto it = refSymbols.find(r);
1308 if (it != refSymbols.end()) {
1311 llvm::SMTExprRef sym = createSymbol(r);
1312 refSymbols[r] = sym;
1316llvm::SMTExprRef IntervalDataFlowAnalysis::createSymbol(mlir::Type ty,
const char *name)
const {
1317 if (isBooleanType(ty)) {
1318 return smtSolver->mkSymbol(name, smtSolver->getBoolSort());
1320 return field.get().createSymbol(smtSolver, name);
1323llvm::SMTExprRef IntervalDataFlowAnalysis::createSymbol(
const SourceRef &r)
const {
1325 return createSymbol(r.getType(), name.c_str());
1328llvm::SMTExprRef IntervalDataFlowAnalysis::createSymbol(Value v)
const {
1330 return createSymbol(v.getType(), name.c_str());
1333llvm::DynamicAPInt IntervalDataFlowAnalysis::getConst(Operation *op)
const {
1334 ensure(isConstOp(op),
"op is not a const op");
1338 llvm::DynamicAPInt fieldConst = TypeSwitch<Operation *, llvm::DynamicAPInt>(op)
1339 .Case<FeltConstantOp>([&](
auto feltConst) {
1342 return field.get().reduce(feltConst.getValue().getReducedValueOrRaw());
1344 .Case<arith::ConstantIndexOp>([&](
auto indexConst) {
1345 return DynamicAPInt(indexConst.value());
1347 .Case<arith::ConstantIntOp>([&](
auto intConst) {
1348 auto valAttr = dyn_cast<IntegerAttr>(intConst.getValue());
1349 ensure(valAttr !=
nullptr,
"arith::ConstantIntOp must have an IntegerAttr as its value");
1352 .Default([](
auto *illegalOp) {
1354 debug::Appender(err) <<
"unhandled getConst case: " << *illegalOp;
1355 llvm::report_fatal_error(Twine(err));
1356 return llvm::DynamicAPInt();
1363 Operation *op,
const LatticeValue &a,
const LatticeValue &b
1365 ensure(isArithmeticOp(op),
"is not arithmetic op");
1367 auto lhs = a.getScalarValue(), rhs = b.getScalarValue();
1368 ensure(lhs.getExpr(),
"cannot perform arithmetic over null lhs smt expr");
1369 ensure(rhs.getExpr(),
"cannot perform arithmetic over null rhs smt expr");
1372 auto res = TypeSwitch<Operation *, ExpressionValue>(op)
1373 .Case<AddFeltOp>([&](
auto) {
return add(smtSolver, lhs, rhs); })
1374 .Case<SubFeltOp>([&](
auto) {
return sub(smtSolver, lhs, rhs); })
1375 .Case<MulFeltOp>([&](
auto) {
return mul(smtSolver, lhs, rhs); })
1376 .Case<DivFeltOp>([&](
auto) {
return div(smtSolver, op, lhs, rhs); })
1377 .Case<UnsignedIntDivFeltOp>([&](
auto) {
return uintDiv(smtSolver, op, lhs, rhs); })
1378 .Case<SignedIntDivFeltOp>([&](
auto) {
return sintDiv(smtSolver, op, lhs, rhs); })
1379 .Case<UnsignedModFeltOp>([&](
auto) {
return mod(smtSolver, lhs, rhs); })
1380 .Case<SignedModFeltOp>([&](
auto) {
return sintMod(smtSolver, lhs, rhs); })
1381 .Case<AndFeltOp>([&](
auto) {
return bitAnd(smtSolver, lhs, rhs); })
1382 .Case<OrFeltOp>([&](
auto) {
return bitOr(smtSolver, lhs, rhs); })
1383 .Case<XorFeltOp, arith::XOrIOp>([&](
auto) {
return bitXor(smtSolver, lhs, rhs); })
1384 .Case<ShlFeltOp>([&](
auto) {
return shiftLeft(smtSolver, lhs, rhs); })
1385 .Case<ShrFeltOp>([&](
auto) {
return shiftRight(smtSolver, lhs, rhs); })
1386 .Case<CmpOp>([&](
auto cmpOp) {
return cmp(smtSolver, cmpOp, lhs, rhs); })
1387 .Case<AndBoolOp>([&](
auto) {
return boolAnd(smtSolver, lhs, rhs); })
1388 .Case<OrBoolOp>([&](
auto) {
return boolOr(smtSolver, lhs, rhs); })
1389 .Case<XorBoolOp>([&](
auto) {
return boolXor(smtSolver, lhs, rhs); })
1390 .Default([&](
auto *unsupported) {
1393 "unsupported binary arithmetic operation"
1396 return ExpressionValue();
1400 ensure(res.getExpr(),
"arithmetic produced null smt expr");
1405IntervalDataFlowAnalysis::performUnaryArithmetic(Operation *op,
const LatticeValue &a) {
1406 ensure(isArithmeticOp(op),
"is not arithmetic op");
1408 auto val = a.getScalarValue();
1409 ensure(val.getExpr(),
"cannot perform arithmetic over null smt expr");
1411 auto res = TypeSwitch<Operation *, ExpressionValue>(op)
1412 .Case<NegFeltOp>([&](
auto) {
return neg(smtSolver, val); })
1413 .Case<NotFeltOp>([&](
auto) {
return notOp(smtSolver, val); })
1414 .Case<NotBoolOp>([&](
auto) {
return boolNot(smtSolver, val); })
1416 .Case<InvFeltOp>([&](
auto inv) {
1418 }).Default([&](
auto *unsupported) {
1421 "unsupported unary arithmetic operation, defaulting to over-approximated interval"
1427 ensure(res.getExpr(),
"arithmetic produced null smt expr");
1431void IntervalDataFlowAnalysis::applyInterval(Operation *valUser, Value val,
Interval newInterval) {
1433 ExpressionValue oldLatticeVal = valLattice->getValue().getScalarValue();
1436 ExpressionValue newLatticeVal = refineReducedInterval(oldLatticeVal,
intersection);
1437 ChangeResult changed = valLattice->setValue(newLatticeVal);
1439 if (
auto blockArg = llvm::dyn_cast<BlockArgument>(val)) {
1440 auto fnOp = dyn_cast<FuncDefOp>(blockArg.getOwner()->getParentOp());
1443 if (propagateInputConstraints && fnOp && fnOp.isStructConstrain() &&
1444 blockArg.getArgNumber() > 0 && !newInterval.isEntire()) {
1445 auto structOp = fnOp->getParentOfType<StructDefOp>();
1446 FuncDefOp computeFn = structOp.getComputeFuncOp();
1447 BlockArgument computeArg = computeFn.getArgument(blockArg.getArgNumber() - 1);
1450 SourceRef ref(computeArg);
1451 ExpressionValue newArgVal(
1453 trackUnreducedIntervals ? std::optional<UnreducedInterval>(newInterval.firstUnreduced())
1456 propagateIfChanged(computeEntryLattice, computeEntryLattice->setValue(newArgVal));
1461 Operation *definingOp = val.getDefiningOp();
1463 propagateIfChanged(valLattice, changed);
1467 const Field &f = field.get();
1475 auto cmpCase = [&](CmpOp cmpOp) {
1481 newInterval.isBoolean() || newInterval.isEmpty(),
1482 "new interval for CmpOp is not boolean or empty"
1484 if (!newInterval.isDegenerate()) {
1489 bool cmpTrue = newInterval.rhs() == f.one();
1491 Value lhs = cmpOp.getLhs(), rhs = cmpOp.getRhs();
1493 ExpressionValue lhsExpr = lhsLat->getValue().getScalarValue(),
1494 rhsExpr = rhsLat->getValue().getScalarValue();
1496 Interval newLhsInterval, newRhsInterval;
1497 const Interval &lhsInterval = lhsExpr.getInterval();
1498 const Interval &rhsInterval = rhsExpr.getInterval();
1502 auto eqCase = [&]() {
1503 return (pred == FeltCmpPredicate::EQ && cmpTrue) ||
1504 (pred == FeltCmpPredicate::NE && !cmpTrue);
1506 auto neCase = [&]() {
1507 return (pred == FeltCmpPredicate::NE && cmpTrue) ||
1508 (pred == FeltCmpPredicate::EQ && !cmpTrue);
1510 auto ltCase = [&]() {
1511 return (pred == FeltCmpPredicate::LT && cmpTrue) ||
1512 (pred == FeltCmpPredicate::GE && !cmpTrue);
1514 auto leCase = [&]() {
1515 return (pred == FeltCmpPredicate::LE && cmpTrue) ||
1516 (pred == FeltCmpPredicate::GT && !cmpTrue);
1518 auto gtCase = [&]() {
1519 return (pred == FeltCmpPredicate::GT && cmpTrue) ||
1520 (pred == FeltCmpPredicate::LE && !cmpTrue);
1522 auto geCase = [&]() {
1523 return (pred == FeltCmpPredicate::GE && cmpTrue) ||
1524 (pred == FeltCmpPredicate::LT && !cmpTrue);
1529 newLhsInterval = newRhsInterval = lhsInterval.intersect(rhsInterval);
1530 }
else if (neCase()) {
1531 if (lhsInterval.isDegenerate() && rhsInterval.isDegenerate() && lhsInterval == rhsInterval) {
1535 }
else if (lhsInterval.isDegenerate()) {
1537 newLhsInterval = lhsInterval;
1538 newRhsInterval = rhsInterval.difference(lhsInterval);
1539 }
else if (rhsInterval.isDegenerate()) {
1541 newLhsInterval = lhsInterval.difference(rhsInterval);
1542 newRhsInterval = rhsInterval;
1545 newLhsInterval = lhsInterval;
1546 newRhsInterval = rhsInterval;
1548 }
else if (ltCase()) {
1549 newLhsInterval = lhsInterval.toUnreduced().computeLTPart(rhsInterval.toUnreduced()).reduce(f);
1550 newRhsInterval = rhsInterval.toUnreduced().computeGEPart(lhsInterval.toUnreduced()).reduce(f);
1551 }
else if (leCase()) {
1552 newLhsInterval = lhsInterval.toUnreduced().computeLEPart(rhsInterval.toUnreduced()).reduce(f);
1553 newRhsInterval = rhsInterval.toUnreduced().computeGTPart(lhsInterval.toUnreduced()).reduce(f);
1554 }
else if (gtCase()) {
1555 newLhsInterval = lhsInterval.toUnreduced().computeGTPart(rhsInterval.toUnreduced()).reduce(f);
1556 newRhsInterval = rhsInterval.toUnreduced().computeLEPart(lhsInterval.toUnreduced()).reduce(f);
1557 }
else if (geCase()) {
1558 newLhsInterval = lhsInterval.toUnreduced().computeGEPart(rhsInterval.toUnreduced()).reduce(f);
1559 newRhsInterval = rhsInterval.toUnreduced().computeLTPart(lhsInterval.toUnreduced()).reduce(f);
1561 cmpOp->emitWarning(
"unhandled cmp predicate").report();
1566 applyInterval(cmpOp, lhs, newLhsInterval);
1567 applyInterval(cmpOp, rhs, newRhsInterval);
1575 auto mulCase = [&](MulFeltOp mulOp) {
1577 auto constCase = [&](FeltConstantOp constOperand, Value multiplicand) {
1578 DynamicAPInt constVal = getConst(constOperand);
1579 if (constVal == 0) {
1584 applyInterval(mulOp, multiplicand, updatedInterval);
1587 Value lhs = mulOp.getLhs(), rhs = mulOp.getRhs();
1589 auto lhsConstOp = dyn_cast_if_present<FeltConstantOp>(lhs.getDefiningOp());
1590 auto rhsConstOp = dyn_cast_if_present<FeltConstantOp>(rhs.getDefiningOp());
1592 if (lhsConstOp && rhsConstOp) {
1594 }
else if (lhsConstOp) {
1595 constCase(lhsConstOp, rhs);
1597 }
else if (rhsConstOp) {
1598 constCase(rhsConstOp, lhs);
1604 if (newInterval.intersect(zeroInt).isNotEmpty()) {
1610 ExpressionValue lhsExpr = lhsLat->getValue().getScalarValue(),
1611 rhsExpr = rhsLat->getValue().getScalarValue();
1612 Interval newLhsInterval = lhsExpr.getInterval().difference(zeroInt);
1613 Interval newRhsInterval = rhsExpr.getInterval().difference(zeroInt);
1614 applyInterval(mulOp, lhs, newLhsInterval);
1615 applyInterval(mulOp, rhs, newRhsInterval);
1618 auto addCase = [&](AddFeltOp addOp) {
1619 Value lhs = addOp.getLhs(), rhs = addOp.getRhs();
1621 ExpressionValue lhsVal = lhsLat->getValue().getScalarValue();
1622 ExpressionValue rhsVal = rhsLat->getValue().getScalarValue();
1624 const Interval &currLhsInt = lhsVal.getInterval(), &currRhsInt = rhsVal.getInterval();
1626 Interval derivedLhsInt = newInterval - currRhsInt;
1627 Interval derivedRhsInt = newInterval - currLhsInt;
1629 Interval finalLhsInt = currLhsInt.intersect(derivedLhsInt);
1630 Interval finalRhsInt = currRhsInt.intersect(derivedRhsInt);
1632 applyInterval(addOp, lhs, finalLhsInt);
1633 applyInterval(addOp, rhs, finalRhsInt);
1636 auto subCase = [&](SubFeltOp subOp) {
1637 Value lhs = subOp.getLhs(), rhs = subOp.getRhs();
1639 ExpressionValue lhsVal = lhsLat->getValue().getScalarValue();
1640 ExpressionValue rhsVal = rhsLat->getValue().getScalarValue();
1642 const Interval &currLhsInt = lhsVal.getInterval(), &currRhsInt = rhsVal.getInterval();
1644 Interval derivedLhsInt = newInterval + currRhsInt;
1645 Interval derivedRhsInt = currLhsInt - newInterval;
1647 Interval finalLhsInt = currLhsInt.intersect(derivedLhsInt);
1648 Interval finalRhsInt = currRhsInt.intersect(derivedRhsInt);
1650 applyInterval(subOp, lhs, finalLhsInt);
1651 applyInterval(subOp, rhs, finalRhsInt);
1654 auto selectCase = [&](arith::SelectOp selectOp) {
1655 Value cond = selectOp.getCondition();
1656 Value trueVal = selectOp.getTrueValue();
1657 Value falseVal = selectOp.getFalseValue();
1663 const Interval &condInterval = condExpr.getInterval();
1664 if (condInterval.isDegenerate() && condInterval.rhs() == f.one()) {
1665 applyInterval(selectOp, trueVal, newInterval);
1668 if (condInterval.isDegenerate() && condInterval.rhs() == f.zero()) {
1669 applyInterval(selectOp, falseVal, newInterval);
1673 Interval trueOverlap = trueExpr.getInterval().intersect(newInterval);
1674 Interval falseOverlap = falseExpr.getInterval().intersect(newInterval);
1675 bool truePossible = trueOverlap.isNotEmpty();
1676 bool falsePossible = falseOverlap.isNotEmpty();
1678 if (truePossible && !falsePossible) {
1680 applyInterval(selectOp, trueVal, newInterval);
1683 if (!truePossible && falsePossible) {
1685 applyInterval(selectOp, falseVal, newInterval);
1688 if (!truePossible && !falsePossible) {
1693 auto readmCase = [&](MemberReadOp) {
1694 SourceRefLatticeValue sourceRefVal = getSourceRefState(val);
1696 if (sourceRefVal.isSingleValue()) {
1697 const SourceRef &ref = sourceRefVal.getSingleValue();
1698 readResults[ref].insert(valLattice);
1701 for (Lattice *l : readResults[ref]) {
1702 if (l != valLattice) {
1703 propagateIfChanged(l, l->setValue(newLatticeVal));
1709 auto readArrCase = [&](ReadArrayOp) {
1710 auto arrayRef = getArrayAccessRef(valUser, llvm::cast<ReadArrayOp>(definingOp));
1711 if (succeeded(arrayRef)) {
1712 readResults[*arrayRef].insert(valLattice);
1714 for (Lattice *l : readResults[*arrayRef]) {
1715 if (l != valLattice) {
1716 propagateIfChanged(l, l->setValue(newLatticeVal));
1721 SourceRefLatticeValue sourceRefVal = getSourceRefState(val);
1723 if (sourceRefVal.isSingleValue()) {
1724 const SourceRef &ref = sourceRefVal.getSingleValue();
1725 readResults[ref].insert(valLattice);
1728 for (Lattice *l : readResults[ref]) {
1729 if (l != valLattice) {
1730 propagateIfChanged(l, l->setValue(newLatticeVal));
1738 auto castCase = [&](Operation *op) {
1739 if (
auto intToFelt = dyn_cast<IntToFeltOp>(op);
1741 !intToFelt.getValue().getType().isInteger(1)) {
1744 if (
auto feltToIndex = dyn_cast<FeltToIndexOp>(op);
1748 applyInterval(op, op->getOperand(0), newInterval);
1755 TypeSwitch<Operation *>(definingOp)
1756 .Case<CmpOp>([&](
auto op) { cmpCase(op); })
1757 .Case<AddFeltOp>([&](
auto op) {
return addCase(op); })
1758 .Case<SubFeltOp>([&](
auto op) {
return subCase(op); })
1759 .Case<MulFeltOp>([&](
auto op) { mulCase(op); })
1760 .Case<arith::SelectOp>([&](
auto op) { selectCase(op); })
1761 .Case<MemberReadOp>([&](
auto op){ readmCase(op); })
1762 .Case<ReadArrayOp>([&](
auto op){ readArrCase(op); })
1763 .Case<IntToFeltOp, FeltToIndexOp>([&](
auto op) { castCase(op); })
1764 .Default([&](Operation *) { });
1768 propagateIfChanged(valLattice, changed);
1771FailureOr<std::pair<DenseSet<Value>,
Interval>>
1772IntervalDataFlowAnalysis::getGeneralizedDecompInterval(
1773 Operation * , Value lhs, Value rhs
1775 auto isZeroConst = [
this](Value v) {
1776 Operation *op = v.getDefiningOp();
1780 if (!isConstOp(op)) {
1783 return getConst(op) == field.get().zero();
1785 bool lhsIsZero = isZeroConst(lhs), rhsIsZero = isZeroConst(rhs);
1786 Value exprTree =
nullptr;
1787 if (lhsIsZero && !rhsIsZero) {
1789 }
else if (!lhsIsZero && rhsIsZero) {
1796 std::optional<SourceRef> signalRef = std::nullopt;
1797 DenseSet<Value> signalVals;
1798 SmallVector<DynamicAPInt> consts;
1799 SmallVector<Value> frontier {exprTree};
1800 while (!frontier.empty()) {
1801 Value v = frontier.back();
1802 frontier.pop_back();
1803 Operation *op = v.getDefiningOp();
1807 auto handleRefValue = [
this, &signalRef, &signalVal, &signalVals]() {
1808 SourceRefLatticeValue refSet = getSourceRefState(signalVal);
1809 if (!refSet.isScalar() || !refSet.isSingleValue()) {
1812 SourceRef r = refSet.getSingleValue();
1813 if (signalRef.has_value() && signalRef.value() != r) {
1815 }
else if (!signalRef.has_value()) {
1818 signalVals.insert(signalVal);
1823 if (op && matchPattern(op, subPattern)) {
1824 if (failed(handleRefValue())) {
1827 consts.push_back(getConst(c));
1829 }
else if (
m_RefValue(&signalVal).match(v)) {
1830 if (failed(handleRefValue())) {
1833 consts.push_back(field.get().zero());
1839 if (op && matchPattern(op, mulPattern)) {
1840 frontier.push_back(a);
1841 frontier.push_back(b);
1850 std::sort(consts.begin(), consts.end());
1851 Interval iv = UnreducedInterval(consts.front(), consts.back()).reduce(field.get());
1852 return std::make_pair(std::move(signalVals), iv);
1860 SymbolTableCollection tables;
1862 auto computeIntervalsImpl =
1863 [&solver, &am, &ctx, &tables,
this](
1864 FuncDefOp fn, llvm::MapVector<SourceRef, Interval> &memberRanges,
1865 llvm::MapVector<SourceRef, UnreducedInterval> &memberUnreducedRanges,
1866 llvm::SetVector<ExpressionValue> &
1868 auto setUnreducedRange =
1870 memberUnreducedRanges.erase(ref);
1871 memberUnreducedRanges.insert({ref, interval});
1884 searchSet.insert(ref);
1888 auto mergeInterval = [&memberRanges, &memberUnreducedRanges](
1890 std::optional<UnreducedInterval> unreducedInterval = std::nullopt
1892 auto *existing = memberRanges.find(ref);
1893 if (existing != memberRanges.end()) {
1895 bool intervalChanged = mergedInterval != existing->second;
1896 existing->second = std::move(mergedInterval);
1898 if (unreducedInterval.has_value()) {
1899 auto *existingUnreduced = memberUnreducedRanges.find(ref);
1900 if (existingUnreduced != memberUnreducedRanges.end()) {
1901 existingUnreduced->second = existingUnreduced->second.intersect(*unreducedInterval);
1903 memberUnreducedRanges.insert({ref, *unreducedInterval});
1905 }
else if (intervalChanged) {
1906 memberUnreducedRanges.erase(ref);
1911 memberRanges[ref] = interval;
1912 if (unreducedInterval.has_value()) {
1913 memberUnreducedRanges.insert({ref, *unreducedInterval});
1918 for (BlockArgument arg : fn.getArguments()) {
1920 if (searchSet.erase(ref)) {
1924 if (!expr.getExpr()) {
1927 expr = expr.withUnreducedInterval(expr.getInterval().firstUnreduced());
1930 memberRanges[ref] = expr.getInterval();
1931 if (expr.hasUnreducedInterval()) {
1932 setUnreducedRange(ref, expr.getUnreducedInterval());
1934 assert(memberRanges[ref].getField() == ctx.
getField() &&
"bad interval defaults");
1942 if (!lattices.empty() && searchSet.erase(ref)) {
1944 std::optional<UnreducedInterval> joinedUnreduced = std::nullopt;
1945 bool sawFirst =
false;
1957 memberRanges[ref] = joinedInterval;
1958 if (joinedUnreduced.has_value()) {
1959 setUnreducedRange(ref, *joinedUnreduced);
1961 assert(memberRanges[ref].getField() == ctx.
getField() &&
"bad interval defaults");
1966 if (searchSet.erase(ref)) {
1967 memberRanges[ref] = val.getInterval();
1968 if (val.hasUnreducedInterval()) {
1969 setUnreducedRange(ref, val.getUnreducedInterval());
1971 assert(memberRanges[ref].getField() == ctx.
getField() &&
"bad interval defaults");
1978 if (fn.isStructConstrain()) {
1979 auto mergeChildConstrainIntervals = [&](
CallOp fnCall) {
1994 auto calledStruct = calledFn->getParentOfType<
StructDefOp>();
1995 if (calledStruct == structDef) {
2000 if (childAnalysis.inProgress(ctx)) {
2003 if (!childAnalysis.constructed(ctx)) {
2005 succeeded(childAnalysis.runAnalysis(solver, am, ctx)),
2006 "could not construct interval analysis for child struct"
2013 llvm::MapVector<SourceRef, Interval> callOperandIntervals;
2014 for (
unsigned i = 0; i < calledFn.getNumArguments(); i++) {
2015 SourceRef prefix(calledFn.getArgument(i));
2016 Value operand = fnCall.getOperand(i);
2017 std::optional<SourceRefLatticeValue> identityVal =
2018 getIdentitySourceRefState(solver, operand);
2019 if (identityVal.has_value()) {
2020 identityTranslations.push_back({prefix, *identityVal});
2023 if (!llvm::isa<ArrayType, StructType, pod::PodType>(operand.getType())) {
2026 if (lattice !=
nullptr) {
2028 callOperandIntervals[prefix] = expr.
getInterval();
2033 const StructIntervals &childIntervals = childAnalysis.getResult(ctx);
2036 for (
const auto &[childRef, childInterval] : constrainIntervals) {
2037 auto translatedRefs = translateRef(childRef, identityTranslations);
2038 if (failed(translatedRefs)) {
2042 std::optional<UnreducedInterval> childUnreduced = std::nullopt;
2043 if (
const auto *childUnreducedIt = constrainUnreducedIntervals.find(childRef);
2044 childUnreducedIt != constrainUnreducedIntervals.end()) {
2045 childUnreduced = childUnreducedIt->second;
2049 for (
const SourceRef &translatedRef : *translatedRefs) {
2050 uniqueTranslatedRefs.insert(translatedRef);
2052 if (uniqueTranslatedRefs.size() != 1) {
2056 const SourceRef &translatedRef = *uniqueTranslatedRefs.begin();
2057 if (functionRefs.contains(translatedRef)) {
2058 mergeInterval(translatedRef, childInterval, childUnreduced);
2059 searchSet.erase(translatedRef);
2065 llvm::EquivalenceClasses<SourceRef> directEqRefs =
2066 collectDirectEqualityRefs(solver, calledFn);
2067 for (
const auto *leader : directEqRefs) {
2068 if (!leader->isLeader()) {
2072 llvm::MapVector<SourceRef, Interval> translatedEqRefs;
2074 bool hasInterval =
false;
2075 bool ambiguousTranslation =
false;
2077 for (
const SourceRef &member : directEqRefs.members(*leader)) {
2079 if (
const auto *childIntervalIt = constrainIntervals.find(member);
2080 childIntervalIt != constrainIntervals.end()) {
2081 memberInterval = memberInterval.
intersect(childIntervalIt->second);
2083 if (
auto *callOperandIt = callOperandIntervals.find(member);
2084 callOperandIt != callOperandIntervals.end()) {
2085 memberInterval = memberInterval.
intersect(callOperandIt->second);
2086 contextualInterval = contextualInterval.
intersect(memberInterval);
2090 auto translatedRefs = translateRef(member, identityTranslations);
2091 if (failed(translatedRefs)) {
2096 for (
const SourceRef &translatedRef : *translatedRefs) {
2097 uniqueTranslatedRefs.insert(translatedRef);
2099 if (uniqueTranslatedRefs.size() != 1) {
2100 ambiguousTranslation =
true;
2104 const SourceRef &translatedRef = *uniqueTranslatedRefs.begin();
2105 if (!functionRefs.contains(translatedRef)) {
2109 if (
auto *parentIntervalIt = memberRanges.find(translatedRef);
2110 parentIntervalIt != memberRanges.end()) {
2111 memberInterval = memberInterval.
intersect(parentIntervalIt->second);
2114 translatedEqRefs[translatedRef] = memberInterval;
2115 contextualInterval = contextualInterval.
intersect(memberInterval);
2119 if (ambiguousTranslation || !hasInterval || translatedEqRefs.empty()) {
2123 for (
const auto &[translatedRef, _] : translatedEqRefs) {
2124 mergeInterval(translatedRef, contextualInterval);
2125 searchSet.erase(translatedRef);
2130 fn.walk(mergeChildConstrainIntervals);
2134 for (
const auto &ref : searchSet) {
2137 setUnreducedRange(ref, memberRanges[ref].firstUnreduced());
2146 llvm::SmallVector<std::pair<SourceRef, Interval>> sortedRanges;
2147 sortedRanges.reserve(memberRanges.size());
2148 for (
const auto &[ref, interval] : memberRanges) {
2149 sortedRanges.emplace_back(ref, interval);
2151 llvm::sort(sortedRanges, [](
const auto &a,
const auto &b) {
return a.first < b.first; });
2152 llvm::SmallVector<std::pair<SourceRef, UnreducedInterval>> sortedUnreducedRanges;
2153 sortedUnreducedRanges.reserve(memberUnreducedRanges.size());
2154 for (
const auto &[ref, interval] : memberUnreducedRanges) {
2155 sortedUnreducedRanges.emplace_back(ref, interval);
2157 llvm::sort(sortedUnreducedRanges, [](
const auto &a,
const auto &b) {
2158 return a.first < b.first;
2160 memberRanges.clear();
2161 memberUnreducedRanges.clear();
2162 for (
auto &[ref, interval] : sortedRanges) {
2163 memberRanges[ref] = interval;
2165 for (
auto &[ref, interval] : sortedUnreducedRanges) {
2166 memberUnreducedRanges.insert({ref, interval});
2170 if (
auto computeFn = structDef.getComputeFuncOp()) {
2171 computeIntervalsImpl(
2172 computeFn, computeMemberRanges, computeMemberUnreducedRanges, computeSolverConstraints
2175 if (
auto constrainFn = structDef.getConstrainFuncOp()) {
2176 computeIntervalsImpl(
2177 constrainFn, constrainMemberRanges, constrainMemberUnreducedRanges,
2178 constrainSolverConstraints
2186 mlir::raw_ostream &os,
bool withConstraints,
bool printCompute,
bool printUnreduced
2188 auto writeIntervals =
2189 [&os, &withConstraints, &printUnreduced](
2190 const char *fnName,
const llvm::MapVector<SourceRef, Interval> &memberRanges,
2191 const llvm::MapVector<SourceRef, UnreducedInterval> &memberUnreducedRanges,
2192 const llvm::SetVector<ExpressionValue> &solverConstraints,
bool printName
2197 os.indent(indent) << fnName <<
" {";
2201 if (memberRanges.empty()) {
2206 for (
const auto &[ref, interval] : memberRanges) {
2208 os.indent(indent) << ref <<
" in " << interval;
2209 if (printUnreduced) {
2210 const auto *unreducedIt = memberUnreducedRanges.find(ref);
2211 if (unreducedIt != memberUnreducedRanges.end()) {
2212 os <<
" ( " << unreducedIt->second <<
" )";
2217 if (withConstraints) {
2219 os.indent(indent) <<
"Solver Constraints { ";
2220 if (solverConstraints.empty()) {
2223 for (
const auto &e : solverConstraints) {
2225 os.indent(indent + 4);
2226 e.getExpr()->print(os);
2229 os.indent(indent) <<
'}';
2235 os.indent(indent - 4) <<
'}';
2239 os <<
"StructIntervals { ";
2240 if (constrainMemberRanges.empty() && (!printCompute || computeMemberRanges.empty())) {
2248 computeSolverConstraints, printCompute
2253 constrainSolverConstraints, printCompute
Tracks a solver expression and an interval range for that expression.
ExpressionValue withUnreducedInterval(const UnreducedInterval &newUnreducedInterval) const
ExpressionValue withExpression(const llvm::SMTExprRef &newExpr) const
Return the current expression with a new SMT expression.
const Interval & getInterval() const
const std::optional< UnreducedInterval > & getOptionalUnreducedInterval() const
ExpressionValue withOptionalUnreducedInterval(std::optional< UnreducedInterval > newUnreducedInterval) const
ExpressionValue withInterval(const Interval &newInterval) const
Return the current expression with a new interval.
void print(mlir::raw_ostream &os) const
bool operator==(const ExpressionValue &rhs) const
llvm::SMTExprRef getExpr() const
bool isBoolSort(const llvm::SMTSolverRef &solver) const
bool hasUnreducedInterval() const
const Field & getField() const
const UnreducedInterval & getUnreducedInterval() const
Information about the prime finite field used for the interval analysis.
llvm::DynamicAPInt zero() const
Returns the zero field element.
llvm::DynamicAPInt prime() const
For the prime field p, returns p.
llvm::DynamicAPInt one() const
Returns the unit field element.
unsigned bitWidth() const
Width sufficient for the modulus and its representatives, independent of literal storage.
llvm::DynamicAPInt inv(const llvm::DynamicAPInt &i) const
Returns the multiplicative inverse of i in prime field p.
llvm::SMTExprRef createSymbol(const llvm::SMTSolverRef &solver, const char *name) const
Create a SMT solver symbol with the current field's bitwidth.
llvm::DynamicAPInt maxVal() const
Returns p - 1, which is the max value possible in a prime field described by p.
const LatticeValue & getValue() const
mlir::ChangeResult setValue(const LatticeValue &val)
IntervalAnalysisLatticeValue LatticeValue
mlir::ChangeResult meet(const AbstractSparseLattice &other) override
void print(mlir::raw_ostream &os) const override
mlir::ChangeResult join(const AbstractSparseLattice &other) override
mlir::ChangeResult addSolverConstraint(const ExpressionValue &e)
mlir::LogicalResult visitOperation(mlir::Operation *op, mlir::ArrayRef< const Lattice * > operands, mlir::ArrayRef< Lattice * > results) override
Visit an operation with the lattices of its operands.
llvm::SMTExprRef getOrCreateSymbol(const SourceRef &r)
Either return the existing SMT expression that corresponds to the SourceRef, or create one.
const llvm::DenseMap< SourceRef, ExpressionValue > & getWriteResults() const
const llvm::DenseMap< SourceRef, llvm::DenseSet< Lattice * > > & getReadResults() const
Intervals over a finite field.
static Interval True(const Field &f)
llvm::DynamicAPInt rhs() const
Interval intersect(const Interval &rhs) const
Intersect.
UnreducedInterval toUnreduced() const
Convert to an UnreducedInterval.
static Interval Boolean(const Field &f)
UnreducedInterval firstUnreduced() const
Get the first side of the interval for TypeF intervals, otherwise just get the full interval as an Un...
static Interval Entire(const Field &f)
bool isDegenerate() const
static Interval False(const Field &f)
llvm::DynamicAPInt lhs() const
Interval join(const Interval &rhs) const
Union.
static SourceRefLatticeValue getValueState(mlir::DataFlowSolver &solver, mlir::Value val)
Defines an index into an LLZK object.
static SourceRefIndex forArrayDimension(int64_t size)
Select every nonnegative index in a static or dynamic array dimension.
A value at a given point of the SourceRefLattice.
mlir::FailureOr< std::pair< SourceRefLatticeValue, mlir::ChangeResult > > referencePodRecord(mlir::StringAttr recordName) const
Add the given pod recordName to the SourceRefs contained within this value.
const SourceRef & getSingleValue() const
mlir::FailureOr< std::pair< SourceRefLatticeValue, mlir::ChangeResult > > extract(const std::vector< SourceRefIndex > &indices) const
Perform an array.extract or array.read operation, depending on how many indices are provided.
A reference to a "source", which is the base value from which other SSA values are derived.
mlir::FailureOr< SourceRef > createChild(const SourceRefIndex &r) const
std::vector< SourceRefIndex > Path
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::Type getType() const
const llvm::MapVector< SourceRef, Interval > & getConstrainIntervals() const
const llvm::MapVector< SourceRef, UnreducedInterval > & getConstrainUnreducedIntervals() const
void print(mlir::raw_ostream &os, bool withConstraints=false, bool printCompute=false, bool printUnreduced=false) const
mlir::LogicalResult computeIntervals(mlir::DataFlowSolver &solver, mlir::AnalysisManager &am, const IntervalAnalysisContext &ctx)
An inclusive interval [a, b] where a and b are arbitrary integers not necessarily bound to a given fi...
UnreducedInterval computeLTPart(const UnreducedInterval &rhs) const
Return the part of the interval that is guaranteed to be less than the rhs's max value.
UnreducedInterval computeGEPart(const UnreducedInterval &rhs) const
Return the part of the interval that is greater than or equal to the rhs's lower bound.
UnreducedInterval computeGTPart(const UnreducedInterval &rhs) const
Return the part of the interval that is greater than the rhs's lower bound.
Interval reduce(const Field &field) const
Reduce the interval to an interval in the given field.
UnreducedInterval computeLEPart(const UnreducedInterval &rhs) const
Return the part of the interval that is less than or equal to the rhs's upper bound.
Helper for converting between linear and multi-dimensional indexing with checks to ensure indices are...
static ArrayIndexGen from(ArrayType)
Construct new ArrayIndexGen. Will assert if hasStaticShape() is false.
std::optional< llvm::SmallVector< mlir::Value > > delinearize(int64_t, mlir::Location, mlir::OpBuilder &) const
::mlir::Type getElementType() const
::llzk::boolean::FeltCmpPredicate getPredicate()
std::variant< ScalarTy, ArrayTy > & getValue()
bool isSingleValue() const
const ScalarTy & getScalarValue() const
IntervalAnalysisLattice * getLatticeElement(mlir::Value value) override
bool isStructConstrain()
Return true iff the function is within a StructDefOp and named FUNC_NAME_CONSTRAIN.
bool isOperationLive(DataFlowSolver &solver, Operation *op)
ExpressionValue boolNot(const llvm::SMTSolverRef &solver, const ExpressionValue &val)
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.
ExpressionValue sintMod(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
RefValueCapture m_RefValue()
ExpressionValue intersection(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
FailureOr< Interval > signedIntDiv(const Interval &lhs, const Interval &rhs)
Computes signed integer division with possibly non-Degenerate divisors.
std::vector< std::pair< SourceRef, SourceRefLatticeValue > > SourceRefRemappings
ExpressionValue mod(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
ExpressionValue shiftLeft(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
ExpressionValue fallbackUnaryOp(const llvm::SMTSolverRef &solver, Operation *op, const ExpressionValue &val)
constexpr char FUNC_NAME_CONSTRAIN[]
Interval signedMod(const Interval &lhs, const Interval &rhs)
Computes signed integer remainder with possibly non-Degenerate divisors.
void ensure(bool condition, const llvm::Twine &errMsg)
ExpressionValue boolXor(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
ExpressionValue cmp(const llvm::SMTSolverRef &solver, CmpOp op, const ExpressionValue &lhs, const ExpressionValue &rhs)
ExpressionValue neg(const llvm::SMTSolverRef &solver, const ExpressionValue &val)
llvm::SMTExprRef createFieldInverseExpr(const llvm::SMTSolverRef &solver, Operation *op, const ExpressionValue &val, StringRef suffix="")
constexpr T checkedCast(U u) noexcept
ExpressionValue sintDiv(const llvm::SMTSolverRef &solver, Operation *op, const ExpressionValue &lhs, const ExpressionValue &rhs)
ExpressionValue boolAnd(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
FailureOr< Interval > unsignedIntDiv(const Interval &lhs, const Interval &rhs)
Computes unsigned integer division with possibly non-Degenerate divisors.
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)
std::string buildStringViaPrint(const T &base, Args &&...args)
Generate a string by calling base.print(llvm::raw_ostream &) on a stream backed by the returned strin...
ExpressionValue boolToFelt(const llvm::SMTSolverRef &solver, const ExpressionValue &expr, unsigned bitwidth)
mlir::FailureOr< SymbolLookupResult< T > > resolveCallableSilently(mlir::SymbolTableCollection &symbolTable, mlir::CallOpInterface call)
Resolve a callable without emitting a diagnostic for missing top-level symbols.
ConstantCapture m_Constant()
std::string buildStringViaInsertionOp(Args &&...args)
Generate a string by using the insertion operator (<<) to append all args to a stream backed by the r...
ExpressionValue bitOr(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
ExpressionValue uintDiv(const llvm::SMTSolverRef &solver, Operation *op, const ExpressionValue &lhs, const ExpressionValue &rhs)
ExpressionValue bitAnd(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
ExpressionValue shiftRight(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
APSInt toAPSInt(const DynamicAPInt &i)
ExpressionValue sub(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
auto m_CommutativeOp(LhsMatcher lhs, RhsMatcher rhs)
ExpressionValue bitXor(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
ExpressionValue notOp(const llvm::SMTSolverRef &solver, const ExpressionValue &val)
FailureOr< Interval > feltDiv(const Interval &lhs, const Interval &rhs)
Computes finite-field division by multiplying the dividend by the multiplicative inverse of the divis...
ExpressionValue boolOr(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
llvm::DynamicAPInt integerAttrToDynamicAPInt(mlir::IntegerAttr attr)
Decode an MLIR integer using its signedness; signless i1 denotes a boolean.
ExpressionValue selectValue(const llvm::SMTSolverRef &solver, const ExpressionValue &cond, const ExpressionValue &trueVal, const ExpressionValue &falseVal)
Parameters and shared objects to pass to child analyses.
const Field & getField() const
IntervalDataFlowAnalysis * intervalDFA
bool doTrackUnreducedIntervals() const