Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
lookup_builder.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cstddef>
5#include <cstdint>
6#include <stdexcept>
7#include <vector>
8
17
18namespace bb::avm2::tracegen {
19
20// A lookup builder that uses a function `find_in_dst` to find the destination row for a given source tuple.
21template <typename LookupSettings_> class IndexedLookupTraceBuilder : public InteractionBuilderInterface {
22 public:
24 : outer_dst_selector(LookupSettings_::DST_SELECTOR)
25 {}
29 ~IndexedLookupTraceBuilder() override = default;
30
31 void process(TraceContainer& trace) override
32 {
33 init(trace);
34
35 // Let "src_sel {c1, c2, ...} in dst_sel {d1, d2, ...}" be a lookup,
36 // For each row with src_sel == 1, we take the values of {c1, c2, ...},
37 // find a row dst_row in the target columns {d1, d2, ...} where the values match.
38 // It is assumed that SRC_SELECTOR is boolean as visit_column() is being called
39 // for every non-zero row, i.e., a row with SRC_SELECTOR different from 1 and 0 is visited.
40 // Then we increment the count in the counts column at dst_row.
41 // The complexity is O(|src_selector|) * O(find_in_dst).
42 trace.visit_column(LookupSettings::SRC_SELECTOR, [&](uint32_t row, const FF&) {
43 auto src_values = trace.get_multiple(LookupSettings::SRC_COLUMNS, row);
44 uint32_t dst_row = 0;
45 try {
46 dst_row = find_in_dst(src_values); // Assumes an efficient implementation.
47 } catch (const std::runtime_error& e) {
48 // Add row information and rethrow.
49 throw std::runtime_error(std::string(e.what()) + " at row " + std::to_string(row));
50 }
51
52 trace.set(LookupSettings::COUNTS, dst_row, trace.get(LookupSettings::COUNTS, dst_row) + 1);
53 if (LookupSettings::DST_SELECTOR != this->outer_dst_selector) {
54 // This step might write to the same cell from multiple threads, so we use atomic limbs to avoid UB.
55 // Since we are always writing a 1, the end result will be 1 even under concurrency.
56 trace.set(LookupSettings::DST_SELECTOR, dst_row, 1, /*use_atomic_limbs=*/true);
57 }
58 });
59 }
60
61 protected:
62 using LookupSettings = LookupSettings_;
64 virtual uint32_t find_in_dst(const TupleType& tup) const = 0;
65 virtual void init(TraceContainer&) {}; // Optional initialization step.
66
67 // The outer (bigger) table selector.
69};
70
71// This class is used when the lookup is into a non-precomputed table.
72// It calculates the counts by trying to find the tuple in the destination columns.
73// It uses a SharedIndexCache to avoid rebuilding the index when multiple lookups target the same destination.
74// This class should work for any lookup that is not precomputed.
75template <typename LookupSettings_>
77 public:
79 : IndexedLookupTraceBuilder<LookupSettings_>()
80 , cache_(cache)
81 {}
86 virtual ~LookupIntoDynamicTableGeneric() = default;
87
89 {
90 return bb::utils::hash_as_tuple(this->outer_dst_selector, LookupSettings::DST_COLUMNS);
91 }
92
93 protected:
94 using LookupSettings = LookupSettings_;
96
97 void init(TraceContainer& trace) override
98 {
101 LookupSettings::DST_COLUMNS,
102 trace,
103 [this](const TraceContainer& t) { return build_index(t); });
104 }
105
106 uint32_t find_in_dst(const TupleType& tup) const override
107 {
108 size_t key_hash = std::hash<TupleType>{}(tup);
109 auto it = index_ptr_->find(key_hash);
110 if (it != index_ptr_->end()) {
111 const auto& rows = it->second;
112 for (uint32_t row : rows) {
113 if (trace_ptr_->get_multiple(LookupSettings::DST_COLUMNS, row) == tup) {
114 return row;
115 }
116 }
117 }
118 throw std::runtime_error("Failed computing counts for " + std::string(LookupSettings::NAME) +
119 ". Could not find tuple in destination. " +
120 "SRC tuple: " + column_values_to_string(tup, LookupSettings::SRC_COLUMNS));
121 }
122
123 private:
125 {
126 DstIndex idx;
127 idx.reserve(trace.get_column_rows(this->outer_dst_selector));
128 trace.visit_column(this->outer_dst_selector, [&](uint32_t row, const FF&) {
129 auto dst_values = trace.get_multiple(LookupSettings::DST_COLUMNS, row);
130 size_t key_hash = std::hash<decltype(dst_values)>{}(dst_values);
131
132 auto& rows = idx[key_hash];
133 // We need to handle possible hash collisions.
134 bool found_match = false;
135 for (uint32_t existing_row : rows) {
136 if (trace.get_multiple(LookupSettings::DST_COLUMNS, existing_row) == dst_values) {
137 found_match = true;
138 break;
139 }
140 }
141 // If we find a match, we keep that row.
142 // If we don't find a match, we add the new row.
143 if (!found_match) {
144 rows.push_back(row);
145 }
146 });
147
148 return idx;
149 }
150
152 const DstIndex* index_ptr_ = nullptr;
153 const TraceContainer* trace_ptr_ = nullptr;
154};
155
156// This class is used when the lookup is into a non-precomputed table.
157// It is optimized for the case when the source and destination tuples
158// are expected to be in the same order (possibly with other tuples in the middle
159// in the destination table).
160// The approach is that for a given source row, you start sequentially looking at the
161// destination rows until you find a match. Then you move to the next source row.
162// Then you keep looking from the last destination row you found a match.
163// WARNING: Do not use this class if you expect to "reuse" destination rows.
164// In this case the two tables will likely not be in order.
165template <typename LookupSettings> class LookupIntoDynamicTableSequential : public InteractionBuilderInterface {
166 public:
168 : outer_dst_selector(LookupSettings::DST_SELECTOR)
169 {}
174
175 void process(TraceContainer& trace) override
176 {
177 uint32_t dst_row = 0;
178 uint32_t max_dst_row = trace.get_column_rows(this->outer_dst_selector);
179
180 // For the sequential builder, it is critical that we visit the source rows in order.
181 // Since the trace does not guarantee visiting rows in order, we need to collect the rows.
182 std::vector<uint32_t> src_rows_in_order;
183 src_rows_in_order.reserve(trace.get_column_rows(LookupSettings::SRC_SELECTOR));
184 trace.visit_column(LookupSettings::SRC_SELECTOR,
185 [&](uint32_t row, const FF&) { src_rows_in_order.push_back(row); });
186 std::ranges::sort(src_rows_in_order.begin(), src_rows_in_order.end());
187
188 for (uint32_t row : src_rows_in_order) {
189 auto src_values = trace.get_multiple(LookupSettings::SRC_COLUMNS, row);
190
191 // We find the first row in the destination columns where the values match.
192 bool found = false;
193 while (!found && dst_row < max_dst_row) {
194 auto dst_selector = trace.get(this->outer_dst_selector, dst_row);
195 // All our sequential lookups do not use fine grained selectors and therefore
196 // most of the time dst_selector == 1. Therefore, we do not expect much gain to
197 // filter by dst_selector == 1 outside of the loop.
198 if (dst_selector == 1 && src_values == trace.get_multiple(LookupSettings::DST_COLUMNS, dst_row)) {
199 trace.set(LookupSettings::COUNTS, dst_row, trace.get(LookupSettings::COUNTS, dst_row) + 1);
200
201 if (LookupSettings::DST_SELECTOR != this->outer_dst_selector) {
202 // This step might write to the same cell from multiple threads, so we use atomic limbs to avoid
203 // UB. Since we are always writing a 1, the end result will be 1 even under concurrency.
204 trace.set(LookupSettings::DST_SELECTOR, dst_row, 1, /*use_atomic_limbs=*/true);
205 }
206
207 found = true;
208 // We don't want to increment dst_row if we found a match.
209 // It could be that the next "query" will find the same tuple.
210 break;
211 }
212 ++dst_row;
213 }
214
215 if (!found) {
216 throw std::runtime_error(
217 "Failed computing counts for " + std::string(LookupSettings::NAME) +
218 ". Could not find tuple in destination.\nSRC tuple (row " + std::to_string(row) +
219 "): " + column_values_to_string(src_values, LookupSettings::SRC_COLUMNS) +
220 "\nNOTE: Remember that you cannot use LookupIntoDynamicTableSequential with a deduplicated trace!");
221 }
222 }
223 }
224
225 private:
226 // The outer (bigger) table selector.
228};
229
230} // namespace bb::avm2::tracegen
virtual uint32_t find_in_dst(const TupleType &tup) const =0
IndexedLookupTraceBuilder(Column outer_dst_selector)
RefTuple< LookupSettings::LOOKUP_TUPLE_SIZE > TupleType
void process(TraceContainer &trace) override
LookupIntoDynamicTableGeneric(SharedIndexCache &cache, Column outer_dst_selector)
RefTuple< LookupSettings::LOOKUP_TUPLE_SIZE > TupleType
uint32_t find_in_dst(const TupleType &tup) const override
DstIndex build_index(const TraceContainer &trace)
void init(TraceContainer &trace) override
const DstIndex & get_or_build(Column outer_dst_selector, std::span< const ColumnAndShifts > dst_columns, const TraceContainer &trace, const std::function< DstIndex(const TraceContainer &)> &build_fn)
void set(Column col, uint32_t row, const FF &value, bool use_atomic_limbs=false)
auto get_multiple(const std::array< ColumnAndShifts, N > &cols, uint32_t row) const
TestTraceContainer trace
const auto init
Definition fr.bench.cpp:135
typename detail::RefTupleHelper< N >::type RefTuple
unordered_flat_map< size_t, std::vector< uint32_t > > DstIndex
AvmFlavorSettings::FF FF
Definition field.hpp:10
std::string column_values_to_string(const std::array< FF, N > &arr, const std::array< ColumnAndShifts, N > &columns)
Definition stringify.hpp:46
size_t hash_as_tuple(const Ts &... ts)
Definition utils.hpp:22
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
BB_VF_LOAD_LIMBS * this