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