LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
WitgenLowering.cpp
Go to the documentation of this file.
1//===-- LLZKWitgenLoweringPass.cpp -----------------------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
9
10#include "WitgenLowering.h"
11
12#include "WitgenDriver.h"
13#include "WitgenUtils.h"
14#include "WitnessSelection.h"
15
28#include "llzk/Util/Compare.h"
29#include "llzk/Util/Constants.h"
31#include "llzk/Util/Field.h"
33#include "llzk/Util/Walk.h"
34
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>
49
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>
56
57#include <algorithm>
58#include <limits>
59
60using namespace mlir;
61
62namespace llzk::witgen {
63namespace {
64
66struct LoweredValue {
67 Type sourceType;
68 llvm::SmallVector<Value> leaves;
69};
70
72static FailureOr<std::reference_wrapper<const Field>> getModuleField(ModuleOp moduleOp) {
73 FieldSet fields;
74 if (failed(collectFields(moduleOp.getOperation(), fields, false))) {
75 return moduleOp.emitError(
76 "llzk-witgen execution-engine lowering failed collecting 'felt.type' fields"
77 );
78 }
79 if (fields.size() != 1) {
80 return moduleOp.emitError("llzk-witgen execution-engine lowering requires exactly one field");
81 }
82 return *fields.begin();
83}
84
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() != '_') {
91 result += "__";
92 }
93 for (char c : piece) {
94 result += llvm::isAlnum(c) ? c : '_';
95 }
96 }
97 return std::string(result);
98}
99
101static Value makeIndexConstant(OpBuilder &builder, Location loc, int64_t value) {
102 return arith::ConstantIndexOp::create(builder, loc, value).getResult();
103}
104
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)
109 );
110}
111
113static FailureOr<Type> lowerScalarType(MLIRContext *context, Type type, const Field &field) {
114 if (isa<felt::FeltType>(type)) {
115 return IntegerType::get(context, field.bitWidth());
116 }
117 if (isa<IndexType>(type)) {
118 return type;
119 }
120 if (auto intType = dyn_cast<IntegerType>(type)) {
121 if (intType.getWidth() == 1) {
122 return intType;
123 }
124 }
125 return failure();
126}
127
129static bool isScalarType(Type type) {
130 return isa<felt::FeltType, IndexType>(type) ||
131 (isa<IntegerType>(type) && llvm::cast<IntegerType>(type).getWidth() == 1);
132}
133
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
138) {
139 auto emitScalarLeaf = [&](Type leafType) {
140 auto lowered = lowerScalarType(origin->getContext(), leafType, field);
141 if (failed(lowered)) {
142 return failure();
143 }
144 if (!storage && prefixShape.empty()) {
145 out.push_back(*lowered);
146 return success();
147 }
148 llvm::SmallVector<int64_t> shape(prefixShape.begin(), prefixShape.end());
149 if (shape.empty()) {
150 shape.push_back(1);
151 }
152 out.push_back(MemRefType::get(shape, *lowered));
153 return success();
154 };
155
156 if (isScalarType(type)) {
157 return emitScalarLeaf(type);
158 }
159
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
165 );
166 }
167
168 if (auto podType = dyn_cast<pod::PodType>(type)) {
169 for (pod::RecordAttr record : podType.getRecords()) {
170 if (failed(
171 flattenTypeLeaves(record.getType(), tables, origin, field, out, prefixShape, true)
172 )) {
173 return failure();
174 }
175 }
176 return success();
177 }
178
179 if (auto structType = dyn_cast<component::StructType>(type)) {
180 auto def = structType.getDefinition(tables, origin);
181 if (failed(def)) {
182 return origin->emitError("could not resolve struct type during witgen lowering");
183 }
184 for (component::MemberDefOp member : def->get().getMemberDefs()) {
185 if (failed(
186 flattenTypeLeaves(member.getType(), tables, origin, field, out, prefixShape, true)
187 )) {
188 return failure();
189 }
190 }
191 return success();
192 }
193
194 return origin->emitError("unsupported type in llzk-witgen lowering: ") << type;
195}
196
198static MemRefType
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)
203 );
204}
205
207static LogicalResult flattenABILeafTypes(
208 Type type, SymbolTableCollection &tables, Operation *origin, const Field &field,
209 SmallVectorImpl<Type> &out, size_t prefixRank = 0, bool aggregateStorage = false
210) {
211 auto emitScalarLeaf = [&](Type leafType) {
212 auto lowered = lowerScalarType(origin->getContext(), leafType, field);
213 if (failed(lowered)) {
214 return failure();
215 }
216 if (!aggregateStorage && prefixRank == 0) {
217 out.push_back(*lowered);
218 return success();
219 }
220 SmallVector<int64_t> shape;
221 if (prefixRank == 0) {
222 shape.push_back(1);
223 } else {
224 shape.assign(prefixRank, ShapedType::kDynamic);
225 }
226 out.push_back(getStridedMemRefType(origin->getContext(), shape, *lowered));
227 return success();
228 };
229
230 if (isScalarType(type)) {
231 return emitScalarLeaf(type);
232 }
233
234 if (auto arrayType = dyn_cast<array::ArrayType>(type)) {
235 return flattenABILeafTypes(
236 arrayType.getElementType(), tables, origin, field, out, prefixRank + arrayType.getRank(),
237 true
238 );
239 }
240
241 if (auto podType = dyn_cast<pod::PodType>(type)) {
242 for (pod::RecordAttr record : podType.getRecords()) {
243 if (failed(
244 flattenABILeafTypes(record.getType(), tables, origin, field, out, prefixRank, true)
245 )) {
246 return failure();
247 }
248 }
249 return success();
250 }
251
252 if (auto structType = dyn_cast<component::StructType>(type)) {
253 auto def = structType.getDefinition(tables, origin);
254 if (failed(def)) {
255 return origin->emitError("could not resolve struct type during witgen lowering");
256 }
257 for (component::MemberDefOp member : def->get().getMemberDefs()) {
258 if (failed(
259 flattenABILeafTypes(member.getType(), tables, origin, field, out, prefixRank, true)
260 )) {
261 return failure();
262 }
263 }
264 return success();
265 }
266
267 return origin->emitError("unsupported type in llzk-witgen lowering: ") << type;
268}
269
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))) {
275 return failure();
276 }
277 return leaves.size();
278}
279
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))) {
285 return failure();
286 }
287 return leaves;
288}
289
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))) {
295 return failure();
296 }
297 return leaves;
298}
299
301static FailureOr<std::pair<size_t, size_t>> getNamedLeafSpan(
302 Type ownerType, StringRef name, SymbolTableCollection &tables, Operation *origin,
303 const Field &field
304) {
305 if (auto podType = dyn_cast<pod::PodType>(ownerType)) {
306 size_t running = 0;
307 for (pod::RecordAttr record : podType.getRecords()) {
308 auto count = getLeafCount(record.getType(), tables, origin, field);
309 if (failed(count)) {
310 return failure();
311 }
312 if (record.getName().getValue() == name) {
313 return std::pair<size_t, size_t> {running, *count};
314 }
315 running += *count;
316 }
317 }
318
319 if (auto structType = dyn_cast<component::StructType>(ownerType)) {
320 auto def = structType.getDefinition(tables, origin);
321 if (failed(def)) {
322 return origin->emitError("could not resolve struct type during witgen lowering");
323 }
324 size_t running = 0;
325 for (component::MemberDefOp member : def->get().getMemberDefs()) {
326 auto count = getLeafCount(member.getType(), tables, origin, field);
327 if (failed(count)) {
328 return failure();
329 }
330 if (member.getSymName() == name) {
331 return std::pair<size_t, size_t> {running, *count};
332 }
333 running += *count;
334 }
335 }
336
337 return origin->emitError("could not resolve aggregate member/record @") << name;
338}
339
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();
347 }
348 }
349 }
350 if (auto structType = dyn_cast<component::StructType>(ownerType)) {
351 auto def = structType.getDefinition(tables, origin);
352 if (failed(def)) {
353 return origin->emitError("could not resolve struct type during witgen lowering");
354 }
355 for (component::MemberDefOp member : def->get().getMemberDefs()) {
356 if (member.getSymName() == name) {
357 return member.getType();
358 }
359 }
360 }
361 return origin->emitError("could not resolve aggregate member/record @") << name;
362}
363
365static FailureOr<Value> createZeroMemRef(OpBuilder &builder, Location loc, MemRefType memrefType) {
366 auto elementCount = getStaticElementCount(memrefType, "witgen zero memref");
367 if (!elementCount) {
368 return emitError(loc) << llvm::toString(elementCount.takeError());
369 }
370 Value alloc = memref::AllocOp::create(builder, loc, memrefType);
371 auto elementType = memrefType.getElementType();
372 Value zero;
373 if (isa<IndexType>(elementType)) {
374 zero = arith::ConstantIndexOp::create(builder, loc, 0);
375 } else {
376 zero = arith::ConstantOp::create(
377 builder, loc, IntegerAttr::get(llvm::cast<IntegerType>(elementType), 0)
378 );
379 }
380 auto strides = mlir::computeStrides(memrefType.getShape());
381 for (size_t flat = 0; flat < *elementCount; ++flat) {
382 auto flatSigned = checkedCast<int64_t>(flat);
383 if (!flatSigned) {
384 return emitError(loc) << llvm::toString(flatSigned.takeError());
385 }
386 SmallVector<Value> indices;
387 for (int64_t index : mlir::delinearize(*flatSigned, strides)) {
388 indices.push_back(makeIndexConstant(builder, loc, index));
389 }
390 memref::StoreOp::create(builder, loc, zero, alloc, indices);
391 }
392 return alloc;
393}
394
396static FailureOr<Value> createRandomMemRef(
397 OpBuilder &builder, Location loc, MemRefType memrefType, const Field &field,
398 std::mt19937_64 &rng
399) {
400 auto elementCount = getStaticElementCount(memrefType, "witgen random memref");
401 if (!elementCount) {
402 return emitError(loc) << llvm::toString(elementCount.takeError());
403 }
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) {
408 auto flatSigned = checkedCast<int64_t>(flat);
409 if (!flatSigned) {
410 return emitError(loc) << llvm::toString(flatSigned.takeError());
411 }
412 SmallVector<Value> indices;
413 for (int64_t index : mlir::delinearize(*flatSigned, strides)) {
414 indices.push_back(makeIndexConstant(builder, loc, index));
415 }
416 if (isa<IndexType>(elementType)) {
417 auto value = randomIndexValue(rng);
418 memref::StoreOp::create(
419 builder, loc, arith::ConstantIndexOp::create(builder, loc, value), alloc, indices
420 );
421 continue;
422 }
423 auto intType = llvm::cast<IntegerType>(elementType);
424 if (intType.getWidth() == 1) {
425 memref::StoreOp::create(
426 builder, loc,
427 arith::ConstantOp::create(
428 builder, loc, IntegerAttr::get(intType, APInt(1, randomBoolValue(rng)))
429 ),
430 alloc, indices
431 );
432 continue;
433 }
434 auto candidate = randomFieldElement(rng, field);
435 memref::StoreOp::create(
436 builder, loc,
437 arith::ConstantOp::create(
438 builder, loc,
439 IntegerAttr::get(
440 intType, cantFail(checkedToAPInt(candidate, intType.getWidth(), false))
441 )
442 ),
443 alloc, indices
444 );
445 }
446 return alloc;
447}
448
450static FailureOr<LoweredValue> createDefaultValue(
451 OpBuilder &builder, Location loc, Type type, SymbolTableCollection &tables, Operation *origin,
452 const Field &field, UninitializedBehavior behavior, std::mt19937_64 &rng
453) {
454 LoweredValue lowered {type, {}};
455 auto leafTypes = getLeafTypes(type, tables, origin, field);
456 if (failed(leafTypes)) {
457 return failure();
458 }
459 for (Type leafType : *leafTypes) {
460 if (behavior == UninitializedBehavior::Fail) {
461 return origin->emitError(
462 "fail-mode default materialization is unsupported in witgen lowering because it would "
463 "hide uninitialized reads"
464 );
465 }
466 if (behavior == UninitializedBehavior::Random) {
467 if (auto memrefType = dyn_cast<MemRefType>(leafType)) {
468 auto randomMemRef = createRandomMemRef(builder, loc, memrefType, field, rng);
469 if (failed(randomMemRef)) {
470 return failure();
471 }
472 lowered.leaves.push_back(*randomMemRef);
473 continue;
474 }
475 if (isa<IndexType>(leafType)) {
476 lowered.leaves.push_back(
477 arith::ConstantIndexOp::create(builder, loc, randomIndexValue(rng))
478 );
479 continue;
480 }
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)))
486 )
487 );
488 continue;
489 }
490 auto candidate = randomFieldElement(rng, field);
491 lowered.leaves.push_back(
492 arith::ConstantOp::create(
493 builder, loc,
494 IntegerAttr::get(
495 intType, cantFail(checkedToAPInt(candidate, intType.getWidth(), false))
496 )
497 )
498 );
499 continue;
500 }
501 if (auto memrefType = dyn_cast<MemRefType>(leafType)) {
502 auto zeroMemRef = createZeroMemRef(builder, loc, memrefType);
503 if (failed(zeroMemRef)) {
504 return failure();
505 }
506 lowered.leaves.push_back(*zeroMemRef);
507 continue;
508 }
509 if (isa<IndexType>(leafType)) {
510 lowered.leaves.push_back(arith::ConstantIndexOp::create(builder, loc, 0));
511 continue;
512 }
513 lowered.leaves.push_back(
514 arith::ConstantOp::create(
515 builder, loc, IntegerAttr::get(llvm::cast<IntegerType>(leafType), 0)
516 )
517 );
518 }
519 return lowered;
520}
521
523static Value normalizeWideValue(
524 OpBuilder &builder, Location loc, Value wideValue, unsigned dstWidth, const Field &field
525) {
526 auto wideType = llvm::cast<IntegerType>(wideValue.getType());
527 Value modulus = arith::ConstantOp::create(
528 builder, loc, field.getPrimeAttr(builder.getContext(), wideType.getWidth())
529 );
530 Value reduced = arith::RemUIOp::create(builder, loc, wideValue, modulus);
531 return arith::TruncIOp::create(
532 builder, loc, IntegerType::get(builder.getContext(), dstWidth), reduced
533 );
534}
535
537static Value normalizeSignedWideValue(
538 OpBuilder &builder, Location loc, Value wideValue, unsigned dstWidth, const Field &field
539) {
540 auto wideType = llvm::cast<IntegerType>(wideValue.getType());
541 Value modulus = arith::ConstantOp::create(
542 builder, loc, field.getPrimeAttr(builder.getContext(), wideType.getWidth())
543 );
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
551 );
552}
553
555static Value
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);
562 Value prime =
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)))
566 );
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);
570}
571
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);
578}
579
581static Value
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);
590}
591
593static Value
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);
600 Value modulus =
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);
605}
606
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);
613 Value modulus =
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);
617}
618
620static Value
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);
629}
630
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) {
637 if (exponent[bit]) {
638 result = lowerFeltMul(builder, loc, result, base, field);
639 }
640 if (bit + 1 < exponent.getBitWidth()) {
641 base = lowerFeltMul(builder, loc, base, base, field);
642 }
643 }
644 return result;
645}
646
648static Value
649lowerFeltDiv(OpBuilder &builder, Location loc, Value lhs, Value rhs, const Field &field) {
650 return lowerFeltMul(builder, loc, lhs, lowerFeltInv(builder, loc, rhs, field), field);
651}
652
654static Value
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))
664 );
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);
669 {
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);
674 }
675 {
676 OpBuilder::InsertionGuard guard(builder);
677 builder.setInsertionPointToStart(&ifOp.getElseRegion().front());
678 scf::YieldOp::create(builder, loc, result);
679 }
680 result = ifOp.getResult(0);
681 if (bit + 1 < field.bitWidth()) {
682 currentBase = lowerFeltMul(builder, loc, currentBase, currentBase, field);
683 }
684 }
685 return result;
686}
687
689static Value
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);
694}
695
697static Value
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);
704}
705
707static Value
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);
714}
715
717static Value lowerFeltUnsignedDiv(OpBuilder &builder, Location loc, Value lhs, Value rhs) {
718 return arith::DivUIOp::create(builder, loc, lhs, rhs);
719}
720
722static Value
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);
729}
730
732static Value lowerFeltUnsignedMod(OpBuilder &builder, Location loc, Value lhs, Value rhs) {
733 return arith::RemUIOp::create(builder, loc, lhs, rhs);
734}
735
737static Value
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);
744}
745
747static Value
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()))
752 );
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))
757 );
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);
761}
762
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))
770 );
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);
774}
775
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));
783 }
784 return memref::LoadOp::create(builder, loc, storageLeaf, indices);
785}
786
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));
794 }
795 memref::StoreOp::create(builder, loc, scalar, storageLeaf, indices);
796}
797
799static LogicalResult copyIntoStorage(
800 OpBuilder &builder, Location loc, Type sourceType, ArrayRef<Value> destLeaves,
801 ArrayRef<Value> sourceLeaves, SymbolTableCollection &tables, Operation *origin,
802 const Field &field
803) {
804 auto leafTypes = getLeafTypes(sourceType, tables, origin, field);
805 if (failed(leafTypes)) {
806 return failure();
807 }
808 if (destLeaves.size() != sourceLeaves.size() || destLeaves.size() != leafTypes->size()) {
809 return origin->emitError("flattened leaf mismatch while copying aggregate storage");
810 }
811 for (auto [leafType, destLeaf, srcLeaf] : llvm::zip(*leafTypes, destLeaves, sourceLeaves)) {
812 if (isa<MemRefType>(leafType)) {
813 memref::CopyOp::create(builder, loc, srcLeaf, destLeaf);
814 continue;
815 }
816 storeStorageScalar(builder, loc, srcLeaf, destLeaf);
817 }
818 return success();
819}
820
822static FailureOr<LoweredValue> readNamedAggregateValue(
823 OpBuilder &builder, Location loc, Type ownerType, StringRef name, const LoweredValue &owner,
824 SymbolTableCollection &tables, Operation *origin, const Field &field
825) {
826 auto subType = getNamedSubType(ownerType, name, tables, origin);
827 if (failed(subType)) {
828 return failure();
829 }
830 auto span = getNamedLeafSpan(ownerType, name, tables, origin, field);
831 if (failed(span)) {
832 return failure();
833 }
834 LoweredValue result {*subType, {}};
835 auto leafTypes = getLeafTypes(*subType, tables, origin, field);
836 if (failed(leafTypes)) {
837 return failure();
838 }
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);
843 } else {
844 result.leaves.push_back(loadStorageScalar(builder, loc, leafValue));
845 }
846 }
847 return result;
848}
849
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
854) {
855 auto subType = getNamedSubType(ownerType, name, tables, origin);
856 if (failed(subType)) {
857 return failure();
858 }
859 auto span = getNamedLeafSpan(ownerType, name, tables, origin, field);
860 if (failed(span)) {
861 return failure();
862 }
863 return copyIntoStorage(
864 builder, loc, *subType, ArrayRef<Value>(owner.leaves).slice(span->first, span->second),
865 value.leaves, tables, origin, field
866 );
867}
868
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;
876 auto indexedRank = checkedCast<int64_t>(outerIndices.size());
877 if (!indexedRank) {
878 return emitError(loc) << llvm::toString(indexedRank.takeError());
879 }
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);
885 }
886 for (int64_t dim = *indexedRank; dim < sourceType.getRank(); ++dim) {
887 mixedOffsets.push_back(builder.getIndexAttr(0));
888 }
889 for (int64_t dim = 0; dim < *indexedRank; ++dim) {
890 mixedSizes.push_back(builder.getIndexAttr(1));
891 }
892 for (int64_t dim = *indexedRank; dim < sourceType.getRank(); ++dim) {
893 mixedSizes.push_back(memref::getMixedSize(builder, loc, source, dim));
894 }
895 for (int64_t dim = 0; dim < sourceType.getRank(); ++dim) {
896 mixedStrides.push_back(builder.getIndexAttr(1));
897 }
898 SmallVector<int64_t> desiredShape;
899 auto reserveSize = checkedCast<size_t>(sourceType.getRank() - *indexedRank);
900 if (!reserveSize) {
901 return emitError(loc) << llvm::toString(reserveSize.takeError());
902 }
903 desiredShape.reserve(*reserveSize);
904 for (int64_t dim = *indexedRank; dim < sourceType.getRank(); ++dim) {
905 auto dimIndex = checkedCast<size_t>(dim);
906 if (!dimIndex) {
907 return emitError(loc) << llvm::toString(dimIndex.takeError());
908 }
909 if (auto attr = llvm::dyn_cast<Attribute>(mixedSizes[*dimIndex])) {
910 desiredShape.push_back(llvm::cast<IntegerAttr>(attr).getInt());
911 } else {
912 desiredShape.push_back(ShapedType::kDynamic);
913 }
914 }
915 if (desiredShape.empty()) {
916 desiredShape.push_back(1);
917 }
918 auto resultType = llvm::cast<MemRefType>(memref::SubViewOp::inferRankReducedResultType(
919 desiredShape, sourceType, mixedOffsets, mixedSizes, mixedStrides
920 ));
921 auto op = memref::SubViewOp::create(
922 builder, loc, resultType, source, mixedOffsets, mixedSizes, mixedStrides
923 );
924 return success(op.getResult());
925}
926
928static FailureOr<LoweredValue> readArrayElement(
929 OpBuilder &builder, Location loc, array::ArrayType arrayType, const LoweredValue &arrayValue,
930 ArrayRef<Value> indices
931) {
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)
937 );
938 return result;
939 }
940
941 for (Value sourceLeaf : arrayValue.leaves) {
942 auto subview = createElementSubview(builder, loc, sourceLeaf, indices);
943 if (failed(subview)) {
944 return failure();
945 }
946 result.leaves.push_back(*subview);
947 }
948 return result;
949}
950
952static LogicalResult writeArrayElement(
953 OpBuilder &builder, Location loc, array::ArrayType arrayType, LoweredValue &arrayValue,
954 ArrayRef<Value> indices, const LoweredValue &elementValue
955) {
956 Type elementType = arrayType.getElementType();
957 if (isScalarType(elementType)) {
958 memref::StoreOp::create(
959 builder, loc, elementValue.leaves.front(), arrayValue.leaves.front(), indices
960 );
961 return success();
962 }
963
964 for (auto [destLeaf, srcLeaf] : llvm::zip(arrayValue.leaves, elementValue.leaves)) {
965 auto subview = createElementSubview(builder, loc, destLeaf, indices);
966 if (failed(subview)) {
967 return failure();
968 }
969 memref::CopyOp::create(builder, loc, srcLeaf, *subview);
970 }
971 return success();
972}
973
975static LogicalResult appendFlatLeavesToTypes(
976 OpBuilder &builder, Location loc, const LoweredValue &value, ArrayRef<Type> targetLeafTypes,
977 SmallVectorImpl<Value> &out, Operation *origin
978) {
979 if (targetLeafTypes.size() != value.leaves.size()) {
980 return origin->emitError("flattened leaf mismatch during call lowering");
981 }
982 for (auto [leafValue, leafType] : llvm::zip(value.leaves, targetLeafTypes)) {
983 if (leafValue.getType() == leafType) {
984 out.push_back(leafValue);
985 continue;
986 }
987 if (isa<MemRefType>(leafValue.getType()) && isa<MemRefType>(leafType)) {
988 out.push_back(memref::CastOp::create(builder, loc, leafType, leafValue));
989 continue;
990 }
991 return origin->emitError("lowered leaf type mismatch during call lowering");
992 }
993 return success();
994}
995
997class BodyLowerer {
998public:
1000 BodyLowerer(
1001 ModuleOp mod, SymbolTableCollection &symbolTables, const Field &moduleField,
1002 const WitgenOptions &options
1003 )
1004 : moduleOp(mod), tables(symbolTables), field(moduleField),
1005 uninitializedBehavior(options.uninitializedBehavior), rng(makeDefaultValueRng(options)) {}
1006
1008 FailureOr<func::FuncOp> lowerFunction(function::FuncDefOp funcOp) {
1009 if (funcOp.isExternal()) {
1010 return funcOp.emitError("execution-engine backend does not lower extern functions");
1011 }
1012 if (!funcOp.getBody().hasOneBlock()) {
1013 return funcOp.emitError("execution-engine backend only supports single-block functions");
1014 }
1015
1016 SmallVector<Type> loweredArgTypes;
1017 for (Type argType : funcOp.getArgumentTypes()) {
1018 if (failed(
1019 flattenABILeafTypes(argType, tables, funcOp.getOperation(), field, loweredArgTypes)
1020 )) {
1021 return failure();
1022 }
1023 }
1024 SmallVector<Type> loweredResultTypes;
1025 for (Type resultType : funcOp.getResultTypes()) {
1026 if (failed(flattenABILeafTypes(
1027 resultType, tables, funcOp.getOperation(), field, loweredResultTypes
1028 ))) {
1029 return failure();
1030 }
1031 }
1032
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)
1038 );
1039 Block *entry = loweredFunc.addEntryBlock();
1040 OpBuilder builder(entry, entry->begin());
1041
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();
1049 return failure();
1050 }
1051 LoweredValue lowered {argType, {}};
1052 lowered.leaves.append(
1053 entry->getArguments().begin() + cursor,
1054 entry->getArguments().begin() + cursor + *leafCount
1055 );
1056 cursor += *leafCount;
1057 valueMap[arg] = std::move(lowered);
1058 }
1059
1060 if (failed(lowerBlock(builder, funcOp.getBody().front(), valueMap))) {
1061 loweredFunc.erase();
1062 return failure();
1063 }
1064 return loweredFunc;
1065 }
1066
1067private:
1068 ModuleOp moduleOp;
1069 SymbolTableCollection &tables;
1070 const Field &field;
1071 UninitializedBehavior uninitializedBehavior;
1072 std::mt19937_64 rng;
1073
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");
1080 }
1081 return it->second;
1082 }
1083
1085 FailureOr<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");
1091 }
1092 return lowered->leaves.front();
1093 }
1094
1096 LogicalResult
1097 lowerBlock(OpBuilder &builder, Block &block, DenseMap<Value, LoweredValue> &valueMap) {
1098 for (Operation &op : block) {
1099 if (failed(lowerOperation(builder, op, valueMap))) {
1100 return failure();
1101 }
1102 }
1103 return success();
1104 }
1105
1107 FailureOr<Value>
1108 lowerFeltCmp(OpBuilder &builder, Location loc, boolean::CmpOp cmpOp, Value lhs, Value rhs) {
1109 // Implementation note: uses an immediately-invoked lambda to avoid disagreement between the
1110 // `-Wmaybe-uninitialized` and `-Wcovered-switch-default` warnings.
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;
1125 }
1126 return emitError(loc, "unsupported comparison predicate");
1127 }();
1128 if (failed(predicate)) {
1129 return failure();
1130 }
1131 return arith::CmpIOp::create(builder, loc, *predicate, lhs, rhs).getResult();
1132 }
1133
1135 LogicalResult
1136 lowerOperation(OpBuilder &builder, Operation &op, DenseMap<Value, LoweredValue> &valueMap) {
1137 Location loc = op.getLoc();
1138
1139 auto bind = [&](Value result, LoweredValue lowered) {
1140 valueMap[result] = std::move(lowered);
1141 return success();
1142 };
1143
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()
1152 ))) {
1153 return failure();
1154 }
1155 }
1156 func::ReturnOp::create(builder, loc, results);
1157 return success();
1158 }
1159
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()
1168 ))) {
1169 return failure();
1170 }
1171 }
1172 scf::YieldOp::create(builder, loc, results);
1173 return success();
1174 }
1175 if (auto conditionOp = dyn_cast<scf::ConditionOp>(op)) {
1176 auto condition =
1177 lookupScalar(conditionOp.getCondition(), valueMap, conditionOp.getOperation());
1178 if (failed(condition)) {
1179 return failure();
1180 }
1181 SmallVector<Value> results;
1182 for (Value operand : conditionOp.getArgs()) {
1183 auto lowered = lookup(operand, valueMap, conditionOp.getOperation());
1184 auto leafTypes =
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()
1189 ))) {
1190 return failure();
1191 }
1192 }
1193 scf::ConditionOp::create(builder, loc, *condition, results);
1194 return success();
1195 }
1196
1197 if (auto constantOp = dyn_cast<arith::ConstantOp>(op)) {
1198 Operation *clone = builder.clone(op);
1199 return bind(
1200 constantOp.getResult(), LoweredValue {constantOp.getType(), {clone->getResult(0)}}
1201 );
1202 }
1203
1204 if (auto feltConst = dyn_cast<felt::FeltConstantOp>(op)) {
1205 auto constVal = feltConst.getValue().getReducedValue();
1206 if (!constVal) {
1207 return feltConst.emitError(
1208 "felt constant requires a specified field for witness generation"
1209 );
1210 }
1211 auto intType = IntegerType::get(builder.getContext(), field.bitWidth());
1212 // Store the field representative in an APInt with the exact storage width.
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}});
1216 }
1217
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
1222 );
1223 if (failed(lowered)) {
1224 return failure();
1225 }
1226 return bind(nondetOp.getResult(), std::move(*lowered));
1227 }
1228
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)) {
1233 return failure();
1234 }
1235 return bind(
1236 addOp.getResult(),
1237 LoweredValue {addOp.getType(), {lowerFeltAdd(builder, loc, *lhs, *rhs, field)}}
1238 );
1239 }
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)) {
1244 return failure();
1245 }
1246 return bind(
1247 powOp.getResult(),
1248 LoweredValue {powOp.getType(), {lowerFeltPow(builder, loc, *lhs, *rhs, field)}}
1249 );
1250 }
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)) {
1255 return failure();
1256 }
1257 return bind(
1258 andOp.getResult(),
1259 LoweredValue {andOp.getType(), {arith::AndIOp::create(builder, loc, *lhs, *rhs)}}
1260 );
1261 }
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)) {
1266 return failure();
1267 }
1268 return bind(
1269 orOp.getResult(),
1270 LoweredValue {orOp.getType(), {lowerFeltOr(builder, loc, *lhs, *rhs, field)}}
1271 );
1272 }
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)) {
1277 return failure();
1278 }
1279 return bind(
1280 xorOp.getResult(),
1281 LoweredValue {xorOp.getType(), {lowerFeltXor(builder, loc, *lhs, *rhs, field)}}
1282 );
1283 }
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)) {
1288 return failure();
1289 }
1290 return bind(
1291 subOp.getResult(),
1292 LoweredValue {subOp.getType(), {lowerFeltSub(builder, loc, *lhs, *rhs, field)}}
1293 );
1294 }
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)) {
1299 return failure();
1300 }
1301 return bind(
1302 mulOp.getResult(),
1303 LoweredValue {mulOp.getType(), {lowerFeltMul(builder, loc, *lhs, *rhs, field)}}
1304 );
1305 }
1306 if (auto negOp = dyn_cast<felt::NegFeltOp>(op)) {
1307 auto operand = lookupScalar(negOp.getOperand(), valueMap, negOp.getOperation());
1308 if (failed(operand)) {
1309 return failure();
1310 }
1311 return bind(
1312 negOp.getResult(),
1313 LoweredValue {negOp.getType(), {lowerFeltNeg(builder, loc, *operand, field)}}
1314 );
1315 }
1316 if (auto invOp = dyn_cast<felt::InvFeltOp>(op)) {
1317 auto operand = lookupScalar(invOp.getOperand(), valueMap, invOp.getOperation());
1318 if (failed(operand)) {
1319 return failure();
1320 }
1321 return bind(
1322 invOp.getResult(),
1323 LoweredValue {invOp.getType(), {lowerFeltInv(builder, loc, *operand, field)}}
1324 );
1325 }
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)) {
1330 return failure();
1331 }
1332 return bind(
1333 divOp.getResult(),
1334 LoweredValue {divOp.getType(), {lowerFeltDiv(builder, loc, *lhs, *rhs, field)}}
1335 );
1336 }
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)) {
1341 return failure();
1342 }
1343 assertNonZeroFelt(builder, loc, *rhs, "felt.uintdiv divisor must be non-zero");
1344 return bind(
1345 uintDivOp.getResult(),
1346 LoweredValue {uintDivOp.getType(), {lowerFeltUnsignedDiv(builder, loc, *lhs, *rhs)}}
1347 );
1348 }
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)) {
1353 return failure();
1354 }
1355 assertNonZeroFelt(builder, loc, *rhs, "felt.sintdiv divisor must be non-zero");
1356 return bind(
1357 sintDivOp.getResult(),
1358 LoweredValue {sintDivOp.getType(), {lowerFeltSignedDiv(builder, loc, *lhs, *rhs, field)}}
1359 );
1360 }
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)) {
1365 return failure();
1366 }
1367 assertNonZeroFelt(builder, loc, *rhs, "felt.umod divisor must be non-zero");
1368 return bind(
1369 umodOp.getResult(),
1370 LoweredValue {umodOp.getType(), {lowerFeltUnsignedMod(builder, loc, *lhs, *rhs)}}
1371 );
1372 }
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)) {
1377 return failure();
1378 }
1379 assertNonZeroFelt(builder, loc, *rhs, "felt.smod divisor must be non-zero");
1380 return bind(
1381 smodOp.getResult(),
1382 LoweredValue {smodOp.getType(), {lowerFeltSignedMod(builder, loc, *lhs, *rhs, field)}}
1383 );
1384 }
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)) {
1389 return failure();
1390 }
1391 return bind(
1392 shrOp.getResult(),
1393 LoweredValue {shrOp.getType(), {lowerFeltShr(builder, loc, *lhs, *rhs, field)}}
1394 );
1395 }
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)) {
1400 return failure();
1401 }
1402 return bind(
1403 shlOp.getResult(),
1404 LoweredValue {shlOp.getType(), {lowerFeltShl(builder, loc, *lhs, *rhs, field)}}
1405 );
1406 }
1407 if (auto notOp = dyn_cast<felt::NotFeltOp>(op)) {
1408 auto operand = lookupScalar(notOp.getOperand(), valueMap, notOp.getOperation());
1409 if (failed(operand)) {
1410 return failure();
1411 }
1412 return bind(
1413 notOp.getResult(),
1414 LoweredValue {notOp.getType(), {lowerFeltNot(builder, loc, *operand, field)}}
1415 );
1416 }
1417
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)) {
1422 return failure();
1423 }
1424 auto lowered = lowerFeltCmp(builder, loc, cmpOp, *lhs, *rhs);
1425 if (failed(lowered)) {
1426 return failure();
1427 }
1428 return bind(cmpOp.getResult(), LoweredValue {cmpOp.getType(), {*lowered}});
1429 }
1430 if (auto assertOp = dyn_cast<boolean::AssertOp>(op)) {
1431 auto condition = lookupScalar(assertOp.getCondition(), valueMap, assertOp.getOperation());
1432 if (failed(condition)) {
1433 return failure();
1434 }
1435 cf::AssertOp::create(
1436 builder, loc, *condition,
1437 assertOp.getMsg() ? assertOp.getMsg()->str() : "bool.assert failed"
1438 );
1439 return success();
1440 }
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)) {
1445 return failure();
1446 }
1447 return bind(
1448 andOp.getResult(),
1449 LoweredValue {andOp.getType(), {arith::AndIOp::create(builder, loc, *lhs, *rhs)}}
1450 );
1451 }
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)) {
1456 return failure();
1457 }
1458 return bind(
1459 orOp.getResult(),
1460 LoweredValue {orOp.getType(), {arith::OrIOp::create(builder, loc, *lhs, *rhs)}}
1461 );
1462 }
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)) {
1467 return failure();
1468 }
1469 return bind(
1470 xorOp.getResult(),
1471 LoweredValue {xorOp.getType(), {arith::XOrIOp::create(builder, loc, *lhs, *rhs)}}
1472 );
1473 }
1474 if (auto notOp = dyn_cast<boolean::NotBoolOp>(op)) {
1475 auto operand = lookupScalar(notOp.getOperand(), valueMap, notOp.getOperation());
1476 if (failed(operand)) {
1477 return failure();
1478 }
1479 Value one = arith::ConstantOp::create(
1480 builder, loc, IntegerAttr::get(IntegerType::get(builder.getContext(), 1), 1)
1481 );
1482 return bind(
1483 notOp.getResult(),
1484 LoweredValue {notOp.getType(), {arith::XOrIOp::create(builder, loc, *operand, one)}}
1485 );
1486 }
1487
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"
1492 );
1493 }
1494 auto operand = lookupScalar(intToFelt.getValue(), valueMap, intToFelt.getOperation());
1495 if (failed(operand)) {
1496 return failure();
1497 }
1498 const Field &resultField = intToFelt.getType().getField();
1499 auto dstType = IntegerType::get(builder.getContext(), resultField.bitWidth());
1500 Value lowered;
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");
1506
1507 // Widen before checking so that a small field does not truncate an
1508 // otherwise out-of-range index before the comparison.
1509 unsigned wideWidth = std::max(resultField.bitWidth(), IndexType::kInternalStorageBitWidth);
1510 Value widened = arith::IndexCastUIOp::create(
1511 builder, loc, IntegerType::get(builder.getContext(), wideWidth), *operand
1512 );
1513 Value modulus = arith::ConstantOp::create(
1514 builder, loc, resultField.getPrimeAttr(builder.getContext(), wideWidth)
1515 );
1516 Value fitsField =
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");
1519
1520 lowered = wideWidth == dstType.getWidth()
1521 ? widened
1522 : arith::TruncIOp::create(builder, loc, dstType, widened).getResult();
1523 } else {
1524 // The op verifier only permits index or i1. Booleans are unsigned 0/1
1525 // and fit every field, so zero extension needs no runtime range check.
1526 lowered = arith::ExtUIOp::create(builder, loc, dstType, *operand);
1527 }
1528 return bind(intToFelt.getResult(), LoweredValue {intToFelt.getType(), {lowered}});
1529 }
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"
1534 );
1535 }
1536 auto operand = lookupScalar(feltToIndex.getValue(), valueMap, feltToIndex.getOperation());
1537 if (failed(operand)) {
1538 return failure();
1539 }
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);
1545 }
1546 Value maxIndex = arith::ConstantOp::create(
1547 builder, loc, IntegerAttr::get(wideType, std::numeric_limits<int64_t>::max())
1548 );
1549 Value fitsIndex =
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");
1552 return bind(
1553 feltToIndex.getResult(),
1554 LoweredValue {
1555 feltToIndex.getType(),
1556 {arith::IndexCastUIOp::create(builder, loc, builder.getIndexType(), *operand)}
1557 }
1558 );
1559 }
1560
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
1565 );
1566 if (failed(lowered)) {
1567 return failure();
1568 }
1569 return bind(structNewOp.getResult(), std::move(*lowered));
1570 }
1571 if (auto readMemberOp = dyn_cast<component::MemberReadOp>(op)) {
1572 auto componentValue =
1573 lookup(readMemberOp.getComponent(), valueMap, readMemberOp.getOperation());
1574 if (failed(componentValue)) {
1575 return failure();
1576 }
1577 auto lowered = readNamedAggregateValue(
1578 builder, loc, readMemberOp.getComponent().getType(), readMemberOp.getMemberName(),
1579 *componentValue, tables, readMemberOp.getOperation(), field
1580 );
1581 if (failed(lowered)) {
1582 return failure();
1583 }
1584 return bind(readMemberOp.getResult(), std::move(*lowered));
1585 }
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)) {
1591 return failure();
1592 }
1593 return writeNamedAggregateValue(
1594 builder, loc, writeMemberOp.getComponent().getType(), writeMemberOp.getMemberName(),
1595 valueMap[writeMemberOp.getComponent()], *memberValue, tables,
1596 writeMemberOp.getOperation(), field
1597 );
1598 }
1599
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
1604 );
1605 if (failed(lowered)) {
1606 return failure();
1607 }
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
1613 ))) {
1614 return failure();
1615 }
1616 }
1617 return bind(newPodOp.getResult(), std::move(*lowered));
1618 }
1619 if (auto readPodOp = dyn_cast<pod::ReadPodOp>(op)) {
1620 auto podValue = lookup(readPodOp.getPodRef(), valueMap, readPodOp.getOperation());
1621 if (failed(podValue)) {
1622 return failure();
1623 }
1624 auto lowered = readNamedAggregateValue(
1625 builder, loc, readPodOp.getPodRef().getType(), readPodOp.getRecordName(), *podValue,
1626 tables, readPodOp.getOperation(), field
1627 );
1628 if (failed(lowered)) {
1629 return failure();
1630 }
1631 return bind(readPodOp.getResult(), std::move(*lowered));
1632 }
1633 if (auto writePodOp = dyn_cast<pod::WritePodOp>(op)) {
1634 auto recordValue = lookup(writePodOp.getValue(), valueMap, writePodOp.getOperation());
1635 if (failed(recordValue)) {
1636 return failure();
1637 }
1638 return writeNamedAggregateValue(
1639 builder, loc, writePodOp.getPodRef().getType(), writePodOp.getRecordName(),
1640 valueMap[writePodOp.getPodRef()], *recordValue, tables, writePodOp.getOperation(), field
1641 );
1642 }
1643
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
1648 );
1649 if (failed(lowered)) {
1650 return failure();
1651 }
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()));
1656 }
1657 if (arrayNewOp.getElements().size() != *elementCount) {
1658 return arrayNewOp.emitError(
1659 "expected one explicit element per array slot in witgen lowering"
1660 );
1661 }
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)) {
1666 return failure();
1667 }
1668 SmallVector<Value> indices;
1669 auto strides = mlir::computeStrides(shape);
1670 auto flatSigned = checkedCast<int64_t>(flatIndex);
1671 if (!flatSigned) {
1672 return arrayNewOp.emitError(llvm::toString(flatSigned.takeError()));
1673 }
1674 for (int64_t index : mlir::delinearize(*flatSigned, strides)) {
1675 indices.push_back(makeIndexConstant(builder, loc, index));
1676 }
1677 if (failed(writeArrayElement(
1678 builder, loc, arrayNewOp.getType(), *lowered, indices, *elementValue
1679 ))) {
1680 return failure();
1681 }
1682 }
1683 }
1684 return bind(arrayNewOp.getResult(), std::move(*lowered));
1685 }
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)) {
1691 return failure();
1692 }
1693 indices.push_back(*loweredIndex);
1694 }
1695 auto arrayValue = lookup(readArrayOp.getArrRef(), valueMap, readArrayOp.getOperation());
1696 if (failed(arrayValue)) {
1697 return failure();
1698 }
1699 auto lowered = readArrayElement(
1700 builder, loc, llvm::cast<array::ArrayType>(readArrayOp.getArrRef().getType()),
1701 *arrayValue, indices
1702 );
1703 if (failed(lowered)) {
1704 return failure();
1705 }
1706 return bind(readArrayOp.getResult(), std::move(*lowered));
1707 }
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)) {
1713 return failure();
1714 }
1715 indices.push_back(*loweredIndex);
1716 }
1717 auto elementValue = lookup(writeArrayOp.getRvalue(), valueMap, writeArrayOp.getOperation());
1718 if (failed(elementValue)) {
1719 return failure();
1720 }
1721 return writeArrayElement(
1722 builder, loc, llvm::cast<array::ArrayType>(writeArrayOp.getArrRef().getType()),
1723 valueMap[writeArrayOp.getArrRef()], indices, *elementValue
1724 );
1725 }
1726
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)) {
1731 return failure();
1732 }
1733 return bind(
1734 cmpiOp.getResult(),
1735 LoweredValue {
1736 cmpiOp.getType(),
1737 {arith::CmpIOp::create(builder, loc, cmpiOp.getPredicate(), *lhs, *rhs)}
1738 }
1739 );
1740 }
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)) {
1746 return failure();
1747 }
1748 return bind(
1749 selectOp.getResult(),
1750 LoweredValue {
1751 selectOp.getType(),
1752 {arith::SelectOp::create(builder, loc, *cond, *trueValue, *falseValue)}
1753 }
1754 );
1755 }
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)) {
1760 return failure();
1761 }
1762 return bind(
1763 addiOp.getResult(),
1764 LoweredValue {addiOp.getType(), {arith::AddIOp::create(builder, loc, *lhs, *rhs)}}
1765 );
1766 }
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)) {
1771 return failure();
1772 }
1773 return bind(
1774 subiOp.getResult(),
1775 LoweredValue {subiOp.getType(), {arith::SubIOp::create(builder, loc, *lhs, *rhs)}}
1776 );
1777 }
1778
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"
1783 );
1784 }
1785 auto *callable = callOp.resolveCallableInTable(&tables);
1786 auto callee = dyn_cast_or_null<function::FuncDefOp>(callable);
1787 if (!callee) {
1788 return callOp.emitError("failed to resolve callee during execution-engine lowering");
1789 }
1790 SmallVector<Type> resultTypes;
1791 for (Type resultType : callOp.getResultTypes()) {
1792 if (failed(
1793 flattenABILeafTypes(resultType, tables, callOp.getOperation(), field, resultTypes)
1794 )) {
1795 return failure();
1796 }
1797 }
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()
1805 ))) {
1806 return failure();
1807 }
1808 }
1809 auto loweredCall =
1810 func::CallOp::create(builder, loc, mangleFunctionName(callee), resultTypes, flatArgs);
1811 auto loweredCallResults = loweredCall.getResults();
1812 size_t totalResults = loweredCallResults.size();
1813 size_t cursor = 0;
1814 for (auto [oldResult, oldType] : llvm::zip(callOp.getResults(), callOp.getResultTypes())) {
1815 auto leafCount = getLeafCount(oldType, tables, callOp.getOperation(), field);
1816 if (failed(leafCount)) {
1817 return failure();
1818 }
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");
1823 }
1824 LoweredValue lowered {oldType, {}};
1825 lowered.leaves.append(
1826 loweredCallResults.begin() + static_cast<ptrdiff_t>(cursor),
1827 loweredCallResults.begin() + static_cast<ptrdiff_t>(nextCursor)
1828 );
1829 valueMap[oldResult] = std::move(lowered);
1830 cursor = nextCursor;
1831 }
1832 return success();
1833 }
1834
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()
1844 ))) {
1845 return failure();
1846 }
1847 auto count = getLeafCount(initType, tables, whileOp.getOperation(), field);
1848 if (failed(count)) {
1849 return failure();
1850 }
1851 beforeLeafCounts.push_back(*count);
1852 }
1853
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)) {
1860 return failure();
1861 }
1862 loweredResultTypes.append(leafTypes->begin(), leafTypes->end());
1863 resultLeafCounts.push_back(*count);
1864 }
1865
1866 auto mapRegionArguments = [&](auto oldArgs, auto oldTypes, auto leafCounts, auto newArgs,
1867 StringRef overflowMessage,
1868 DenseMap<Value, LoweredValue> &regionMap) -> LogicalResult {
1869 size_t totalArgs = newArgs.size();
1870 size_t cursor = 0;
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);
1876 }
1877 LoweredValue lowered {oldType, {}};
1878 lowered.leaves.append(
1879 newArgs.begin() + llzk::checkedCast<ptrdiff_t>(cursor),
1880 newArgs.begin() + llzk::checkedCast<ptrdiff_t>(nextCursor)
1881 );
1882 regionMap[oldArg] = std::move(lowered);
1883 cursor = nextCursor;
1884 }
1885 return success();
1886 };
1887
1888 LogicalResult whileLoweringStatus = success();
1889 auto newWhile = scf::WhileOp::create(
1890 builder, loc, loweredResultTypes, initArgs,
1891 [&](OpBuilder &regionBuilder, Location /*regionLoc*/, 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",
1896 beforeMap
1897 )) ||
1898 failed(lowerBlock(regionBuilder, whileOp.getBefore().front(), beforeMap))) {
1899 whileLoweringStatus = failure();
1900 }
1901 }, [&](OpBuilder &regionBuilder, Location /*regionLoc*/, 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
1906 )) ||
1907 failed(lowerBlock(regionBuilder, whileOp.getAfter().front(), afterMap))) {
1908 whileLoweringStatus = failure();
1909 }
1910 }
1911 );
1912 if (failed(whileLoweringStatus)) {
1913 newWhile.erase();
1914 return failure();
1915 }
1916
1917 auto newWhileResults = newWhile.getResults();
1918 size_t totalResults = newWhileResults.size();
1919 size_t cursor = 0;
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");
1926 }
1927 LoweredValue lowered {oldType, {}};
1928 lowered.leaves.append(
1929 newWhileResults.begin() + llzk::checkedCast<ptrdiff_t>(cursor),
1930 newWhileResults.begin() + llzk::checkedCast<ptrdiff_t>(nextCursor)
1931 );
1932 valueMap[oldResult] = std::move(lowered);
1933 cursor = nextCursor;
1934 }
1935 return success();
1936 }
1937
1938 if (auto ifOp = dyn_cast<scf::IfOp>(op)) {
1939 auto condition = lookupScalar(ifOp.getCondition(), valueMap, ifOp.getOperation());
1940 if (failed(condition)) {
1941 return failure();
1942 }
1943
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)) {
1950 return failure();
1951 }
1952 loweredResultTypes.append(leafTypes->begin(), leafTypes->end());
1953 resultLeafCounts.push_back(*count);
1954 }
1955
1956 auto newIf = scf::IfOp::create(
1957 builder, loc, loweredResultTypes, *condition, true, !ifOp.getElseRegion().empty()
1958 );
1959
1960 {
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))) {
1964 newIf.erase();
1965 return failure();
1966 }
1967 }
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))) {
1972 newIf.erase();
1973 return failure();
1974 }
1975 }
1976 auto newIfResults = newIf.getResults();
1977 size_t totalResults = newIfResults.size();
1978 size_t cursor = 0;
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");
1985 }
1986 LoweredValue lowered {oldType, {}};
1987 lowered.leaves.append(
1988 newIfResults.begin() + llzk::checkedCast<ptrdiff_t>(cursor),
1989 newIfResults.begin() + llzk::checkedCast<ptrdiff_t>(nextCursor)
1990 );
1991 valueMap[oldResult] = std::move(lowered);
1992 cursor = nextCursor;
1993 }
1994 return success();
1995 }
1996
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)) {
2002 return failure();
2003 }
2004
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()
2013 ))) {
2014 return failure();
2015 }
2016 auto count = getLeafCount(resultType, tables, forOp.getOperation(), field);
2017 if (failed(count)) {
2018 return failure();
2019 }
2020 initLeafCounts.push_back(*count);
2021 }
2022
2023 auto newFor = scf::ForOp::create(builder, loc, *lb, *ub, *step, initArgs);
2024 if (Attribute unsignedCmpAttr = forOp->getAttr("unsignedCmp")) {
2025 newFor->setAttr("unsignedCmp", unsignedCmpAttr);
2026 }
2027 DenseMap<Value, LoweredValue> bodyMap(valueMap.begin(), valueMap.end());
2028 bodyMap[forOp.getInductionVar()] =
2029 LoweredValue {forOp.getInductionVar().getType(), {newFor.getInductionVar()}};
2030 {
2031 auto newForIterArgs = newFor.getRegionIterArgs();
2032 size_t totalIterArgs = newForIterArgs.size();
2033 size_t cursor = 0;
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");
2040 }
2041 LoweredValue lowered {oldType, {}};
2042 lowered.leaves.append(
2043 newForIterArgs.begin() + static_cast<ptrdiff_t>(cursor),
2044 newForIterArgs.begin() + static_cast<ptrdiff_t>(nextCursor)
2045 );
2046 bodyMap[oldIterArg] = std::move(lowered);
2047 cursor = nextCursor;
2048 }
2049 }
2050
2051 newFor.getBody()->clear();
2052 OpBuilder bodyBuilder = OpBuilder::atBlockBegin(newFor.getBody());
2053 if (failed(lowerBlock(bodyBuilder, *forOp.getBody(), bodyMap))) {
2054 return failure();
2055 }
2056
2057 {
2058 auto newForResults = newFor.getResults();
2059 size_t totalForResults = newForResults.size();
2060 size_t cursor = 0;
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");
2067 }
2068 LoweredValue lowered {oldType, {}};
2069 lowered.leaves.append(
2070 newForResults.begin() + static_cast<ptrdiff_t>(cursor),
2071 newForResults.begin() + static_cast<ptrdiff_t>(nextCursor)
2072 );
2073 valueMap[oldResult] = std::move(lowered);
2074 cursor = nextCursor;
2075 }
2076 }
2077 return success();
2078 }
2079
2080 return op.emitError("unsupported operation in execution-engine lowering: ") << op.getName();
2081 }
2082};
2083
2085class LowerComputeToCorePass : public PassWrapper<LowerComputeToCorePass, OperationPass<ModuleOp>> {
2086public:
2087 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LowerComputeToCorePass)
2088
2089 explicit LowerComputeToCorePass(const WitgenOptions &opts) : options(opts) {}
2090
2092 StringRef getArgument() const final { return "llzk-lower-compute-to-core"; }
2093
2095 StringRef getDescription() const final {
2096 return "Lower LLZK compute IR to func/arith/cf/scf/memref";
2097 }
2098
2100 StringRef getName() const override { return "LowerComputeToCorePass"; }
2101
2103 void runOnOperation() override {
2104 ModuleOp moduleOp = getOperation();
2105 auto field = getModuleField(moduleOp);
2106 if (failed(field)) {
2107 signalPassFailure();
2108 return;
2109 }
2110
2111 SymbolTableCollection tables;
2112 BodyLowerer lowerer(moduleOp, tables, field->get(), options);
2113 auto funcs = walkCollect<function::FuncDefOp>(moduleOp, [](auto funcOp) {
2114 return !funcOp.nameIsConstrain();
2115 });
2116 for (function::FuncDefOp funcOp : funcs) {
2117 if (failed(lowerer.lowerFunction(funcOp))) {
2118 signalPassFailure();
2119 return;
2120 }
2121 }
2122 }
2123
2124private:
2125 WitgenOptions options;
2126};
2127
2129class CreateWitgenEntryPass : public PassWrapper<CreateWitgenEntryPass, OperationPass<ModuleOp>> {
2130public:
2131 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CreateWitgenEntryPass)
2132
2133
2134 explicit CreateWitgenEntryPass(OutputScope newOutputScope) : outputScope(newOutputScope) {}
2135
2137 StringRef getArgument() const final { return "llzk-create-witgen-entry"; }
2138
2140 StringRef getDescription() const final {
2141 return "Create the llzk-witgen execution-engine entry wrapper";
2142 }
2143
2145 StringRef getName() const override { return "CreateWitgenEntryPass"; }
2146
2148 void runOnOperation() override {
2149 ModuleOp moduleOp = getOperation();
2150 auto field = getModuleField(moduleOp);
2151 if (failed(field)) {
2152 signalPassFailure();
2153 return;
2154 }
2155
2156 SymbolTableCollection tables;
2157 auto mainDef = getMainInstanceDef(tables, moduleOp.getOperation());
2158 if (failed(mainDef) || !mainDef.value()) {
2159 moduleOp.emitError("module is missing a concrete llzk.main struct");
2160 signalPassFailure();
2161 return;
2162 }
2163 function::FuncDefOp computeFunc = mainDef->get().getComputeFuncOp();
2164 if (!computeFunc) {
2165 moduleOp.emitError("main struct is missing @compute");
2166 signalPassFailure();
2167 return;
2168 }
2169
2170 auto outputs =
2171 collectOutputBindings(mainDef->get(), tables, computeFunc.getOperation(), outputScope);
2172 if (failed(outputs)) {
2173 signalPassFailure();
2174 return;
2175 }
2176
2177 OpBuilder builder(moduleOp.getContext());
2178 builder.setInsertionPointToEnd(moduleOp.getBody());
2179
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
2185 ))) {
2186 signalPassFailure();
2187 return;
2188 }
2189 if (loweredLeafTypes.size() != 1 || !isa<MemRefType>(loweredLeafTypes.front())) {
2190 computeFunc.emitError(
2191 "execution-engine wrapper only supports felt and array<...xfelt> inputs"
2192 );
2193 signalPassFailure();
2194 return;
2195 }
2196 wrapperArgs.push_back(loweredLeafTypes.front());
2197 }
2198 for (const OutputBinding &output : *outputs) {
2199 SmallVector<Type> loweredLeafTypes;
2200 if (failed(flattenTypeLeaves(
2201 output.type, tables, computeFunc.getOperation(), field->get(), loweredLeafTypes, {},
2202 true
2203 ))) {
2204 signalPassFailure();
2205 return;
2206 }
2207 if (loweredLeafTypes.size() != 1 || !isa<MemRefType>(loweredLeafTypes.front())) {
2208 computeFunc.emitError(
2209 "execution-engine wrapper only supports felt and array<...xfelt> outputs"
2210 );
2211 signalPassFailure();
2212 return;
2213 }
2214 wrapperArgs.push_back(loweredLeafTypes.front());
2215 }
2216
2217 auto wrapper = func::FuncOp::create(
2218 builder, computeFunc.getLoc(), "__llzk_witgen_main",
2219 builder.getFunctionType(wrapperArgs, TypeRange {})
2220 );
2221 wrapper->setAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(), builder.getUnitAttr());
2222 Block *entry = wrapper.addEntryBlock();
2223 builder.setInsertionPointToStart(entry);
2224
2225 SmallVector<Type> loweredMainResultTypes;
2226 for (Type resultType : computeFunc.getResultTypes()) {
2227 if (failed(flattenABILeafTypes(
2228 resultType, tables, computeFunc.getOperation(), field->get(), loweredMainResultTypes
2229 ))) {
2230 signalPassFailure();
2231 return;
2232 }
2233 }
2234
2235 SmallVector<Value> mainArgs;
2236 for (auto [argType, wrapperArg] : llvm::zip(
2237 computeFunc.getArgumentTypes(),
2238 entry->getArguments().take_front(computeFunc.getNumArguments())
2239 )) {
2240 if (isScalarType(argType)) {
2241 mainArgs.push_back(loadStorageScalar(builder, computeFunc.getLoc(), wrapperArg));
2242 } else {
2243 auto abiLeafTypes =
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();
2249 return;
2250 }
2251 if (wrapperArg.getType() == abiLeafTypes->front()) {
2252 mainArgs.push_back(wrapperArg);
2253 } else {
2254 mainArgs.push_back(
2255 memref::CastOp::create(
2256 builder, computeFunc.getLoc(), abiLeafTypes->front(), wrapperArg
2257 )
2258 );
2259 }
2260 }
2261 }
2262 auto loweredMain = func::CallOp::create(
2263 builder, computeFunc.getLoc(), mangleFunctionName(computeFunc), loweredMainResultTypes,
2264 mainArgs
2265 );
2266
2267 LoweredValue mainResultValue {
2268 computeFunc.getResultTypes().front(),
2269 llvm::SmallVector<Value>(loweredMain.getResults().begin(), loweredMain.getResults().end())
2270 };
2271
2272 auto extractOutputSlice = [&](ArrayRef<std::string> path, Type currentType,
2273 ArrayRef<Value> leaves,
2274 auto &self) -> FailureOr<SmallVector<Value>> {
2275 if (path.empty()) {
2276 return SmallVector<Value>(leaves.begin(), leaves.end());
2277 }
2278 if (auto structType = dyn_cast<component::StructType>(currentType)) {
2279 auto defLookup = structType.getDefinition(tables, computeFunc.getOperation());
2280 if (failed(defLookup)) {
2281 return failure();
2282 }
2283 unsigned localCursor = 0;
2284 for (component::MemberDefOp member : defLookup->get().getMemberDefs()) {
2285 auto leafCount =
2286 getLeafCount(member.getType(), tables, member.getOperation(), field->get());
2287 if (failed(leafCount)) {
2288 return failure();
2289 }
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);
2294 }
2295 }
2296 return computeFunc.emitError("failed to find struct member while wiring witgen outputs");
2297 }
2298 if (auto podType = dyn_cast<pod::PodType>(currentType)) {
2299 unsigned localCursor = 0;
2300 for (pod::RecordAttr record : podType.getRecords()) {
2301 auto leafCount =
2302 getLeafCount(record.getType(), tables, computeFunc.getOperation(), field->get());
2303 if (failed(leafCount)) {
2304 return failure();
2305 }
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);
2310 }
2311 }
2312 return computeFunc.emitError("failed to find POD record while wiring witgen outputs");
2313 }
2314 return computeFunc.emitError("extra witness path components for non-aggregate output");
2315 };
2316
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
2321 );
2322 if (failed(slice) || slice->empty()) {
2323 wrapper.emitError("missing selected witness output slice while building witgen entry");
2324 signalPassFailure();
2325 return;
2326 }
2327 if (isScalarType(output.type)) {
2328 storeStorageScalar(
2329 builder, computeFunc.getLoc(),
2330 loadStorageScalar(builder, computeFunc.getLoc(), slice->front()), outputMemRef
2331 );
2332 } else {
2333 memref::CopyOp::create(builder, computeFunc.getLoc(), slice->front(), outputMemRef);
2334 }
2335 }
2336 func::ReturnOp::create(builder, computeFunc.getLoc());
2337
2338 // Remove `llzk.main` attribute because the main struct is deleted below.
2339 moduleOp->removeAttr(MAIN_ATTR_NAME);
2340
2341 SmallVector<Operation *> toErase;
2342 for (Operation &op : moduleOp.getBody()->getOperations()) {
2343 if (!isa<func::FuncOp>(op)) {
2344 toErase.push_back(&op);
2345 }
2346 }
2347 for (Operation *op : toErase) {
2348 op->erase();
2349 }
2350 }
2351
2352private:
2353 OutputScope outputScope;
2354};
2355
2356} // namespace
2357
2358void addWitgenPreparePipeline(OpPassManager &pm, const WitgenOptions &) {
2359 using namespace llzk::polymorphic;
2360 pm.addPass(createFlatteningPass(
2362 ));
2363 pm.addPass(mlir::createLowerAffinePass());
2364 // TODO: simplify lowering with `llzk-inline-structs` and `llzk-pod-to-scalar` when both are
2365 // available and support PODs.
2366 pm.addPass(mlir::createCanonicalizerPass());
2367 pm.addPass(mlir::createCSEPass());
2368}
2369
2370std::unique_ptr<Pass> createLowerComputeToCorePass(const WitgenOptions &options) {
2371 return std::make_unique<LowerComputeToCorePass>(options);
2372}
2373
2374std::unique_ptr<Pass> createCreateWitgenEntryPass(OutputScope outputScope) {
2375 return std::make_unique<CreateWitgenEntryPass>(outputScope);
2376}
2377
2378} // namespace llzk::witgen
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
Definition LICENSE.txt:28
std::unique_ptr<::mlir::Pass > createFlatteningPass()
llvm::Expected< T > checkedCast(U u)
Definition WitgenUtils.h:28
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.
Definition ValueModel.h:55
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
Definition Compare.h:94
FailureOr< SymbolLookupResult< StructDefOp > > getMainInstanceDef(SymbolTableCollection &symbolTable, Operation *lookupFrom)
llvm::SmallSet< FieldRef, 2 > FieldSet
Typealias for a set of Fields.
Definition Field.h:160
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.