LLZK 0.1.0
Veridise's ZK Language IR
Loading...
Searching...
No Matches
LLZKR1CSLoweringPass.cpp
Go to the documentation of this file.
1//===-- LLZKR1CSLoweringPass.cpp --------------------------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
13//===----------------------------------------------------------------------===//
14
22#include "r1cs/Dialect/IR/Attrs.h"
23#include "r1cs/Dialect/IR/Ops.h"
24#include "r1cs/Dialect/IR/Types.h"
25
26#include <mlir/IR/BuiltinOps.h>
27
28#include <llvm/ADT/DenseMap.h>
29#include <llvm/ADT/DenseMapInfo.h>
30#include <llvm/ADT/SmallVector.h>
31#include <llvm/Support/Debug.h>
32
33#include <deque>
34#include <memory>
35
36// Include the generated base pass class definitions.
37namespace llzk {
38#define GEN_PASS_DEF_R1CSLOWERINGPASS
40} // namespace llzk
41
42using namespace mlir;
43using namespace llzk;
44using namespace llzk::felt;
45using namespace llzk::function;
46using namespace llzk::component;
47using namespace llzk::constrain;
48
49#define DEBUG_TYPE "llzk-r1cs-lowering"
50#define R1CS_AUXILIARY_FIELD_PREFIX "__llzk_r1cs_lowering_pass_aux_field_"
51
52namespace {
53
55struct LinearCombination {
56 DenseMap<Value, APSInt> terms; // variable -> coeff
57 APSInt constant;
58
59 LinearCombination() : constant(APInt(1, 0), false) {}
60
61 void addTerm(Value v, APSInt coeff) {
62 if (coeff.isZero()) {
63 return;
64 }
65
66 if (!terms.contains(v)) {
67 terms[v] = coeff;
68 } else {
69 terms[v] += coeff;
70 }
71 }
72
73 void negate() {
74 for (auto &kv : terms) {
75 kv.second = -kv.second;
76 }
77 constant = -constant;
78 };
79
80 void addConstant(APSInt c) { constant += c; }
81
82 LinearCombination scaled(const APSInt &factor) const {
83 LinearCombination result;
84 if (factor.isZero()) {
85 return result;
86 }
87
88 for (const auto &kv : terms) {
89 result.terms[kv.first] = expandingMul(kv.second, factor);
90 }
91 result.constant = expandingMul(constant, factor);
92 return result;
93 }
94
95 LinearCombination add(const LinearCombination &other) const {
96 LinearCombination result(*this);
97
98 for (const auto &kv : other.terms) {
99 if (!result.terms.contains(kv.first)) {
100 result.terms[kv.first] = kv.second;
101 } else {
102 result.terms[kv.first] = expandingAdd(result.terms[kv.first], kv.second);
103 }
105 result.constant = expandingAdd(result.constant, other.constant);
106 return result;
108
109 LinearCombination negated() const {
110 return scaled(APSInt(APInt(constant.getBitWidth(), -1, true), false));
111 }
112
113 void print(raw_ostream &os) const {
114 bool first = true;
115 for (const auto &[val, coeff] : terms) {
116 if (!first) {
117 os << " + ";
119 first = false;
120 os << coeff << '*' << val;
122 if (!constant.isZero()) {
123 if (!first) {
124 os << " + ";
125 }
126 os << constant;
127 }
128 if (first && constant.isZero()) {
129 os << '0';
130 }
131 }
132};
133
134/// A struct representing a * b = c R1CS constraint
135struct R1CSConstraint {
136 LinearCombination a;
137 LinearCombination b;
138 LinearCombination c;
139
140 R1CSConstraint negated() const {
141 R1CSConstraint result(*this);
142 result.a = a.negated();
143 result.c = c.negated();
144 return result;
145 }
146
147 R1CSConstraint scaled(const APSInt &factor) const {
148 R1CSConstraint result(*this);
149 result.a = a.scaled(factor);
150 result.c = c.scaled(factor);
151 return result;
152 }
153
154 R1CSConstraint(const APSInt &constant) { c.constant = constant; }
155
156 R1CSConstraint() = default;
157
158 inline bool isLinearOnly() const { return a.terms.empty() && b.terms.empty(); }
159
160 R1CSConstraint multiply(const R1CSConstraint &other) {
161 auto isDegZero = [](const R1CSConstraint &constraint) {
162 return constraint.a.terms.empty() && constraint.b.terms.empty() && constraint.c.terms.empty();
163 };
164
165 if (isDegZero(other)) {
166 return this->scaled(other.c.constant);
167 }
168 if (isDegZero(*this)) {
169 return other.scaled(this->c.constant);
170 }
171
172 if (isLinearOnly() && other.isLinearOnly()) {
173 R1CSConstraint result;
174 result.a = this->c;
175 result.b = other.c;
176
177 // We do NOT compute `c = a * b` because R1CS doesn't need it explicitly
178 // It suffices to enforce: a * b = c
179
180 return result;
181 }
182 llvm::errs() << "R1CSConstraint::multiply: Only supported for purely linear constraints.\n";
183 llvm_unreachable("Invalid multiply: non-linear constraint(s) involved");
184 }
185
186 R1CSConstraint add(const R1CSConstraint &other) {
187
188 if (isLinearOnly()) {
189 R1CSConstraint result(other);
190 result.c = result.c.add(this->c);
191 return result;
192 }
193 if (other.isLinearOnly()) {
194 R1CSConstraint result(*this);
195 result.c = result.c.add(other.c);
196 return result;
197 }
198 llvm::errs() << "R1CSConstraint::add: Only supported for purely linear constraints.\n";
199 llvm_unreachable("Invalid add: non-linear constraint(s) involved");
200 }
201
202 void print(raw_ostream &os) const {
203 os << '(';
204 a.print(os);
205 os << ") * (";
206 b.print(os);
207 os << ") = ";
208 c.print(os);
209 }
210};
211
212class R1CSLoweringPass : public llzk::impl::R1CSLoweringPassBase<R1CSLoweringPass> {
213private:
214 unsigned auxCounter = 0;
215
216 // Normalize a felt-valued expression into R1CS-compatible form.
217 // This performs *minimal* rewriting:
218 // - Only rewrites Add/Sub of two degree-2 terms
219 // - Operates bottom-up using post-order traversal
220 //
221 // Resulting expression is R1CS-compatible (i.e., one multiplication per constraint)
222 // and can be directly used in EmitEqualityOp or as operands of other expressions.
223
224 void getPostOrder(Value root, SmallVectorImpl<Value> &postOrder) {
225 SmallVector<Value, 16> worklist;
226 DenseSet<Value> visited;
227
228 worklist.push_back(root);
229
230 while (!worklist.empty()) {
231 Value val = worklist.back();
232
233 if (visited.contains(val)) {
234 worklist.pop_back();
235 postOrder.push_back(val);
236 continue;
237 }
238
239 visited.insert(val);
240 if (Operation *op = val.getDefiningOp()) {
241 for (Value operand : op->getOperands()) {
242 worklist.push_back(operand);
243 }
244 }
245 }
246 }
247
272 Value normalizeForR1CS(
273 Value root, StructDefOp structDef, FuncDefOp constrainFunc,
274 DenseMap<Value, unsigned> &degreeMemo, DenseMap<Value, Value> &rewrites,
275 SmallVectorImpl<AuxAssignment> &auxAssignments, OpBuilder &builder
276 ) {
277 if (auto it = rewrites.find(root); it != rewrites.end()) {
278 return it->second;
279 }
280
281 SmallVector<Value, 16> postOrder;
282 getPostOrder(root, postOrder);
283
284 // We perform a bottom up rewrite of the expressions. For any expression e := op(e_1, ...,
285 // e_n) we first rewrite e_1, ..., e_n if necessary and then rewrite e based on op.
286 for (Value val : postOrder) {
287 if (rewrites.contains(val)) {
288 continue;
289 }
290
291 Operation *op = val.getDefiningOp();
292
293 if (!op) {
294 // Block arguments, etc.
295 degreeMemo[val] = 1;
296 rewrites[val] = val;
297 continue;
298 }
299
300 // Case 1: Felt constant op. The degree is 0 and no rewrite is needed.
301 if (auto c = llvm::dyn_cast<FeltConstantOp>(op)) {
302 degreeMemo[val] = 0;
303 rewrites[val] = val;
304 continue;
305 }
306
307 // Case 2: Field read op. The degree is 1 and no rewrite needed.
308 if (auto fr = llvm::dyn_cast<FieldReadOp>(op)) {
309 degreeMemo[val] = 1;
310 rewrites[val] = val;
311 continue;
312 }
313
314 // Helper function for getting degree from memo map
315 auto getDeg = [&degreeMemo](Value v) -> unsigned {
316 auto it = degreeMemo.find(v);
317 assert(it != degreeMemo.end() && "Missing degree");
318 return it->second;
319 };
320
321 // Case 3: lhs +/- rhs. There are three subcases cases to consider:
322 // 1) If deg(lhs) <= degree(rhs) < 2 then nothing needs to be done
323 // 2) If deg(lhs) = 2 and degree(rhs) < 2 then nothing further has to be done.
324 // 3) If deg(lhs) = deg(rhs) = 2 then we lower one of lhs or rhs.
325 auto handleAddOrSub = [&](Value lhsOrig, Value rhsOrig, bool isAdd) {
326 Value lhs = rewrites[lhsOrig];
327 Value rhs = rewrites[rhsOrig];
328 unsigned degLhs = getDeg(lhs);
329 unsigned degRhs = getDeg(rhs);
330
331 if (degLhs == 2 && degRhs == 2) {
332 builder.setInsertionPoint(op);
333 std::string auxName = R1CS_AUXILIARY_FIELD_PREFIX + std::to_string(auxCounter++);
334 FieldDefOp auxField = addAuxField(structDef, auxName);
335 Value self = constrainFunc.getArgument(0);
336 Value aux = builder.create<FieldReadOp>(
337 val.getLoc(), val.getType(), self, auxField.getNameAttr()
338 );
339 auto eqOp = builder.create<EmitEqualityOp>(val.getLoc(), aux, lhs);
340 auxAssignments.push_back({auxName, lhs});
341 degreeMemo[aux] = 1;
342 rewrites[aux] = aux;
343 replaceSubsequentUsesWith(lhs, aux, eqOp);
344 lhs = aux;
345 degLhs = 1;
346
347 Operation *newOp = isAdd
348 ? builder.create<AddFeltOp>(val.getLoc(), val.getType(), lhs, rhs)
349 : builder.create<SubFeltOp>(val.getLoc(), val.getType(), lhs, rhs);
350 Value result = newOp->getResult(0);
351 degreeMemo[result] = std::max(degLhs, degRhs);
352 rewrites[val] = result;
353 rewrites[result] = result;
354 val.replaceAllUsesWith(result);
355 if (val.use_empty()) {
356 op->erase();
357 }
358 } else {
359 degreeMemo[val] = std::max(degLhs, degRhs);
360 rewrites[val] = val;
361 }
362 };
363
364 if (auto add = llvm::dyn_cast<AddFeltOp>(op)) {
365 handleAddOrSub(add.getLhs(), add.getRhs(), /*isAdd=*/true);
366 continue;
367 }
368
369 if (auto sub = llvm::dyn_cast<SubFeltOp>(op)) {
370 handleAddOrSub(sub.getLhs(), sub.getRhs(), /*isAdd=*/false);
371 continue;
372 }
373
374 // Case 4: lhs * rhs. Nothing further needs to be done assuming the degree lowering pass has
375 // been run with maxDegree = 2. This is because both operands are normalized and at most one
376 // operand can be quadratic.
377 if (auto mul = llvm::dyn_cast<MulFeltOp>(op)) {
378 Value lhs = rewrites[mul.getLhs()];
379 Value rhs = rewrites[mul.getRhs()];
380 unsigned degLhs = getDeg(lhs);
381 unsigned degRhs = getDeg(rhs);
382
383 degreeMemo[val] = degLhs + degRhs;
384 rewrites[val] = val;
385 continue;
386 }
387
388 // Case 6: Neg. Similar to multiplication, nothing needs to be done since we are doing the
389 // rewrite bottom up
390 if (auto neg = llvm::dyn_cast<NegFeltOp>(op)) {
391 Value inner = rewrites[neg.getOperand()];
392 unsigned deg = getDeg(inner);
393 degreeMemo[val] = deg;
394 rewrites[val] = val;
395 continue;
396 }
397
398 llvm::errs() << "Unhandled op in normalize ForR1CS: " << *op << '\n';
399 signalPassFailure();
400 }
401
402 return rewrites[root];
403 }
404
405 R1CSConstraint lowerPolyToR1CS(Value poly) {
406 // Worklist-based post-order traversal
407 SmallVector<Value, 16> worklist = {poly};
408 DenseMap<Value, R1CSConstraint> constraintMap;
409 DenseSet<Value> visited;
410 SmallVector<Value, 16> postorder;
411
412 getPostOrder(poly, postorder);
413
414 // Bottom-up construction of R1CSConstraints
415 for (Value v : postorder) {
416 Operation *op = v.getDefiningOp();
417 if (!op || llvm::isa<FieldReadOp>(op)) {
418 // Leaf (input variable or field read)
419 R1CSConstraint eq;
420 eq.c.addTerm(v, APSInt::get(1));
421 constraintMap[v] = eq;
422 continue;
423 }
424 if (auto add = dyn_cast<AddFeltOp>(op)) {
425 R1CSConstraint lhsC = constraintMap[add.getLhs()];
426 R1CSConstraint rhsC = constraintMap[add.getRhs()];
427 constraintMap[v] = lhsC.add(rhsC);
428 } else if (auto sub = dyn_cast<SubFeltOp>(op)) {
429 R1CSConstraint lhsC = constraintMap[sub.getLhs()];
430 R1CSConstraint rhsC = constraintMap[sub.getRhs()];
431 constraintMap[v] = lhsC.add(rhsC.negated());
432 } else if (auto mul = dyn_cast<MulFeltOp>(op)) {
433 R1CSConstraint lhsC = constraintMap[mul.getLhs()];
434 R1CSConstraint rhsC = constraintMap[mul.getRhs()];
435 constraintMap[v] = lhsC.multiply(rhsC);
436 } else if (auto neg = dyn_cast<NegFeltOp>(op)) {
437 R1CSConstraint inner = constraintMap[op->getOperand(0)];
438 constraintMap[v] = inner.negated();
439 } else if (auto cst = dyn_cast<FeltConstantOp>(op)) {
440 R1CSConstraint c(APSInt(cst.getValueAttr().getValue(), false));
441 constraintMap[v] = c;
442 } else {
443 llvm::errs() << "Unhandled op in R1CS lowering: " << *op << '\n';
444 llvm_unreachable("unhandled op");
445 }
446 }
447
448 return constraintMap[poly];
449 }
450
451 R1CSConstraint
452 lowerEquationToR1CS(Value p, Value q, const DenseMap<Value, unsigned> &degreeMemo) {
453 R1CSConstraint pconst = lowerPolyToR1CS(p);
454 R1CSConstraint qconst = lowerPolyToR1CS(q);
455
456 if (degreeMemo.at(p) == 2) {
457 return pconst.add(qconst.negated());
458 }
459 return qconst.add(pconst.negated());
460 }
461
462 Value emitLinearCombination(
463 LinearCombination lc, IRMapping &valueMap, DenseMap<StringRef, Value> &fieldMap,
464 OpBuilder &builder, Location loc
465 ) {
466 Value result = nullptr;
467
468 auto getMapping = [&valueMap, &fieldMap, this](const Value &v) {
469 if (!valueMap.contains(v)) {
470 Operation *op = v.getDefiningOp();
471 if (auto read = dyn_cast<FieldReadOp>(op)) {
472 auto fieldVal = fieldMap.find(read.getFieldName());
473 assert(fieldVal != fieldMap.end() && "Field read not associated with a value");
474 return fieldVal->second;
475 }
476 op->emitError("Value not mapped in R1CS lowering").report();
477 signalPassFailure();
478 }
479 return valueMap.lookup(v);
480 };
481
482 auto linearTy = r1cs::LinearType::get(builder.getContext());
483
484 // Start with the constant, if present
485 if (!lc.constant.isZero()) {
486 result = builder.create<r1cs::ConstOp>(
487 loc, linearTy, r1cs::FeltAttr::get(builder.getContext(), lc.constant)
488 );
489 }
490
491 for (auto &[val, coeff] : lc.terms) {
492 Value mapped = getMapping(val);
493 // %tmp = r1cs.to_linear %mapped
494 // most of these will be removed with CSE passes
495 Value lin = builder.create<r1cs::ToLinearOp>(loc, linearTy, mapped);
496 // %scaled = r1cs.mul_const %lin, coeff
497 Value scaled = coeff == 1
498 ? lin
499 : builder.create<r1cs::MulConstOp>(
500 loc, linearTy, lin, r1cs::FeltAttr::get(builder.getContext(), coeff)
501 );
502
503 // Accumulate via r1cs.add
504 if (!result) {
505 result = scaled;
506 } else {
507 result = builder.create<r1cs::AddOp>(loc, linearTy, result, scaled);
508 }
509 }
510
511 if (!result) {
512 // Entire linear combination was zero
513 result = builder.create<r1cs::ConstOp>(
514 loc, r1cs::LinearType::get(builder.getContext()),
515 r1cs::FeltAttr::get(builder.getContext(), 0)
516 );
517 }
518
519 return result;
520 }
521
522 void buildAndEmitR1CS(
523 ModuleOp &moduleOp, StructDefOp &structDef, FuncDefOp &constrainFunc,
524 DenseMap<Value, unsigned> &degreeMemo
525 ) {
526 SmallVector<R1CSConstraint, 16> constraints;
527 constrainFunc.walk([&](EmitEqualityOp eqOp) {
528 OpBuilder builder(eqOp);
529 R1CSConstraint eq = lowerEquationToR1CS(eqOp.getLhs(), eqOp.getRhs(), degreeMemo);
530 constraints.push_back(eq);
531 });
532 moduleOp->setAttr(LANG_ATTR_NAME, StringAttr::get(moduleOp.getContext(), "r1cs"));
533 Block &entryBlock = constrainFunc.getBody().front();
534 IRMapping valueMap;
535 Location loc = structDef.getLoc();
536 OpBuilder topBuilder(moduleOp.getBodyRegion());
537
538 // Validate struct fields are felt and prepare signal types for circuit result types
539 bool hasPublicSignals = false;
540 for (auto field : structDef.getFieldDefs()) {
541 if (!field.getType().isa<FeltType>()) {
542 field.emitError("Only felt fields are supported as output signals").report();
543 signalPassFailure();
544 return;
545 }
546 if (field.isPublic()) {
547 hasPublicSignals = true;
548 }
549 }
550
551 if (!hasPublicSignals) {
552 structDef.emitError("Struct should have at least one public output").report();
553 }
554 llvm::SmallVector<mlir::NamedAttribute> argAttrPairs;
555
556 for (auto [i, arg] : llvm::enumerate(llvm::drop_begin(entryBlock.getArguments(), 1))) {
557 if (constrainFunc.hasArgPublicAttr(i + 1)) {
558 auto key = topBuilder.getStringAttr(std::to_string(i));
559 auto value = r1cs::PublicAttr::get(moduleOp.getContext());
560 argAttrPairs.emplace_back(key, value);
561 }
562 }
563 auto dictAttr = topBuilder.getDictionaryAttr(argAttrPairs);
564 auto circuit =
565 topBuilder.create<r1cs::CircuitDefOp>(loc, structDef.getSymName().str(), dictAttr);
566
567 Block *circuitBlock = circuit.addEntryBlock();
568
569 OpBuilder bodyBuilder = OpBuilder::atBlockEnd(circuitBlock);
570
571 // Step 3: Validate that all parameters to the constrain function are felt types
572 for (auto [i, arg] : llvm::enumerate(llvm::drop_begin(entryBlock.getArguments(), 1))) {
573 if (!arg.getType().isa<FeltType>()) {
574 constrainFunc.emitOpError("All input arguments must be of felt type").report();
575 signalPassFailure();
576 return;
577 }
578 auto blockArg = circuitBlock->addArgument(bodyBuilder.getType<r1cs::SignalType>(), loc);
579 valueMap.map(arg, blockArg);
580 }
581
582 // Step 4: For every struct field we a) create a signaldefop and b) add that signal to our
583 // outputs
584 DenseMap<StringRef, Value> fieldSignalMap;
585 uint32_t signalDefCntr = 0;
586 for (auto field : structDef.getFieldDefs()) {
587 r1cs::PublicAttr pubAttr;
588 if (field.hasPublicAttr()) {
589 pubAttr = bodyBuilder.getAttr<r1cs::PublicAttr>();
590 }
591 auto defOp = bodyBuilder.create<r1cs::SignalDefOp>(
592 field.getLoc(), bodyBuilder.getType<r1cs::SignalType>(),
593 bodyBuilder.getUI32IntegerAttr(signalDefCntr), pubAttr
594 );
595 signalDefCntr++;
596 fieldSignalMap.insert({field.getName(), defOp.getOut()});
597 }
598 DenseMap<std::tuple<Value, Value, StringRef>, Value> binaryOpCache;
599 // Step 5: Emit the R1CS constraints
600 for (const R1CSConstraint &constraint : constraints) {
601 Value aVal = emitLinearCombination(constraint.a, valueMap, fieldSignalMap, bodyBuilder, loc);
602 Value bVal = emitLinearCombination(constraint.b, valueMap, fieldSignalMap, bodyBuilder, loc);
603 Value cVal = emitLinearCombination(constraint.c, valueMap, fieldSignalMap, bodyBuilder, loc);
604 bodyBuilder.create<r1cs::ConstrainOp>(loc, aVal, bVal, cVal);
605 }
606 }
607
608 void getDependentDialects(mlir::DialectRegistry &registry) const override {
609 registry.insert<r1cs::R1CSDialect>();
610 }
611
612 void runOnOperation() override {
613 ModuleOp moduleOp = getOperation();
614 assert(
615 moduleOp->getContext()->getLoadedDialect<r1cs::R1CSDialect>() && "R1CS dialect not loaded"
616 );
617 moduleOp.walk([this, &moduleOp](StructDefOp structDef) {
618 FuncDefOp constrainFunc = structDef.getConstrainFuncOp();
619 FuncDefOp computeFunc = structDef.getComputeFuncOp();
620 if (!constrainFunc || !computeFunc) {
621 structDef.emitOpError("Missing compute or constrain function").report();
622 signalPassFailure();
623 return;
624 }
625
627 signalPassFailure();
628 return;
629 }
630
631 DenseMap<Value, unsigned> degreeMemo;
632 DenseMap<Value, Value> rewrites;
633 SmallVector<AuxAssignment> auxAssignments;
634
635 constrainFunc.walk([&](EmitEqualityOp eqOp) {
636 OpBuilder builder(eqOp);
637 Value lhs = normalizeForR1CS(
638 eqOp.getLhs(), structDef, constrainFunc, degreeMemo, rewrites, auxAssignments, builder
639 );
640 Value rhs = normalizeForR1CS(
641 eqOp.getRhs(), structDef, constrainFunc, degreeMemo, rewrites, auxAssignments, builder
642 );
643
644 unsigned degLhs = degreeMemo.lookup(lhs);
645 unsigned degRhs = degreeMemo.lookup(rhs);
646
647 // If both sides are degree 2, isolate one side
648 if (degLhs == 2 && degRhs == 2) {
649 builder.setInsertionPoint(eqOp);
650 std::string auxName = R1CS_AUXILIARY_FIELD_PREFIX + std::to_string(auxCounter++);
651 FieldDefOp auxField = addAuxField(structDef, auxName);
652 Value self = constrainFunc.getArgument(0);
653 Value aux = builder.create<FieldReadOp>(
654 eqOp.getLoc(), lhs.getType(), self, auxField.getNameAttr()
655 );
656 auto eqAux = builder.create<EmitEqualityOp>(eqOp.getLoc(), aux, lhs);
657 auxAssignments.push_back({auxName, lhs});
658 degreeMemo[aux] = 1;
659 replaceSubsequentUsesWith(lhs, aux, eqAux);
660 lhs = aux;
661 }
662
663 builder.create<EmitEqualityOp>(eqOp.getLoc(), lhs, rhs);
664 eqOp.erase();
665 });
666
667 Block &computeBlock = computeFunc.getBody().front();
668 OpBuilder builder(&computeBlock, computeBlock.getTerminator()->getIterator());
669 Value selfVal = getSelfValueFromCompute(computeFunc);
670 DenseMap<Value, Value> rebuildMemo;
671
672 for (const auto &assign : auxAssignments) {
673 Value expr = rebuildExprInCompute(assign.computedValue, computeFunc, builder, rebuildMemo);
674 builder.create<FieldWriteOp>(
675 assign.computedValue.getLoc(), selfVal, builder.getStringAttr(assign.auxFieldName), expr
676 );
677 }
678 buildAndEmitR1CS(moduleOp, structDef, constrainFunc, degreeMemo);
679 structDef.erase();
680 });
681 }
682};
683} // namespace
684
685std::unique_ptr<mlir::Pass> llzk::createR1CSLoweringPass() {
686 return std::make_unique<R1CSLoweringPass>();
687}
This file defines helpers for manipulating APInts/APSInts for large numbers and operations over those...
#define R1CS_AUXILIARY_FIELD_PREFIX
::std::vector< FieldDefOp > getFieldDefs()
Get all FieldDefOp in this structure.
Definition Ops.cpp:341
::llvm::StringRef getSymName()
Definition Ops.cpp.inc:1953
::llzk::function::FuncDefOp getConstrainFuncOp()
Gets the FuncDefOp that defines the constrain function in this structure, if present.
Definition Ops.cpp:357
::llzk::function::FuncDefOp getComputeFuncOp()
Gets the FuncDefOp that defines the compute function in this structure, if present.
Definition Ops.cpp:353
::mlir::Region & getBody()
Definition Ops.cpp.inc:848
static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type, ::llvm::ArrayRef<::mlir::NamedAttribute > attrs={})
bool hasArgPublicAttr(unsigned index)
Return true iff the argument at the given index has pub attribute.
Definition Ops.cpp:202
ExpressionValue sub(llvm::SMTSolverRef solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
Value rebuildExprInCompute(Value val, FuncDefOp computeFunc, OpBuilder &builder, DenseMap< Value, Value > &memo)
void replaceSubsequentUsesWith(Value oldVal, Value newVal, Operation *afterOp)
Value getSelfValueFromCompute(FuncDefOp computeFunc)
constexpr char LANG_ATTR_NAME[]
Name of the attribute on the top-level ModuleOp that specifies the IR language name.
Definition Constants.h:31
ExpressionValue add(llvm::SMTSolverRef solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
llvm::APSInt expandingAdd(const llvm::APSInt &lhs, const llvm::APSInt &rhs)
Safely add lhs and rhs, expanding the width of the result as necessary.
ExpressionValue mul(llvm::SMTSolverRef solver, const ExpressionValue &lhs, const ExpressionValue &rhs)
llvm::APSInt expandingMul(const llvm::APSInt &lhs, const llvm::APSInt &rhs)
Safely multiple lhs and rhs, expanding the width of the result as necessary.
LogicalResult checkForAuxFieldConflicts(StructDefOp structDef, StringRef prefix)
FieldDefOp addAuxField(StructDefOp structDef, StringRef name)
ExpressionValue neg(llvm::SMTSolverRef solver, const ExpressionValue &val)
std::unique_ptr< mlir::Pass > createR1CSLoweringPass()
mlir::Operation * create(MlirOpBuilder cBuilder, MlirLocation cLocation, Args &&...args)
Creates a new operation using an ODS build method.
Definition Builder.h:41