Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
alu_trace.cpp
Go to the documentation of this file.
2
3#include <cstddef>
4#include <cstdint>
5#include <ranges>
6#include <stdexcept>
7
17
18namespace bb::avm2::tracegen {
19
21{
22 for (size_t i = 0; i < NUM_TAGS; i++) {
23 tag_inverses.at(i) = FF(i);
24 }
26}
27
29{
30 if (static_cast<uint8_t>(a_tag) >= static_cast<uint8_t>(b_tag)) {
31 return tag_inverses[static_cast<uint8_t>(a_tag) - static_cast<uint8_t>(b_tag)];
32 }
33
34 return -tag_inverses[static_cast<uint8_t>(b_tag) - static_cast<uint8_t>(a_tag)];
35}
36
38 const simulation::AluEvent& event) const
39{
40 const MemoryTag a_tag = event.a.get_tag();
41 bool is_ff = a_tag == MemoryTag::FF;
42 bool is_u128 = a_tag == MemoryTag::U128;
43 bool no_tag_err = event.error != simulation::AluError::TAG_ERROR;
44
45 // We rely on the following assert in computing C::alu_tag_ff_diff_inv value
46 // below. Namely: (tag - MemoryTag::FF).invert() == tag.invert().
47 static_assert(static_cast<uint8_t>(MemoryTag::FF) == 0);
48
49 switch (event.operation) {
51 return { { Column::alu_sel_op_add, 1 },
52 { Column::alu_op_id, SUBTRACE_INFO_MAP.at(ExecutionOpCode::ADD).subtrace_operation_id },
53 // I think the only situation in which a + b != c as fields is when c overflows the bit size
54 // if this in unclear, I can use > or actually check bit sizes:
55 { Column::alu_cf, event.a.as_ff() + event.b.as_ff() != event.c.as_ff() } };
57 return { { Column::alu_sel_op_sub, 1 },
58 { Column::alu_op_id, SUBTRACE_INFO_MAP.at(ExecutionOpCode::SUB).subtrace_operation_id },
59 { Column::alu_cf, event.a.as_ff() - event.b.as_ff() != event.c.as_ff() } };
61 uint256_t a_int = static_cast<uint256_t>(event.a.as_ff());
62 uint256_t b_int = static_cast<uint256_t>(event.b.as_ff());
63
64 // Columns shared for all tags in a MUL:
66 { Column::alu_sel_op_mul, 1 },
67 { Column::alu_op_id, SUBTRACE_INFO_MAP.at(ExecutionOpCode::MUL).subtrace_operation_id },
68 { Column::alu_constant_64, 64 },
69 { Column::alu_sel_is_u128, is_u128 },
70 { Column::alu_tag_u128_diff_inv, get_tag_diff_inverse(a_tag, MemoryTag::U128) },
71 };
72 if (is_u128) {
73 // For u128s, we decompose a and b into 64 bit chunks:
74 auto a_decomp = simulation::decompose(static_cast<uint128_t>(event.a.as_ff()));
75 auto b_decomp = simulation::decompose(static_cast<uint128_t>(event.b.as_ff()));
76 // c_hi = old_c_hi - a_hi * b_hi % 2^64
77 auto hi_operand = static_cast<uint256_t>(a_decomp.hi) * static_cast<uint256_t>(b_decomp.hi);
78 res.insert(res.end(),
79 {
80 { Column::alu_sel_mul_u128, 1 },
81 { Column::alu_sel_mul_div_u128, 1 },
82 { Column::alu_sel_decompose_a, 1 },
83 { Column::alu_a_lo, a_decomp.lo },
84 { Column::alu_a_lo_bits, 64 },
85 { Column::alu_a_hi, a_decomp.hi },
86 { Column::alu_a_hi_bits, 64 },
87 { Column::alu_b_lo, b_decomp.lo },
88 { Column::alu_b_hi, b_decomp.hi },
89 { Column::alu_c_hi, (((a_int * b_int) >> 128) - hi_operand) % (uint256_t(1) << 64) },
90 { Column::alu_cf, hi_operand == 0 ? 0 : 1 },
91 });
92 } else {
93 // For non-u128s, we just take the top bits of a*b:
94 res.insert(res.end(), { { Column::alu_c_hi, is_ff ? 0 : (a_int * b_int) >> get_tag_bits(a_tag) } });
95 }
96 return res;
97 }
99 bool div_0_error = event.error == simulation::AluError::DIV_0_ERROR;
100 auto remainder = no_tag_err && !div_0_error ? event.a - event.b * event.c : MemoryValue::from_tag(a_tag, 0);
101
102 // Columns shared for all tags in a DIV:
104 { Column::alu_sel_op_div, 1 },
105 { Column::alu_op_id, SUBTRACE_INFO_MAP.at(ExecutionOpCode::DIV).subtrace_operation_id },
106 { Column::alu_helper1, remainder },
107 { Column::alu_constant_64, 64 },
108 { Column::alu_sel_is_ff, is_ff },
109 { Column::alu_tag_ff_diff_inv, tag_inverses[static_cast<uint8_t>(a_tag)] }, // Relies on MemoryTag::FF is 0
110 { Column::alu_sel_is_u128, is_u128 },
111 { Column::alu_tag_u128_diff_inv, get_tag_diff_inverse(a_tag, MemoryTag::U128) },
112 { Column::alu_b_inv, div_0_error ? 0 : event.b.as_ff() }, // Will be inverted in batch later
113 { Column::alu_sel_div_no_0_err, div_0_error ? 0 : 1 },
114 };
115 if (is_u128) {
116 // For u128s, we decompose c and b into 64 bit chunks:
117 auto c_decomp = simulation::decompose(static_cast<uint128_t>(event.c.as_ff()));
118 auto b_decomp = simulation::decompose(static_cast<uint128_t>(event.b.as_ff()));
119 res.insert(res.end(),
120 {
121 { Column::alu_sel_mul_div_u128, 1 },
122 { Column::alu_sel_decompose_a, 1 },
123 { Column::alu_a_lo, c_decomp.lo },
124 { Column::alu_a_lo_bits, 64 },
125 { Column::alu_a_hi, c_decomp.hi },
126 { Column::alu_a_hi_bits, 64 },
127 { Column::alu_b_lo, b_decomp.lo },
128 { Column::alu_b_hi, b_decomp.hi },
129 });
130 }
131 return res;
132 }
134 bool div_0_error = event.error == simulation::AluError::DIV_0_ERROR;
135 return {
136 { Column::alu_sel_op_fdiv, 1 },
137 { Column::alu_op_id, SUBTRACE_INFO_MAP.at(ExecutionOpCode::FDIV).subtrace_operation_id },
138 { Column::alu_sel_is_ff, is_ff },
139 { Column::alu_tag_ff_diff_inv, tag_inverses[static_cast<uint8_t>(a_tag)] }, // Relies on MemoryTag::FF is 0
140 { Column::alu_b_inv, div_0_error ? 0 : event.b.as_ff() }, // Will be inverted in batch later
141 };
142 }
144 const FF diff = event.a.as_ff() - event.b.as_ff();
145 return {
146 { Column::alu_sel_op_eq, 1 },
147 { Column::alu_op_id, SUBTRACE_INFO_MAP.at(ExecutionOpCode::EQ).subtrace_operation_id },
148 { Column::alu_ab_diff_inv, diff }, // Will be inverted in batch later
149 };
150 }
152 return {
153 { Column::alu_lt_ops_input_a, event.b },
154 { Column::alu_lt_ops_input_b, event.a },
155 { Column::alu_lt_ops_result_c, event.c },
156 { Column::alu_sel_op_lt, 1 },
157 { Column::alu_sel_lt_ops, no_tag_err },
158 { Column::alu_op_id,
159 static_cast<uint8_t>(SUBTRACE_INFO_MAP.at(ExecutionOpCode::LT).subtrace_operation_id) },
160 { Column::alu_sel_is_ff, is_ff },
161 { Column::alu_sel_ff_lt_ops, is_ff && no_tag_err },
162 { Column::alu_sel_int_lt_ops, !is_ff && no_tag_err },
163 { Column::alu_tag_ff_diff_inv, tag_inverses[static_cast<uint8_t>(a_tag)] }, // Relies on MemoryTag::FF is 0
164 };
166 return {
167 { Column::alu_lt_ops_input_a, event.a },
168 { Column::alu_lt_ops_input_b, event.b },
169 { Column::alu_lt_ops_result_c, MemoryValue::from<uint1_t>(event.c.as_ff() == 0 && no_tag_err ? 1 : 0) },
170 { Column::alu_sel_op_lte, 1 },
171 { Column::alu_sel_lt_ops, no_tag_err },
172 { Column::alu_op_id,
173 static_cast<uint8_t>(SUBTRACE_INFO_MAP.at(ExecutionOpCode::LTE).subtrace_operation_id) },
174 { Column::alu_sel_is_ff, is_ff },
175 { Column::alu_sel_ff_lt_ops, is_ff && no_tag_err },
176 { Column::alu_sel_int_lt_ops, !is_ff && no_tag_err },
177 { Column::alu_tag_ff_diff_inv, tag_inverses[static_cast<uint8_t>(a_tag)] }, // Relies on MemoryTag::FF is 0
178 };
180 return {
181 { Column::alu_sel_op_not, 1 },
182 { Column::alu_op_id, SUBTRACE_INFO_MAP.at(ExecutionOpCode::NOT).subtrace_operation_id },
183 { Column::alu_sel_is_ff, is_ff },
184 { Column::alu_tag_ff_diff_inv, tag_inverses[static_cast<uint8_t>(a_tag)] }, // Relies on MemoryTag::FF is 0
185 };
186 }
188 auto a_num = static_cast<uint128_t>(event.a.as_ff());
189 auto b_num = static_cast<uint128_t>(event.b.as_ff());
190 auto tag_bits = get_tag_bits(a_tag);
191 // Whether we shift by more than the bit size (=> result is 0):
192 bool overflow = b_num > tag_bits;
193 // The bit size of the low limb of decomposed input a (if overflow, assigned as max_bits to range check
194 // b - max_bits):
195 auto shift_lo_bits = overflow ? tag_bits : tag_bits - b_num;
196 // The low limb of decomposed input a (if overflow, assigned as b - max_bits to range check and
197 // prove b > max_bits):
198 auto a_lo = overflow ? b_num - tag_bits : a_num % (static_cast<uint128_t>(1) << shift_lo_bits);
199 return {
200 { Column::alu_sel_op_shl, 1 },
201 { Column::alu_sel_shift_ops, 1 },
202 { Column::alu_sel_shift_ops_no_overflow, overflow ? 0 : 1 },
203 { Column::alu_sel_decompose_a, is_ff ? 0 : 1 },
204 { Column::alu_a_lo, a_lo },
205 { Column::alu_a_lo_bits, shift_lo_bits },
206 { Column::alu_a_hi, a_num >> shift_lo_bits },
207 { Column::alu_a_hi_bits, overflow ? tag_bits : b_num },
208 { Column::alu_shift_lo_bits, shift_lo_bits },
209 { Column::alu_two_pow_shift_lo_bits, static_cast<uint128_t>(1) << shift_lo_bits },
210 { Column::alu_sel_is_ff, is_ff },
211 { Column::alu_tag_ff_diff_inv, tag_inverses[static_cast<uint8_t>(a_tag)] }, // Relies on MemoryTag::FF is 0
212 { Column::alu_op_id, SUBTRACE_INFO_MAP.at(ExecutionOpCode::SHL).subtrace_operation_id },
213 { Column::alu_helper1, static_cast<uint128_t>(1) << b_num },
214 };
215 }
217 auto a_num = static_cast<uint128_t>(event.a.as_ff());
218 auto b_num = static_cast<uint128_t>(event.b.as_ff());
219 auto tag_bits = get_tag_bits(a_tag);
220 // Whether we shift by more than the bit size (=> result is 0):
221 bool overflow = b_num > tag_bits;
222 // The bit size of the low limb of decomposed input a (if overflow, assigned as max_bits to range check
223 // b - max_bits):
224 auto shift_lo_bits = overflow ? tag_bits : b_num;
225 // The low limb of decomposed input a (if overflow, assigned as b - max_bits to range check and
226 // prove b > max_bits):
227 auto a_lo = overflow ? b_num - tag_bits : a_num % (static_cast<uint128_t>(1) << shift_lo_bits);
228 return {
229 { Column::alu_sel_op_shr, 1 },
230 { Column::alu_sel_shift_ops, 1 },
231 { Column::alu_sel_shift_ops_no_overflow, overflow ? 0 : 1 },
232 { Column::alu_sel_decompose_a, is_ff ? 0 : 1 },
233 { Column::alu_a_lo, a_lo },
234 { Column::alu_a_lo_bits, shift_lo_bits },
235 { Column::alu_a_hi, a_num >> shift_lo_bits },
236 { Column::alu_a_hi_bits, overflow ? tag_bits : tag_bits - b_num },
237 { Column::alu_shift_lo_bits, shift_lo_bits },
238 { Column::alu_two_pow_shift_lo_bits, static_cast<uint128_t>(1) << shift_lo_bits },
239 { Column::alu_sel_is_ff, is_ff },
240 { Column::alu_tag_ff_diff_inv, tag_inverses[static_cast<uint8_t>(a_tag)] }, // Relies on MemoryTag::FF is 0
241 { Column::alu_op_id, SUBTRACE_INFO_MAP.at(ExecutionOpCode::SHR).subtrace_operation_id },
242 };
243 }
245 const uint256_t value = static_cast<uint256_t>(event.a.as_ff());
246 const MemoryTag dst_tag = static_cast<MemoryTag>(static_cast<uint8_t>(event.b.as_ff()));
247 bool is_trivial = dst_tag == MemoryTag::FF || value <= get_tag_max_value(dst_tag);
248 bool is_lt_128 = !is_trivial && value < (static_cast<uint256_t>(1) << 128);
249 bool is_gte_128 = !is_trivial && !is_lt_128;
250 const uint256_t lo_128 = is_trivial ? 0 : value & ((static_cast<uint256_t>(1) << 128) - 1);
251 const uint8_t dst_bits = get_tag_bits(dst_tag);
252 const uint256_t mid = is_trivial ? 0 : lo_128 >> dst_bits;
253
254 return {
255 { Column::alu_sel_op_truncate, 1 },
256 { Column::alu_sel_trunc_trivial, is_trivial },
257 { Column::alu_sel_trunc_lt_128, is_lt_128 },
258 { Column::alu_sel_trunc_gte_128, is_gte_128 },
259 { Column::alu_sel_trunc_non_trivial, !is_trivial },
260 { Column::alu_a_lo, lo_128 },
261 { Column::alu_a_hi, is_gte_128 ? value >> 128 : 0 },
262 { Column::alu_mid, mid },
263 { Column::alu_op_id, AVM_EXEC_OP_ID_ALU_TRUNCATE },
264 { Column::alu_mid_bits, is_trivial ? 0 : 128 - dst_bits },
265 };
266 }
267 default:
268 throw std::runtime_error("Unknown ALU operation");
269 break;
270 }
271}
272
274{
275 const MemoryTag a_tag = event.a.get_tag();
276 const FF a_tag_ff = static_cast<FF>(static_cast<uint8_t>(a_tag));
277 const MemoryTag b_tag = event.b.get_tag();
278 const FF b_tag_ff = static_cast<FF>(static_cast<uint8_t>(b_tag));
279 // Tag errors currently have cases:
280 // 1. Input tagged as a field for NOT or DIV operations
281 // 2. Mismatched tags for inputs a and b for all opcodes apart from TRUNC
282
283 // Case 1:
284 bool ff_tag_err =
285 ((a_tag == MemoryTag::FF) &&
288 ((a_tag != MemoryTag::FF) && (event.operation == simulation::AluOperation::FDIV));
289 // Case 2:
290 bool ab_tags_mismatch = (a_tag_ff != b_tag_ff) && (event.operation != simulation::AluOperation::TRUNCATE);
291 // Note: both cases can occur at the same time. Case 1 only requires sel_tag_error to be on, so we
292 // check ab_tags_mismatch first:
293 if (ab_tags_mismatch) {
294 return { { Column::alu_sel_tag_err, 1 },
295 { Column::alu_sel_ab_tag_mismatch, 1 },
296 { Column::alu_ab_tags_diff_inv, get_tag_diff_inverse(a_tag, b_tag) } };
297 }
298 if (ff_tag_err) {
299 // Note: There is no 'alu_sel_ff_tag_err' because we can handle this with existing selectors:
300 // (sel_op_div + sel_op_not) * sel_is_ff
301 return { { Column::alu_sel_tag_err, 1 } };
302 }
303 // We shouldn't have emitted an event with a tag error when one doesn't exist:
304 assert(false && "ALU Event emitted with tag error, but none exists");
305 return {};
306}
307
310{
311 using C = Column;
313
314 uint32_t row = 0;
315 for (const auto& event : events) {
316 // For TRUNCATE, the destination tag is passed through b in the event, but will be
317 // set to ia_tag in the ALU subtrace. (See alu.pil for more details.).
318 const uint8_t a_tag_u8 = event.operation == simulation::AluOperation::TRUNCATE
319 ? static_cast<uint8_t>(event.b.as_ff())
320 : static_cast<uint8_t>(event.a.get_tag());
321 const FF b_tag = static_cast<FF>(static_cast<uint8_t>(event.b.get_tag()));
322 const FF c_tag = static_cast<FF>(static_cast<uint8_t>(event.c.get_tag()));
323 bool tag_check_failed = event.error.has_value() && event.error == AluError::TAG_ERROR;
324 if (tag_check_failed) {
325 // Tag error specific columns:
327 }
328 bool div_0_error = event.error.has_value() && event.error == AluError::DIV_0_ERROR;
329 if (div_0_error) {
330 // TODO(MW): Below needed?
331 // Should not emit a divide by 0 error if we are not in DIV or FDIV or have no 0 divisor:
332 assert((event.b.as_ff() == FF(0)) &&
333 ((event.operation == simulation::AluOperation::DIV) ||
334 (event.operation == simulation::AluOperation::FDIV)) &&
335 "ALU Event emitted with divide by zero error, but none exists");
336 }
337
338 // Operation specific columns:
340
341 trace.set(row,
342 { {
343 { C::alu_sel, 1 },
344 { C::alu_ia, event.a },
345 { C::alu_ib, event.b },
346 { C::alu_ic, event.c },
347 { C::alu_ia_tag, a_tag_u8 },
348 { C::alu_ib_tag, b_tag },
349 { C::alu_ic_tag, c_tag },
350 { C::alu_max_bits, get_tag_bits(static_cast<MemoryTag>(a_tag_u8)) },
351 { C::alu_max_value, get_tag_max_value(static_cast<MemoryTag>(a_tag_u8)) },
352 { C::alu_sel_div_0_err, div_0_error ? 1 : 0 },
353 { C::alu_sel_err, tag_check_failed || div_0_error ? 1 : 0 },
354 } });
355
356 row++;
357 }
358
359 // Batch invert the columns.
360 trace.invert_columns({ { C::alu_ab_diff_inv, C::alu_b_inv } });
361}
362
366 .add<lookup_alu_range_check_decomposition_a_lo_settings, InteractionType::LookupGeneric>(
367 Column::range_check_sel)
369 Column::range_check_sel)
370 .add<lookup_alu_range_check_decomposition_b_lo_settings, InteractionType::LookupGeneric>(
371 Column::range_check_sel)
373 Column::range_check_sel)
374 .add<lookup_alu_range_check_mul_u128_c_hi_settings, InteractionType::LookupGeneric>(Column::range_check_sel)
376 .add<lookup_alu_ff_gt_settings, InteractionType::LookupGeneric>()
378 .add<lookup_alu_shifts_two_pow_settings, InteractionType::LookupIntoIndexedByClk>()
380 .add<lookup_alu_large_trunc_canonical_dec_settings, InteractionType::LookupGeneric>();
381} // namespace bb::avm2::tracegen
MemoryTag dst_tag
#define AVM_EXEC_OP_ID_ALU_TRUNCATE
static TaggedValue from_tag(ValueTag tag, FF value)
std::vector< std::pair< Column, FF > > get_tag_error_columns(const simulation::AluEvent &event) const
std::vector< std::pair< Column, FF > > get_operation_specific_columns(const simulation::AluEvent &event) const
Definition alu_trace.cpp:37
static constexpr size_t NUM_TAGS
Definition alu_trace.hpp:20
static const InteractionDefinition interactions
Definition alu_trace.hpp:17
void process(const simulation::EventEmitterInterface< simulation::AluEvent >::Container &events, TraceContainer &trace)
FF get_tag_diff_inverse(const MemoryTag a_tag, const MemoryTag b_tag) const
Definition alu_trace.cpp:28
std::array< FF, NUM_TAGS > tag_inverses
Definition alu_trace.hpp:22
InteractionDefinition & add(auto &&... args)
TestTraceContainer trace
U256Decomposition decompose(const uint256_t &x)
const std::unordered_map< ExecutionOpCode, SubtraceInfo > SUBTRACE_INFO_MAP
lookup_settings< lookup_alu_gt_div_remainder_settings_ > lookup_alu_gt_div_remainder_settings
lookup_settings< lookup_alu_int_gt_settings_ > lookup_alu_int_gt_settings
lookup_settings< lookup_alu_range_check_decomposition_a_hi_settings_ > lookup_alu_range_check_decomposition_a_hi_settings
lookup_settings< lookup_alu_tag_max_bits_value_settings_ > lookup_alu_tag_max_bits_value_settings
lookup_settings< lookup_alu_range_check_trunc_mid_settings_ > lookup_alu_range_check_trunc_mid_settings
uint8_t get_tag_bits(ValueTag tag)
uint256_t get_tag_max_value(ValueTag tag)
lookup_settings< lookup_alu_range_check_decomposition_b_hi_settings_ > lookup_alu_range_check_decomposition_b_hi_settings
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
simulation::PublicDataTreeReadWriteEvent event
unsigned __int128 uint128_t
Definition serialize.hpp:44
static void batch_invert(C &coeffs) noexcept