55 using size_t_for_storage = term::size_t_for_storage;
64 size_t subfield_boundary_;
69 size_t nwires_cse_eliminated_;
70 size_t nwires_not_needed_;
75 size_t nwires_overhead_;
77 explicit QuadCircuit(
const Field& f)
81 subfield_boundary_(0),
84 nwires_cse_eliminated_(0),
85 nwires_not_needed_(0),
88 nwires_overhead_(-1) {
91 size_t ki0 = kstore(f.zero());
92 proofs::check(ki0 == 0,
"ki0 == 0");
93 size_t ki1 = kstore(f.one());
94 proofs::check(ki1 == 1,
"ki1 == 1");
108 size_t linear(
size_t op0) {
return mul(0, op0); }
109 size_t linear(
const Elt& k,
size_t op0) {
return mul(k, 0, op0); }
111 size_t mul(
const Elt& k,
size_t op) {
112 if (k == f_.zero()) {
114 }
else if (k == f_.one() || nodes_[op].zero()) {
117 return push_node(scale(k, op));
121 size_t mul(
size_t op0,
size_t op1) {
return mul(f_.one(), op0, op1); }
123 size_t mul(
const Elt& k,
size_t op0,
size_t op1) {
124 const auto& n0 = nodes_[op0];
125 const auto& n1 = nodes_[op1];
129 }
else if (n0.constant()) {
131 return mul(f_.mulf(k, kload(n0.terms[0].ki)), op1);
132 }
else if (n0.linearp()) {
134 return mul(f_.mulf(k, kload(n0.terms[0].ki)), n0.terms[0].op1, op1);
135 }
else if (n1.zero() || n1.constant() || n1.linearp()) {
136 return mul(k, op1, op0);
139 return push_node(node(kstore(k), op0, op1));
143 size_t add(
size_t op0,
size_t op1) {
144 const auto& n0 = nodes_[op0];
145 const auto& n1 = nodes_[op1];
149 }
else if (n1.zero()) {
161 if (n0.info.depth < n1.info.depth) {
163 }
else if (n1.info.depth < n0.info.depth) {
166 return push_node(merge(op0, op1));
169 size_t sub(
size_t op0,
size_t op1) {
return add(op0, mul(f_.mone(), op1)); }
171 size_t konst(
const Elt& k) {
return push_node(node(kstore(k), 0, 0)); }
176 size_t assert0(
size_t op) {
177 const node* n = &nodes_[op];
183 }
else if (n->linearp()) {
188 if (n->terms[0].ki == 0) {
191 return assert0(n->terms[0].op1);
195 std::vector<term> terms;
196 terms.push_back(
term(op, hack));
197 size_t n1 = push_node(node(terms));
198 nodes_[n1].info.is_assert0 =
true;
206 size_t axpy(
size_t y,
const Elt& a,
size_t x) {
207 if (a == f_.zero()) {
210 return add(y, linear(a, x));
212 size_t apy(
size_t y,
const Elt& a) {
213 if (a == f_.zero()) {
216 return add(y, konst(a));
219 size_t input() {
return push_node(node(quad_corner_t(ninput_++))); }
223 void private_input() {
226 "private_input can only be called once after setting public inputs");
227 npub_input_ = ninput_;
233 void begin_full_field() {
234 proofs::check(subfield_boundary_ == 0,
235 "begin_full_field() can only be called once");
236 subfield_boundary_ = ninput_;
239 size_t ninput()
const {
return ninput_; }
241 void output(
size_t n,
size_t wire_id) {
242 output_internal(n, quad_corner_t(wire_id));
245 std::unique_ptr<Circuit<Field>> mkcircuit(
size_t nc) {
246 size_t depth_ub = compute_depth_ub();
247 fixup_last_layer_assertions(depth_ub);
248 compute_needed(depth_ub);
251 std::unique_ptr<Circuit<Field>> c =
252 sched.mkcircuit(constants_, depth_ub, nc);
255 nwires_ = sched.nwires_;
256 nquad_terms_ = sched.nquad_terms_;
257 nwires_overhead_ = sched.nwires_overhead_;
259 c->ninputs = ninput();
260 c->npub_in = npub_input_;
261 c->subfield_boundary = subfield_boundary_;
263 circuit_id(c->id, *c, f_);
268 void output_internal(
size_t n, quad_corner_t wire_id) {
269 nodes_[n].info.is_output =
true;
270 nodes_[n].info.desired_wire_id_for_output = wire_id;
274 size_t push_node(node n) {
277 uint64_t d = n.hash();
279 auto pred = [&](PdqHash::value_t op) {
return n == nodes_[op]; };
280 if (
size_t op = cse_.find(d, pred); op != PdqHash::kNil) {
284 ++nwires_cse_eliminated_;
291 for (
const auto& t : n.terms) {
292 n.info.depth = std::max<size_t>(
293 n.info.depth, 1 + std::max<size_t>(nodes_[t.op0].info.depth,
294 nodes_[t.op1].info.depth));
297 size_t nid = nodes_.size();
306 node materialize_input(
size_t op) {
307 if (nodes_[op].info.is_input) {
308 return node(1, 0, op);
314 node scale(
const Elt& k,
size_t op) {
315 node n = materialize_input(op);
316 for (
auto& t : n.terms) {
317 t.ki = kstore(f_.mulf(kload(t.ki), k));
322 void push_back_unless_zero(std::vector<term>& terms,
const term& t)
const {
328 node merge(
size_t op0,
size_t op1) {
329 const node n0 = materialize_input(op0);
330 const node n1 = materialize_input(op1);
331 const std::vector<term>& t0 = n0.terms;
332 const std::vector<term>& t1 = n1.terms;
333 std::vector<term> terms;
334 size_t i0 = 0, i1 = 0;
335 while (i0 < t0.size() && i1 < t1.size()) {
337 if (t0[i0].eqndx(t1[i1])) {
339 t.ki = kstore(f_.addf(kload(t.ki), kload(t1[i1].ki)));
342 }
else if (t0[i0].ltndx(t1[i1])) {
347 push_back_unless_zero(terms, t);
350 while (i0 < t0.size()) {
351 push_back_unless_zero(terms, t0[i0++]);
354 while (i1 < t1.size()) {
355 push_back_unless_zero(terms, t1[i1++]);
364 std::vector<Elt> constants_;
367 std::vector<node> nodes_;
370 size_t kstore(
const Elt& k) {
371 uint64_t d = elt_hash(k, f_);
372 auto pred = [&](PdqHash::value_t ki) {
return k == constants_[ki]; };
373 size_t ki = consttab_.find(d, pred);
375 if (ki == PdqHash::kNil) {
376 ki = constants_.size();
377 constants_.push_back(k);
378 consttab_.insert(d, ki);
382 Elt& kload(
size_t ki) {
return constants_[ki]; }
384 void mark_needed(
size_t op,
size_t depth_at_which_needed) {
385 nodeinfo* nfo = &nodes_[op].info;
386 nfo->is_needed =
true;
387 nfo->max_needed_depth =
388 std::max<size_t>(depth_at_which_needed, nfo->max_needed_depth);
393 if (depth_at_which_needed > nfo->depth + 1) {
394 nodeinfo* nfo0 = &nodes_[0].info;
395 nfo0->is_needed =
true;
396 nfo0->max_needed_depth =
397 std::max<size_t>(depth_at_which_needed - 1, nfo0->max_needed_depth);
401 size_t compute_depth_ub() {
403 for (
auto& n : nodes_) {
404 if (n.info.is_output) {
405 r = std::max<size_t>(r, 1 + n.info.depth);
406 }
else if (n.info.is_assert0) {
413 r = std::max<size_t>(r, n.info.depth);
415 r = std::max<size_t>(r, 1 + n.info.depth);
423 void fixup_last_layer_assertions(
size_t depth_ub) {
425 for (
auto& n : nodes_) {
426 if (!n.info.is_output && n.info.is_assert0 && n.info.depth == depth_ub &&
428 n.info.is_assert0 =
false;
429 output_internal(n.terms[0].op1, nodeinfo::kWireIdUndefined);
434 void compute_needed(
size_t depth_ub) {
435 nwires_not_needed_ = 0;
436 for (
size_t i = nodes_.size(); i-- > 0;) {
437 nodeinfo* nfo = &nodes_[i].info;
445 if (nfo->is_output) {
446 mark_needed(i, depth_ub);
449 if (nfo->is_assert0) {
450 mark_needed(i, nfo->depth + 1);
453 if (nfo->is_needed) {
454 for (
const auto& t : nodes_[i].terms) {
455 mark_needed(t.op0, nfo->depth);
456 mark_needed(t.op1, nfo->depth);
459 ++nwires_not_needed_;