Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
trace_to_polynomials.cpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Completed, auditors: [Sergei], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
13
22namespace bb {
23
24template <class Flavor>
26{
27
28 BB_BENCH_NAME("trace populate");
29
30 auto copy_cycles = populate_wires_and_selectors_and_compute_copy_cycles(builder, polynomials);
31
32 if constexpr (Flavor::HasEccOpQueue) {
33 BB_BENCH_NAME("add_ecc_op_wires_to_prover_instance");
34
35 add_ecc_op_wires_to_prover_instance(builder, polynomials);
36 }
37
38 // Compute the permutation argument polynomials (sigma/id) and add them to proving key
39 {
40 BB_BENCH_NAME("compute_permutation_argument_polynomials");
41
42 compute_permutation_argument_polynomials<Flavor>(builder, polynomials, copy_cycles);
43 }
44}
45
46template <class Flavor>
48 Builder& builder, ProverPolynomials& polynomials)
49{
50
51 BB_BENCH_NAME("construct_trace_data");
52
53 CopyCycles copy_cycles; // at most one copy cycle per variable
54
55 RefArray<Polynomial, NUM_WIRES> wires = polynomials.get_wires();
56 auto selectors = polynomials.get_selectors();
57
58 // Two-phase parallelisation. Phase 1 fans out over blocks to populate wires and emit copy-cycle
59 // nodes; phase 2 fans out over a flattened (block, selector) task list to fill selectors.
60 auto blocks_array = builder.blocks.get();
61 const size_t num_blocks = blocks_array.size();
62
63 // Pre-pass: count copy-cycle sizes per real-variable index so each copy_cycles[i] can be
64 // reserve()d once before the serial concat in phase 1.5, avoiding repeated reallocations.
65 {
66 BB_BENCH_NAME("counting copy_cycles");
67 const size_t num_cycles = builder.get_num_variables();
68 std::vector<uint32_t> cycle_counts(builder.real_variable_index.size(), 0);
69 for (auto& block : blocks_array) {
70 const uint32_t block_size = static_cast<uint32_t>(block.size());
71 for (uint32_t block_row_idx = 0; block_row_idx < block_size; ++block_row_idx) {
72 for (uint32_t wire_idx = 0; wire_idx < NUM_WIRES; ++wire_idx) {
73 uint32_t var_idx = block.wires[wire_idx][block_row_idx];
74 // var_idx may be untrusted (e.g. from ACIR) so use .at() to catch OOB. This validates real_var_idx
75 // as an in-range index for both cycle_counts and the CSR offsets (same size), which is why phase
76 // 1.5 below can index the cursor array without .at().
77 ++cycle_counts.at(builder.real_variable_index.at(var_idx));
78 }
79 }
80 }
81 // Exclusive prefix sum of cycle counts gives the CSR offsets; a single flat node array
82 // replaces one heap-allocated vector per variable.
83 copy_cycles.offsets.resize(num_cycles + 1);
84 uint32_t running = 0;
85 for (size_t i = 0; i < num_cycles; ++i) {
86 copy_cycles.offsets[i] = running;
87 running += cycle_counts[i];
88 }
89 copy_cycles.offsets[num_cycles] = running;
90 copy_cycles.nodes.resize(running);
91 }
92
93 // Phase 1: per-block parallel pass over wires and emit copy-cycle nodes.
95 {
96 BB_BENCH_NAME("populate_wires_and_emit_cycles");
97 parallel_for(num_blocks, [&](size_t block_idx) {
98 auto& block = blocks_array[block_idx];
99 const uint32_t offset = block.trace_offset();
100 const uint32_t block_size = static_cast<uint32_t>(block.size());
101 auto& local_nodes = per_block_nodes[block_idx];
102 local_nodes.reserve(static_cast<size_t>(block_size) * NUM_WIRES);
103
104 // NB: The order of row/column loops is arbitrary but needs to be row/column to match old copy_cycle code.
105 for (uint32_t block_row_idx = 0; block_row_idx < block_size; ++block_row_idx) {
106 for (uint32_t wire_idx = 0; wire_idx < NUM_WIRES; ++wire_idx) {
107 uint32_t var_idx = block.wires[wire_idx][block_row_idx]; // an index into the variables array
108 // Use .at() so out-of-range var_idx is caught instead of producing a silent OOB read.
109 uint32_t real_var_idx = builder.real_variable_index.at(var_idx);
110 uint32_t trace_row_idx = block_row_idx + offset;
111 // Insert the real witness values from this block into the wire polys at the correct offset
112 wires[wire_idx].at(trace_row_idx) = builder.get_variable(var_idx);
113 local_nodes.emplace_back(real_var_idx, cycle_node{ wire_idx, trace_row_idx });
114 }
115 }
116 });
117 }
118
119 // Phase 1.5: Serial concat in block order to preserve cycle-node ordering within each variable's cycle list.
120 {
121 BB_BENCH_NAME("fill_copy_cycles");
122 std::vector<uint32_t> cursors(copy_cycles.offsets.begin(), copy_cycles.offsets.end() - 1);
123 for (const auto& block_nodes : per_block_nodes) {
124 for (const auto& [real_var_idx, node] : block_nodes) {
125 copy_cycles.nodes[cursors[real_var_idx]++] = node;
126 }
127 }
128 }
129
130 // Phase 2: parallel selector filling across a flattened task list.
131 // Non-gate selectors (q_m/q_c/q_l/q_r/q_o/q_4/q_5) come from every block via codegen-emitted
132 // `get_block_non_gate_selectors(block)`. Gate selectors come from the codegen's
133 // `get_gate_blocks(blocks)` × `GATE_KINDS` parallel arrays — one task per flavor gate
134 // selector, sourced from the unique block that owns the matching `GateKind`.
135 {
136 BB_BENCH_NAME("populate_selectors");
137 struct SelectorTask {
138 Selector<FF>* source;
139 size_t target_poly_idx;
140 uint32_t trace_offset;
141 uint32_t block_size;
142 };
143
144 const auto& polynomials_ref = polynomials;
145 const size_t num_non_gate = polynomials_ref.get_non_gate_selectors().size();
146
147 std::vector<SelectorTask> selector_tasks;
148 for (auto& block : blocks_array) {
149 const uint32_t offset = block.trace_offset();
150 const uint32_t block_size = static_cast<uint32_t>(block.size());
151 auto non_gate = Flavor::Generated::get_block_non_gate_selectors(block);
152 for (size_t i = 0; i < non_gate.size(); ++i) {
153 selector_tasks.emplace_back(SelectorTask{ &non_gate[i], i, offset, block_size });
154 }
155 }
156
157 auto gate_blocks = Flavor::Generated::get_gate_blocks(builder.blocks);
158 constexpr auto& gate_kinds = Flavor::Generated::GATE_KINDS;
159 for (size_t i = 0; i < gate_kinds.size(); ++i) {
160 auto& block = gate_blocks[i];
161 selector_tasks.emplace_back(SelectorTask{ &block.gate_selector_for(gate_kinds[i]),
162 num_non_gate + i,
163 block.trace_offset(),
164 static_cast<uint32_t>(block.size()) });
165 }
166
167 parallel_for(selector_tasks.size(), [&](size_t task_idx) {
168 const auto& task = selector_tasks[task_idx];
169 const auto& source = *task.source;
170 auto& poly = selectors[task.target_poly_idx];
171 // Bulk-copy the range that lands inside the polynomial's backing store (tile-wise, no
172 // per-element virtual call); rows clamped off by the store bounds would previously have
173 // been dropped by set_if_valid_index and must be zero.
174 const size_t dst_begin = std::max<size_t>(task.trace_offset, poly.start_index());
175 const size_t dst_end = std::min<size_t>(task.trace_offset + task.block_size, poly.end_index());
176 if (dst_begin < dst_end) {
177 source.copy_into(&poly.at(dst_begin), dst_begin - task.trace_offset, dst_end - dst_begin);
178 }
179 });
180 }
181
182 return copy_cycles;
183}
184
185template <class Flavor>
186void TraceToPolynomials<Flavor>::add_ecc_op_wires_to_prover_instance(Builder& builder, ProverPolynomials& polynomials)
187 requires Flavor::HasEccOpQueue
188{
189 auto& ecc_op_selector = polynomials.lagrange_ecc_op();
190
191 // The EccOpQueueRelation constrains ecc_op_wire[row] == w_shift[row] where lagrange_ecc_op == 1;
192 // equivalently, ecc_op_wire[row] == w[row + NUM_ZERO_ROWS], so we write ecc_op_wire starting at
193 // (ecc_op_block.trace_offset() - NUM_ZERO_ROWS).
194 const auto& ecc_op_block = builder.blocks.ecc_op;
195 const size_t wire_start = ecc_op_block.trace_offset();
196 BB_ASSERT_GTE(wire_start, NUM_ZERO_ROWS, "ecc_op block must start beyond the zero row");
197 const size_t op_wire_start = wire_start - NUM_ZERO_ROWS;
198 for (auto [ecc_op_wire, wire] : zip_view(polynomials.get_ecc_op_wires(), polynomials.get_wires())) {
199 for (size_t i = 0; i < ecc_op_block.size(); ++i) {
200 ecc_op_wire.at(op_wire_start + i) = wire[wire_start + i];
201 ecc_op_selector.at(op_wire_start + i) = 1;
202 }
203 }
204}
205
206template class TraceToPolynomials<UltraFlavor>;
209#ifdef STARKNET_GARAGA_FLAVORS
212#endif
214template class TraceToPolynomials<MegaFlavor>;
219
220} // namespace bb
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:128
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
A container for the prover polynomials.
A template class for a reference array. Behaves as if std::array<T&, N> was possible.
Definition ref_array.hpp:23
constexpr std::size_t size() const
Read (and targeted-write) interface over one selector column.
virtual size_t size() const =0
static CopyCycles populate_wires_and_selectors_and_compute_copy_cycles(Builder &builder, ProverPolynomials &)
Populate wire polynomials, selector polynomials and copy cycles from raw circuit data.
typename Flavor::CircuitBuilder Builder
static void populate(Builder &builder, ProverPolynomials &)
Given a circuit, populate a proving key with wire polys, selector polys, and sigma/id polys.
typename Flavor::ProverPolynomials ProverPolynomials
AluTraceBuilder builder
Definition alu.test.cpp:124
typename ECCVMFlavor::ProverPolynomials ProverPolynomials
ssize_t offset
Definition engine.cpp:62
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:112
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Copy cycles for all variables of a circuit in CSR (flat) form.
std::vector< cycle_node > nodes
std::vector< uint32_t > offsets
cycle_node represents the idx of a value of the circuit. It will belong to a CyclicPermutation,...