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 <array>
4#include <cstddef>
5#include <cstdint>
6#include <stdexcept>
7
16
17namespace bb::avm2::tracegen {
18
19namespace {
20
21using C = Column;
22
29const FF& get_tag_inverse(size_t index)
30{
31 constexpr size_t NUM_TAGS = static_cast<size_t>(MemoryTag::MAX) + 1;
32 static const std::array<FF, NUM_TAGS> tag_inverses = []() {
34 for (size_t i = 0; i < NUM_TAGS; i++) {
35 inverses.at(i) = FF(i);
36 }
37 FF::batch_invert(inverses);
38 return inverses;
39 }();
40
41 return tag_inverses.at(index);
42}
43
51FF get_tag_diff_inverse(const MemoryTag a_tag, const MemoryTag b_tag)
52{
53 if (static_cast<uint8_t>(a_tag) >= static_cast<uint8_t>(b_tag)) {
54 return get_tag_inverse(static_cast<uint8_t>(a_tag) - static_cast<uint8_t>(b_tag));
55 }
56
57 return -get_tag_inverse(static_cast<uint8_t>(b_tag) - static_cast<uint8_t>(a_tag));
58}
59
66std::vector<std::pair<C, FF>> get_operation_specific_columns(const simulation::AluEvent& event)
67{
68 const MemoryTag a_tag = event.a.get_tag();
69 bool is_ff = a_tag == MemoryTag::FF;
70 bool is_u128 = a_tag == MemoryTag::U128;
71 bool has_error = event.error;
72
73 switch (event.operation) {
75 return { { C::alu_sel_op_add, 1 },
76 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_ADD },
77 // a + b = cf * 2^(max_bits) + c so cf == 1 iff a + b != c over the integers.
78 // For FF, cf is always 0, therefore we can make the comparison over FF as this field is much larger
79 // than 128 bits.
80 { C::alu_cf, !has_error && (event.a.as_ff() + event.b.as_ff() != event.c.as_ff()) ? 1 : 0 } };
82 return { { C::alu_sel_op_sub, 1 },
83 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_SUB },
84 // a - b + cf * 2^(max_bits) = c so cf == 1 iff a - b != c over the integers.
85 // For FF, cf is always 0, therefore we can make the comparison over FF as this field is much larger
86 // than 128 bits.
87 { C::alu_cf, !has_error && (event.a.as_ff() - event.b.as_ff() != event.c.as_ff()) ? 1 : 0 } };
89 uint256_t a_int = static_cast<uint256_t>(event.a.as_ff());
90 uint256_t b_int = static_cast<uint256_t>(event.b.as_ff());
91
92 // Columns shared for all tags in a MUL:
94 { C::alu_sel_op_mul, 1 },
95 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_MUL },
96 { C::alu_constant_64, 64 },
97 { C::alu_sel_mul_no_err_non_ff, (has_error || is_ff) ? 0 : 1 },
98 };
99
100 if (!has_error) {
101 if (is_u128) {
102 // For u128s, we decompose a and b into 64 bit chunks:
103 auto a_decomp = simulation::decompose_128(static_cast<uint128_t>(a_int));
104 auto b_decomp = simulation::decompose_128(static_cast<uint128_t>(b_int));
105 // c_hi = (c_hi_full - a_hi * b_hi) % 2^64 (see alu.pil for more details)
106 // cf == (c_hi_full - a_hi * b_hi)/2^64
107 uint256_t hi_operand = (((a_int * b_int) >> 128) -
108 static_cast<uint256_t>(a_decomp.hi) * static_cast<uint256_t>(b_decomp.hi));
109 res.insert(res.end(),
110 {
111 { C::alu_sel_mul_div_u128, 1 },
112 { C::alu_sel_decompose_a, 1 },
113 { C::alu_a_lo_bits, 64 },
114 { C::alu_a_hi_bits, 64 },
115 { C::alu_a_lo, a_decomp.lo },
116 { C::alu_a_hi, a_decomp.hi },
117 { C::alu_b_lo, b_decomp.lo },
118 { C::alu_b_hi, b_decomp.hi },
119 { C::alu_c_hi, hi_operand & static_cast<uint256_t>(MASK_64) },
120 { C::alu_cf, hi_operand >> 64 },
121 });
122 } else {
123 // For non-u128s, we just take the top bits of a*b:
124 res.insert(
125 res.end(),
126 { { C::alu_c_hi, is_ff ? 0 : (a_int * b_int) >> static_cast<uint256_t>(get_tag_bits(a_tag)) } });
127 }
128 }
129
130 return res;
131 }
133 // Columns shared for all tags in a DIV:
135 { C::alu_sel_op_div, 1 },
136 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_DIV },
137 { C::alu_constant_64, 64 },
138 { C::alu_b_inv, event.b.as_ff() }, // Will be inverted in batch later
139 };
140
141 if (!has_error) {
142 auto remainder = event.a - event.b * event.c;
143 res.insert(res.end(),
144 {
145 { C::alu_sel_div_no_err, 1 },
146 { C::alu_helper1, remainder.as_ff() },
147 { C::alu_sel_int_gt, 1 },
148 { C::alu_gt_input_a, event.b.as_ff() },
149 { C::alu_gt_input_b, remainder.as_ff() },
150 { C::alu_gt_result_c, 1 },
151 });
152 if (is_u128) {
153 // For u128s, we decompose c and b into 64 bit chunks:
154 auto c_decomp = simulation::decompose_128(static_cast<uint128_t>(event.c.as_ff()));
155 auto b_decomp = simulation::decompose_128(static_cast<uint128_t>(event.b.as_ff()));
156 res.insert(res.end(),
157 {
158 { C::alu_sel_mul_div_u128, 1 },
159 { C::alu_sel_decompose_a, 1 },
160 { C::alu_a_lo_bits, 64 },
161 { C::alu_a_hi_bits, 64 },
162 { C::alu_a_lo, c_decomp.lo },
163 { C::alu_a_hi, c_decomp.hi },
164 { C::alu_b_lo, b_decomp.lo },
165 { C::alu_b_hi, b_decomp.hi },
166 });
167 }
168 }
169 return res;
170 };
172 return {
173 { C::alu_sel_op_fdiv, 1 },
174 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_FDIV },
175 { C::alu_b_inv, event.b.as_ff() }, // Will be inverted in batch later
176 };
177 }
179 const FF diff = event.a.as_ff() - event.b.as_ff();
180 return {
181 { C::alu_sel_op_eq, 1 },
182 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_EQ },
183 { C::alu_ab_diff_inv, has_error ? 0 : diff }, // Will be inverted in batch later
184 };
185 }
187 // Unconditional columns:
189 { C::alu_sel_op_lt, 1 },
190 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_LT },
191 { C::alu_gt_input_a, event.b },
192 { C::alu_gt_input_b, event.a },
193 };
194
195 // Columns when there is no error:
196 if (!has_error) {
197 res.insert(res.end(),
198 {
199 { C::alu_gt_result_c, event.c.as_ff() == 1 ? 1 : 0 },
200 { C::alu_sel_ff_gt, is_ff ? 1 : 0 },
201 { C::alu_sel_int_gt, is_ff ? 0 : 1 },
202 });
203 }
204 return res;
205 }
207 // Unconditional columns:
209 { C::alu_sel_op_lte, 1 },
210 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_LTE },
211 { C::alu_gt_input_a, event.a },
212 { C::alu_gt_input_b, event.b },
213 };
214
215 // Columns when there is no error:
216 if (!has_error) {
217 res.insert(res.end(),
218 {
219 { C::alu_gt_result_c, event.c.as_ff() == 0 ? 1 : 0 },
220 { C::alu_sel_ff_gt, is_ff ? 1 : 0 },
221 { C::alu_sel_int_gt, is_ff ? 0 : 1 },
222 });
223 }
224 return res;
225 }
227 return {
228 { C::alu_sel_op_not, 1 },
229 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_NOT },
230 };
231 }
233 // Unconditional columns:
235 { C::alu_sel_op_shl, 1 },
236 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_SHL },
237 };
238
239 if (!has_error) {
240 uint128_t a_num = static_cast<uint128_t>(event.a.as_ff());
241 uint128_t b_num = static_cast<uint128_t>(event.b.as_ff());
242 uint128_t max_bits = static_cast<uint128_t>(get_tag_bits(a_tag));
243 // Whether we shift by more than the bit size (=> result is 0):
244 bool overflow = b_num > max_bits;
245 // The bit size of the low limb of decomposed input a (if overflow, assigned as max_bits to range check
246 // otherwise b - max_bits):
247 uint128_t a_lo_bits = overflow ? max_bits : max_bits - b_num;
248 // Cast to uint256_t to be sure that the shift 1 << a_lo_bits is cpp defined behaviour.
249 const uint128_t mask =
250 static_cast<uint128_t>((static_cast<uint256_t>(1) << uint256_t::from_uint128(a_lo_bits)) - 1);
251 // The low limb of decomposed input a (if overflow, assigned as b - max_bits to range check and
252 // prove b > max_bits).
253 uint128_t a_lo =
254 overflow ? b_num - max_bits : a_num & mask; // Make use of x % pow_of_two = x & (pow_of_two - 1)
255 uint128_t a_hi = a_lo_bits >= 128 ? 0 : a_num >> a_lo_bits; // 128-bit shift undefined behaviour guard.
256 uint128_t a_hi_bits = overflow ? max_bits : b_num;
257 res.insert(
258 res.end(),
259 {
260 { C::alu_sel_shift_ops_no_overflow, overflow ? 0 : 1 },
261 { C::alu_sel_decompose_a, 1 },
262 { C::alu_a_lo, a_lo },
263 { C::alu_a_lo_bits, a_lo_bits },
264 { C::alu_a_hi, a_hi },
265 { C::alu_a_hi_bits, a_hi_bits },
266 { C::alu_shift_lo_bits, a_lo_bits },
267 { C::alu_two_pow_shift_lo_bits,
268 overflow ? 0 : static_cast<uint256_t>(1) << uint256_t::from_uint128(a_lo_bits) },
269 { C::alu_helper1, overflow ? 0 : static_cast<uint256_t>(1) << uint256_t::from_uint128(b_num) },
270 });
271 }
272 return res;
273 }
275 // Unconditional columns:
277 { C::alu_sel_op_shr, 1 },
278 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_SHR },
279 };
280
281 if (!has_error) {
282 uint128_t a_num = static_cast<uint128_t>(event.a.as_ff());
283 uint128_t b_num = static_cast<uint128_t>(event.b.as_ff());
284 uint8_t max_bits = get_tag_bits(a_tag);
285 // Whether we shift by more than the bit size (=> result is 0):
286 bool overflow = b_num > max_bits;
287 // The bit size of the low limb of decomposed input a (if overflow, assigned as max_bits to range check
288 // otherwise b.
289 uint8_t a_lo_bits = overflow ? max_bits : static_cast<uint8_t>(b_num);
290 // Cast to uint256_t to be sure that the shift 1 << a_lo_bits is cpp defined behaviour.
291 const uint128_t mask =
292 static_cast<uint128_t>((static_cast<uint256_t>(1) << static_cast<uint256_t>(a_lo_bits)) - 1);
293 // The low limb of decomposed input a (if overflow, assigned as b - max_bits to range check and
294 // prove b > max_bits).
295 uint128_t a_lo =
296 overflow ? b_num - max_bits : a_num & mask; // Make use of x % pow_of_two = x & (pow_of_two - 1)
297 uint128_t a_hi = a_lo_bits >= 128 ? 0 : a_num >> a_lo_bits; // 128-bit shift undefined behaviour guard.
298 uint128_t a_hi_bits = overflow ? max_bits : max_bits - b_num;
299 res.insert(res.end(),
300 {
301 { C::alu_sel_shift_ops_no_overflow, overflow ? 0 : 1 },
302 { C::alu_sel_decompose_a, 1 },
303 { C::alu_a_lo, a_lo },
304 { C::alu_a_lo_bits, a_lo_bits },
305 { C::alu_a_hi, a_hi },
306 { C::alu_a_hi_bits, a_hi_bits },
307 { C::alu_shift_lo_bits, a_lo_bits },
308 { C::alu_two_pow_shift_lo_bits,
309 overflow ? 0 : static_cast<uint256_t>(1) << uint256_t::from_uint128(a_lo_bits) },
310 });
311 }
312 return res;
313 }
315 const uint256_t value = static_cast<uint256_t>(event.a.as_ff());
316 const MemoryTag dst_tag = static_cast<MemoryTag>(static_cast<uint8_t>(event.b.as_ff()));
317 bool is_trivial = dst_tag == MemoryTag::FF || value <= get_tag_max_value(dst_tag);
318 bool is_lt_128 = !is_trivial && value < (static_cast<uint256_t>(1) << 128);
319 bool is_gte_128 = !is_trivial && !is_lt_128;
320 // Make use of x % pow_of_two = x & (pow_of_two - 1)
321 const uint256_t lo_128 = is_trivial ? 0 : value & MASK_128;
322 const uint8_t dst_bits = get_tag_bits(dst_tag);
323 const uint256_t mid = is_trivial ? 0 : lo_128 >> static_cast<uint256_t>(dst_bits);
324
325 return {
326 { C::alu_sel_op_truncate, 1 },
327 { C::alu_op_id, AVM_EXEC_OP_ID_ALU_TRUNCATE },
328 { C::alu_sel_trunc_trivial, is_trivial ? 1 : 0 },
329 { C::alu_sel_trunc_lt_128, is_lt_128 ? 1 : 0 },
330 { C::alu_sel_trunc_gte_128, is_gte_128 ? 1 : 0 },
331 { C::alu_sel_trunc_non_trivial, is_trivial ? 0 : 1 },
332 { C::alu_a_lo, lo_128 },
333 { C::alu_mid, mid },
334 { C::alu_mid_bits, is_trivial ? 0 : 128 - dst_bits },
335 };
336 }
337 default:
338 throw std::runtime_error("Unknown ALU operation");
339 break;
340 }
341}
342
355std::vector<std::pair<C, FF>> get_error_columns(const simulation::AluEvent& event)
356{
357 const MemoryTag a_tag = event.a.get_tag();
358 const FF a_tag_ff = static_cast<FF>(static_cast<uint8_t>(a_tag));
359 const MemoryTag b_tag = event.b.get_tag();
360 const FF b_tag_ff = static_cast<FF>(static_cast<uint8_t>(b_tag));
361
362 std::vector<std::pair<C, FF>> error_columns = {
363 { C::alu_sel_err, 1 },
364 };
365
366 // Case 1:
367 bool ff_tag_err =
368 ((a_tag == MemoryTag::FF) &&
371 ((a_tag != MemoryTag::FF) && (event.operation == simulation::AluOperation::FDIV));
372
373 // Case 2:
374 bool ab_tags_mismatch = (a_tag_ff != b_tag_ff) && (event.operation != simulation::AluOperation::TRUNCATE);
375
376 // Case 3:
377 bool div_0_error =
378 (event.operation == simulation::AluOperation::DIV || event.operation == simulation::AluOperation::FDIV) &&
379 event.b.as_ff() == 0;
380
381 if (ab_tags_mismatch || ff_tag_err) {
382 error_columns.push_back({ C::alu_sel_tag_err, 1 });
383 // Note: There is no 'alu_sel_ff_tag_err' because we can handle this with existing selectors:
384 // (sel_op_div + sel_op_not) * sel_is_ff
385 }
386
387 if (ab_tags_mismatch) {
388 error_columns.push_back({ C::alu_sel_ab_tag_mismatch, 1 });
389 error_columns.push_back({ C::alu_ab_tags_diff_inv, get_tag_diff_inverse(a_tag, b_tag) });
390 }
391
392 if (div_0_error) {
393 error_columns.push_back({ C::alu_sel_div_0_err, 1 });
394 }
395
396 // We shouldn't have emitted an event with an error when one doesn't exist:
397 BB_ASSERT(error_columns.size() != 1, "ALU Event emitted with an error, but none exists");
398
399 return error_columns;
400}
401
402} // namespace
403
411 TraceContainer& trace)
412{
413 // We rely on this for NOT opcode, where b's tag is set to 0 (FF) when a is of FF type.
414 static_assert(static_cast<uint8_t>(MemoryTag::FF) == 0);
415
416 uint32_t row = 0;
417 for (const auto& event : events) {
418 // For TRUNCATE, the destination tag is passed through b in the event, but will be
419 // set to ia_tag in the ALU subtrace. (See alu.pil for more details.).
420 const MemoryTag a_tag = event.operation == simulation::AluOperation::TRUNCATE
421 ? static_cast<MemoryTag>(static_cast<uint8_t>(event.b.as_ff()))
422 : event.a.get_tag();
423 const FF b_tag = static_cast<FF>(static_cast<uint8_t>(event.b.get_tag()));
424 const FF c_tag = static_cast<FF>(static_cast<uint8_t>(event.c.get_tag()));
425
426 if (event.error) {
427 trace.set(row, get_error_columns(event));
428 }
429
430 // Operation specific columns:
431 trace.set(row, get_operation_specific_columns(event));
432
433 // For TRUNCATE, we set b to 0 as the destination tag is passed through b in the event, but will be
434 // set to ia_tag in the ALU subtrace. (See alu.pil for more details.).
435 // Note that setting b to 0 is not required to satisfy the relations but makes the trace cleaner.
436 const FF b_ff = event.operation != simulation::AluOperation::TRUNCATE ? event.b.as_ff() : 0;
437
438 trace.set(row,
439 { {
440 { C::alu_sel, 1 },
441 { C::alu_ia, event.a },
442 { C::alu_ib, b_ff },
443 { C::alu_ic, event.c },
444 { C::alu_ia_tag, static_cast<uint8_t>(a_tag) },
445 { C::alu_ib_tag, b_tag },
446 { C::alu_ic_tag, c_tag },
447 { C::alu_max_bits, get_tag_bits(a_tag) },
448 { C::alu_max_value, get_tag_max_value(a_tag) },
449 { C::alu_sel_is_ff, a_tag == MemoryTag::FF ? 1 : 0 },
450 { C::alu_tag_ff_diff_inv, get_tag_diff_inverse(a_tag, MemoryTag::FF) },
451 { C::alu_sel_is_u128, a_tag == MemoryTag::U128 ? 1 : 0 },
452 { C::alu_tag_u128_diff_inv, get_tag_diff_inverse(a_tag, MemoryTag::U128) },
453 } });
454
455 row++;
456 }
457
458 // Batch invert the columns.
459 trace.invert_columns({ { C::alu_ab_diff_inv, C::alu_b_inv } });
460}
461
465 .add<lookup_alu_range_check_decomposition_a_lo_settings, InteractionType::LookupGeneric>(C::range_check_sel)
467 .add<lookup_alu_range_check_decomposition_b_lo_settings, InteractionType::LookupGeneric>(C::range_check_sel)
469 .add<lookup_alu_range_check_mul_c_hi_settings, InteractionType::LookupGeneric>(C::range_check_sel)
471 .add<lookup_alu_int_gt_settings, InteractionType::LookupGeneric>(C::gt_sel)
473 .add<lookup_alu_range_check_trunc_mid_settings, InteractionType::LookupGeneric>(C::range_check_sel)
475
476} // namespace bb::avm2::tracegen
MemoryTag dst_tag
#define BB_ASSERT(expression,...)
Definition assert.hpp:67
#define AVM_EXEC_OP_ID_ALU_TRUNCATE
#define AVM_EXEC_OP_ID_ALU_LTE
#define AVM_EXEC_OP_ID_ALU_DIV
#define AVM_EXEC_OP_ID_ALU_ADD
#define AVM_EXEC_OP_ID_ALU_SHL
#define AVM_EXEC_OP_ID_ALU_EQ
#define AVM_EXEC_OP_ID_ALU_SUB
#define AVM_EXEC_OP_ID_ALU_NOT
#define AVM_EXEC_OP_ID_ALU_MUL
#define AVM_EXEC_OP_ID_ALU_FDIV
#define AVM_EXEC_OP_ID_ALU_SHR
#define AVM_EXEC_OP_ID_ALU_LT
static const InteractionDefinition interactions
Definition alu_trace.hpp:20
void process(const simulation::EventEmitterInterface< simulation::AluEvent >::Container &events, TraceContainer &trace)
Process the ALU events and populate the ALU relevant columns in the trace.
InteractionDefinition & add(auto &&... args)
static constexpr uint256_t from_uint128(const uint128_t a) noexcept
Definition uint256.hpp:94
TestTraceContainer trace
U128Decomposition decompose_128(const uint128_t &x)
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
constexpr uint128_t MASK_64
Definition constants.hpp:23
constexpr uint256_t MASK_128
Definition constants.hpp:22
lookup_settings< lookup_alu_large_trunc_canonical_dec_settings_ > lookup_alu_large_trunc_canonical_dec_settings
lookup_settings< lookup_alu_ff_gt_settings_ > lookup_alu_ff_gt_settings
uint8_t get_tag_bits(ValueTag tag)
lookup_settings< lookup_alu_shifts_two_pow_settings_ > lookup_alu_shifts_two_pow_settings
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
ValueTag MemoryTag
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