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 <array>
4#include <bit>
5#include <cstddef>
6#include <cstdint>
7#include <stdexcept>
8#include <utility>
9
20
21namespace bb::avm2::tracegen {
22
23// A lookup builder that uses a function `find_in_dst` to find the destination row for a given source tuple.
24template <typename LookupSettings_> class IndexedLookupTraceBuilder : public InteractionBuilderInterface {
25 public:
27 : outer_dst_selector(LookupSettings_::DST_SELECTOR)
28 {}
32 ~IndexedLookupTraceBuilder() override = default;
33
34 void process(TraceContainer& trace) override
35 {
36 init(trace);
37
38 // Let "src_sel {c1, c2, ...} in dst_sel {d1, d2, ...}" be a lookup,
39 // For each row that has a 1 in the src_sel, we take the values of {c1, c2, ...},
40 // find a row dst_row in the target columns {d1, d2, ...} where the values match.
41 // Then we increment the count in the counts column at dst_row.
42 // The complexity is O(|src_selector|) * O(find_in_dst).
43 trace.visit_column(LookupSettings::SRC_SELECTOR, [&](uint32_t row, const FF&) {
44 auto src_values = trace.get_multiple(LookupSettings::SRC_COLUMNS, row);
45 uint32_t dst_row = 0;
46 try {
47 dst_row = find_in_dst(src_values); // Assumes an efficient implementation.
48 } catch (const std::runtime_error& e) {
49 // Add row information and rethrow.
50 throw std::runtime_error(std::string(e.what()) + " at row " + std::to_string(row));
51 }
52
53 trace.set(LookupSettings::COUNTS, dst_row, trace.get(LookupSettings::COUNTS, dst_row) + 1);
54 // Set the fine grained inner selector if it's not already one.
55 if (LookupSettings::DST_SELECTOR != this->outer_dst_selector &&
56 trace.get(LookupSettings::DST_SELECTOR, dst_row) != 1) {
57 trace.set(LookupSettings::DST_SELECTOR, dst_row, 1);
58 }
59 });
60 }
61
62 protected:
63 using LookupSettings = LookupSettings_;
65 virtual uint32_t find_in_dst(const TupleType& tup) const = 0;
66 virtual void init(TraceContainer&) {}; // Optional initialization step.
67
68 // The outer (bigger) table selector.
70};
71
72// This class is used when the lookup is into a non-precomputed table.
73// It calculates the counts by trying to find the tuple in the destination columns.
74// It uses a SharedIndexCache to avoid rebuilding the index when multiple lookups target the same destination.
75// This class should work for any lookup that is not precomputed.
76template <typename LookupSettings_>
78 public:
80 : IndexedLookupTraceBuilder<LookupSettings_>()
81 , cache_(cache)
82 {}
87 virtual ~LookupIntoDynamicTableGeneric() = default;
88
90 {
91 return bb::utils::hash_as_tuple(this->outer_dst_selector, LookupSettings::DST_COLUMNS);
92 }
93
94 protected:
95 using LookupSettings = LookupSettings_;
97
98 void init(TraceContainer& trace) override
99 {
100 trace_ptr_ = &trace;
102 LookupSettings::DST_COLUMNS,
103 trace,
104 [this](const TraceContainer& t) { return build_index(t); });
105 }
106
107 uint32_t find_in_dst(const TupleType& tup) const override
108 {
109 size_t key_hash = std::hash<TupleType>{}(tup);
110 auto it = index_ptr_->find(key_hash);
111 if (it != index_ptr_->end()) {
112 const auto& rows = it->second;
113 for (uint32_t row : rows) {
114 if (trace_ptr_->get_multiple(LookupSettings::DST_COLUMNS, row) == tup) {
115 return row;
116 }
117 }
118 }
119 throw std::runtime_error("Failed computing counts for " + std::string(LookupSettings::NAME) +
120 ". Could not find tuple in destination. " +
121 "SRC tuple: " + column_values_to_string(tup, LookupSettings::SRC_COLUMNS));
122 }
123
124 private:
126 {
127 DstIndex idx;
128 idx.reserve(trace.get_column_rows(this->outer_dst_selector));
129 trace.visit_column(this->outer_dst_selector, [&](uint32_t row, const FF&) {
130 auto dst_values = trace.get_multiple(LookupSettings::DST_COLUMNS, row);
131 size_t key_hash = std::hash<decltype(dst_values)>{}(dst_values);
132
133 auto& rows = idx[key_hash];
134 // We need to handle possible hash collisions.
135 bool found_match = false;
136 for (uint32_t existing_row : rows) {
137 if (trace.get_multiple(LookupSettings::DST_COLUMNS, existing_row) == dst_values) {
138 found_match = true;
139 break;
140 }
141 }
142 // If we find a match, we keep that row.
143 // If we don't find a match, we add the new row.
144 if (!found_match) {
145 rows.push_back(row);
146 }
147 });
148
149 return idx;
150 }
151
153 const DstIndex* index_ptr_ = nullptr;
154 const TraceContainer* trace_ptr_ = nullptr;
155};
156
157// This class is used when the lookup is into a non-precomputed table.
158// It is optimized for the case when the source and destination tuples
159// are expected to be in the same order (possibly with other tuples in the middle
160// in the destination table).
161// The approach is that for a given source row, you start sequentially looking at the
162// destination rows until you find a match. Then you move to the next source row.
163// Then you keep looking from the last destination row you found a match.
164// WARNING: Do not use this class if you expect to "reuse" destination rows.
165// In this case the two tables will likely not be in order.
166template <typename LookupSettings> class LookupIntoDynamicTableSequential : public InteractionBuilderInterface {
167 public:
169 : outer_dst_selector(LookupSettings::DST_SELECTOR)
170 {}
175
176 void process(TraceContainer& trace) override
177 {
178 uint32_t dst_row = 0;
179 uint32_t max_dst_row = trace.get_column_rows(this->outer_dst_selector);
180
181 // For the sequential builder, it is critical that we visit the source rows in order.
182 // Since the trace does not guarantee visiting rows in order, we need to collect the rows.
183 std::vector<uint32_t> src_rows_in_order;
184 src_rows_in_order.reserve(trace.get_column_rows(LookupSettings::SRC_SELECTOR));
185 trace.visit_column(LookupSettings::SRC_SELECTOR,
186 [&](uint32_t row, const FF&) { src_rows_in_order.push_back(row); });
187 std::sort(src_rows_in_order.begin(), src_rows_in_order.end());
188
189 for (uint32_t row : src_rows_in_order) {
190 auto src_values = trace.get_multiple(LookupSettings::SRC_COLUMNS, row);
191
192 // We find the first row in the destination columns where the values match.
193 bool found = false;
194 while (!found && dst_row < max_dst_row) {
195 // TODO: As an optimization, we could try to only walk the rows where the selector is active.
196 // We can't just do a visit because we cannot skip rows with that.
197 auto dst_selector = trace.get(this->outer_dst_selector, dst_row);
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 // Set the fine grained inner selector if it's not already one.
202 if (LookupSettings::DST_SELECTOR != this->outer_dst_selector &&
203 trace.get(LookupSettings::DST_SELECTOR, dst_row) != 1) {
204 trace.set(LookupSettings::DST_SELECTOR, dst_row, 1);
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, std::function< DstIndex(const TraceContainer &)> build_fn)
const FF & get(Column col, uint32_t row) const
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
std::string column_values_to_string(const std::array< FF, N > &arr, const std::array< ColumnAndShifts, N > &columns)
Definition stringify.hpp:46
AvmFlavorSettings::FF FF
Definition field.hpp:10
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)