35#include <mlir/Conversion/AffineToStandard/AffineToStandard.h>
36#include <mlir/Dialect/Arith/IR/Arith.h>
37#include <mlir/Dialect/ControlFlow/IR/ControlFlowOps.h>
38#include <mlir/Dialect/Func/IR/FuncOps.h>
39#include <mlir/Dialect/LLVMIR/LLVMDialect.h>
40#include <mlir/Dialect/MemRef/IR/MemRef.h>
41#include <mlir/Dialect/SCF/IR/SCF.h>
42#include <mlir/Dialect/Utils/IndexingUtils.h>
43#include <mlir/IR/Builders.h>
44#include <mlir/IR/BuiltinAttributes.h>
45#include <mlir/IR/BuiltinOps.h>
46#include <mlir/IR/SymbolTable.h>
47#include <mlir/Pass/PassManager.h>
48#include <mlir/Transforms/Passes.h>
50#include <llvm/ADT/APInt.h>
51#include <llvm/ADT/STLExtras.h>
52#include <llvm/ADT/SmallString.h>
53#include <llvm/ADT/StringMap.h>
54#include <llvm/ADT/TypeSwitch.h>
55#include <llvm/Support/MathExtras.h>
68 llvm::SmallVector<Value> leaves;
72static FailureOr<std::reference_wrapper<const Field>> getModuleField(ModuleOp moduleOp) {
74 if (failed(
collectFields(moduleOp.getOperation(), fields,
false))) {
75 return moduleOp.emitError(
76 "llzk-witgen execution-engine lowering failed collecting 'felt.type' fields"
79 if (fields.size() != 1) {
80 return moduleOp.emitError(
"llzk-witgen execution-engine lowering requires exactly one field");
82 return *fields.begin();
86static std::string mangleFunctionName(function::FuncDefOp funcOp) {
87 auto symbolRef = funcOp.getFullyQualifiedName(
false);
88 llvm::SmallString<128> result(
"__llzk_witgen_");
89 for (StringRef piece :
getNames(symbolRef)) {
90 if (!result.empty() && result.back() !=
'_') {
93 for (
char c : piece) {
94 result += llvm::isAlnum(c) ? c :
'_';
97 return std::string(result);
101static Value makeIndexConstant(OpBuilder &builder, Location loc, int64_t value) {
102 return arith::ConstantIndexOp::create(builder, loc, value).getResult();
106static Value makeOneFelt(OpBuilder &builder, Location loc,
const Field &field) {
107 return arith::ConstantOp::create(
108 builder, loc, IntegerAttr::get(IntegerType::get(builder.getContext(), field.bitWidth()), 1)
113static FailureOr<Type> lowerScalarType(MLIRContext *context, Type type,
const Field &field) {
114 if (isa<felt::FeltType>(type)) {
115 return IntegerType::get(context, field.bitWidth());
117 if (isa<IndexType>(type)) {
120 if (
auto intType = dyn_cast<IntegerType>(type)) {
121 if (intType.getWidth() == 1) {
129static bool isScalarType(Type type) {
130 return isa<felt::FeltType, IndexType>(type) ||
131 (isa<IntegerType>(type) && llvm::cast<IntegerType>(type).getWidth() == 1);
135static LogicalResult flattenTypeLeaves(
136 Type type, SymbolTableCollection &tables, Operation *origin,
const Field &field,
137 SmallVectorImpl<Type> &out, llvm::ArrayRef<int64_t> prefixShape = {},
bool storage = false
139 auto emitScalarLeaf = [&](Type leafType) {
140 auto lowered = lowerScalarType(origin->getContext(), leafType, field);
141 if (failed(lowered)) {
144 if (!storage && prefixShape.empty()) {
145 out.push_back(*lowered);
148 llvm::SmallVector<int64_t> shape(prefixShape.begin(), prefixShape.end());
152 out.push_back(MemRefType::get(shape, *lowered));
156 if (isScalarType(type)) {
157 return emitScalarLeaf(type);
160 if (
auto arrayType = dyn_cast<array::ArrayType>(type)) {
161 llvm::SmallVector<int64_t> newPrefix(prefixShape.begin(), prefixShape.end());
162 newPrefix.append(arrayType.getShape().begin(), arrayType.getShape().end());
163 return flattenTypeLeaves(
164 arrayType.getElementType(), tables, origin, field, out, newPrefix,
true
168 if (
auto podType = dyn_cast<pod::PodType>(type)) {
169 for (pod::RecordAttr record : podType.getRecords()) {
171 flattenTypeLeaves(record.getType(), tables, origin, field, out, prefixShape,
true)
179 if (
auto structType = dyn_cast<component::StructType>(type)) {
180 auto def = structType.getDefinition(tables, origin);
182 return origin->emitError(
"could not resolve struct type during witgen lowering");
184 for (component::MemberDefOp member : def->get().getMemberDefs()) {
186 flattenTypeLeaves(member.getType(), tables, origin, field, out, prefixShape,
true)
194 return origin->emitError(
"unsupported type in llzk-witgen lowering: ") << type;
199getStridedMemRefType(MLIRContext *context, ArrayRef<int64_t> shape, Type elementType) {
200 SmallVector<int64_t> strides(shape.size(), ShapedType::kDynamic);
201 return MemRefType::get(
202 shape, elementType, StridedLayoutAttr::get(context, ShapedType::kDynamic, strides)
207static LogicalResult flattenABILeafTypes(
208 Type type, SymbolTableCollection &tables, Operation *origin,
const Field &field,
209 SmallVectorImpl<Type> &out,
size_t prefixRank = 0,
bool aggregateStorage =
false
211 auto emitScalarLeaf = [&](Type leafType) {
212 auto lowered = lowerScalarType(origin->getContext(), leafType, field);
213 if (failed(lowered)) {
216 if (!aggregateStorage && prefixRank == 0) {
217 out.push_back(*lowered);
220 SmallVector<int64_t> shape;
221 if (prefixRank == 0) {
224 shape.assign(prefixRank, ShapedType::kDynamic);
226 out.push_back(getStridedMemRefType(origin->getContext(), shape, *lowered));
230 if (isScalarType(type)) {
231 return emitScalarLeaf(type);
234 if (
auto arrayType = dyn_cast<array::ArrayType>(type)) {
235 return flattenABILeafTypes(
236 arrayType.getElementType(), tables, origin, field, out, prefixRank + arrayType.getRank(),
241 if (
auto podType = dyn_cast<pod::PodType>(type)) {
242 for (pod::RecordAttr record : podType.getRecords()) {
244 flattenABILeafTypes(record.getType(), tables, origin, field, out, prefixRank,
true)
252 if (
auto structType = dyn_cast<component::StructType>(type)) {
253 auto def = structType.getDefinition(tables, origin);
255 return origin->emitError(
"could not resolve struct type during witgen lowering");
257 for (component::MemberDefOp member : def->get().getMemberDefs()) {
259 flattenABILeafTypes(member.getType(), tables, origin, field, out, prefixRank,
true)
267 return origin->emitError(
"unsupported type in llzk-witgen lowering: ") << type;
271static FailureOr<size_t>
272getLeafCount(Type type, SymbolTableCollection &tables, Operation *origin,
const Field &field) {
273 SmallVector<Type> leaves;
274 if (failed(flattenTypeLeaves(type, tables, origin, field, leaves))) {
277 return leaves.size();
281static FailureOr<SmallVector<Type>>
282getLeafTypes(Type type, SymbolTableCollection &tables, Operation *origin,
const Field &field) {
283 SmallVector<Type> leaves;
284 if (failed(flattenTypeLeaves(type, tables, origin, field, leaves))) {
291static FailureOr<SmallVector<Type>>
292getABILeafTypes(Type type, SymbolTableCollection &tables, Operation *origin,
const Field &field) {
293 SmallVector<Type> leaves;
294 if (failed(flattenABILeafTypes(type, tables, origin, field, leaves))) {
301static FailureOr<std::pair<size_t, size_t>> getNamedLeafSpan(
302 Type ownerType, StringRef name, SymbolTableCollection &tables, Operation *origin,
305 if (
auto podType = dyn_cast<pod::PodType>(ownerType)) {
307 for (pod::RecordAttr record : podType.getRecords()) {
308 auto count = getLeafCount(record.getType(), tables, origin, field);
312 if (record.getName().getValue() == name) {
313 return std::pair<size_t, size_t> {running, *count};
319 if (
auto structType = dyn_cast<component::StructType>(ownerType)) {
320 auto def = structType.getDefinition(tables, origin);
322 return origin->emitError(
"could not resolve struct type during witgen lowering");
325 for (component::MemberDefOp member : def->get().getMemberDefs()) {
326 auto count = getLeafCount(member.getType(), tables, origin, field);
330 if (member.getSymName() == name) {
331 return std::pair<size_t, size_t> {running, *count};
337 return origin->emitError(
"could not resolve aggregate member/record @") << name;
341static FailureOr<Type>
342getNamedSubType(Type ownerType, StringRef name, SymbolTableCollection &tables, Operation *origin) {
343 if (
auto podType = dyn_cast<pod::PodType>(ownerType)) {
344 for (pod::RecordAttr record : podType.getRecords()) {
345 if (record.getName().getValue() == name) {
346 return record.getType();
350 if (
auto structType = dyn_cast<component::StructType>(ownerType)) {
351 auto def = structType.getDefinition(tables, origin);
353 return origin->emitError(
"could not resolve struct type during witgen lowering");
355 for (component::MemberDefOp member : def->get().getMemberDefs()) {
356 if (member.getSymName() == name) {
357 return member.getType();
361 return origin->emitError(
"could not resolve aggregate member/record @") << name;
365static FailureOr<Value> createZeroMemRef(OpBuilder &builder, Location loc, MemRefType memrefType) {
368 return emitError(loc) << llvm::toString(elementCount.takeError());
370 Value alloc = memref::AllocOp::create(builder, loc, memrefType);
371 auto elementType = memrefType.getElementType();
373 if (isa<IndexType>(elementType)) {
374 zero = arith::ConstantIndexOp::create(builder, loc, 0);
376 zero = arith::ConstantOp::create(
377 builder, loc, IntegerAttr::get(llvm::cast<IntegerType>(elementType), 0)
380 auto strides = mlir::computeStrides(memrefType.getShape());
381 for (
size_t flat = 0; flat < *elementCount; ++flat) {
384 return emitError(loc) << llvm::toString(flatSigned.takeError());
386 SmallVector<Value> indices;
387 for (int64_t index : mlir::delinearize(*flatSigned, strides)) {
388 indices.push_back(makeIndexConstant(builder, loc, index));
390 memref::StoreOp::create(builder, loc, zero, alloc, indices);
396static FailureOr<Value> createRandomMemRef(
397 OpBuilder &builder, Location loc, MemRefType memrefType,
const Field &field,
402 return emitError(loc) << llvm::toString(elementCount.takeError());
404 Value alloc = memref::AllocOp::create(builder, loc, memrefType);
405 auto elementType = memrefType.getElementType();
406 auto strides = mlir::computeStrides(memrefType.getShape());
407 for (
size_t flat = 0; flat < *elementCount; ++flat) {
410 return emitError(loc) << llvm::toString(flatSigned.takeError());
412 SmallVector<Value> indices;
413 for (int64_t index : mlir::delinearize(*flatSigned, strides)) {
414 indices.push_back(makeIndexConstant(builder, loc, index));
416 if (isa<IndexType>(elementType)) {
418 memref::StoreOp::create(
419 builder, loc, arith::ConstantIndexOp::create(builder, loc, value), alloc, indices
423 auto intType = llvm::cast<IntegerType>(elementType);
424 if (intType.getWidth() == 1) {
425 memref::StoreOp::create(
427 arith::ConstantOp::create(
428 builder, loc, IntegerAttr::get(intType, APInt(1,
randomBoolValue(rng)))
435 memref::StoreOp::create(
437 arith::ConstantOp::create(
440 intType, cantFail(
checkedToAPInt(candidate, intType.getWidth(),
false))
450static FailureOr<LoweredValue> createDefaultValue(
451 OpBuilder &builder, Location loc, Type type, SymbolTableCollection &tables, Operation *origin,
454 LoweredValue lowered {type, {}};
455 auto leafTypes = getLeafTypes(type, tables, origin, field);
456 if (failed(leafTypes)) {
459 for (Type leafType : *leafTypes) {
461 return origin->emitError(
462 "fail-mode default materialization is unsupported in witgen lowering because it would "
463 "hide uninitialized reads"
467 if (
auto memrefType = dyn_cast<MemRefType>(leafType)) {
468 auto randomMemRef = createRandomMemRef(builder, loc, memrefType, field, rng);
469 if (failed(randomMemRef)) {
472 lowered.leaves.push_back(*randomMemRef);
475 if (isa<IndexType>(leafType)) {
476 lowered.leaves.push_back(
481 auto intType = llvm::cast<IntegerType>(leafType);
482 if (intType.getWidth() == 1) {
483 lowered.leaves.push_back(
484 arith::ConstantOp::create(
485 builder, loc, IntegerAttr::get(intType, APInt(1,
randomBoolValue(rng)))
491 lowered.leaves.push_back(
492 arith::ConstantOp::create(
495 intType, cantFail(
checkedToAPInt(candidate, intType.getWidth(),
false))
501 if (
auto memrefType = dyn_cast<MemRefType>(leafType)) {
502 auto zeroMemRef = createZeroMemRef(builder, loc, memrefType);
503 if (failed(zeroMemRef)) {
506 lowered.leaves.push_back(*zeroMemRef);
509 if (isa<IndexType>(leafType)) {
510 lowered.leaves.push_back(arith::ConstantIndexOp::create(builder, loc, 0));
513 lowered.leaves.push_back(
514 arith::ConstantOp::create(
515 builder, loc, IntegerAttr::get(llvm::cast<IntegerType>(leafType), 0)
523static Value normalizeWideValue(
524 OpBuilder &builder, Location loc, Value wideValue,
unsigned dstWidth,
const Field &field
526 auto wideType = llvm::cast<IntegerType>(wideValue.getType());
527 Value modulus = arith::ConstantOp::create(
528 builder, loc, field.getPrimeAttr(builder.getContext(), wideType.getWidth())
530 Value reduced = arith::RemUIOp::create(builder, loc, wideValue, modulus);
531 return arith::TruncIOp::create(
532 builder, loc, IntegerType::get(builder.getContext(), dstWidth), reduced
537static Value normalizeSignedWideValue(
538 OpBuilder &builder, Location loc, Value wideValue,
unsigned dstWidth,
const Field &field
540 auto wideType = llvm::cast<IntegerType>(wideValue.getType());
541 Value modulus = arith::ConstantOp::create(
542 builder, loc, field.getPrimeAttr(builder.getContext(), wideType.getWidth())
544 Value reduced = arith::RemSIOp::create(builder, loc, wideValue, modulus);
545 Value zero = arith::ConstantOp::create(builder, loc, IntegerAttr::get(wideType, 0));
546 Value isNegative = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::slt, reduced, zero);
547 Value adjusted = arith::AddIOp::create(builder, loc, reduced, modulus);
548 Value canonical = arith::SelectOp::create(builder, loc, isNegative, adjusted, reduced);
549 return arith::TruncIOp::create(
550 builder, loc, IntegerType::get(builder.getContext(), dstWidth), canonical
556lowerFeltToSignedWide(OpBuilder &builder, Location loc, Value operand,
const Field &field) {
557 unsigned width = field.bitWidth();
558 unsigned wideWidth = width + 1;
559 auto feltType = IntegerType::get(builder.getContext(), width);
560 auto wideType = IntegerType::get(builder.getContext(), wideWidth);
561 Value operandWide = arith::ExtUIOp::create(builder, loc, wideType, operand);
563 arith::ConstantOp::create(builder, loc, field.getPrimeAttr(builder.getContext(), wideWidth));
564 Value half = arith::ConstantOp::create(
565 builder, loc, IntegerAttr::get(feltType, cantFail(
checkedToAPInt(field.half(), width,
false)))
567 Value isNegative = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::uge, operand, half);
568 Value signedOperand = arith::SubIOp::create(builder, loc, operandWide, prime);
569 return arith::SelectOp::create(builder, loc, isNegative, signedOperand, operandWide);
573static void assertNonZeroFelt(OpBuilder &builder, Location loc, Value operand, StringRef message) {
574 auto operandType = llvm::cast<IntegerType>(operand.getType());
575 Value zero = arith::ConstantOp::create(builder, loc, IntegerAttr::get(operandType, 0));
576 Value isNonZero = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::ne, operand, zero);
577 cf::AssertOp::create(builder, loc, isNonZero, message);
582lowerFeltAdd(OpBuilder &builder, Location loc, Value lhs, Value rhs,
const Field &field) {
583 unsigned width = field.bitWidth();
584 unsigned wideWidth = width + 1;
585 auto wideType = IntegerType::get(builder.getContext(), wideWidth);
586 Value lhsWide = arith::ExtUIOp::create(builder, loc, wideType, lhs);
587 Value rhsWide = arith::ExtUIOp::create(builder, loc, wideType, rhs);
588 Value sum = arith::AddIOp::create(builder, loc, lhsWide, rhsWide);
589 return normalizeWideValue(builder, loc, sum, width, field);
594lowerFeltSub(OpBuilder &builder, Location loc, Value lhs, Value rhs,
const Field &field) {
595 unsigned width = field.bitWidth();
596 unsigned wideWidth = width + 1;
597 auto wideType = IntegerType::get(builder.getContext(), wideWidth);
598 Value lhsWide = arith::ExtUIOp::create(builder, loc, wideType, lhs);
599 Value rhsWide = arith::ExtUIOp::create(builder, loc, wideType, rhs);
601 arith::ConstantOp::create(builder, loc, field.getPrimeAttr(builder.getContext(), wideWidth));
602 Value lhsPlusMod = arith::AddIOp::create(builder, loc, lhsWide, modulus);
603 Value diff = arith::SubIOp::create(builder, loc, lhsPlusMod, rhsWide);
604 return normalizeWideValue(builder, loc, diff, width, field);
608static Value lowerFeltNeg(OpBuilder &builder, Location loc, Value operand,
const Field &field) {
609 unsigned width = field.bitWidth();
610 unsigned wideWidth = width + 1;
611 auto wideType = IntegerType::get(builder.getContext(), wideWidth);
612 Value operandWide = arith::ExtUIOp::create(builder, loc, wideType, operand);
614 arith::ConstantOp::create(builder, loc, field.getPrimeAttr(builder.getContext(), wideWidth));
615 Value diff = arith::SubIOp::create(builder, loc, modulus, operandWide);
616 return normalizeWideValue(builder, loc, diff, width, field);
621lowerFeltMul(OpBuilder &builder, Location loc, Value lhs, Value rhs,
const Field &field) {
622 unsigned width = field.bitWidth();
623 unsigned wideWidth = width * 2;
624 auto wideType = IntegerType::get(builder.getContext(), wideWidth);
625 Value lhsWide = arith::ExtUIOp::create(builder, loc, wideType, lhs);
626 Value rhsWide = arith::ExtUIOp::create(builder, loc, wideType, rhs);
627 Value product = arith::MulIOp::create(builder, loc, lhsWide, rhsWide);
628 return normalizeWideValue(builder, loc, product, width, field);
632static Value lowerFeltInv(OpBuilder &builder, Location loc, Value operand,
const Field &field) {
633 llvm::APInt exponent = cantFail(
checkedToAPInt(field.prime() - 2, field.bitWidth(),
false));
634 Value result = makeOneFelt(builder, loc, field);
635 Value base = operand;
636 for (
unsigned bit = 0; bit < exponent.getBitWidth(); ++bit) {
638 result = lowerFeltMul(builder, loc, result, base, field);
640 if (bit + 1 < exponent.getBitWidth()) {
641 base = lowerFeltMul(builder, loc, base, base, field);
649lowerFeltDiv(OpBuilder &builder, Location loc, Value lhs, Value rhs,
const Field &field) {
650 return lowerFeltMul(builder, loc, lhs, lowerFeltInv(builder, loc, rhs, field), field);
655lowerFeltPow(OpBuilder &builder, Location loc, Value base, Value exponent,
const Field &field) {
656 auto feltType = IntegerType::get(builder.getContext(), field.bitWidth());
657 Value zero = arith::ConstantOp::create(builder, loc, IntegerAttr::get(feltType, 0));
658 Value one = arith::ConstantOp::create(builder, loc, IntegerAttr::get(feltType, 1));
659 Value result = makeOneFelt(builder, loc, field);
660 Value currentBase = base;
661 for (
unsigned bit = 0; bit < field.bitWidth(); ++bit) {
662 Value bitIndex = arith::ConstantOp::create(
663 builder, loc, IntegerAttr::get(feltType, llvm::APInt(field.bitWidth(), bit))
665 Value shifted = arith::ShRUIOp::create(builder, loc, exponent, bitIndex);
666 Value masked = arith::AndIOp::create(builder, loc, shifted, one);
667 Value bitIsSet = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::ne, masked, zero);
668 auto ifOp = scf::IfOp::create(builder, loc, TypeRange {feltType}, bitIsSet,
true);
670 OpBuilder::InsertionGuard guard(builder);
671 builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
672 Value multiplied = lowerFeltMul(builder, loc, result, currentBase, field);
673 scf::YieldOp::create(builder, loc, multiplied);
676 OpBuilder::InsertionGuard guard(builder);
677 builder.setInsertionPointToStart(&ifOp.getElseRegion().front());
678 scf::YieldOp::create(builder, loc, result);
680 result = ifOp.getResult(0);
681 if (bit + 1 < field.bitWidth()) {
682 currentBase = lowerFeltMul(builder, loc, currentBase, currentBase, field);
690lowerFeltShl(OpBuilder &builder, Location loc, Value lhs, Value rhs,
const Field &field) {
691 auto feltType = IntegerType::get(builder.getContext(), field.bitWidth());
692 Value two = arith::ConstantOp::create(builder, loc, IntegerAttr::get(feltType, 2));
693 return lowerFeltMul(builder, loc, lhs, lowerFeltPow(builder, loc, two, rhs, field), field);
698lowerFeltOr(OpBuilder &builder, Location loc, Value lhs, Value rhs,
const Field &field) {
699 unsigned width = field.bitWidth();
700 auto wideType = IntegerType::get(builder.getContext(), width + 1);
701 Value orValue = arith::OrIOp::create(builder, loc, lhs, rhs);
702 Value orWide = arith::ExtUIOp::create(builder, loc, wideType, orValue);
703 return normalizeWideValue(builder, loc, orWide, width, field);
708lowerFeltXor(OpBuilder &builder, Location loc, Value lhs, Value rhs,
const Field &field) {
709 unsigned width = field.bitWidth();
710 auto wideType = IntegerType::get(builder.getContext(), width + 1);
711 Value xorValue = arith::XOrIOp::create(builder, loc, lhs, rhs);
712 Value xorWide = arith::ExtUIOp::create(builder, loc, wideType, xorValue);
713 return normalizeWideValue(builder, loc, xorWide, width, field);
717static Value lowerFeltUnsignedDiv(OpBuilder &builder, Location loc, Value lhs, Value rhs) {
718 return arith::DivUIOp::create(builder, loc, lhs, rhs);
723lowerFeltSignedDiv(OpBuilder &builder, Location loc, Value lhs, Value rhs,
const Field &field) {
724 unsigned width = field.bitWidth();
725 Value lhsSigned = lowerFeltToSignedWide(builder, loc, lhs, field);
726 Value rhsSigned = lowerFeltToSignedWide(builder, loc, rhs, field);
727 Value quotient = arith::DivSIOp::create(builder, loc, lhsSigned, rhsSigned);
728 return normalizeSignedWideValue(builder, loc, quotient, width, field);
732static Value lowerFeltUnsignedMod(OpBuilder &builder, Location loc, Value lhs, Value rhs) {
733 return arith::RemUIOp::create(builder, loc, lhs, rhs);
738lowerFeltSignedMod(OpBuilder &builder, Location loc, Value lhs, Value rhs,
const Field &field) {
739 unsigned width = field.bitWidth();
740 Value lhsSigned = lowerFeltToSignedWide(builder, loc, lhs, field);
741 Value rhsSigned = lowerFeltToSignedWide(builder, loc, rhs, field);
742 Value remainder = arith::RemSIOp::create(builder, loc, lhsSigned, rhsSigned);
743 return normalizeSignedWideValue(builder, loc, remainder, width, field);
748lowerFeltShr(OpBuilder &builder, Location loc, Value lhs, Value rhs,
const Field &field) {
749 auto feltType = IntegerType::get(builder.getContext(), field.bitWidth());
750 Value width = arith::ConstantOp::create(
751 builder, loc, IntegerAttr::get(feltType, llvm::APInt(field.bitWidth(), field.bitWidth()))
753 Value shiftTooLarge = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::uge, rhs, width);
754 Value zero = arith::ConstantOp::create(builder, loc, IntegerAttr::get(feltType, 0));
755 Value maxValidShift = arith::ConstantOp::create(
756 builder, loc, IntegerAttr::get(feltType, llvm::APInt(field.bitWidth(), field.bitWidth() - 1))
758 Value clampedShift = arith::MinUIOp::create(builder, loc, rhs, maxValidShift);
759 Value shifted = arith::ShRUIOp::create(builder, loc, lhs, clampedShift);
760 return arith::SelectOp::create(builder, loc, shiftTooLarge, zero, shifted);
764static Value lowerFeltNot(OpBuilder &builder, Location loc, Value operand,
const Field &field) {
765 unsigned width = field.bitWidth();
766 auto feltType = IntegerType::get(builder.getContext(), width);
767 auto wideType = IntegerType::get(builder.getContext(), width + 1);
768 Value maxMask = arith::ConstantOp::create(
769 builder, loc, IntegerAttr::get(feltType, llvm::APInt::getAllOnes(width))
771 Value complement = arith::XOrIOp::create(builder, loc, operand, maxMask);
772 Value complementWide = arith::ExtUIOp::create(builder, loc, wideType, complement);
773 return normalizeWideValue(builder, loc, complementWide, width, field);
777static Value loadStorageScalar(OpBuilder &builder, Location loc, Value storageLeaf) {
778 auto memrefType = llvm::cast<MemRefType>(storageLeaf.getType());
779 SmallVector<Value> indices;
780 indices.reserve(memrefType.getRank());
781 for (int64_t dim = 0; dim < memrefType.getRank(); ++dim) {
782 indices.push_back(makeIndexConstant(builder, loc, 0));
784 return memref::LoadOp::create(builder, loc, storageLeaf, indices);
788static void storeStorageScalar(OpBuilder &builder, Location loc, Value scalar, Value storageLeaf) {
789 auto memrefType = llvm::cast<MemRefType>(storageLeaf.getType());
790 SmallVector<Value> indices;
791 indices.reserve(memrefType.getRank());
792 for (int64_t dim = 0; dim < memrefType.getRank(); ++dim) {
793 indices.push_back(makeIndexConstant(builder, loc, 0));
795 memref::StoreOp::create(builder, loc, scalar, storageLeaf, indices);
799static LogicalResult copyIntoStorage(
800 OpBuilder &builder, Location loc, Type sourceType, ArrayRef<Value> destLeaves,
801 ArrayRef<Value> sourceLeaves, SymbolTableCollection &tables, Operation *origin,
804 auto leafTypes = getLeafTypes(sourceType, tables, origin, field);
805 if (failed(leafTypes)) {
808 if (destLeaves.size() != sourceLeaves.size() || destLeaves.size() != leafTypes->size()) {
809 return origin->emitError(
"flattened leaf mismatch while copying aggregate storage");
811 for (
auto [leafType, destLeaf, srcLeaf] : llvm::zip(*leafTypes, destLeaves, sourceLeaves)) {
812 if (isa<MemRefType>(leafType)) {
813 memref::CopyOp::create(builder, loc, srcLeaf, destLeaf);
816 storeStorageScalar(builder, loc, srcLeaf, destLeaf);
822static FailureOr<LoweredValue> readNamedAggregateValue(
823 OpBuilder &builder, Location loc, Type ownerType, StringRef name,
const LoweredValue &owner,
824 SymbolTableCollection &tables, Operation *origin,
const Field &field
826 auto subType = getNamedSubType(ownerType, name, tables, origin);
827 if (failed(subType)) {
830 auto span = getNamedLeafSpan(ownerType, name, tables, origin, field);
834 LoweredValue result {*subType, {}};
835 auto leafTypes = getLeafTypes(*subType, tables, origin, field);
836 if (failed(leafTypes)) {
839 auto leaves = ArrayRef<Value>(owner.leaves).slice(span->first, span->second);
840 for (
auto [leafType, leafValue] : llvm::zip(*leafTypes, leaves)) {
841 if (isa<MemRefType>(leafType)) {
842 result.leaves.push_back(leafValue);
844 result.leaves.push_back(loadStorageScalar(builder, loc, leafValue));
851static LogicalResult writeNamedAggregateValue(
852 OpBuilder &builder, Location loc, Type ownerType, StringRef name, LoweredValue &owner,
853 const LoweredValue &value, SymbolTableCollection &tables, Operation *origin,
const Field &field
855 auto subType = getNamedSubType(ownerType, name, tables, origin);
856 if (failed(subType)) {
859 auto span = getNamedLeafSpan(ownerType, name, tables, origin, field);
863 return copyIntoStorage(
864 builder, loc, *subType, ArrayRef<Value>(owner.leaves).slice(span->first, span->second),
865 value.leaves, tables, origin, field
870static FailureOr<Value>
871createElementSubview(OpBuilder &builder, Location loc, Value
source, ValueRange outerIndices) {
872 auto sourceType = llvm::cast<MemRefType>(
source.getType());
873 SmallVector<OpFoldResult> mixedOffsets;
874 SmallVector<OpFoldResult> mixedSizes;
875 SmallVector<OpFoldResult> mixedStrides;
878 return emitError(loc) << llvm::toString(indexedRank.takeError());
880 mixedOffsets.reserve(sourceType.getRank());
881 mixedSizes.reserve(sourceType.getRank());
882 mixedStrides.reserve(sourceType.getRank());
883 for (Value index : outerIndices) {
884 mixedOffsets.push_back(index);
886 for (int64_t dim = *indexedRank; dim < sourceType.getRank(); ++dim) {
887 mixedOffsets.push_back(builder.getIndexAttr(0));
889 for (int64_t dim = 0; dim < *indexedRank; ++dim) {
890 mixedSizes.push_back(builder.getIndexAttr(1));
892 for (int64_t dim = *indexedRank; dim < sourceType.getRank(); ++dim) {
893 mixedSizes.push_back(memref::getMixedSize(builder, loc,
source, dim));
895 for (int64_t dim = 0; dim < sourceType.getRank(); ++dim) {
896 mixedStrides.push_back(builder.getIndexAttr(1));
898 SmallVector<int64_t> desiredShape;
901 return emitError(loc) << llvm::toString(reserveSize.takeError());
903 desiredShape.reserve(*reserveSize);
904 for (int64_t dim = *indexedRank; dim < sourceType.getRank(); ++dim) {
907 return emitError(loc) << llvm::toString(dimIndex.takeError());
909 if (
auto attr = llvm::dyn_cast<Attribute>(mixedSizes[*dimIndex])) {
910 desiredShape.push_back(llvm::cast<IntegerAttr>(attr).getInt());
912 desiredShape.push_back(ShapedType::kDynamic);
915 if (desiredShape.empty()) {
916 desiredShape.push_back(1);
918 auto resultType = llvm::cast<MemRefType>(memref::SubViewOp::inferRankReducedResultType(
919 desiredShape, sourceType, mixedOffsets, mixedSizes, mixedStrides
921 auto op = memref::SubViewOp::create(
922 builder, loc, resultType,
source, mixedOffsets, mixedSizes, mixedStrides
924 return success(op.getResult());
928static FailureOr<LoweredValue> readArrayElement(
929 OpBuilder &builder, Location loc, array::ArrayType arrayType,
const LoweredValue &arrayValue,
930 ArrayRef<Value> indices
932 Type elementType = arrayType.getElementType();
933 LoweredValue result {elementType, {}};
934 if (isScalarType(elementType)) {
935 result.leaves.push_back(
936 memref::LoadOp::create(builder, loc, arrayValue.leaves.front(), indices)
941 for (Value sourceLeaf : arrayValue.leaves) {
942 auto subview = createElementSubview(builder, loc, sourceLeaf, indices);
943 if (failed(subview)) {
946 result.leaves.push_back(*subview);
952static LogicalResult writeArrayElement(
953 OpBuilder &builder, Location loc, array::ArrayType arrayType, LoweredValue &arrayValue,
954 ArrayRef<Value> indices,
const LoweredValue &elementValue
956 Type elementType = arrayType.getElementType();
957 if (isScalarType(elementType)) {
958 memref::StoreOp::create(
959 builder, loc, elementValue.leaves.front(), arrayValue.leaves.front(), indices
964 for (
auto [destLeaf, srcLeaf] : llvm::zip(arrayValue.leaves, elementValue.leaves)) {
965 auto subview = createElementSubview(builder, loc, destLeaf, indices);
966 if (failed(subview)) {
969 memref::CopyOp::create(builder, loc, srcLeaf, *subview);
975static LogicalResult appendFlatLeavesToTypes(
976 OpBuilder &builder, Location loc,
const LoweredValue &value, ArrayRef<Type> targetLeafTypes,
977 SmallVectorImpl<Value> &out, Operation *origin
979 if (targetLeafTypes.size() != value.leaves.size()) {
980 return origin->emitError(
"flattened leaf mismatch during call lowering");
982 for (
auto [leafValue, leafType] : llvm::zip(value.leaves, targetLeafTypes)) {
983 if (leafValue.getType() == leafType) {
984 out.push_back(leafValue);
987 if (isa<MemRefType>(leafValue.getType()) && isa<MemRefType>(leafType)) {
988 out.push_back(memref::CastOp::create(builder, loc, leafType, leafValue));
991 return origin->emitError(
"lowered leaf type mismatch during call lowering");
1001 ModuleOp
mod, SymbolTableCollection &symbolTables,
const Field &moduleField,
1002 const WitgenOptions &options
1004 : moduleOp(
mod), tables(symbolTables), field(moduleField),
1005 uninitializedBehavior(options.uninitializedBehavior), rng(
makeDefaultValueRng(options)) {}
1008 FailureOr<func::FuncOp> lowerFunction(function::FuncDefOp funcOp) {
1009 if (funcOp.isExternal()) {
1010 return funcOp.emitError(
"execution-engine backend does not lower extern functions");
1012 if (!funcOp.getBody().hasOneBlock()) {
1013 return funcOp.emitError(
"execution-engine backend only supports single-block functions");
1016 SmallVector<Type> loweredArgTypes;
1017 for (Type argType : funcOp.getArgumentTypes()) {
1019 flattenABILeafTypes(argType, tables, funcOp.getOperation(), field, loweredArgTypes)
1024 SmallVector<Type> loweredResultTypes;
1025 for (Type resultType : funcOp.getResultTypes()) {
1026 if (failed(flattenABILeafTypes(
1027 resultType, tables, funcOp.getOperation(), field, loweredResultTypes
1033 OpBuilder moduleBuilder(moduleOp.getContext());
1034 moduleBuilder.setInsertionPointToEnd(moduleOp.getBody());
1035 auto loweredFunc = func::FuncOp::create(
1036 moduleBuilder, funcOp.getLoc(), mangleFunctionName(funcOp),
1037 moduleBuilder.getFunctionType(loweredArgTypes, loweredResultTypes)
1039 Block *entry = loweredFunc.addEntryBlock();
1040 OpBuilder builder(entry, entry->begin());
1042 DenseMap<Value, LoweredValue> valueMap;
1043 unsigned cursor = 0;
1044 for (
auto [arg, argType] :
1045 llvm::zip(funcOp.getBody().front().getArguments(), funcOp.getArgumentTypes())) {
1046 auto leafCount = getLeafCount(argType, tables, funcOp.getOperation(), field);
1047 if (failed(leafCount)) {
1048 loweredFunc.erase();
1051 LoweredValue lowered {argType, {}};
1052 lowered.leaves.append(
1053 entry->getArguments().begin() + cursor,
1054 entry->getArguments().begin() + cursor + *leafCount
1056 cursor += *leafCount;
1057 valueMap[arg] = std::move(lowered);
1060 if (failed(lowerBlock(builder, funcOp.getBody().front(), valueMap))) {
1061 loweredFunc.erase();
1069 SymbolTableCollection &tables;
1072 std::mt19937_64 rng;
1075 FailureOr<LoweredValue>
1076 lookup(Value value, DenseMap<Value, LoweredValue> &valueMap, Operation *origin) {
1077 auto it = valueMap.find(value);
1078 if (it == valueMap.end()) {
1079 return origin->emitError(
"failed to find lowered SSA value");
1086 lookupScalar(Value value, DenseMap<Value, LoweredValue> &valueMap, Operation *origin) {
1087 auto lowered = lookup(value, valueMap, origin);
1088 if (failed(lowered) || lowered->leaves.size() != 1 ||
1089 isa<MemRefType>(lowered->leaves.front().getType())) {
1090 return origin->emitError(
"expected scalar lowered value");
1092 return lowered->leaves.front();
1097 lowerBlock(OpBuilder &builder, Block &block, DenseMap<Value, LoweredValue> &valueMap) {
1098 for (Operation &op : block) {
1099 if (failed(lowerOperation(builder, op, valueMap))) {
1108 lowerFeltCmp(OpBuilder &builder, Location loc, boolean::CmpOp cmpOp, Value lhs, Value rhs) {
1111 auto predicate = [&cmpOp, &loc]() -> FailureOr<arith::CmpIPredicate> {
1112 switch (cmpOp.getPredicate()) {
1114 return arith::CmpIPredicate::eq;
1116 return arith::CmpIPredicate::ne;
1118 return arith::CmpIPredicate::ult;
1120 return arith::CmpIPredicate::ule;
1122 return arith::CmpIPredicate::ugt;
1124 return arith::CmpIPredicate::uge;
1126 return emitError(loc,
"unsupported comparison predicate");
1128 if (failed(predicate)) {
1131 return arith::CmpIOp::create(builder, loc, *predicate, lhs, rhs).getResult();
1136 lowerOperation(OpBuilder &builder, Operation &op, DenseMap<Value, LoweredValue> &valueMap) {
1137 Location loc = op.getLoc();
1139 auto bind = [&](Value result, LoweredValue lowered) {
1140 valueMap[result] = std::move(lowered);
1144 if (
auto returnOp = dyn_cast<function::ReturnOp>(op)) {
1145 SmallVector<Value> results;
1146 for (Value operand : returnOp.getOperands()) {
1147 auto lowered = lookup(operand, valueMap, returnOp.getOperation());
1148 auto leafTypes = getABILeafTypes(operand.getType(), tables, returnOp.getOperation(), field);
1149 if (failed(lowered) || failed(leafTypes) ||
1150 failed(appendFlatLeavesToTypes(
1151 builder, loc, *lowered, *leafTypes, results, returnOp.getOperation()
1156 func::ReturnOp::create(builder, loc, results);
1160 if (
auto yieldOp = dyn_cast<scf::YieldOp>(op)) {
1161 SmallVector<Value> results;
1162 for (Value operand : yieldOp.getOperands()) {
1163 auto lowered = lookup(operand, valueMap, yieldOp.getOperation());
1164 auto leafTypes = getABILeafTypes(operand.getType(), tables, yieldOp.getOperation(), field);
1165 if (failed(lowered) || failed(leafTypes) ||
1166 failed(appendFlatLeavesToTypes(
1167 builder, loc, *lowered, *leafTypes, results, yieldOp.getOperation()
1172 scf::YieldOp::create(builder, loc, results);
1175 if (
auto conditionOp = dyn_cast<scf::ConditionOp>(op)) {
1177 lookupScalar(conditionOp.getCondition(), valueMap, conditionOp.getOperation());
1178 if (failed(condition)) {
1181 SmallVector<Value> results;
1182 for (Value operand : conditionOp.getArgs()) {
1183 auto lowered = lookup(operand, valueMap, conditionOp.getOperation());
1185 getABILeafTypes(operand.getType(), tables, conditionOp.getOperation(), field);
1186 if (failed(lowered) || failed(leafTypes) ||
1187 failed(appendFlatLeavesToTypes(
1188 builder, loc, *lowered, *leafTypes, results, conditionOp.getOperation()
1193 scf::ConditionOp::create(builder, loc, *condition, results);
1197 if (
auto constantOp = dyn_cast<arith::ConstantOp>(op)) {
1198 Operation *clone = builder.clone(op);
1200 constantOp.getResult(), LoweredValue {constantOp.getType(), {clone->getResult(0)}}
1204 if (
auto feltConst = dyn_cast<felt::FeltConstantOp>(op)) {
1205 auto constVal = feltConst.getValue().getReducedValue();
1207 return feltConst.emitError(
1208 "felt constant requires a specified field for witness generation"
1211 auto intType = IntegerType::get(builder.getContext(), field.bitWidth());
1213 auto intVal = cantFail(
checkedToAPInt(*constVal, field.bitWidth(),
false));
1214 Value lowered = arith::ConstantOp::create(builder, loc, IntegerAttr::get(intType, intVal));
1215 return bind(feltConst.getResult(), LoweredValue {feltConst.getType(), {lowered}});
1218 if (
auto nondetOp = dyn_cast<llzk::NonDetOp>(op)) {
1219 auto lowered = createDefaultValue(
1220 builder, loc, nondetOp.getType(), tables, nondetOp.getOperation(), field,
1221 uninitializedBehavior, rng
1223 if (failed(lowered)) {
1226 return bind(nondetOp.getResult(), std::move(*lowered));
1229 if (
auto addOp = dyn_cast<felt::AddFeltOp>(op)) {
1230 auto lhs = lookupScalar(addOp.getLhs(), valueMap, addOp.getOperation());
1231 auto rhs = lookupScalar(addOp.getRhs(), valueMap, addOp.getOperation());
1232 if (failed(lhs) || failed(rhs)) {
1237 LoweredValue {addOp.getType(), {lowerFeltAdd(builder, loc, *lhs, *rhs, field)}}
1240 if (
auto powOp = dyn_cast<felt::PowFeltOp>(op)) {
1241 auto lhs = lookupScalar(powOp.getLhs(), valueMap, powOp.getOperation());
1242 auto rhs = lookupScalar(powOp.getRhs(), valueMap, powOp.getOperation());
1243 if (failed(lhs) || failed(rhs)) {
1248 LoweredValue {powOp.getType(), {lowerFeltPow(builder, loc, *lhs, *rhs, field)}}
1251 if (
auto andOp = dyn_cast<felt::AndFeltOp>(op)) {
1252 auto lhs = lookupScalar(andOp.getLhs(), valueMap, andOp.getOperation());
1253 auto rhs = lookupScalar(andOp.getRhs(), valueMap, andOp.getOperation());
1254 if (failed(lhs) || failed(rhs)) {
1259 LoweredValue {andOp.getType(), {arith::AndIOp::create(builder, loc, *lhs, *rhs)}}
1262 if (
auto orOp = dyn_cast<felt::OrFeltOp>(op)) {
1263 auto lhs = lookupScalar(orOp.getLhs(), valueMap, orOp.getOperation());
1264 auto rhs = lookupScalar(orOp.getRhs(), valueMap, orOp.getOperation());
1265 if (failed(lhs) || failed(rhs)) {
1270 LoweredValue {orOp.getType(), {lowerFeltOr(builder, loc, *lhs, *rhs, field)}}
1273 if (
auto xorOp = dyn_cast<felt::XorFeltOp>(op)) {
1274 auto lhs = lookupScalar(xorOp.getLhs(), valueMap, xorOp.getOperation());
1275 auto rhs = lookupScalar(xorOp.getRhs(), valueMap, xorOp.getOperation());
1276 if (failed(lhs) || failed(rhs)) {
1281 LoweredValue {xorOp.getType(), {lowerFeltXor(builder, loc, *lhs, *rhs, field)}}
1284 if (
auto subOp = dyn_cast<felt::SubFeltOp>(op)) {
1285 auto lhs = lookupScalar(subOp.getLhs(), valueMap, subOp.getOperation());
1286 auto rhs = lookupScalar(subOp.getRhs(), valueMap, subOp.getOperation());
1287 if (failed(lhs) || failed(rhs)) {
1292 LoweredValue {subOp.getType(), {lowerFeltSub(builder, loc, *lhs, *rhs, field)}}
1295 if (
auto mulOp = dyn_cast<felt::MulFeltOp>(op)) {
1296 auto lhs = lookupScalar(mulOp.getLhs(), valueMap, mulOp.getOperation());
1297 auto rhs = lookupScalar(mulOp.getRhs(), valueMap, mulOp.getOperation());
1298 if (failed(lhs) || failed(rhs)) {
1303 LoweredValue {mulOp.getType(), {lowerFeltMul(builder, loc, *lhs, *rhs, field)}}
1306 if (
auto negOp = dyn_cast<felt::NegFeltOp>(op)) {
1307 auto operand = lookupScalar(negOp.getOperand(), valueMap, negOp.getOperation());
1308 if (failed(operand)) {
1313 LoweredValue {negOp.getType(), {lowerFeltNeg(builder, loc, *operand, field)}}
1316 if (
auto invOp = dyn_cast<felt::InvFeltOp>(op)) {
1317 auto operand = lookupScalar(invOp.getOperand(), valueMap, invOp.getOperation());
1318 if (failed(operand)) {
1323 LoweredValue {invOp.getType(), {lowerFeltInv(builder, loc, *operand, field)}}
1326 if (
auto divOp = dyn_cast<felt::DivFeltOp>(op)) {
1327 auto lhs = lookupScalar(divOp.getLhs(), valueMap, divOp.getOperation());
1328 auto rhs = lookupScalar(divOp.getRhs(), valueMap, divOp.getOperation());
1329 if (failed(lhs) || failed(rhs)) {
1334 LoweredValue {divOp.getType(), {lowerFeltDiv(builder, loc, *lhs, *rhs, field)}}
1337 if (
auto uintDivOp = dyn_cast<felt::UnsignedIntDivFeltOp>(op)) {
1338 auto lhs = lookupScalar(uintDivOp.getLhs(), valueMap, uintDivOp.getOperation());
1339 auto rhs = lookupScalar(uintDivOp.getRhs(), valueMap, uintDivOp.getOperation());
1340 if (failed(lhs) || failed(rhs)) {
1343 assertNonZeroFelt(builder, loc, *rhs,
"felt.uintdiv divisor must be non-zero");
1345 uintDivOp.getResult(),
1346 LoweredValue {uintDivOp.getType(), {lowerFeltUnsignedDiv(builder, loc, *lhs, *rhs)}}
1349 if (
auto sintDivOp = dyn_cast<felt::SignedIntDivFeltOp>(op)) {
1350 auto lhs = lookupScalar(sintDivOp.getLhs(), valueMap, sintDivOp.getOperation());
1351 auto rhs = lookupScalar(sintDivOp.getRhs(), valueMap, sintDivOp.getOperation());
1352 if (failed(lhs) || failed(rhs)) {
1355 assertNonZeroFelt(builder, loc, *rhs,
"felt.sintdiv divisor must be non-zero");
1357 sintDivOp.getResult(),
1358 LoweredValue {sintDivOp.getType(), {lowerFeltSignedDiv(builder, loc, *lhs, *rhs, field)}}
1361 if (
auto umodOp = dyn_cast<felt::UnsignedModFeltOp>(op)) {
1362 auto lhs = lookupScalar(umodOp.getLhs(), valueMap, umodOp.getOperation());
1363 auto rhs = lookupScalar(umodOp.getRhs(), valueMap, umodOp.getOperation());
1364 if (failed(lhs) || failed(rhs)) {
1367 assertNonZeroFelt(builder, loc, *rhs,
"felt.umod divisor must be non-zero");
1370 LoweredValue {umodOp.getType(), {lowerFeltUnsignedMod(builder, loc, *lhs, *rhs)}}
1373 if (
auto smodOp = dyn_cast<felt::SignedModFeltOp>(op)) {
1374 auto lhs = lookupScalar(smodOp.getLhs(), valueMap, smodOp.getOperation());
1375 auto rhs = lookupScalar(smodOp.getRhs(), valueMap, smodOp.getOperation());
1376 if (failed(lhs) || failed(rhs)) {
1379 assertNonZeroFelt(builder, loc, *rhs,
"felt.smod divisor must be non-zero");
1382 LoweredValue {smodOp.getType(), {lowerFeltSignedMod(builder, loc, *lhs, *rhs, field)}}
1385 if (
auto shrOp = dyn_cast<felt::ShrFeltOp>(op)) {
1386 auto lhs = lookupScalar(shrOp.getLhs(), valueMap, shrOp.getOperation());
1387 auto rhs = lookupScalar(shrOp.getRhs(), valueMap, shrOp.getOperation());
1388 if (failed(lhs) || failed(rhs)) {
1393 LoweredValue {shrOp.getType(), {lowerFeltShr(builder, loc, *lhs, *rhs, field)}}
1396 if (
auto shlOp = dyn_cast<felt::ShlFeltOp>(op)) {
1397 auto lhs = lookupScalar(shlOp.getLhs(), valueMap, shlOp.getOperation());
1398 auto rhs = lookupScalar(shlOp.getRhs(), valueMap, shlOp.getOperation());
1399 if (failed(lhs) || failed(rhs)) {
1404 LoweredValue {shlOp.getType(), {lowerFeltShl(builder, loc, *lhs, *rhs, field)}}
1407 if (
auto notOp = dyn_cast<felt::NotFeltOp>(op)) {
1408 auto operand = lookupScalar(
notOp.getOperand(), valueMap,
notOp.getOperation());
1409 if (failed(operand)) {
1414 LoweredValue {notOp.getType(), {lowerFeltNot(builder, loc, *operand, field)}}
1418 if (
auto cmpOp = dyn_cast<boolean::CmpOp>(op)) {
1419 auto lhs = lookupScalar(cmpOp.getLhs(), valueMap, cmpOp.getOperation());
1420 auto rhs = lookupScalar(cmpOp.getRhs(), valueMap, cmpOp.getOperation());
1421 if (failed(lhs) || failed(rhs)) {
1424 auto lowered = lowerFeltCmp(builder, loc, cmpOp, *lhs, *rhs);
1425 if (failed(lowered)) {
1428 return bind(cmpOp.getResult(), LoweredValue {cmpOp.getType(), {*lowered}});
1430 if (
auto assertOp = dyn_cast<boolean::AssertOp>(op)) {
1431 auto condition = lookupScalar(assertOp.getCondition(), valueMap, assertOp.getOperation());
1432 if (failed(condition)) {
1435 cf::AssertOp::create(
1436 builder, loc, *condition,
1437 assertOp.getMsg() ? assertOp.getMsg()->str() :
"bool.assert failed"
1441 if (
auto andOp = dyn_cast<boolean::AndBoolOp>(op)) {
1442 auto lhs = lookupScalar(andOp.getLhs(), valueMap, andOp.getOperation());
1443 auto rhs = lookupScalar(andOp.getRhs(), valueMap, andOp.getOperation());
1444 if (failed(lhs) || failed(rhs)) {
1449 LoweredValue {andOp.getType(), {arith::AndIOp::create(builder, loc, *lhs, *rhs)}}
1452 if (
auto orOp = dyn_cast<boolean::OrBoolOp>(op)) {
1453 auto lhs = lookupScalar(orOp.getLhs(), valueMap, orOp.getOperation());
1454 auto rhs = lookupScalar(orOp.getRhs(), valueMap, orOp.getOperation());
1455 if (failed(lhs) || failed(rhs)) {
1460 LoweredValue {orOp.getType(), {arith::OrIOp::create(builder, loc, *lhs, *rhs)}}
1463 if (
auto xorOp = dyn_cast<boolean::XorBoolOp>(op)) {
1464 auto lhs = lookupScalar(xorOp.getLhs(), valueMap, xorOp.getOperation());
1465 auto rhs = lookupScalar(xorOp.getRhs(), valueMap, xorOp.getOperation());
1466 if (failed(lhs) || failed(rhs)) {
1471 LoweredValue {xorOp.getType(), {arith::XOrIOp::create(builder, loc, *lhs, *rhs)}}
1474 if (
auto notOp = dyn_cast<boolean::NotBoolOp>(op)) {
1475 auto operand = lookupScalar(
notOp.getOperand(), valueMap,
notOp.getOperation());
1476 if (failed(operand)) {
1479 Value one = arith::ConstantOp::create(
1480 builder, loc, IntegerAttr::get(IntegerType::get(builder.getContext(), 1), 1)
1484 LoweredValue {notOp.getType(), {arith::XOrIOp::create(builder, loc, *operand, one)}}
1488 if (
auto intToFelt = dyn_cast<cast::IntToFeltOp>(op)) {
1489 if (intToFelt.getOverflow() != cast::OverflowSemantics::ASSERT) {
1490 return intToFelt.emitError(
1491 "witgen lowering does not currently support non-assert cast overflow semantics"
1494 auto operand = lookupScalar(intToFelt.getValue(), valueMap, intToFelt.getOperation());
1495 if (failed(operand)) {
1498 const Field &resultField = intToFelt.getType().getField();
1499 auto dstType = IntegerType::get(builder.getContext(), resultField.bitWidth());
1501 if (isa<IndexType>((*operand).getType())) {
1502 Value zero = makeIndexConstant(builder, loc, 0);
1503 Value isNonNegative =
1504 arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::sge, *operand, zero);
1505 cf::AssertOp::create(builder, loc, isNonNegative,
"cast.tofelt input must be non-negative");
1509 unsigned wideWidth = std::max(resultField.bitWidth(), IndexType::kInternalStorageBitWidth);
1510 Value widened = arith::IndexCastUIOp::create(
1511 builder, loc, IntegerType::get(builder.getContext(), wideWidth), *operand
1513 Value modulus = arith::ConstantOp::create(
1514 builder, loc, resultField.getPrimeAttr(builder.getContext(), wideWidth)
1517 arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::ult, widened, modulus);
1518 cf::AssertOp::create(builder, loc, fitsField,
"cast.tofelt input does not fit in felt");
1520 lowered = wideWidth == dstType.getWidth()
1522 : arith::TruncIOp::create(builder, loc, dstType, widened).getResult();
1526 lowered = arith::ExtUIOp::create(builder, loc, dstType, *operand);
1528 return bind(intToFelt.getResult(), LoweredValue {intToFelt.getType(), {lowered}});
1530 if (
auto feltToIndex = dyn_cast<cast::FeltToIndexOp>(op)) {
1531 if (feltToIndex.getOverflow() != cast::OverflowSemantics::ASSERT) {
1532 return feltToIndex.emitError(
1533 "witgen lowering does not currently support non-assert cast overflow semantics"
1536 auto operand = lookupScalar(feltToIndex.getValue(), valueMap, feltToIndex.getOperation());
1537 if (failed(operand)) {
1540 unsigned wideWidth = std::max(field.bitWidth(), IndexType::kInternalStorageBitWidth);
1541 auto wideType = IntegerType::get(builder.getContext(), wideWidth);
1542 Value widened = *operand;
1543 if (field.bitWidth() < wideWidth) {
1544 widened = arith::ExtUIOp::create(builder, loc, wideType, widened);
1546 Value maxIndex = arith::ConstantOp::create(
1547 builder, loc, IntegerAttr::get(wideType, std::numeric_limits<int64_t>::max())
1550 arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::ule, widened, maxIndex);
1551 cf::AssertOp::create(builder, loc, fitsIndex,
"cast.toindex input does not fit in index");
1553 feltToIndex.getResult(),
1555 feltToIndex.getType(),
1556 {arith::IndexCastUIOp::create(builder, loc, builder.getIndexType(), *operand)}
1561 if (
auto structNewOp = dyn_cast<component::CreateStructOp>(op)) {
1562 auto lowered = createDefaultValue(
1563 builder, loc, structNewOp.getType(), tables, structNewOp.getOperation(), field,
1564 uninitializedBehavior, rng
1566 if (failed(lowered)) {
1569 return bind(structNewOp.getResult(), std::move(*lowered));
1571 if (
auto readMemberOp = dyn_cast<component::MemberReadOp>(op)) {
1572 auto componentValue =
1573 lookup(readMemberOp.getComponent(), valueMap, readMemberOp.getOperation());
1574 if (failed(componentValue)) {
1577 auto lowered = readNamedAggregateValue(
1578 builder, loc, readMemberOp.getComponent().getType(), readMemberOp.getMemberName(),
1579 *componentValue, tables, readMemberOp.getOperation(), field
1581 if (failed(lowered)) {
1584 return bind(readMemberOp.getResult(), std::move(*lowered));
1586 if (
auto writeMemberOp = dyn_cast<component::MemberWriteOp>(op)) {
1587 auto componentValue =
1588 lookup(writeMemberOp.getComponent(), valueMap, writeMemberOp.getOperation());
1589 auto memberValue = lookup(writeMemberOp.getVal(), valueMap, writeMemberOp.getOperation());
1590 if (failed(componentValue) || failed(memberValue)) {
1593 return writeNamedAggregateValue(
1594 builder, loc, writeMemberOp.getComponent().getType(), writeMemberOp.getMemberName(),
1595 valueMap[writeMemberOp.getComponent()], *memberValue, tables,
1596 writeMemberOp.getOperation(), field
1600 if (
auto newPodOp = dyn_cast<pod::NewPodOp>(op)) {
1601 auto lowered = createDefaultValue(
1602 builder, loc, newPodOp.getType(), tables, newPodOp.getOperation(), field,
1603 uninitializedBehavior, rng
1605 if (failed(lowered)) {
1608 for (pod::RecordValue init : newPodOp.getInitializedRecordValues()) {
1609 auto value = lookup(init.value, valueMap, newPodOp.getOperation());
1610 if (failed(value) || failed(writeNamedAggregateValue(
1611 builder, loc, newPodOp.getType(), init.name, *lowered, *value,
1612 tables, newPodOp.getOperation(), field
1617 return bind(newPodOp.getResult(), std::move(*lowered));
1619 if (
auto readPodOp = dyn_cast<pod::ReadPodOp>(op)) {
1620 auto podValue = lookup(readPodOp.getPodRef(), valueMap, readPodOp.getOperation());
1621 if (failed(podValue)) {
1624 auto lowered = readNamedAggregateValue(
1625 builder, loc, readPodOp.getPodRef().getType(), readPodOp.getRecordName(), *podValue,
1626 tables, readPodOp.getOperation(), field
1628 if (failed(lowered)) {
1631 return bind(readPodOp.getResult(), std::move(*lowered));
1633 if (
auto writePodOp = dyn_cast<pod::WritePodOp>(op)) {
1634 auto recordValue = lookup(writePodOp.getValue(), valueMap, writePodOp.getOperation());
1635 if (failed(recordValue)) {
1638 return writeNamedAggregateValue(
1639 builder, loc, writePodOp.getPodRef().getType(), writePodOp.getRecordName(),
1640 valueMap[writePodOp.getPodRef()], *recordValue, tables, writePodOp.getOperation(), field
1644 if (
auto arrayNewOp = dyn_cast<array::CreateArrayOp>(op)) {
1645 auto lowered = createDefaultValue(
1646 builder, loc, arrayNewOp.getType(), tables, arrayNewOp.getOperation(), field,
1647 uninitializedBehavior, rng
1649 if (failed(lowered)) {
1652 if (!arrayNewOp.getElements().empty()) {
1653 auto elementCount = checkedCast<size_t>(arrayNewOp.getType().getNumElements());
1654 if (!elementCount) {
1655 return arrayNewOp.emitError(llvm::toString(elementCount.takeError()));
1657 if (arrayNewOp.getElements().size() != *elementCount) {
1658 return arrayNewOp.emitError(
1659 "expected one explicit element per array slot in witgen lowering"
1662 auto shape = arrayNewOp.getType().getShape();
1663 for (
auto [flatIndex, operand] : llvm::enumerate(arrayNewOp.getElements())) {
1664 auto elementValue = lookup(operand, valueMap, arrayNewOp.getOperation());
1665 if (failed(elementValue)) {
1668 SmallVector<Value> indices;
1669 auto strides = mlir::computeStrides(shape);
1670 auto flatSigned = checkedCast<int64_t>(flatIndex);
1672 return arrayNewOp.emitError(llvm::toString(flatSigned.takeError()));
1674 for (int64_t index : mlir::delinearize(*flatSigned, strides)) {
1675 indices.push_back(makeIndexConstant(builder, loc, index));
1677 if (failed(writeArrayElement(
1678 builder, loc, arrayNewOp.getType(), *lowered, indices, *elementValue
1684 return bind(arrayNewOp.getResult(), std::move(*lowered));
1686 if (
auto readArrayOp = dyn_cast<array::ReadArrayOp>(op)) {
1687 SmallVector<Value> indices;
1688 for (Value indexValue : readArrayOp.getIndices()) {
1689 auto loweredIndex = lookupScalar(indexValue, valueMap, readArrayOp.getOperation());
1690 if (failed(loweredIndex)) {
1693 indices.push_back(*loweredIndex);
1695 auto arrayValue = lookup(readArrayOp.getArrRef(), valueMap, readArrayOp.getOperation());
1696 if (failed(arrayValue)) {
1699 auto lowered = readArrayElement(
1700 builder, loc, llvm::cast<array::ArrayType>(readArrayOp.getArrRef().getType()),
1701 *arrayValue, indices
1703 if (failed(lowered)) {
1706 return bind(readArrayOp.getResult(), std::move(*lowered));
1708 if (
auto writeArrayOp = dyn_cast<array::WriteArrayOp>(op)) {
1709 SmallVector<Value> indices;
1710 for (Value indexValue : writeArrayOp.getIndices()) {
1711 auto loweredIndex = lookupScalar(indexValue, valueMap, writeArrayOp.getOperation());
1712 if (failed(loweredIndex)) {
1715 indices.push_back(*loweredIndex);
1717 auto elementValue = lookup(writeArrayOp.getRvalue(), valueMap, writeArrayOp.getOperation());
1718 if (failed(elementValue)) {
1721 return writeArrayElement(
1722 builder, loc, llvm::cast<array::ArrayType>(writeArrayOp.getArrRef().getType()),
1723 valueMap[writeArrayOp.getArrRef()], indices, *elementValue
1727 if (
auto cmpiOp = dyn_cast<arith::CmpIOp>(op)) {
1728 auto lhs = lookupScalar(cmpiOp.getLhs(), valueMap, cmpiOp.getOperation());
1729 auto rhs = lookupScalar(cmpiOp.getRhs(), valueMap, cmpiOp.getOperation());
1730 if (failed(lhs) || failed(rhs)) {
1737 {arith::CmpIOp::create(builder, loc, cmpiOp.getPredicate(), *lhs, *rhs)}
1741 if (
auto selectOp = dyn_cast<arith::SelectOp>(op)) {
1742 auto cond = lookupScalar(selectOp.getCondition(), valueMap, selectOp.getOperation());
1743 auto trueValue = lookupScalar(selectOp.getTrueValue(), valueMap, selectOp.getOperation());
1744 auto falseValue = lookupScalar(selectOp.getFalseValue(), valueMap, selectOp.getOperation());
1745 if (failed(cond) || failed(trueValue) || failed(falseValue)) {
1749 selectOp.getResult(),
1752 {arith::SelectOp::create(builder, loc, *cond, *trueValue, *falseValue)}
1756 if (
auto addiOp = dyn_cast<arith::AddIOp>(op)) {
1757 auto lhs = lookupScalar(addiOp.getLhs(), valueMap, addiOp.getOperation());
1758 auto rhs = lookupScalar(addiOp.getRhs(), valueMap, addiOp.getOperation());
1759 if (failed(lhs) || failed(rhs)) {
1764 LoweredValue {addiOp.getType(), {arith::AddIOp::create(builder, loc, *lhs, *rhs)}}
1767 if (
auto subiOp = dyn_cast<arith::SubIOp>(op)) {
1768 auto lhs = lookupScalar(subiOp.getLhs(), valueMap, subiOp.getOperation());
1769 auto rhs = lookupScalar(subiOp.getRhs(), valueMap, subiOp.getOperation());
1770 if (failed(lhs) || failed(rhs)) {
1775 LoweredValue {subiOp.getType(), {arith::SubIOp::create(builder, loc, *lhs, *rhs)}}
1779 if (
auto callOp = dyn_cast<function::CallOp>(op)) {
1780 if (callOp.getTemplateParams() || !callOp.getMapOperands().empty()) {
1781 return callOp.emitError(
1782 "execution-engine backend encountered an unflattened function.call"
1785 auto *callable = callOp.resolveCallableInTable(&tables);
1786 auto callee = dyn_cast_or_null<function::FuncDefOp>(callable);
1788 return callOp.emitError(
"failed to resolve callee during execution-engine lowering");
1790 SmallVector<Type> resultTypes;
1791 for (Type resultType : callOp.getResultTypes()) {
1793 flattenABILeafTypes(resultType, tables, callOp.getOperation(), field, resultTypes)
1798 SmallVector<Value> flatArgs;
1799 for (Value operand : callOp.getArgOperands()) {
1800 auto lowered = lookup(operand, valueMap, callOp.getOperation());
1801 auto leafTypes = getABILeafTypes(operand.getType(), tables, callOp.getOperation(), field);
1802 if (failed(lowered) || failed(leafTypes) ||
1803 failed(appendFlatLeavesToTypes(
1804 builder, loc, *lowered, *leafTypes, flatArgs, callOp.getOperation()
1810 func::CallOp::create(builder, loc, mangleFunctionName(callee), resultTypes, flatArgs);
1811 auto loweredCallResults = loweredCall.getResults();
1812 size_t totalResults = loweredCallResults.size();
1814 for (
auto [oldResult, oldType] : llvm::zip(callOp.getResults(), callOp.getResultTypes())) {
1815 auto leafCount = getLeafCount(oldType, tables, callOp.getOperation(), field);
1816 if (failed(leafCount)) {
1819 bool overflow =
false;
1820 size_t nextCursor = llvm::SaturatingAdd(cursor, *leafCount, &overflow);
1821 if (overflow || nextCursor > totalResults) {
1822 return callOp.emitError(
"leaf count overflow while lowering function call results");
1824 LoweredValue lowered {oldType, {}};
1825 lowered.leaves.append(
1826 loweredCallResults.begin() +
static_cast<ptrdiff_t
>(cursor),
1827 loweredCallResults.begin() +
static_cast<ptrdiff_t
>(nextCursor)
1829 valueMap[oldResult] = std::move(lowered);
1830 cursor = nextCursor;
1835 if (
auto whileOp = dyn_cast<scf::WhileOp>(op)) {
1836 SmallVector<Value> initArgs;
1837 SmallVector<size_t> beforeLeafCounts;
1838 for (
auto [init, initType] : llvm::zip(whileOp.getInits(), whileOp.getOperandTypes())) {
1839 auto lowered = lookup(init, valueMap, whileOp.getOperation());
1840 auto leafTypes = getABILeafTypes(initType, tables, whileOp.getOperation(), field);
1841 if (failed(lowered) || failed(leafTypes) ||
1842 failed(appendFlatLeavesToTypes(
1843 builder, loc, *lowered, *leafTypes, initArgs, whileOp.getOperation()
1847 auto count = getLeafCount(initType, tables, whileOp.getOperation(), field);
1848 if (failed(count)) {
1851 beforeLeafCounts.push_back(*count);
1854 SmallVector<size_t> resultLeafCounts;
1855 SmallVector<Type> loweredResultTypes;
1856 for (Type resultType : whileOp.getResultTypes()) {
1857 auto leafTypes = getABILeafTypes(resultType, tables, whileOp.getOperation(), field);
1858 auto count = getLeafCount(resultType, tables, whileOp.getOperation(), field);
1859 if (failed(leafTypes) || failed(count)) {
1862 loweredResultTypes.append(leafTypes->begin(), leafTypes->end());
1863 resultLeafCounts.push_back(*count);
1866 auto mapRegionArguments = [&](
auto oldArgs,
auto oldTypes,
auto leafCounts,
auto newArgs,
1867 StringRef overflowMessage,
1868 DenseMap<Value, LoweredValue> ®ionMap) -> LogicalResult {
1869 size_t totalArgs = newArgs.size();
1871 for (
auto [oldArg, oldType, leafCount] : llvm::zip(oldArgs, oldTypes, leafCounts)) {
1872 bool overflow =
false;
1873 size_t nextCursor = llvm::SaturatingAdd(cursor, leafCount, &overflow);
1874 if (overflow || nextCursor > totalArgs) {
1875 return whileOp.emitError(overflowMessage);
1877 LoweredValue lowered {oldType, {}};
1878 lowered.leaves.append(
1882 regionMap[oldArg] = std::move(lowered);
1883 cursor = nextCursor;
1888 LogicalResult whileLoweringStatus = success();
1889 auto newWhile = scf::WhileOp::create(
1890 builder, loc, loweredResultTypes, initArgs,
1891 [&](OpBuilder ®ionBuilder, Location , ValueRange beforeArgs) {
1892 DenseMap<Value, LoweredValue> beforeMap(valueMap.begin(), valueMap.end());
1893 if (failed(mapRegionArguments(
1894 whileOp.getBeforeArguments(), whileOp.getOperandTypes(), beforeLeafCounts,
1895 beforeArgs,
"leaf count overflow while lowering while-loop before-region args",
1898 failed(lowerBlock(regionBuilder, whileOp.getBefore().front(), beforeMap))) {
1899 whileLoweringStatus = failure();
1901 }, [&](OpBuilder ®ionBuilder, Location , ValueRange afterArgs) {
1902 DenseMap<Value, LoweredValue> afterMap(valueMap.begin(), valueMap.end());
1903 if (failed(mapRegionArguments(
1904 whileOp.getAfterArguments(), whileOp.getResultTypes(), resultLeafCounts, afterArgs,
1905 "leaf count overflow while lowering while-loop after-region args", afterMap
1907 failed(lowerBlock(regionBuilder, whileOp.getAfter().front(), afterMap))) {
1908 whileLoweringStatus = failure();
1912 if (failed(whileLoweringStatus)) {
1917 auto newWhileResults = newWhile.getResults();
1918 size_t totalResults = newWhileResults.size();
1920 for (
auto [oldResult, oldType, leafCount] :
1921 llvm::zip(whileOp.getResults(), whileOp.getResultTypes(), resultLeafCounts)) {
1922 bool overflow =
false;
1923 size_t nextCursor = llvm::SaturatingAdd(cursor, leafCount, &overflow);
1924 if (overflow || nextCursor > totalResults) {
1925 return whileOp.emitError(
"leaf count overflow while lowering while-loop results");
1927 LoweredValue lowered {oldType, {}};
1928 lowered.leaves.append(
1932 valueMap[oldResult] = std::move(lowered);
1933 cursor = nextCursor;
1938 if (
auto ifOp = dyn_cast<scf::IfOp>(op)) {
1939 auto condition = lookupScalar(ifOp.getCondition(), valueMap, ifOp.getOperation());
1940 if (failed(condition)) {
1944 SmallVector<size_t> resultLeafCounts;
1945 SmallVector<Type> loweredResultTypes;
1946 for (Type resultType : ifOp.getResultTypes()) {
1947 auto leafTypes = getABILeafTypes(resultType, tables, ifOp.getOperation(), field);
1948 auto count = getLeafCount(resultType, tables, ifOp.getOperation(), field);
1949 if (failed(leafTypes) || failed(count)) {
1952 loweredResultTypes.append(leafTypes->begin(), leafTypes->end());
1953 resultLeafCounts.push_back(*count);
1956 auto newIf = scf::IfOp::create(
1957 builder, loc, loweredResultTypes, *condition,
true, !ifOp.getElseRegion().empty()
1961 OpBuilder thenBuilder = OpBuilder::atBlockBegin(&newIf.getThenRegion().front());
1962 DenseMap<Value, LoweredValue> thenMap(valueMap.begin(), valueMap.end());
1963 if (failed(lowerBlock(thenBuilder, ifOp.getThenRegion().front(), thenMap))) {
1968 if (!ifOp.getElseRegion().empty()) {
1969 OpBuilder elseBuilder = OpBuilder::atBlockBegin(&newIf.getElseRegion().front());
1970 DenseMap<Value, LoweredValue> elseMap(valueMap.begin(), valueMap.end());
1971 if (failed(lowerBlock(elseBuilder, ifOp.getElseRegion().front(), elseMap))) {
1976 auto newIfResults = newIf.getResults();
1977 size_t totalResults = newIfResults.size();
1979 for (
auto [oldResult, oldType, leafCount] :
1980 llvm::zip(ifOp.getResults(), ifOp.getResultTypes(), resultLeafCounts)) {
1981 bool overflow =
false;
1982 size_t nextCursor = llvm::SaturatingAdd(cursor, leafCount, &overflow);
1983 if (overflow || nextCursor > totalResults) {
1984 return ifOp.emitError(
"leaf count overflow while lowering if-op results");
1986 LoweredValue lowered {oldType, {}};
1987 lowered.leaves.append(
1991 valueMap[oldResult] = std::move(lowered);
1992 cursor = nextCursor;
1997 if (
auto forOp = dyn_cast<scf::ForOp>(op)) {
1998 auto lb = lookupScalar(forOp.getLowerBound(), valueMap, forOp.getOperation());
1999 auto ub = lookupScalar(forOp.getUpperBound(), valueMap, forOp.getOperation());
2000 auto step = lookupScalar(forOp.getStep(), valueMap, forOp.getOperation());
2001 if (failed(lb) || failed(ub) || failed(step)) {
2005 SmallVector<Value> initArgs;
2006 SmallVector<size_t> initLeafCounts;
2007 for (
auto [init, resultType] : llvm::zip(forOp.getInitArgs(), forOp.getResultTypes())) {
2008 auto lowered = lookup(init, valueMap, forOp.getOperation());
2009 auto leafTypes = getABILeafTypes(resultType, tables, forOp.getOperation(), field);
2010 if (failed(lowered) || failed(leafTypes) ||
2011 failed(appendFlatLeavesToTypes(
2012 builder, loc, *lowered, *leafTypes, initArgs, forOp.getOperation()
2016 auto count = getLeafCount(resultType, tables, forOp.getOperation(), field);
2017 if (failed(count)) {
2020 initLeafCounts.push_back(*count);
2023 auto newFor = scf::ForOp::create(builder, loc, *lb, *ub, *step, initArgs);
2024 if (Attribute unsignedCmpAttr = forOp->getAttr(
"unsignedCmp")) {
2025 newFor->setAttr(
"unsignedCmp", unsignedCmpAttr);
2027 DenseMap<Value, LoweredValue> bodyMap(valueMap.begin(), valueMap.end());
2028 bodyMap[forOp.getInductionVar()] =
2029 LoweredValue {forOp.getInductionVar().getType(), {newFor.getInductionVar()}};
2031 auto newForIterArgs = newFor.getRegionIterArgs();
2032 size_t totalIterArgs = newForIterArgs.size();
2034 for (
auto [oldIterArg, oldType, leafCount] :
2035 llvm::zip(forOp.getRegionIterArgs(), forOp.getResultTypes(), initLeafCounts)) {
2036 bool overflow =
false;
2037 size_t nextCursor = llvm::SaturatingAdd(cursor, leafCount, &overflow);
2038 if (overflow || nextCursor > totalIterArgs) {
2039 return forOp.emitError(
"leaf count overflow while lowering for-loop region iter args");
2041 LoweredValue lowered {oldType, {}};
2042 lowered.leaves.append(
2043 newForIterArgs.begin() +
static_cast<ptrdiff_t
>(cursor),
2044 newForIterArgs.begin() +
static_cast<ptrdiff_t
>(nextCursor)
2046 bodyMap[oldIterArg] = std::move(lowered);
2047 cursor = nextCursor;
2051 newFor.getBody()->clear();
2052 OpBuilder bodyBuilder = OpBuilder::atBlockBegin(newFor.getBody());
2053 if (failed(lowerBlock(bodyBuilder, *forOp.getBody(), bodyMap))) {
2058 auto newForResults = newFor.getResults();
2059 size_t totalForResults = newForResults.size();
2061 for (
auto [oldResult, oldType, leafCount] :
2062 llvm::zip(forOp.getResults(), forOp.getResultTypes(), initLeafCounts)) {
2063 bool overflow =
false;
2064 size_t nextCursor = llvm::SaturatingAdd(cursor, leafCount, &overflow);
2065 if (overflow || nextCursor > totalForResults) {
2066 return forOp.emitError(
"leaf count overflow while lowering for-loop results");
2068 LoweredValue lowered {oldType, {}};
2069 lowered.leaves.append(
2070 newForResults.begin() +
static_cast<ptrdiff_t
>(cursor),
2071 newForResults.begin() +
static_cast<ptrdiff_t
>(nextCursor)
2073 valueMap[oldResult] = std::move(lowered);
2074 cursor = nextCursor;
2080 return op.emitError(
"unsupported operation in execution-engine lowering: ") << op.getName();
2085class LowerComputeToCorePass :
public PassWrapper<LowerComputeToCorePass, OperationPass<ModuleOp>> {
2087 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LowerComputeToCorePass)
2089 explicit LowerComputeToCorePass(
const WitgenOptions &opts) : options(opts) {}
2092 StringRef getArgument() const final {
return "llzk-lower-compute-to-core"; }
2095 StringRef getDescription() const final {
2096 return "Lower LLZK compute IR to func/arith/cf/scf/memref";
2100 StringRef getName()
const override {
return "LowerComputeToCorePass"; }
2103 void runOnOperation()
override {
2104 ModuleOp moduleOp = getOperation();
2105 auto field = getModuleField(moduleOp);
2106 if (failed(field)) {
2107 signalPassFailure();
2111 SymbolTableCollection tables;
2112 BodyLowerer lowerer(moduleOp, tables, field->get(), options);
2113 auto funcs = walkCollect<function::FuncDefOp>(moduleOp, [](
auto funcOp) {
2114 return !funcOp.nameIsConstrain();
2116 for (function::FuncDefOp funcOp : funcs) {
2117 if (failed(lowerer.lowerFunction(funcOp))) {
2118 signalPassFailure();
2125 WitgenOptions options;
2129class CreateWitgenEntryPass :
public PassWrapper<CreateWitgenEntryPass, OperationPass<ModuleOp>> {
2131 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CreateWitgenEntryPass)
2134 explicit CreateWitgenEntryPass(OutputScope newOutputScope) : outputScope(newOutputScope) {}
2137 StringRef getArgument() const final {
return "llzk-create-witgen-entry"; }
2140 StringRef getDescription() const final {
2141 return "Create the llzk-witgen execution-engine entry wrapper";
2145 StringRef getName()
const override {
return "CreateWitgenEntryPass"; }
2148 void runOnOperation()
override {
2149 ModuleOp moduleOp = getOperation();
2150 auto field = getModuleField(moduleOp);
2151 if (failed(field)) {
2152 signalPassFailure();
2156 SymbolTableCollection tables;
2158 if (failed(mainDef) || !mainDef.value()) {
2159 moduleOp.emitError(
"module is missing a concrete llzk.main struct");
2160 signalPassFailure();
2163 function::FuncDefOp computeFunc = mainDef->get().getComputeFuncOp();
2165 moduleOp.emitError(
"main struct is missing @compute");
2166 signalPassFailure();
2172 if (failed(outputs)) {
2173 signalPassFailure();
2177 OpBuilder builder(moduleOp.getContext());
2178 builder.setInsertionPointToEnd(moduleOp.getBody());
2180 SmallVector<Type> wrapperArgs;
2181 for (Type argType : computeFunc.getArgumentTypes()) {
2182 SmallVector<Type> loweredLeafTypes;
2183 if (failed(flattenTypeLeaves(
2184 argType, tables, computeFunc.getOperation(), field->get(), loweredLeafTypes, {},
true
2186 signalPassFailure();
2189 if (loweredLeafTypes.size() != 1 || !isa<MemRefType>(loweredLeafTypes.front())) {
2190 computeFunc.emitError(
2191 "execution-engine wrapper only supports felt and array<...xfelt> inputs"
2193 signalPassFailure();
2196 wrapperArgs.push_back(loweredLeafTypes.front());
2198 for (
const OutputBinding &output : *outputs) {
2199 SmallVector<Type> loweredLeafTypes;
2200 if (failed(flattenTypeLeaves(
2201 output.type, tables, computeFunc.getOperation(), field->get(), loweredLeafTypes, {},
2204 signalPassFailure();
2207 if (loweredLeafTypes.size() != 1 || !isa<MemRefType>(loweredLeafTypes.front())) {
2208 computeFunc.emitError(
2209 "execution-engine wrapper only supports felt and array<...xfelt> outputs"
2211 signalPassFailure();
2214 wrapperArgs.push_back(loweredLeafTypes.front());
2217 auto wrapper = func::FuncOp::create(
2218 builder, computeFunc.getLoc(),
"__llzk_witgen_main",
2219 builder.getFunctionType(wrapperArgs, TypeRange {})
2221 wrapper->setAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(), builder.getUnitAttr());
2222 Block *entry = wrapper.addEntryBlock();
2223 builder.setInsertionPointToStart(entry);
2225 SmallVector<Type> loweredMainResultTypes;
2226 for (Type resultType : computeFunc.getResultTypes()) {
2227 if (failed(flattenABILeafTypes(
2228 resultType, tables, computeFunc.getOperation(), field->get(), loweredMainResultTypes
2230 signalPassFailure();
2235 SmallVector<Value> mainArgs;
2236 for (
auto [argType, wrapperArg] : llvm::zip(
2237 computeFunc.getArgumentTypes(),
2238 entry->getArguments().take_front(computeFunc.getNumArguments())
2240 if (isScalarType(argType)) {
2241 mainArgs.push_back(loadStorageScalar(builder, computeFunc.getLoc(), wrapperArg));
2244 getABILeafTypes(argType, tables, computeFunc.getOperation(), field->get());
2245 if (failed(abiLeafTypes) || abiLeafTypes->size() != 1 ||
2246 !isa<MemRefType>(abiLeafTypes->front())) {
2247 computeFunc.emitError(
"failed to derive execution-engine ABI type for main input");
2248 signalPassFailure();
2251 if (wrapperArg.getType() == abiLeafTypes->front()) {
2252 mainArgs.push_back(wrapperArg);
2255 memref::CastOp::create(
2256 builder, computeFunc.getLoc(), abiLeafTypes->front(), wrapperArg
2262 auto loweredMain = func::CallOp::create(
2263 builder, computeFunc.getLoc(), mangleFunctionName(computeFunc), loweredMainResultTypes,
2267 LoweredValue mainResultValue {
2268 computeFunc.getResultTypes().front(),
2269 llvm::SmallVector<Value>(loweredMain.getResults().begin(), loweredMain.getResults().end())
2272 auto extractOutputSlice = [&](ArrayRef<std::string> path, Type currentType,
2273 ArrayRef<Value> leaves,
2274 auto &self) -> FailureOr<SmallVector<Value>> {
2276 return SmallVector<Value>(leaves.begin(), leaves.end());
2278 if (
auto structType = dyn_cast<component::StructType>(currentType)) {
2279 auto defLookup = structType.getDefinition(tables, computeFunc.getOperation());
2280 if (failed(defLookup)) {
2283 unsigned localCursor = 0;
2284 for (component::MemberDefOp member : defLookup->get().getMemberDefs()) {
2286 getLeafCount(member.getType(), tables, member.getOperation(), field->get());
2287 if (failed(leafCount)) {
2290 ArrayRef<Value> slice = ArrayRef<Value>(leaves).slice(localCursor, *leafCount);
2291 localCursor += *leafCount;
2292 if (member.getSymName() == path.front()) {
2293 return self(path.drop_front(), member.getType(), slice, self);
2296 return computeFunc.emitError(
"failed to find struct member while wiring witgen outputs");
2298 if (
auto podType = dyn_cast<pod::PodType>(currentType)) {
2299 unsigned localCursor = 0;
2300 for (pod::RecordAttr record : podType.getRecords()) {
2302 getLeafCount(record.getType(), tables, computeFunc.getOperation(), field->get());
2303 if (failed(leafCount)) {
2306 ArrayRef<Value> slice = ArrayRef<Value>(leaves).slice(localCursor, *leafCount);
2307 localCursor += *leafCount;
2308 if (record.getName().getValue() == path.front()) {
2309 return self(path.drop_front(), record.getType(), slice, self);
2312 return computeFunc.emitError(
"failed to find POD record while wiring witgen outputs");
2314 return computeFunc.emitError(
"extra witness path components for non-aggregate output");
2317 auto outputArgs = entry->getArguments().drop_front(computeFunc.getNumArguments());
2318 for (
auto [output, outputMemRef] : llvm::zip(*outputs, outputArgs)) {
2319 auto slice = extractOutputSlice(
2320 output.path, mainResultValue.sourceType, mainResultValue.leaves, extractOutputSlice
2322 if (failed(slice) || slice->empty()) {
2323 wrapper.emitError(
"missing selected witness output slice while building witgen entry");
2324 signalPassFailure();
2327 if (isScalarType(output.type)) {
2329 builder, computeFunc.getLoc(),
2330 loadStorageScalar(builder, computeFunc.getLoc(), slice->front()), outputMemRef
2333 memref::CopyOp::create(builder, computeFunc.getLoc(), slice->front(), outputMemRef);
2336 func::ReturnOp::create(builder, computeFunc.getLoc());
2339 moduleOp->removeAttr(MAIN_ATTR_NAME);
2341 SmallVector<Operation *> toErase;
2342 for (Operation &op : moduleOp.getBody()->getOperations()) {
2343 if (!isa<func::FuncOp>(op)) {
2344 toErase.push_back(&op);
2347 for (Operation *op : toErase) {
2363 pm.addPass(mlir::createLowerAffinePass());
2366 pm.addPass(mlir::createCanonicalizerPass());
2367 pm.addPass(mlir::createCSEPass());
2371 return std::make_unique<LowerComputeToCorePass>(options);
2375 return std::make_unique<CreateWitgenEntryPass>(outputScope);
This file implements helpers for DynamicAPInt operations and conversions that LLVM does not provide.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation source
std::unique_ptr<::mlir::Pass > createFlatteningPass()
llvm::Expected< T > checkedCast(U u)
std::mt19937_64 makeDefaultValueRng(const WitgenOptions &options)
Seed an RNG for random/default witness value materialization.
OutputScope
Select the JSON scope emitted by llzk-witgen.
FailureOr< llvm::SmallVector< OutputBinding > > collectOutputBindings(component::StructDefOp mainDef, SymbolTableCollection &tables, Operation *origin, OutputScope scope)
Collect the selected output bindings for the requested scope.
void addWitgenPreparePipeline(OpPassManager &pm, const WitgenOptions &)
UninitializedBehavior
Control how witgen materializes uninitialized/default values.
std::unique_ptr< Pass > createLowerComputeToCorePass(const WitgenOptions &options)
Create the pass that lowers supported LLZK compute IR into core MLIR dialects suitable for LLVM lower...
llvm::DynamicAPInt randomFieldElement(std::mt19937_64 &rng, const Field &field)
Draw a uniformly distributed field element in [0, prime).
bool randomBoolValue(std::mt19937_64 &rng)
Draw a uniformly distributed boolean value.
std::unique_ptr< Pass > createCreateWitgenEntryPass(OutputScope outputScope)
Create the pass that synthesizes the stable llzk-witgen JIT entry wrapper.
llvm::Expected< size_t > getStaticElementCount(ShapedType type, llvm::StringRef context)
int64_t randomIndexValue(std::mt19937_64 &rng)
Draw a uniformly distributed signed index value.
ExpressionValue mod(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
llvm::SmallVector< StringRef > getNames(SymbolRefAttr ref)
constexpr T checkedCast(U u) noexcept
FailureOr< SymbolLookupResult< StructDefOp > > getMainInstanceDef(SymbolTableCollection &symbolTable, Operation *lookupFrom)
llvm::SmallSet< FieldRef, 2 > FieldSet
Typealias for a set of Fields.
mlir::LogicalResult collectFields(mlir::Operation *root, FieldSet &fields, bool silent=true)
Collects all the fields used in a circuit.
Expected< APInt > checkedToAPInt(const DynamicAPInt &value, unsigned bitWidth, bool isSigned)
ExpressionValue notOp(const llvm::SMTSolverRef &solver, const ExpressionValue &val)
Configure one llzk-witgen execution.