27#include <mlir/Dialect/Arith/IR/Arith.h>
28#include <mlir/Dialect/SCF/IR/SCF.h>
29#include <mlir/Dialect/Utils/IndexingUtils.h>
30#include <mlir/IR/Operation.h>
32#include <llvm/ADT/STLExtras.h>
33#include <llvm/ADT/SmallVector.h>
34#include <llvm/Support/MathExtras.h>
46static bool usesUnsignedCmp(scf::ForOp forOp) {
47 if (
auto boolAttr = forOp->getAttrOfType<BoolAttr>(
"unsignedCmp")) {
48 return boolAttr.getValue();
50 return forOp->hasAttr(
"unsignedCmp");
55 bool terminated =
false;
56 llvm::SmallVector<WitnessVal> values;
60llvm::Expected<size_t> checkedLinearize(
61 llvm::ArrayRef<int64_t> shape, llvm::ArrayRef<int64_t> indices, llvm::StringRef context
63 if (shape.size() != indices.size()) {
64 return makeError(
"wrong number of array indices");
66 for (
auto [idx, dim] : llvm::zip_equal(indices, shape)) {
67 if (idx < 0 || dim < 0 || idx >= dim) {
71 auto strides = mlir::computeStrides(shape);
79 ModuleOp module, SymbolTableCollection &symbolTables,
const Field &moduleField,
82 : moduleOp(module), tables(symbolTables), field(moduleField), uninitializedBehavior(behavior),
88class InvocationInterpreter {
91 InvocationInterpreter(
92 ModuleOp module, SymbolTableCollection &symbolTables,
const Field &moduleField,
95 : moduleOp(module), tables(symbolTables), field(moduleField), uninitializedBehavior(behavior),
99 llvm::Expected<llvm::SmallVector<WitnessVal>>
101 if (funcOp.isExternal()) {
102 return makeError(
"extern functions are not supported in llzk-witgen");
104 if (!funcOp.
getBody().hasOneBlock()) {
105 return makeError(
"multi-block functions are not supported in llzk-witgen");
107 if (funcOp.getNumArguments() != args.size()) {
108 return makeError(
"wrong number of arguments passed to function");
111 llvm::DenseMap<mlir::Value, WitnessVal> scope;
112 Block &entry = funcOp.
getBody().front();
113 for (
auto [arg, value] : llvm::zip(entry.getArguments(), args)) {
117 auto result = runBlock(entry, scope);
119 return result.takeError();
121 return result->values;
126 SymbolTableCollection &tables;
129 std::mt19937_64 &rng;
132 llvm::Expected<BlockResult>
133 runBlock(Block &block, llvm::DenseMap<mlir::Value, WitnessVal> &scope) {
134 for (Operation &op : block) {
135 auto handled = runOperation(op, scope);
137 return handled.takeError();
139 if (handled->terminated) {
143 return BlockResult {};
147 llvm::Expected<BlockResult> runRegion(
148 Region ®ion, ArrayRef<WitnessVal> args, llvm::DenseMap<mlir::Value, WitnessVal> scope
150 if (!region.hasOneBlock()) {
151 return makeError(
"multi-block regions are not supported in llzk-witgen");
153 Block &block = region.front();
154 if (block.getNumArguments() != args.size()) {
155 return makeError(
"region argument count mismatch");
157 for (
auto [arg, value] : llvm::zip(block.getArguments(), args)) {
160 return runBlock(block, scope);
164 llvm::Expected<WitnessVal>
165 lookup(mlir::Value value, llvm::DenseMap<mlir::Value, WitnessVal> &scope) {
166 auto it = scope.find(value);
167 if (it == scope.end()) {
168 return makeError(
"failed to find SSA value during interpretation");
174 llvm::Expected<llvm::SmallVector<WitnessVal>>
175 collectOperands(OperandRange operands, llvm::DenseMap<mlir::Value, WitnessVal> &scope) {
176 llvm::SmallVector<WitnessVal> values;
177 values.reserve(operands.size());
178 for (mlir::Value operand : operands) {
179 auto value = lookup(operand, scope);
181 return value.takeError();
183 values.push_back(*value);
189 llvm::Expected<BlockResult>
190 runOperation(Operation &op, llvm::DenseMap<mlir::Value, WitnessVal> &scope) {
191 if (
auto returnOp = dyn_cast<function::ReturnOp>(op)) {
192 auto values = collectOperands(returnOp.getOperands(), scope);
194 return values.takeError();
196 return BlockResult {
true, std::move(*values)};
198 if (
auto yieldOp = dyn_cast<scf::YieldOp>(op)) {
199 auto values = collectOperands(yieldOp.getOperands(), scope);
201 return values.takeError();
203 return BlockResult {
true, std::move(*values)};
205 if (
auto conditionOp = dyn_cast<scf::ConditionOp>(op)) {
206 auto values = collectOperands(conditionOp.getOperands(), scope);
208 return values.takeError();
210 return BlockResult {
true, std::move(*values)};
213 auto bind = [&](ArrayRef<WitnessVal> results) -> llvm::Expected<BlockResult> {
214 if (results.size() != op.getNumResults()) {
215 return makeError(
"internal result count mismatch");
217 for (
auto [result, value] : llvm::zip(op.getResults(), results)) {
218 scope[result] = value;
220 return BlockResult {};
223 if (
auto constantOp = dyn_cast<arith::ConstantOp>(op)) {
224 Attribute valueAttr = constantOp.getValue();
225 if (
auto integerAttr = dyn_cast<IntegerAttr>(valueAttr)) {
226 if (integerAttr.getType().isInteger(1)) {
227 return bind({
WitnessVal(integerAttr.getValue().getBoolValue())});
229 return bind({
WitnessVal(integerAttr.getValue().getSExtValue())});
231 return makeError(
"unsupported arith.constant value");
234 if (
auto nondetOp = dyn_cast<llzk::NonDetOp>(op)) {
236 nondetOp.getType(), tables, nondetOp.getOperation(), field, uninitializedBehavior, &rng
239 return value.takeError();
241 return bind({*value});
244 if (
auto assertOp = dyn_cast<boolean::AssertOp>(op)) {
245 auto condition = lookup(assertOp.getCondition(), scope);
247 return condition.takeError();
249 auto boolValue =
asBool(*condition);
251 return boolValue.takeError();
254 std::string msg =
"bool.assert failed";
255 if (
auto attr = assertOp.getMsg()) {
260 return BlockResult {};
263 if (
auto andOp = dyn_cast<boolean::AndBoolOp>(op)) {
264 auto lhsValue = lookup(andOp.getLhs(), scope);
265 auto rhsValue = lookup(andOp.getRhs(), scope);
267 return lhsValue.takeError();
270 return rhsValue.takeError();
272 auto lhs =
asBool(*lhsValue);
274 return lhs.takeError();
276 auto rhs =
asBool(*rhsValue);
278 return rhs.takeError();
282 if (
auto orOp = dyn_cast<boolean::OrBoolOp>(op)) {
283 auto lhsValue = lookup(orOp.getLhs(), scope);
284 auto rhsValue = lookup(orOp.getRhs(), scope);
286 return lhsValue.takeError();
289 return rhsValue.takeError();
291 auto lhs =
asBool(*lhsValue);
293 return lhs.takeError();
295 auto rhs =
asBool(*rhsValue);
297 return rhs.takeError();
301 if (
auto xorOp = dyn_cast<boolean::XorBoolOp>(op)) {
302 auto lhsValue = lookup(xorOp.getLhs(), scope);
303 auto rhsValue = lookup(xorOp.getRhs(), scope);
305 return lhsValue.takeError();
308 return rhsValue.takeError();
310 auto lhs =
asBool(*lhsValue);
312 return lhs.takeError();
314 auto rhs =
asBool(*rhsValue);
316 return rhs.takeError();
320 if (
auto notOp = dyn_cast<boolean::NotBoolOp>(op)) {
321 auto operand = lookup(
notOp.getOperand(), scope);
323 return operand.takeError();
325 auto boolValue =
asBool(*operand);
327 return boolValue.takeError();
331 if (
auto cmpOp = dyn_cast<boolean::CmpOp>(op)) {
332 auto lhs = lookup(cmpOp.getLhs(), scope);
333 auto rhs = lookup(cmpOp.getRhs(), scope);
335 return lhs.takeError();
338 return rhs.takeError();
340 auto lhsValue =
asFelt(*lhs);
342 return lhsValue.takeError();
344 auto rhsValue =
asFelt(*rhs);
346 return rhsValue.takeError();
349 switch (cmpOp.getPredicate()) {
351 result = *lhsValue == *rhsValue;
354 result = *lhsValue != *rhsValue;
357 result = *lhsValue < *rhsValue;
360 result = *lhsValue <= *rhsValue;
363 result = *lhsValue > *rhsValue;
366 result = *lhsValue >= *rhsValue;
372 if (
auto feltConst = dyn_cast<felt::FeltConstantOp>(op)) {
373 auto value = feltConst.getValue().getReducedValue();
375 return makeError(
"felt constant requires a specified field for witness generation");
380 auto handleBinaryFelt = [&](
auto feltOp,
auto fn) -> llvm::Expected<BlockResult> {
381 auto lhsValue = lookup(feltOp.getLhs(), scope);
382 auto rhsValue = lookup(feltOp.getRhs(), scope);
384 return lhsValue.takeError();
387 return rhsValue.takeError();
389 auto lhs =
asFelt(*lhsValue);
391 return lhs.takeError();
393 auto rhs =
asFelt(*rhsValue);
395 return rhs.takeError();
397 return bind({
WitnessVal(field.reduce(fn(*lhs, *rhs)))});
400 if (
auto addOp = dyn_cast<felt::AddFeltOp>(op)) {
401 return handleBinaryFelt(addOp, [](
const auto &lhs,
const auto &rhs) {
return lhs + rhs; });
403 if (
auto powOp = dyn_cast<felt::PowFeltOp>(op)) {
404 return handleBinaryFelt(powOp, [&](
const auto &lhs,
const auto &rhs) {
405 return modExp(lhs, rhs, field.prime());
408 if (
auto andOp = dyn_cast<felt::AndFeltOp>(op)) {
409 return handleBinaryFelt(andOp, [](
const auto &lhs,
const auto &rhs) {
return lhs & rhs; });
411 if (
auto orOp = dyn_cast<felt::OrFeltOp>(op)) {
412 return handleBinaryFelt(orOp, [](
const auto &lhs,
const auto &rhs) {
return lhs | rhs; });
414 if (
auto xorOp = dyn_cast<felt::XorFeltOp>(op)) {
415 return handleBinaryFelt(xorOp, [](
const auto &lhs,
const auto &rhs) {
return lhs ^ rhs; });
417 if (
auto subOp = dyn_cast<felt::SubFeltOp>(op)) {
418 return handleBinaryFelt(subOp, [](
const auto &lhs,
const auto &rhs) {
return lhs - rhs; });
420 if (
auto mulOp = dyn_cast<felt::MulFeltOp>(op)) {
421 return handleBinaryFelt(mulOp, [](
const auto &lhs,
const auto &rhs) {
return lhs * rhs; });
423 if (
auto divOp = dyn_cast<felt::DivFeltOp>(op)) {
424 return handleBinaryFelt(divOp, [&](
const auto &lhs,
const auto &rhs) {
425 return lhs * field.inv(rhs);
428 if (
auto uintDivOp = dyn_cast<felt::UnsignedIntDivFeltOp>(op)) {
429 auto lhsValue = lookup(uintDivOp.getLhs(), scope);
430 auto rhsValue = lookup(uintDivOp.getRhs(), scope);
432 return lhsValue.takeError();
435 return rhsValue.takeError();
437 auto lhs =
asFelt(*lhsValue);
439 return lhs.takeError();
441 auto rhs =
asFelt(*rhsValue);
443 return rhs.takeError();
446 return makeError(
"felt.uintdiv divisor must be non-zero");
450 if (
auto sintDivOp = dyn_cast<felt::SignedIntDivFeltOp>(op)) {
451 auto lhsValue = lookup(sintDivOp.getLhs(), scope);
452 auto rhsValue = lookup(sintDivOp.getRhs(), scope);
454 return lhsValue.takeError();
457 return rhsValue.takeError();
459 auto lhs =
asFelt(*lhsValue);
461 return lhs.takeError();
463 auto rhs =
asFelt(*rhsValue);
465 return rhs.takeError();
468 return makeError(
"felt.sintdiv divisor must be non-zero");
470 return bind({
WitnessVal(field.reduce(field.toSigned(*lhs) / field.toSigned(*rhs)))});
472 if (
auto umodOp = dyn_cast<felt::UnsignedModFeltOp>(op)) {
473 auto lhsValue = lookup(umodOp.getLhs(), scope);
474 auto rhsValue = lookup(umodOp.getRhs(), scope);
476 return lhsValue.takeError();
479 return rhsValue.takeError();
481 auto lhs =
asFelt(*lhsValue);
483 return lhs.takeError();
485 auto rhs =
asFelt(*rhsValue);
487 return rhs.takeError();
490 return makeError(
"felt.umod divisor must be non-zero");
494 if (
auto smodOp = dyn_cast<felt::SignedModFeltOp>(op)) {
495 auto lhsValue = lookup(smodOp.getLhs(), scope);
496 auto rhsValue = lookup(smodOp.getRhs(), scope);
498 return lhsValue.takeError();
501 return rhsValue.takeError();
503 auto lhs =
asFelt(*lhsValue);
505 return lhs.takeError();
507 auto rhs =
asFelt(*rhsValue);
509 return rhs.takeError();
512 return makeError(
"felt.smod divisor must be non-zero");
514 return bind({
WitnessVal(field.reduce(field.toSigned(*lhs) % field.toSigned(*rhs)))});
516 if (
auto shrOp = dyn_cast<felt::ShrFeltOp>(op)) {
517 auto lhsValue = lookup(shrOp.getLhs(), scope);
518 auto rhsValue = lookup(shrOp.getRhs(), scope);
520 return lhsValue.takeError();
523 return rhsValue.takeError();
525 auto lhs =
asFelt(*lhsValue);
527 return lhs.takeError();
529 auto rhs =
asFelt(*rhsValue);
531 return rhs.takeError();
533 llvm::DynamicAPInt result(0);
534 if (*rhs < field.bitWidth()) {
535 result = *lhs >> *rhs;
539 if (
auto shlOp = dyn_cast<felt::ShlFeltOp>(op)) {
540 auto lhsValue = lookup(shlOp.getLhs(), scope);
541 auto rhsValue = lookup(shlOp.getRhs(), scope);
543 return lhsValue.takeError();
546 return rhsValue.takeError();
548 auto lhs =
asFelt(*lhsValue);
550 return lhs.takeError();
552 auto rhs =
asFelt(*rhsValue);
554 return rhs.takeError();
556 llvm::DynamicAPInt two(2);
560 return bind({
WitnessVal(field.reduce(*lhs *
modExp(two, *rhs, field.prime())))});
562 if (
auto negOp = dyn_cast<felt::NegFeltOp>(op)) {
563 auto operand = lookup(negOp.getOperand(), scope);
565 return operand.takeError();
567 auto feltValue =
asFelt(*operand);
569 return feltValue.takeError();
571 return bind({
WitnessVal(field.reduce(-*feltValue))});
573 if (
auto invOp = dyn_cast<felt::InvFeltOp>(op)) {
574 auto operand = lookup(invOp.getOperand(), scope);
576 return operand.takeError();
578 auto feltValue =
asFelt(*operand);
580 return feltValue.takeError();
582 return bind({
WitnessVal(field.inv(*feltValue))});
584 if (
auto notOp = dyn_cast<felt::NotFeltOp>(op)) {
585 auto operand = lookup(
notOp.getOperand(), scope);
587 return operand.takeError();
589 auto feltValue =
asFelt(*operand);
591 return feltValue.takeError();
593 llvm::DynamicAPInt maxMask = (llvm::DynamicAPInt(1) << field.bitWidth()) - 1;
594 return bind({
WitnessVal(field.reduce(maxMask ^ *feltValue))});
596 if (
auto intToFeltOp = dyn_cast<cast::IntToFeltOp>(op)) {
597 const Field &resultField = intToFeltOp.getType().getField();
598 auto operand = lookup(intToFeltOp.getValue(), scope);
600 return operand.takeError();
602 if (std::holds_alternative<bool>(*operand)) {
604 llvm::DynamicAPInt(std::get<bool>(*operand) ? 1 : 0), resultField,
605 intToFeltOp.getOverflow()
608 return makeError(
"integer does not fit in felt");
612 auto integer =
asIndex(*operand);
614 return integer.takeError();
617 llvm::DynamicAPInt(*integer), resultField, intToFeltOp.getOverflow()
620 return makeError(
"integer does not fit in felt");
624 if (
auto feltToIndexOp = dyn_cast<cast::FeltToIndexOp>(op)) {
625 auto operand = lookup(feltToIndexOp.getValue(), scope);
627 return operand.takeError();
629 auto feltValue =
asFelt(*operand);
631 return feltValue.takeError();
634 *feltValue, IndexType::kInternalStorageBitWidth, feltToIndexOp.getOverflow()
637 return makeError(
"felt value does not fit in index");
642 if (
auto structNewOp = dyn_cast<component::CreateStructOp>(op)) {
644 structNewOp.getType(), tables, structNewOp.getOperation(), field, uninitializedBehavior,
648 return value.takeError();
650 return bind({*value});
652 if (
auto readMemberOp = dyn_cast<component::MemberReadOp>(op)) {
653 auto componentValue = lookup(readMemberOp.getComponent(), scope);
654 if (!componentValue) {
655 return componentValue.takeError();
657 auto structValue =
asStruct(*componentValue);
659 return structValue.takeError();
661 auto it = (*structValue)->members.find(readMemberOp.getMemberName());
662 if (it == (*structValue)->members.end()) {
663 return makeError(
"missing struct member");
665 return bind({it->second});
667 if (
auto writeMemberOp = dyn_cast<component::MemberWriteOp>(op)) {
668 auto componentValue = lookup(writeMemberOp.getComponent(), scope);
669 auto memberValue = lookup(writeMemberOp.getVal(), scope);
670 if (!componentValue) {
671 return componentValue.takeError();
674 return memberValue.takeError();
676 auto structValue =
asStruct(*componentValue);
678 return structValue.takeError();
680 (*structValue)->members[writeMemberOp.getMemberName()] = *memberValue;
681 return BlockResult {};
684 if (
auto newPodOp = dyn_cast<pod::NewPodOp>(op)) {
686 newPodOp.getType(), tables, newPodOp.getOperation(), field, uninitializedBehavior, &rng
689 return podValue.takeError();
691 auto podRef =
asPod(*podValue);
693 return podRef.takeError();
695 auto initValues = newPodOp.getInitializedRecordValues();
696 for (pod::RecordValue init : initValues) {
697 auto value = lookup(init.value, scope);
699 return value.takeError();
701 (*podRef)->records[init.name] = *value;
703 return bind({*podRef});
705 if (
auto readPodOp = dyn_cast<pod::ReadPodOp>(op)) {
706 auto podValue = lookup(readPodOp.getPodRef(), scope);
708 return podValue.takeError();
710 auto podRef =
asPod(*podValue);
712 return podRef.takeError();
714 auto it = (*podRef)->records.find(readPodOp.getRecordName());
715 if (it == (*podRef)->records.end()) {
718 return bind({it->second});
720 if (
auto writePodOp = dyn_cast<pod::WritePodOp>(op)) {
721 auto podValue = lookup(writePodOp.getPodRef(), scope);
722 auto recordValue = lookup(writePodOp.getValue(), scope);
724 return podValue.takeError();
727 return recordValue.takeError();
729 auto podRef =
asPod(*podValue);
731 return podRef.takeError();
733 (*podRef)->records[writePodOp.getRecordName()] = *recordValue;
734 return BlockResult {};
737 if (
auto arrayNewOp = dyn_cast<array::CreateArrayOp>(op)) {
738 auto arrayValue = std::make_shared<ArrayValue>();
739 arrayValue->type = arrayNewOp.getType();
740 if (arrayNewOp.getElements().empty()) {
744 return elementCount.takeError();
746 arrayValue->elements.reserve(*elementCount);
747 for (
size_t i = 0; i < *elementCount; ++i) {
749 arrayValue->type.getElementType(), tables, arrayNewOp.getOperation(), field,
750 uninitializedBehavior, &rng
753 return elem.takeError();
755 arrayValue->elements.push_back(*elem);
758 auto values = collectOperands(arrayNewOp.getElements(), scope);
760 return values.takeError();
762 arrayValue->elements.assign(values->begin(), values->end());
764 return bind({arrayValue});
766 if (
auto readArrayOp = dyn_cast<array::ReadArrayOp>(op)) {
767 auto arrayValue = lookup(readArrayOp.getArrRef(), scope);
769 return arrayValue.takeError();
771 auto arrayRef =
asArray(*arrayValue);
773 return arrayRef.takeError();
775 llvm::SmallVector<int64_t> indices;
776 for (mlir::Value indexVal : readArrayOp.getIndices()) {
777 auto value = lookup(indexVal, scope);
779 return value.takeError();
783 return index.takeError();
785 indices.push_back(*index);
788 checkedLinearize((*arrayRef)->type.getShape(), indices,
"array index out of bounds");
790 return offset.takeError();
792 return bind({(*arrayRef)->elements[*offset]});
794 if (
auto writeArrayOp = dyn_cast<array::WriteArrayOp>(op)) {
795 auto arrayValue = lookup(writeArrayOp.getArrRef(), scope);
796 auto rvalue = lookup(writeArrayOp.getRvalue(), scope);
798 return arrayValue.takeError();
801 return rvalue.takeError();
803 auto arrayRef =
asArray(*arrayValue);
805 return arrayRef.takeError();
807 llvm::SmallVector<int64_t> indices;
808 for (mlir::Value indexVal : writeArrayOp.getIndices()) {
809 auto value = lookup(indexVal, scope);
811 return value.takeError();
815 return index.takeError();
817 indices.push_back(*index);
820 checkedLinearize((*arrayRef)->type.getShape(), indices,
"array index out of bounds");
822 return offset.takeError();
824 (*arrayRef)->elements[*offset] = *rvalue;
825 return BlockResult {};
827 if (
auto extractArrayOp = dyn_cast<array::ExtractArrayOp>(op)) {
828 auto arrayValue = lookup(extractArrayOp.getArrRef(), scope);
830 return arrayValue.takeError();
832 auto arrayRef =
asArray(*arrayValue);
834 return arrayRef.takeError();
836 llvm::SmallVector<int64_t> indices;
837 for (mlir::Value indexVal : extractArrayOp.getIndices()) {
838 auto value = lookup(indexVal, scope);
840 return value.takeError();
844 return index.takeError();
846 indices.push_back(*index);
848 llvm::ArrayRef<int64_t> shape = (*arrayRef)->type.getShape();
849 if (indices.size() >= shape.size()) {
850 return makeError(
"array.extract indices exceed array rank");
855 return subArraySize.takeError();
858 checkedLinearize(shape.take_front(indices.size()), indices,
"array index out of bounds");
860 return prefixOffset.takeError();
862 bool baseOverflow =
false;
863 size_t base = llvm::SaturatingMultiply(*prefixOffset, *subArraySize, &baseOverflow);
865 return makeError(
"array.extract element offset would overflow size_t");
867 auto subArray = std::make_shared<ArrayValue>();
868 subArray->type = extractArrayOp.getType();
869 subArray->elements.reserve(*subArraySize);
870 for (
size_t i = 0; i < *subArraySize; ++i) {
871 bool overflow =
false;
872 size_t elementOffset = llvm::SaturatingAdd(base, i, &overflow);
874 return makeError(
"array.extract element offset would overflow size_t");
876 subArray->elements.push_back((*arrayRef)->elements[elementOffset]);
878 return bind({subArray});
880 if (
auto insertArrayOp = dyn_cast<array::InsertArrayOp>(op)) {
881 auto arrayValue = lookup(insertArrayOp.getArrRef(), scope);
882 auto subArrayValue = lookup(insertArrayOp.getRvalue(), scope);
884 return arrayValue.takeError();
886 if (!subArrayValue) {
887 return subArrayValue.takeError();
889 auto arrayRef =
asArray(*arrayValue);
890 auto subArrayRef =
asArray(*subArrayValue);
892 return arrayRef.takeError();
895 return subArrayRef.takeError();
897 llvm::SmallVector<int64_t> indices;
898 for (mlir::Value indexVal : insertArrayOp.getIndices()) {
899 auto value = lookup(indexVal, scope);
901 return value.takeError();
905 return index.takeError();
907 indices.push_back(*index);
909 llvm::ArrayRef<int64_t> shape = (*arrayRef)->type.getShape();
910 size_t subArraySize = (*subArrayRef)->elements.size();
912 checkedLinearize(shape.take_front(indices.size()), indices,
"array index out of bounds");
914 return prefixOffset.takeError();
916 bool baseOverflow =
false;
917 size_t base = llvm::SaturatingMultiply(*prefixOffset, subArraySize, &baseOverflow);
919 return makeError(
"array.insert element offset would overflow size_t");
921 for (
size_t i = 0; i < subArraySize; ++i) {
922 bool overflow =
false;
923 size_t elementOffset = llvm::SaturatingAdd(base, i, &overflow);
925 return makeError(
"array.insert element offset would overflow size_t");
927 (*arrayRef)->elements[elementOffset] = (*subArrayRef)->elements[i];
929 return BlockResult {};
931 if (
auto arrayLenOp = dyn_cast<array::ArrayLengthOp>(op)) {
932 auto dimValue = lookup(arrayLenOp.getDim(), scope);
934 return dimValue.takeError();
938 return dim.takeError();
940 llvm::ArrayRef<int64_t> shape = arrayLenOp.getArrRefType().getShape();
943 return dimIndex.takeError();
945 if (*dimIndex >= shape.size()) {
946 return makeError(
"array.len dimension out of bounds");
951 if (
auto callOp = dyn_cast<function::CallOp>(op)) {
952 if (callOp.getTemplateParams() || !callOp.getMapOperands().empty()) {
953 return makeError(
"templated or affine-instantiated calls are not supported in llzk-witgen");
956 if (failed(callee)) {
957 return makeError(
"could not resolve called function");
959 auto args = collectOperands(callOp.getArgOperands(), scope);
961 return args.takeError();
963 auto results = run(callee->get(), *args);
965 return results.takeError();
967 return bind(*results);
970 auto handleBinaryIndex = [&](
auto arithOp,
auto fn) -> llvm::Expected<BlockResult> {
971 auto lhs = lookup(arithOp.getLhs(), scope);
972 auto rhs = lookup(arithOp.getRhs(), scope);
974 return lhs.takeError();
977 return rhs.takeError();
981 return lhsValue.takeError();
985 return rhsValue.takeError();
987 return bind({
WitnessVal(fn(*lhsValue, *rhsValue))});
990 if (
auto addIOp = dyn_cast<arith::AddIOp>(op)) {
991 return handleBinaryIndex(addIOp, [](int64_t lhs, int64_t rhs) {
return lhs + rhs; });
993 if (
auto subIOp = dyn_cast<arith::SubIOp>(op)) {
994 return handleBinaryIndex(subIOp, [](int64_t lhs, int64_t rhs) {
return lhs - rhs; });
996 if (
auto mulIOp = dyn_cast<arith::MulIOp>(op)) {
997 return handleBinaryIndex(mulIOp, [](int64_t lhs, int64_t rhs) {
return lhs * rhs; });
999 if (
auto divUIOp = dyn_cast<arith::DivUIOp>(op)) {
1000 return handleBinaryIndex(divUIOp, [](int64_t lhs, int64_t rhs) {
1002 auto divRes =
static_cast<uint64_t
>(lhs) /
static_cast<uint64_t
>(rhs);
1003 return static_cast<int64_t
>(divRes);
1006 if (
auto cmpIOp = dyn_cast<arith::CmpIOp>(op)) {
1007 return handleBinaryIndex(cmpIOp, [&cmpIOp](int64_t lhs, int64_t rhs) ->
bool {
1008 switch (cmpIOp.getPredicate()) {
1009 case arith::CmpIPredicate::eq:
1011 case arith::CmpIPredicate::ne:
1013 case arith::CmpIPredicate::slt:
1015 case arith::CmpIPredicate::sle:
1017 case arith::CmpIPredicate::sgt:
1019 case arith::CmpIPredicate::sge:
1022 case arith::CmpIPredicate::ult:
1023 return static_cast<uint64_t
>(lhs) <
static_cast<uint64_t
>(rhs);
1024 case arith::CmpIPredicate::ule:
1025 return static_cast<uint64_t
>(lhs) <=
static_cast<uint64_t
>(rhs);
1026 case arith::CmpIPredicate::ugt:
1027 return static_cast<uint64_t
>(lhs) >
static_cast<uint64_t
>(rhs);
1028 case arith::CmpIPredicate::uge:
1029 return static_cast<uint64_t
>(lhs) >=
static_cast<uint64_t
>(rhs);
1031 llvm_unreachable(
"unknown comparison predicate");
1035 if (
auto selectOp = dyn_cast<arith::SelectOp>(op)) {
1036 auto cond = lookup(selectOp.getCondition(), scope);
1037 auto trueValue = lookup(selectOp.getTrueValue(), scope);
1038 auto falseValue = lookup(selectOp.getFalseValue(), scope);
1040 return cond.takeError();
1043 return trueValue.takeError();
1046 return falseValue.takeError();
1048 auto condition =
asBool(*cond);
1050 return condition.takeError();
1052 return bind({*condition ? *trueValue : *falseValue});
1055 if (
auto ifOp = dyn_cast<scf::IfOp>(op)) {
1056 auto cond = lookup(ifOp.getCondition(), scope);
1058 return cond.takeError();
1060 auto condition =
asBool(*cond);
1062 return condition.takeError();
1064 if (!*condition && ifOp.getNumResults() == 0 && ifOp.getElseRegion().empty()) {
1067 Region ®ion = *condition ? ifOp.getThenRegion() : ifOp.getElseRegion();
1068 auto result = runRegion(region, {}, scope);
1070 return result.takeError();
1072 return bind(result->values);
1075 if (
auto forOp = dyn_cast<scf::ForOp>(op)) {
1076 auto lowerBoundValue = lookup(forOp.getLowerBound(), scope);
1077 auto upperBoundValue = lookup(forOp.getUpperBound(), scope);
1078 auto stepValue = lookup(forOp.getStep(), scope);
1079 if (!lowerBoundValue) {
1080 return lowerBoundValue.takeError();
1082 if (!upperBoundValue) {
1083 return upperBoundValue.takeError();
1086 return stepValue.takeError();
1088 auto lowerBound =
asIndex(*lowerBoundValue);
1090 return lowerBound.takeError();
1092 auto upperBound =
asIndex(*upperBoundValue);
1094 return upperBound.takeError();
1096 auto step =
asIndex(*stepValue);
1098 return step.takeError();
1100 auto iterValuesOrErr = collectOperands(forOp.getInitArgs(), scope);
1101 if (!iterValuesOrErr) {
1102 return iterValuesOrErr.takeError();
1104 llvm::SmallVector<WitnessVal> iterValues = std::move(*iterValuesOrErr);
1106 if (usesUnsignedCmp(forOp)) {
1108 auto lowerBoundUIntValue =
static_cast<uint64_t
>(*lowerBound);
1109 auto upperBoundUIntValue =
static_cast<uint64_t
>(*upperBound);
1110 auto stepUInt =
static_cast<uint64_t
>(*step);
1111 for (uint64_t iv = lowerBoundUIntValue, ub = upperBoundUIntValue, unsignedStep = stepUInt;
1112 iv < ub; iv += unsignedStep) {
1115 return signedIV.takeError();
1117 llvm::SmallVector<WitnessVal> regionArgs;
1119 regionArgs.append(iterValues.begin(), iterValues.end());
1120 auto result = runRegion(forOp.getRegion(), regionArgs, scope);
1122 return result.takeError();
1124 iterValues = std::move(result->values);
1127 for (int64_t iv = *lowerBound; iv < *upperBound; iv += *step) {
1128 llvm::SmallVector<WitnessVal> regionArgs;
1130 regionArgs.append(iterValues.begin(), iterValues.end());
1131 auto result = runRegion(forOp.getRegion(), regionArgs, scope);
1133 return result.takeError();
1135 iterValues = std::move(result->values);
1138 return bind(iterValues);
1141 if (
auto whileOp = dyn_cast<scf::WhileOp>(op)) {
1142 auto iterValuesOrErr = collectOperands(whileOp.getInits(), scope);
1143 if (!iterValuesOrErr) {
1144 return iterValuesOrErr.takeError();
1146 llvm::SmallVector<WitnessVal> iterValues = std::move(*iterValuesOrErr);
1148 auto beforeResult = runRegion(whileOp.getBefore(), iterValues, scope);
1149 if (!beforeResult) {
1150 return beforeResult.takeError();
1152 if (!beforeResult->terminated) {
1153 return makeError(
"scf.while before region must terminate with scf.condition");
1155 if (beforeResult->values.empty()) {
1156 return makeError(
"scf.while before region did not produce a condition");
1159 auto condition =
asBool(beforeResult->values.front());
1161 return condition.takeError();
1164 llvm::SmallVector<WitnessVal> nextValues;
1165 nextValues.append(beforeResult->values.begin() + 1, beforeResult->values.end());
1167 return bind(nextValues);
1170 auto afterResult = runRegion(whileOp.getAfter(), nextValues, scope);
1172 return afterResult.takeError();
1174 if (!afterResult->terminated) {
1175 return makeError(
"scf.while after region must terminate with scf.yield");
1177 iterValues = std::move(afterResult->values);
1181 return makeError(llvm::Twine(
"unsupported op in llzk-witgen: ") + op.getName().getStringRef());
1188llvm::Expected<llvm::SmallVector<WitnessVal>>
1190 return InvocationInterpreter(moduleOp, tables, field, uninitializedBehavior, rng)
This file implements helpers for DynamicAPInt operations and conversions that LLVM does not provide.
This file defines methods symbol lookup across LLZK operations and included files.
Information about the prime finite field used for the interval analysis.
::mlir::Region & getBody()
llvm::Expected< llvm::SmallVector< WitnessVal > > run(llzk::function::FuncDefOp funcOp, mlir::ArrayRef< WitnessVal > args)
Run a function with concrete arguments and return its result values.
FunctionInterpreter(mlir::ModuleOp moduleOp, mlir::SymbolTableCollection &tables, const llzk::Field &field, UninitializedBehavior uninitializedBehavior, std::mt19937_64 rng)
Build an interpreter for one module and field configuration.
std::optional< DynamicAPInt > applyIntToFeltOverflow(const DynamicAPInt &value, const Field &field, OverflowSemantics overflow)
std::optional< DynamicAPInt > applyFeltToIndexOverflow(const DynamicAPInt &value, unsigned indexBitWidth, OverflowSemantics overflow)
llvm::Expected< T > checkedCast(U u)
std::variant< std::monostate, bool, int64_t, llvm::DynamicAPInt, ArrayValueRef, PodValueRef, StructValueRef > WitnessVal
Runtime value representation used by the tool-local interpreter.
llvm::Expected< PodValueRef > asPod(const WitnessVal &value)
Require a POD value from the runtime variant.
llvm::Expected< bool > asBool(const WitnessVal &value)
Require a boolean value from the runtime variant.
UninitializedBehavior
Control how witgen materializes uninitialized/default values.
llvm::Expected< size_t > getStaticShapeElementCount(llvm::ArrayRef< int64_t > shape, llvm::StringRef context)
Return the static element count for one shape, rejecting dynamic sizes.
llvm::Expected< WitnessVal > defaultValue(Type type, SymbolTableCollection &tables, Operation *origin, const Field &field, UninitializedBehavior behavior, std::mt19937_64 *rng)
Build a default value for a supported LLZK type.
llvm::Expected< int64_t > asIndex(const WitnessVal &value)
Require an index value from the runtime variant.
llvm::Expected< size_t > checkedShapeDimToSize(int64_t dim, llvm::StringRef context)
Convert one static dimension to size_t, rejecting dynamic or invalid sizes.
llvm::Expected< llvm::DynamicAPInt > asFelt(const WitnessVal &value)
Require a felt value from the runtime variant.
llvm::Error makeError(const llvm::Twine &msg)
Build a string-backed error for user-facing witgen failures.
llvm::Expected< StructValueRef > asStruct(const WitnessVal &value)
Require a struct value from the runtime variant.
llvm::Expected< ArrayValueRef > asArray(const WitnessVal &value)
Require an array value from the runtime variant.
mlir::FailureOr< SymbolLookupResultUntyped > lookupTopLevelSymbol(mlir::SymbolTableCollection &tables, mlir::SymbolRefAttr symbol, mlir::Operation *origin, bool reportMissing=true)
APSInt toAPSInt(const DynamicAPInt &i)
DynamicAPInt modExp(const DynamicAPInt &base, const DynamicAPInt &exp, const DynamicAPInt &mod)
ExpressionValue notOp(const llvm::SMTSolverRef &solver, const ExpressionValue &val)