44#include < ranges>
55
66#include " barretenberg/common/assert.hpp"
7+ #include " barretenberg/common/compiler_hints.hpp"
78#include " barretenberg/common/log.hpp"
89#include " barretenberg/common/ref_vector.hpp"
910#include " barretenberg/vm2/common/field.hpp"
@@ -13,7 +14,19 @@ namespace bb::avm2::tracegen {
1314namespace {
1415
1516// We need a zero value to return (a reference to) when a value is not found.
16- static const FF zero = FF ::zero();
17+ const FF zero = FF ::zero();
18+
19+ // Writes each 64-bit limb of the field with a relaxed atomic store. Each limb is naturally aligned (FF is
20+ // alignas(32), 4x uint64_t), so this lowers to 4 plain `movq` stores on x86-64 — no lock, no libatomic call
21+ // (unlike a whole-field std::atomic_ref<FF>, whose 32 bytes exceed the lock-free width). Lets set() make a
22+ // same-cell concurrent write data-race-free when every writer stores the same value.
23+ inline void store_per_limb (FF & cell, const FF & value)
24+ {
25+ static_assert (sizeof (FF ) == 4 * sizeof (uint64_t ));
26+ for (size_t i = 0 ; i < 4 ; ++i) {
27+ std::atomic_ref<uint64_t >(cell.data [i]).store (value.data [i], std::memory_order_relaxed);
28+ }
29+ }
1730
1831} // namespace
1932
@@ -62,7 +75,7 @@ TraceContainer::ColumnInterval& TraceContainer::get_or_create_shard(SparseColumn
6275 return *expected; // CAS failure loaded the winning pointer into `expected` (acquire).
6376}
6477
65- void TraceContainer::set (Column col, uint32_t row, const FF & value)
78+ void TraceContainer::set (Column col, uint32_t row, const FF & value, bool use_atomic_limbs )
6679{
6780 auto & column_data = (*trace)[static_cast <size_t >(col)];
6881 const size_t shard_idx = row / INTERVAL_SIZE ;
@@ -73,12 +86,22 @@ void TraceContainer::set(Column col, uint32_t row, const FF& value)
7386 // Lock-free: a single atomic load finds the shard (created on first write), then we write our
7487 // own dense cell directly. Different rows are distinct array elements, so concurrent writers of
7588 // this column (or even of the same shard, at a chunk boundary) never race and never serialize.
76- get_or_create_shard (column_data, shard_idx).rows [offset] = value;
89+ auto & cell = get_or_create_shard (column_data, shard_idx).rows [offset];
90+ if (BB_UNLIKELY (use_atomic_limbs)) {
91+ store_per_limb (cell, value);
92+ } else {
93+ cell = value;
94+ }
7795 } else {
78- // Zero value: clear if present. We never create a shard (clearing an absent row is a no-op).
96+ // Zero value: clear if present. We never create a shard, so sparse (mostly-zero) columns are not
97+ // materialized (an unset cell already reads as zero).
7998 ColumnInterval* shard = column_data.slots [shard_idx].load (std::memory_order_acquire);
8099 if (shard != nullptr ) {
81- shard->rows [offset] = FF::zero ();
100+ if (BB_UNLIKELY (use_atomic_limbs)) {
101+ store_per_limb (shard->rows [offset], zero);
102+ } else {
103+ shard->rows [offset] = FF::zero ();
104+ }
82105 }
83106 }
84107}
0 commit comments