Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
execution_trace.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <cstddef>
6#include <numeric>
7#include <ranges>
8#include <stdexcept>
9
37
42
43namespace bb::avm2::tracegen {
44namespace {
45
46constexpr std::array<C, AVM_MAX_OPERANDS> OPERAND_COLUMNS = {
47 C::execution_op_0_, C::execution_op_1_, C::execution_op_2_, C::execution_op_3_,
48 C::execution_op_4_, C::execution_op_5_, C::execution_op_6_,
49};
50constexpr std::array<C, AVM_MAX_OPERANDS> OPERAND_IS_ADDRESS_COLUMNS = {
51 C::execution_sel_op_is_address_0_, C::execution_sel_op_is_address_1_, C::execution_sel_op_is_address_2_,
52 C::execution_sel_op_is_address_3_, C::execution_sel_op_is_address_4_, C::execution_sel_op_is_address_5_,
53 C::execution_sel_op_is_address_6_,
54};
55constexpr std::array<C, AVM_MAX_OPERANDS> OPERAND_AFTER_RELATIVE_COLUMNS = {
56 C::execution_op_after_relative_0_, C::execution_op_after_relative_1_, C::execution_op_after_relative_2_,
57 C::execution_op_after_relative_3_, C::execution_op_after_relative_4_, C::execution_op_after_relative_5_,
58 C::execution_op_after_relative_6_,
59};
60constexpr std::array<C, AVM_MAX_OPERANDS> RESOLVED_OPERAND_COLUMNS = {
61 C::execution_rop_0_, C::execution_rop_1_, C::execution_rop_2_, C::execution_rop_3_,
62 C::execution_rop_4_, C::execution_rop_5_, C::execution_rop_6_,
63};
64constexpr std::array<C, AVM_MAX_OPERANDS> RESOLVED_OPERAND_TAG_COLUMNS = {
65 C::execution_rop_tag_0_, C::execution_rop_tag_1_, C::execution_rop_tag_2_, C::execution_rop_tag_3_,
66 C::execution_rop_tag_4_, C::execution_rop_tag_5_, C::execution_rop_tag_6_,
67};
68constexpr std::array<C, AVM_MAX_OPERANDS> OPERAND_APPLY_INDIRECTION_COLUMNS = {
69 C::execution_sel_apply_indirection_0_, C::execution_sel_apply_indirection_1_, C::execution_sel_apply_indirection_2_,
70 C::execution_sel_apply_indirection_3_, C::execution_sel_apply_indirection_4_, C::execution_sel_apply_indirection_5_,
71 C::execution_sel_apply_indirection_6_,
72};
73constexpr std::array<C, AVM_MAX_OPERANDS> OPERAND_RELATIVE_OVERFLOW_COLUMNS = {
74 C::execution_sel_relative_overflow_0_, C::execution_sel_relative_overflow_1_, C::execution_sel_relative_overflow_2_,
75 C::execution_sel_relative_overflow_3_, C::execution_sel_relative_overflow_4_, C::execution_sel_relative_overflow_5_,
76 C::execution_sel_relative_overflow_6_,
77};
78constexpr std::array<C, AVM_MAX_OPERANDS> OPERAND_IS_RELATIVE_VALID_BASE_COLUMNS = {
79 C::execution_sel_op_do_overflow_check_0_, C::execution_sel_op_do_overflow_check_1_,
80 C::execution_sel_op_do_overflow_check_2_, C::execution_sel_op_do_overflow_check_3_,
81 C::execution_sel_op_do_overflow_check_4_, C::execution_sel_op_do_overflow_check_5_,
82 C::execution_sel_op_do_overflow_check_6_,
83};
84constexpr size_t TOTAL_INDIRECT_BITS = 16;
85static_assert(static_cast<size_t>(AVM_MAX_OPERANDS) * 2 <= TOTAL_INDIRECT_BITS);
86constexpr std::array<C, TOTAL_INDIRECT_BITS / 2> OPERAND_IS_RELATIVE_WIRE_COLUMNS = {
87 C::execution_sel_op_is_relative_wire_0_, C::execution_sel_op_is_relative_wire_1_,
88 C::execution_sel_op_is_relative_wire_2_, C::execution_sel_op_is_relative_wire_3_,
89 C::execution_sel_op_is_relative_wire_4_, C::execution_sel_op_is_relative_wire_5_,
90 C::execution_sel_op_is_relative_wire_6_, C::execution_sel_op_is_relative_wire_7_,
91
92};
93constexpr std::array<C, TOTAL_INDIRECT_BITS / 2> OPERAND_IS_INDIRECT_WIRE_COLUMNS = {
94 C::execution_sel_op_is_indirect_wire_0_, C::execution_sel_op_is_indirect_wire_1_,
95 C::execution_sel_op_is_indirect_wire_2_, C::execution_sel_op_is_indirect_wire_3_,
96 C::execution_sel_op_is_indirect_wire_4_, C::execution_sel_op_is_indirect_wire_5_,
97 C::execution_sel_op_is_indirect_wire_6_, C::execution_sel_op_is_indirect_wire_7_,
98};
99
100constexpr std::array<C, AVM_MAX_REGISTERS> REGISTER_COLUMNS = {
101 C::execution_register_0_, C::execution_register_1_, C::execution_register_2_,
102 C::execution_register_3_, C::execution_register_4_, C::execution_register_5_,
103};
104constexpr std::array<C, AVM_MAX_REGISTERS> REGISTER_MEM_TAG_COLUMNS = {
105 C::execution_mem_tag_reg_0_, C::execution_mem_tag_reg_1_, C::execution_mem_tag_reg_2_,
106 C::execution_mem_tag_reg_3_, C::execution_mem_tag_reg_4_, C::execution_mem_tag_reg_5_,
107};
108constexpr std::array<C, AVM_MAX_REGISTERS> REGISTER_IS_WRITE_COLUMNS = {
109 C::execution_rw_reg_0_, C::execution_rw_reg_1_, C::execution_rw_reg_2_,
110 C::execution_rw_reg_3_, C::execution_rw_reg_4_, C::execution_rw_reg_5_,
111};
112constexpr std::array<C, AVM_MAX_REGISTERS> REGISTER_MEM_OP_COLUMNS = {
113 C::execution_sel_mem_op_reg_0_, C::execution_sel_mem_op_reg_1_, C::execution_sel_mem_op_reg_2_,
114 C::execution_sel_mem_op_reg_3_, C::execution_sel_mem_op_reg_4_, C::execution_sel_mem_op_reg_5_,
115};
116constexpr std::array<C, AVM_MAX_REGISTERS> REGISTER_EXPECTED_TAG_COLUMNS = {
117 C::execution_expected_tag_reg_0_, C::execution_expected_tag_reg_1_, C::execution_expected_tag_reg_2_,
118 C::execution_expected_tag_reg_3_, C::execution_expected_tag_reg_4_, C::execution_expected_tag_reg_5_,
119};
120constexpr std::array<C, AVM_MAX_REGISTERS> REGISTER_TAG_CHECK_COLUMNS = {
121 C::execution_sel_tag_check_reg_0_, C::execution_sel_tag_check_reg_1_, C::execution_sel_tag_check_reg_2_,
122 C::execution_sel_tag_check_reg_3_, C::execution_sel_tag_check_reg_4_, C::execution_sel_tag_check_reg_5_,
123};
124constexpr std::array<C, AVM_MAX_REGISTERS> REGISTER_OP_REG_EFFECTIVE_COLUMNS = {
125 C::execution_sel_op_reg_effective_0_, C::execution_sel_op_reg_effective_1_, C::execution_sel_op_reg_effective_2_,
126 C::execution_sel_op_reg_effective_3_, C::execution_sel_op_reg_effective_4_, C::execution_sel_op_reg_effective_5_,
127};
128
136C get_execution_opcode_selector(ExecutionOpCode exec_opcode)
137{
138 switch (exec_opcode) {
140 return C::execution_sel_execute_get_env_var;
142 return C::execution_sel_execute_mov;
144 return C::execution_sel_execute_jump;
146 return C::execution_sel_execute_jumpi;
148 return C::execution_sel_execute_call;
150 return C::execution_sel_execute_static_call;
152 return C::execution_sel_execute_internal_call;
154 return C::execution_sel_execute_internal_return;
156 return C::execution_sel_execute_return;
158 return C::execution_sel_execute_revert;
160 return C::execution_sel_execute_success_copy;
162 return C::execution_sel_execute_returndata_size;
164 return C::execution_sel_execute_debug_log;
166 return C::execution_sel_execute_sload;
168 return C::execution_sel_execute_sstore;
170 return C::execution_sel_execute_notehash_exists;
172 return C::execution_sel_execute_emit_notehash;
174 return C::execution_sel_execute_l1_to_l2_message_exists;
176 return C::execution_sel_execute_nullifier_exists;
178 return C::execution_sel_execute_emit_nullifier;
180 return C::execution_sel_execute_send_l2_to_l1_msg;
181 default:
182 throw std::runtime_error("Execution opcode does not have a corresponding selector");
183 }
184}
185
189struct FailingContexts {
190 bool app_logic_failure = false;
191 bool teardown_failure = false;
194 unordered_flat_set<uint32_t> does_context_fail;
195};
196
208FailingContexts preprocess_for_discard(
210{
211 FailingContexts dying_info;
212
213 // We use `after_context_event` to retrieve parent_id, context_id, and phase to be consistent with
214 // how these values are populated in the trace (see ExecutionTraceBuilder::process()). These values
215 // should not change during the life-cycle of an execution event though and before_context_event
216 // would lead to the same results.
217
218 // Preprocessing pass 1: find the events that exit the app logic and teardown phases
219 for (const auto& ex_event : ex_events) {
220 bool is_exit = ex_event.is_exit();
221 bool is_top_level = ex_event.after_context_event.parent_id == 0;
222
223 if (is_exit && is_top_level) {
224 if (ex_event.after_context_event.phase == TransactionPhase::APP_LOGIC) {
225 dying_info.app_logic_failure = ex_event.is_failure();
226 dying_info.app_logic_exit_context_id = ex_event.after_context_event.id;
227 } else if (ex_event.after_context_event.phase == TransactionPhase::TEARDOWN) {
228 dying_info.teardown_failure = ex_event.is_failure();
229 dying_info.teardown_exit_context_id = ex_event.after_context_event.id;
230 break; // Teardown is the last phase we care about
231 }
232 }
233 }
234
235 // Preprocessing pass 2: find all contexts that fail and mark them
236 for (const auto& ex_event : ex_events) {
237 if (ex_event.is_failure()) {
238 dying_info.does_context_fail.insert(ex_event.after_context_event.id);
239 }
240 }
241
242 return dying_info;
243}
244
252bool is_phase_discarded(TransactionPhase phase, const FailingContexts& failures)
253{
254 // Note that app logic also gets discarded if teardown failures
255 return (phase == TransactionPhase::APP_LOGIC && (failures.app_logic_failure || failures.teardown_failure)) ||
256 (phase == TransactionPhase::TEARDOWN && failures.teardown_failure);
257}
258
266uint32_t dying_context_for_phase(TransactionPhase phase, const FailingContexts& failures)
267{
269 "Execution events must have app logic or teardown phase");
270
271 switch (phase) {
273 if (failures.app_logic_failure) {
274 return failures.app_logic_exit_context_id;
275 }
276
277 // Note that app logic also gets discarded if teardown failures
278 if (failures.teardown_failure) {
279 return failures.teardown_exit_context_id;
280 }
281
282 return 0;
283 }
285 return failures.teardown_failure ? failures.teardown_exit_context_id : 0;
286 default:
287 __builtin_unreachable(); // tell the compiler "we never reach here"
288 }
289}
290
291} // namespace
292
319{
320 uint32_t row = 1; // We start from row 1 because this trace contains shifted columns.
321
322 // Preprocess events to determine which contexts will fail
323 const FailingContexts failures = preprocess_for_discard(ex_events);
324
325 // Some variables updated per loop iteration to track
326 // whether or not the upcoming row should "discard" [side effects].
327 uint32_t dying_context_id = 0;
328 // dying_context_id captures whether we discard or not. Namely, discard == 1 <=> dying_context_id != 0
329 // is a circuit invariant. For this reason, we use a lambda to preserve the invariant.
330 auto is_discarding = [&dying_context_id]() { return dying_context_id != 0; };
331 bool is_first_event_in_enqueued_call = true;
332 bool prev_row_was_enter_call = false;
333
334 for (const auto& ex_event : ex_events) {
335 // Check if this is the first event in an enqueued call and whether
336 // the phase should be discarded
337 if (!is_discarding() && is_first_event_in_enqueued_call &&
338 is_phase_discarded(ex_event.after_context_event.phase, failures)) {
339 dying_context_id = dying_context_for_phase(ex_event.after_context_event.phase, failures);
340 }
341
342 const bool has_parent = ex_event.after_context_event.parent_id != 0;
343
344 /**************************************************************************************************
345 * Setup.
346 **************************************************************************************************/
347
348 trace.set(
349 row,
350 { {
351 { C::execution_sel, 1 },
352 { C::execution_clk, row },
353 // Selectors that indicate "dispatch" from tx trace
354 // Note: Enqueued Call End is determined during the opcode execution temporality group
355 { C::execution_enqueued_call_start, is_first_event_in_enqueued_call ? 1 : 0 },
356 // Context
357 { C::execution_context_id, ex_event.after_context_event.id },
358 { C::execution_parent_id, ex_event.after_context_event.parent_id },
359 // Warning: pc in after_context_event is the pc of the next instruction, not the current instruction.
360 { C::execution_pc, ex_event.before_context_event.pc },
361 { C::execution_msg_sender, ex_event.after_context_event.msg_sender },
362 { C::execution_contract_address, ex_event.after_context_event.contract_addr },
363 { C::execution_transaction_fee, ex_event.after_context_event.transaction_fee },
364 { C::execution_is_static, ex_event.after_context_event.is_static },
365 { C::execution_parent_calldata_addr, ex_event.after_context_event.parent_cd_addr },
366 { C::execution_parent_calldata_size, ex_event.after_context_event.parent_cd_size },
367 { C::execution_last_child_returndata_addr, ex_event.after_context_event.last_child_rd_addr },
368 { C::execution_last_child_returndata_size, ex_event.after_context_event.last_child_rd_size },
369 { C::execution_last_child_success, ex_event.after_context_event.last_child_success },
370 { C::execution_last_child_id, ex_event.after_context_event.last_child_id },
371 { C::execution_l2_gas_limit, ex_event.after_context_event.gas_limit.l2_gas },
372 { C::execution_da_gas_limit, ex_event.after_context_event.gas_limit.da_gas },
373 { C::execution_l2_gas_used, ex_event.after_context_event.gas_used.l2_gas },
374 { C::execution_da_gas_used, ex_event.after_context_event.gas_used.da_gas },
375 { C::execution_parent_l2_gas_limit, ex_event.after_context_event.parent_gas_limit.l2_gas },
376 { C::execution_parent_da_gas_limit, ex_event.after_context_event.parent_gas_limit.da_gas },
377 { C::execution_parent_l2_gas_used, ex_event.after_context_event.parent_gas_used.l2_gas },
378 { C::execution_parent_da_gas_used, ex_event.after_context_event.parent_gas_used.da_gas },
379 { C::execution_next_context_id, ex_event.next_context_id },
380 // Context - gas.
381 { C::execution_prev_l2_gas_used, ex_event.before_context_event.gas_used.l2_gas },
382 { C::execution_prev_da_gas_used, ex_event.before_context_event.gas_used.da_gas },
383 // Context - tree states
384 // Context - tree states - Written public data slots tree
385 { C::execution_prev_written_public_data_slots_tree_root,
386 ex_event.before_context_event.written_public_data_slots_tree_snapshot.root },
387 { C::execution_prev_written_public_data_slots_tree_size,
388 ex_event.before_context_event.written_public_data_slots_tree_snapshot.next_available_leaf_index },
389 { C::execution_written_public_data_slots_tree_root,
390 ex_event.after_context_event.written_public_data_slots_tree_snapshot.root },
391 { C::execution_written_public_data_slots_tree_size,
392 ex_event.after_context_event.written_public_data_slots_tree_snapshot.next_available_leaf_index },
393 // Context - tree states - Nullifier tree
394 { C::execution_prev_nullifier_tree_root,
395 ex_event.before_context_event.tree_states.nullifier_tree.tree.root },
396 { C::execution_prev_nullifier_tree_size,
397 ex_event.before_context_event.tree_states.nullifier_tree.tree.next_available_leaf_index },
398 { C::execution_prev_num_nullifiers_emitted,
399 ex_event.before_context_event.tree_states.nullifier_tree.counter },
400 { C::execution_nullifier_tree_root, ex_event.after_context_event.tree_states.nullifier_tree.tree.root },
401 { C::execution_nullifier_tree_size,
402 ex_event.after_context_event.tree_states.nullifier_tree.tree.next_available_leaf_index },
403 { C::execution_num_nullifiers_emitted,
404 ex_event.after_context_event.tree_states.nullifier_tree.counter },
405 // Context - tree states - Public data tree
406 { C::execution_prev_public_data_tree_root,
407 ex_event.before_context_event.tree_states.public_data_tree.tree.root },
408 { C::execution_prev_public_data_tree_size,
409 ex_event.before_context_event.tree_states.public_data_tree.tree.next_available_leaf_index },
410 { C::execution_public_data_tree_root,
411 ex_event.after_context_event.tree_states.public_data_tree.tree.root },
412 { C::execution_public_data_tree_size,
413 ex_event.after_context_event.tree_states.public_data_tree.tree.next_available_leaf_index },
414 // Context - tree states - Note hash tree
415 { C::execution_prev_note_hash_tree_root,
416 ex_event.before_context_event.tree_states.note_hash_tree.tree.root },
417 { C::execution_prev_note_hash_tree_size,
418 ex_event.before_context_event.tree_states.note_hash_tree.tree.next_available_leaf_index },
419 { C::execution_prev_num_note_hashes_emitted,
420 ex_event.before_context_event.tree_states.note_hash_tree.counter },
421 { C::execution_note_hash_tree_root, ex_event.after_context_event.tree_states.note_hash_tree.tree.root },
422 { C::execution_note_hash_tree_size,
423 ex_event.after_context_event.tree_states.note_hash_tree.tree.next_available_leaf_index },
424 { C::execution_num_note_hashes_emitted,
425 ex_event.after_context_event.tree_states.note_hash_tree.counter },
426 // Context - tree states - L1 to L2 message tree
427 { C::execution_l1_l2_tree_root,
428 ex_event.after_context_event.tree_states.l1_to_l2_message_tree.tree.root },
429 // Context - tree states - Retrieved bytecodes tree
430 { C::execution_prev_retrieved_bytecodes_tree_root,
431 ex_event.before_context_event.retrieved_bytecodes_tree_snapshot.root },
432 { C::execution_prev_retrieved_bytecodes_tree_size,
433 ex_event.before_context_event.retrieved_bytecodes_tree_snapshot.next_available_leaf_index },
434 { C::execution_retrieved_bytecodes_tree_root,
435 ex_event.after_context_event.retrieved_bytecodes_tree_snapshot.root },
436 { C::execution_retrieved_bytecodes_tree_size,
437 ex_event.after_context_event.retrieved_bytecodes_tree_snapshot.next_available_leaf_index },
438 // Context - side effects
439 { C::execution_prev_num_public_log_fields, ex_event.before_context_event.numPublicLogFields },
440 { C::execution_num_public_log_fields, ex_event.after_context_event.numPublicLogFields },
441 { C::execution_prev_num_l2_to_l1_messages, ex_event.before_context_event.numL2ToL1Messages },
442 { C::execution_num_l2_to_l1_messages, ex_event.after_context_event.numL2ToL1Messages },
443 // Helpers for identifying parent context
444 { C::execution_has_parent_ctx, has_parent ? 1 : 0 },
445 { C::execution_is_parent_id_inv, ex_event.after_context_event.parent_id }, // Will be inverted in batch.
446 } });
447
448 // Internal stack
449 // Important: It is crucial to use `before_context_event` to populate the internal call stack columns because
450 // these values are mutated by the internal call and return opcodes and therefore
451 // `after_context_event` would populate incorrect values.
452 const auto& internal_call_return_id = ex_event.before_context_event.internal_call_return_id;
453 trace.set(row,
454 { {
455 { C::execution_internal_call_id, ex_event.before_context_event.internal_call_id },
456 { C::execution_internal_call_return_id, internal_call_return_id },
457 { C::execution_next_internal_call_id, ex_event.before_context_event.next_internal_call_id },
458 } });
459
460 /**************************************************************************************************
461 * Temporality group 1: Bytecode retrieval.
462 **************************************************************************************************/
463
464 const bool bytecode_retrieval_failed = ex_event.error == ExecutionError::BYTECODE_RETRIEVAL;
465 const bool sel_first_row_in_context = prev_row_was_enter_call || is_first_event_in_enqueued_call;
466 trace.set(row,
467 { {
468 { C::execution_sel_first_row_in_context, sel_first_row_in_context ? 1 : 0 },
469 { C::execution_sel_bytecode_retrieval_failure, bytecode_retrieval_failed ? 1 : 0 },
470 { C::execution_sel_bytecode_retrieval_success, !bytecode_retrieval_failed ? 1 : 0 },
471 { C::execution_bytecode_id, ex_event.after_context_event.bytecode_id },
472 } });
473
474 /**************************************************************************************************
475 * Temporality group 2: Instruction fetching. Mapping from wire to execution and addressing.
476 **************************************************************************************************/
477
478 // This will only have a value if instruction fetching succeeded.
480 // Set whether instruction fetching failed (sel_parsing_err in instr_fetching.pil).
481 const bool error_in_instruction_fetching = ex_event.error == ExecutionError::INSTRUCTION_FETCHING;
482 // If bytecode retrieval failed, we cannot fetch any instructions.
483 const bool instruction_fetching_success = !bytecode_retrieval_failed && !error_in_instruction_fetching;
484 // We do not need to check bytecode_retrieval_failed below (unlike #[NO_FETCHING_NO_INSTR_FETCH_ERROR]) because
485 // ExecutionError is an enum, enforcing mutual exclusivity.
486 trace.set(C::execution_sel_instruction_fetching_failure, row, error_in_instruction_fetching ? 1 : 0);
487
488 if (instruction_fetching_success) {
489 exec_opcode = ex_event.wire_instruction.get_exec_opcode();
490 process_instr_fetching(ex_event.wire_instruction, trace, row);
491
492 // If we fetched an instruction successfully, we can set the next PC.
493 // In circuit, we enforce next_pc to be pc + instr_size, but in simulation,
494 // we set next_pc (as member of the context) to be the real pc of the next instruction
495 // which is different for JUMP, JUMPI, INTERNALCALL, and INTERNALRETURN.
496 // Therefore, we must not use after_context_event.pc (which is simulation next_pc) to set
497 // C::execution_next_pc.
498 trace.set(row,
499 { {
500 { C::execution_next_pc,
501 static_cast<uint32_t>(ex_event.before_context_event.pc +
502 ex_event.wire_instruction.size_in_bytes()) },
503 } });
504
505 // Along this function we need to set the info we get from the #[EXEC_SPEC_READ] lookup.
506 process_execution_spec(ex_event, trace, row);
507
508 process_addressing(ex_event.addressing_event, ex_event.wire_instruction, trace, row);
509 }
510
511 const bool addressing_failed = ex_event.error == ExecutionError::ADDRESSING;
512
513 /**************************************************************************************************
514 * Temporality group 3: Registers read.
515 **************************************************************************************************/
516
517 // Note that if addressing did not fail, register reading will be performed.
519 std::ranges::fill(registers, MemoryValue::from_tag(static_cast<MemoryTag>(0), 0));
520 const bool do_process_registers = instruction_fetching_success && !addressing_failed;
521 const bool register_processing_failed = ex_event.error == ExecutionError::REGISTER_READ;
522 if (do_process_registers) {
524 *exec_opcode, ex_event.inputs, ex_event.output, registers, register_processing_failed, trace, row);
525 }
526
527 /**************************************************************************************************
528 * Temporality group 4: Gas (both base and dynamic).
529 **************************************************************************************************/
530
531 const bool check_gas = do_process_registers && !register_processing_failed;
532 if (check_gas) {
533 process_gas(ex_event.gas_event, *exec_opcode, trace, row);
534
535 // To_Radix Dynamic Gas Factor related selectors.
536 // We need the register information to compute dynamic gas factor and process_gas() does not have
537 // access to it and nor should it.
538 if (*exec_opcode == ExecutionOpCode::TORADIXBE) {
539 uint32_t radix = ex_event.inputs[1].as<uint32_t>(); // Safe since already tag checked
540 uint32_t num_limbs = ex_event.inputs[2].as<uint32_t>(); // Safe since already tag checked
541 uint32_t num_p_limbs = radix > 256 ? 32 : static_cast<uint32_t>(get_p_limbs_per_radix_size(radix));
542 trace.set(row,
543 { {
544 // To Radix BE Dynamic Gas
545 { C::execution_two_five_six, 256 },
546 { C::execution_sel_radix_gt_256, radix > 256 ? 1 : 0 },
547 { C::execution_sel_lookup_num_p_limbs, radix <= 256 ? 1 : 0 },
548 { C::execution_num_p_limbs, num_p_limbs },
549 { C::execution_sel_use_num_limbs, num_limbs > num_p_limbs ? 1 : 0 },
550 // Don't set dyn gas factor here since already set in process_gas
551 } });
552 } else if (*exec_opcode == ExecutionOpCode::SSTORE) {
553 trace.set(row,
554 { {
555 // SSTORE Dynamic Gas
556 { C::execution_written_slots_tree_height, AVM_WRITTEN_PUBLIC_DATA_SLOTS_TREE_HEIGHT },
557 { C::execution_written_slots_merkle_separator, DOM_SEP__WRITTEN_SLOTS_MERKLE },
558 { C::execution_written_slots_tree_siloing_separator, DOM_SEP__PUBLIC_LEAF_SLOT },
559 } });
560 }
561 }
562
563 const bool oog = ex_event.error == ExecutionError::GAS;
564 /**************************************************************************************************
565 * Temporality group 5: Opcode execution.
566 **************************************************************************************************/
567
568 const bool execute_opcode = check_gas && !oog;
569
570 // These booleans are used after of the "opcode code execution" block but need
571 // to be set as part of the "opcode code execution" block.
572 bool sel_enter_call = false;
573 bool sel_exit_call = false;
574 bool execute_revert = false;
575
576 const bool opcode_execution_failed = ex_event.error == ExecutionError::OPCODE_EXECUTION;
577 if (execute_opcode) {
578 // At this point we can assume instruction fetching succeeded, so this should never fail.
579 const auto& dispatch_to_subtrace = get_subtrace_info_map().at(*exec_opcode);
580 trace.set(row,
581 { {
582 { C::execution_sel_execute_opcode, 1 },
583 { C::execution_sel_opcode_error, opcode_execution_failed ? 1 : 0 },
584 { get_subtrace_selector(dispatch_to_subtrace.subtrace_selector), 1 },
585 } });
586
587 // Execution Trace opcodes - separating for clarity
588 if (dispatch_to_subtrace.subtrace_selector == SubtraceSel::EXECUTION) {
589 trace.set(get_execution_opcode_selector(*exec_opcode), row, 1);
590 }
591
592 // Execution trace opcodes specific logic.
593 // Note that the opcode selectors were set above. (e.g., sel_execute_call, sel_execute_static_call, ..).
594 if (*exec_opcode == ExecutionOpCode::CALL || *exec_opcode == ExecutionOpCode::STATICCALL) {
595 sel_enter_call = true;
596
597 const Gas gas_left = ex_event.after_context_event.gas_limit - ex_event.after_context_event.gas_used;
598
599 uint32_t allocated_l2_gas = registers[0].as<uint32_t>();
600 bool is_l2_gas_left_gt_allocated = gas_left.l2_gas > allocated_l2_gas;
601
602 uint32_t allocated_da_gas = registers[1].as<uint32_t>();
603 bool is_da_gas_left_gt_allocated = gas_left.da_gas > allocated_da_gas;
604
605 trace.set(row,
606 { {
607 { C::execution_sel_enter_call, 1 },
608 { C::execution_l2_gas_left, gas_left.l2_gas },
609 { C::execution_da_gas_left, gas_left.da_gas },
610 { C::execution_is_l2_gas_left_gt_allocated, is_l2_gas_left_gt_allocated ? 1 : 0 },
611 { C::execution_is_da_gas_left_gt_allocated, is_da_gas_left_gt_allocated ? 1 : 0 },
612 } });
613 } else if (*exec_opcode == ExecutionOpCode::RETURN) {
614 sel_exit_call = true;
615 trace.set(row,
616 { {
617 { C::execution_nested_return, has_parent ? 1 : 0 },
618 } });
619 } else if (*exec_opcode == ExecutionOpCode::REVERT) {
620 sel_exit_call = true;
621 execute_revert = true;
622 } else if (exec_opcode == ExecutionOpCode::GETENVVAR) {
623 BB_ASSERT_EQ(ex_event.addressing_event.resolution_info.size(),
624 static_cast<size_t>(2),
625 "GETENVVAR should have exactly two resolved operands (envvar enum and output)");
626 // rop[1] is the envvar enum
627 Operand envvar_enum = ex_event.addressing_event.resolution_info[1].resolved_operand;
628 process_get_env_var_opcode(envvar_enum, ex_event.output, trace, row);
629 } else if (*exec_opcode == ExecutionOpCode::INTERNALRETURN) {
630 if (!opcode_execution_failed) {
631 // If we have an opcode error, we don't need to compute the inverse (see internal_call.pil)
632 trace.set(C::execution_internal_call_return_id_inv,
633 row,
634 internal_call_return_id); // Will be inverted in batch later.
635 trace.set(C::execution_sel_read_unwind_call_stack, row, 1);
636 }
637 } else if (*exec_opcode == ExecutionOpCode::SSTORE) {
638 // Equivalent to PIL's (MAX + INITIAL_SIZE - prev_written_public_data_slots_tree_size)
639 // since prev_size = counter + 1 and INITIAL_SIZE = 1.
640 uint32_t remaining_data_writes = MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX -
641 ex_event.before_context_event.tree_states.public_data_tree.counter;
642
643 trace.set(row,
644 { {
645 { C::execution_max_data_writes_reached, (remaining_data_writes == 0) ? 1 : 0 },
646 { C::execution_remaining_data_writes_inv,
647 remaining_data_writes }, // Will be inverted in batch later.
648 { C::execution_sel_write_public_data, opcode_execution_failed ? 0 : 1 },
649 // written_slots_tree_height, _merkle_separator, _tree_siloing_separator
650 // are set in the check_gas SSTORE branch above (check_gas covers execute_opcode).
651 } });
652 } else if (*exec_opcode == ExecutionOpCode::NOTEHASHEXISTS) {
653 uint64_t leaf_index = registers[1].as<uint64_t>();
654 uint64_t note_hash_tree_leaf_count = NOTE_HASH_TREE_LEAF_COUNT;
655 bool note_hash_leaf_in_range = leaf_index < note_hash_tree_leaf_count;
656
657 trace.set(row,
658 { {
659 { C::execution_note_hash_leaf_in_range, note_hash_leaf_in_range ? 1 : 0 },
660 { C::execution_note_hash_tree_leaf_count, FF(note_hash_tree_leaf_count) },
661 } });
662 } else if (*exec_opcode == ExecutionOpCode::EMITNOTEHASH) {
663 uint32_t remaining_note_hashes =
664 MAX_NOTE_HASHES_PER_TX - ex_event.before_context_event.tree_states.note_hash_tree.counter;
665
666 trace.set(row,
667 { {
668 { C::execution_sel_reached_max_note_hashes, (remaining_note_hashes == 0) ? 1 : 0 },
669 { C::execution_remaining_note_hashes_inv,
670 remaining_note_hashes }, // Will be inverted in batch later.
671 { C::execution_sel_write_note_hash, opcode_execution_failed ? 0 : 1 },
672 } });
673 } else if (*exec_opcode == ExecutionOpCode::L1TOL2MSGEXISTS) {
674 uint64_t leaf_index = registers[1].as<uint64_t>();
675 uint64_t l1_to_l2_msg_tree_leaf_count = L1_TO_L2_MSG_TREE_LEAF_COUNT;
676 bool l1_to_l2_msg_leaf_in_range = leaf_index < l1_to_l2_msg_tree_leaf_count;
677
678 trace.set(row,
679 { {
680 { C::execution_l1_to_l2_msg_leaf_in_range, l1_to_l2_msg_leaf_in_range ? 1 : 0 },
681 { C::execution_l1_to_l2_msg_tree_leaf_count, FF(l1_to_l2_msg_tree_leaf_count) },
682 } });
683 } else if (exec_opcode == ExecutionOpCode::NULLIFIEREXISTS) {
684 trace.set(row,
685 { {
686 { C::execution_nullifier_tree_height, NULLIFIER_TREE_HEIGHT },
687 { C::execution_nullifier_merkle_separator, DOM_SEP__NULLIFIER_MERKLE },
688 } });
689 } else if (*exec_opcode == ExecutionOpCode::EMITNULLIFIER) {
690 uint32_t remaining_nullifiers =
691 MAX_NULLIFIERS_PER_TX - ex_event.before_context_event.tree_states.nullifier_tree.counter;
692
693 trace.set(row,
694 { { { C::execution_sel_reached_max_nullifiers, (remaining_nullifiers == 0) ? 1 : 0 },
695 { C::execution_remaining_nullifiers_inv,
696 remaining_nullifiers }, // Will be inverted in batch later.
697 { C::execution_sel_write_nullifier,
698 (remaining_nullifiers != 0 && !ex_event.before_context_event.is_static) ? 1 : 0 },
699 { C::execution_nullifier_pi_offset,
701 ex_event.before_context_event.tree_states.nullifier_tree.counter },
702 { C::execution_nullifier_tree_height, NULLIFIER_TREE_HEIGHT },
703 { C::execution_nullifier_merkle_separator, DOM_SEP__NULLIFIER_MERKLE },
704 { C::execution_nullifier_siloing_separator, DOM_SEP__SILOED_NULLIFIER } } });
705 } else if (*exec_opcode == ExecutionOpCode::SENDL2TOL1MSG) {
706 uint32_t remaining_l2_to_l1_msgs =
707 MAX_L2_TO_L1_MSGS_PER_TX - ex_event.before_context_event.numL2ToL1Messages;
708
709 FF recipient = registers[0].as<FF>();
710 bool sel_too_large_recipient_error =
711 static_cast<uint256_t>(recipient) > static_cast<uint256_t>(MAX_ETH_ADDRESS_VALUE);
712
713 trace.set(
714 row,
715 { { { C::execution_sel_l2_to_l1_msg_limit_error, (remaining_l2_to_l1_msgs == 0) ? 1 : 0 },
716 { C::execution_remaining_l2_to_l1_msgs_inv,
717 remaining_l2_to_l1_msgs }, // Will be inverted in batch later.
718 { C::execution_max_eth_address_value, FF(MAX_ETH_ADDRESS_VALUE) },
719 { C::execution_sel_too_large_recipient_error, sel_too_large_recipient_error ? 1 : 0 },
720 { C::execution_sel_write_l2_to_l1_msg, (!opcode_execution_failed && !is_discarding()) ? 1 : 0 },
721 {
722 C::execution_public_inputs_index,
724 ex_event.before_context_event.numL2ToL1Messages,
725 } } });
726 }
727 }
728
729 /**************************************************************************************************
730 * Temporality group 6: Register write.
731 **************************************************************************************************/
732
733 const bool do_process_register_write = execute_opcode && !opcode_execution_failed;
734 if (do_process_register_write) {
735 process_registers_write(*exec_opcode, trace, row);
736 }
737
738 /**************************************************************************************************
739 * Discarding and error related selectors.
740 **************************************************************************************************/
741
742 const bool is_dying_context = ex_event.after_context_event.id == dying_context_id;
743 // Need to generate the item below for checking "is dying context" in circuit
744 // No need to condition by `!is_dying_context` as batch inversion skips 0.
745 const FF dying_context_diff = FF(ex_event.after_context_event.id) - FF(dying_context_id);
746
747 // This is here instead of guarded by `execute_opcode` because is_err is a higher level error
748 // than just an opcode error (i.e., it is on if there are any errors in any temporality group).
749 const bool is_err = ex_event.error != ExecutionError::NONE;
750 sel_exit_call = sel_exit_call || is_err; // sel_execute_revert || sel_execute_return || sel_error
751 const bool is_failure = execute_revert || is_err;
752 const bool enqueued_call_end = sel_exit_call && !has_parent;
753 const bool nested_failure = is_failure && has_parent;
754
755 trace.set(row,
756 { {
757 { C::execution_sel_exit_call, sel_exit_call ? 1 : 0 },
758 { C::execution_nested_failure, nested_failure ? 1 : 0 },
759 { C::execution_sel_error, is_err ? 1 : 0 },
760 { C::execution_sel_failure, is_failure ? 1 : 0 },
761 { C::execution_discard, is_discarding() ? 1 : 0 },
762 { C::execution_dying_context_id, dying_context_id },
763 { C::execution_dying_context_id_inv, dying_context_id }, // Will be inverted in batch.
764 { C::execution_is_dying_context, is_dying_context ? 1 : 0 },
765 { C::execution_dying_context_diff_inv, dying_context_diff }, // Will be inverted in batch.
766 { C::execution_enqueued_call_end, enqueued_call_end ? 1 : 0 },
767 } });
768
769 // Trace-generation is done for this event.
770 // Now, use this event to determine whether we should set/reset the discard flag for the NEXT event.
771 // Note: is_failure implies discard is true.
772 const bool event_kills_dying_context = is_failure && is_dying_context;
773
774 if (event_kills_dying_context) {
775 // Set/unset discard flag if the current event is the one that kills the dying context
776 dying_context_id = 0;
777 } else if (sel_enter_call && !is_discarding() &&
778 failures.does_context_fail.contains(ex_event.next_context_id)) {
779 // If making a nested call, and discard isn't already high...
780 // if the nested context being entered eventually dies, we set which context is dying (implicitly raise
781 // discard flag). NOTE: If a [STATIC]CALL instruction _itself_ errors, we don't set the discard flag
782 // because we aren't actually entering a new context. This is already captured by `sel_enter_call`
783 // boolean which is set to true only during opcode execution temporality group which cannot
784 // fail for CALL/STATICALL.
785 dying_context_id = ex_event.next_context_id;
786 }
787 // Otherwise, we aren't entering or exiting a dying context,
788 // so just propagate discard and dying context.
789 // Implicit: dying_context_id = dying_context_id; discard = discard;
790
791 // If an enqueued call just exited, next event (if any) is the first in an enqueued call.
792 // Update flag for next iteration.
793 is_first_event_in_enqueued_call = !has_parent && sel_exit_call;
794
795 // Track this bool for use determining whether the next row is the first in a context
796 prev_row_was_enter_call = sel_enter_call;
797
798 row++;
799 }
800
801 // Batch invert the columns.
803}
804
817 TraceContainer& trace,
818 uint32_t row)
819{
820 trace.set(row,
821 { {
822 { C::execution_sel_instruction_fetching_success, 1 },
823 { C::execution_exec_opcode, static_cast<uint8_t>(instruction.get_exec_opcode()) },
824 { C::execution_addressing_mode, instruction.addressing_mode },
825 { C::execution_instr_size, instruction.size_in_bytes() },
826 } });
827
828 auto operands = instruction.operands;
829 BB_ASSERT_LTE(operands.size(), static_cast<size_t>(AVM_MAX_OPERANDS), "Operands size is out of range");
830 // Pad operands with zeros.
831 operands.resize(AVM_MAX_OPERANDS, Operand::from<FF>(0));
832
833 for (size_t i = 0; i < AVM_MAX_OPERANDS; i++) {
834 trace.set(OPERAND_COLUMNS[i], row, operands.at(i));
835 }
836}
837
846 TraceContainer& trace,
847 uint32_t row)
848{
849 // At this point we can assume instruction fetching succeeded, so this should never fail.
850 ExecutionOpCode exec_opcode = ex_event.wire_instruction.get_exec_opcode();
851 const auto& exec_spec = get_exec_instruction_spec().at(exec_opcode);
852 const auto& gas_cost = exec_spec.gas_cost;
853
854 // Gas.
855 trace.set(row,
856 { {
857 { C::execution_opcode_gas, gas_cost.opcode_gas },
858 { C::execution_base_da_gas, gas_cost.base_da },
859 { C::execution_dynamic_l2_gas, gas_cost.dyn_l2 },
860 { C::execution_dynamic_da_gas, gas_cost.dyn_da },
861 } });
862
863 const auto& register_info = exec_spec.register_info;
864 for (size_t i = 0; i < AVM_MAX_REGISTERS; i++) {
865 trace.set(row,
866 { {
867 { REGISTER_IS_WRITE_COLUMNS[i], register_info.is_write(i) ? 1 : 0 },
868 { REGISTER_MEM_OP_COLUMNS[i], register_info.is_active(i) ? 1 : 0 },
869 { REGISTER_EXPECTED_TAG_COLUMNS[i],
870 register_info.need_tag_check(i) ? static_cast<uint8_t>(*(register_info.expected_tag(i))) : 0 },
871 { REGISTER_TAG_CHECK_COLUMNS[i], register_info.need_tag_check(i) ? 1 : 0 },
872 } });
873 }
874
875 // Set is_address columns
876 const auto& num_addresses = exec_spec.num_addresses;
877 for (size_t i = 0; i < num_addresses; i++) {
878 trace.set(OPERAND_IS_ADDRESS_COLUMNS[i], row, 1);
879 }
880
881 // At this point we can assume instruction fetching succeeded, so this should never fail.
882 const auto& dispatch_to_subtrace = get_subtrace_info_map().at(exec_opcode);
883 trace.set(row,
884 { {
885 { C::execution_subtrace_id, get_subtrace_id(dispatch_to_subtrace.subtrace_selector) },
886 { C::execution_subtrace_operation_id, dispatch_to_subtrace.subtrace_operation_id },
887 { C::execution_dyn_gas_id, exec_spec.dyn_gas_id },
888 } });
889}
890
900 ExecutionOpCode exec_opcode,
901 TraceContainer& trace,
902 uint32_t row)
903{
904 bool oog = gas_event.oog_l2 || gas_event.oog_da;
905 trace.set(row,
906 { {
907 { C::execution_sel_check_gas, 1 },
908 { C::execution_out_of_gas_l2, gas_event.oog_l2 ? 1 : 0 },
909 { C::execution_out_of_gas_da, gas_event.oog_da ? 1 : 0 },
910 { C::execution_sel_out_of_gas, oog ? 1 : 0 },
911 // Addressing gas.
912 { C::execution_addressing_gas, gas_event.addressing_gas },
913 // Dynamic gas.
914 { C::execution_dynamic_l2_gas_factor, gas_event.dynamic_gas_factor.l2_gas },
915 { C::execution_dynamic_da_gas_factor, gas_event.dynamic_gas_factor.da_gas },
916 // Derived cumulative gas used.
917 { C::execution_total_gas_l2, gas_event.total_gas_used_l2 },
918 { C::execution_total_gas_da, gas_event.total_gas_used_da },
919 } });
920
921 const auto& exec_spec = get_exec_instruction_spec().at(exec_opcode);
922 if (exec_spec.dyn_gas_id != 0) {
923 trace.set(get_dyn_gas_selector(exec_spec.dyn_gas_id), row, 1);
924 }
925}
926
937 TraceContainer& trace,
938 uint32_t row)
939{
940 // At this point we can assume instruction fetching succeeded, so this should never fail.
941 ExecutionOpCode exec_opcode = instruction.get_exec_opcode();
942 const ExecInstructionSpec& ex_spec = get_exec_instruction_spec().at(exec_opcode);
943
944 auto resolution_info_vec = addr_event.resolution_info;
946 resolution_info_vec.size(), static_cast<size_t>(AVM_MAX_OPERANDS), "Resolution info size is out of range");
947 // Pad with default values for the missing operands.
948 resolution_info_vec.resize(AVM_MAX_OPERANDS,
949 {
950 // This is the default we want: both tag and value 0.
951 .after_relative = FF::zero(),
952 .resolved_operand = Operand::from_tag(static_cast<ValueTag>(0), 0),
953 .error = std::nullopt,
954 });
955
956 std::array<bool, AVM_MAX_OPERANDS> apply_indirection{};
959 std::array<bool, AVM_MAX_OPERANDS> is_relative_effective{};
960 std::array<bool, AVM_MAX_OPERANDS> is_indirect_effective{};
962 std::array<FF, AVM_MAX_OPERANDS> after_relative{};
963 std::array<FF, AVM_MAX_OPERANDS> resolved_operand{};
964 std::array<uint8_t, AVM_MAX_OPERANDS> resolved_operand_tag{};
965 uint8_t num_relative_operands = 0;
966
967 // The error about the base address being invalid is stored in every resolution_info member when it happens.
968 bool base_address_invalid = resolution_info_vec[0].error.has_value() &&
969 *resolution_info_vec[0].error == AddressingEventError::BASE_ADDRESS_INVALID;
970 bool do_base_check = false; // Whether we need to retrieve the base address,
971 // i.e., at least one operand is relative.
972
973 // Gather operand information.
974 for (size_t i = 0; i < AVM_MAX_OPERANDS; i++) {
975 const auto& resolution_info = resolution_info_vec[i];
976 bool op_is_address = i < ex_spec.num_addresses;
977 relative_oob[i] = resolution_info.error.has_value() &&
978 *resolution_info.error == AddressingEventError::RELATIVE_COMPUTATION_OOB;
979 is_relative[i] = is_operand_relative(instruction.addressing_mode, i);
980 is_indirect[i] = is_operand_indirect(instruction.addressing_mode, i);
981 is_relative_effective[i] = op_is_address && is_relative[i];
982 is_indirect_effective[i] = op_is_address && is_indirect[i];
983 apply_indirection[i] = is_indirect_effective[i] && !relative_oob[i] && !base_address_invalid;
984 resolved_operand_tag[i] = static_cast<uint8_t>(resolution_info.resolved_operand.get_tag());
985 after_relative[i] = resolution_info.after_relative;
986 resolved_operand[i] = resolution_info.resolved_operand;
987 if (is_relative_effective[i]) {
988 do_base_check = true;
989 num_relative_operands++;
990 }
991 }
992
993 BB_ASSERT(do_base_check || !base_address_invalid, "Base address is invalid but we are not checking it.");
994
995 // Set the operand columns.
996 for (size_t i = 0; i < AVM_MAX_OPERANDS; i++) {
997 trace.set(row,
998 { {
999 { OPERAND_IS_RELATIVE_WIRE_COLUMNS[i], is_relative[i] ? 1 : 0 },
1000 { OPERAND_IS_INDIRECT_WIRE_COLUMNS[i], is_indirect[i] ? 1 : 0 },
1001 { OPERAND_RELATIVE_OVERFLOW_COLUMNS[i], relative_oob[i] ? 1 : 0 },
1002 { OPERAND_AFTER_RELATIVE_COLUMNS[i], after_relative[i] },
1003 { OPERAND_APPLY_INDIRECTION_COLUMNS[i], apply_indirection[i] ? 1 : 0 },
1004 { OPERAND_IS_RELATIVE_VALID_BASE_COLUMNS[i],
1005 (is_relative_effective[i] && !base_address_invalid) ? 1 : 0 },
1006 { RESOLVED_OPERAND_COLUMNS[i], resolved_operand[i] },
1007 { RESOLVED_OPERAND_TAG_COLUMNS[i], resolved_operand_tag[i] },
1008 } });
1009 }
1010
1011 // We need to compute relative and indirect over the whole 16 bits of the indirect flag.
1012 // See comment in PIL file about indirect upper bits.
1013 for (size_t i = AVM_MAX_OPERANDS; i < TOTAL_INDIRECT_BITS / 2; i++) {
1014 bool is_relative = is_operand_relative(instruction.addressing_mode, i);
1015 bool is_indirect = is_operand_indirect(instruction.addressing_mode, i);
1016 trace.set(row,
1017 { {
1018 { OPERAND_IS_RELATIVE_WIRE_COLUMNS[i], is_relative ? 1 : 0 },
1019 { OPERAND_IS_INDIRECT_WIRE_COLUMNS[i], is_indirect ? 1 : 0 },
1020 } });
1021 }
1022
1023 // Inverse of following difference is required when base address is invalid.
1024 FF base_address_tag_diff = base_address_invalid ? FF(static_cast<uint8_t>(addr_event.base_address.get_tag())) -
1025 FF(static_cast<uint8_t>(MemoryTag::U32))
1026 : 0;
1027
1028 // Tag check after indirection.
1029 bool some_final_check_failed = std::ranges::any_of(addr_event.resolution_info, [](const auto& info) {
1030 return info.error.has_value() && *info.error == AddressingEventError::INVALID_ADDRESS_AFTER_INDIRECTION;
1031 });
1032 FF batched_tags_diff = 0;
1033 if (some_final_check_failed) {
1034 FF power_of_2 = 1;
1035 for (size_t i = 0; i < AVM_MAX_OPERANDS; ++i) {
1036 if (apply_indirection[i]) {
1037 batched_tags_diff += power_of_2 * (FF(resolved_operand_tag[i]) - FF(MEM_TAG_U32));
1038 }
1039 power_of_2 *= 8; // 2^3
1040 }
1041 }
1042
1043 // Collect addressing errors. See PIL file for reference.
1044 bool addressing_failed =
1045 std::ranges::any_of(addr_event.resolution_info, [](const auto& info) { return info.error.has_value(); });
1046 FF addressing_error_collection =
1047 addressing_failed
1048 ? FF(
1049 // Base address invalid.
1050 (base_address_invalid ? 1 : 0) +
1051 // Relative overflow.
1052 std::accumulate(addr_event.resolution_info.begin(),
1053 addr_event.resolution_info.end(),
1054 static_cast<uint32_t>(0),
1055 [](uint32_t acc, const auto& info) {
1056 return acc +
1057 (info.error.has_value() &&
1058 *info.error == AddressingEventError::RELATIVE_COMPUTATION_OOB
1059 ? 1
1060 : 0);
1061 }) +
1062 // Some invalid address after indirection.
1063 (some_final_check_failed ? 1 : 0))
1064 : 0;
1065
1066 trace.set(
1067 row,
1068 { {
1069 { C::execution_sel_addressing_error, addressing_failed ? 1 : 0 },
1070 { C::execution_addressing_error_collection_inv, addressing_error_collection }, // Will be inverted in batch.
1071 { C::execution_base_address_val, addr_event.base_address.as_ff() },
1072 { C::execution_base_address_tag, static_cast<uint8_t>(addr_event.base_address.get_tag()) },
1073 { C::execution_base_address_tag_diff_inv, base_address_tag_diff }, // Will be inverted in batch.
1074 { C::execution_batched_tags_diff_inv, batched_tags_diff }, // Will be inverted in batch.
1075 { C::execution_sel_some_final_check_failed, some_final_check_failed ? 1 : 0 },
1076 { C::execution_sel_base_address_failure, base_address_invalid ? 1 : 0 },
1077 { C::execution_num_relative_operands_inv, num_relative_operands }, // Will be inverted in batch later.
1078 { C::execution_sel_do_base_check, do_base_check ? 1 : 0 },
1079 { C::execution_highest_address, AVM_HIGHEST_MEM_ADDRESS },
1080 } });
1081}
1082
1089{
1090 trace.invert_columns({ {
1091 // Registers.
1092 C::execution_batched_tags_diff_inv_reg,
1093 // Context.
1094 C::execution_is_parent_id_inv,
1095 C::execution_internal_call_return_id_inv,
1096 // Trees.
1097 C::execution_remaining_data_writes_inv,
1098 C::execution_remaining_note_hashes_inv,
1099 C::execution_remaining_nullifiers_inv,
1100 // SendL2ToL1Msg.
1101 C::execution_remaining_l2_to_l1_msgs_inv,
1102 // Discard.
1103 C::execution_dying_context_id_inv,
1104 C::execution_dying_context_diff_inv,
1105 // Addressing.
1106 C::execution_addressing_error_collection_inv,
1107 C::execution_batched_tags_diff_inv,
1108 C::execution_base_address_tag_diff_inv,
1109 C::execution_num_relative_operands_inv,
1110 } });
1111}
1112
1126 const MemoryValue& output,
1128 bool register_processing_failed,
1129 TraceContainer& trace,
1130 uint32_t row)
1131{
1132 BB_ASSERT_EQ(registers.size(), static_cast<size_t>(AVM_MAX_REGISTERS), "Registers size is out of range");
1133 // At this point we can assume instruction fetching succeeded, so this should never fail.
1134 const auto& register_info = get_exec_instruction_spec().at(exec_opcode).register_info;
1135
1136 // Registers. We set all of them here, even the write ones. This is fine because
1137 // if an error occured before the register write group, simulation would pass the default
1138 // value-tag (0, 0). Furthermore, the permutation of the memory write would not be activated.
1139 size_t input_counter = 0;
1140 for (uint8_t i = 0; i < AVM_MAX_REGISTERS; ++i) {
1141 if (register_info.is_active(i)) {
1142 if (register_info.is_write(i)) {
1143 // If this is a write operation, we need to get the value from the output.
1144 registers[i] = output;
1145 } else {
1146 // If this is a read operation, we need to get the value from the input.
1147
1148 // Register specifications must be consistent with the number of inputs.
1149 BB_ASSERT(inputs.size() > input_counter, "Not enough inputs for register read");
1150
1151 registers[i] = inputs.at(input_counter);
1152 input_counter++;
1153 }
1154 }
1155 }
1156
1157 for (size_t i = 0; i < AVM_MAX_REGISTERS; i++) {
1158 trace.set(REGISTER_COLUMNS[i], row, registers[i]);
1159 trace.set(REGISTER_MEM_TAG_COLUMNS[i], row, static_cast<uint8_t>(registers[i].get_tag()));
1160 // This one is special because it sets the reads (but not the writes).
1161 // If we got here, sel_read_registers=1.
1162 if (register_info.is_active(i) && !register_info.is_write(i)) {
1163 trace.set(REGISTER_OP_REG_EFFECTIVE_COLUMNS[i], row, 1);
1164 }
1165 }
1166
1167 FF batched_tags_diff_reg = 0;
1168 if (register_processing_failed) {
1169 FF power_of_2 = 1;
1170 for (size_t i = 0; i < AVM_MAX_REGISTERS; ++i) {
1171 if (register_info.need_tag_check(i)) {
1172 batched_tags_diff_reg += power_of_2 * (FF(static_cast<uint8_t>(registers[i].get_tag())) -
1173 FF(static_cast<uint8_t>(*register_info.expected_tag(i))));
1174 }
1175 power_of_2 *= 8; // 2^3
1176 }
1177 }
1178
1179 trace.set(row,
1180 { {
1181 { C::execution_sel_read_registers, 1 },
1182 { C::execution_batched_tags_diff_inv_reg, batched_tags_diff_reg }, // Will be inverted in batch.
1183 { C::execution_sel_register_read_error, register_processing_failed ? 1 : 0 },
1184 } });
1185}
1186
1195{
1196 const auto& register_info = get_exec_instruction_spec().at(exec_opcode).register_info;
1197 trace.set(C::execution_sel_write_registers, row, 1);
1198
1199 for (size_t i = 0; i < AVM_MAX_REGISTERS; i++) {
1200 // This one is special because it sets the writes.
1201 // If we got here, sel_write_registers=1.
1202 if (register_info.is_active(i) && register_info.is_write(i)) {
1203 trace.set(REGISTER_OP_REG_EFFECTIVE_COLUMNS[i], row, 1);
1204 }
1205 }
1206}
1207
1217 MemoryValue output,
1218 TraceContainer& trace,
1219 uint32_t row)
1220{
1221 BB_ASSERT_EQ(envvar_enum.get_tag(), ValueTag::U8, "Envvar enum tag is not U8");
1222 const auto& envvar_spec = GetEnvVarSpec::get_table(envvar_enum.as<uint8_t>());
1223
1224 trace.set(row,
1225 { {
1226 { C::execution_sel_execute_get_env_var, 1 },
1227 { C::execution_sel_envvar_pi_lookup_col0, envvar_spec.envvar_pi_lookup_col0 ? 1 : 0 },
1228 { C::execution_sel_envvar_pi_lookup_col1, envvar_spec.envvar_pi_lookup_col1 ? 1 : 0 },
1229 { C::execution_envvar_pi_row_idx, envvar_spec.envvar_pi_row_idx },
1230 { C::execution_is_address, envvar_spec.is_address ? 1 : 0 },
1231 { C::execution_is_sender, envvar_spec.is_sender ? 1 : 0 },
1232 { C::execution_is_transactionfee, envvar_spec.is_transactionfee ? 1 : 0 },
1233 { C::execution_is_isstaticcall, envvar_spec.is_isstaticcall ? 1 : 0 },
1234 { C::execution_is_l2gasleft, envvar_spec.is_l2gasleft ? 1 : 0 },
1235 { C::execution_is_dagasleft, envvar_spec.is_dagasleft ? 1 : 0 },
1236 { C::execution_value_from_pi,
1237 envvar_spec.envvar_pi_lookup_col0 || envvar_spec.envvar_pi_lookup_col1 ? output.as_ff() : 0 },
1238 { C::execution_mem_tag_reg_0_, envvar_spec.out_tag },
1239 } });
1240}
1241
1244 // Execution specification (precomputed)
1246 // Bytecode retrieval
1247 .add<InteractionType::LookupGeneric, lookup_execution_bytecode_retrieval_result_settings>()
1248 // Instruction fetching
1250 .add<InteractionType::LookupGeneric, lookup_execution_instruction_fetching_body_settings>()
1251 // Addressing
1253 .add<InteractionType::LookupGeneric, lookup_addressing_relative_overflow_result_1_settings>(C::gt_sel)
1255 .add<InteractionType::LookupGeneric, lookup_addressing_relative_overflow_result_3_settings>(C::gt_sel)
1257 .add<InteractionType::LookupGeneric, lookup_addressing_relative_overflow_result_5_settings>(C::gt_sel)
1259 // Internal Call Stack
1260 .add<InteractionType::Permutation, perm_internal_call_push_call_stack_settings>()
1262 // Gas
1263 .add<InteractionType::LookupIntoIndexedByRow, lookup_gas_addressing_gas_read_settings>()
1265 .add<InteractionType::LookupGeneric, lookup_gas_is_out_of_gas_da_settings>(C::gt_sel)
1267 // Gas - ToRadix BE
1268 .add<InteractionType::LookupGeneric, lookup_execution_check_radix_gt_256_settings>(C::gt_sel)
1270 .add<InteractionType::LookupGeneric, lookup_execution_get_max_limbs_settings>(C::gt_sel)
1271 // Dynamic Gas - SStore
1273 // Context Stack
1274 .add<InteractionType::Permutation, perm_context_ctx_stack_call_settings>()
1276 .add<InteractionType::LookupGeneric, lookup_context_ctx_stack_return_settings>()
1277 // External Call
1279 .add<InteractionType::LookupGeneric, lookup_external_call_is_da_gas_left_gt_allocated_settings>(C::gt_sel)
1280 // GetEnvVar opcode
1282 .add<InteractionType::LookupIntoIndexedByRow, lookup_get_env_var_read_from_public_inputs_col0_settings>()
1284 // Sload opcode (cannot be sequential as public data tree check trace is sorted in tracegen)
1285 .add<InteractionType::LookupGeneric, lookup_sload_storage_read_settings>()
1286 // Sstore opcode
1288 // NoteHashExists
1289 .add<InteractionType::LookupSequential, lookup_notehash_exists_note_hash_read_settings>()
1291 // NullifierExists opcode
1292 .add<InteractionType::LookupSequential, lookup_nullifier_exists_nullifier_exists_check_settings>()
1293 // EmitNullifier
1295 // EmitNoteHash
1296 .add<InteractionType::LookupSequential, lookup_emit_notehash_notehash_tree_write_settings>()
1297 // L1ToL2MsgExists
1299 C::gt_sel)
1300 .add<InteractionType::LookupSequential, lookup_l1_to_l2_message_exists_l1_to_l2_msg_read_settings>()
1301 // SendL2ToL1Msg
1303 .add<InteractionType::LookupIntoIndexedByRow, lookup_send_l2_to_l1_msg_write_l2_to_l1_msg_settings>()
1304 // Dispatching to other sub-traces
1306 .add<InteractionType::LookupGeneric, lookup_execution_dispatch_to_bitwise_settings>()
1308 .add<InteractionType::Permutation, perm_execution_dispatch_to_rd_copy_settings>()
1310 .add<InteractionType::LookupGeneric, lookup_execution_dispatch_to_set_settings>()
1312 .add<InteractionType::Permutation, perm_execution_dispatch_to_emit_public_log_settings>()
1314 .add<InteractionType::Permutation, perm_execution_dispatch_to_sha256_compression_settings>()
1316 .add<InteractionType::Permutation, perm_execution_dispatch_to_ecc_add_settings>()
1318
1319} // namespace bb::avm2::tracegen
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
#define MEM_TAG_U32
#define AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX
#define AVM_MAX_OPERANDS
#define AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_NULLIFIERS_ROW_IDX
#define NOTE_HASH_TREE_LEAF_COUNT
#define DOM_SEP__WRITTEN_SLOTS_MERKLE
#define AVM_WRITTEN_PUBLIC_DATA_SLOTS_TREE_HEIGHT
#define MAX_ETH_ADDRESS_VALUE
#define L1_TO_L2_MSG_TREE_LEAF_COUNT
#define DOM_SEP__SILOED_NULLIFIER
#define AVM_MAX_REGISTERS
#define NULLIFIER_TREE_HEIGHT
#define MAX_L2_TO_L1_MSGS_PER_TX
#define DOM_SEP__PUBLIC_LEAF_SLOT
#define MAX_NOTE_HASHES_PER_TX
#define DOM_SEP__NULLIFIER_MERKLE
#define MAX_NULLIFIERS_PER_TX
#define AVM_HIGHEST_MEM_ADDRESS
#define MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
static TaggedValue from_tag(ValueTag tag, FF value)
ValueTag get_tag() const
void process_execution_spec(const simulation::ExecutionEvent &ex_event, TraceContainer &trace, uint32_t row)
Process the execution specification lookup columns (gas costs, register info, subtrace dispatch).
void process_instr_fetching(const simulation::Instruction &instruction, TraceContainer &trace, uint32_t row)
Process instruction fetching in execution and populate the relevant columns in the trace.
void invert_columns(TraceContainer &trace)
Batch-invert all columns that were populated with pre-inversion values during trace generation.
static const InteractionDefinition interactions
void process_registers(ExecutionOpCode exec_opcode, const std::vector< MemoryValue > &inputs, const MemoryValue &output, std::span< MemoryValue > registers, bool register_processing_failed, TraceContainer &trace, uint32_t row)
Process register reads: populate register value/tag columns and detect tag check failures.
void process_get_env_var_opcode(simulation::Operand envvar_enum, MemoryValue output, TraceContainer &trace, uint32_t row)
Process the GETENVVAR opcode: populate environment variable lookup and selector columns.
void process_registers_write(ExecutionOpCode exec_opcode, TraceContainer &trace, uint32_t row)
Process register writes: activate the write selector and effective write columns for the opcode.
void process_gas(const simulation::GasEvent &gas_event, ExecutionOpCode exec_opcode, TraceContainer &trace, uint32_t row)
Process gas consumption and populate gas-related columns (OOG flags, addressing gas,...
void process(const simulation::EventEmitterInterface< simulation::ExecutionEvent >::Container &ex_events, TraceContainer &trace)
Process the execution events and populate the relevant columns in the trace. ExecutionError enum is u...
void process_addressing(const simulation::AddressingEvent &addr_event, const simulation::Instruction &instruction, TraceContainer &trace, uint32_t row)
Process addressing resolution and populate operand columns (relative, indirect, resolved values,...
static Table get_table(uint8_t envvar)
InteractionDefinition & add(auto &&... args)
#define info(...)
Definition log.hpp:93
TestTraceContainer trace
bool app_logic_failure
uint32_t app_logic_exit_context_id
bool teardown_failure
unordered_flat_set< uint32_t > does_context_fail
uint32_t teardown_exit_context_id
GasEvent gas_event
Instruction instruction
AvmProvingInputs inputs
Column get_dyn_gas_selector(uint32_t dyn_gas_id)
Get the column selector for a given dynamic gas ID.
const std::unordered_map< ExecutionOpCode, SubtraceInfo > & get_subtrace_info_map()
Column get_subtrace_selector(SubtraceSel subtrace_sel)
Get the column selector for a given subtrace selector.
FF get_subtrace_id(SubtraceSel subtrace_sel)
Get the subtrace ID for a given subtrace enum.
bool is_operand_relative(uint16_t indirect_flag, size_t operand_index)
Checks if the operand at the given index is relative.
size_t get_p_limbs_per_radix_size(size_t radix)
Gets the number of limbs that the modulus, p, decomposes into for a given radix.
Definition to_radix.cpp:75
AvmFlavorSettings::FF FF
Definition field.hpp:10
bool is_operand_indirect(uint16_t indirect_flag, size_t operand_index)
Checks if the operand at the given index is indirect.
const std::unordered_map< ExecutionOpCode, ExecInstructionSpec > & get_exec_instruction_spec()
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Settings to be passed ot GenericLookupRelationImpl.
std::vector< OperandResolutionInfo > resolution_info
ExecutionOpCode get_exec_opcode() const