Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
execution_trace_block.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Luke, Raju], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
15#include <bit>
16#include <cstddef>
17#include <cstring>
18#include <utility>
19
20#ifdef CHECK_CIRCUIT_STACKTRACES
21#include <backward.hpp>
22#endif
23
24namespace bb {
25
26#ifdef CHECK_CIRCUIT_STACKTRACES
27struct BbStackTrace : backward::StackTrace {
28 BbStackTrace() { load_here(32); }
29};
30struct StackTraces {
31 std::vector<BbStackTrace> stack_traces;
32 void populate() { stack_traces.emplace_back(); }
33 void print(size_t gate_idx) const { backward::Printer{}.print(stack_traces.at(gate_idx)); }
34 // Don't interfere with equality semantics of structs that include this in debug builds
35 bool operator==(const StackTraces& other) const
36 {
37 static_cast<void>(other);
38 return true;
39 }
40};
41#endif
42
64
74template <typename FF, size_t NUM_WIRES> struct GateRow {
75 std::array<uint32_t, NUM_WIRES> wires{};
85
86 bool operator==(const GateRow& other) const = default;
87};
88
89// Gates are stored in tiles of GATE_TILE_SIZE: within a tile each field is a small contiguous
90// array, so per-gate writes stay within one hot tile of sizeof(GateTile) bytes — roughly
91// GATE_TILE_SIZE * sizeof(GateRow), ~2.4KB for a 32-byte FF — while per-column reads see
92// contiguous multi-cache-line runs instead of one strided element per row (AoSoA).
93// GATE_TILE_SIZE is a benchmarked cache trade-off for the 32-byte bn254 FF, deliberately a fixed
94// constant rather than derived from sizeof(FF) so it cannot drift silently with the field type.
95static constexpr size_t GATE_TILE_SIZE = 8;
96static_assert(std::has_single_bit(GATE_TILE_SIZE));
97static constexpr size_t GATE_TILE_SHIFT = std::countr_zero(GATE_TILE_SIZE);
98static constexpr size_t GATE_TILE_MASK = GATE_TILE_SIZE - 1;
99
100template <typename FF, size_t NUM_WIRES> struct GateTile {
102 std::array<FF, GATE_TILE_SIZE> q_m{};
103 std::array<FF, GATE_TILE_SIZE> q_c{};
104 std::array<FF, GATE_TILE_SIZE> q_1{};
105 std::array<FF, GATE_TILE_SIZE> q_2{};
106 std::array<FF, GATE_TILE_SIZE> q_3{};
107 std::array<FF, GATE_TILE_SIZE> q_4{};
108 std::array<FF, GATE_TILE_SIZE> q_5{};
109 std::array<FF, GATE_TILE_SIZE> gate_value{};
110 std::array<GateKind, GATE_TILE_SIZE> gate_kind{};
111
112 // Gate-selector read semantics: a slot reads its gate_value for the kind it holds, zero for
113 // every other kind.
115 {
116 return gate_kind[slot] == kind ? gate_value[slot] : FF{ 0 };
117 }
118
119 bool operator==(const GateTile& other) const = default;
120};
121
125template <typename FF, size_t NUM_WIRES> struct NonGateSelectorField {
127 std::array<FF, GATE_TILE_SIZE> GateTile<FF, NUM_WIRES>::* tile_field;
128};
129
130// The single definition of the non-gate selector list and its order: the selector count,
131// append_gate's row->tile copies, and the block's column wiring all derive from this table.
132// The q_m()..q_5() accessors on ExecutionTraceBlock index columns_ in this order.
133template <typename FF, size_t NUM_WIRES>
143
150template <typename FF> class Selector {
151 public:
152 Selector() = default;
153 virtual ~Selector() = default;
154
155 Selector(const Selector&) = default;
156 Selector& operator=(const Selector&) = default;
157 Selector(Selector&&) = delete;
159
163 virtual void set(size_t idx, int value) = 0;
164
168 virtual void set(size_t idx, const FF& value) = 0;
169
173 virtual const FF& operator[](size_t index) const = 0;
174
178 virtual const FF& back() const = 0;
179
180 virtual size_t size() const = 0;
181 virtual bool empty() const = 0;
182
187 virtual void copy_into(FF* dst, size_t start, size_t count) const = 0;
188};
189
193template <typename FF, size_t NUM_WIRES> class SelectorColumn : public Selector<FF> {
194 public:
197 using Field = std::array<FF, GATE_TILE_SIZE> Tile::*;
198
199 SelectorColumn(Tiles* tiles, const size_t* num_rows, Field field)
200 : tiles_(tiles)
201 , num_rows_(num_rows)
202 , field_(field)
203 {}
204
205 void set(size_t idx, int value) override { at(idx) = value; }
206 void set(size_t idx, const FF& value) override { at(idx) = value; }
207
208 const FF& operator[](size_t i) const override
209 {
210 return ((*tiles_)[i >> GATE_TILE_SHIFT].*field_)[i & GATE_TILE_MASK];
211 }
212 const FF& back() const override { return (*this)[*num_rows_ - 1]; }
213
214 size_t size() const override { return *num_rows_; }
215 bool empty() const override { return *num_rows_ == 0; }
216
217 void copy_into(FF* dst, size_t start, size_t count) const override
218 {
219 size_t i = 0;
220 while (i < count && ((start + i) & GATE_TILE_MASK) != 0) {
221 dst[i] = (*this)[start + i];
222 ++i;
223 }
224 for (; i + GATE_TILE_SIZE <= count; i += GATE_TILE_SIZE) {
225 const auto& run = (*tiles_)[(start + i) >> GATE_TILE_SHIFT].*field_;
226 memcpy(static_cast<void*>(dst + i), static_cast<const void*>(run.data()), sizeof(FF) * GATE_TILE_SIZE);
227 }
228 for (; i < count; ++i) {
229 dst[i] = (*this)[start + i];
230 }
231 }
232
233 private:
234 FF& at(size_t i) { return ((*tiles_)[i >> GATE_TILE_SHIFT].*field_)[i & GATE_TILE_MASK]; }
235
237 const size_t* num_rows_;
239};
240
245template <typename FF, size_t NUM_WIRES> class GateSelectorColumn : public Selector<FF> {
246 public:
249
250 GateSelectorColumn(Tiles* tiles, const size_t* num_rows, GateKind kind)
251 : tiles_(tiles)
252 , num_rows_(num_rows)
253 , kind_(kind)
254 {}
255
256 void set(size_t idx, int value) override { set(idx, FF(value)); }
257 void set(size_t idx, const FF& value) override
258 {
259 Tile& tile = (*tiles_)[idx >> GATE_TILE_SHIFT];
260 const size_t slot = idx & GATE_TILE_MASK;
261 if (tile.gate_kind[slot] != kind_) {
262 // Only one gate kind may be active per row; claiming the row is only legal if no other
263 // kind holds a nonzero value on it.
264 BB_ASSERT(tile.gate_value[slot] == FF{ 0 },
265 "GateSelectorColumn: overwriting an active gate selector of another kind");
266 tile.gate_kind[slot] = kind_;
267 }
268 tile.gate_value[slot] = value;
269 }
270
271 const FF& operator[](size_t i) const override
272 {
273 const Tile& tile = (*tiles_)[i >> GATE_TILE_SHIFT];
274 const size_t slot = i & GATE_TILE_MASK;
275 return tile.gate_kind[slot] == kind_ ? tile.gate_value[slot] : zero_value();
276 }
277 const FF& back() const override { return (*this)[*num_rows_ - 1]; }
278
279 size_t size() const override { return *num_rows_; }
280 bool empty() const override { return *num_rows_ == 0; }
281
282 void copy_into(FF* dst, size_t start, size_t count) const override
283 {
284 size_t i = 0;
285 while (i < count && ((start + i) & GATE_TILE_MASK) != 0) {
286 dst[i] = (*this)[start + i];
287 ++i;
288 }
289 for (; i + GATE_TILE_SIZE <= count; i += GATE_TILE_SIZE) {
290 const Tile& tile = (*tiles_)[(start + i) >> GATE_TILE_SHIFT];
291 constexpr_for<0, GATE_TILE_SIZE, 1>([&]<size_t k>() { dst[i + k] = tile.gate_selector_or_zero(k, kind_); });
292 }
293 for (; i < count; ++i) {
294 dst[i] = (*this)[start + i];
295 }
296 }
297
298 GateKind kind() const { return kind_; }
299
300 private:
301 static const FF& zero_value()
302 {
303 static const FF zero{};
304 return zero;
305 }
306
308 const size_t* num_rows_;
310};
311
315template <typename FF, size_t NUM_WIRES> class WireColumn {
316 public:
319
320 WireColumn(Tiles* tiles, const size_t* num_rows, size_t wire_idx)
321 : tiles_(tiles)
322 , num_rows_(num_rows)
323 , wire_idx_(wire_idx)
324 {}
325
326 uint32_t& operator[](size_t i) { return (*tiles_)[i >> GATE_TILE_SHIFT].wires[wire_idx_][i & GATE_TILE_MASK]; }
327 const uint32_t& operator[](size_t i) const
328 {
329 return (*tiles_)[i >> GATE_TILE_SHIFT].wires[wire_idx_][i & GATE_TILE_MASK];
330 }
331 uint32_t& back() { return (*this)[*num_rows_ - 1]; }
332 const uint32_t& back() const { return (*this)[*num_rows_ - 1]; }
333 size_t size() const { return *num_rows_; }
334 bool empty() const { return *num_rows_ == 0; }
335
336 // Minimal iterator support for range-for over a wire column.
338 public:
339 const_iterator(const WireColumn* col, size_t i)
340 : col_(col)
341 , i_(i)
342 {}
343 const uint32_t& operator*() const { return (*col_)[i_]; }
345 {
346 ++i_;
347 return *this;
348 }
349 bool operator!=(const const_iterator& other) const { return i_ != other.i_; }
350
351 private:
353 size_t i_;
354 };
355 const_iterator begin() const { return { this, 0 }; }
356 const_iterator end() const { return { this, size() }; }
357
358 private:
360 const size_t* num_rows_;
361 size_t wire_idx_;
362};
363
368template <typename FF, size_t NUM_WIRES_> class ExecutionTraceBlock {
369 public:
370 static constexpr size_t NUM_WIRES = NUM_WIRES_;
371 static constexpr size_t NUM_NON_GATE_SELECTORS = NON_GATE_SELECTORS<FF, NUM_WIRES>.size();
372
377 using Wires = std::array<WireType, NUM_WIRES>;
378
380
385 {
386 gate_columns_.reserve(kinds.size());
387 for (GateKind k : kinds) {
388 gate_columns_.emplace_back(&tiles, &num_rows_, k);
389 }
390 }
391
392 // The column views alias this block's row storage; copies and moves rebind them to their own storage.
395 , data_freed_(other.data_freed_)
397 , tiles(other.tiles)
398 , num_rows_(other.num_rows_)
399 {
401 }
403 {
404 if (this == &other) {
405 return *this;
406 }
408 data_freed_ = other.data_freed_;
410 tiles = other.tiles;
411 num_rows_ = other.num_rows_;
413 return *this;
414 }
416 : cached_size_(other.cached_size_)
417 , data_freed_(other.data_freed_)
418 , trace_offset_(other.trace_offset_)
419 , tiles(std::move(other.tiles))
420 , num_rows_(other.num_rows_)
421 {
423 }
425 {
427 data_freed_ = other.data_freed_;
428 trace_offset_ = other.trace_offset_;
429 tiles = std::move(other.tiles);
430 num_rows_ = other.num_rows_;
432 return *this;
433 }
435
436#ifdef CHECK_CIRCUIT_STACKTRACES
437 // If enabled, we keep slow stack traces to be able to correlate gates with code locations where they were added
438 StackTraces stack_traces;
439#endif
440#ifdef TRACY_HACK_GATES_AS_MEMORY
441 std::vector<size_t> allocated_gates;
442#endif
444 {
445#ifdef TRACY_HACK_GATES_AS_MEMORY
446 std::unique_lock<std::mutex> lock(GLOBAL_GATE_MUTEX);
447 GLOBAL_GATE++;
448 TRACY_GATE_ALLOC(GLOBAL_GATE);
449 allocated_gates.push_back(GLOBAL_GATE);
450#endif
451 }
452
453 size_t cached_size_ = 0; // set by free_data() so size() works after freeing
454 bool data_freed_ = false; // true after free_data() has been called
455 uint32_t trace_offset_ = std::numeric_limits<uint32_t>::max(); // where this block starts in the trace
456
457 uint32_t trace_offset() const
458 {
459 BB_ASSERT(trace_offset_ != std::numeric_limits<uint32_t>::max());
460 return trace_offset_;
461 }
462
463 // The first trace row past this block's data (trace_offset + size).
464 size_t trace_end() const { return trace_offset() + size(); }
465
466 bool operator==(const ExecutionTraceBlock& other) const
467 {
468 return cached_size_ == other.cached_size_ && data_freed_ == other.data_freed_ &&
469 trace_offset_ == other.trace_offset_ && num_rows_ == other.num_rows_ && tiles == other.tiles &&
471 }
472
473 size_t size() const { return data_freed_ ? cached_size_ : num_rows_; }
474
478 void append_gate(const Row& row)
479 {
480#ifdef CHECK_CIRCUIT_STACKTRACES
481 this->stack_traces.populate();
482#endif
483 this->tracy_gate();
485 "ExecutionTraceBlock: block does not own this gate kind.");
486 Tile& tile = tile_for_append();
487 const size_t slot = num_rows_ & GATE_TILE_MASK;
488 for (size_t w = 0; w < NUM_WIRES; ++w) {
489 tile.wires[w][slot] = row.wires[w];
490 }
491 for (const auto& sel : NON_GATE_SELECTORS<FF, NUM_WIRES>) {
492 (tile.*sel.tile_field)[slot] = row.*sel.row_field;
493 }
494 tile.gate_kind[slot] = row.gate_kind;
495 tile.gate_value[slot] = row.gate_value;
496 ++num_rows_;
497 }
498
502 void reserve(size_t num_rows) { tiles.reserve((num_rows + GATE_TILE_SIZE - 1) >> GATE_TILE_SHIFT); }
503
504 bool owns_gate_kind(GateKind kind) const
505 {
506 for (const auto& col : gate_columns_) {
507 if (col.kind() == kind) {
508 return true;
509 }
510 }
511 return false;
512 }
513
515 {
517 kinds.reserve(gate_columns_.size());
518 for (const auto& col : gate_columns_) {
519 kinds.push_back(col.kind());
520 }
521 return kinds;
522 }
523
529 {
530 for (auto& col : gate_columns_) {
531 if (col.kind() == kind) {
532 return col;
533 }
534 }
535 throw_or_abort("ExecutionTraceBlock: block does not own this gate kind");
536 return gate_columns_[0]; // unreachable
537 }
538
543 {
545 ptrs.reserve(columns_.size() + gate_columns_.size());
546 for (auto& s : columns_) {
547 ptrs.push_back(&s);
548 }
549 for (auto& s : gate_columns_) {
550 ptrs.push_back(&s);
551 }
552 return RefVector<Selector<FF>>(ptrs);
553 }
554
555#ifdef TRACY_HACK_GATES_AS_MEMORY
557 {
558 std::unique_lock<std::mutex> lock(GLOBAL_GATE_MUTEX);
559 for ([[maybe_unused]] size_t gate : allocated_gates) {
560 if (!FREED_GATES.contains(gate)) {
561 TRACY_GATE_FREE(gate);
562 FREED_GATES.insert(gate);
563 }
564 }
565 }
566#endif
567
573 {
575 data_freed_ = true;
576 tiles.clear();
577 tiles.shrink_to_fit();
578 num_rows_ = 0;
579 }
580
581 WireType& w_l() { return wires[0]; };
582 WireType& w_r() { return wires[1]; };
583 WireType& w_o() { return wires[2]; };
584 WireType& w_4() { return wires[3]; };
585
586 // Accessor indices follow NON_GATE_SELECTORS table order.
594
596 size_t num_rows_ = 0;
597
598 // Wire column views; rebound on copy/move alongside the selector views.
600 WireType{ &tiles, &num_rows_, 1 },
601 WireType{ &tiles, &num_rows_, 2 },
602 WireType{ &tiles, &num_rows_, 3 } };
603
604 private:
606 {
607 for (size_t i = 0; i < NUM_WIRES; ++i) {
608 wires[i] = WireType{ &tiles, &num_rows_, i };
609 }
610 gate_columns_.clear();
611 gate_columns_.reserve(other.gate_columns_.size());
612 for (const auto& col : other.gate_columns_) {
613 gate_columns_.emplace_back(&tiles, &num_rows_, col.kind());
614 }
615 }
616
618 {
619 if ((num_rows_ & GATE_TILE_MASK) == 0 && (num_rows_ >> GATE_TILE_SHIFT) == tiles.size()) {
620 return tiles.emplace_back();
621 }
622 return tiles[num_rows_ >> GATE_TILE_SHIFT];
623 }
624
625 template <size_t... Is>
631
632 // Column views into the tiles, one per NON_GATE_SELECTORS entry in table order; rebound on
633 // copy/move (see the copy/move constructors).
636
638};
639
645template <typename FF, size_t NUM_WIRES>
647{
648 if (idx >= block.size()) {
649 return FF{ 0 };
650 }
651 const auto& tile = block.tiles[idx >> GATE_TILE_SHIFT];
652 const size_t slot = idx & GATE_TILE_MASK;
653 return tile.gate_selector_or_zero(slot, kind);
654}
655
656} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_DEBUG(expression,...)
Definition assert.hpp:55
bb::field< bb::Bn254FrParams > FF
Definition field.cpp:24
Row-major storage for the gates of one execution trace block: one vector of GateRow....
std::array< WireType, NUM_WIRES > Wires
SelectorColumn< FF, NUM_WIRES > & q_5()
static constexpr size_t NUM_WIRES
std::vector< GateKind > owned_gate_kinds() const
bool operator==(const ExecutionTraceBlock &other) const
SelectorColumn< FF, NUM_WIRES > & q_3()
SelectorColumn< FF, NUM_WIRES > & q_c()
ExecutionTraceBlock & operator=(ExecutionTraceBlock &&other) noexcept
void reserve(size_t num_rows)
Reserve capacity for num_rows gates.
SelectorColumn< FF, NUM_WIRES > & q_m()
GateSelectorColumn< FF, NUM_WIRES > & gate_selector_for(GateKind kind)
Reference to this block's selector view for kind; aborts if the block does not own it....
void copy_gate_columns_from(const ExecutionTraceBlock &other)
RefVector< Selector< FF > > get_selectors()
All selectors of this block: the non-gate selectors followed by the owned gate selectors.
void free_data()
Release gate memory. Caches block size so size() still works.
std::vector< GateSelectorColumn< FF, NUM_WIRES > > gate_columns_
ExecutionTraceBlock(ExecutionTraceBlock &&other) noexcept
~ExecutionTraceBlock()=default
std::array< SelectorColumn< FF, NUM_WIRES >, NUM_NON_GATE_SELECTORS > columns_
static constexpr size_t NUM_NON_GATE_SELECTORS
ExecutionTraceBlock(std::initializer_list< GateKind > kinds)
Construct a block that owns the listed gate kinds.
SelectorColumn< FF, NUM_WIRES > & q_2()
void append_gate(const Row &row)
Append one complete gate: wires, non-gate selectors, and the (single) active gate selector.
ExecutionTraceBlock & operator=(const ExecutionTraceBlock &other)
SelectorColumn< FF, NUM_WIRES > & q_1()
std::array< SelectorColumn< FF, NUM_WIRES >, NUM_NON_GATE_SELECTORS > make_selector_columns(std::index_sequence< Is... >)
bool owns_gate_kind(GateKind kind) const
ExecutionTraceBlock(const ExecutionTraceBlock &other)
SelectorColumn< FF, NUM_WIRES > & q_4()
Column view over the gate selector of one kind in the row-major gate storage: rows whose gate_kind ma...
const FF & operator[](size_t i) const override
Get value at specified index.
GateSelectorColumn(Tiles *tiles, const size_t *num_rows, GateKind kind)
void set(size_t idx, int value) override
Set the value at index using integer.
void set(size_t idx, const FF &value) override
Set the value at index using a field element.
void copy_into(FF *dst, size_t start, size_t count) const override
Bulk-copy count values of this column starting at start into dst (tile-wise, avoiding a virtual call ...
size_t size() const override
const FF & back() const override
Get the last value in the selector.
A template class for a reference vector. Behaves as if std::vector<T&> was possible.
Column view over one non-gate selector field of the row-major gate storage.
bool empty() const override
const FF & operator[](size_t i) const override
Get value at specified index.
void set(size_t idx, const FF &value) override
Set the value at index using a field element.
GateTile< FF, NUM_WIRES > Tile
void copy_into(FF *dst, size_t start, size_t count) const override
Bulk-copy count values of this column starting at start into dst (tile-wise, avoiding a virtual call ...
SelectorColumn(Tiles *tiles, const size_t *num_rows, Field field)
std::array< FF, GATE_TILE_SIZE > Tile::* Field
std::vector< Tile > Tiles
const FF & back() const override
Get the last value in the selector.
void set(size_t idx, int value) override
Set the value at index using integer.
size_t size() const override
Read (and targeted-write) interface over one selector column.
virtual size_t size() const =0
virtual void copy_into(FF *dst, size_t start, size_t count) const =0
Bulk-copy count values of this column starting at start into dst (tile-wise, avoiding a virtual call ...
virtual ~Selector()=default
virtual const FF & back() const =0
Get the last value in the selector.
Selector(Selector &&)=delete
virtual void set(size_t idx, int value)=0
Set the value at index using integer.
virtual bool empty() const =0
Selector & operator=(const Selector &)=default
Selector & operator=(Selector &&)=delete
virtual void set(size_t idx, const FF &value)=0
Set the value at index using a field element.
virtual const FF & operator[](size_t index) const =0
Get value at specified index.
Selector()=default
Selector(const Selector &)=default
bool operator!=(const const_iterator &other) const
const_iterator(const WireColumn *col, size_t i)
Mutable view over one wire column of the row-major gate storage.
const_iterator end() const
WireColumn(Tiles *tiles, const size_t *num_rows, size_t wire_idx)
const_iterator begin() const
const uint32_t & back() const
const uint32_t & operator[](size_t i) const
std::vector< Tile > Tiles
uint32_t & operator[](size_t i)
#define TRACY_GATE_ALLOC(t)
Definition mem.hpp:16
#define TRACY_GATE_FREE(t)
Definition mem.hpp:17
bool operator==(schnorr_signature const &lhs, schnorr_signature const &rhs)
Definition schnorr.hpp:38
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
constexpr std::array NON_GATE_SELECTORS
FF read_gate_selector(const ExecutionTraceBlock< FF, NUM_WIRES > &block, GateKind kind, size_t idx)
Gate-selector value at (block, idx) for kind, returning zero if the block does not own this kind or t...
GateKind
Tag identifying which gate selector a block owns. Used by cross-block readers to decide whether (bloc...
@ Poseidon2QuadIntTerminal
@ Poseidon2TransitionEntry
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
One gate: its wire indices, the non-gate selectors present on every block (see NON_GATE_SELECTORS),...
bool operator==(const GateRow &other) const =default
std::array< uint32_t, NUM_WIRES > wires
std::array< FF, GATE_TILE_SIZE > q_m
std::array< FF, GATE_TILE_SIZE > gate_value
std::array< FF, GATE_TILE_SIZE > q_c
FF gate_selector_or_zero(size_t slot, GateKind kind) const
std::array< GateKind, GATE_TILE_SIZE > gate_kind
std::array< FF, GATE_TILE_SIZE > q_4
bool operator==(const GateTile &other) const =default
std::array< std::array< uint32_t, GATE_TILE_SIZE >, NUM_WIRES > wires
std::array< FF, GATE_TILE_SIZE > q_1
std::array< FF, GATE_TILE_SIZE > q_3
std::array< FF, GATE_TILE_SIZE > q_2
std::array< FF, GATE_TILE_SIZE > q_5
The (GateRow field, GateTile field) pair of one non-gate selector.
void throw_or_abort(std::string const &err)