Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
affine_add_packed.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <algorithm>
8#include <cstddef>
9#include <cstdint>
10#include <utility>
11
13
14// Scratch buffers for batch_affine_add, one VectorFieldPushSpan each: the per-element denominators
15// dx = x2 - x1, numerators dy = y2 - y1, coordinate sums xsum = x1 + x2, and inverted denominators
16// inv. All four must be distinct from one another and from the point spans, and inv must not alias dx
17// (batch_invert cannot invert in place).
24
25// out[i] = lhs[i] + rhs[i] for every i, where each lhs[i] and rhs[i] are distinct, non-opposite affine
26// points — the "unsafe" affine add (no points at infinity, no doubling). Three passes:
27//
28// prep: dx = x2 - x1, dy = y2 - y1, xsum = x1 + x2 (per element, via zip_for_each)
29// batch_invert: turn every dx into 1 / dx with a single field inversion
30// finish: slope = dy / dx; x3 = slope^2 - xsum; y3 = slope * (x1 - x3) - y1
31//
32// lhs, rhs and out share one shape (same element count). finish forms x3 and y3 before writing either
33// output, so out may alias lhs and/or rhs. The scratch spans must be distinct from everything. Equal
34// x-coordinates (a zero dx) abort in batch_invert.
35template <typename Params>
40{
42 lhs.y,
43 rhs.x,
44 rhs.y,
45 s.dx,
46 s.dy,
47 s.xsum,
48 [](const auto& x1, const auto& y1, const auto& x2, const auto& y2, auto& dx, auto& dy, auto& xsum) {
49 dx = x2 - x1;
50 dy = y2 - y1;
51 xsum = x1 + x2;
52 });
53 batch_invert(s.dx, s.inv);
54
56 lhs.x,
57 lhs.y,
58 s.dy,
59 s.inv,
60 s.xsum,
61 out.x,
62 out.y,
63 [](const auto& x1, const auto& y1, const auto& dy, const auto& inv, const auto& xsum, auto& ox, auto& oy) {
64 auto slope = dy * inv;
65 auto x3 = slope * slope - xsum;
66 auto y3 = slope * (x1 - x3) - y1;
67 ox = x3;
68 oy = y3;
69 });
70}
71
72// Working buffers for batch_affine_double, one VectorFieldPushSpan each: the per-element denominators
73// den = 2y, numerators num = 3x^2, and inverted denominators inv. All three must be distinct from one
74// another and from the point spans, and inv must not alias den (batch_invert cannot invert in place).
80
81// Inner kernel of the Stage 6b reduction's doubling pass, driven by batch_affine_double_indexed_packed.
82//
83// out[i] = 2 * in[i] for every i, where each in[i] is a finite affine point with y != 0 — the
84// "unsafe" affine doubling (no point at infinity, no y == 0). Three passes:
85//
86// prep: den = 2y, num = 3x^2 (per element, via zip_for_each)
87// batch_invert: turn every den into 1 / den with a single field inversion
88// finish: slope = num / den; x3 = slope^2 - 2x; y3 = slope * (x - x3) - y
89//
90// in and out share one shape (same element count). finish forms x3 and y3 before writing either
91// output, so out may alias in. The scratch spans must be distinct from everything. A zero y (a zero
92// denominator) aborts in batch_invert.
93template <typename Params>
97{
98 zip_for_each(in.x, in.y, s.den, s.num, [](const auto& x, const auto& y, auto& den, auto& num) {
99 den = y + y;
100 const auto xx = x * x;
101 num = xx + xx + xx;
102 });
103 batch_invert(s.den, s.inv);
104
105 zip_for_each(in.x,
106 in.y,
107 s.num,
108 s.inv,
109 out.x,
110 out.y,
111 [](const auto& x, const auto& y, const auto& num, const auto& inv, auto& ox, auto& oy) {
112 auto slope = num * inv;
113 auto x3 = slope * slope - (x + x);
114 auto y3 = slope * (x - x3) - y;
115 ox = x3;
116 oy = y3;
117 });
118}
119
120// The four indexed bucket-reduce kernels below (add/double × scalar/packed) compute the same affine
121// arithmetic and differ ONLY in memory movement: the _scalar variants mutate the scattered SoA bucket
122// slots in place (one field at a time), while the _packed variants gather the indexed operands into
123// contiguous VectorFields, run the SIMD kernel above, and scatter the results back. The gather/scatter
124// bridge only earns its cost when the arithmetic is SIMD-wide, so the caller runs _packed only on
125// single-threaded WASM-SIMD MSMs and _scalar everywhere else (native, and multi-threaded WASM where
126// SIMD-6b is gated off).
127
128// Scalar SoA twin of batch_affine_add_indexed_packed (and of the AoS batch_affine_add_indexed_impl):
129// buckets[pairs[k].first] += buckets[pairs[k].second] over a column view, with one field inversion for
130// the whole batch (Montgomery's trick). This is the native path of the Stage 6b reduction; the WASM
131// path is the packed wrapper. `scratch` holds num_pairs field elements. Same preconditions as the
132// packed wrapper (clean pre-filtered pairs, distinct dsts disjoint from srcs).
133template <typename Field>
136 size_t num_pairs,
137 Field* scratch) noexcept
138{
139 if (num_pairs == 0) {
140 return;
141 }
142 Field acc = Field::one();
143
144 // Forward pass: build the batch-inversion product, treating dst as point 2, src as point 1.
145 for_each_indexed_pair<Direction::Forward>(
146 buckets, pairs, num_pairs, [&](Field& x_dst, Field& y_dst, const Field& x_src, const Field& y_src, size_t i) {
147 scratch[i] = x_src + x_dst; // x1 + x2 (saved for the backward pass)
148 x_dst -= x_src; // x2 - x1 (denominator)
149 y_dst -= y_src; // y2 - y1 (numerator before scaling)
150 y_dst *= acc;
151 acc *= x_dst;
152 });
153
154 if (acc == Field::zero()) {
155 throw_or_abort("attempted to invert zero in batch_affine_add_indexed_scalar");
156 }
157 acc = acc.invert();
158
159 // Backward pass: complete each slope and write x3, y3 into the dst slot.
160 for_each_indexed_pair<Direction::Backward>(
161 buckets, pairs, num_pairs, [&](Field& x_dst, Field& y_dst, const Field& x_src, const Field& y_src, size_t i) {
162 y_dst *= acc; // slope = (y2 - y1) / (x2 - x1)
163 acc *= x_dst; // unwind the running inverse (x_dst still holds x2 - x1)
164 x_dst = y_dst.sqr();
165 x_dst -= scratch[i]; // x3 = slope^2 - (x1 + x2)
166 Field temp = x_src - x_dst;
167 temp *= y_dst;
168 y_dst = temp - y_src; // y3 = slope * (x1 - x3) - y1
169 });
170}
171
172// Scalar SoA twin of batch_affine_double_indexed_packed: buckets[indices[k]] = 2 * buckets[indices[k]]
173// over a column view, one field inversion for the whole batch. Native path of the Stage 6b doublings.
174template <typename Field>
176 const uint32_t* indices,
177 size_t num_points,
178 Field* scratch) noexcept
179{
180 if (num_points == 0) {
181 return;
182 }
183 Field acc = Field::one();
184
185 for_each_indexed_point<Direction::Forward>(buckets, indices, num_points, [&](Field& x, Field& y, size_t i) {
186 scratch[i] = x.sqr();
187 scratch[i] = scratch[i] + scratch[i] + scratch[i]; // 3 x^2
188 scratch[i] *= acc;
189 acc *= (y + y); // 2y
190 });
191
192 if (acc == Field::zero()) {
193 throw_or_abort("attempted to invert zero in batch_affine_double_indexed_scalar");
194 }
195 acc = acc.invert();
196
197 for_each_indexed_point<Direction::Backward>(buckets, indices, num_points, [&](Field& x, Field& y, size_t i) {
198 scratch[i] *= acc; // slope = 3x^2 / 2y
199 acc *= (y + y);
200 const Field tx = x;
201 x = scratch[i].sqr() - (x + x); // x3 = slope^2 - 2x
202 y = scratch[i] * (tx - x) - y; // y3 = slope * (x - x3) - y
203 });
204}
205
206// buckets[pairs[k].first] += buckets[pairs[k].second] for every pair, with `buckets` a column (SoA)
207// view: gather the two operand points by index into packed VectorFields, run batch_affine_add, scatter
208// each sum back to its dst index. The WASM-SIMD replacement for the scalar batch_affine_add_indexed_impl
209// — same contract, but the field arithmetic runs W elements at a time and the column layout lets the
210// gather/scatter share one index array per coordinate.
211//
212// Preconditions (identical to the scalar impl, guaranteed by the caller's try_filter_pair): every
213// referenced bucket is a finite point, each pair's two points have distinct x (dx != 0, so no doubling
214// or inverse), the dst indices pairs[*].first are distinct, and the dst set is disjoint from the src
215// set. gather_from reads all operands before scatter_to writes anything, so the in-place mutation is
216// hazard-free regardless of overlap.
217//
218// lhs / rhs / out are caller-owned scratch coordinate spans (out may share lhs's backing); the wrapper
219// reset()s and refills them. Their capacity bounds the chunk size — a num_pairs larger than capacity is
220// processed in capacity-sized chunks, one batch_affine_add (hence one field inversion) per chunk.
221template <typename Params>
224 size_t num_pairs,
228 BatchAffineAddScratch<Params>& scratch) noexcept
229{
230 if (num_pairs == 0) {
231 return;
232 }
233 const size_t capacity = lhs.capacity();
234 BB_ASSERT(capacity > 0);
235
236 for (size_t span_base = 0; span_base < num_pairs; span_base += capacity) {
237 const size_t chunk = std::min(capacity, num_pairs - span_base);
238 lhs.gather_from(buckets, [&](size_t k) { return pairs[span_base + k].first; }, chunk);
239 rhs.gather_from(buckets, [&](size_t k) { return pairs[span_base + k].second; }, chunk);
240 batch_affine_add(lhs, rhs, out, scratch);
241 out.scatter_to(buckets, [&](size_t k) { return pairs[span_base + k].first; });
242 }
243}
244
245// buckets[indices[k]] = 2 * buckets[indices[k]] for every index, with `buckets` a column (SoA) view:
246// gather each point by index into a packed VectorField, run batch_affine_double, scatter the result
247// back. The WASM-SIMD replacement for the scalar batch_affine_double_indexed_impl.
248//
249// Preconditions (identical to the scalar impl): every referenced bucket is a finite point with y != 0,
250// and `indices` contains no duplicates. Same chunking and scratch-ownership rules as
251// batch_affine_add_indexed_packed.
252template <typename Params>
254 const uint32_t* indices,
255 size_t num_points,
258 BatchAffineDoubleScratch<Params>& scratch) noexcept
259{
260 if (num_points == 0) {
261 return;
262 }
263 const size_t capacity = in.capacity();
264 BB_ASSERT(capacity > 0);
265
266 for (size_t span_base = 0; span_base < num_points; span_base += capacity) {
267 const size_t chunk = std::min(capacity, num_points - span_base);
268 in.gather_from(buckets, [&](size_t k) { return indices[span_base + k]; }, chunk);
269 batch_affine_double(in, out, scratch);
270 out.scatter_to(buckets, [&](size_t k) { return indices[span_base + k]; });
271 }
272}
273
274} // namespace bb::group_elements
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
AffineElement const size_t num_pairs
void batch_affine_add_indexed_scalar(AffineColumnSpan< Field > &buckets, const std::pair< uint32_t, uint32_t > *pairs, size_t num_pairs, Field *scratch) noexcept
const size_t num_points
const uint32_t * indices
void batch_affine_add(const VectorAffineElementPushSpan< Params > &lhs, const VectorAffineElementPushSpan< Params > &rhs, VectorAffineElementPushSpan< Params > &out, BatchAffineAddScratch< Params > &s) noexcept
AffineElement * rhs
void batch_affine_double(const VectorAffineElementPushSpan< Params > &in, VectorAffineElementPushSpan< Params > &out, BatchAffineDoubleScratch< Params > &s) noexcept
void batch_affine_double_indexed_scalar(AffineColumnSpan< Field > &buckets, const uint32_t *indices, size_t num_points, Field *scratch) noexcept
void batch_affine_double_indexed_packed(AffineColumnSpan< typename VectorField< Params >::Field > &buckets, const uint32_t *indices, size_t num_points, VectorAffineElementPushSpan< Params > &in, VectorAffineElementPushSpan< Params > &out, BatchAffineDoubleScratch< Params > &scratch) noexcept
const std::pair< uint32_t, uint32_t > * pairs
void batch_affine_add_indexed_packed(AffineColumnSpan< typename VectorField< Params >::Field > &buckets, const std::pair< uint32_t, uint32_t > *pairs, size_t num_pairs, VectorAffineElementPushSpan< Params > &lhs, VectorAffineElementPushSpan< Params > &rhs, VectorAffineElementPushSpan< Params > &out, BatchAffineAddScratch< Params > &scratch) noexcept
void zip_for_each(Args &&... args)
void batch_invert(const VectorFieldPushSpan< Params > &in, VectorFieldPushSpan< Params > &out) noexcept
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
bb::VectorAffineElementPushSpan< BaseParams > lhs
bb::VectorAffineElementPushSpan< BaseParams > out
void throw_or_abort(std::string const &err)