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