Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
trace_container.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
10
11namespace bb::avm2::tracegen {
12namespace {
13
14// We need a zero value to return (a reference to) when a value is not found.
15const FF zero = FF::zero();
16
17// Writes each 64-bit limb of the field with a relaxed atomic store. Each limb is naturally aligned (FF is
18// alignas(32), 4x uint64_t), so this lowers to 4 plain `movq` stores on x86-64 — no lock, no libatomic call
19// (unlike a whole-field std::atomic_ref<FF>, whose 32 bytes exceed the lock-free width). Lets set() make a
20// same-cell concurrent write data-race-free when every writer stores the same value.
21inline void store_per_limb(FF& cell, const FF& value)
22{
23 static_assert(sizeof(FF) == 4 * sizeof(uint64_t));
24 for (size_t i = 0; i < 4; ++i) {
26 }
27}
28
29} // namespace
30
34
35const FF& TraceContainer::get(Column col, uint32_t row) const
36{
37 auto& column_data = (*trace)[static_cast<size_t>(col)];
38 const size_t shard_idx = row / INTERVAL_SIZE;
39 if (shard_idx >= NUM_SHARDS) {
40 return zero;
41 }
43 if (shard == nullptr) {
44 return zero;
45 }
46 return shard->rows[row % INTERVAL_SIZE];
47}
48
50{
51 if (is_shift(col)) {
52 return get(unshift_column(col).value(), row + 1);
53 }
54 return get(static_cast<Column>(col), row);
55}
56
58{
60 if (shard != nullptr) {
61 return *shard;
62 }
63 // Slow path: this slot has never been written. Construct a shard and install it with a single CAS.
64 // Creators of different shards target different atomics and run fully in parallel with no lock. If we
65 // lose the race for this slot (only possible when two chunks share a shard at a boundary), we discard
66 // our spare copy and use the winner's.
68 ColumnInterval* expected = nullptr;
69 if (column_data.slots[shard_idx].compare_exchange_strong(
71 return *fresh.release();
72 }
73 return *expected; // CAS failure loaded the winning pointer into `expected` (acquire).
74}
75
76void TraceContainer::set(Column col, uint32_t row, const FF& value, bool use_atomic_limbs)
77{
78 auto& column_data = (*trace)[static_cast<size_t>(col)];
79 const size_t shard_idx = row / INTERVAL_SIZE;
80 BB_ASSERT_LT(shard_idx, NUM_SHARDS, "row exceeds the maximum trace size");
81 const uint32_t offset = row % INTERVAL_SIZE;
82
83 if (!value.is_zero()) {
84 // Lock-free: a single atomic load finds the shard (created on first write), then we write our
85 // own dense cell directly. Different rows are distinct array elements, so concurrent writers of
86 // this column (or even of the same shard, at a chunk boundary) never race and never serialize.
90 } else {
91 cell = value;
92 }
93 } else {
94 // Zero value: clear if present. We never create a shard, so sparse (mostly-zero) columns are not
95 // materialized (an unset cell already reads as zero).
97 if (shard != nullptr) {
99 store_per_limb(shard->rows[offset], zero);
100 } else {
101 shard->rows[offset] = zero;
102 }
103 }
104 }
105}
106
107void TraceContainer::set(uint32_t row, std::span<const std::pair<Column, FF>> values)
108{
109 for (const auto& [col, value] : values) {
110 set(col, row, value);
111 }
112}
113
115{
116 BB_ASSERT_LTE(size, MAX_AVM_TRACE_SIZE, "size exceeds the maximum trace size");
117
118 if (size == 0) {
119 return;
120 }
121 auto& column_data = (*trace)[static_cast<size_t>(col)];
122 const size_t num_shards = (size + INTERVAL_SIZE - 1) / INTERVAL_SIZE;
123 // Each shard's dense row array is full size on creation, so reserving just materializes the shards up
124 // front (e.g. for precomputed columns). Lock-free: get_or_create_shard installs each via CAS.
125 for (size_t k = 0; k < num_shards; ++k) {
127 }
128}
129
131{
132 // The number of rows is (highest non-zero absolute row + 1). We find it by scanning shards from the
133 // top down and, within the first non-empty shard, scanning its rows from the top. Shards are
134 // top-dense, so this terminates almost immediately. This is only called after the parallel fill
135 // phase, so no lock is needed. Lower shards cannot hold a higher row, so the first hit is the answer.
136 auto& column_data = (*trace)[static_cast<size_t>(col)];
137 for (size_t k = NUM_SHARDS; k-- > 0;) {
139 if (shard_ptr == nullptr) {
140 continue;
141 }
142 const auto& rows = shard_ptr->rows;
143 const uint32_t base = static_cast<uint32_t>(k) * INTERVAL_SIZE;
144 for (uint32_t off = INTERVAL_SIZE; off-- > 0;) {
145 if (!rows[off].is_zero()) {
146 return base + off + 1;
147 }
148 }
149 }
150 return 0;
151}
152
154{
155 uint32_t max_rows = 0;
156 for (size_t col = WITNESS_START_IDX; col < WITNESS_END_IDX; ++col) {
158 }
159 return max_rows;
160}
161
163{
164 uint32_t max_rows = 0;
165 for (size_t col = 0; col < num_columns(); ++col) {
167 }
168 return max_rows;
169}
170
171void TraceContainer::visit_column(Column col, const std::function<void(uint32_t, const FF&)>& visitor) const
172{
173 auto& column_data = (*trace)[static_cast<size_t>(col)];
174 for (size_t k = 0; k < NUM_SHARDS; ++k) {
176 if (shard_ptr == nullptr) {
177 continue;
178 }
179 auto& shard = *shard_ptr;
180 const uint32_t base = static_cast<uint32_t>(k) * INTERVAL_SIZE;
181 for (uint32_t off = 0; off < INTERVAL_SIZE; ++off) {
182 if (!shard.rows[off].is_zero()) {
183 visitor(base + off, shard.rows[off]);
184 }
185 }
186 }
187}
188
190{
191 for (const auto& col : cols) {
193 }
194}
195
197{
199 auto& column_data = (*trace)[static_cast<size_t>(col)];
200 for (size_t k = 0; k < NUM_SHARDS; ++k) {
202 if (shard_ptr == nullptr) {
203 continue;
204 }
205 auto& shard = *shard_ptr;
206 for (auto& value : shard.rows) {
207 if (!value.is_zero()) {
208 ff_vector.push_back(value);
209 }
210 }
211 }
212 FF::batch_invert<RefVector<FF>>(ff_vector);
213}
214
216{
217 // Lock-free: exchange hands each non-null shard pointer to exactly one caller, which frees it.
218 auto& column_data = (*trace)[static_cast<size_t>(col)];
219 for (size_t k = 0; k < NUM_SHARDS; ++k) {
220 delete column_data.slots[k].exchange(nullptr, std::memory_order_acq_rel);
221 }
222}
223
224} // namespace bb::avm2::tracegen
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:143
A template class for a reference vector. Behaves as if std::vector<T&> was possible.
static constexpr size_t num_columns()
const FF & get(Column col, uint32_t row) const
void reserve_column(Column col, size_t size)
const FF & get_column_or_shift(ColumnAndShifts col, uint32_t row) const
void set(Column col, uint32_t row, const FF &value, bool use_atomic_limbs=false)
void invert_columns(std::span< const Column > cols)
static ColumnInterval & get_or_create_shard(SparseColumn &column_data, size_t shard_idx)
void visit_column(Column col, const std::function< void(uint32_t, const FF &)> &visitor) const
uint32_t get_column_rows(Column col) const
static constexpr uint32_t INTERVAL_SIZE
#define BB_UNLIKELY(x)
TestTraceContainer trace
ssize_t offset
Definition engine.cpp:62
bool is_shift(ColumnAndShifts c)
std::optional< Column > unshift_column(ColumnAndShifts c)
constexpr auto WITNESS_END_IDX
Definition columns.hpp:74
constexpr std::size_t MAX_AVM_TRACE_SIZE
Definition constants.hpp:14
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr auto WITNESS_START_IDX
Definition columns.hpp:73
ColumnAndShifts
Definition columns.hpp:35
constexpr auto NUM_COLUMNS_WITHOUT_SHIFTS
Definition columns.hpp:41
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
BB_INLINE constexpr bool is_zero() const noexcept