-
Notifications
You must be signed in to change notification settings - Fork 614
fix(avm): avoid data race on shared interaction selector writes #24456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| #include <ranges> | ||
|
|
||
| #include "barretenberg/common/assert.hpp" | ||
| #include "barretenberg/common/compiler_hints.hpp" | ||
| #include "barretenberg/common/log.hpp" | ||
| #include "barretenberg/common/ref_vector.hpp" | ||
| #include "barretenberg/vm2/common/field.hpp" | ||
|
|
@@ -13,7 +14,19 @@ namespace bb::avm2::tracegen { | |
| namespace { | ||
|
|
||
| // We need a zero value to return (a reference to) when a value is not found. | ||
| static const FF zero = FF::zero(); | ||
| const FF zero = FF::zero(); | ||
|
|
||
| // Writes each 64-bit limb of the field with a relaxed atomic store. Each limb is naturally aligned (FF is | ||
| // alignas(32), 4x uint64_t), so this lowers to 4 plain `movq` stores on x86-64 — no lock, no libatomic call | ||
| // (unlike a whole-field std::atomic_ref<FF>, whose 32 bytes exceed the lock-free width). Lets set() make a | ||
| // same-cell concurrent write data-race-free when every writer stores the same value. | ||
| inline void store_per_limb(FF& cell, const FF& value) | ||
| { | ||
| static_assert(sizeof(FF) == 4 * sizeof(uint64_t)); | ||
| for (size_t i = 0; i < 4; ++i) { | ||
| std::atomic_ref<uint64_t>(cell.data[i]).store(value.data[i], std::memory_order_relaxed); | ||
| } | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
|
|
@@ -62,7 +75,7 @@ TraceContainer::ColumnInterval& TraceContainer::get_or_create_shard(SparseColumn | |
| return *expected; // CAS failure loaded the winning pointer into `expected` (acquire). | ||
| } | ||
|
|
||
| void TraceContainer::set(Column col, uint32_t row, const FF& value) | ||
| void TraceContainer::set(Column col, uint32_t row, const FF& value, bool use_atomic_limbs) | ||
| { | ||
| auto& column_data = (*trace)[static_cast<size_t>(col)]; | ||
| const size_t shard_idx = row / INTERVAL_SIZE; | ||
|
|
@@ -73,12 +86,22 @@ void TraceContainer::set(Column col, uint32_t row, const FF& value) | |
| // Lock-free: a single atomic load finds the shard (created on first write), then we write our | ||
| // own dense cell directly. Different rows are distinct array elements, so concurrent writers of | ||
| // this column (or even of the same shard, at a chunk boundary) never race and never serialize. | ||
| get_or_create_shard(column_data, shard_idx).rows[offset] = value; | ||
| auto& cell = get_or_create_shard(column_data, shard_idx).rows[offset]; | ||
| if (BB_UNLIKELY(use_atomic_limbs)) { | ||
| store_per_limb(cell, value); | ||
| } else { | ||
| cell = value; | ||
| } | ||
| } else { | ||
| // Zero value: clear if present. We never create a shard (clearing an absent row is a no-op). | ||
| // Zero value: clear if present. We never create a shard, so sparse (mostly-zero) columns are not | ||
| // materialized (an unset cell already reads as zero). | ||
| ColumnInterval* shard = column_data.slots[shard_idx].load(std::memory_order_acquire); | ||
| if (shard != nullptr) { | ||
| shard->rows[offset] = FF::zero(); | ||
| if (BB_UNLIKELY(use_atomic_limbs)) { | ||
| store_per_limb(shard->rows[offset], zero); | ||
| } else { | ||
| shard->rows[offset] = FF::zero(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use the constant
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm yeah, it probably would make sense to use either |
||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you remove the second boolean condition because it is not worth it performance-wise or because it would not be thread-safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not safe even if it's a read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok thanks for the pointer.