From 4bb512d6935ac03338a920ec1dbc810d77e0c58c Mon Sep 17 00:00:00 2001 From: Jean M <132435771+jeanmon@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:47:17 +0200 Subject: [PATCH] chore(avm): re-ordering routines by opcode order (#7298) --- .../vm/avm_trace/avm_alu_trace.cpp | 846 ++-- .../vm/avm_trace/avm_alu_trace.hpp | 26 +- .../vm/avm_trace/avm_execution.cpp | 276 +- .../barretenberg/vm/avm_trace/avm_opcode.cpp | 20 + .../barretenberg/vm/avm_trace/avm_trace.cpp | 3970 +++++++++-------- .../barretenberg/vm/avm_trace/avm_trace.hpp | 163 +- 6 files changed, 2715 insertions(+), 2586 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_alu_trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_alu_trace.cpp index 5a81dc569c3..c0b704d472e 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_alu_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_alu_trace.cpp @@ -2,48 +2,181 @@ namespace bb::avm_trace { +/************************************************************************************************** + * HELPERS IN ANONYMOUS NAMESPACE + **************************************************************************************************/ +namespace { + /** - * @brief Resetting the internal state so that a new Alu trace can be rebuilt using the same object. - * + * Helper function to decompose a uint256_t into a b-lower bits and (256-b) upper bits + * The outputs are cast to uint256_t so they are easier to use in checks */ -void AvmAluTraceBuilder::reset() +std::tuple decompose(uint256_t const& a, uint8_t const b) { - alu_trace.clear(); - range_checked_required = false; + uint256_t upper_bitmask = (uint256_t(1) << uint256_t(b)) - 1; + uint256_t a_lo = a & upper_bitmask; + uint256_t a_hi = a >> b; + return std::make_tuple(a_lo, a_hi); +} + +// This creates a witness exclusively for the relation a > b +// This is useful when we want to enforce in certain checks that a must be greater than b +std::tuple gt_witness(uint256_t const& a, uint256_t const& b) +{ + uint256_t two_pow_128 = uint256_t(1) << uint256_t(128); + auto [a_lo, a_hi] = decompose(a, 128); + auto [b_lo, b_hi] = decompose(b, 128); + bool borrow = a_lo <= b_lo; + auto borrow_u256 = uint256_t(static_cast(borrow)); + uint256_t r_lo = a_lo - b_lo - 1 + borrow_u256 * two_pow_128; + uint256_t r_hi = a_hi - b_hi - borrow_u256; + return std::make_tuple(r_lo, r_hi, borrow); +} + +// This check is more flexible than gt_witness and is used when we want to generate the witness +// to the relation (a - b - 1) * q + (b - a) * (1 - q) +// where q = 1 if a > b and q = 0 if a <= b +std::tuple gt_or_lte_witness(uint256_t const& a, uint256_t const& b) +{ + uint256_t two_pow_126 = uint256_t(1) << uint256_t(128); + auto [a_lo, a_hi] = decompose(a, 128); + auto [b_lo, b_hi] = decompose(b, 128); + bool isGT = a > b; + if (isGT) { + return gt_witness(a, b); + } + bool borrow = b_lo < a_lo; + auto borrow_u256 = uint256_t(static_cast(borrow)); + uint256_t r_lo = b_lo - a_lo + borrow_u256 * two_pow_126; + uint256_t r_hi = b_hi - a_hi - borrow_u256; + return std::make_tuple(r_lo, r_hi, borrow); +} + +// Returns the number of bits associated with a given memory tag +uint8_t mem_tag_bits(AvmMemoryTag in_tag) +{ + switch (in_tag) { + case AvmMemoryTag::U8: + return 8; + case AvmMemoryTag::U16: + return 16; + case AvmMemoryTag::U32: + return 32; + case AvmMemoryTag::U64: + return 64; + case AvmMemoryTag::U128: + return 128; + case AvmMemoryTag::FF: + return 254; + case AvmMemoryTag::U0: + return 0; + } + return 0; } +} // Anonymous namespace + +/************************************************************************************************** + * PRIVATE HELPERS + **************************************************************************************************/ /** - * @brief Prepare the Alu trace to be incorporated into the main trace. - * - * @return The Alu trace (which is moved). + * @brief This is a helper function that decomposes the input into the various registers of the ALU. + * This additionally increments the counts for the corresponding range lookups entries. + * @return A triplet of */ -std::vector AvmAluTraceBuilder::finalize() +template +std::tuple> AvmAluTraceBuilder::to_alu_slice_registers(T a) { - return std::move(alu_trace); + range_checked_required = true; + auto alu_u8_r0 = static_cast(a); + a >>= 8; + auto alu_u8_r1 = static_cast(a); + a >>= 8; + std::array alu_u16_reg; + for (size_t i = 0; i < 15; i++) { + auto alu_u16 = static_cast(a); + u16_range_chk_counters[i][alu_u16]++; + alu_u16_reg.at(i) = alu_u16; + a >>= 16; + } + u8_range_chk_counters[0][alu_u8_r0]++; + u8_range_chk_counters[1][alu_u8_r1]++; + return std::make_tuple(alu_u8_r0, alu_u8_r1, alu_u16_reg); } /** - * @brief Helper routine telling whether range check is required. + * @brief This is a helper function that is used to generate the range check entries for operations that require + * multi-row range checks This additionally increments the counts for the corresponding range lookups entries. + * @param row The initial row where the comparison operation was performed + * @param hi_lo_limbs The vector of 128-bit limbs hi and lo pairs of limbs that will be range checked. + * @return A vector of AluTraceEntry rows for the range checks for the operation. + */ +std::vector AvmAluTraceBuilder::cmp_range_check_helper( + AvmAluTraceBuilder::AluTraceEntry row, std::vector hi_lo_limbs) +{ + // Assume each limb is 128 bits and since we can perform 256-bit range check per rows + // we need to have (limbs.size() / 2) range checks rows + size_t num_rows = hi_lo_limbs.size() / 2; + // The first row is the original comparison instruction (LT/LTE) + std::vector rows{ std::move(row) }; + rows.resize(num_rows, {}); + + // We need to ensure that the number of rows is even + ASSERT(hi_lo_limbs.size() % 2 == 0); + // Now for each row, we need to unpack a pair from the hi_lo_limb array into the ALUs 8-bit and 16-bit registers + // The first row unpacks a_lo and a_hi, the second row unpacks b_lo and b_hi, and so on. + for (size_t j = 0; j < num_rows; j++) { + auto& r = rows.at(j); + uint256_t lo_limb = hi_lo_limbs.at(2 * j); + uint256_t hi_limb = hi_lo_limbs.at(2 * j + 1); + uint256_t limb = lo_limb + (hi_limb << 128); + // Unpack lo limb and handle in the 8-bit registers + auto [alu_u8_r0, alu_u8_r1, alu_u16_reg] = AvmAluTraceBuilder::to_alu_slice_registers(limb); + r.alu_u8_r0 = alu_u8_r0; + r.alu_u8_r1 = alu_u8_r1; + r.alu_u16_reg = alu_u16_reg; + + r.cmp_rng_ctr = j > 0 ? static_cast(num_rows - j) : 0; + r.rng_chk_sel = j > 0; + r.alu_op_eq_diff_inv = j > 0 ? FF(num_rows - j).invert() : 0; + + std::vector limb_arr = { hi_lo_limbs.begin() + static_cast(2 * j), hi_lo_limbs.end() }; + // Resizing here is probably suboptimal for performance, we can probably handle the shorter vectors and + // pad with zero during the finalise + limb_arr.resize(10, FF::zero()); + r.hi_lo_limbs = limb_arr; + } + return rows; +} + +/************************************************************************************************** + * RESET/FINALIZE + **************************************************************************************************/ + +/** + * @brief Resetting the internal state so that a new Alu trace can be rebuilt using the same object. * - * @return A boolean telling whether range check is required. */ -bool AvmAluTraceBuilder::is_range_check_required() const +void AvmAluTraceBuilder::reset() { - return range_checked_required; + alu_trace.clear(); + range_checked_required = false; } /** - * @brief Helper function that returns a boolean if this entry is an alu operation. - * This is helpful to filter out range check rows or the second row in the 128-bit multiply. + * @brief Prepare the Alu trace to be incorporated into the main trace. * - * @return A boolean telling whether range check is required. + * @return The Alu trace (which is moved). */ -bool AvmAluTraceBuilder::is_alu_row_enabled(AvmAluTraceBuilder::AluTraceEntry const& r) +std::vector AvmAluTraceBuilder::finalize() { - return (r.alu_op_add || r.alu_op_sub || r.alu_op_mul || r.alu_op_eq || r.alu_op_not || r.alu_op_lt || - r.alu_op_lte || r.alu_op_shr || r.alu_op_shl || r.alu_op_cast || r.alu_op_div); + return std::move(alu_trace); } +/************************************************************************************************** + * COMPUTE - ARITHMETIC + **************************************************************************************************/ + /** * @brief Build Alu trace and compute the result of an addition of type defined by in_tag. * Besides the addition calculation, for the types u8, u16, u32, u64, and u128, we @@ -345,69 +478,120 @@ FF AvmAluTraceBuilder::op_mul(FF const& a, FF const& b, AvmMemoryTag in_tag, uin return c; } -/** - * @brief Build Alu trace and compute the result of a Bitwise Not of type defined by in_tag. - * - * @param a Unary operand of Not - * @param in_tag Instruction tag defining the number of bits on which the addition applies. - * It is assumed that the caller never uses the type u0. - * @param clk Clock referring to the operation in the main trace. - * - * @return FF The result of the not casted in a finite field element - */ -FF AvmAluTraceBuilder::op_not(FF const& a, AvmMemoryTag in_tag, uint32_t const clk) + +FF AvmAluTraceBuilder::op_div(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk) { - FF c = 0; - uint128_t a_u128{ a }; - uint128_t c_u128 = ~a_u128; + uint256_t a_u256{ a }; + uint256_t b_u256{ b }; + uint256_t c_u256 = a_u256 / b_u256; + uint256_t rem_u256 = a_u256 % b_u256; - switch (in_tag) { - case AvmMemoryTag::U8: - c = FF{ static_cast(c_u128) }; - break; - case AvmMemoryTag::U16: - c = FF{ static_cast(c_u128) }; - break; - case AvmMemoryTag::U32: - c = FF{ static_cast(c_u128) }; - break; - case AvmMemoryTag::U64: - c = FF{ static_cast(c_u128) }; - break; - case AvmMemoryTag::U128: - c = FF{ uint256_t::from_uint128(c_u128) }; - break; - case AvmMemoryTag::FF: // Unsupported as instruction tag {} - case AvmMemoryTag::U0: // Unsupported as instruction tag {} - return FF{ 0 }; + // If dividing by zero, don't add any rows in the ALU, the error will be handled in the main trace + if (b_u256 == 0) { + return 0; } - alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry{ - .alu_clk = clk, - .alu_op_not = true, - .alu_u8_tag = in_tag == AvmMemoryTag::U8, - .alu_u16_tag = in_tag == AvmMemoryTag::U16, - .alu_u32_tag = in_tag == AvmMemoryTag::U32, - .alu_u64_tag = in_tag == AvmMemoryTag::U64, - .alu_u128_tag = in_tag == AvmMemoryTag::U128, - .alu_ia = a, - .alu_ic = c, - }); + if (a_u256 < b_u256) { + // If a < b, the result is trivially 0 + uint256_t rng_chk_lo = b_u256 - a_u256 - 1; + auto [u8_r0, u8_r1, u16_reg] = to_alu_slice_registers(rng_chk_lo); + alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry({ + .alu_clk = clk, + .alu_op_div = true, + .alu_u8_tag = in_tag == AvmMemoryTag::U8, + .alu_u16_tag = in_tag == AvmMemoryTag::U16, + .alu_u32_tag = in_tag == AvmMemoryTag::U32, + .alu_u64_tag = in_tag == AvmMemoryTag::U64, + .alu_u128_tag = in_tag == AvmMemoryTag::U128, + .alu_ia = a, + .alu_ib = b, + .alu_ic = 0, + .alu_u8_r0 = u8_r0, + .alu_u8_r1 = u8_r1, + .alu_u16_reg = u16_reg, + .hi_lo_limbs = { rng_chk_lo, 0, 0, 0, 0, 0 }, + .remainder = a, - return c; -} + })); + return 0; + } + // Decompose a and primality check that b*c < p when a is a 256-bit integer + auto [a_lo, a_hi] = decompose(b_u256 * c_u256, 128); + auto [p_sub_a_lo, p_sub_a_hi, p_a_borrow] = gt_witness(FF::modulus, b_u256 * c_u256); + // Decompose the divisor + auto [divisor_lo, divisor_hi] = decompose(b_u256, 64); + // Decompose the quotient + auto [quotient_lo, quotient_hi] = decompose(c_u256, 64); + uint256_t partial_prod = divisor_lo * quotient_hi + divisor_hi * quotient_lo; + // Decompose the partial product + auto [partial_prod_lo, partial_prod_hi] = decompose(partial_prod, 64); -/** - * @brief Build Alu trace and return a boolean based on equality of operands of type defined by in_tag. - * - * @param a Left operand of the equality - * @param b Right operand of the equality - * @param in_tag Instruction tag defining the number of bits for equality - * @param clk Clock referring to the operation in the main trace. - * - * @return FF The boolean result of equality casted to a finite field element - */ + FF b_hi = b_u256 - rem_u256 - 1; + + // 64 bit range checks for the divisor and quotient limbs + // Spread over two rows + std::array div_u64_rng_chk; + std::array div_u64_rng_chk_shifted; + for (size_t i = 0; i < 4; i++) { + div_u64_rng_chk.at(i) = uint16_t(divisor_lo >> (16 * i)); + div_u64_rng_chk.at(i + 4) = uint16_t(divisor_hi >> (16 * i)); + div_u64_range_chk_counters[i][uint16_t(divisor_lo >> (16 * i))]++; + div_u64_range_chk_counters[i + 4][uint16_t(divisor_hi >> (16 * i))]++; + + div_u64_rng_chk_shifted.at(i) = uint16_t(quotient_lo >> (16 * i)); + div_u64_rng_chk_shifted.at(i + 4) = uint16_t(quotient_hi >> (16 * i)); + div_u64_range_chk_counters[i][uint16_t(quotient_lo >> (16 * i))]++; + div_u64_range_chk_counters[i + 4][uint16_t(quotient_hi >> (16 * i))]++; + } + + // Each hi and lo limb is range checked over 128 bits + // Load the range check values into the ALU registers + auto hi_lo_limbs = std::vector{ a_lo, a_hi, partial_prod, b_hi, p_sub_a_lo, p_sub_a_hi }; + AvmAluTraceBuilder::AluTraceEntry row{ + .alu_clk = clk, + .alu_op_div = true, + .alu_u8_tag = in_tag == AvmMemoryTag::U8, + .alu_u16_tag = in_tag == AvmMemoryTag::U16, + .alu_u32_tag = in_tag == AvmMemoryTag::U32, + .alu_u64_tag = in_tag == AvmMemoryTag::U64, + .alu_u128_tag = in_tag == AvmMemoryTag::U128, + .alu_ia = a, + .alu_ib = b, + .alu_ic = FF{ c_u256 }, + .remainder = rem_u256, + .divisor_lo = divisor_lo, + .divisor_hi = divisor_hi, + .quotient_lo = quotient_lo, + .quotient_hi = quotient_hi, + .partial_prod_lo = partial_prod_lo, + .partial_prod_hi = partial_prod_hi, + .div_u64_range_chk_sel = true, + .div_u64_range_chk = div_u64_rng_chk, + + }; + // We perform the range checks here + std::vector rows = cmp_range_check_helper(row, hi_lo_limbs); + // Add the range checks for the quotient limbs in the row after the division operation + rows.at(1).div_u64_range_chk = div_u64_rng_chk_shifted; + rows.at(1).div_u64_range_chk_sel = true; + alu_trace.insert(alu_trace.end(), rows.begin(), rows.end()); + return c_u256; +} +/************************************************************************************************** + * COMPUTE - COMPARATORS + **************************************************************************************************/ + +/** + * @brief Build Alu trace and return a boolean based on equality of operands of type defined by in_tag. + * + * @param a Left operand of the equality + * @param b Right operand of the equality + * @param in_tag Instruction tag defining the number of bits for equality + * @param clk Clock referring to the operation in the main trace. + * + * @return FF The boolean result of equality casted to a finite field element + */ FF AvmAluTraceBuilder::op_eq(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t const clk) { FF c = a - b; @@ -433,122 +617,6 @@ FF AvmAluTraceBuilder::op_eq(FF const& a, FF const& b, AvmMemoryTag in_tag, uint return res; } -/** - * @brief This is a helper function that decomposes the input into the various registers of the ALU. - * This additionally increments the counts for the corresponding range lookups entries. - * @return A triplet of - */ -template -std::tuple> AvmAluTraceBuilder::to_alu_slice_registers(T a) -{ - range_checked_required = true; - auto alu_u8_r0 = static_cast(a); - a >>= 8; - auto alu_u8_r1 = static_cast(a); - a >>= 8; - std::array alu_u16_reg; - for (size_t i = 0; i < 15; i++) { - auto alu_u16 = static_cast(a); - u16_range_chk_counters[i][alu_u16]++; - alu_u16_reg.at(i) = alu_u16; - a >>= 16; - } - u8_range_chk_counters[0][alu_u8_r0]++; - u8_range_chk_counters[1][alu_u8_r1]++; - return std::make_tuple(alu_u8_r0, alu_u8_r1, alu_u16_reg); -} - -/** - * @brief This is a helper function that is used to generate the range check entries for operations that require - * multi-row range checks This additionally increments the counts for the corresponding range lookups entries. - * @param row The initial row where the comparison operation was performed - * @param hi_lo_limbs The vector of 128-bit limbs hi and lo pairs of limbs that will be range checked. - * @return A vector of AluTraceEntry rows for the range checks for the operation. - */ -std::vector AvmAluTraceBuilder::cmp_range_check_helper( - AvmAluTraceBuilder::AluTraceEntry row, std::vector hi_lo_limbs) -{ - // Assume each limb is 128 bits and since we can perform 256-bit range check per rows - // we need to have (limbs.size() / 2) range checks rows - size_t num_rows = hi_lo_limbs.size() / 2; - // The first row is the original comparison instruction (LT/LTE) - std::vector rows{ std::move(row) }; - rows.resize(num_rows, {}); - - // We need to ensure that the number of rows is even - ASSERT(hi_lo_limbs.size() % 2 == 0); - // Now for each row, we need to unpack a pair from the hi_lo_limb array into the ALUs 8-bit and 16-bit registers - // The first row unpacks a_lo and a_hi, the second row unpacks b_lo and b_hi, and so on. - for (size_t j = 0; j < num_rows; j++) { - auto& r = rows.at(j); - uint256_t lo_limb = hi_lo_limbs.at(2 * j); - uint256_t hi_limb = hi_lo_limbs.at(2 * j + 1); - uint256_t limb = lo_limb + (hi_limb << 128); - // Unpack lo limb and handle in the 8-bit registers - auto [alu_u8_r0, alu_u8_r1, alu_u16_reg] = AvmAluTraceBuilder::to_alu_slice_registers(limb); - r.alu_u8_r0 = alu_u8_r0; - r.alu_u8_r1 = alu_u8_r1; - r.alu_u16_reg = alu_u16_reg; - - r.cmp_rng_ctr = j > 0 ? static_cast(num_rows - j) : 0; - r.rng_chk_sel = j > 0; - r.alu_op_eq_diff_inv = j > 0 ? FF(num_rows - j).invert() : 0; - - std::vector limb_arr = { hi_lo_limbs.begin() + static_cast(2 * j), hi_lo_limbs.end() }; - // Resizing here is probably suboptimal for performance, we can probably handle the shorter vectors and - // pad with zero during the finalise - limb_arr.resize(10, FF::zero()); - r.hi_lo_limbs = limb_arr; - } - return rows; -} - -/** - * Helper function to decompose a uint256_t into a b-lower bits and (256-b) upper bits - * The outputs are cast to uint256_t so they are easier to use in checks - */ - -std::tuple decompose(uint256_t const& a, uint8_t const b) -{ - uint256_t upper_bitmask = (uint256_t(1) << uint256_t(b)) - 1; - uint256_t a_lo = a & upper_bitmask; - uint256_t a_hi = a >> b; - return std::make_tuple(a_lo, a_hi); -} - -// This creates a witness exclusively for the relation a > b -// This is useful when we want to enforce in certain checks that a must be greater than b -std::tuple gt_witness(uint256_t const& a, uint256_t const& b) -{ - uint256_t two_pow_128 = uint256_t(1) << uint256_t(128); - auto [a_lo, a_hi] = decompose(a, 128); - auto [b_lo, b_hi] = decompose(b, 128); - bool borrow = a_lo <= b_lo; - auto borrow_u256 = uint256_t(static_cast(borrow)); - uint256_t r_lo = a_lo - b_lo - 1 + borrow_u256 * two_pow_128; - uint256_t r_hi = a_hi - b_hi - borrow_u256; - return std::make_tuple(r_lo, r_hi, borrow); -} - -// This check is more flexible than gt_witness and is used when we want to generate the witness -// to the relation (a - b - 1) * q + (b - a) * (1 - q) -// where q = 1 if a > b and q = 0 if a <= b -std::tuple gt_or_lte_witness(uint256_t const& a, uint256_t const& b) -{ - uint256_t two_pow_126 = uint256_t(1) << uint256_t(128); - auto [a_lo, a_hi] = decompose(a, 128); - auto [b_lo, b_hi] = decompose(b, 128); - bool isGT = a > b; - if (isGT) { - return gt_witness(a, b); - } - bool borrow = b_lo < a_lo; - auto borrow_u256 = uint256_t(static_cast(borrow)); - uint256_t r_lo = b_lo - a_lo + borrow_u256 * two_pow_126; - uint256_t r_hi = b_hi - a_hi - borrow_u256; - return std::make_tuple(r_lo, r_hi, borrow); -} - /** * @brief Build Alu trace and compute the result of a LT operation on two operands. * The tag type in_tag does not change the result of the operation. But we @@ -660,52 +728,50 @@ FF AvmAluTraceBuilder::op_lte(FF const& a, FF const& b, AvmMemoryTag in_tag, uin return FF{ static_cast(c) }; } +/************************************************************************************************** + * COMPUTE - BITWISE + **************************************************************************************************/ + /** - * @brief Build ALU trace for the CAST opcode. + * @brief Build Alu trace and compute the result of a Bitwise Not of type defined by in_tag. * - * @param a Input value to be casted. Tag of the input is not taken into account. - * @param in_tag Tag specifying the type for the input to be casted into. + * @param a Unary operand of Not + * @param in_tag Instruction tag defining the number of bits on which the addition applies. + * It is assumed that the caller never uses the type u0. * @param clk Clock referring to the operation in the main trace. - * @return The casted value as a finite field element. + * + * @return FF The result of the not casted in a finite field element */ -FF AvmAluTraceBuilder::op_cast(FF const& a, AvmMemoryTag in_tag, uint32_t clk) +FF AvmAluTraceBuilder::op_not(FF const& a, AvmMemoryTag in_tag, uint32_t const clk) { - FF c; + FF c = 0; + uint128_t a_u128{ a }; + uint128_t c_u128 = ~a_u128; switch (in_tag) { case AvmMemoryTag::U8: - c = FF(uint8_t(a)); + c = FF{ static_cast(c_u128) }; break; case AvmMemoryTag::U16: - c = FF(uint16_t(a)); + c = FF{ static_cast(c_u128) }; break; case AvmMemoryTag::U32: - c = FF(uint32_t(a)); + c = FF{ static_cast(c_u128) }; break; case AvmMemoryTag::U64: - c = FF(uint64_t(a)); + c = FF{ static_cast(c_u128) }; break; case AvmMemoryTag::U128: - c = FF(uint256_t::from_uint128(uint128_t(a))); - break; - case AvmMemoryTag::FF: - c = a; - break; - default: - c = 0; + c = FF{ uint256_t::from_uint128(c_u128) }; break; + case AvmMemoryTag::FF: // Unsupported as instruction tag {} + case AvmMemoryTag::U0: // Unsupported as instruction tag {} + return FF{ 0 }; } - // Get the decomposition of a - auto [a_lo, a_hi] = decompose(uint256_t(a), 128); - // Decomposition of p-a - auto [p_sub_a_lo, p_sub_a_hi, p_a_borrow] = gt_witness(FF::modulus, uint256_t(a)); - auto [u8_r0, u8_r1, u16_reg] = to_alu_slice_registers(uint256_t(a)); - alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry{ .alu_clk = clk, - .alu_op_cast = true, - .alu_ff_tag = in_tag == AvmMemoryTag::FF, + .alu_op_not = true, .alu_u8_tag = in_tag == AvmMemoryTag::U8, .alu_u16_tag = in_tag == AvmMemoryTag::U16, .alu_u32_tag = in_tag == AvmMemoryTag::U32, @@ -713,73 +779,35 @@ FF AvmAluTraceBuilder::op_cast(FF const& a, AvmMemoryTag in_tag, uint32_t clk) .alu_u128_tag = in_tag == AvmMemoryTag::U128, .alu_ia = a, .alu_ic = c, - .alu_u8_r0 = u8_r0, - .alu_u8_r1 = u8_r1, - .alu_u16_reg = u16_reg, - .hi_lo_limbs = { a_lo, a_hi, p_sub_a_lo, p_sub_a_hi }, - .p_a_borrow = p_a_borrow, - }); - - uint256_t sub = (p_sub_a_hi << 128) + p_sub_a_lo; - auto [sub_u8_r0, sub_u8_r1, sub_u16_reg] = to_alu_slice_registers(sub); - - alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry{ - .alu_op_cast_prev = true, - .alu_u8_r0 = sub_u8_r0, - .alu_u8_r1 = sub_u8_r1, - .alu_u16_reg = sub_u16_reg, - .hi_lo_limbs = { p_sub_a_lo, p_sub_a_hi }, }); return c; } -// Returns the number of bits associated with a given memory tag -uint8_t mem_tag_bits(AvmMemoryTag in_tag) -{ - switch (in_tag) { - case AvmMemoryTag::U8: - return 8; - case AvmMemoryTag::U16: - return 16; - case AvmMemoryTag::U32: - return 32; - case AvmMemoryTag::U64: - return 64; - case AvmMemoryTag::U128: - return 128; - case AvmMemoryTag::FF: - return 254; - case AvmMemoryTag::U0: - return 0; - } - return 0; -} - -/** - * @brief Build Alu trace and compute the result of a SHR operation on two operands of type defined by in_tag. - * - * @param a Left operand of the SHR - * @param b Right operand of the SHR - * @param clk Clock referring to the operation in the main trace. - * @param in_tag Instruction tag defining the number of bits for the SHR. - * - * @return FF The boolean result of SHR casted to a finite field element - */ -FF AvmAluTraceBuilder::op_shr(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk) +/** + * @brief Build Alu trace and compute the result of a SHL operation on two operands of type defined by in_tag. + * + * @param a Left operand of the SHL + * @param b Right operand of the SHL + * @param clk Clock referring to the operation in the main trace. + * @param in_tag Instruction tag defining the number of bits for the SHL. + * + * @return FF The boolean result of SHL casted to a finite field element + */ +FF AvmAluTraceBuilder::op_shl(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk) { // Perform the shift operation over 256-bit integers uint256_t a_u256{ a }; - // Check that the shifted amount is an 8-bit integer + // Check that the shift amount is an 8-bit integer ASSERT(uint256_t(b) < 256); ASSERT(in_tag != AvmMemoryTag::U0 || in_tag != AvmMemoryTag::FF); uint8_t b_u8 = static_cast(uint256_t(b)); - uint256_t c_u256 = a_u256 >> b_u8; + + uint256_t c_u256 = a_u256 << b_u8; uint8_t num_bits = mem_tag_bits(in_tag); u8_pow_2_counters[0][b_u8]++; - // If we are shifting more than the number of bits, the result is trivially 0 if (b_u8 >= num_bits) { u8_pow_2_counters[1][b_u8 - num_bits]++; @@ -788,7 +816,7 @@ FF AvmAluTraceBuilder::op_shr(FF const& a, FF const& b, AvmMemoryTag in_tag, uin [[maybe_unused]] auto [alu_u8_r0, alu_u8_r1, alu_u16_reg] = AvmAluTraceBuilder::to_alu_slice_registers(0); alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry{ .alu_clk = clk, - .alu_op_shr = true, + .alu_op_shl = true, .alu_ff_tag = in_tag == AvmMemoryTag::FF, .alu_u8_tag = in_tag == AvmMemoryTag::U8, .alu_u16_tag = in_tag == AvmMemoryTag::U16, @@ -807,23 +835,46 @@ FF AvmAluTraceBuilder::op_shr(FF const& a, FF const& b, AvmMemoryTag in_tag, uin } // We decompose the input into two limbs partitioned at the b-th bit, we use x_lo and x_hi // to avoid any confusion with the a_lo and a_hi that form part of the range check - auto [x_lo, x_hi] = decompose(a, b_u8); + auto [x_lo, x_hi] = decompose(a, num_bits - b_u8); + + u8_pow_2_counters[1][num_bits - b_u8]++; // We can modify the dynamic range check by performing an additional static one - // rng_chk_lo = 2^b - x_lo - 1 && rng_chk_hi = 2^(num_bits - b) - x_hi - 1 - uint256_t rng_chk_lo = (uint256_t(1) << b_u8) - x_lo - 1; - uint256_t rng_chk_hi = (uint256_t(1) << (num_bits - b_u8)) - x_hi - 1; + // rng_chk_lo = 2^(num_bits - b) - x_lo - 1 && rng_chk_hi = 2^b - x_hi - 1 + uint256_t rng_chk_lo = uint256_t(uint256_t(1) << (num_bits - b_u8)) - x_lo - 1; + uint256_t rng_chk_hi = uint256_t(uint256_t(1) << b_u8) - x_hi - 1; // Each hi and lo limb is range checked over 128 bits - uint256_t limb = rng_chk_lo + (rng_chk_hi << uint256_t(128)); + uint256_t limb = rng_chk_lo + (rng_chk_hi << 128); // Load the range check values into the ALU registers auto [alu_u8_r0, alu_u8_r1, alu_u16_reg] = AvmAluTraceBuilder::to_alu_slice_registers(limb); - // Add counters for the pow of two lookups - u8_pow_2_counters[1][num_bits - b_u8]++; + FF c = 0; + switch (in_tag) { + case AvmMemoryTag::U8: + c = FF{ uint8_t(c_u256) }; + break; + case AvmMemoryTag::U16: + c = FF{ uint16_t(c_u256) }; + break; + case AvmMemoryTag::U32: + c = FF{ uint32_t(c_u256) }; + break; + case AvmMemoryTag::U64: + c = FF{ uint64_t(c_u256) }; + break; + case AvmMemoryTag::U128: + c = FF{ uint256_t::from_uint128(uint128_t(c_u256)) }; + break; + // Unsupported instruction tags, asserted earlier in function + case AvmMemoryTag::U0: + case AvmMemoryTag::FF: + __builtin_unreachable(); + } alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry{ .alu_clk = clk, - .alu_op_shr = true, + .alu_op_shl = true, + .alu_ff_tag = in_tag == AvmMemoryTag::FF, .alu_u8_tag = in_tag == AvmMemoryTag::U8, .alu_u16_tag = in_tag == AvmMemoryTag::U16, .alu_u32_tag = in_tag == AvmMemoryTag::U32, @@ -831,8 +882,7 @@ FF AvmAluTraceBuilder::op_shr(FF const& a, FF const& b, AvmMemoryTag in_tag, uin .alu_u128_tag = in_tag == AvmMemoryTag::U128, .alu_ia = a, .alu_ib = b, - // Could be replaced with x_hi but nice to have 2 ways of calculating the result - .alu_ic = FF(c_u256), + .alu_ic = c, .alu_u8_r0 = alu_u8_r0, .alu_u8_r1 = alu_u8_r1, .alu_u16_reg = alu_u16_reg, @@ -840,35 +890,34 @@ FF AvmAluTraceBuilder::op_shr(FF const& a, FF const& b, AvmMemoryTag in_tag, uin .mem_tag_bits = num_bits, .mem_tag_sub_shift = static_cast(num_bits - b_u8), .shift_lt_bit_len = true, - }); - return c_u256; + return c; } /** - * @brief Build Alu trace and compute the result of a SHL operation on two operands of type defined by in_tag. + * @brief Build Alu trace and compute the result of a SHR operation on two operands of type defined by in_tag. * - * @param a Left operand of the SHL - * @param b Right operand of the SHL + * @param a Left operand of the SHR + * @param b Right operand of the SHR * @param clk Clock referring to the operation in the main trace. - * @param in_tag Instruction tag defining the number of bits for the SHL. + * @param in_tag Instruction tag defining the number of bits for the SHR. * - * @return FF The boolean result of SHL casted to a finite field element + * @return FF The boolean result of SHR casted to a finite field element */ -FF AvmAluTraceBuilder::op_shl(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk) +FF AvmAluTraceBuilder::op_shr(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk) { // Perform the shift operation over 256-bit integers uint256_t a_u256{ a }; - // Check that the shift amount is an 8-bit integer + // Check that the shifted amount is an 8-bit integer ASSERT(uint256_t(b) < 256); ASSERT(in_tag != AvmMemoryTag::U0 || in_tag != AvmMemoryTag::FF); uint8_t b_u8 = static_cast(uint256_t(b)); - - uint256_t c_u256 = a_u256 << b_u8; + uint256_t c_u256 = a_u256 >> b_u8; uint8_t num_bits = mem_tag_bits(in_tag); u8_pow_2_counters[0][b_u8]++; + // If we are shifting more than the number of bits, the result is trivially 0 if (b_u8 >= num_bits) { u8_pow_2_counters[1][b_u8 - num_bits]++; @@ -877,7 +926,7 @@ FF AvmAluTraceBuilder::op_shl(FF const& a, FF const& b, AvmMemoryTag in_tag, uin [[maybe_unused]] auto [alu_u8_r0, alu_u8_r1, alu_u16_reg] = AvmAluTraceBuilder::to_alu_slice_registers(0); alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry{ .alu_clk = clk, - .alu_op_shl = true, + .alu_op_shr = true, .alu_ff_tag = in_tag == AvmMemoryTag::FF, .alu_u8_tag = in_tag == AvmMemoryTag::U8, .alu_u16_tag = in_tag == AvmMemoryTag::U16, @@ -896,46 +945,23 @@ FF AvmAluTraceBuilder::op_shl(FF const& a, FF const& b, AvmMemoryTag in_tag, uin } // We decompose the input into two limbs partitioned at the b-th bit, we use x_lo and x_hi // to avoid any confusion with the a_lo and a_hi that form part of the range check - auto [x_lo, x_hi] = decompose(a, num_bits - b_u8); - - u8_pow_2_counters[1][num_bits - b_u8]++; + auto [x_lo, x_hi] = decompose(a, b_u8); // We can modify the dynamic range check by performing an additional static one - // rng_chk_lo = 2^(num_bits - b) - x_lo - 1 && rng_chk_hi = 2^b - x_hi - 1 - uint256_t rng_chk_lo = uint256_t(uint256_t(1) << (num_bits - b_u8)) - x_lo - 1; - uint256_t rng_chk_hi = uint256_t(uint256_t(1) << b_u8) - x_hi - 1; + // rng_chk_lo = 2^b - x_lo - 1 && rng_chk_hi = 2^(num_bits - b) - x_hi - 1 + uint256_t rng_chk_lo = (uint256_t(1) << b_u8) - x_lo - 1; + uint256_t rng_chk_hi = (uint256_t(1) << (num_bits - b_u8)) - x_hi - 1; // Each hi and lo limb is range checked over 128 bits - uint256_t limb = rng_chk_lo + (rng_chk_hi << 128); + uint256_t limb = rng_chk_lo + (rng_chk_hi << uint256_t(128)); // Load the range check values into the ALU registers auto [alu_u8_r0, alu_u8_r1, alu_u16_reg] = AvmAluTraceBuilder::to_alu_slice_registers(limb); - FF c = 0; - switch (in_tag) { - case AvmMemoryTag::U8: - c = FF{ uint8_t(c_u256) }; - break; - case AvmMemoryTag::U16: - c = FF{ uint16_t(c_u256) }; - break; - case AvmMemoryTag::U32: - c = FF{ uint32_t(c_u256) }; - break; - case AvmMemoryTag::U64: - c = FF{ uint64_t(c_u256) }; - break; - case AvmMemoryTag::U128: - c = FF{ uint256_t::from_uint128(uint128_t(c_u256)) }; - break; - // Unsupported instruction tags, asserted earlier in function - case AvmMemoryTag::U0: - case AvmMemoryTag::FF: - __builtin_unreachable(); - } + // Add counters for the pow of two lookups + u8_pow_2_counters[1][num_bits - b_u8]++; alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry{ .alu_clk = clk, - .alu_op_shl = true, - .alu_ff_tag = in_tag == AvmMemoryTag::FF, + .alu_op_shr = true, .alu_u8_tag = in_tag == AvmMemoryTag::U8, .alu_u16_tag = in_tag == AvmMemoryTag::U16, .alu_u32_tag = in_tag == AvmMemoryTag::U32, @@ -943,7 +969,8 @@ FF AvmAluTraceBuilder::op_shl(FF const& a, FF const& b, AvmMemoryTag in_tag, uin .alu_u128_tag = in_tag == AvmMemoryTag::U128, .alu_ia = a, .alu_ib = b, - .alu_ic = c, + // Could be replaced with x_hi but nice to have 2 ways of calculating the result + .alu_ic = FF(c_u256), .alu_u8_r0 = alu_u8_r0, .alu_u8_r1 = alu_u8_r1, .alu_u16_reg = alu_u16_reg, @@ -951,106 +978,109 @@ FF AvmAluTraceBuilder::op_shl(FF const& a, FF const& b, AvmMemoryTag in_tag, uin .mem_tag_bits = num_bits, .mem_tag_sub_shift = static_cast(num_bits - b_u8), .shift_lt_bit_len = true, + }); - return c; + return c_u256; } -FF AvmAluTraceBuilder::op_div(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk) -{ - uint256_t a_u256{ a }; - uint256_t b_u256{ b }; - uint256_t c_u256 = a_u256 / b_u256; - uint256_t rem_u256 = a_u256 % b_u256; - // If dividing by zero, don't add any rows in the ALU, the error will be handled in the main trace - if (b_u256 == 0) { - return 0; - } +/************************************************************************************************** + * COMPUTE - TYPE CONVERSIONS + **************************************************************************************************/ - if (a_u256 < b_u256) { - // If a < b, the result is trivially 0 - uint256_t rng_chk_lo = b_u256 - a_u256 - 1; - auto [u8_r0, u8_r1, u16_reg] = to_alu_slice_registers(rng_chk_lo); - alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry({ - .alu_clk = clk, - .alu_op_div = true, - .alu_u8_tag = in_tag == AvmMemoryTag::U8, - .alu_u16_tag = in_tag == AvmMemoryTag::U16, - .alu_u32_tag = in_tag == AvmMemoryTag::U32, - .alu_u64_tag = in_tag == AvmMemoryTag::U64, - .alu_u128_tag = in_tag == AvmMemoryTag::U128, - .alu_ia = a, - .alu_ib = b, - .alu_ic = 0, - .alu_u8_r0 = u8_r0, - .alu_u8_r1 = u8_r1, - .alu_u16_reg = u16_reg, - .hi_lo_limbs = { rng_chk_lo, 0, 0, 0, 0, 0 }, - .remainder = a, +/** + * @brief Build ALU trace for the CAST opcode. + * + * @param a Input value to be casted. Tag of the input is not taken into account. + * @param in_tag Tag specifying the type for the input to be casted into. + * @param clk Clock referring to the operation in the main trace. + * @return The casted value as a finite field element. + */ +FF AvmAluTraceBuilder::op_cast(FF const& a, AvmMemoryTag in_tag, uint32_t clk) +{ + FF c; - })); - return 0; + switch (in_tag) { + case AvmMemoryTag::U8: + c = FF(uint8_t(a)); + break; + case AvmMemoryTag::U16: + c = FF(uint16_t(a)); + break; + case AvmMemoryTag::U32: + c = FF(uint32_t(a)); + break; + case AvmMemoryTag::U64: + c = FF(uint64_t(a)); + break; + case AvmMemoryTag::U128: + c = FF(uint256_t::from_uint128(uint128_t(a))); + break; + case AvmMemoryTag::FF: + c = a; + break; + default: + c = 0; + break; } - // Decompose a and primality check that b*c < p when a is a 256-bit integer - auto [a_lo, a_hi] = decompose(b_u256 * c_u256, 128); - auto [p_sub_a_lo, p_sub_a_hi, p_a_borrow] = gt_witness(FF::modulus, b_u256 * c_u256); - // Decompose the divisor - auto [divisor_lo, divisor_hi] = decompose(b_u256, 64); - // Decompose the quotient - auto [quotient_lo, quotient_hi] = decompose(c_u256, 64); - uint256_t partial_prod = divisor_lo * quotient_hi + divisor_hi * quotient_lo; - // Decompose the partial product - auto [partial_prod_lo, partial_prod_hi] = decompose(partial_prod, 64); - - FF b_hi = b_u256 - rem_u256 - 1; - // 64 bit range checks for the divisor and quotient limbs - // Spread over two rows - std::array div_u64_rng_chk; - std::array div_u64_rng_chk_shifted; - for (size_t i = 0; i < 4; i++) { - div_u64_rng_chk.at(i) = uint16_t(divisor_lo >> (16 * i)); - div_u64_rng_chk.at(i + 4) = uint16_t(divisor_hi >> (16 * i)); - div_u64_range_chk_counters[i][uint16_t(divisor_lo >> (16 * i))]++; - div_u64_range_chk_counters[i + 4][uint16_t(divisor_hi >> (16 * i))]++; - - div_u64_rng_chk_shifted.at(i) = uint16_t(quotient_lo >> (16 * i)); - div_u64_rng_chk_shifted.at(i + 4) = uint16_t(quotient_hi >> (16 * i)); - div_u64_range_chk_counters[i][uint16_t(quotient_lo >> (16 * i))]++; - div_u64_range_chk_counters[i + 4][uint16_t(quotient_hi >> (16 * i))]++; - } + // Get the decomposition of a + auto [a_lo, a_hi] = decompose(uint256_t(a), 128); + // Decomposition of p-a + auto [p_sub_a_lo, p_sub_a_hi, p_a_borrow] = gt_witness(FF::modulus, uint256_t(a)); + auto [u8_r0, u8_r1, u16_reg] = to_alu_slice_registers(uint256_t(a)); - // Each hi and lo limb is range checked over 128 bits - // Load the range check values into the ALU registers - auto hi_lo_limbs = std::vector{ a_lo, a_hi, partial_prod, b_hi, p_sub_a_lo, p_sub_a_hi }; - AvmAluTraceBuilder::AluTraceEntry row{ + alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry{ .alu_clk = clk, - .alu_op_div = true, + .alu_op_cast = true, + .alu_ff_tag = in_tag == AvmMemoryTag::FF, .alu_u8_tag = in_tag == AvmMemoryTag::U8, .alu_u16_tag = in_tag == AvmMemoryTag::U16, .alu_u32_tag = in_tag == AvmMemoryTag::U32, .alu_u64_tag = in_tag == AvmMemoryTag::U64, .alu_u128_tag = in_tag == AvmMemoryTag::U128, .alu_ia = a, - .alu_ib = b, - .alu_ic = FF{ c_u256 }, - .remainder = rem_u256, - .divisor_lo = divisor_lo, - .divisor_hi = divisor_hi, - .quotient_lo = quotient_lo, - .quotient_hi = quotient_hi, - .partial_prod_lo = partial_prod_lo, - .partial_prod_hi = partial_prod_hi, - .div_u64_range_chk_sel = true, - .div_u64_range_chk = div_u64_rng_chk, + .alu_ic = c, + .alu_u8_r0 = u8_r0, + .alu_u8_r1 = u8_r1, + .alu_u16_reg = u16_reg, + .hi_lo_limbs = { a_lo, a_hi, p_sub_a_lo, p_sub_a_hi }, + .p_a_borrow = p_a_borrow, + }); - }; - // We perform the range checks here - std::vector rows = cmp_range_check_helper(row, hi_lo_limbs); - // Add the range checks for the quotient limbs in the row after the division operation - rows.at(1).div_u64_range_chk = div_u64_rng_chk_shifted; - rows.at(1).div_u64_range_chk_sel = true; - alu_trace.insert(alu_trace.end(), rows.begin(), rows.end()); - return c_u256; + uint256_t sub = (p_sub_a_hi << 128) + p_sub_a_lo; + auto [sub_u8_r0, sub_u8_r1, sub_u16_reg] = to_alu_slice_registers(sub); + + alu_trace.push_back(AvmAluTraceBuilder::AluTraceEntry{ + .alu_op_cast_prev = true, + .alu_u8_r0 = sub_u8_r0, + .alu_u8_r1 = sub_u8_r1, + .alu_u16_reg = sub_u16_reg, + .hi_lo_limbs = { p_sub_a_lo, p_sub_a_hi }, + }); + + return c; +} + +/** + * @brief Helper routine telling whether range check is required. + * + * @return A boolean telling whether range check is required. + */ +bool AvmAluTraceBuilder::is_range_check_required() const +{ + return range_checked_required; +} + +/** + * @brief Helper function that returns a boolean if this entry is an alu operation. + * This is helpful to filter out range check rows or the second row in the 128-bit multiply. + * + * @return A boolean telling whether range check is required. + */ +bool AvmAluTraceBuilder::is_alu_row_enabled(AvmAluTraceBuilder::AluTraceEntry const& r) +{ + return (r.alu_op_add || r.alu_op_sub || r.alu_op_mul || r.alu_op_eq || r.alu_op_not || r.alu_op_lt || + r.alu_op_lte || r.alu_op_shr || r.alu_op_shl || r.alu_op_cast || r.alu_op_div); } } // namespace bb::avm_trace diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_alu_trace.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_alu_trace.hpp index fdd751b0cf8..85538563eb6 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_alu_trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_alu_trace.hpp @@ -18,15 +18,18 @@ class AvmAluTraceBuilder { bool alu_op_add = false; bool alu_op_sub = false; bool alu_op_mul = false; - bool alu_op_not = false; + bool alu_op_div = false; + bool alu_op_eq = false; bool alu_op_lt = false; bool alu_op_lte = false; + + bool alu_op_not = false; + bool alu_op_shl = false; + bool alu_op_shr = false; + bool alu_op_cast = false; bool alu_op_cast_prev = false; - bool alu_op_shr = false; - bool alu_op_shl = false; - bool alu_op_div = false; bool alu_ff_tag = false; bool alu_u8_tag = false; @@ -85,17 +88,24 @@ class AvmAluTraceBuilder { void reset(); std::vector finalize(); + // Compute - Arithmetic FF op_add(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); FF op_sub(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); FF op_mul(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); - FF op_not(FF const& a, AvmMemoryTag in_tag, uint32_t clk); + FF op_div(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); + + // Compute - Comparators FF op_eq(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); FF op_lt(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); FF op_lte(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); - FF op_cast(FF const& a, AvmMemoryTag in_tag, uint32_t clk); - FF op_shr(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); + + // Compute - Bitwise + FF op_not(FF const& a, AvmMemoryTag in_tag, uint32_t clk); FF op_shl(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); - FF op_div(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); + FF op_shr(FF const& a, FF const& b, AvmMemoryTag in_tag, uint32_t clk); + + // Compute - Type Conversions + FF op_cast(FF const& a, AvmMemoryTag in_tag, uint32_t clk); bool is_range_check_required() const; static bool is_alu_row_enabled(AvmAluTraceBuilder::AluTraceEntry const& r); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.cpp index ea7905c1210..a8ee5e4aaa5 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.cpp @@ -358,12 +358,6 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(4)), std::get(inst.operands.at(1))); break; - case OpCode::FDIV: - trace_builder.op_fdiv(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2)), - std::get(inst.operands.at(3))); - break; case OpCode::DIV: trace_builder.op_div(std::get(inst.operands.at(0)), std::get(inst.operands.at(2)), @@ -371,6 +365,13 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(4)), std::get(inst.operands.at(1))); break; + case OpCode::FDIV: + trace_builder.op_fdiv(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2)), + std::get(inst.operands.at(3))); + break; + // Compute - Comparators case OpCode::EQ: trace_builder.op_eq(std::get(inst.operands.at(0)), @@ -393,14 +394,8 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(4)), std::get(inst.operands.at(1))); break; - // Compute - Bitwise - case OpCode::NOT: - trace_builder.op_not(std::get(inst.operands.at(0)), - std::get(inst.operands.at(2)), - std::get(inst.operands.at(3)), - std::get(inst.operands.at(1))); - break; + // Compute - Bitwise case OpCode::AND: trace_builder.op_and(std::get(inst.operands.at(0)), std::get(inst.operands.at(2)), @@ -415,7 +410,6 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(4)), std::get(inst.operands.at(1))); break; - case OpCode::XOR: trace_builder.op_xor(std::get(inst.operands.at(0)), std::get(inst.operands.at(2)), @@ -423,11 +417,10 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(4)), std::get(inst.operands.at(1))); break; - case OpCode::SHR: - trace_builder.op_shr(std::get(inst.operands.at(0)), + case OpCode::NOT: + trace_builder.op_not(std::get(inst.operands.at(0)), std::get(inst.operands.at(2)), std::get(inst.operands.at(3)), - std::get(inst.operands.at(4)), std::get(inst.operands.at(1))); break; case OpCode::SHL: @@ -437,6 +430,14 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(4)), std::get(inst.operands.at(1))); break; + case OpCode::SHR: + trace_builder.op_shr(std::get(inst.operands.at(0)), + std::get(inst.operands.at(2)), + std::get(inst.operands.at(3)), + std::get(inst.operands.at(4)), + std::get(inst.operands.at(1))); + break; + // Compute - Type Conversions case OpCode::CAST: trace_builder.op_cast(std::get(inst.operands.at(0)), @@ -444,21 +445,9 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(3)), std::get(inst.operands.at(1))); break; - // Execution Environment - Calldata - case OpCode::CALLDATACOPY: - trace_builder.op_calldata_copy(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2)), - std::get(inst.operands.at(3))); - break; - // Machine State - Gas - case OpCode::L2GASLEFT: - trace_builder.op_l2gasleft(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); - break; - case OpCode::DAGASLEFT: - trace_builder.op_dagasleft(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); - break; - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/6284): support indirect for below + + // Execution Environment + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/6284): support indirect for below case OpCode::ADDRESS: trace_builder.op_address(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); break; @@ -477,6 +466,8 @@ std::vector Execution::gen_trace(std::vector const& instructio trace_builder.op_transaction_fee(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); break; + + // Execution Environment - Globals case OpCode::CHAINID: trace_builder.op_chain_id(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); break; @@ -487,12 +478,12 @@ std::vector Execution::gen_trace(std::vector const& instructio trace_builder.op_block_number(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); break; - case OpCode::COINBASE: - trace_builder.op_coinbase(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); - break; case OpCode::TIMESTAMP: trace_builder.op_timestamp(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); break; + case OpCode::COINBASE: + trace_builder.op_coinbase(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); + break; case OpCode::FEEPERL2GAS: trace_builder.op_fee_per_l2_gas(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); @@ -501,62 +492,23 @@ std::vector Execution::gen_trace(std::vector const& instructio trace_builder.op_fee_per_da_gas(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); break; - case OpCode::NOTEHASHEXISTS: - trace_builder.op_note_hash_exists(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - // TODO: leaf offset exists - // std::get(inst.operands.at(2)) - std::get(inst.operands.at(3))); - break; - case OpCode::EMITNOTEHASH: - trace_builder.op_emit_note_hash(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1))); - break; - case OpCode::NULLIFIEREXISTS: - trace_builder.op_nullifier_exists(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - // std::get(inst.operands.at(2)) - /**TODO: Address offset for siloing */ - std::get(inst.operands.at(3))); - break; - case OpCode::EMITNULLIFIER: - trace_builder.op_emit_nullifier(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1))); - break; - case OpCode::SLOAD: - trace_builder.op_sload(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2)), - std::get(inst.operands.at(3))); - break; - case OpCode::SSTORE: - trace_builder.op_sstore(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2)), - std::get(inst.operands.at(3))); - break; - case OpCode::L1TOL2MSGEXISTS: - trace_builder.op_l1_to_l2_msg_exists(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - // TODO: leaf offset exists - // std::get(inst.operands.at(2)) - std::get(inst.operands.at(3))); - break; - case OpCode::GETCONTRACTINSTANCE: - trace_builder.op_get_contract_instance(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2))); + + // Execution Environment - Calldata + case OpCode::CALLDATACOPY: + trace_builder.op_calldata_copy(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2)), + std::get(inst.operands.at(3))); break; - case OpCode::EMITUNENCRYPTEDLOG: - trace_builder.op_emit_unencrypted_log(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2))); + + // Machine State - Gas + case OpCode::L2GASLEFT: + trace_builder.op_l2gasleft(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); break; - case OpCode::SENDL2TOL1MSG: - trace_builder.op_emit_l2_to_l1_msg(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2))); + case OpCode::DAGASLEFT: + trace_builder.op_dagasleft(std::get(inst.operands.at(0)), std::get(inst.operands.at(1))); break; + // Machine State - Internal Control Flow case OpCode::JUMP: trace_builder.op_jump(std::get(inst.operands.at(0))); @@ -572,6 +524,7 @@ std::vector Execution::gen_trace(std::vector const& instructio case OpCode::INTERNALRETURN: trace_builder.op_internal_return(); break; + // Machine State - Memory case OpCode::SET: { uint128_t val = 0; @@ -613,20 +566,69 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(3)), std::get(inst.operands.at(4))); break; - // Control Flow - Contract Calls - case OpCode::RETURN: { - auto ret = trace_builder.op_return(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2))); - returndata.insert(returndata.end(), ret.begin(), ret.end()); + // World State + case OpCode::SLOAD: + trace_builder.op_sload(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2)), + std::get(inst.operands.at(3))); + break; + case OpCode::SSTORE: + trace_builder.op_sstore(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2)), + std::get(inst.operands.at(3))); + break; + case OpCode::NOTEHASHEXISTS: + trace_builder.op_note_hash_exists(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + // TODO: leaf offset exists + // std::get(inst.operands.at(2)) + std::get(inst.operands.at(3))); + break; + case OpCode::EMITNOTEHASH: + trace_builder.op_emit_note_hash(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1))); + break; + case OpCode::NULLIFIEREXISTS: + trace_builder.op_nullifier_exists(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + // std::get(inst.operands.at(2)) + /**TODO: Address offset for siloing */ + std::get(inst.operands.at(3))); + break; + case OpCode::EMITNULLIFIER: + trace_builder.op_emit_nullifier(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1))); + break; + + case OpCode::L1TOL2MSGEXISTS: + trace_builder.op_l1_to_l2_msg_exists(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + // TODO: leaf offset exists + // std::get(inst.operands.at(2)) + std::get(inst.operands.at(3))); + break; + case OpCode::GETCONTRACTINSTANCE: + trace_builder.op_get_contract_instance(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2))); + break; + + // Accrued Substate + case OpCode::EMITUNENCRYPTEDLOG: + trace_builder.op_emit_unencrypted_log(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2))); break; - } - case OpCode::DEBUGLOG: - // We want a noop, but we need to execute something that both advances the PC, - // and adds a valid row to the trace. - trace_builder.op_jump(pc + 1); + case OpCode::SENDL2TOL1MSG: + trace_builder.op_emit_l2_to_l1_msg(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2))); break; + + // Control Flow - Contract Calls case OpCode::CALL: trace_builder.op_call(std::get(inst.operands.at(0)), std::get(inst.operands.at(1)), @@ -638,24 +640,34 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(7)), std::get(inst.operands.at(8))); break; - case OpCode::TORADIXLE: - trace_builder.op_to_radix_le(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2)), - std::get(inst.operands.at(3)), - std::get(inst.operands.at(4))); + case OpCode::RETURN: { + auto ret = trace_builder.op_return(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2))); + returndata.insert(returndata.end(), ret.begin(), ret.end()); + break; - case OpCode::SHA256COMPRESSION: - trace_builder.op_sha256_compression(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2)), - std::get(inst.operands.at(3))); + } + case OpCode::REVERT: + trace_builder.op_revert(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2))); break; - case OpCode::SHA256: - trace_builder.op_sha256(std::get(inst.operands.at(0)), + + // Misc + case OpCode::DEBUGLOG: + // We want a noop, but we need to execute something that both advances the PC, + // and adds a valid row to the trace. + trace_builder.op_jump(pc + 1); + break; + + // Gadgets + case OpCode::KECCAK: + trace_builder.op_keccak(std::get(inst.operands.at(0)), std::get(inst.operands.at(1)), std::get(inst.operands.at(2)), std::get(inst.operands.at(3))); + break; case OpCode::POSEIDON2: trace_builder.op_poseidon2_permutation(std::get(inst.operands.at(0)), @@ -663,19 +675,11 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(2))); break; - case OpCode::KECCAK: - trace_builder.op_keccak(std::get(inst.operands.at(0)), + case OpCode::SHA256: + trace_builder.op_sha256(std::get(inst.operands.at(0)), std::get(inst.operands.at(1)), std::get(inst.operands.at(2)), std::get(inst.operands.at(3))); - - break; - case OpCode::KECCAKF1600: - trace_builder.op_keccakf1600(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2)), - std::get(inst.operands.at(3))); - break; case OpCode::PEDERSEN: trace_builder.op_pedersen_hash(std::get(inst.operands.at(0)), @@ -701,10 +705,30 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(3)), std::get(inst.operands.at(4))); break; - case OpCode::REVERT: - trace_builder.op_revert(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2))); + + // Conversions + case OpCode::TORADIXLE: + trace_builder.op_to_radix_le(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2)), + std::get(inst.operands.at(3)), + std::get(inst.operands.at(4))); + break; + + // Future Gadgets -- pending changes in noir + case OpCode::SHA256COMPRESSION: + trace_builder.op_sha256_compression(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2)), + std::get(inst.operands.at(3))); + break; + + case OpCode::KECCAKF1600: + trace_builder.op_keccakf1600(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2)), + std::get(inst.operands.at(3))); + break; default: throw_or_abort("Don't know how to execute opcode " + to_hex(inst.op_code) + " at pc " + std::to_string(pc) + diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.cpp index f137128d9f0..7a4a837c574 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.cpp @@ -24,6 +24,8 @@ std::string to_hex(OpCode opcode) std::string to_string(OpCode opcode) { switch (opcode) { + // Compute + // Compute - Arithmetic case OpCode::ADD: return "ADD"; case OpCode::SUB: @@ -34,12 +36,14 @@ std::string to_string(OpCode opcode) return "DIV"; case OpCode::FDIV: return "FDIV"; + // Compute - Comparators case OpCode::EQ: return "EQ"; case OpCode::LT: return "LT"; case OpCode::LTE: return "LTE"; + // Compute - Bitwise case OpCode::AND: return "AND"; case OpCode::OR: @@ -52,8 +56,10 @@ std::string to_string(OpCode opcode) return "SHL"; case OpCode::SHR: return "SHR"; + // Compute - Type Conversions case OpCode::CAST: return "CAST"; + // Execution Environment case OpCode::ADDRESS: return "ADDRESS"; case OpCode::STORAGEADDRESS: @@ -64,6 +70,7 @@ std::string to_string(OpCode opcode) return "FUNCTIONSELECTOR"; case OpCode::TRANSACTIONFEE: return "TRANSACTIONFEE"; + // Execution Environment - Globals case OpCode::CHAINID: return "CHAINID"; case OpCode::VERSION: @@ -82,12 +89,16 @@ std::string to_string(OpCode opcode) return "BLOCKL2GASLIMIT"; case OpCode::BLOCKDAGASLIMIT: return "BLOCKDAGASLIMIT"; + // Execution Environment - Calldata case OpCode::CALLDATACOPY: return "CALLDATACOPY"; + // Machine State + // Machine State - Gas case OpCode::L2GASLEFT: return "L2GASLEFT"; case OpCode::DAGASLEFT: return "DAGASLEFT"; + // Machine State - Internal Control Flow case OpCode::JUMP: return "JUMP"; case OpCode::JUMPI: @@ -96,12 +107,14 @@ std::string to_string(OpCode opcode) return "INTERNALCALL"; case OpCode::INTERNALRETURN: return "INTERNALRETURN"; + // Machine State - Memory case OpCode::SET: return "SET"; case OpCode::MOV: return "MOV"; case OpCode::CMOV: return "CMOV"; + // World State case OpCode::SLOAD: return "SLOAD"; case OpCode::SSTORE: @@ -120,10 +133,12 @@ std::string to_string(OpCode opcode) return "HEADERMEMBER"; case OpCode::GETCONTRACTINSTANCE: return "GETCONTRACTINSTANCE"; + // Accrued Substate case OpCode::EMITUNENCRYPTEDLOG: return "EMITUNENCRYPTEDLOG"; case OpCode::SENDL2TOL1MSG: return "SENDL2TOL1MSG"; + // Control Flow - Contract Calls case OpCode::CALL: return "CALL"; case OpCode::STATICCALL: @@ -134,8 +149,10 @@ std::string to_string(OpCode opcode) return "RETURN"; case OpCode::REVERT: return "REVERT"; + // Misc case OpCode::DEBUGLOG: return "DEBUGLOG"; + // Gadgets case OpCode::KECCAK: return "KECCAK"; case OpCode::POSEIDON2: @@ -148,12 +165,15 @@ std::string to_string(OpCode opcode) return "ECADD"; case OpCode::MSM: return "MSM"; + // Conversions case OpCode::TORADIXLE: return "TORADIXLE"; + // Future Gadgets -- pending changes in noir case OpCode::SHA256COMPRESSION: return "SHA256COMPRESSION"; case OpCode::KECCAKF1600: return "KECCAKF1600"; + // Sentinel case OpCode::LAST_OPCODE_SENTINEL: return "LAST_OPCODE_SENTINEL"; default: diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp index 55b22de128d..805381a34e9 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp @@ -26,49 +26,126 @@ namespace bb::avm_trace { -/** - * @brief Constructor of a trace builder of AVM. Only serves to set the capacity of the - * underlying traces and initialize gas values. - */ -AvmTraceBuilder::AvmTraceBuilder(VmPublicInputs public_inputs, - ExecutionHints execution_hints, - uint32_t side_effect_counter, - std::vector calldata) - // NOTE: we initialise the environment builder here as it requires public inputs - : kernel_trace_builder(std::move(public_inputs)) - , calldata(std::move(calldata)) - , side_effect_counter(side_effect_counter) - , initial_side_effect_counter(side_effect_counter) - , execution_hints(std::move(execution_hints)) +/************************************************************************************************** + * HELPERS IN ANONYMOUS NAMESPACE + **************************************************************************************************/ +namespace { +// WARNING: FOR TESTING ONLY +// Generates the minimal lookup table for the binary trace +uint32_t finalize_bin_trace_lookup_for_testing(std::vector& main_trace, AvmBinaryTraceBuilder& bin_trace_builder) { - main_trace.reserve(AVM_TRACE_SIZE); - - // TODO: think about cast - gas_trace_builder.set_initial_gas(static_cast(std::get( - kernel_trace_builder.public_inputs)[L2_GAS_LEFT_CONTEXT_INPUTS_OFFSET]), - static_cast(std::get( - kernel_trace_builder.public_inputs)[DA_GAS_LEFT_CONTEXT_INPUTS_OFFSET])); + // Generate ByteLength Lookup table of instruction tags to the number of bytes + // {U8: 1, U16: 2, U32: 4, U64: 8, U128: 16} + for (auto const& [clk, count] : bin_trace_builder.byte_operation_counter) { + // from the clk we can derive the a and b inputs + auto b = static_cast(clk); + auto a = static_cast(clk >> 8); + auto op_id = static_cast(clk >> 16); + uint8_t bit_op = 0; + if (op_id == 0) { + bit_op = a & b; + } else if (op_id == 1) { + bit_op = a | b; + } else { + bit_op = a ^ b; + } + if (clk > (main_trace.size() - 1)) { + main_trace.push_back(Row{ + .main_clk = FF(clk), + .byte_lookup_sel_bin = FF(1), + .byte_lookup_table_input_a = a, + .byte_lookup_table_input_b = b, + .byte_lookup_table_op_id = op_id, + .byte_lookup_table_output = bit_op, + .lookup_byte_operations_counts = count, + }); + } else { + main_trace.at(clk).lookup_byte_operations_counts = count; + main_trace.at(clk).byte_lookup_sel_bin = FF(1); + main_trace.at(clk).byte_lookup_table_op_id = op_id; + main_trace.at(clk).byte_lookup_table_input_a = a; + main_trace.at(clk).byte_lookup_table_input_b = b; + main_trace.at(clk).byte_lookup_table_output = bit_op; + } + // Add the counter value stored throughout the execution + } + return static_cast(main_trace.size()); } -/** - * @brief Resetting the internal state so that a new trace can be rebuilt using the same object. - * - */ -void AvmTraceBuilder::reset() +constexpr size_t L2_HI_GAS_COUNTS_IDX = 0; +constexpr size_t L2_LO_GAS_COUNTS_IDX = 1; +constexpr size_t DA_HI_GAS_COUNTS_IDX = 2; +constexpr size_t DA_LO_GAS_COUNTS_IDX = 3; + +// WARNING: FOR TESTING ONLY +// Generates the lookup table for the range checks without doing a full 2**16 rows +uint32_t finalize_rng_chks_for_testing( + std::vector& main_trace, + AvmAluTraceBuilder const& alu_trace_builder, + AvmMemTraceBuilder const& mem_trace_builder, + std::unordered_map const& mem_rng_check_lo_counts, + std::unordered_map const& mem_rng_check_mid_counts, + std::unordered_map const& mem_rng_check_hi_counts, + std::array, 4> const& rem_gas_rng_check_counts) { - main_trace.clear(); - mem_trace_builder.reset(); - alu_trace_builder.reset(); - bin_trace_builder.reset(); - kernel_trace_builder.reset(); - gas_trace_builder.reset(); - conversion_trace_builder.reset(); - sha256_trace_builder.reset(); - poseidon2_trace_builder.reset(); - keccak_trace_builder.reset(); - pedersen_trace_builder.reset(); + // Build the main_trace, and add any new rows with specific clks that line up with lookup reads - external_call_counter = 0; + // Is there a "spread-like" operator in cpp or can I make it generic of the first param of the unordered map + std::vector> u8_rng_chks = { alu_trace_builder.u8_range_chk_counters[0], + alu_trace_builder.u8_range_chk_counters[1], + alu_trace_builder.u8_pow_2_counters[0], + alu_trace_builder.u8_pow_2_counters[1], + std::move(mem_rng_check_hi_counts) }; + + std::vector const>> u16_rng_chks; + + u16_rng_chks.emplace_back(mem_rng_check_lo_counts); + u16_rng_chks.emplace_back(mem_rng_check_mid_counts); + for (size_t i = 0; i < 4; i++) { + u16_rng_chks.emplace_back(rem_gas_rng_check_counts[i]); + } + + for (size_t i = 0; i < 15; i++) { + u16_rng_chks.emplace_back(alu_trace_builder.u16_range_chk_counters[i]); + } + + auto custom_clk = std::set{}; + for (auto const& row : u8_rng_chks) { + for (auto const& [key, value] : row) { + custom_clk.insert(key); + } + } + + for (auto const& row : alu_trace_builder.u16_range_chk_counters) { + for (auto const& [key, value] : row) { + custom_clk.insert(key); + } + } + + for (auto row : u16_rng_chks) { + for (auto const& [key, value] : row.get()) { + custom_clk.insert(key); + } + } + + for (auto const& row : alu_trace_builder.div_u64_range_chk_counters) { + for (auto const& [key, value] : row) { + custom_clk.insert(key); + } + } + + for (auto const& [clk, count] : mem_trace_builder.m_tag_err_lookup_counts) { + custom_clk.insert(clk); + } + + auto old_size = main_trace.size(); + for (auto const& clk : custom_clk) { + if (clk >= old_size) { + main_trace.push_back(Row{ .main_clk = FF(clk) }); + } + } + + return static_cast(main_trace.size()); } /** @@ -91,6 +168,42 @@ std::array unpack_indirects(uint8_t indirect, std::array std::array vec_to_arr(std::vector const& vec) +{ + std::array arr; + ASSERT(T == vec.size()); + for (size_t i = 0; i < T; i++) { + arr[i] = vec[i]; + } + return arr; +} +} // anonymous namespace + +/************************************************************************************************** + * HELPERS + **************************************************************************************************/ + +/** + * @brief HALT opcode + * This opcode effectively stops program execution, and is used in the relation that + * ensures the program counter increments on each opcode. + * i.e. the program counter should freeze and the halt flag is set to 1. + */ +void AvmTraceBuilder::halt() +{ + auto clk = main_trace.size() + 1; + + main_trace.push_back(Row{ + .main_clk = clk, + .main_call_ptr = call_ptr, + .main_internal_return_ptr = FF(internal_return_ptr), + .main_pc = FF(pc), + .main_sel_op_halt = FF(1), + }); + + pc = UINT32_MAX; // This ensures that no subsequent opcode will be executed. +} + /** * @brief Loads a value from memory into a given intermediate register at a specified clock cycle. * Handles both direct and indirect memory access. @@ -170,70 +283,275 @@ AvmTraceBuilder::MemOp AvmTraceBuilder::constrained_write_to_memory(uint8_t spac .val = value }; } -/** - * @brief Addition with direct or indirect memory access. - * - * @param indirect A byte encoding information about indirect/direct memory access. - * @param a_offset An index in memory pointing to the first operand of the addition. - * @param b_offset An index in memory pointing to the second operand of the addition. - * @param dst_offset An index in memory pointing to the output of the addition. - * @param in_tag The instruction memory tag of the operands. - */ -void AvmTraceBuilder::op_add( - uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) +// TODO(ilyas: #6383): Temporary way to bulk read slices +template +uint32_t AvmTraceBuilder::read_slice_to_memory(uint8_t space_id, + uint32_t clk, + AddressWithMode addr, + AvmMemoryTag r_tag, + AvmMemoryTag w_tag, + FF internal_return_ptr, + size_t slice_len, + std::vector& slice) { - auto clk = static_cast(main_trace.size()) + 1; - - // Resolve any potential indirects in the order they are encoded in the indirect byte. - auto [resolved_a, resolved_b, resolved_c] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); - - // Reading from memory and loading into ia resp. ib. - auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); - auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, in_tag, IntermRegister::IB); - - bool tag_match = read_a.tag_match && read_b.tag_match; - - // a + b = c - FF a = read_a.val; - FF b = read_b.val; - - // In case of a memory tag error, we do not perform the computation. - // Therefore, we do not create any entry in ALU table and store the value 0 as - // output (c) in memory. - FF c = tag_match ? alu_trace_builder.op_add(a, b, in_tag, clk) : FF(0); - - // Write into memory value c from intermediate register ic. - auto write_c = constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, in_tag, IntermRegister::IC); - - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::ADD); - - main_trace.push_back(Row{ - .main_clk = clk, - .main_alu_in_tag = FF(static_cast(in_tag)), - .main_call_ptr = call_ptr, - .main_ia = read_a.val, - .main_ib = read_b.val, - .main_ic = write_c.val, - .main_ind_addr_a = FF(read_a.indirect_address), - .main_ind_addr_b = FF(read_b.indirect_address), - .main_ind_addr_c = FF(write_c.indirect_address), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(read_a.direct_address), - .main_mem_addr_b = FF(read_b.direct_address), - .main_mem_addr_c = FF(write_c.direct_address), - .main_pc = FF(pc++), - .main_r_in_tag = FF(static_cast(in_tag)), - .main_rwc = FF(1), - .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_b = FF(1), - .main_sel_mem_op_c = FF(1), - .main_sel_op_add = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), - .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), - .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), - .main_tag_err = FF(static_cast(!tag_match)), - .main_w_in_tag = FF(static_cast(in_tag)), + // If the mem_op is indirect, it goes into register A + bool is_indirect = addr.mode == AddressingMode::INDIRECT; + auto src_offset = addr.offset; + // We have 4 registers that we are able to use to read from memory within a single main trace row + auto register_order = std::array{ IntermRegister::IA, IntermRegister::IB, IntermRegister::IC, IntermRegister::ID }; + // If the slice size isnt a multiple of 4, we still need an extra row to write the remainder + uint32_t const num_main_rows = static_cast(slice_len) / 4 + static_cast(slice_len % 4 != 0); + for (uint32_t i = 0; i < num_main_rows; i++) { + Row main_row{ + .main_clk = clk + i, + .main_internal_return_ptr = FF(internal_return_ptr), + .main_pc = FF(pc), + .main_r_in_tag = FF(static_cast(r_tag)), + .main_w_in_tag = FF(static_cast(w_tag)), + }; + // Write 4 values to memory in each_row + for (uint32_t j = 0; j < 4; j++) { + auto offset = i * 4 + j; + // If we exceed the slice size, we break + if (offset >= slice_len) { + break; + } + MemOp mem_read; + if (is_indirect) { + // If the first address is indirect we read it into register A, this can only happen once per slice read + mem_read = constrained_read_from_memory(space_id, clk + i, addr, r_tag, w_tag, IntermRegister::IA); + // Set this to false for the rest of the reads + is_indirect = false; + src_offset = mem_read.direct_address; + } else { + auto mem_load = mem_trace_builder.read_and_load_from_memory( + space_id, clk + i, register_order[j], src_offset + offset, r_tag, w_tag); + mem_read = MemOp{ + .is_indirect = false, + .indirect_address = 0, + .direct_address = src_offset + offset, + .tag = r_tag, + .tag_match = mem_load.tag_match, + .val = MEM(mem_load.val), + }; + } + slice.emplace_back(MEM(mem_read.val)); + // This looks a bit gross, but it is fine for now. + if (j == 0) { + main_row.main_ia = slice.at(offset); + main_row.main_ind_addr_a = FF(mem_read.indirect_address); + main_row.main_sel_resolve_ind_addr_a = FF(static_cast(mem_read.is_indirect)); + main_row.main_mem_addr_a = FF(mem_read.direct_address); + main_row.main_sel_mem_op_a = FF(1); + main_row.main_tag_err = FF(static_cast(!mem_read.tag_match)); + } else if (j == 1) { + main_row.main_ib = slice.at(offset); + main_row.main_mem_addr_b = FF(mem_read.direct_address); + main_row.main_sel_mem_op_b = FF(1); + main_row.main_tag_err = FF(static_cast(!mem_read.tag_match)); + } else if (j == 2) { + main_row.main_ic = slice.at(offset); + main_row.main_mem_addr_c = FF(mem_read.direct_address); + main_row.main_sel_mem_op_c = FF(1); + main_row.main_tag_err = FF(static_cast(!mem_read.tag_match)); + } else { + main_row.main_id = slice.at(offset); + main_row.main_mem_addr_d = FF(mem_read.direct_address); + main_row.main_sel_mem_op_d = FF(1); + main_row.main_tag_err = FF(static_cast(!mem_read.tag_match)); + } + } + main_trace.emplace_back(main_row); + } + return num_main_rows; +} + +// TODO(ilyas: #6383): Temporary way to bulk write slices +uint32_t AvmTraceBuilder::write_slice_to_memory(uint8_t space_id, + uint32_t clk, + AddressWithMode addr, + AvmMemoryTag r_tag, + AvmMemoryTag w_tag, + FF internal_return_ptr, + std::vector const& slice) +{ + bool is_indirect = addr.mode == AddressingMode::INDIRECT; + auto dst_offset = addr.offset; + // We have 4 registers that we are able to use to write to memory within a single main trace row + auto register_order = std::array{ IntermRegister::IA, IntermRegister::IB, IntermRegister::IC, IntermRegister::ID }; + // If the slice size isnt a multiple of 4, we still need an extra row to write the remainder + uint32_t const num_main_rows = + static_cast(slice.size()) / 4 + static_cast(slice.size() % 4 != 0); + for (uint32_t i = 0; i < num_main_rows; i++) { + Row main_row{ + .main_clk = clk + i, + .main_internal_return_ptr = FF(internal_return_ptr), + .main_pc = FF(pc), + .main_r_in_tag = FF(static_cast(r_tag)), + .main_w_in_tag = FF(static_cast(w_tag)), + }; + // Write 4 values to memory in each_row + for (uint32_t j = 0; j < 4; j++) { + auto offset = i * 4 + j; + // If we exceed the slice size, we break + if (offset >= slice.size()) { + break; + } + MemOp mem_write; + if (is_indirect) { + mem_write = constrained_write_to_memory( + space_id, clk + i, addr, slice.at(offset), r_tag, w_tag, IntermRegister::IA); + // Ensure futures calls are direct + is_indirect = false; + dst_offset = mem_write.direct_address; + } else { + mem_trace_builder.write_into_memory( + space_id, clk + i, register_order[j], dst_offset + offset, slice.at(offset), r_tag, w_tag); + mem_write = MemOp{ + .is_indirect = false, + .indirect_address = 0, + .direct_address = dst_offset + offset, + .tag = w_tag, + .tag_match = true, + .val = slice.at(offset), + }; + } + // This looks a bit gross, but it is fine for now. + if (j == 0) { + main_row.main_ia = slice.at(offset); + main_row.main_ind_addr_a = FF(mem_write.indirect_address); + main_row.main_sel_resolve_ind_addr_a = FF(static_cast(mem_write.is_indirect)); + main_row.main_mem_addr_a = FF(mem_write.direct_address); + main_row.main_sel_mem_op_a = FF(1); + main_row.main_rwa = FF(1); + } else if (j == 1) { + main_row.main_ib = slice.at(offset); + main_row.main_mem_addr_b = FF(mem_write.direct_address); + main_row.main_sel_mem_op_b = FF(1); + main_row.main_rwb = FF(1); + } else if (j == 2) { + main_row.main_ic = slice.at(offset); + main_row.main_mem_addr_c = FF(mem_write.direct_address); + main_row.main_sel_mem_op_c = FF(1); + main_row.main_rwc = FF(1); + } else { + main_row.main_id = slice.at(offset); + main_row.main_mem_addr_d = FF(mem_write.direct_address); + main_row.main_sel_mem_op_d = FF(1); + main_row.main_rwd = FF(1); + } + } + main_trace.emplace_back(main_row); + } + return num_main_rows; +} + +// Finalise Lookup Counts +// +// For log derivative lookups, we require a column that contains the number of times each lookup is consumed +// As we build the trace, we keep track of the reads made in a mapping, so that they can be applied to the +// counts column here +// +// NOTE: its coupled to pil - this is not the final iteration +void AvmTraceBuilder::finalise_mem_trace_lookup_counts() +{ + for (auto const& [clk, count] : mem_trace_builder.m_tag_err_lookup_counts) { + main_trace.at(clk).incl_main_tag_err_counts = count; + } +} + +/** + * @brief Constructor of a trace builder of AVM. Only serves to set the capacity of the + * underlying traces and initialize gas values. + */ +AvmTraceBuilder::AvmTraceBuilder(VmPublicInputs public_inputs, + ExecutionHints execution_hints, + uint32_t side_effect_counter, + std::vector calldata) + // NOTE: we initialise the environment builder here as it requires public inputs + : kernel_trace_builder(std::move(public_inputs)) + , calldata(std::move(calldata)) + , side_effect_counter(side_effect_counter) + , initial_side_effect_counter(side_effect_counter) + , execution_hints(std::move(execution_hints)) +{ + main_trace.reserve(AVM_TRACE_SIZE); + + // TODO: think about cast + gas_trace_builder.set_initial_gas(static_cast(std::get( + kernel_trace_builder.public_inputs)[L2_GAS_LEFT_CONTEXT_INPUTS_OFFSET]), + static_cast(std::get( + kernel_trace_builder.public_inputs)[DA_GAS_LEFT_CONTEXT_INPUTS_OFFSET])); +} + +/************************************************************************************************** + * COMPUTE - ARITHMETIC + **************************************************************************************************/ + +/** + * @brief Addition with direct or indirect memory access. + * + * @param indirect A byte encoding information about indirect/direct memory access. + * @param a_offset An index in memory pointing to the first operand of the addition. + * @param b_offset An index in memory pointing to the second operand of the addition. + * @param dst_offset An index in memory pointing to the output of the addition. + * @param in_tag The instruction memory tag of the operands. + */ +void AvmTraceBuilder::op_add( + uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) +{ + auto clk = static_cast(main_trace.size()) + 1; + + // Resolve any potential indirects in the order they are encoded in the indirect byte. + auto [resolved_a, resolved_b, resolved_c] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); + + // Reading from memory and loading into ia resp. ib. + auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); + auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, in_tag, IntermRegister::IB); + + bool tag_match = read_a.tag_match && read_b.tag_match; + + // a + b = c + FF a = read_a.val; + FF b = read_b.val; + + // In case of a memory tag error, we do not perform the computation. + // Therefore, we do not create any entry in ALU table and store the value 0 as + // output (c) in memory. + FF c = tag_match ? alu_trace_builder.op_add(a, b, in_tag, clk) : FF(0); + + // Write into memory value c from intermediate register ic. + auto write_c = constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, in_tag, IntermRegister::IC); + + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::ADD); + + main_trace.push_back(Row{ + .main_clk = clk, + .main_alu_in_tag = FF(static_cast(in_tag)), + .main_call_ptr = call_ptr, + .main_ia = read_a.val, + .main_ib = read_b.val, + .main_ic = write_c.val, + .main_ind_addr_a = FF(read_a.indirect_address), + .main_ind_addr_b = FF(read_b.indirect_address), + .main_ind_addr_c = FF(write_c.indirect_address), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(read_a.direct_address), + .main_mem_addr_b = FF(read_b.direct_address), + .main_mem_addr_c = FF(write_c.direct_address), + .main_pc = FF(pc++), + .main_r_in_tag = FF(static_cast(in_tag)), + .main_rwc = FF(1), + .main_sel_mem_op_a = FF(1), + .main_sel_mem_op_b = FF(1), + .main_sel_mem_op_c = FF(1), + .main_sel_op_add = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), + .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), + .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), + .main_tag_err = FF(static_cast(!tag_match)), + .main_w_in_tag = FF(static_cast(in_tag)), }); } @@ -372,7 +690,7 @@ void AvmTraceBuilder::op_mul( } /** - * @brief Finite field division with direct or indirect memory access. + * @brief Integer division with direct or indirect memory access. * * @param indirect A byte encoding information about indirect/direct memory access. * @param a_offset An index in memory pointing to the first operand of the division. @@ -380,32 +698,33 @@ void AvmTraceBuilder::op_mul( * @param dst_offset An index in memory pointing to the output of the division. * @param in_tag The instruction memory tag of the operands. */ -void AvmTraceBuilder::op_fdiv(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset) +void AvmTraceBuilder::op_div( + uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) { auto clk = static_cast(main_trace.size()) + 1; - // Resolve any potential indirects in the order they are encoded in the indirect byte. - auto [resolved_a, resolved_b, resolved_c] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); + auto [resolved_a, resolved_b, resolved_dst] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); // Reading from memory and loading into ia resp. ib. - auto read_a = - constrained_read_from_memory(call_ptr, clk, resolved_a, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IA); - auto read_b = - constrained_read_from_memory(call_ptr, clk, resolved_b, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IB); - + auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); + auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, in_tag, IntermRegister::IB); bool tag_match = read_a.tag_match && read_b.tag_match; - // a * b^(-1) = c + // a / b = c FF a = read_a.val; FF b = read_b.val; + + // In case of a memory tag error, we do not perform the computation. + // Therefore, we do not create any entry in ALU table and store the value 0 as + // output (c) in memory. FF c; FF inv; FF error; if (!b.is_zero()) { - + // If b is not zero, we prove it is not by providing its inverse as well inv = b.invert(); - c = a * inv; + c = tag_match ? alu_trace_builder.op_div(a, b, in_tag, clk) : FF(0); error = 0; } else { inv = 1; @@ -414,99 +733,125 @@ void AvmTraceBuilder::op_fdiv(uint8_t indirect, uint32_t a_offset, uint32_t b_of } // Write into memory value c from intermediate register ic. - auto write_c = constrained_write_to_memory( - call_ptr, clk, resolved_c, c, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IC); + auto write_dst = constrained_write_to_memory(call_ptr, clk, resolved_dst, c, in_tag, in_tag, IntermRegister::IC); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::FDIV); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::DIV); main_trace.push_back(Row{ .main_clk = clk, + .main_alu_in_tag = FF(static_cast(in_tag)), .main_call_ptr = call_ptr, - .main_ia = tag_match ? read_a.val : FF(0), - .main_ib = tag_match ? read_b.val : FF(0), - .main_ic = tag_match ? write_c.val : FF(0), + .main_ia = read_a.val, + .main_ib = read_b.val, + .main_ic = c, .main_ind_addr_a = FF(read_a.indirect_address), .main_ind_addr_b = FF(read_b.indirect_address), - .main_ind_addr_c = FF(write_c.indirect_address), + .main_ind_addr_c = FF(write_dst.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), .main_inv = tag_match ? inv : FF(1), .main_mem_addr_a = FF(read_a.direct_address), .main_mem_addr_b = FF(read_b.direct_address), - .main_mem_addr_c = FF(write_c.direct_address), + .main_mem_addr_c = FF(write_dst.direct_address), .main_op_err = tag_match ? error : FF(1), .main_pc = FF(pc++), - .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), + .main_r_in_tag = FF(static_cast(in_tag)), .main_rwc = FF(1), .main_sel_mem_op_a = FF(1), .main_sel_mem_op_b = FF(1), .main_sel_mem_op_c = FF(1), - .main_sel_op_fdiv = FF(1), + .main_sel_op_div = FF(1), .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), - .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), + .main_sel_resolve_ind_addr_c = FF(static_cast(write_dst.is_indirect)), .main_tag_err = FF(static_cast(!tag_match)), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), + .main_w_in_tag = FF(static_cast(in_tag)), }); } /** - * @brief Bitwise not with direct or indirect memory access. + * @brief Finite field division with direct or indirect memory access. * * @param indirect A byte encoding information about indirect/direct memory access. - * @param a_offset An index in memory pointing to the only operand of Not. - * @param dst_offset An index in memory pointing to the output of Not. + * @param a_offset An index in memory pointing to the first operand of the division. + * @param b_offset An index in memory pointing to the second operand of the division. + * @param dst_offset An index in memory pointing to the output of the division. * @param in_tag The instruction memory tag of the operands. */ -void AvmTraceBuilder::op_not(uint8_t indirect, uint32_t a_offset, uint32_t dst_offset, AvmMemoryTag in_tag) +void AvmTraceBuilder::op_fdiv(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset) { auto clk = static_cast(main_trace.size()) + 1; // Resolve any potential indirects in the order they are encoded in the indirect byte. - auto [resolved_a, resolved_c] = unpack_indirects<2>(indirect, { a_offset, dst_offset }); + auto [resolved_a, resolved_b, resolved_c] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); - // Reading from memory and loading into ia - auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); + // Reading from memory and loading into ia resp. ib. + auto read_a = + constrained_read_from_memory(call_ptr, clk, resolved_a, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IA); + auto read_b = + constrained_read_from_memory(call_ptr, clk, resolved_b, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IB); - bool tag_match = read_a.tag_match; - // ~a = c + bool tag_match = read_a.tag_match && read_b.tag_match; + + // a * b^(-1) = c FF a = read_a.val; + FF b = read_b.val; + FF c; + FF inv; + FF error; - // In case of a memory tag error, we do not perform the computation. - // Therefore, we do not create any entry in ALU table and store the value 0 as - // output (c) in memory. - FF c = tag_match ? alu_trace_builder.op_not(a, in_tag, clk) : FF(0); + if (!b.is_zero()) { + + inv = b.invert(); + c = a * inv; + error = 0; + } else { + inv = 1; + c = 0; + error = 1; + } // Write into memory value c from intermediate register ic. - auto write_c = constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, in_tag, IntermRegister::IC); + auto write_c = constrained_write_to_memory( + call_ptr, clk, resolved_c, c, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IC); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::NOT); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::FDIV); main_trace.push_back(Row{ .main_clk = clk, - .main_alu_in_tag = FF(static_cast(in_tag)), .main_call_ptr = call_ptr, - .main_ia = read_a.val, - .main_ic = write_c.val, + .main_ia = tag_match ? read_a.val : FF(0), + .main_ib = tag_match ? read_b.val : FF(0), + .main_ic = tag_match ? write_c.val : FF(0), .main_ind_addr_a = FF(read_a.indirect_address), + .main_ind_addr_b = FF(read_b.indirect_address), .main_ind_addr_c = FF(write_c.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), + .main_inv = tag_match ? inv : FF(1), .main_mem_addr_a = FF(read_a.direct_address), + .main_mem_addr_b = FF(read_b.direct_address), .main_mem_addr_c = FF(write_c.direct_address), + .main_op_err = tag_match ? error : FF(1), .main_pc = FF(pc++), - .main_r_in_tag = FF(static_cast(in_tag)), + .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), .main_rwc = FF(1), .main_sel_mem_op_a = FF(1), + .main_sel_mem_op_b = FF(1), .main_sel_mem_op_c = FF(1), - .main_sel_op_not = FF(1), + .main_sel_op_fdiv = FF(1), .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), + .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), - .main_tag_err = FF(static_cast(!read_a.tag_match)), - .main_w_in_tag = FF(static_cast(in_tag)), + .main_tag_err = FF(static_cast(!tag_match)), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), }); } +/************************************************************************************************** + * COMPUTE - COMPARATORS + **************************************************************************************************/ + /** * @brief Equality with direct or indirect memory access. * @@ -572,32 +917,32 @@ void AvmTraceBuilder::op_eq( }); } -void AvmTraceBuilder::op_and( +void AvmTraceBuilder::op_lt( uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) { auto clk = static_cast(main_trace.size()) + 1; auto [resolved_a, resolved_b, resolved_c] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); - // Reading from memory and loading into ia resp. ib. - auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); - auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, in_tag, IntermRegister::IB); + auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, AvmMemoryTag::U8, IntermRegister::IA); + auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, AvmMemoryTag::U8, IntermRegister::IB); bool tag_match = read_a.tag_match && read_b.tag_match; FF a = tag_match ? read_a.val : FF(0); FF b = tag_match ? read_b.val : FF(0); - FF c = tag_match ? bin_trace_builder.op_and(a, b, in_tag, clk) : FF(0); + FF c = tag_match ? alu_trace_builder.op_lt(a, b, in_tag, clk) : FF(0); // Write into memory value c from intermediate register ic. - auto write_c = constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, in_tag, IntermRegister::IC); + auto write_c = + constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, AvmMemoryTag::U8, IntermRegister::IC); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::AND); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::LT); main_trace.push_back(Row{ .main_clk = clk, - .main_bin_op_id = FF(0), + .main_alu_in_tag = FF(static_cast(in_tag)), .main_call_ptr = call_ptr, .main_ia = read_a.val, .main_ib = read_b.val, @@ -612,44 +957,45 @@ void AvmTraceBuilder::op_and( .main_pc = FF(pc++), .main_r_in_tag = FF(static_cast(in_tag)), .main_rwc = FF(1), - .main_sel_bin = FF(1), .main_sel_mem_op_a = FF(1), .main_sel_mem_op_b = FF(1), .main_sel_mem_op_c = FF(1), - .main_sel_op_and = FF(1), + .main_sel_op_lt = FF(1), .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), .main_tag_err = FF(static_cast(!tag_match)), - .main_w_in_tag = FF(static_cast(in_tag)), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::U8)), }); } -void AvmTraceBuilder::op_or( +void AvmTraceBuilder::op_lte( uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) { auto clk = static_cast(main_trace.size()) + 1; + auto [resolved_a, resolved_b, resolved_c] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); // Reading from memory and loading into ia resp. ib. - auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); - auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, in_tag, IntermRegister::IB); + auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, AvmMemoryTag::U8, IntermRegister::IA); + auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, AvmMemoryTag::U8, IntermRegister::IB); bool tag_match = read_a.tag_match && read_b.tag_match; FF a = tag_match ? read_a.val : FF(0); FF b = tag_match ? read_b.val : FF(0); - FF c = tag_match ? bin_trace_builder.op_or(a, b, in_tag, clk) : FF(0); + FF c = tag_match ? alu_trace_builder.op_lte(a, b, in_tag, clk) : FF(0); // Write into memory value c from intermediate register ic. - auto write_c = constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, in_tag, IntermRegister::IC); + auto write_c = + constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, AvmMemoryTag::U8, IntermRegister::IC); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::OR); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::LTE); main_trace.push_back(Row{ .main_clk = clk, - .main_bin_op_id = FF(1), + .main_alu_in_tag = FF(static_cast(in_tag)), .main_call_ptr = call_ptr, .main_ia = read_a.val, .main_ib = read_b.val, @@ -664,20 +1010,23 @@ void AvmTraceBuilder::op_or( .main_pc = FF(pc++), .main_r_in_tag = FF(static_cast(in_tag)), .main_rwc = FF(1), - .main_sel_bin = FF(1), .main_sel_mem_op_a = FF(1), .main_sel_mem_op_b = FF(1), .main_sel_mem_op_c = FF(1), - .main_sel_op_or = FF(1), + .main_sel_op_lte = FF(1), .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), .main_tag_err = FF(static_cast(!tag_match)), - .main_w_in_tag = FF(static_cast(in_tag)), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::U8)), }); } -void AvmTraceBuilder::op_xor( +/************************************************************************************************** + * COMPUTE - BITWISE + **************************************************************************************************/ + +void AvmTraceBuilder::op_and( uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) { auto clk = static_cast(main_trace.size()) + 1; @@ -692,17 +1041,17 @@ void AvmTraceBuilder::op_xor( FF a = tag_match ? read_a.val : FF(0); FF b = tag_match ? read_b.val : FF(0); - FF c = tag_match ? bin_trace_builder.op_xor(a, b, in_tag, clk) : FF(0); + FF c = tag_match ? bin_trace_builder.op_and(a, b, in_tag, clk) : FF(0); // Write into memory value c from intermediate register ic. auto write_c = constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, in_tag, IntermRegister::IC); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::XOR); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::AND); main_trace.push_back(Row{ .main_clk = clk, - .main_bin_op_id = FF(2), + .main_bin_op_id = FF(0), .main_call_ptr = call_ptr, .main_ia = read_a.val, .main_ib = read_b.val, @@ -721,7 +1070,7 @@ void AvmTraceBuilder::op_xor( .main_sel_mem_op_a = FF(1), .main_sel_mem_op_b = FF(1), .main_sel_mem_op_c = FF(1), - .main_sel_op_xor = FF(1), + .main_sel_op_and = FF(1), .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), @@ -730,32 +1079,31 @@ void AvmTraceBuilder::op_xor( }); } -void AvmTraceBuilder::op_lt( +void AvmTraceBuilder::op_or( uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) { auto clk = static_cast(main_trace.size()) + 1; - auto [resolved_a, resolved_b, resolved_c] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); - auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, AvmMemoryTag::U8, IntermRegister::IA); - auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, AvmMemoryTag::U8, IntermRegister::IB); + // Reading from memory and loading into ia resp. ib. + auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); + auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, in_tag, IntermRegister::IB); bool tag_match = read_a.tag_match && read_b.tag_match; FF a = tag_match ? read_a.val : FF(0); FF b = tag_match ? read_b.val : FF(0); - FF c = tag_match ? alu_trace_builder.op_lt(a, b, in_tag, clk) : FF(0); + FF c = tag_match ? bin_trace_builder.op_or(a, b, in_tag, clk) : FF(0); // Write into memory value c from intermediate register ic. - auto write_c = - constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, AvmMemoryTag::U8, IntermRegister::IC); + auto write_c = constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, in_tag, IntermRegister::IC); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::LT); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::OR); main_trace.push_back(Row{ .main_clk = clk, - .main_alu_in_tag = FF(static_cast(in_tag)), + .main_bin_op_id = FF(1), .main_call_ptr = call_ptr, .main_ia = read_a.val, .main_ib = read_b.val, @@ -770,19 +1118,20 @@ void AvmTraceBuilder::op_lt( .main_pc = FF(pc++), .main_r_in_tag = FF(static_cast(in_tag)), .main_rwc = FF(1), + .main_sel_bin = FF(1), .main_sel_mem_op_a = FF(1), .main_sel_mem_op_b = FF(1), .main_sel_mem_op_c = FF(1), - .main_sel_op_lt = FF(1), + .main_sel_op_or = FF(1), .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), .main_tag_err = FF(static_cast(!tag_match)), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::U8)), + .main_w_in_tag = FF(static_cast(in_tag)), }); } -void AvmTraceBuilder::op_lte( +void AvmTraceBuilder::op_xor( uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) { auto clk = static_cast(main_trace.size()) + 1; @@ -790,25 +1139,24 @@ void AvmTraceBuilder::op_lte( auto [resolved_a, resolved_b, resolved_c] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); // Reading from memory and loading into ia resp. ib. - auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, AvmMemoryTag::U8, IntermRegister::IA); - auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, AvmMemoryTag::U8, IntermRegister::IB); + auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); + auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, in_tag, IntermRegister::IB); bool tag_match = read_a.tag_match && read_b.tag_match; FF a = tag_match ? read_a.val : FF(0); FF b = tag_match ? read_b.val : FF(0); - FF c = tag_match ? alu_trace_builder.op_lte(a, b, in_tag, clk) : FF(0); + FF c = tag_match ? bin_trace_builder.op_xor(a, b, in_tag, clk) : FF(0); // Write into memory value c from intermediate register ic. - auto write_c = - constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, AvmMemoryTag::U8, IntermRegister::IC); + auto write_c = constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, in_tag, IntermRegister::IC); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::LTE); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::XOR); main_trace.push_back(Row{ .main_clk = clk, - .main_alu_in_tag = FF(static_cast(in_tag)), + .main_bin_op_id = FF(2), .main_call_ptr = call_ptr, .main_ia = read_a.val, .main_ib = read_b.val, @@ -823,66 +1171,72 @@ void AvmTraceBuilder::op_lte( .main_pc = FF(pc++), .main_r_in_tag = FF(static_cast(in_tag)), .main_rwc = FF(1), + .main_sel_bin = FF(1), .main_sel_mem_op_a = FF(1), .main_sel_mem_op_b = FF(1), .main_sel_mem_op_c = FF(1), - .main_sel_op_lte = FF(1), + .main_sel_op_xor = FF(1), .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), .main_tag_err = FF(static_cast(!tag_match)), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::U8)), + .main_w_in_tag = FF(static_cast(in_tag)), }); } -void AvmTraceBuilder::op_shr( - uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) +/** + * @brief Bitwise not with direct or indirect memory access. + * + * @param indirect A byte encoding information about indirect/direct memory access. + * @param a_offset An index in memory pointing to the only operand of Not. + * @param dst_offset An index in memory pointing to the output of Not. + * @param in_tag The instruction memory tag of the operands. + */ +void AvmTraceBuilder::op_not(uint8_t indirect, uint32_t a_offset, uint32_t dst_offset, AvmMemoryTag in_tag) { - auto clk = static_cast(main_trace.size()) + 1; - auto [resolved_a, resolved_b, resolved_c] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); + // Resolve any potential indirects in the order they are encoded in the indirect byte. + auto [resolved_a, resolved_c] = unpack_indirects<2>(indirect, { a_offset, dst_offset }); - // Reading from memory and loading into ia resp. ib. + // Reading from memory and loading into ia auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); - auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, in_tag, IntermRegister::IB); - bool tag_match = read_a.tag_match && read_b.tag_match; - FF a = tag_match ? read_a.val : FF(0); - FF b = tag_match ? read_b.val : FF(0); + bool tag_match = read_a.tag_match; + // ~a = c + FF a = read_a.val; - FF c = tag_match ? alu_trace_builder.op_shr(a, b, in_tag, clk) : FF(0); + // In case of a memory tag error, we do not perform the computation. + // Therefore, we do not create any entry in ALU table and store the value 0 as + // output (c) in memory. + FF c = tag_match ? alu_trace_builder.op_not(a, in_tag, clk) : FF(0); // Write into memory value c from intermediate register ic. auto write_c = constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, in_tag, IntermRegister::IC); + // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::SHR); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::NOT); main_trace.push_back(Row{ .main_clk = clk, .main_alu_in_tag = FF(static_cast(in_tag)), .main_call_ptr = call_ptr, .main_ia = read_a.val, - .main_ib = read_b.val, .main_ic = write_c.val, .main_ind_addr_a = FF(read_a.indirect_address), - .main_ind_addr_b = FF(read_b.indirect_address), .main_ind_addr_c = FF(write_c.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), .main_mem_addr_a = FF(read_a.direct_address), - .main_mem_addr_b = FF(read_b.direct_address), .main_mem_addr_c = FF(write_c.direct_address), .main_pc = FF(pc++), .main_r_in_tag = FF(static_cast(in_tag)), .main_rwc = FF(1), .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_b = FF(1), .main_sel_mem_op_c = FF(1), - .main_sel_op_shr = FF(1), + .main_sel_op_not = FF(1), .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), - .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), - .main_tag_err = FF(static_cast(!tag_match)), + .main_tag_err = FF(static_cast(!read_a.tag_match)), .main_w_in_tag = FF(static_cast(in_tag)), }); } @@ -938,139 +1292,80 @@ void AvmTraceBuilder::op_shl( }); } -// TODO: Ensure that the bytecode validation and/or deserialization is -// enforcing that val complies to the tag. -/** - * @brief Set a constant from bytecode with direct or indirect memory access. - * SET opcode is implemented purely as a memory operation. As val is a - * constant passed in the bytecode, the deserialization layer or bytecode - * validation circuit is enforcing that the constant complies to in_tag. - * Therefore, no range check is required as part of this opcode relation. - * - * @param indirect A byte encoding information about indirect/direct memory access. - * @param val The constant to be written upcasted to u128 - * @param dst_offset Memory destination offset where val is written to - * @param in_tag The instruction memory tag - */ -void AvmTraceBuilder::op_set(uint8_t indirect, uint128_t val, uint32_t dst_offset, AvmMemoryTag in_tag) +void AvmTraceBuilder::op_shr( + uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) { - auto const clk = static_cast(main_trace.size()) + 1; - auto const val_ff = FF{ uint256_t::from_uint128(val) }; - auto [resolved_c] = unpack_indirects<1>(indirect, { dst_offset }); - auto write_c = - constrained_write_to_memory(call_ptr, clk, resolved_c, val_ff, AvmMemoryTag::U0, in_tag, IntermRegister::IC); + auto clk = static_cast(main_trace.size()) + 1; + + auto [resolved_a, resolved_b, resolved_c] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); + + // Reading from memory and loading into ia resp. ib. + auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); + auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, in_tag, IntermRegister::IB); + bool tag_match = read_a.tag_match && read_b.tag_match; + + FF a = tag_match ? read_a.val : FF(0); + FF b = tag_match ? read_b.val : FF(0); + FF c = tag_match ? alu_trace_builder.op_shr(a, b, in_tag, clk) : FF(0); + + // Write into memory value c from intermediate register ic. + auto write_c = constrained_write_to_memory(call_ptr, clk, resolved_c, c, in_tag, in_tag, IntermRegister::IC); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::SET); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::SHR); main_trace.push_back(Row{ .main_clk = clk, + .main_alu_in_tag = FF(static_cast(in_tag)), .main_call_ptr = call_ptr, + .main_ia = read_a.val, + .main_ib = read_b.val, .main_ic = write_c.val, + .main_ind_addr_a = FF(read_a.indirect_address), + .main_ind_addr_b = FF(read_b.indirect_address), .main_ind_addr_c = FF(write_c.indirect_address), - .main_internal_return_ptr = internal_return_ptr, + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(read_a.direct_address), + .main_mem_addr_b = FF(read_b.direct_address), .main_mem_addr_c = FF(write_c.direct_address), - .main_pc = pc++, - .main_rwc = 1, - .main_sel_mem_op_activate_gas = 1, // TODO: remove in the long term - .main_sel_mem_op_c = 1, + .main_pc = FF(pc++), + .main_r_in_tag = FF(static_cast(in_tag)), + .main_rwc = FF(1), + .main_sel_mem_op_a = FF(1), + .main_sel_mem_op_b = FF(1), + .main_sel_mem_op_c = FF(1), + .main_sel_op_shr = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), + .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), - .main_tag_err = static_cast(!write_c.tag_match), - .main_w_in_tag = static_cast(in_tag), + .main_tag_err = FF(static_cast(!tag_match)), + .main_w_in_tag = FF(static_cast(in_tag)), }); } -/** - * @brief Copy value and tag from a memory cell at position src_offset to the - * memory cell at position dst_offset - * - * @param indirect A byte encoding information about indirect/direct memory access. - * @param src_offset Offset of source memory cell - * @param dst_offset Offset of destination memory cell - */ -void AvmTraceBuilder::op_mov(uint8_t indirect, uint32_t src_offset, uint32_t dst_offset) -{ - auto const clk = static_cast(main_trace.size()) + 1; - bool tag_match = true; - uint32_t direct_src_offset = src_offset; - uint32_t direct_dst_offset = dst_offset; - - bool indirect_src_flag = is_operand_indirect(indirect, 0); - bool indirect_dst_flag = is_operand_indirect(indirect, 1); - - if (indirect_src_flag) { - auto read_ind_a = - mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_A, src_offset); - tag_match = read_ind_a.tag_match; - direct_src_offset = uint32_t(read_ind_a.val); - } - - if (indirect_dst_flag) { - auto read_ind_c = - mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_C, dst_offset); - tag_match = tag_match && read_ind_c.tag_match; - direct_dst_offset = uint32_t(read_ind_c.val); - } - - // Reading from memory and loading into ia without tag check. - auto const [val, tag] = mem_trace_builder.read_and_load_mov_opcode(call_ptr, clk, direct_src_offset); - - // Write into memory from intermediate register ic. - mem_trace_builder.write_into_memory(call_ptr, clk, IntermRegister::IC, direct_dst_offset, val, tag, tag); - - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::MOV); - - main_trace.push_back(Row{ - .main_clk = clk, - .main_call_ptr = call_ptr, - .main_ia = val, - .main_ic = val, - .main_ind_addr_a = indirect_src_flag ? src_offset : 0, - .main_ind_addr_c = indirect_dst_flag ? dst_offset : 0, - .main_internal_return_ptr = internal_return_ptr, - .main_mem_addr_a = direct_src_offset, - .main_mem_addr_c = direct_dst_offset, - .main_pc = pc++, - .main_r_in_tag = static_cast(tag), - .main_rwc = 1, - .main_sel_mem_op_a = 1, - .main_sel_mem_op_c = 1, - .main_sel_mov_ia_to_ic = 1, - .main_sel_op_mov = 1, - .main_sel_resolve_ind_addr_a = static_cast(indirect_src_flag), - .main_sel_resolve_ind_addr_c = static_cast(indirect_dst_flag), - .main_tag_err = static_cast(!tag_match), - .main_w_in_tag = static_cast(tag), - }); -} +/************************************************************************************************** + * COMPUTE - TYPE CONVERSIONS + **************************************************************************************************/ /** - * @brief Copy value and tag from a memory cell at position src_offset to the - * memory cell at position dst_offset. src_offset is a_offset if the value - * defined by cond_offset is non-zero. Otherwise, src_offset is b_offset. + * @brief Cast an element pointed by the address a_offset into type specified by dst_tag and + store the result in address given by dst_offset. * * @param indirect A byte encoding information about indirect/direct memory access. - * @param a_offset Offset of first candidate source memory cell - * @param b_offset Offset of second candidate source memory cell - * @param cond_offset Offset of the condition determining the source offset (a_offset or b_offset) - * @param dst_offset Offset of destination memory cell + * @param a_offset Offset of source memory cell. + * @param dst_offset Offset of destination memory cell. + * @param dst_tag Destination tag specifying the type the source value must be casted to. */ -void AvmTraceBuilder::op_cmov( - uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t cond_offset, uint32_t dst_offset) +void AvmTraceBuilder::op_cast(uint8_t indirect, uint32_t a_offset, uint32_t dst_offset, AvmMemoryTag dst_tag) { auto const clk = static_cast(main_trace.size()) + 1; bool tag_match = true; uint32_t direct_a_offset = a_offset; - uint32_t direct_b_offset = b_offset; - uint32_t direct_cond_offset = cond_offset; uint32_t direct_dst_offset = dst_offset; bool indirect_a_flag = is_operand_indirect(indirect, 0); - bool indirect_b_flag = is_operand_indirect(indirect, 1); - bool indirect_cond_flag = is_operand_indirect(indirect, 2); - bool indirect_dst_flag = is_operand_indirect(indirect, 3); + bool indirect_dst_flag = is_operand_indirect(indirect, 1); if (indirect_a_flag) { auto read_ind_a = @@ -1079,20 +1374,6 @@ void AvmTraceBuilder::op_cmov( tag_match = tag_match && read_ind_a.tag_match; } - if (indirect_b_flag) { - auto read_ind_b = - mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_B, b_offset); - direct_b_offset = uint32_t(read_ind_b.val); - tag_match = tag_match && read_ind_b.tag_match; - } - - if (indirect_cond_flag) { - auto read_ind_d = - mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_D, cond_offset); - direct_cond_offset = uint32_t(read_ind_d.val); - tag_match = tag_match && read_ind_d.tag_match; - } - if (indirect_dst_flag) { auto read_ind_c = mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_C, dst_offset); @@ -1100,67 +1381,63 @@ void AvmTraceBuilder::op_cmov( tag_match = tag_match && read_ind_c.tag_match; } - // Reading from memory and loading into ia or ib without tag check. We also load the conditional value - // in id without any tag check. - std::array const cmov_res = mem_trace_builder.read_and_load_cmov_opcode( - call_ptr, clk, direct_a_offset, direct_b_offset, direct_cond_offset); - - AvmMemTraceBuilder::MemEntry const& a_mem_entry = cmov_res.at(0); - AvmMemTraceBuilder::MemEntry const& b_mem_entry = cmov_res.at(1); - AvmMemTraceBuilder::MemEntry const& cond_mem_entry = cmov_res.at(2); - - const bool id_zero = cond_mem_entry.val == 0; - - auto const& val = id_zero ? b_mem_entry.val : a_mem_entry.val; - auto const& tag = id_zero ? b_mem_entry.tag : a_mem_entry.tag; + // Reading from memory and loading into ia + auto memEntry = mem_trace_builder.read_and_load_cast_opcode(call_ptr, clk, direct_a_offset, dst_tag); + FF a = memEntry.val; - // Write into memory from intermediate register ic. - mem_trace_builder.write_into_memory(call_ptr, clk, IntermRegister::IC, direct_dst_offset, val, tag, tag); + // In case of a memory tag error, we do not perform the computation. + // Therefore, we do not create any entry in ALU table and store the value 0 as + // output (c) in memory. + FF c = tag_match ? alu_trace_builder.op_cast(a, dst_tag, clk) : FF(0); - FF const inv = !id_zero ? cond_mem_entry.val.invert() : 1; + // Write into memory value c from intermediate register ic. + mem_trace_builder.write_into_memory(call_ptr, clk, IntermRegister::IC, direct_dst_offset, c, memEntry.tag, dst_tag); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::CMOV); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::CAST); main_trace.push_back(Row{ .main_clk = clk, + .main_alu_in_tag = FF(static_cast(dst_tag)), .main_call_ptr = call_ptr, - .main_ia = a_mem_entry.val, - .main_ib = b_mem_entry.val, - .main_ic = val, - .main_id = cond_mem_entry.val, - .main_id_zero = static_cast(id_zero), - .main_ind_addr_a = indirect_a_flag ? a_offset : 0, - .main_ind_addr_b = indirect_b_flag ? b_offset : 0, - .main_ind_addr_c = indirect_dst_flag ? dst_offset : 0, - .main_ind_addr_d = indirect_cond_flag ? cond_offset : 0, - .main_internal_return_ptr = internal_return_ptr, - .main_inv = inv, - .main_mem_addr_a = direct_a_offset, - .main_mem_addr_b = direct_b_offset, - .main_mem_addr_c = direct_dst_offset, - .main_mem_addr_d = direct_cond_offset, - .main_pc = pc++, - .main_r_in_tag = static_cast(tag), - .main_rwc = 1, - .main_sel_mem_op_a = 1, - .main_sel_mem_op_b = 1, - .main_sel_mem_op_c = 1, - .main_sel_mem_op_d = 1, - .main_sel_mov_ia_to_ic = static_cast(!id_zero), - .main_sel_mov_ib_to_ic = static_cast(id_zero), - .main_sel_op_cmov = 1, - .main_sel_resolve_ind_addr_a = static_cast(indirect_a_flag), - .main_sel_resolve_ind_addr_b = static_cast(indirect_b_flag), - .main_sel_resolve_ind_addr_c = static_cast(indirect_dst_flag), - .main_sel_resolve_ind_addr_d = static_cast(indirect_cond_flag), - .main_tag_err = static_cast(!tag_match), - .main_w_in_tag = static_cast(tag), + .main_ia = a, + .main_ic = c, + .main_ind_addr_a = indirect_a_flag ? FF(a_offset) : FF(0), + .main_ind_addr_c = indirect_dst_flag ? FF(dst_offset) : FF(0), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(direct_a_offset), + .main_mem_addr_c = FF(direct_dst_offset), + .main_pc = FF(pc++), + .main_r_in_tag = FF(static_cast(memEntry.tag)), + .main_rwc = FF(1), + .main_sel_mem_op_a = FF(1), + .main_sel_mem_op_c = FF(1), + .main_sel_op_cast = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(indirect_a_flag)), + .main_sel_resolve_ind_addr_c = FF(static_cast(indirect_dst_flag)), + .main_tag_err = FF(static_cast(!tag_match)), + .main_w_in_tag = FF(static_cast(dst_tag)), }); } +/************************************************************************************************** + * EXECUTION ENVIRONMENT + **************************************************************************************************/ + // Helper function to add kernel lookup operations into the main trace // TODO: add tag match to kernel_input_lookup opcodes to - it isnt written to - -ve test would catch +/** + * @brief Create a kernel lookup opcode object + * + * Used for looking up into the kernel inputs (context) - {caller, address, etc.} + * + * @param indirect - Perform indirect memory resolution + * @param dst_offset - Memory address to write the lookup result to + * @param selector - The index of the kernel input lookup column + * @param value - The value read from the memory address + * @param w_tag - The memory tag of the value read + * @return Row + */ Row AvmTraceBuilder::create_kernel_lookup_opcode( uint8_t indirect, uint32_t dst_offset, uint32_t selector, FF value, AvmMemoryTag w_tag) { @@ -1249,6 +1526,10 @@ void AvmTraceBuilder::op_transaction_fee(uint8_t indirect, uint32_t dst_offset) main_trace.push_back(row); } +/************************************************************************************************** + * EXECUTION ENVIRONMENT - GLOBALS + **************************************************************************************************/ + void AvmTraceBuilder::op_chain_id(uint8_t indirect, uint32_t dst_offset) { FF ia_value = kernel_trace_builder.op_chain_id(); @@ -1285,18 +1566,6 @@ void AvmTraceBuilder::op_block_number(uint8_t indirect, uint32_t dst_offset) main_trace.push_back(row); } -void AvmTraceBuilder::op_coinbase(uint8_t indirect, uint32_t dst_offset) -{ - FF ia_value = kernel_trace_builder.op_coinbase(); - Row row = create_kernel_lookup_opcode(indirect, dst_offset, COINBASE_SELECTOR, ia_value, AvmMemoryTag::FF); - row.main_sel_op_coinbase = FF(1); - - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(static_cast(row.main_clk), OpCode::COINBASE); - - main_trace.push_back(row); -} - void AvmTraceBuilder::op_timestamp(uint8_t indirect, uint32_t dst_offset) { FF ia_value = kernel_trace_builder.op_timestamp(); @@ -1309,14 +1578,14 @@ void AvmTraceBuilder::op_timestamp(uint8_t indirect, uint32_t dst_offset) main_trace.push_back(row); } -void AvmTraceBuilder::op_fee_per_da_gas(uint8_t indirect, uint32_t dst_offset) +void AvmTraceBuilder::op_coinbase(uint8_t indirect, uint32_t dst_offset) { - FF ia_value = kernel_trace_builder.op_fee_per_da_gas(); - Row row = create_kernel_lookup_opcode(indirect, dst_offset, FEE_PER_DA_GAS_SELECTOR, ia_value, AvmMemoryTag::FF); - row.main_sel_op_fee_per_da_gas = FF(1); + FF ia_value = kernel_trace_builder.op_coinbase(); + Row row = create_kernel_lookup_opcode(indirect, dst_offset, COINBASE_SELECTOR, ia_value, AvmMemoryTag::FF); + row.main_sel_op_coinbase = FF(1); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(static_cast(row.main_clk), OpCode::FEEPERDAGAS); + gas_trace_builder.constrain_gas_lookup(static_cast(row.main_clk), OpCode::COINBASE); main_trace.push_back(row); } @@ -1333,1238 +1602,1120 @@ void AvmTraceBuilder::op_fee_per_l2_gas(uint8_t indirect, uint32_t dst_offset) main_trace.push_back(row); } -// Helper function to add kernel lookup operations into the main trace -Row AvmTraceBuilder::create_kernel_output_opcode(uint8_t indirect, uint32_t clk, uint32_t data_offset) +void AvmTraceBuilder::op_fee_per_da_gas(uint8_t indirect, uint32_t dst_offset) { - auto [resolved_data] = unpack_indirects<1>(indirect, { data_offset }); - auto read_a = constrained_read_from_memory( - call_ptr, clk, resolved_data, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); - bool tag_match = read_a.tag_match; + FF ia_value = kernel_trace_builder.op_fee_per_da_gas(); + Row row = create_kernel_lookup_opcode(indirect, dst_offset, FEE_PER_DA_GAS_SELECTOR, ia_value, AvmMemoryTag::FF); + row.main_sel_op_fee_per_da_gas = FF(1); - return Row{ - .main_clk = clk, - .main_ia = read_a.val, - .main_ind_addr_a = FF(read_a.indirect_address), - .main_internal_return_ptr = internal_return_ptr, - .main_mem_addr_a = FF(read_a.direct_address), - .main_pc = pc++, - .main_r_in_tag = static_cast(AvmMemoryTag::FF), - .main_rwa = 0, - .main_sel_mem_op_a = 1, - .main_sel_q_kernel_output_lookup = 1, - .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), - .main_tag_err = FF(static_cast(!tag_match)), - }; + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(static_cast(row.main_clk), OpCode::FEEPERDAGAS); + + main_trace.push_back(row); } -Row AvmTraceBuilder::create_kernel_output_opcode_with_metadata(uint8_t indirect, - uint32_t clk, - uint32_t data_offset, - AvmMemoryTag data_r_tag, - uint32_t metadata_offset, - AvmMemoryTag metadata_r_tag) +/************************************************************************************************** + * EXECUTION ENVIRONMENT - CALLDATA + **************************************************************************************************/ + +/** + * @brief CALLDATACOPY opcode with direct or indirect memory access, i.e., + * direct: M[dst_offset:dst_offset+copy_size] = calldata[cd_offset:cd_offset+copy_size] + * indirect: M[M[dst_offset]:M[dst_offset]+copy_size] = calldata[cd_offset:cd_offset+copy_size] + * Simplified version with exclusively memory store operations and + * values from calldata passed by an array and loaded into + * intermediate registers. + * Assume that caller passes call_data_mem which is large enough so that + * no out-of-bound memory issues occur. + * TODO: error handling if dst_offset + copy_size > 2^32 which would lead to + * out-of-bound memory write. Similarly, if cd_offset + copy_size is larger + * than call_data_mem.size() + * + * @param indirect A byte encoding information about indirect/direct memory access. + * @param cd_offset The starting index of the region in calldata to be copied. + * @param copy_size The number of finite field elements to be copied into memory. + * @param dst_offset The starting index of memory where calldata will be copied to. + */ +void AvmTraceBuilder::op_calldata_copy(uint8_t indirect, uint32_t cd_offset, uint32_t copy_size, uint32_t dst_offset) { - auto [resolved_data, resolved_metadata] = unpack_indirects<2>(indirect, { data_offset, metadata_offset }); + // We parallelize storing memory operations in chunk of 3, i.e., 1 per intermediate register. + // The variable pos is an index pointing to the first storing operation (pertaining to intermediate + // register Ia) relative to cd_offset: + // cd_offset + pos: Ia memory store operation + // cd_offset + pos + 1: Ib memory store operation + // cd_offset + pos + 2: Ic memory store operation - auto read_a = - constrained_read_from_memory(call_ptr, clk, resolved_data, data_r_tag, AvmMemoryTag::U0, IntermRegister::IA); - auto read_b = constrained_read_from_memory( - call_ptr, clk, resolved_metadata, metadata_r_tag, AvmMemoryTag::U0, IntermRegister::IB); - bool tag_match = read_a.tag_match && read_b.tag_match; + uint32_t pos = 0; + uint32_t direct_dst_offset = dst_offset; // Will be overwritten in indirect mode. - return Row{ - .main_clk = clk, - .main_ia = read_a.val, - .main_ib = read_b.val, - .main_ind_addr_a = FF(read_a.indirect_address), - .main_ind_addr_b = FF(read_b.indirect_address), - .main_internal_return_ptr = internal_return_ptr, - .main_mem_addr_a = FF(read_a.direct_address), - .main_mem_addr_b = FF(read_b.direct_address), - .main_pc = pc++, - .main_r_in_tag = static_cast(data_r_tag), - .main_rwa = 0, - .main_rwb = 0, - .main_sel_mem_op_a = 1, - .main_sel_mem_op_b = 1, - .main_sel_q_kernel_output_lookup = 1, - .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), - .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), - .main_tag_err = FF(static_cast(!tag_match)), - }; -} + while (pos < copy_size) { + FF ib(0); + FF ic(0); + uint32_t mem_op_b(0); + uint32_t mem_op_c(0); + uint32_t mem_addr_b(0); + uint32_t mem_addr_c(0); + uint32_t rwb(0); + uint32_t rwc(0); + auto clk = static_cast(main_trace.size()) + 1; -Row AvmTraceBuilder::create_kernel_output_opcode_with_set_metadata_output_from_hint(uint8_t indirect, - uint32_t clk, - uint32_t data_offset, - uint32_t metadata_offset) -{ + FF ia = calldata.at(cd_offset + pos); + uint32_t mem_op_a(1); + uint32_t rwa = 1; - FF exists = execution_hints.get_side_effect_hints().at(side_effect_counter); - // TODO: throw error if incorrect + bool indirect_flag = false; + bool tag_match = true; - auto [resolved_data, resolved_metadata] = unpack_indirects<2>(indirect, { data_offset, metadata_offset }); - auto read_a = constrained_read_from_memory( - call_ptr, clk, resolved_data, AvmMemoryTag::FF, AvmMemoryTag::U8, IntermRegister::IA); + if (pos == 0 && is_operand_indirect(indirect, 0)) { + indirect_flag = true; + auto ind_read = mem_trace_builder.indirect_read_and_load_from_memory( + call_ptr, clk, IndirectRegister::IND_A, dst_offset); + direct_dst_offset = uint32_t(ind_read.val); + tag_match = ind_read.tag_match; + } - auto write_b = constrained_write_to_memory( - call_ptr, clk, resolved_metadata, exists, AvmMemoryTag::FF, AvmMemoryTag::U8, IntermRegister::IB); - bool tag_match = read_a.tag_match && write_b.tag_match; + uint32_t mem_addr_a = direct_dst_offset + pos; - return Row{ - .main_clk = clk, - .main_ia = read_a.val, - .main_ib = write_b.val, - .main_ind_addr_a = FF(read_a.indirect_address), - .main_ind_addr_b = FF(write_b.indirect_address), - .main_internal_return_ptr = internal_return_ptr, - .main_mem_addr_a = FF(read_a.direct_address), - .main_mem_addr_b = FF(write_b.direct_address), - .main_pc = pc++, - .main_r_in_tag = static_cast(AvmMemoryTag::FF), - .main_rwa = 0, - .main_rwb = 1, - .main_sel_mem_op_a = 1, - .main_sel_mem_op_b = 1, - .main_sel_q_kernel_output_lookup = 1, - .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), - .main_sel_resolve_ind_addr_b = FF(static_cast(write_b.is_indirect)), - .main_tag_err = static_cast(!tag_match), - .main_w_in_tag = static_cast(AvmMemoryTag::U8), - }; -} + // Storing from Ia + mem_trace_builder.write_into_memory( + call_ptr, clk, IntermRegister::IA, mem_addr_a, ia, AvmMemoryTag::U0, AvmMemoryTag::FF); -Row AvmTraceBuilder::create_kernel_output_opcode_with_set_value_from_hint(uint8_t indirect, - uint32_t clk, - uint32_t data_offset, - uint32_t metadata_offset) -{ - FF value = execution_hints.get_side_effect_hints().at(side_effect_counter); - // TODO: throw error if incorrect + if (copy_size - pos > 1) { + ib = calldata.at(cd_offset + pos + 1); + mem_op_b = 1; + mem_addr_b = direct_dst_offset + pos + 1; + rwb = 1; - auto [resolved_data, resolved_metadata] = unpack_indirects<2>(indirect, { data_offset, metadata_offset }); - auto write_a = constrained_write_to_memory( - call_ptr, clk, resolved_data, value, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IA); - auto read_b = constrained_read_from_memory( - call_ptr, clk, resolved_metadata, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IB); - bool tag_match = write_a.tag_match && read_b.tag_match; + // Storing from Ib + mem_trace_builder.write_into_memory( + call_ptr, clk, IntermRegister::IB, mem_addr_b, ib, AvmMemoryTag::U0, AvmMemoryTag::FF); + } - return Row{ - .main_clk = clk, - .main_ia = write_a.val, - .main_ib = read_b.val, - .main_ind_addr_a = FF(write_a.indirect_address), - .main_ind_addr_b = FF(read_b.indirect_address), - .main_internal_return_ptr = internal_return_ptr, - .main_mem_addr_a = FF(write_a.direct_address), - .main_mem_addr_b = FF(read_b.direct_address), - .main_pc = pc, // No PC increment here since we do it in the specific ops - .main_r_in_tag = static_cast(AvmMemoryTag::FF), - .main_rwa = 1, - .main_rwb = 0, - .main_sel_mem_op_a = 1, - .main_sel_mem_op_b = 1, - .main_sel_q_kernel_output_lookup = 1, - .main_sel_resolve_ind_addr_a = FF(static_cast(write_a.is_indirect)), - .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), - .main_tag_err = static_cast(!tag_match), - .main_w_in_tag = static_cast(AvmMemoryTag::FF), - }; -} + if (copy_size - pos > 2) { + ic = calldata.at(cd_offset + pos + 2); + mem_op_c = 1; + mem_addr_c = direct_dst_offset + pos + 2; + rwc = 1; -void AvmTraceBuilder::op_emit_note_hash(uint8_t indirect, uint32_t note_hash_offset) -{ - auto const clk = static_cast(main_trace.size()) + 1; + // Storing from Ic + mem_trace_builder.write_into_memory( + call_ptr, clk, IntermRegister::IC, mem_addr_c, ic, AvmMemoryTag::U0, AvmMemoryTag::FF); + } - Row row = create_kernel_output_opcode(indirect, clk, note_hash_offset); - kernel_trace_builder.op_emit_note_hash(clk, side_effect_counter, row.main_ia); - row.main_sel_op_emit_note_hash = FF(1); + // Constrain gas cost on the first row + if (pos == 0) { + gas_trace_builder.constrain_gas_lookup(clk, OpCode::CALLDATACOPY); + } - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::EMITNOTEHASH); + main_trace.push_back(Row{ + .main_clk = clk, + .main_call_ptr = call_ptr, + .main_ia = ia, + .main_ib = ib, + .main_ic = ic, + .main_ind_addr_a = indirect_flag ? FF(dst_offset) : FF(0), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(mem_addr_a), + .main_mem_addr_b = FF(mem_addr_b), + .main_mem_addr_c = FF(mem_addr_c), + .main_pc = FF(pc), + .main_rwa = FF(rwa), + .main_rwb = FF(rwb), + .main_rwc = FF(rwc), + .main_sel_mem_op_a = FF(mem_op_a), + .main_sel_mem_op_activate_gas = FF(static_cast( + pos == 0)), // TODO: remove in the long term. This activate gas only for the first row. + .main_sel_mem_op_b = FF(mem_op_b), + .main_sel_mem_op_c = FF(mem_op_c), + .main_sel_resolve_ind_addr_a = FF(static_cast(indirect_flag)), + .main_tag_err = FF(static_cast(!tag_match)), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), + }); - main_trace.push_back(row); + if (copy_size - pos > 2) { // Guard to prevent overflow if copy_size is close to uint32_t maximum value. + pos += 3; + } else { + pos = copy_size; + } + } - debug("emit_note_hash side-effect cnt: ", side_effect_counter); - side_effect_counter++; + pc++; } -void AvmTraceBuilder::op_emit_nullifier(uint8_t indirect, uint32_t nullifier_offset) -{ - auto const clk = static_cast(main_trace.size()) + 1; +/************************************************************************************************** + * MACHINE STATE - GAS + **************************************************************************************************/ - Row row = create_kernel_output_opcode(indirect, clk, nullifier_offset); - kernel_trace_builder.op_emit_nullifier(clk, side_effect_counter, row.main_ia); - row.main_sel_op_emit_nullifier = FF(1); +// Helper for "gas left" related opcodes +void AvmTraceBuilder::execute_gasleft(OpCode opcode, uint8_t indirect, uint32_t dst_offset) +{ + assert(opcode == OpCode::L2GASLEFT || opcode == OpCode::DAGASLEFT); - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::EMITNULLIFIER); + auto clk = static_cast(main_trace.size()) + 1; - main_trace.push_back(row); + auto [resolved_dst] = unpack_indirects<1>(indirect, { dst_offset }); - debug("emit_nullifier side-effect cnt: ", side_effect_counter); - side_effect_counter++; -} + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, opcode); -void AvmTraceBuilder::op_emit_l2_to_l1_msg(uint8_t indirect, uint32_t recipient_offset, uint32_t content_offset) -{ - auto const clk = static_cast(main_trace.size()) + 1; + uint32_t gas_remaining = 0; - // Note: unorthadox order - as seen in L2ToL1Message struct in TS - Row row = create_kernel_output_opcode_with_metadata( - indirect, clk, content_offset, AvmMemoryTag::FF, recipient_offset, AvmMemoryTag::FF); - kernel_trace_builder.op_emit_l2_to_l1_msg(clk, side_effect_counter, row.main_ia, row.main_ib); - row.main_sel_op_emit_l2_to_l1_msg = FF(1); + if (opcode == OpCode::L2GASLEFT) { + gas_remaining = gas_trace_builder.get_l2_gas_left(); + } else { + gas_remaining = gas_trace_builder.get_da_gas_left(); + } - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::SENDL2TOL1MSG); + // Write into memory from intermediate register ia. + // TODO: probably will be U32 in final version + auto write_dst = constrained_write_to_memory( + call_ptr, clk, resolved_dst, gas_remaining, AvmMemoryTag::U0, AvmMemoryTag::FF, IntermRegister::IA); - main_trace.push_back(row); + main_trace.push_back(Row{ + .main_clk = clk, + .main_call_ptr = call_ptr, + .main_ia = gas_remaining, + .main_ind_addr_a = FF(write_dst.indirect_address), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(write_dst.direct_address), + .main_pc = FF(pc++), + .main_r_in_tag = FF(static_cast(AvmMemoryTag::U0)), + .main_rwa = FF(1), + .main_sel_mem_op_a = FF(1), + .main_sel_op_dagasleft = (opcode == OpCode::DAGASLEFT) ? FF(1) : FF(0), + .main_sel_op_l2gasleft = (opcode == OpCode::L2GASLEFT) ? FF(1) : FF(0), + .main_sel_resolve_ind_addr_a = FF(static_cast(is_operand_indirect(indirect, 0))), + .main_tag_err = FF(static_cast(!write_dst.tag_match)), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), // TODO: probably will be U32 in final version + // Should the circuit (pil) constrain U32? + }); +} - debug("emit_l2_to_l1_msg side-effect cnt: ", side_effect_counter); - side_effect_counter++; +void AvmTraceBuilder::op_l2gasleft(uint8_t indirect, uint32_t dst_offset) +{ + execute_gasleft(OpCode::L2GASLEFT, indirect, dst_offset); } -void AvmTraceBuilder::op_emit_unencrypted_log(uint8_t indirect, - uint32_t log_offset, - [[maybe_unused]] uint32_t log_size_offset) +void AvmTraceBuilder::op_dagasleft(uint8_t indirect, uint32_t dst_offset) { - auto const clk = static_cast(main_trace.size()) + 1; + execute_gasleft(OpCode::DAGASLEFT, indirect, dst_offset); +} - // FIXME: read (and constrain) log_size_offset - // FIXME: we need to constrain the log_size_offset mem read (and tag check), not just one field! - Row row = create_kernel_output_opcode(indirect, clk, log_offset); - kernel_trace_builder.op_emit_unencrypted_log(clk, side_effect_counter, row.main_ia); - row.main_sel_op_emit_unencrypted_log = FF(1); +/************************************************************************************************** + * MACHINE STATE - INTERNAL CONTROL FLOW + **************************************************************************************************/ + +/** + * @brief JUMP OPCODE + * Jumps to a new `jmp_dest` + * This function must: + * - Set the next program counter to the provided `jmp_dest`. + * + * @param jmp_dest - The destination to jump to + */ +void AvmTraceBuilder::op_jump(uint32_t jmp_dest) +{ + auto clk = static_cast(main_trace.size()) + 1; // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::EMITUNENCRYPTEDLOG); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::JUMP); - main_trace.push_back(row); + main_trace.push_back(Row{ + .main_clk = clk, + .main_call_ptr = call_ptr, + .main_ia = FF(jmp_dest), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_pc = FF(pc), + .main_sel_op_jump = FF(1), + }); - debug("emit_unencrypted_log side-effect cnt: ", side_effect_counter); - side_effect_counter++; + // Adjust parameters for the next row + pc = jmp_dest; } -// State output opcodes that include metadata -void AvmTraceBuilder::op_l1_to_l2_msg_exists(uint8_t indirect, uint32_t log_offset, uint32_t dest_offset) +/** + * @brief JUMPI OPCODE + * Jumps to a new `jmp_dest` if M[cond_offset] > 0 + * This function sets the next program counter to the provided `jmp_dest` if condition > 0. + * Otherwise, program counter is incremented. + * + * @param indirect A byte encoding information about indirect/direct memory access. + * @param jmp_dest The destination to jump to + * @param cond_offset Offset of the condition + */ +void AvmTraceBuilder::op_jumpi(uint8_t indirect, uint32_t jmp_dest, uint32_t cond_offset) { - auto const clk = static_cast(main_trace.size()) + 1; - - Row row = create_kernel_output_opcode_with_set_metadata_output_from_hint(indirect, clk, log_offset, dest_offset); - kernel_trace_builder.op_l1_to_l2_msg_exists( - clk, side_effect_counter, row.main_ia, /*safe*/ static_cast(row.main_ib)); - row.main_sel_op_l1_to_l2_msg_exists = FF(1); + auto clk = static_cast(main_trace.size()) + 1; - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::L1TOL2MSGEXISTS); + bool tag_match = true; + uint32_t direct_cond_offset = cond_offset; - main_trace.push_back(row); + bool indirect_cond_flag = is_operand_indirect(indirect, 0); - debug("l1_to_l2_msg_exists side-effect cnt: ", side_effect_counter); - side_effect_counter++; -} + if (indirect_cond_flag) { + auto read_ind_d = + mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_D, cond_offset); + direct_cond_offset = uint32_t(read_ind_d.val); + tag_match = tag_match && read_ind_d.tag_match; + } -void AvmTraceBuilder::op_note_hash_exists(uint8_t indirect, uint32_t note_hash_offset, uint32_t dest_offset) -{ - auto const clk = static_cast(main_trace.size()) + 1; + // Specific JUMPI loading of conditional value into intermediate register id without any tag constraint. + auto read_d = mem_trace_builder.read_and_load_jumpi_opcode(call_ptr, clk, direct_cond_offset); - Row row = - create_kernel_output_opcode_with_set_metadata_output_from_hint(indirect, clk, note_hash_offset, dest_offset); - kernel_trace_builder.op_note_hash_exists( - clk, side_effect_counter, row.main_ia, /*safe*/ static_cast(row.main_ib)); - row.main_sel_op_note_hash_exists = FF(1); + const bool id_zero = read_d.val == 0; + FF const inv = !id_zero ? read_d.val.invert() : 1; + uint32_t next_pc = !id_zero ? jmp_dest : pc + 1; // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::NOTEHASHEXISTS); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::JUMPI); - main_trace.push_back(row); + main_trace.push_back(Row{ + .main_clk = clk, + .main_call_ptr = call_ptr, + .main_ia = FF(next_pc), + .main_id = read_d.val, + .main_id_zero = static_cast(id_zero), + .main_ind_addr_d = indirect_cond_flag ? cond_offset : 0, + .main_internal_return_ptr = FF(internal_return_ptr), + .main_inv = inv, + .main_mem_addr_d = direct_cond_offset, + .main_pc = FF(pc), + .main_r_in_tag = static_cast(read_d.tag), + .main_sel_mem_op_d = 1, + .main_sel_op_jumpi = FF(1), + .main_sel_resolve_ind_addr_d = static_cast(indirect_cond_flag), + .main_tag_err = static_cast(!tag_match), + .main_w_in_tag = static_cast(read_d.tag), + }); - debug("note_hash_exists side-effect cnt: ", side_effect_counter); - side_effect_counter++; + // Adjust parameters for the next row + pc = next_pc; } -void AvmTraceBuilder::op_nullifier_exists(uint8_t indirect, uint32_t nullifier_offset, uint32_t dest_offset) +/** + * @brief INTERNAL_CALL OPCODE + * This opcode effectively jumps to a new `jmp_dest` and stores the return program counter + * (current program counter + 1) onto a call stack. + * This function must: + * - Set the next program counter to the provided `jmp_dest`. + * - Store the current `pc` + 1 onto the call stack (emulated in memory) + * - Increment the return stack pointer (a pointer to where the call stack is in memory) + * + * Note: We use intermediate register to perform memory storage operations. + * + * @param jmp_dest - The destination to jump to + */ +void AvmTraceBuilder::op_internal_call(uint32_t jmp_dest) { - auto const clk = static_cast(main_trace.size()) + 1; + auto clk = static_cast(main_trace.size()) + 1; - Row row = - create_kernel_output_opcode_with_set_metadata_output_from_hint(indirect, clk, nullifier_offset, dest_offset); - kernel_trace_builder.op_nullifier_exists( - clk, side_effect_counter, row.main_ia, /*safe*/ static_cast(row.main_ib)); - row.main_sel_op_nullifier_exists = FF(1); + // We store the next instruction as the return location + mem_trace_builder.write_into_memory(INTERNAL_CALL_SPACE_ID, + clk, + IntermRegister::IB, + internal_return_ptr, + FF(pc + 1), + AvmMemoryTag::U0, + AvmMemoryTag::U32); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::NULLIFIEREXISTS); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::INTERNALCALL); - main_trace.push_back(row); + main_trace.push_back(Row{ + .main_clk = clk, + .main_call_ptr = call_ptr, + .main_ia = FF(jmp_dest), + .main_ib = FF(pc + 1), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_b = FF(internal_return_ptr), + .main_pc = FF(pc), + .main_rwb = FF(1), + .main_sel_mem_op_b = FF(1), + .main_sel_op_internal_call = FF(1), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::U32)), + }); - debug("nullifier_exists side-effect cnt: ", side_effect_counter); - side_effect_counter++; + // Adjust parameters for the next row + pc = jmp_dest; + internal_return_ptr++; } -void AvmTraceBuilder::op_sload(uint8_t indirect, uint32_t slot_offset, uint32_t size, uint32_t dest_offset) +/** + * @brief INTERNAL_RETURN OPCODE + * The opcode returns from an internal call. + * This function must: + * - Read the return location from the internal_return_ptr + * - Set the next program counter to the return location + * - Decrement the return stack pointer + * + * TODO(https://github.com/AztecProtocol/aztec-packages/issues/3740): This function MUST come after a call + * instruction. + */ +void AvmTraceBuilder::op_internal_return() { auto clk = static_cast(main_trace.size()) + 1; - auto [resolved_slot, resolved_dest] = unpack_indirects<2>(indirect, { slot_offset, dest_offset }); - auto read_slot = constrained_read_from_memory( - call_ptr, clk, resolved_slot, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); + // Internal return pointer is decremented + // We want to load the value pointed by the internal pointer + auto read_a = mem_trace_builder.read_and_load_from_memory( + INTERNAL_CALL_SPACE_ID, clk, IntermRegister::IA, internal_return_ptr - 1, AvmMemoryTag::U32, AvmMemoryTag::U0); + + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::INTERNALRETURN); - // Read the slot value that we will write hints to in a row main_trace.push_back(Row{ .main_clk = clk, - .main_ia = read_slot.val, - .main_ind_addr_a = FF(read_slot.indirect_address), + .main_call_ptr = call_ptr, + .main_ia = read_a.val, .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(read_slot.direct_address), - .main_pc = pc, // No PC increment here since this is the same opcode as the rows created below - .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), + .main_mem_addr_a = FF(internal_return_ptr - 1), + .main_pc = pc, + .main_r_in_tag = FF(static_cast(AvmMemoryTag::U32)), + .main_rwa = FF(0), .main_sel_mem_op_a = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(read_slot.is_indirect)), - .main_tag_err = FF(static_cast(!read_slot.tag_match)), + .main_sel_op_internal_return = FF(1), + .main_tag_err = FF(static_cast(!read_a.tag_match)), }); - clk++; - - AddressWithMode write_dst = resolved_dest; - // Loop over the size and write the hints to memory - for (uint32_t i = 0; i < size; i++) { - FF value = execution_hints.get_side_effect_hints().at(side_effect_counter); - - auto write_a = constrained_write_to_memory( - call_ptr, clk, write_dst, value, AvmMemoryTag::U0, AvmMemoryTag::FF, IntermRegister::IA); - - auto row = Row{ - .main_clk = clk, - .main_ia = value, - .main_ib = read_slot.val + i, // slot increments each time - .main_ind_addr_a = write_a.indirect_address, - .main_internal_return_ptr = internal_return_ptr, - .main_mem_addr_a = write_a.direct_address, // direct address incremented at end of the loop - .main_pc = pc, // No PC increment here since this is the same opcode for all loop iterations - .main_rwa = 1, - .main_sel_mem_op_a = 1, - .main_sel_op_sload = FF(1), - .main_sel_q_kernel_output_lookup = 1, - .main_sel_resolve_ind_addr_a = FF(static_cast(write_a.is_indirect)), - .main_tag_err = FF(static_cast(!write_a.tag_match)), - .main_w_in_tag = static_cast(AvmMemoryTag::FF), - }; - - // Output storage read to kernel outputs (performs lookup) - // Tuples of (slot, value) in the kernel lookup - kernel_trace_builder.op_sload(clk, side_effect_counter, row.main_ib, row.main_ia); - - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::SLOAD); - - main_trace.push_back(row); - - debug("sload side-effect cnt: ", side_effect_counter); - side_effect_counter++; - clk++; - // After the first loop, all future write destinations are direct, increment the direct address - write_dst = AddressWithMode{ AddressingMode::DIRECT, write_a.direct_address + 1 }; - } - pc++; + pc = uint32_t(read_a.val); + internal_return_ptr--; } -void AvmTraceBuilder::op_sstore(uint8_t indirect, uint32_t src_offset, uint32_t size, uint32_t slot_offset) +/************************************************************************************************** + * MACHINE STATE - MEMORY + **************************************************************************************************/ + +// TODO: Ensure that the bytecode validation and/or deserialization is +// enforcing that val complies to the tag. +/** + * @brief Set a constant from bytecode with direct or indirect memory access. + * SET opcode is implemented purely as a memory operation. As val is a + * constant passed in the bytecode, the deserialization layer or bytecode + * validation circuit is enforcing that the constant complies to in_tag. + * Therefore, no range check is required as part of this opcode relation. + * + * @param indirect A byte encoding information about indirect/direct memory access. + * @param val The constant to be written upcasted to u128 + * @param dst_offset Memory destination offset where val is written to + * @param in_tag The instruction memory tag + */ +void AvmTraceBuilder::op_set(uint8_t indirect, uint128_t val, uint32_t dst_offset, AvmMemoryTag in_tag) { - auto clk = static_cast(main_trace.size()) + 1; + auto const clk = static_cast(main_trace.size()) + 1; + auto const val_ff = FF{ uint256_t::from_uint128(val) }; + auto [resolved_c] = unpack_indirects<1>(indirect, { dst_offset }); - auto [resolved_src, resolved_slot] = unpack_indirects<2>(indirect, { src_offset, slot_offset }); + auto write_c = + constrained_write_to_memory(call_ptr, clk, resolved_c, val_ff, AvmMemoryTag::U0, in_tag, IntermRegister::IC); - auto read_slot = constrained_read_from_memory( - call_ptr, clk, resolved_slot, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IA); + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::SET); main_trace.push_back(Row{ .main_clk = clk, - .main_ia = read_slot.val, - .main_ind_addr_a = FF(read_slot.indirect_address), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(read_slot.direct_address), - .main_pc = pc, // No PC increment here since this is the same opcode as the rows created below - .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), - .main_sel_mem_op_a = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(read_slot.is_indirect)), - .main_tag_err = FF(static_cast(!read_slot.tag_match)), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), + .main_call_ptr = call_ptr, + .main_ic = write_c.val, + .main_ind_addr_c = FF(write_c.indirect_address), + .main_internal_return_ptr = internal_return_ptr, + .main_mem_addr_c = FF(write_c.direct_address), + .main_pc = pc++, + .main_rwc = 1, + .main_sel_mem_op_activate_gas = 1, // TODO: remove in the long term + .main_sel_mem_op_c = 1, + .main_sel_resolve_ind_addr_c = FF(static_cast(write_c.is_indirect)), + .main_tag_err = static_cast(!write_c.tag_match), + .main_w_in_tag = static_cast(in_tag), }); - clk++; - - AddressWithMode read_src = resolved_src; - - // This loop reads a _size_ number of elements from memory and places them into a tuple of (ele, slot) - // in the kernel lookup. - for (uint32_t i = 0; i < size; i++) { - auto read_a = constrained_read_from_memory( - call_ptr, clk, read_src, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); - - Row row = Row{ - .main_clk = clk, - .main_ia = read_a.val, - .main_ib = read_slot.val + i, // slot increments each time - .main_ind_addr_a = read_a.indirect_address, - .main_internal_return_ptr = internal_return_ptr, - .main_mem_addr_a = read_a.direct_address, // direct address incremented at end of the loop - .main_pc = pc, - .main_r_in_tag = static_cast(AvmMemoryTag::FF), - .main_sel_mem_op_a = 1, - .main_sel_q_kernel_output_lookup = 1, - .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), - .main_tag_err = FF(static_cast(!read_a.tag_match)), - }; - row.main_sel_op_sstore = FF(1); - kernel_trace_builder.op_sstore(clk, side_effect_counter, row.main_ib, row.main_ia); - - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::SSTORE); - - main_trace.push_back(row); - - debug("sstore side-effect cnt: ", side_effect_counter); - side_effect_counter++; - clk++; - // All future reads are direct, increment the direct address - read_src = AddressWithMode{ AddressingMode::DIRECT, read_a.direct_address + 1 }; - } - pc++; } /** - * @brief Cast an element pointed by the address a_offset into type specified by dst_tag and - store the result in address given by dst_offset. + * @brief Copy value and tag from a memory cell at position src_offset to the + * memory cell at position dst_offset * * @param indirect A byte encoding information about indirect/direct memory access. - * @param a_offset Offset of source memory cell. - * @param dst_offset Offset of destination memory cell. - * @param dst_tag Destination tag specifying the type the source value must be casted to. + * @param src_offset Offset of source memory cell + * @param dst_offset Offset of destination memory cell */ -void AvmTraceBuilder::op_cast(uint8_t indirect, uint32_t a_offset, uint32_t dst_offset, AvmMemoryTag dst_tag) +void AvmTraceBuilder::op_mov(uint8_t indirect, uint32_t src_offset, uint32_t dst_offset) { auto const clk = static_cast(main_trace.size()) + 1; bool tag_match = true; - uint32_t direct_a_offset = a_offset; + uint32_t direct_src_offset = src_offset; uint32_t direct_dst_offset = dst_offset; - bool indirect_a_flag = is_operand_indirect(indirect, 0); + bool indirect_src_flag = is_operand_indirect(indirect, 0); bool indirect_dst_flag = is_operand_indirect(indirect, 1); - if (indirect_a_flag) { + if (indirect_src_flag) { auto read_ind_a = - mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_A, a_offset); - direct_a_offset = uint32_t(read_ind_a.val); - tag_match = tag_match && read_ind_a.tag_match; + mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_A, src_offset); + tag_match = read_ind_a.tag_match; + direct_src_offset = uint32_t(read_ind_a.val); } if (indirect_dst_flag) { auto read_ind_c = mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_C, dst_offset); - direct_dst_offset = uint32_t(read_ind_c.val); tag_match = tag_match && read_ind_c.tag_match; + direct_dst_offset = uint32_t(read_ind_c.val); } - // Reading from memory and loading into ia - auto memEntry = mem_trace_builder.read_and_load_cast_opcode(call_ptr, clk, direct_a_offset, dst_tag); - FF a = memEntry.val; - - // In case of a memory tag error, we do not perform the computation. - // Therefore, we do not create any entry in ALU table and store the value 0 as - // output (c) in memory. - FF c = tag_match ? alu_trace_builder.op_cast(a, dst_tag, clk) : FF(0); + // Reading from memory and loading into ia without tag check. + auto const [val, tag] = mem_trace_builder.read_and_load_mov_opcode(call_ptr, clk, direct_src_offset); - // Write into memory value c from intermediate register ic. - mem_trace_builder.write_into_memory(call_ptr, clk, IntermRegister::IC, direct_dst_offset, c, memEntry.tag, dst_tag); + // Write into memory from intermediate register ic. + mem_trace_builder.write_into_memory(call_ptr, clk, IntermRegister::IC, direct_dst_offset, val, tag, tag); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::CAST); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::MOV); main_trace.push_back(Row{ .main_clk = clk, - .main_alu_in_tag = FF(static_cast(dst_tag)), .main_call_ptr = call_ptr, - .main_ia = a, - .main_ic = c, - .main_ind_addr_a = indirect_a_flag ? FF(a_offset) : FF(0), - .main_ind_addr_c = indirect_dst_flag ? FF(dst_offset) : FF(0), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(direct_a_offset), - .main_mem_addr_c = FF(direct_dst_offset), - .main_pc = FF(pc++), - .main_r_in_tag = FF(static_cast(memEntry.tag)), - .main_rwc = FF(1), - .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_c = FF(1), - .main_sel_op_cast = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(indirect_a_flag)), - .main_sel_resolve_ind_addr_c = FF(static_cast(indirect_dst_flag)), - .main_tag_err = FF(static_cast(!tag_match)), - .main_w_in_tag = FF(static_cast(dst_tag)), + .main_ia = val, + .main_ic = val, + .main_ind_addr_a = indirect_src_flag ? src_offset : 0, + .main_ind_addr_c = indirect_dst_flag ? dst_offset : 0, + .main_internal_return_ptr = internal_return_ptr, + .main_mem_addr_a = direct_src_offset, + .main_mem_addr_c = direct_dst_offset, + .main_pc = pc++, + .main_r_in_tag = static_cast(tag), + .main_rwc = 1, + .main_sel_mem_op_a = 1, + .main_sel_mem_op_c = 1, + .main_sel_mov_ia_to_ic = 1, + .main_sel_op_mov = 1, + .main_sel_resolve_ind_addr_a = static_cast(indirect_src_flag), + .main_sel_resolve_ind_addr_c = static_cast(indirect_dst_flag), + .main_tag_err = static_cast(!tag_match), + .main_w_in_tag = static_cast(tag), }); } + /** - * @brief Integer division with direct or indirect memory access. + * @brief Copy value and tag from a memory cell at position src_offset to the + * memory cell at position dst_offset. src_offset is a_offset if the value + * defined by cond_offset is non-zero. Otherwise, src_offset is b_offset. * * @param indirect A byte encoding information about indirect/direct memory access. - * @param a_offset An index in memory pointing to the first operand of the division. - * @param b_offset An index in memory pointing to the second operand of the division. - * @param dst_offset An index in memory pointing to the output of the division. - * @param in_tag The instruction memory tag of the operands. + * @param a_offset Offset of first candidate source memory cell + * @param b_offset Offset of second candidate source memory cell + * @param cond_offset Offset of the condition determining the source offset (a_offset or b_offset) + * @param dst_offset Offset of destination memory cell */ -void AvmTraceBuilder::op_div( - uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag) +void AvmTraceBuilder::op_cmov( + uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t cond_offset, uint32_t dst_offset) { - auto clk = static_cast(main_trace.size()) + 1; - - auto [resolved_a, resolved_b, resolved_dst] = unpack_indirects<3>(indirect, { a_offset, b_offset, dst_offset }); + auto const clk = static_cast(main_trace.size()) + 1; + bool tag_match = true; + uint32_t direct_a_offset = a_offset; + uint32_t direct_b_offset = b_offset; + uint32_t direct_cond_offset = cond_offset; + uint32_t direct_dst_offset = dst_offset; - // Reading from memory and loading into ia resp. ib. - auto read_a = constrained_read_from_memory(call_ptr, clk, resolved_a, in_tag, in_tag, IntermRegister::IA); - auto read_b = constrained_read_from_memory(call_ptr, clk, resolved_b, in_tag, in_tag, IntermRegister::IB); - bool tag_match = read_a.tag_match && read_b.tag_match; + bool indirect_a_flag = is_operand_indirect(indirect, 0); + bool indirect_b_flag = is_operand_indirect(indirect, 1); + bool indirect_cond_flag = is_operand_indirect(indirect, 2); + bool indirect_dst_flag = is_operand_indirect(indirect, 3); - // a / b = c - FF a = read_a.val; - FF b = read_b.val; + if (indirect_a_flag) { + auto read_ind_a = + mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_A, a_offset); + direct_a_offset = uint32_t(read_ind_a.val); + tag_match = tag_match && read_ind_a.tag_match; + } - // In case of a memory tag error, we do not perform the computation. - // Therefore, we do not create any entry in ALU table and store the value 0 as - // output (c) in memory. - FF c; - FF inv; - FF error; + if (indirect_b_flag) { + auto read_ind_b = + mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_B, b_offset); + direct_b_offset = uint32_t(read_ind_b.val); + tag_match = tag_match && read_ind_b.tag_match; + } - if (!b.is_zero()) { - // If b is not zero, we prove it is not by providing its inverse as well - inv = b.invert(); - c = tag_match ? alu_trace_builder.op_div(a, b, in_tag, clk) : FF(0); - error = 0; - } else { - inv = 1; - c = 0; - error = 1; + if (indirect_cond_flag) { + auto read_ind_d = + mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_D, cond_offset); + direct_cond_offset = uint32_t(read_ind_d.val); + tag_match = tag_match && read_ind_d.tag_match; } - // Write into memory value c from intermediate register ic. - auto write_dst = constrained_write_to_memory(call_ptr, clk, resolved_dst, c, in_tag, in_tag, IntermRegister::IC); + if (indirect_dst_flag) { + auto read_ind_c = + mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_C, dst_offset); + direct_dst_offset = uint32_t(read_ind_c.val); + tag_match = tag_match && read_ind_c.tag_match; + } + + // Reading from memory and loading into ia or ib without tag check. We also load the conditional value + // in id without any tag check. + std::array const cmov_res = mem_trace_builder.read_and_load_cmov_opcode( + call_ptr, clk, direct_a_offset, direct_b_offset, direct_cond_offset); + + AvmMemTraceBuilder::MemEntry const& a_mem_entry = cmov_res.at(0); + AvmMemTraceBuilder::MemEntry const& b_mem_entry = cmov_res.at(1); + AvmMemTraceBuilder::MemEntry const& cond_mem_entry = cmov_res.at(2); + + const bool id_zero = cond_mem_entry.val == 0; + + auto const& val = id_zero ? b_mem_entry.val : a_mem_entry.val; + auto const& tag = id_zero ? b_mem_entry.tag : a_mem_entry.tag; + + // Write into memory from intermediate register ic. + mem_trace_builder.write_into_memory(call_ptr, clk, IntermRegister::IC, direct_dst_offset, val, tag, tag); + + FF const inv = !id_zero ? cond_mem_entry.val.invert() : 1; // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::DIV); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::CMOV); main_trace.push_back(Row{ .main_clk = clk, - .main_alu_in_tag = FF(static_cast(in_tag)), .main_call_ptr = call_ptr, + .main_ia = a_mem_entry.val, + .main_ib = b_mem_entry.val, + .main_ic = val, + .main_id = cond_mem_entry.val, + .main_id_zero = static_cast(id_zero), + .main_ind_addr_a = indirect_a_flag ? a_offset : 0, + .main_ind_addr_b = indirect_b_flag ? b_offset : 0, + .main_ind_addr_c = indirect_dst_flag ? dst_offset : 0, + .main_ind_addr_d = indirect_cond_flag ? cond_offset : 0, + .main_internal_return_ptr = internal_return_ptr, + .main_inv = inv, + .main_mem_addr_a = direct_a_offset, + .main_mem_addr_b = direct_b_offset, + .main_mem_addr_c = direct_dst_offset, + .main_mem_addr_d = direct_cond_offset, + .main_pc = pc++, + .main_r_in_tag = static_cast(tag), + .main_rwc = 1, + .main_sel_mem_op_a = 1, + .main_sel_mem_op_b = 1, + .main_sel_mem_op_c = 1, + .main_sel_mem_op_d = 1, + .main_sel_mov_ia_to_ic = static_cast(!id_zero), + .main_sel_mov_ib_to_ic = static_cast(id_zero), + .main_sel_op_cmov = 1, + .main_sel_resolve_ind_addr_a = static_cast(indirect_a_flag), + .main_sel_resolve_ind_addr_b = static_cast(indirect_b_flag), + .main_sel_resolve_ind_addr_c = static_cast(indirect_dst_flag), + .main_sel_resolve_ind_addr_d = static_cast(indirect_cond_flag), + .main_tag_err = static_cast(!tag_match), + .main_w_in_tag = static_cast(tag), + }); +} + +/************************************************************************************************** + * HELPERS FOR WORLD STATE AND ACCRUED SUBSTATE + **************************************************************************************************/ + +/** + * @brief Create a kernel output opcode object + * + * Used for writing to the kernel app outputs - {new_note_hash, new_nullifier, etc.} + * + * @param indirect - Perform indirect memory resolution + * @param clk - The trace clk + * @param data_offset - The memory address to read the output from + * @return Row + */ +Row AvmTraceBuilder::create_kernel_output_opcode(uint8_t indirect, uint32_t clk, uint32_t data_offset) +{ + auto [resolved_data] = unpack_indirects<1>(indirect, { data_offset }); + auto read_a = constrained_read_from_memory( + call_ptr, clk, resolved_data, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); + bool tag_match = read_a.tag_match; + + return Row{ + .main_clk = clk, + .main_ia = read_a.val, + .main_ind_addr_a = FF(read_a.indirect_address), + .main_internal_return_ptr = internal_return_ptr, + .main_mem_addr_a = FF(read_a.direct_address), + .main_pc = pc++, + .main_r_in_tag = static_cast(AvmMemoryTag::FF), + .main_rwa = 0, + .main_sel_mem_op_a = 1, + .main_sel_q_kernel_output_lookup = 1, + .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), + .main_tag_err = FF(static_cast(!tag_match)), + }; +} + +/** + * @brief Create a kernel output opcode with metadata object + * + * Used for writing to the kernel app outputs with extra metadata - {sload, sstore} (value, slot) + * + * @param indirect - Perform indirect memory resolution + * @param clk - The trace clk + * @param data_offset - The offset of the main value to output + * @param data_r_tag - The data type of the value + * @param metadata_offset - The offset of the metadata (slot in the sload example) + * @param metadata_r_tag - The data type of the metadata + * @return Row + */ +Row AvmTraceBuilder::create_kernel_output_opcode_with_metadata(uint8_t indirect, + uint32_t clk, + uint32_t data_offset, + AvmMemoryTag data_r_tag, + uint32_t metadata_offset, + AvmMemoryTag metadata_r_tag) +{ + auto [resolved_data, resolved_metadata] = unpack_indirects<2>(indirect, { data_offset, metadata_offset }); + + auto read_a = + constrained_read_from_memory(call_ptr, clk, resolved_data, data_r_tag, AvmMemoryTag::U0, IntermRegister::IA); + auto read_b = constrained_read_from_memory( + call_ptr, clk, resolved_metadata, metadata_r_tag, AvmMemoryTag::U0, IntermRegister::IB); + bool tag_match = read_a.tag_match && read_b.tag_match; + + return Row{ + .main_clk = clk, .main_ia = read_a.val, .main_ib = read_b.val, - .main_ic = c, .main_ind_addr_a = FF(read_a.indirect_address), .main_ind_addr_b = FF(read_b.indirect_address), - .main_ind_addr_c = FF(write_dst.indirect_address), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_inv = tag_match ? inv : FF(1), + .main_internal_return_ptr = internal_return_ptr, .main_mem_addr_a = FF(read_a.direct_address), .main_mem_addr_b = FF(read_b.direct_address), - .main_mem_addr_c = FF(write_dst.direct_address), - .main_op_err = tag_match ? error : FF(1), - .main_pc = FF(pc++), - .main_r_in_tag = FF(static_cast(in_tag)), - .main_rwc = FF(1), - .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_b = FF(1), - .main_sel_mem_op_c = FF(1), - .main_sel_op_div = FF(1), + .main_pc = pc++, + .main_r_in_tag = static_cast(data_r_tag), + .main_rwa = 0, + .main_rwb = 0, + .main_sel_mem_op_a = 1, + .main_sel_mem_op_b = 1, + .main_sel_q_kernel_output_lookup = 1, .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), - .main_sel_resolve_ind_addr_c = FF(static_cast(write_dst.is_indirect)), .main_tag_err = FF(static_cast(!tag_match)), - .main_w_in_tag = FF(static_cast(in_tag)), - }); + }; } /** - * @brief CALLDATACOPY opcode with direct or indirect memory access, i.e., - * direct: M[dst_offset:dst_offset+copy_size] = calldata[cd_offset:cd_offset+copy_size] - * indirect: M[M[dst_offset]:M[dst_offset]+copy_size] = calldata[cd_offset:cd_offset+copy_size] - * Simplified version with exclusively memory store operations and - * values from calldata passed by an array and loaded into - * intermediate registers. - * Assume that caller passes call_data_mem which is large enough so that - * no out-of-bound memory issues occur. - * TODO: error handling if dst_offset + copy_size > 2^32 which would lead to - * out-of-bound memory write. Similarly, if cd_offset + copy_size is larger - * than call_data_mem.size() + * @brief Create a kernel output opcode with set metadata output object * - * @param indirect A byte encoding information about indirect/direct memory access. - * @param cd_offset The starting index of the region in calldata to be copied. - * @param copy_size The number of finite field elements to be copied into memory. - * @param dst_offset The starting index of memory where calldata will be copied to. + * Used for writing output opcode where one metadata value is written and comes from a hint + * {note_hash_exists, nullifier_exists, etc. } Where a boolean output if it exists must also be written + * + * @param indirect - Perform indirect memory resolution + * @param clk - The trace clk + * @param data_offset - The offset of the main value to output + * @param metadata_offset - The offset of the metadata (slot in the sload example) + * @return Row */ -void AvmTraceBuilder::op_calldata_copy(uint8_t indirect, uint32_t cd_offset, uint32_t copy_size, uint32_t dst_offset) +Row AvmTraceBuilder::create_kernel_output_opcode_with_set_metadata_output_from_hint(uint8_t indirect, + uint32_t clk, + uint32_t data_offset, + uint32_t metadata_offset) { - // We parallelize storing memory operations in chunk of 3, i.e., 1 per intermediate register. - // The variable pos is an index pointing to the first storing operation (pertaining to intermediate - // register Ia) relative to cd_offset: - // cd_offset + pos: Ia memory store operation - // cd_offset + pos + 1: Ib memory store operation - // cd_offset + pos + 2: Ic memory store operation - - uint32_t pos = 0; - uint32_t direct_dst_offset = dst_offset; // Will be overwritten in indirect mode. - - while (pos < copy_size) { - FF ib(0); - FF ic(0); - uint32_t mem_op_b(0); - uint32_t mem_op_c(0); - uint32_t mem_addr_b(0); - uint32_t mem_addr_c(0); - uint32_t rwb(0); - uint32_t rwc(0); - auto clk = static_cast(main_trace.size()) + 1; - FF ia = calldata.at(cd_offset + pos); - uint32_t mem_op_a(1); - uint32_t rwa = 1; + FF exists = execution_hints.get_side_effect_hints().at(side_effect_counter); + // TODO: throw error if incorrect - bool indirect_flag = false; - bool tag_match = true; + auto [resolved_data, resolved_metadata] = unpack_indirects<2>(indirect, { data_offset, metadata_offset }); + auto read_a = constrained_read_from_memory( + call_ptr, clk, resolved_data, AvmMemoryTag::FF, AvmMemoryTag::U8, IntermRegister::IA); - if (pos == 0 && is_operand_indirect(indirect, 0)) { - indirect_flag = true; - auto ind_read = mem_trace_builder.indirect_read_and_load_from_memory( - call_ptr, clk, IndirectRegister::IND_A, dst_offset); - direct_dst_offset = uint32_t(ind_read.val); - tag_match = ind_read.tag_match; - } + auto write_b = constrained_write_to_memory( + call_ptr, clk, resolved_metadata, exists, AvmMemoryTag::FF, AvmMemoryTag::U8, IntermRegister::IB); + bool tag_match = read_a.tag_match && write_b.tag_match; - uint32_t mem_addr_a = direct_dst_offset + pos; + return Row{ + .main_clk = clk, + .main_ia = read_a.val, + .main_ib = write_b.val, + .main_ind_addr_a = FF(read_a.indirect_address), + .main_ind_addr_b = FF(write_b.indirect_address), + .main_internal_return_ptr = internal_return_ptr, + .main_mem_addr_a = FF(read_a.direct_address), + .main_mem_addr_b = FF(write_b.direct_address), + .main_pc = pc++, + .main_r_in_tag = static_cast(AvmMemoryTag::FF), + .main_rwa = 0, + .main_rwb = 1, + .main_sel_mem_op_a = 1, + .main_sel_mem_op_b = 1, + .main_sel_q_kernel_output_lookup = 1, + .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), + .main_sel_resolve_ind_addr_b = FF(static_cast(write_b.is_indirect)), + .main_tag_err = static_cast(!tag_match), + .main_w_in_tag = static_cast(AvmMemoryTag::U8), + }; +} - // Storing from Ia - mem_trace_builder.write_into_memory( - call_ptr, clk, IntermRegister::IA, mem_addr_a, ia, AvmMemoryTag::U0, AvmMemoryTag::FF); +/** + * @brief Create a kernel output opcode with set metadata output object + * + * Used for writing output opcode where one value is written and comes from a hint + * {sload} + * + * @param indirect - Perform indirect memory resolution + * @param clk - The trace clk + * @param data_offset - The offset of the main value to output + * @param metadata_offset - The offset of the metadata (slot in the sload example) + * @return Row + */ +Row AvmTraceBuilder::create_kernel_output_opcode_with_set_value_from_hint(uint8_t indirect, + uint32_t clk, + uint32_t data_offset, + uint32_t metadata_offset) +{ + FF value = execution_hints.get_side_effect_hints().at(side_effect_counter); + // TODO: throw error if incorrect - if (copy_size - pos > 1) { - ib = calldata.at(cd_offset + pos + 1); - mem_op_b = 1; - mem_addr_b = direct_dst_offset + pos + 1; - rwb = 1; + auto [resolved_data, resolved_metadata] = unpack_indirects<2>(indirect, { data_offset, metadata_offset }); + auto write_a = constrained_write_to_memory( + call_ptr, clk, resolved_data, value, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IA); + auto read_b = constrained_read_from_memory( + call_ptr, clk, resolved_metadata, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IB); + bool tag_match = write_a.tag_match && read_b.tag_match; - // Storing from Ib - mem_trace_builder.write_into_memory( - call_ptr, clk, IntermRegister::IB, mem_addr_b, ib, AvmMemoryTag::U0, AvmMemoryTag::FF); - } + return Row{ + .main_clk = clk, + .main_ia = write_a.val, + .main_ib = read_b.val, + .main_ind_addr_a = FF(write_a.indirect_address), + .main_ind_addr_b = FF(read_b.indirect_address), + .main_internal_return_ptr = internal_return_ptr, + .main_mem_addr_a = FF(write_a.direct_address), + .main_mem_addr_b = FF(read_b.direct_address), + .main_pc = pc, // No PC increment here since we do it in the specific ops + .main_r_in_tag = static_cast(AvmMemoryTag::FF), + .main_rwa = 1, + .main_rwb = 0, + .main_sel_mem_op_a = 1, + .main_sel_mem_op_b = 1, + .main_sel_q_kernel_output_lookup = 1, + .main_sel_resolve_ind_addr_a = FF(static_cast(write_a.is_indirect)), + .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), + .main_tag_err = static_cast(!tag_match), + .main_w_in_tag = static_cast(AvmMemoryTag::FF), + }; +} - if (copy_size - pos > 2) { - ic = calldata.at(cd_offset + pos + 2); - mem_op_c = 1; - mem_addr_c = direct_dst_offset + pos + 2; - rwc = 1; +/************************************************************************************************** + * WORLD STATE + **************************************************************************************************/ - // Storing from Ic - mem_trace_builder.write_into_memory( - call_ptr, clk, IntermRegister::IC, mem_addr_c, ic, AvmMemoryTag::U0, AvmMemoryTag::FF); - } +void AvmTraceBuilder::op_sload(uint8_t indirect, uint32_t slot_offset, uint32_t size, uint32_t dest_offset) +{ + auto clk = static_cast(main_trace.size()) + 1; - // Constrain gas cost on the first row - if (pos == 0) { - gas_trace_builder.constrain_gas_lookup(clk, OpCode::CALLDATACOPY); - } + auto [resolved_slot, resolved_dest] = unpack_indirects<2>(indirect, { slot_offset, dest_offset }); + auto read_slot = constrained_read_from_memory( + call_ptr, clk, resolved_slot, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); - main_trace.push_back(Row{ + // Read the slot value that we will write hints to in a row + main_trace.push_back(Row{ + .main_clk = clk, + .main_ia = read_slot.val, + .main_ind_addr_a = FF(read_slot.indirect_address), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(read_slot.direct_address), + .main_pc = pc, // No PC increment here since this is the same opcode as the rows created below + .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), + .main_sel_mem_op_a = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(read_slot.is_indirect)), + .main_tag_err = FF(static_cast(!read_slot.tag_match)), + }); + clk++; + + AddressWithMode write_dst = resolved_dest; + // Loop over the size and write the hints to memory + for (uint32_t i = 0; i < size; i++) { + FF value = execution_hints.get_side_effect_hints().at(side_effect_counter); + + auto write_a = constrained_write_to_memory( + call_ptr, clk, write_dst, value, AvmMemoryTag::U0, AvmMemoryTag::FF, IntermRegister::IA); + + auto row = Row{ .main_clk = clk, - .main_call_ptr = call_ptr, - .main_ia = ia, - .main_ib = ib, - .main_ic = ic, - .main_ind_addr_a = indirect_flag ? FF(dst_offset) : FF(0), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(mem_addr_a), - .main_mem_addr_b = FF(mem_addr_b), - .main_mem_addr_c = FF(mem_addr_c), - .main_pc = FF(pc), - .main_rwa = FF(rwa), - .main_rwb = FF(rwb), - .main_rwc = FF(rwc), - .main_sel_mem_op_a = FF(mem_op_a), - .main_sel_mem_op_activate_gas = FF(static_cast( - pos == 0)), // TODO: remove in the long term. This activate gas only for the first row. - .main_sel_mem_op_b = FF(mem_op_b), - .main_sel_mem_op_c = FF(mem_op_c), - .main_sel_resolve_ind_addr_a = FF(static_cast(indirect_flag)), - .main_tag_err = FF(static_cast(!tag_match)), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), - }); + .main_ia = value, + .main_ib = read_slot.val + i, // slot increments each time + .main_ind_addr_a = write_a.indirect_address, + .main_internal_return_ptr = internal_return_ptr, + .main_mem_addr_a = write_a.direct_address, // direct address incremented at end of the loop + .main_pc = pc, // No PC increment here since this is the same opcode for all loop iterations + .main_rwa = 1, + .main_sel_mem_op_a = 1, + .main_sel_op_sload = FF(1), + .main_sel_q_kernel_output_lookup = 1, + .main_sel_resolve_ind_addr_a = FF(static_cast(write_a.is_indirect)), + .main_tag_err = FF(static_cast(!write_a.tag_match)), + .main_w_in_tag = static_cast(AvmMemoryTag::FF), + }; - if (copy_size - pos > 2) { // Guard to prevent overflow if copy_size is close to uint32_t maximum value. - pos += 3; - } else { - pos = copy_size; - } - } + // Output storage read to kernel outputs (performs lookup) + // Tuples of (slot, value) in the kernel lookup + kernel_trace_builder.op_sload(clk, side_effect_counter, row.main_ib, row.main_ia); - pc++; -} + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::SLOAD); -// Credit to SEAN for coming up with this revert opcode -std::vector AvmTraceBuilder::op_revert(uint8_t indirect, uint32_t ret_offset, uint32_t ret_size) -{ - return op_return(indirect, ret_offset, ret_size); + main_trace.push_back(row); + + debug("sload side-effect cnt: ", side_effect_counter); + side_effect_counter++; + clk++; + + // After the first loop, all future write destinations are direct, increment the direct address + write_dst = AddressWithMode{ AddressingMode::DIRECT, write_a.direct_address + 1 }; + } + pc++; } -/** - * @brief RETURN opcode with direct and indirect memory access, i.e., - * direct: return(M[ret_offset:ret_offset+ret_size]) - * indirect: return(M[M[ret_offset]:M[ret_offset]+ret_size]) - * Simplified version with exclusively memory load operations into - * intermediate registers and then values are copied to the returned vector. - * TODO: taking care of flagging this row as the last one? Special STOP flag? - * TODO: error handling if ret_offset + ret_size > 2^32 which would lead to - * out-of-bound memory read. - * - * @param indirect A byte encoding information about indirect/direct memory access. - * @param ret_offset The starting index of the memory region to be returned. - * @param ret_size The number of elements to be returned. - * @return The returned memory region as a std::vector. - */ -std::vector AvmTraceBuilder::op_return(uint8_t indirect, uint32_t ret_offset, uint32_t ret_size) +void AvmTraceBuilder::op_sstore(uint8_t indirect, uint32_t src_offset, uint32_t size, uint32_t slot_offset) { - if (ret_size == 0) { - halt(); - return {}; - } + auto clk = static_cast(main_trace.size()) + 1; - // We parallelize loading memory operations in chunk of 3, i.e., 1 per intermediate register. - // The variable pos is an index pointing to the first storing operation (pertaining to intermediate - // register Ia) relative to ret_offset: - // ret_offset + pos: Ia memory load operation - // ret_offset + pos + 1: Ib memory load operation - // ret_offset + pos + 2: Ic memory load operation - // In indirect mode, ret_offset is first resolved by the first indirect load. + auto [resolved_src, resolved_slot] = unpack_indirects<2>(indirect, { src_offset, slot_offset }); - uint32_t pos = 0; - std::vector returnMem; - uint32_t direct_ret_offset = ret_offset; // Will be overwritten in indirect mode. + auto read_slot = constrained_read_from_memory( + call_ptr, clk, resolved_slot, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IA); - while (pos < ret_size) { - FF ib(0); - FF ic(0); - uint32_t mem_op_b(0); - uint32_t mem_op_c(0); - uint32_t mem_addr_b(0); - uint32_t mem_addr_c(0); - auto clk = static_cast(main_trace.size()) + 1; + main_trace.push_back(Row{ + .main_clk = clk, + .main_ia = read_slot.val, + .main_ind_addr_a = FF(read_slot.indirect_address), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(read_slot.direct_address), + .main_pc = pc, // No PC increment here since this is the same opcode as the rows created below + .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), + .main_sel_mem_op_a = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(read_slot.is_indirect)), + .main_tag_err = FF(static_cast(!read_slot.tag_match)), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), + }); + clk++; - uint32_t mem_op_a(1); - bool indirect_flag = false; - bool tag_match = true; + AddressWithMode read_src = resolved_src; - if (pos == 0 && is_operand_indirect(indirect, 0)) { - indirect_flag = true; - auto ind_read = mem_trace_builder.indirect_read_and_load_from_memory( - call_ptr, clk, IndirectRegister::IND_A, ret_offset); - direct_ret_offset = uint32_t(ind_read.val); - tag_match = ind_read.tag_match; - } + // This loop reads a _size_ number of elements from memory and places them into a tuple of (ele, slot) + // in the kernel lookup. + for (uint32_t i = 0; i < size; i++) { + auto read_a = constrained_read_from_memory( + call_ptr, clk, read_src, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); - uint32_t mem_addr_a = direct_ret_offset + pos; + Row row = Row{ + .main_clk = clk, + .main_ia = read_a.val, + .main_ib = read_slot.val + i, // slot increments each time + .main_ind_addr_a = read_a.indirect_address, + .main_internal_return_ptr = internal_return_ptr, + .main_mem_addr_a = read_a.direct_address, // direct address incremented at end of the loop + .main_pc = pc, + .main_r_in_tag = static_cast(AvmMemoryTag::FF), + .main_sel_mem_op_a = 1, + .main_sel_q_kernel_output_lookup = 1, + .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), + .main_tag_err = FF(static_cast(!read_a.tag_match)), + }; + row.main_sel_op_sstore = FF(1); + kernel_trace_builder.op_sstore(clk, side_effect_counter, row.main_ib, row.main_ia); - // Reading and loading to Ia - auto read_a = mem_trace_builder.read_and_load_from_memory( - call_ptr, clk, IntermRegister::IA, mem_addr_a, AvmMemoryTag::FF, AvmMemoryTag::FF); - tag_match = tag_match && read_a.tag_match; + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::SSTORE); - FF ia = read_a.val; - returnMem.push_back(ia); + main_trace.push_back(row); - if (ret_size - pos > 1) { - mem_op_b = 1; - mem_addr_b = direct_ret_offset + pos + 1; - - // Reading and loading to Ib - auto read_b = mem_trace_builder.read_and_load_from_memory( - call_ptr, clk, IntermRegister::IB, mem_addr_b, AvmMemoryTag::FF, AvmMemoryTag::FF); - tag_match = tag_match && read_b.tag_match; - ib = read_b.val; - returnMem.push_back(ib); - } + debug("sstore side-effect cnt: ", side_effect_counter); + side_effect_counter++; + clk++; + // All future reads are direct, increment the direct address + read_src = AddressWithMode{ AddressingMode::DIRECT, read_a.direct_address + 1 }; + } + pc++; +} - if (ret_size - pos > 2) { - mem_op_c = 1; - mem_addr_c = direct_ret_offset + pos + 2; +void AvmTraceBuilder::op_note_hash_exists(uint8_t indirect, uint32_t note_hash_offset, uint32_t dest_offset) +{ + auto const clk = static_cast(main_trace.size()) + 1; - // Reading and loading to Ic - auto read_c = mem_trace_builder.read_and_load_from_memory( - call_ptr, clk, IntermRegister::IC, mem_addr_c, AvmMemoryTag::FF, AvmMemoryTag::FF); - tag_match = tag_match && read_c.tag_match; - ic = read_c.val; - returnMem.push_back(ic); - } + Row row = + create_kernel_output_opcode_with_set_metadata_output_from_hint(indirect, clk, note_hash_offset, dest_offset); + kernel_trace_builder.op_note_hash_exists( + clk, side_effect_counter, row.main_ia, /*safe*/ static_cast(row.main_ib)); + row.main_sel_op_note_hash_exists = FF(1); - // Constrain gas cost on the first row - if (pos == 0) { - gas_trace_builder.constrain_gas_lookup(clk, OpCode::RETURN); - } + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::NOTEHASHEXISTS); - main_trace.push_back(Row{ - .main_clk = clk, - .main_call_ptr = call_ptr, - .main_ia = ia, - .main_ib = ib, - .main_ic = ic, - .main_ind_addr_a = indirect_flag ? FF(ret_offset) : FF(0), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(mem_addr_a), - .main_mem_addr_b = FF(mem_addr_b), - .main_mem_addr_c = FF(mem_addr_c), - .main_pc = FF(pc), - .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), - .main_sel_mem_op_a = FF(mem_op_a), - .main_sel_mem_op_activate_gas = FF(static_cast( - pos == 0)), // TODO: remove in the long term. This activate gas only for the first row. - .main_sel_mem_op_b = FF(mem_op_b), - .main_sel_mem_op_c = FF(mem_op_c), - .main_sel_op_halt = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(indirect_flag)), - .main_tag_err = FF(static_cast(!tag_match)), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), - }); + main_trace.push_back(row); - if (ret_size - pos > 2) { // Guard to prevent overflow if ret_size is close to uint32_t maximum value. - pos += 3; - } else { - pos = ret_size; - } - } - pc = UINT32_MAX; // This ensures that no subsequent opcode will be executed. - return returnMem; + debug("note_hash_exists side-effect cnt: ", side_effect_counter); + side_effect_counter++; } -/** - * @brief HALT opcode - * This opcode effectively stops program execution, and is used in the relation that - * ensures the program counter increments on each opcode. - * i.e. the program counter should freeze and the halt flag is set to 1. - */ -void AvmTraceBuilder::halt() +void AvmTraceBuilder::op_emit_note_hash(uint8_t indirect, uint32_t note_hash_offset) { - auto clk = main_trace.size() + 1; + auto const clk = static_cast(main_trace.size()) + 1; - main_trace.push_back(Row{ - .main_clk = clk, - .main_call_ptr = call_ptr, - .main_internal_return_ptr = FF(internal_return_ptr), - .main_pc = FF(pc), - .main_sel_op_halt = FF(1), - }); + Row row = create_kernel_output_opcode(indirect, clk, note_hash_offset); + kernel_trace_builder.op_emit_note_hash(clk, side_effect_counter, row.main_ia); + row.main_sel_op_emit_note_hash = FF(1); - pc = UINT32_MAX; // This ensures that no subsequent opcode will be executed. + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::EMITNOTEHASH); + + main_trace.push_back(row); + + debug("emit_note_hash side-effect cnt: ", side_effect_counter); + side_effect_counter++; } -void AvmTraceBuilder::execute_gasleft(OpCode opcode, uint8_t indirect, uint32_t dst_offset) +void AvmTraceBuilder::op_nullifier_exists(uint8_t indirect, uint32_t nullifier_offset, uint32_t dest_offset) { - assert(opcode == OpCode::L2GASLEFT || opcode == OpCode::DAGASLEFT); - - auto clk = static_cast(main_trace.size()) + 1; + auto const clk = static_cast(main_trace.size()) + 1; - auto [resolved_dst] = unpack_indirects<1>(indirect, { dst_offset }); + Row row = + create_kernel_output_opcode_with_set_metadata_output_from_hint(indirect, clk, nullifier_offset, dest_offset); + kernel_trace_builder.op_nullifier_exists( + clk, side_effect_counter, row.main_ia, /*safe*/ static_cast(row.main_ib)); + row.main_sel_op_nullifier_exists = FF(1); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, opcode); - - uint32_t gas_remaining = 0; - - if (opcode == OpCode::L2GASLEFT) { - gas_remaining = gas_trace_builder.get_l2_gas_left(); - } else { - gas_remaining = gas_trace_builder.get_da_gas_left(); - } - - // Write into memory from intermediate register ia. - // TODO: probably will be U32 in final version - auto write_dst = constrained_write_to_memory( - call_ptr, clk, resolved_dst, gas_remaining, AvmMemoryTag::U0, AvmMemoryTag::FF, IntermRegister::IA); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::NULLIFIEREXISTS); - main_trace.push_back(Row{ - .main_clk = clk, - .main_call_ptr = call_ptr, - .main_ia = gas_remaining, - .main_ind_addr_a = FF(write_dst.indirect_address), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(write_dst.direct_address), - .main_pc = FF(pc++), - .main_r_in_tag = FF(static_cast(AvmMemoryTag::U0)), - .main_rwa = FF(1), - .main_sel_mem_op_a = FF(1), - .main_sel_op_dagasleft = (opcode == OpCode::DAGASLEFT) ? FF(1) : FF(0), - .main_sel_op_l2gasleft = (opcode == OpCode::L2GASLEFT) ? FF(1) : FF(0), - .main_sel_resolve_ind_addr_a = FF(static_cast(is_operand_indirect(indirect, 0))), - .main_tag_err = FF(static_cast(!write_dst.tag_match)), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), // TODO: probably will be U32 in final version - // Should the circuit (pil) constrain U32? - }); -} + main_trace.push_back(row); -void AvmTraceBuilder::op_l2gasleft(uint8_t indirect, uint32_t dst_offset) -{ - execute_gasleft(OpCode::L2GASLEFT, indirect, dst_offset); + debug("nullifier_exists side-effect cnt: ", side_effect_counter); + side_effect_counter++; } -void AvmTraceBuilder::op_dagasleft(uint8_t indirect, uint32_t dst_offset) +void AvmTraceBuilder::op_emit_nullifier(uint8_t indirect, uint32_t nullifier_offset) { - execute_gasleft(OpCode::DAGASLEFT, indirect, dst_offset); -} + auto const clk = static_cast(main_trace.size()) + 1; -/** - * @brief JUMP OPCODE - * Jumps to a new `jmp_dest` - * This function must: - * - Set the next program counter to the provided `jmp_dest`. - * - * @param jmp_dest - The destination to jump to - */ -void AvmTraceBuilder::op_jump(uint32_t jmp_dest) -{ - auto clk = static_cast(main_trace.size()) + 1; + Row row = create_kernel_output_opcode(indirect, clk, nullifier_offset); + kernel_trace_builder.op_emit_nullifier(clk, side_effect_counter, row.main_ia); + row.main_sel_op_emit_nullifier = FF(1); // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::JUMP); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::EMITNULLIFIER); - main_trace.push_back(Row{ - .main_clk = clk, - .main_call_ptr = call_ptr, - .main_ia = FF(jmp_dest), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_pc = FF(pc), - .main_sel_op_jump = FF(1), - }); + main_trace.push_back(row); - // Adjust parameters for the next row - pc = jmp_dest; + debug("emit_nullifier side-effect cnt: ", side_effect_counter); + side_effect_counter++; } -/** - * @brief JUMPI OPCODE - * Jumps to a new `jmp_dest` if M[cond_offset] > 0 - * This function sets the next program counter to the provided `jmp_dest` if condition > 0. - * Otherwise, program counter is incremented. - * - * @param indirect A byte encoding information about indirect/direct memory access. - * @param jmp_dest The destination to jump to - * @param cond_offset Offset of the condition - */ -void AvmTraceBuilder::op_jumpi(uint8_t indirect, uint32_t jmp_dest, uint32_t cond_offset) +void AvmTraceBuilder::op_l1_to_l2_msg_exists(uint8_t indirect, uint32_t log_offset, uint32_t dest_offset) { - auto clk = static_cast(main_trace.size()) + 1; + auto const clk = static_cast(main_trace.size()) + 1; - bool tag_match = true; - uint32_t direct_cond_offset = cond_offset; + Row row = create_kernel_output_opcode_with_set_metadata_output_from_hint(indirect, clk, log_offset, dest_offset); + kernel_trace_builder.op_l1_to_l2_msg_exists( + clk, side_effect_counter, row.main_ia, /*safe*/ static_cast(row.main_ib)); + row.main_sel_op_l1_to_l2_msg_exists = FF(1); - bool indirect_cond_flag = is_operand_indirect(indirect, 0); + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::L1TOL2MSGEXISTS); - if (indirect_cond_flag) { - auto read_ind_d = - mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_D, cond_offset); - direct_cond_offset = uint32_t(read_ind_d.val); - tag_match = tag_match && read_ind_d.tag_match; - } + main_trace.push_back(row); - // Specific JUMPI loading of conditional value into intermediate register id without any tag constraint. - auto read_d = mem_trace_builder.read_and_load_jumpi_opcode(call_ptr, clk, direct_cond_offset); + debug("l1_to_l2_msg_exists side-effect cnt: ", side_effect_counter); + side_effect_counter++; +} - const bool id_zero = read_d.val == 0; - FF const inv = !id_zero ? read_d.val.invert() : 1; - uint32_t next_pc = !id_zero ? jmp_dest : pc + 1; +void AvmTraceBuilder::op_get_contract_instance(uint8_t indirect, uint32_t address_offset, uint32_t dst_offset) +{ + auto clk = static_cast(main_trace.size()) + 1; + + auto [resolved_address_offset, resolved_dst_offset] = unpack_indirects<2>(indirect, { address_offset, dst_offset }); + auto read_address = constrained_read_from_memory( + call_ptr, clk, resolved_address_offset, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); + bool tag_match = read_address.tag_match; // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::JUMPI); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::GETCONTRACTINSTANCE); main_trace.push_back(Row{ .main_clk = clk, - .main_call_ptr = call_ptr, - .main_ia = FF(next_pc), - .main_id = read_d.val, - .main_id_zero = static_cast(id_zero), - .main_ind_addr_d = indirect_cond_flag ? cond_offset : 0, + .main_ia = read_address.val, + .main_ind_addr_a = FF(read_address.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), - .main_inv = inv, - .main_mem_addr_d = direct_cond_offset, - .main_pc = FF(pc), - .main_r_in_tag = static_cast(read_d.tag), - .main_sel_mem_op_d = 1, - .main_sel_op_jumpi = FF(1), - .main_sel_resolve_ind_addr_d = static_cast(indirect_cond_flag), - .main_tag_err = static_cast(!tag_match), - .main_w_in_tag = static_cast(read_d.tag), - }); - - // Adjust parameters for the next row - pc = next_pc; -} - -/** - * @brief INTERNAL_CALL OPCODE - * This opcode effectively jumps to a new `jmp_dest` and stores the return program counter - * (current program counter + 1) onto a call stack. - * This function must: - * - Set the next program counter to the provided `jmp_dest`. - * - Store the current `pc` + 1 onto the call stack (emulated in memory) - * - Increment the return stack pointer (a pointer to where the call stack is in memory) - * - * Note: We use intermediate register to perform memory storage operations. - * - * @param jmp_dest - The destination to jump to - */ -void AvmTraceBuilder::op_internal_call(uint32_t jmp_dest) -{ - auto clk = static_cast(main_trace.size()) + 1; - - // We store the next instruction as the return location - mem_trace_builder.write_into_memory(INTERNAL_CALL_SPACE_ID, - clk, - IntermRegister::IB, - internal_return_ptr, - FF(pc + 1), - AvmMemoryTag::U0, - AvmMemoryTag::U32); - - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::INTERNALCALL); - - main_trace.push_back(Row{ - .main_clk = clk, - .main_call_ptr = call_ptr, - .main_ia = FF(jmp_dest), - .main_ib = FF(pc + 1), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_b = FF(internal_return_ptr), - .main_pc = FF(pc), - .main_rwb = FF(1), - .main_sel_mem_op_b = FF(1), - .main_sel_op_internal_call = FF(1), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::U32)), - }); - - // Adjust parameters for the next row - pc = jmp_dest; - internal_return_ptr++; -} - -/** - * @brief INTERNAL_RETURN OPCODE - * The opcode returns from an internal call. - * This function must: - * - Read the return location from the internal_return_ptr - * - Set the next program counter to the return location - * - Decrement the return stack pointer - * - * TODO(https://github.com/AztecProtocol/aztec-packages/issues/3740): This function MUST come after a call - * instruction. - */ -void AvmTraceBuilder::op_internal_return() -{ - auto clk = static_cast(main_trace.size()) + 1; - - // Internal return pointer is decremented - // We want to load the value pointed by the internal pointer - auto read_a = mem_trace_builder.read_and_load_from_memory( - INTERNAL_CALL_SPACE_ID, clk, IntermRegister::IA, internal_return_ptr - 1, AvmMemoryTag::U32, AvmMemoryTag::U0); - - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::INTERNALRETURN); - - main_trace.push_back(Row{ - .main_clk = clk, - .main_call_ptr = call_ptr, - .main_ia = read_a.val, - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(internal_return_ptr - 1), - .main_pc = pc, - .main_r_in_tag = FF(static_cast(AvmMemoryTag::U32)), - .main_rwa = FF(0), + .main_mem_addr_a = FF(read_address.direct_address), + .main_pc = FF(pc++), + .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), .main_sel_mem_op_a = FF(1), - .main_sel_op_internal_return = FF(1), - .main_tag_err = FF(static_cast(!read_a.tag_match)), + .main_sel_mem_op_activate_gas = FF(1), // TODO: remove in the long term + .main_sel_op_get_contract_instance = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(read_address.is_indirect)), + .main_tag_err = FF(static_cast(!tag_match)), }); + clk++; + // Read the contract instance + ContractInstanceHint contract_instance = execution_hints.contract_instance_hints.at(read_address.val); - pc = uint32_t(read_a.val); - internal_return_ptr--; -} - -// TODO(ilyas: #6383): Temporary way to bulk write slices -uint32_t AvmTraceBuilder::write_slice_to_memory(uint8_t space_id, - uint32_t clk, - AddressWithMode addr, - AvmMemoryTag r_tag, - AvmMemoryTag w_tag, - FF internal_return_ptr, - std::vector const& slice) -{ - bool is_indirect = addr.mode == AddressingMode::INDIRECT; - auto dst_offset = addr.offset; - // We have 4 registers that we are able to use to write to memory within a single main trace row - auto register_order = std::array{ IntermRegister::IA, IntermRegister::IB, IntermRegister::IC, IntermRegister::ID }; - // If the slice size isnt a multiple of 4, we still need an extra row to write the remainder - uint32_t const num_main_rows = - static_cast(slice.size()) / 4 + static_cast(slice.size() % 4 != 0); - for (uint32_t i = 0; i < num_main_rows; i++) { - Row main_row{ - .main_clk = clk + i, - .main_internal_return_ptr = FF(internal_return_ptr), - .main_pc = FF(pc), - .main_r_in_tag = FF(static_cast(r_tag)), - .main_w_in_tag = FF(static_cast(w_tag)), - }; - // Write 4 values to memory in each_row - for (uint32_t j = 0; j < 4; j++) { - auto offset = i * 4 + j; - // If we exceed the slice size, we break - if (offset >= slice.size()) { - break; - } - MemOp mem_write; - if (is_indirect) { - mem_write = constrained_write_to_memory( - space_id, clk + i, addr, slice.at(offset), r_tag, w_tag, IntermRegister::IA); - // Ensure futures calls are direct - is_indirect = false; - dst_offset = mem_write.direct_address; - } else { - mem_trace_builder.write_into_memory( - space_id, clk + i, register_order[j], dst_offset + offset, slice.at(offset), r_tag, w_tag); - mem_write = MemOp{ - .is_indirect = false, - .indirect_address = 0, - .direct_address = dst_offset + offset, - .tag = w_tag, - .tag_match = true, - .val = slice.at(offset), - }; - } - // This looks a bit gross, but it is fine for now. - if (j == 0) { - main_row.main_ia = slice.at(offset); - main_row.main_ind_addr_a = FF(mem_write.indirect_address); - main_row.main_sel_resolve_ind_addr_a = FF(static_cast(mem_write.is_indirect)); - main_row.main_mem_addr_a = FF(mem_write.direct_address); - main_row.main_sel_mem_op_a = FF(1); - main_row.main_rwa = FF(1); - } else if (j == 1) { - main_row.main_ib = slice.at(offset); - main_row.main_mem_addr_b = FF(mem_write.direct_address); - main_row.main_sel_mem_op_b = FF(1); - main_row.main_rwb = FF(1); - } else if (j == 2) { - main_row.main_ic = slice.at(offset); - main_row.main_mem_addr_c = FF(mem_write.direct_address); - main_row.main_sel_mem_op_c = FF(1); - main_row.main_rwc = FF(1); - } else { - main_row.main_id = slice.at(offset); - main_row.main_mem_addr_d = FF(mem_write.direct_address); - main_row.main_sel_mem_op_d = FF(1); - main_row.main_rwd = FF(1); - } - } - main_trace.emplace_back(main_row); - } - return num_main_rows; -} + // NOTE: we don't write the first entry (the contract instance's address/key) to memory + std::vector contract_instance_vec = { contract_instance.instance_found_in_address, + contract_instance.salt, + contract_instance.deployer_addr, + contract_instance.contract_class_id, + contract_instance.initialisation_hash, + contract_instance.public_key_hash }; + write_slice_to_memory(call_ptr, + clk, + resolved_dst_offset, + AvmMemoryTag::U0, + AvmMemoryTag::FF, + internal_return_ptr, + contract_instance_vec); -template std::array vec_to_arr(std::vector const& vec) -{ - std::array arr; - ASSERT(T == vec.size()); - for (size_t i = 0; i < T; i++) { - arr[i] = vec[i]; - } - return arr; + debug("contract_instance cnt: ", side_effect_counter); + side_effect_counter++; } -// TODO(ilyas: #6383): Temporary way to bulk read slices -template -uint32_t AvmTraceBuilder::read_slice_to_memory(uint8_t space_id, - uint32_t clk, - AddressWithMode addr, - AvmMemoryTag r_tag, - AvmMemoryTag w_tag, - FF internal_return_ptr, - size_t slice_len, - std::vector& slice) -{ - // If the mem_op is indirect, it goes into register A - bool is_indirect = addr.mode == AddressingMode::INDIRECT; - auto src_offset = addr.offset; - // We have 4 registers that we are able to use to read from memory within a single main trace row - auto register_order = std::array{ IntermRegister::IA, IntermRegister::IB, IntermRegister::IC, IntermRegister::ID }; - // If the slice size isnt a multiple of 4, we still need an extra row to write the remainder - uint32_t const num_main_rows = static_cast(slice_len) / 4 + static_cast(slice_len % 4 != 0); - for (uint32_t i = 0; i < num_main_rows; i++) { - Row main_row{ - .main_clk = clk + i, - .main_internal_return_ptr = FF(internal_return_ptr), - .main_pc = FF(pc), - .main_r_in_tag = FF(static_cast(r_tag)), - .main_w_in_tag = FF(static_cast(w_tag)), - }; - // Write 4 values to memory in each_row - for (uint32_t j = 0; j < 4; j++) { - auto offset = i * 4 + j; - // If we exceed the slice size, we break - if (offset >= slice_len) { - break; - } - MemOp mem_read; - if (is_indirect) { - // If the first address is indirect we read it into register A, this can only happen once per slice read - mem_read = constrained_read_from_memory(space_id, clk + i, addr, r_tag, w_tag, IntermRegister::IA); - // Set this to false for the rest of the reads - is_indirect = false; - src_offset = mem_read.direct_address; - } else { - auto mem_load = mem_trace_builder.read_and_load_from_memory( - space_id, clk + i, register_order[j], src_offset + offset, r_tag, w_tag); - mem_read = MemOp{ - .is_indirect = false, - .indirect_address = 0, - .direct_address = src_offset + offset, - .tag = r_tag, - .tag_match = mem_load.tag_match, - .val = MEM(mem_load.val), - }; - } - slice.emplace_back(MEM(mem_read.val)); - // This looks a bit gross, but it is fine for now. - if (j == 0) { - main_row.main_ia = slice.at(offset); - main_row.main_ind_addr_a = FF(mem_read.indirect_address); - main_row.main_sel_resolve_ind_addr_a = FF(static_cast(mem_read.is_indirect)); - main_row.main_mem_addr_a = FF(mem_read.direct_address); - main_row.main_sel_mem_op_a = FF(1); - main_row.main_tag_err = FF(static_cast(!mem_read.tag_match)); - } else if (j == 1) { - main_row.main_ib = slice.at(offset); - main_row.main_mem_addr_b = FF(mem_read.direct_address); - main_row.main_sel_mem_op_b = FF(1); - main_row.main_tag_err = FF(static_cast(!mem_read.tag_match)); - } else if (j == 2) { - main_row.main_ic = slice.at(offset); - main_row.main_mem_addr_c = FF(mem_read.direct_address); - main_row.main_sel_mem_op_c = FF(1); - main_row.main_tag_err = FF(static_cast(!mem_read.tag_match)); - } else { - main_row.main_id = slice.at(offset); - main_row.main_mem_addr_d = FF(mem_read.direct_address); - main_row.main_sel_mem_op_d = FF(1); - main_row.main_tag_err = FF(static_cast(!mem_read.tag_match)); - } - } - main_trace.emplace_back(main_row); - } - return num_main_rows; + +/************************************************************************************************** + * ACCRUED SUBSTATE + **************************************************************************************************/ + +void AvmTraceBuilder::op_emit_unencrypted_log(uint8_t indirect, + uint32_t log_offset, + [[maybe_unused]] uint32_t log_size_offset) +{ + auto const clk = static_cast(main_trace.size()) + 1; + + // FIXME: read (and constrain) log_size_offset + // FIXME: we need to constrain the log_size_offset mem read (and tag check), not just one field! + Row row = create_kernel_output_opcode(indirect, clk, log_offset); + kernel_trace_builder.op_emit_unencrypted_log(clk, side_effect_counter, row.main_ia); + row.main_sel_op_emit_unencrypted_log = FF(1); + + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::EMITUNENCRYPTEDLOG); + + main_trace.push_back(row); + + debug("emit_unencrypted_log side-effect cnt: ", side_effect_counter); + side_effect_counter++; +} + +void AvmTraceBuilder::op_emit_l2_to_l1_msg(uint8_t indirect, uint32_t recipient_offset, uint32_t content_offset) +{ + auto const clk = static_cast(main_trace.size()) + 1; + + // Note: unorthadox order - as seen in L2ToL1Message struct in TS + Row row = create_kernel_output_opcode_with_metadata( + indirect, clk, content_offset, AvmMemoryTag::FF, recipient_offset, AvmMemoryTag::FF); + kernel_trace_builder.op_emit_l2_to_l1_msg(clk, side_effect_counter, row.main_ia, row.main_ib); + row.main_sel_op_emit_l2_to_l1_msg = FF(1); + + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::SENDL2TOL1MSG); + + main_trace.push_back(row); + + debug("emit_l2_to_l1_msg side-effect cnt: ", side_effect_counter); + side_effect_counter++; } +/************************************************************************************************** + * CONTROL FLOW - CONTRACT CALLS + **************************************************************************************************/ + /** * @brief External Call with direct or indirect memory access. * @@ -2666,250 +2817,153 @@ void AvmTraceBuilder::op_call(uint8_t indirect, side_effect_counter = static_cast(hint.end_side_effect_counter); } -void AvmTraceBuilder::op_get_contract_instance(uint8_t indirect, uint32_t address_offset, uint32_t dst_offset) -{ - auto clk = static_cast(main_trace.size()) + 1; - - auto [resolved_address_offset, resolved_dst_offset] = unpack_indirects<2>(indirect, { address_offset, dst_offset }); - auto read_address = constrained_read_from_memory( - call_ptr, clk, resolved_address_offset, AvmMemoryTag::FF, AvmMemoryTag::U0, IntermRegister::IA); - bool tag_match = read_address.tag_match; - - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::GETCONTRACTINSTANCE); - - main_trace.push_back(Row{ - .main_clk = clk, - .main_ia = read_address.val, - .main_ind_addr_a = FF(read_address.indirect_address), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(read_address.direct_address), - .main_pc = FF(pc++), - .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), - .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_activate_gas = FF(1), // TODO: remove in the long term - .main_sel_op_get_contract_instance = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(read_address.is_indirect)), - .main_tag_err = FF(static_cast(!tag_match)), - }); - clk++; - // Read the contract instance - ContractInstanceHint contract_instance = execution_hints.contract_instance_hints.at(read_address.val); - - // NOTE: we don't write the first entry (the contract instance's address/key) to memory - std::vector contract_instance_vec = { contract_instance.instance_found_in_address, - contract_instance.salt, - contract_instance.deployer_addr, - contract_instance.contract_class_id, - contract_instance.initialisation_hash, - contract_instance.public_key_hash }; - write_slice_to_memory(call_ptr, - clk, - resolved_dst_offset, - AvmMemoryTag::U0, - AvmMemoryTag::FF, - internal_return_ptr, - contract_instance_vec); - - debug("contract_instance cnt: ", side_effect_counter); - side_effect_counter++; -} - /** - * @brief To_Radix_LE with direct or indirect memory access. + * @brief RETURN opcode with direct and indirect memory access, i.e., + * direct: return(M[ret_offset:ret_offset+ret_size]) + * indirect: return(M[M[ret_offset]:M[ret_offset]+ret_size]) + * Simplified version with exclusively memory load operations into + * intermediate registers and then values are copied to the returned vector. + * TODO: taking care of flagging this row as the last one? Special STOP flag? + * TODO: error handling if ret_offset + ret_size > 2^32 which would lead to + * out-of-bound memory read. * * @param indirect A byte encoding information about indirect/direct memory access. - * @param src_offset An index in memory pointing to the input of the To_Radix_LE conversion. - * @param dst_offset An index in memory pointing to the output of the To_Radix_LE conversion. - * @param radix A strict upper bound of each converted limb, i.e., 0 <= limb < radix. - * @param num_limbs The number of limbs to the value into. + * @param ret_offset The starting index of the memory region to be returned. + * @param ret_size The number of elements to be returned. + * @return The returned memory region as a std::vector. */ -void AvmTraceBuilder::op_to_radix_le( - uint8_t indirect, uint32_t src_offset, uint32_t dst_offset, uint32_t radix, uint32_t num_limbs) +std::vector AvmTraceBuilder::op_return(uint8_t indirect, uint32_t ret_offset, uint32_t ret_size) { - auto clk = static_cast(main_trace.size()) + 1; - auto [resolved_src_offset, resolved_dst_offset] = unpack_indirects<2>(indirect, { src_offset, dst_offset }); - - auto read_src = constrained_read_from_memory( - call_ptr, clk, resolved_src_offset, AvmMemoryTag::FF, AvmMemoryTag::U8, IntermRegister::IA); - - auto read_dst = constrained_read_from_memory( - call_ptr, clk, resolved_dst_offset, AvmMemoryTag::FF, AvmMemoryTag::U8, IntermRegister::IB); - - FF input = read_src.val; - - // In case of a memory tag error, we do not perform the computation. - // Therefore, we do not create any entry in gadget table and return a vector of 0 - std::vector res = read_src.tag_match - ? conversion_trace_builder.op_to_radix_le(input, radix, num_limbs, clk) - : std::vector(num_limbs, 0); - - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::TORADIXLE); - - // This is the row that contains the selector to trigger the sel_op_radix_le - // In this row, we read the input value and the destination address into register A and B respectively - main_trace.push_back(Row{ - .main_clk = clk, - .main_call_ptr = call_ptr, - .main_ia = input, - .main_ib = read_dst.val, - .main_ic = radix, - .main_id = num_limbs, - .main_ind_addr_a = read_src.indirect_address, - .main_ind_addr_b = read_dst.indirect_address, - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = read_src.direct_address, - .main_mem_addr_b = read_dst.direct_address, - .main_pc = FF(pc++), - .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), - .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_b = FF(1), - .main_sel_op_radix_le = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(read_src.is_indirect)), - .main_sel_resolve_ind_addr_b = FF(static_cast(read_dst.is_indirect)), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::U8)), - }); - // Increment the clock so we dont write at the same clock cycle - // Instead we temporarily encode the writes into the subsequent rows of the main trace - clk++; - - // MemTrace, write into memory value b from intermediate register ib. - std::vector ff_res = {}; - ff_res.reserve(res.size()); - for (auto const& limb : res) { - ff_res.emplace_back(limb); + if (ret_size == 0) { + halt(); + return {}; } - write_slice_to_memory( - call_ptr, clk, resolved_dst_offset, AvmMemoryTag::FF, AvmMemoryTag::U8, FF(internal_return_ptr), ff_res); -} - -/** - * @brief SHA256 Compression with direct or indirect memory access. - * - * @param indirect byte encoding information about indirect/direct memory access. - * @param h_init_offset An index in memory pointing to the first U32 value of the state array to be used in the next - * instance of sha256 compression. - * @param input_offset An index in memory pointing to the first U32 value of the input array to be used in the next - * instance of sha256 compression. - * @param output_offset An index in memory pointing to where the first U32 value of the output array should be - * stored. - */ -void AvmTraceBuilder::op_sha256_compression(uint8_t indirect, - uint32_t output_offset, - uint32_t h_init_offset, - uint32_t input_offset) -{ - // The clk plays a crucial role in this function as we attempt to write across multiple lines in the main trace. - auto clk = static_cast(main_trace.size()) + 1; - - // Resolve the indirect flags, the results of this function are used to determine the memory offsets - // that point to the starting memory addresses for the input and output values. - auto [resolved_h_init_offset, resolved_input_offset, resolved_output_offset] = - unpack_indirects<3>(indirect, { h_init_offset, input_offset, output_offset }); - auto read_a = constrained_read_from_memory( - call_ptr, clk, resolved_h_init_offset, AvmMemoryTag::U32, AvmMemoryTag::U0, IntermRegister::IA); - auto read_b = constrained_read_from_memory( - call_ptr, clk, resolved_input_offset, AvmMemoryTag::U32, AvmMemoryTag::U0, IntermRegister::IB); - bool tag_match = read_a.tag_match && read_b.tag_match; + // We parallelize loading memory operations in chunk of 3, i.e., 1 per intermediate register. + // The variable pos is an index pointing to the first storing operation (pertaining to intermediate + // register Ia) relative to ret_offset: + // ret_offset + pos: Ia memory load operation + // ret_offset + pos + 1: Ib memory load operation + // ret_offset + pos + 2: Ic memory load operation + // In indirect mode, ret_offset is first resolved by the first indirect load. - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::SHA256COMPRESSION); + uint32_t pos = 0; + std::vector returnMem; + uint32_t direct_ret_offset = ret_offset; // Will be overwritten in indirect mode. - // Since the above adds mem_reads in the mem_trace_builder at clk, we need to follow up resolving the reads in - // the main trace at the same clk cycle to preserve the cross-table permutation - // - // TODO<#6383>: We put the first value of each of the input, output (which is 0 at this point) and h_init arrays - // into the main trace at the intermediate registers simply for the permutation check, in the future this will - // change. - // Note: we could avoid output being zero if we loaded the input and state beforehand (with a new function that - // did not lay down constraints), but this is a simplification - main_trace.push_back(Row{ - .main_clk = clk, - .main_ia = read_a.val, // First element of state - .main_ib = read_b.val, // First element of input - .main_ind_addr_a = FF(read_a.indirect_address), - .main_ind_addr_b = FF(read_b.indirect_address), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(read_a.direct_address), - .main_mem_addr_b = FF(read_b.direct_address), - .main_pc = FF(pc++), - .main_r_in_tag = FF(static_cast(AvmMemoryTag::U32)), - .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_b = FF(1), - .main_sel_op_sha256 = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), - .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), - .main_tag_err = FF(static_cast(!tag_match)), - }); - // We store the current clk this main trace row occurred so that we can line up the sha256 gadget operation at - // the same clk later. - auto sha_op_clk = clk; - // We need to increment the clk - clk++; - // State array input is fixed to 256 bits - std::vector h_init_vec; - // Input for hash is expanded to 512 bits - std::vector input_vec; - // Read results are written to h_init array. - read_slice_to_memory(call_ptr, - clk, - resolved_h_init_offset, - AvmMemoryTag::U32, - AvmMemoryTag::U32, - FF(internal_return_ptr), - 8, - h_init_vec); + while (pos < ret_size) { + FF ib(0); + FF ic(0); + uint32_t mem_op_b(0); + uint32_t mem_op_c(0); + uint32_t mem_addr_b(0); + uint32_t mem_addr_c(0); + auto clk = static_cast(main_trace.size()) + 1; - // Increment the clock by 2 since (8 reads / 4 reads per row = 2) - clk += 2; - // Read results are written to input array - read_slice_to_memory(call_ptr, - clk, - resolved_input_offset, - AvmMemoryTag::U32, - AvmMemoryTag::U32, - FF(internal_return_ptr), - 16, - input_vec); - // Increment the clock by 4 since (16 / 4 = 4) - clk += 4; + uint32_t mem_op_a(1); + bool indirect_flag = false; + bool tag_match = true; - // Now that we have read all the values, we can perform the operation to get the resulting witness. - // Note: We use the sha_op_clk to ensure that the sha256 operation is performed at the same clock cycle as the - // main trace that has the selector - std::array h_init = vec_to_arr(h_init_vec); - std::array input = vec_to_arr(input_vec); + if (pos == 0 && is_operand_indirect(indirect, 0)) { + indirect_flag = true; + auto ind_read = mem_trace_builder.indirect_read_and_load_from_memory( + call_ptr, clk, IndirectRegister::IND_A, ret_offset); + direct_ret_offset = uint32_t(ind_read.val); + tag_match = ind_read.tag_match; + } - std::array result = sha256_trace_builder.sha256_compression(h_init, input, sha_op_clk); - // We convert the results to field elements here - std::vector ff_result; - for (uint32_t i = 0; i < 8; i++) { - ff_result.emplace_back(result[i]); + uint32_t mem_addr_a = direct_ret_offset + pos; + + // Reading and loading to Ia + auto read_a = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::IA, mem_addr_a, AvmMemoryTag::FF, AvmMemoryTag::FF); + tag_match = tag_match && read_a.tag_match; + + FF ia = read_a.val; + returnMem.push_back(ia); + + if (ret_size - pos > 1) { + mem_op_b = 1; + mem_addr_b = direct_ret_offset + pos + 1; + + // Reading and loading to Ib + auto read_b = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::IB, mem_addr_b, AvmMemoryTag::FF, AvmMemoryTag::FF); + tag_match = tag_match && read_b.tag_match; + ib = read_b.val; + returnMem.push_back(ib); + } + + if (ret_size - pos > 2) { + mem_op_c = 1; + mem_addr_c = direct_ret_offset + pos + 2; + + // Reading and loading to Ic + auto read_c = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::IC, mem_addr_c, AvmMemoryTag::FF, AvmMemoryTag::FF); + tag_match = tag_match && read_c.tag_match; + ic = read_c.val; + returnMem.push_back(ic); + } + + // Constrain gas cost on the first row + if (pos == 0) { + gas_trace_builder.constrain_gas_lookup(clk, OpCode::RETURN); + } + + main_trace.push_back(Row{ + .main_clk = clk, + .main_call_ptr = call_ptr, + .main_ia = ia, + .main_ib = ib, + .main_ic = ic, + .main_ind_addr_a = indirect_flag ? FF(ret_offset) : FF(0), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(mem_addr_a), + .main_mem_addr_b = FF(mem_addr_b), + .main_mem_addr_c = FF(mem_addr_c), + .main_pc = FF(pc), + .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), + .main_sel_mem_op_a = FF(mem_op_a), + .main_sel_mem_op_activate_gas = FF(static_cast( + pos == 0)), // TODO: remove in the long term. This activate gas only for the first row. + .main_sel_mem_op_b = FF(mem_op_b), + .main_sel_mem_op_c = FF(mem_op_c), + .main_sel_op_halt = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(indirect_flag)), + .main_tag_err = FF(static_cast(!tag_match)), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), + }); + + if (ret_size - pos > 2) { // Guard to prevent overflow if ret_size is close to uint32_t maximum value. + pos += 3; + } else { + pos = ret_size; + } } + pc = UINT32_MAX; // This ensures that no subsequent opcode will be executed. + return returnMem; +} - // Write the result to memory after - write_slice_to_memory(call_ptr, - clk, - resolved_output_offset, - AvmMemoryTag::U32, - AvmMemoryTag::U32, - FF(internal_return_ptr), - ff_result); +std::vector AvmTraceBuilder::op_revert(uint8_t indirect, uint32_t ret_offset, uint32_t ret_size) +{ + return op_return(indirect, ret_offset, ret_size); } +/************************************************************************************************** + * GADGETS + **************************************************************************************************/ + /** - * @brief SHA256 Hash with direct or indirect memory access. - * This function is temporary until we have transitioned to sha256Compression + * @brief Keccak with direct or indirect memory access. + * Keccak is TEMPORARY while we wait for the transition to keccakf1600, so we do the minimal to store the result * @param indirect byte encoding information about indirect/direct memory access. - * @param output_offset An index in memory pointing to where the first U32 value of the output array should be + * @param output_offset An index in memory pointing to where the first u8 value of the output array should be * stored. - * @param input_offset An index in memory pointing to the first U8 value of the state array to be used in the next - * instance of sha256. - * @param input_size_offset An index in memory pointing to the U32 value of the input size. + * @param input_offset An index in memory pointing to the first u8 value of the input array to be used + * @param input_size offset An index in memory pointing to the size of the input array. */ -void AvmTraceBuilder::op_sha256(uint8_t indirect, +void AvmTraceBuilder::op_keccak(uint8_t indirect, uint32_t output_offset, uint32_t input_offset, uint32_t input_size_offset) @@ -2918,23 +2972,25 @@ void AvmTraceBuilder::op_sha256(uint8_t indirect, auto [resolved_output_offset, resolved_input_offset, resolved_input_size_offset] = unpack_indirects<3>(indirect, { output_offset, input_offset, input_size_offset }); - gas_trace_builder.constrain_gas_lookup(clk, OpCode::SHA256); + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::KECCAK); + // Read the input length first auto input_length_read = constrained_read_from_memory( call_ptr, clk, resolved_input_size_offset, AvmMemoryTag::U32, AvmMemoryTag::U0, IntermRegister::IB); // Store the clock time that we will use to line up the gadget later - auto sha256_op_clk = clk; + auto keccak_op_clk = clk; main_trace.push_back(Row{ .main_clk = clk, .main_ib = input_length_read.val, // Message Length .main_ind_addr_b = FF(input_length_read.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_b = FF(input_length_read.direct_address), + .main_mem_addr_b = FF(input_length_read.direct_address), // length .main_pc = FF(pc++), .main_r_in_tag = FF(static_cast(AvmMemoryTag::U32)), .main_sel_mem_op_b = FF(1), - .main_sel_op_sha256 = FF(1), + .main_sel_op_keccak = FF(1), .main_sel_resolve_ind_addr_b = FF(static_cast(input_length_read.is_indirect)), .main_tag_err = FF(static_cast(!input_length_read.tag_match)), }); @@ -2942,25 +2998,27 @@ void AvmTraceBuilder::op_sha256(uint8_t indirect, std::vector input; input.reserve(uint32_t(input_length_read.val)); + // Read the slice length from memory uint32_t num_main_rows = read_slice_to_memory(call_ptr, clk, resolved_input_offset, AvmMemoryTag::U8, - AvmMemoryTag::U0, + AvmMemoryTag::U8, FF(internal_return_ptr), uint32_t(input_length_read.val), input); + clk += num_main_rows; - // - std::array result = sha256_trace_builder.sha256(input, sha256_op_clk); + std::array result = keccak_trace_builder.keccak(keccak_op_clk, input, uint32_t(input_length_read.val)); + // We convert the results to field elements here std::vector ff_result; for (uint32_t i = 0; i < 32; i++) { ff_result.emplace_back(result[i]); } // Write the result to memory after write_slice_to_memory( - call_ptr, clk, resolved_output_offset, AvmMemoryTag::U0, AvmMemoryTag::U8, FF(internal_return_ptr), ff_result); + call_ptr, clk, resolved_output_offset, AvmMemoryTag::U8, AvmMemoryTag::U8, FF(internal_return_ptr), ff_result); } /** @@ -3040,113 +3098,16 @@ void AvmTraceBuilder::op_poseidon2_permutation(uint8_t indirect, uint32_t input_ } /** - * @brief Keccakf1600 with direct or indirect memory access. - * This function temporarily has the same interface as the kecccak opcode for compatibility, when the keccak - * migration is complete (to keccakf1600) We will update this function call as we will not likely need - * input_size_offset - * @param indirect byte encoding information about indirect/direct memory access. - * @param output_offset An index in memory pointing to where the first u64 value of the output array should be - * stored. - * @param input_offset An index in memory pointing to the first u64 value of the input array to be used in the next - * instance of poseidon2 permutation. - * @param input_size offset An index in memory pointing to the size of the input array. Temporary while we maintain - * the same interface as keccak (this is fixed to 25) - */ -void AvmTraceBuilder::op_keccakf1600(uint8_t indirect, - uint32_t output_offset, - uint32_t input_offset, - uint32_t input_size_offset) -{ - // What happens if the input_size_offset is > 25 when the state is more that that? - auto clk = static_cast(main_trace.size()) + 1; - auto [resolved_output_offset, resolved_input_offset] = - unpack_indirects<2>(indirect, { output_offset, input_offset }); - auto input_read = constrained_read_from_memory( - call_ptr, clk, resolved_input_offset, AvmMemoryTag::U64, AvmMemoryTag::U0, IntermRegister::IA); - auto output_read = constrained_read_from_memory( - call_ptr, clk, resolved_output_offset, AvmMemoryTag::U64, AvmMemoryTag::U0, IntermRegister::IC); - bool tag_match = input_read.tag_match && output_read.tag_match; - - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::KECCAKF1600); - - main_trace.push_back(Row{ - .main_clk = clk, - .main_ia = input_read.val, // First element of input - .main_ic = output_read.val, // First element of output - .main_ind_addr_a = FF(input_read.indirect_address), - .main_ind_addr_c = FF(output_read.indirect_address), - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(input_read.direct_address), - .main_mem_addr_c = FF(output_read.direct_address), - .main_pc = FF(pc++), - .main_r_in_tag = FF(static_cast(AvmMemoryTag::U64)), - .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_c = FF(1), - .main_sel_op_keccak = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(input_read.is_indirect)), - .main_sel_resolve_ind_addr_c = FF(static_cast(output_read.is_indirect)), - .main_tag_err = FF(static_cast(!tag_match)), - }); - // We store the current clk this main trace row occurred so that we can line up the keccak gadget operation - // at the same clk later. - auto keccak_op_clk = clk; - // We need to increment the clk - clk++; - auto input_length_read = mem_trace_builder.read_and_load_from_memory( - call_ptr, clk, IntermRegister::IB, input_size_offset, AvmMemoryTag::U32, AvmMemoryTag::U0); - main_trace.push_back(Row{ - .main_clk = clk, - .main_ib = input_length_read.val, // Message Length - .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_b = FF(input_size_offset), // length - .main_pc = FF(pc), - .main_r_in_tag = FF(static_cast(AvmMemoryTag::U32)), - .main_sel_mem_op_b = FF(1), - .main_tag_err = FF(static_cast(!input_length_read.tag_match)), - }); - clk++; - // Array input is fixed to 1600 bits - std::vector input_vec; - // Read results are written to input array - uint32_t num_main_rows = read_slice_to_memory(call_ptr, - clk, - resolved_input_offset, - AvmMemoryTag::U64, - AvmMemoryTag::U0, - FF(internal_return_ptr), - 25, - input_vec); - - std::array input = vec_to_arr(input_vec); - // Increment the clock by 7 since (25 reads / 4 reads per row = 7) - clk += num_main_rows; - - // Now that we have read all the values, we can perform the operation to get the resulting witness. - // Note: We use the keccak_op_clk to ensure that the keccakf1600 operation is performed at the same clock cycle - // as the main trace that has the selector - std::array result = keccak_trace_builder.keccakf1600(keccak_op_clk, input); - // We convert the results to field elements here - std::vector ff_result; - for (uint32_t i = 0; i < 25; i++) { - ff_result.emplace_back(result[i]); - } - - // Write the result to memory after - write_slice_to_memory( - call_ptr, clk, resolved_output_offset, AvmMemoryTag::U0, AvmMemoryTag::U64, FF(internal_return_ptr), ff_result); -} - -/** - * @brief Keccak with direct or indirect memory access. - * Keccak is TEMPORARY while we wait for the transition to keccakf1600, so we do the minimal to store the result + * @brief SHA256 Hash with direct or indirect memory access. + * This function is temporary until we have transitioned to sha256Compression * @param indirect byte encoding information about indirect/direct memory access. - * @param output_offset An index in memory pointing to where the first u8 value of the output array should be + * @param output_offset An index in memory pointing to where the first U32 value of the output array should be * stored. - * @param input_offset An index in memory pointing to the first u8 value of the input array to be used - * @param input_size offset An index in memory pointing to the size of the input array. + * @param input_offset An index in memory pointing to the first U8 value of the state array to be used in the next + * instance of sha256. + * @param input_size_offset An index in memory pointing to the U32 value of the input size. */ -void AvmTraceBuilder::op_keccak(uint8_t indirect, +void AvmTraceBuilder::op_sha256(uint8_t indirect, uint32_t output_offset, uint32_t input_offset, uint32_t input_size_offset) @@ -3155,25 +3116,23 @@ void AvmTraceBuilder::op_keccak(uint8_t indirect, auto [resolved_output_offset, resolved_input_offset, resolved_input_size_offset] = unpack_indirects<3>(indirect, { output_offset, input_offset, input_size_offset }); - // Constrain gas cost - gas_trace_builder.constrain_gas_lookup(clk, OpCode::KECCAK); + gas_trace_builder.constrain_gas_lookup(clk, OpCode::SHA256); - // Read the input length first auto input_length_read = constrained_read_from_memory( call_ptr, clk, resolved_input_size_offset, AvmMemoryTag::U32, AvmMemoryTag::U0, IntermRegister::IB); // Store the clock time that we will use to line up the gadget later - auto keccak_op_clk = clk; + auto sha256_op_clk = clk; main_trace.push_back(Row{ .main_clk = clk, .main_ib = input_length_read.val, // Message Length .main_ind_addr_b = FF(input_length_read.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_b = FF(input_length_read.direct_address), // length + .main_mem_addr_b = FF(input_length_read.direct_address), .main_pc = FF(pc++), .main_r_in_tag = FF(static_cast(AvmMemoryTag::U32)), .main_sel_mem_op_b = FF(1), - .main_sel_op_keccak = FF(1), + .main_sel_op_sha256 = FF(1), .main_sel_resolve_ind_addr_b = FF(static_cast(input_length_read.is_indirect)), .main_tag_err = FF(static_cast(!input_length_read.tag_match)), }); @@ -3181,27 +3140,25 @@ void AvmTraceBuilder::op_keccak(uint8_t indirect, std::vector input; input.reserve(uint32_t(input_length_read.val)); - // Read the slice length from memory uint32_t num_main_rows = read_slice_to_memory(call_ptr, clk, resolved_input_offset, AvmMemoryTag::U8, - AvmMemoryTag::U8, + AvmMemoryTag::U0, FF(internal_return_ptr), uint32_t(input_length_read.val), input); - clk += num_main_rows; + // + std::array result = sha256_trace_builder.sha256(input, sha256_op_clk); - std::array result = keccak_trace_builder.keccak(keccak_op_clk, input, uint32_t(input_length_read.val)); - // We convert the results to field elements here std::vector ff_result; for (uint32_t i = 0; i < 32; i++) { ff_result.emplace_back(result[i]); } // Write the result to memory after write_slice_to_memory( - call_ptr, clk, resolved_output_offset, AvmMemoryTag::U8, AvmMemoryTag::U8, FF(internal_return_ptr), ff_result); + call_ptr, clk, resolved_output_offset, AvmMemoryTag::U0, AvmMemoryTag::U8, FF(internal_return_ptr), ff_result); } /** @@ -3417,6 +3374,7 @@ void AvmTraceBuilder::op_ec_add(uint8_t indirect, .main_w_in_tag = FF(static_cast(AvmMemoryTag::U8)), }); } + void AvmTraceBuilder::op_variable_msm(uint8_t indirect, uint32_t points_offset, uint32_t scalars_offset, @@ -3549,175 +3507,336 @@ void AvmTraceBuilder::op_variable_msm(uint8_t indirect, main_trace.push_back(Row{ .main_clk = clk, - .main_ia = result.x, - .main_ib = result.y, - .main_ind_addr_a = FF(write_x.indirect_address), + .main_ia = result.x, + .main_ib = result.y, + .main_ind_addr_a = FF(write_x.indirect_address), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(write_x.direct_address), + .main_mem_addr_b = FF(write_x.direct_address + 1), + .main_pc = FF(pc), + .main_rwa = FF(1), + .main_rwb = FF(1), + .main_sel_mem_op_a = FF(1), + .main_sel_mem_op_b = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(write_x.is_indirect)), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), + }); + clk++; + // Write the infinity + mem_trace_builder.write_into_memory(call_ptr, + clk, + IntermRegister::IA, + write_x.direct_address + 2, + result.is_point_at_infinity(), + AvmMemoryTag::U0, + AvmMemoryTag::U8); + main_trace.push_back(Row{ + .main_clk = clk, + .main_ia = static_cast(result.is_point_at_infinity()), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(write_x.direct_address + 2), + .main_pc = FF(pc), + .main_rwa = FF(1), + .main_sel_mem_op_a = FF(1), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::U8)), + }); + + pc++; +} + +/************************************************************************************************** + * CONVERSIONS + **************************************************************************************************/ + +/** + * @brief To_Radix_LE with direct or indirect memory access. + * + * @param indirect A byte encoding information about indirect/direct memory access. + * @param src_offset An index in memory pointing to the input of the To_Radix_LE conversion. + * @param dst_offset An index in memory pointing to the output of the To_Radix_LE conversion. + * @param radix A strict upper bound of each converted limb, i.e., 0 <= limb < radix. + * @param num_limbs The number of limbs to the value into. + */ +void AvmTraceBuilder::op_to_radix_le( + uint8_t indirect, uint32_t src_offset, uint32_t dst_offset, uint32_t radix, uint32_t num_limbs) +{ + auto clk = static_cast(main_trace.size()) + 1; + auto [resolved_src_offset, resolved_dst_offset] = unpack_indirects<2>(indirect, { src_offset, dst_offset }); + + auto read_src = constrained_read_from_memory( + call_ptr, clk, resolved_src_offset, AvmMemoryTag::FF, AvmMemoryTag::U8, IntermRegister::IA); + + auto read_dst = constrained_read_from_memory( + call_ptr, clk, resolved_dst_offset, AvmMemoryTag::FF, AvmMemoryTag::U8, IntermRegister::IB); + + FF input = read_src.val; + + // In case of a memory tag error, we do not perform the computation. + // Therefore, we do not create any entry in gadget table and return a vector of 0 + std::vector res = read_src.tag_match + ? conversion_trace_builder.op_to_radix_le(input, radix, num_limbs, clk) + : std::vector(num_limbs, 0); + + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::TORADIXLE); + + // This is the row that contains the selector to trigger the sel_op_radix_le + // In this row, we read the input value and the destination address into register A and B respectively + main_trace.push_back(Row{ + .main_clk = clk, + .main_call_ptr = call_ptr, + .main_ia = input, + .main_ib = read_dst.val, + .main_ic = radix, + .main_id = num_limbs, + .main_ind_addr_a = read_src.indirect_address, + .main_ind_addr_b = read_dst.indirect_address, + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = read_src.direct_address, + .main_mem_addr_b = read_dst.direct_address, + .main_pc = FF(pc++), + .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), + .main_sel_mem_op_a = FF(1), + .main_sel_mem_op_b = FF(1), + .main_sel_op_radix_le = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(read_src.is_indirect)), + .main_sel_resolve_ind_addr_b = FF(static_cast(read_dst.is_indirect)), + .main_w_in_tag = FF(static_cast(AvmMemoryTag::U8)), + }); + // Increment the clock so we dont write at the same clock cycle + // Instead we temporarily encode the writes into the subsequent rows of the main trace + clk++; + + // MemTrace, write into memory value b from intermediate register ib. + std::vector ff_res = {}; + ff_res.reserve(res.size()); + for (auto const& limb : res) { + ff_res.emplace_back(limb); + } + write_slice_to_memory( + call_ptr, clk, resolved_dst_offset, AvmMemoryTag::FF, AvmMemoryTag::U8, FF(internal_return_ptr), ff_res); +} + +/************************************************************************************************** + * FUTURE GADGETS -- pending changes in noir + **************************************************************************************************/ + +/** + * @brief SHA256 Compression with direct or indirect memory access. + * + * @param indirect byte encoding information about indirect/direct memory access. + * @param h_init_offset An index in memory pointing to the first U32 value of the state array to be used in the next + * instance of sha256 compression. + * @param input_offset An index in memory pointing to the first U32 value of the input array to be used in the next + * instance of sha256 compression. + * @param output_offset An index in memory pointing to where the first U32 value of the output array should be + * stored. + */ +void AvmTraceBuilder::op_sha256_compression(uint8_t indirect, + uint32_t output_offset, + uint32_t h_init_offset, + uint32_t input_offset) +{ + // The clk plays a crucial role in this function as we attempt to write across multiple lines in the main trace. + auto clk = static_cast(main_trace.size()) + 1; + + // Resolve the indirect flags, the results of this function are used to determine the memory offsets + // that point to the starting memory addresses for the input and output values. + auto [resolved_h_init_offset, resolved_input_offset, resolved_output_offset] = + unpack_indirects<3>(indirect, { h_init_offset, input_offset, output_offset }); + + auto read_a = constrained_read_from_memory( + call_ptr, clk, resolved_h_init_offset, AvmMemoryTag::U32, AvmMemoryTag::U0, IntermRegister::IA); + auto read_b = constrained_read_from_memory( + call_ptr, clk, resolved_input_offset, AvmMemoryTag::U32, AvmMemoryTag::U0, IntermRegister::IB); + bool tag_match = read_a.tag_match && read_b.tag_match; + + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::SHA256COMPRESSION); + + // Since the above adds mem_reads in the mem_trace_builder at clk, we need to follow up resolving the reads in + // the main trace at the same clk cycle to preserve the cross-table permutation + // + // TODO<#6383>: We put the first value of each of the input, output (which is 0 at this point) and h_init arrays + // into the main trace at the intermediate registers simply for the permutation check, in the future this will + // change. + // Note: we could avoid output being zero if we loaded the input and state beforehand (with a new function that + // did not lay down constraints), but this is a simplification + main_trace.push_back(Row{ + .main_clk = clk, + .main_ia = read_a.val, // First element of state + .main_ib = read_b.val, // First element of input + .main_ind_addr_a = FF(read_a.indirect_address), + .main_ind_addr_b = FF(read_b.indirect_address), + .main_internal_return_ptr = FF(internal_return_ptr), + .main_mem_addr_a = FF(read_a.direct_address), + .main_mem_addr_b = FF(read_b.direct_address), + .main_pc = FF(pc++), + .main_r_in_tag = FF(static_cast(AvmMemoryTag::U32)), + .main_sel_mem_op_a = FF(1), + .main_sel_mem_op_b = FF(1), + .main_sel_op_sha256 = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(read_a.is_indirect)), + .main_sel_resolve_ind_addr_b = FF(static_cast(read_b.is_indirect)), + .main_tag_err = FF(static_cast(!tag_match)), + }); + // We store the current clk this main trace row occurred so that we can line up the sha256 gadget operation at + // the same clk later. + auto sha_op_clk = clk; + // We need to increment the clk + clk++; + // State array input is fixed to 256 bits + std::vector h_init_vec; + // Input for hash is expanded to 512 bits + std::vector input_vec; + // Read results are written to h_init array. + read_slice_to_memory(call_ptr, + clk, + resolved_h_init_offset, + AvmMemoryTag::U32, + AvmMemoryTag::U32, + FF(internal_return_ptr), + 8, + h_init_vec); + + // Increment the clock by 2 since (8 reads / 4 reads per row = 2) + clk += 2; + // Read results are written to input array + read_slice_to_memory(call_ptr, + clk, + resolved_input_offset, + AvmMemoryTag::U32, + AvmMemoryTag::U32, + FF(internal_return_ptr), + 16, + input_vec); + // Increment the clock by 4 since (16 / 4 = 4) + clk += 4; + + // Now that we have read all the values, we can perform the operation to get the resulting witness. + // Note: We use the sha_op_clk to ensure that the sha256 operation is performed at the same clock cycle as the + // main trace that has the selector + std::array h_init = vec_to_arr(h_init_vec); + std::array input = vec_to_arr(input_vec); + + std::array result = sha256_trace_builder.sha256_compression(h_init, input, sha_op_clk); + // We convert the results to field elements here + std::vector ff_result; + for (uint32_t i = 0; i < 8; i++) { + ff_result.emplace_back(result[i]); + } + + // Write the result to memory after + write_slice_to_memory(call_ptr, + clk, + resolved_output_offset, + AvmMemoryTag::U32, + AvmMemoryTag::U32, + FF(internal_return_ptr), + ff_result); +} + +/** + * @brief Keccakf1600 with direct or indirect memory access. + * This function temporarily has the same interface as the kecccak opcode for compatibility, when the keccak + * migration is complete (to keccakf1600) We will update this function call as we will not likely need + * input_size_offset + * @param indirect byte encoding information about indirect/direct memory access. + * @param output_offset An index in memory pointing to where the first u64 value of the output array should be + * stored. + * @param input_offset An index in memory pointing to the first u64 value of the input array to be used in the next + * instance of poseidon2 permutation. + * @param input_size offset An index in memory pointing to the size of the input array. Temporary while we maintain + * the same interface as keccak (this is fixed to 25) + */ +void AvmTraceBuilder::op_keccakf1600(uint8_t indirect, + uint32_t output_offset, + uint32_t input_offset, + uint32_t input_size_offset) +{ + // What happens if the input_size_offset is > 25 when the state is more that that? + auto clk = static_cast(main_trace.size()) + 1; + auto [resolved_output_offset, resolved_input_offset] = + unpack_indirects<2>(indirect, { output_offset, input_offset }); + auto input_read = constrained_read_from_memory( + call_ptr, clk, resolved_input_offset, AvmMemoryTag::U64, AvmMemoryTag::U0, IntermRegister::IA); + auto output_read = constrained_read_from_memory( + call_ptr, clk, resolved_output_offset, AvmMemoryTag::U64, AvmMemoryTag::U0, IntermRegister::IC); + bool tag_match = input_read.tag_match && output_read.tag_match; + + // Constrain gas cost + gas_trace_builder.constrain_gas_lookup(clk, OpCode::KECCAKF1600); + + main_trace.push_back(Row{ + .main_clk = clk, + .main_ia = input_read.val, // First element of input + .main_ic = output_read.val, // First element of output + .main_ind_addr_a = FF(input_read.indirect_address), + .main_ind_addr_c = FF(output_read.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(write_x.direct_address), - .main_mem_addr_b = FF(write_x.direct_address + 1), - .main_pc = FF(pc), - .main_rwa = FF(1), - .main_rwb = FF(1), + .main_mem_addr_a = FF(input_read.direct_address), + .main_mem_addr_c = FF(output_read.direct_address), + .main_pc = FF(pc++), + .main_r_in_tag = FF(static_cast(AvmMemoryTag::U64)), .main_sel_mem_op_a = FF(1), - .main_sel_mem_op_b = FF(1), - .main_sel_resolve_ind_addr_a = FF(static_cast(write_x.is_indirect)), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), + .main_sel_mem_op_c = FF(1), + .main_sel_op_keccak = FF(1), + .main_sel_resolve_ind_addr_a = FF(static_cast(input_read.is_indirect)), + .main_sel_resolve_ind_addr_c = FF(static_cast(output_read.is_indirect)), + .main_tag_err = FF(static_cast(!tag_match)), }); + // We store the current clk this main trace row occurred so that we can line up the keccak gadget operation + // at the same clk later. + auto keccak_op_clk = clk; + // We need to increment the clk clk++; - // Write the infinity - mem_trace_builder.write_into_memory(call_ptr, - clk, - IntermRegister::IA, - write_x.direct_address + 2, - result.is_point_at_infinity(), - AvmMemoryTag::U0, - AvmMemoryTag::U8); + auto input_length_read = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::IB, input_size_offset, AvmMemoryTag::U32, AvmMemoryTag::U0); main_trace.push_back(Row{ .main_clk = clk, - .main_ia = static_cast(result.is_point_at_infinity()), + .main_ib = input_length_read.val, // Message Length .main_internal_return_ptr = FF(internal_return_ptr), - .main_mem_addr_a = FF(write_x.direct_address + 2), + .main_mem_addr_b = FF(input_size_offset), // length .main_pc = FF(pc), - .main_rwa = FF(1), - .main_sel_mem_op_a = FF(1), - .main_w_in_tag = FF(static_cast(AvmMemoryTag::U8)), + .main_r_in_tag = FF(static_cast(AvmMemoryTag::U32)), + .main_sel_mem_op_b = FF(1), + .main_tag_err = FF(static_cast(!input_length_read.tag_match)), }); + clk++; + // Array input is fixed to 1600 bits + std::vector input_vec; + // Read results are written to input array + uint32_t num_main_rows = read_slice_to_memory(call_ptr, + clk, + resolved_input_offset, + AvmMemoryTag::U64, + AvmMemoryTag::U0, + FF(internal_return_ptr), + 25, + input_vec); - pc++; -} -// Finalise Lookup Counts -// -// For log derivative lookups, we require a column that contains the number of times each lookup is consumed -// As we build the trace, we keep track of the reads made in a mapping, so that they can be applied to the -// counts column here -// -// NOTE: its coupled to pil - this is not the final iteration -void AvmTraceBuilder::finalise_mem_trace_lookup_counts() -{ - for (auto const& [clk, count] : mem_trace_builder.m_tag_err_lookup_counts) { - main_trace.at(clk).incl_main_tag_err_counts = count; - } -} - -namespace { -// WARNING: FOR TESTING ONLY -// Generates the minimal lookup table for the binary trace -uint32_t finalize_bin_trace_lookup_for_testing(std::vector& main_trace, AvmBinaryTraceBuilder& bin_trace_builder) -{ - // Generate ByteLength Lookup table of instruction tags to the number of bytes - // {U8: 1, U16: 2, U32: 4, U64: 8, U128: 16} - for (auto const& [clk, count] : bin_trace_builder.byte_operation_counter) { - // from the clk we can derive the a and b inputs - auto b = static_cast(clk); - auto a = static_cast(clk >> 8); - auto op_id = static_cast(clk >> 16); - uint8_t bit_op = 0; - if (op_id == 0) { - bit_op = a & b; - } else if (op_id == 1) { - bit_op = a | b; - } else { - bit_op = a ^ b; - } - if (clk > (main_trace.size() - 1)) { - main_trace.push_back(Row{ - .main_clk = FF(clk), - .byte_lookup_sel_bin = FF(1), - .byte_lookup_table_input_a = a, - .byte_lookup_table_input_b = b, - .byte_lookup_table_op_id = op_id, - .byte_lookup_table_output = bit_op, - .lookup_byte_operations_counts = count, - }); - } else { - main_trace.at(clk).lookup_byte_operations_counts = count; - main_trace.at(clk).byte_lookup_sel_bin = FF(1); - main_trace.at(clk).byte_lookup_table_op_id = op_id; - main_trace.at(clk).byte_lookup_table_input_a = a; - main_trace.at(clk).byte_lookup_table_input_b = b; - main_trace.at(clk).byte_lookup_table_output = bit_op; - } - // Add the counter value stored throughout the execution - } - return static_cast(main_trace.size()); -} - -constexpr size_t L2_HI_GAS_COUNTS_IDX = 0; -constexpr size_t L2_LO_GAS_COUNTS_IDX = 1; -constexpr size_t DA_HI_GAS_COUNTS_IDX = 2; -constexpr size_t DA_LO_GAS_COUNTS_IDX = 3; - -// WARNING: FOR TESTING ONLY -// Generates the lookup table for the range checks without doing a full 2**16 rows -uint32_t finalize_rng_chks_for_testing( - std::vector& main_trace, - AvmAluTraceBuilder const& alu_trace_builder, - AvmMemTraceBuilder const& mem_trace_builder, - std::unordered_map const& mem_rng_check_lo_counts, - std::unordered_map const& mem_rng_check_mid_counts, - std::unordered_map const& mem_rng_check_hi_counts, - std::array, 4> const& rem_gas_rng_check_counts) -{ - // Build the main_trace, and add any new rows with specific clks that line up with lookup reads - - // Is there a "spread-like" operator in cpp or can I make it generic of the first param of the unordered map - std::vector> u8_rng_chks = { alu_trace_builder.u8_range_chk_counters[0], - alu_trace_builder.u8_range_chk_counters[1], - alu_trace_builder.u8_pow_2_counters[0], - alu_trace_builder.u8_pow_2_counters[1], - std::move(mem_rng_check_hi_counts) }; - - std::vector const>> u16_rng_chks; - - u16_rng_chks.emplace_back(mem_rng_check_lo_counts); - u16_rng_chks.emplace_back(mem_rng_check_mid_counts); - for (size_t i = 0; i < 4; i++) { - u16_rng_chks.emplace_back(rem_gas_rng_check_counts[i]); - } - - for (size_t i = 0; i < 15; i++) { - u16_rng_chks.emplace_back(alu_trace_builder.u16_range_chk_counters[i]); - } - - auto custom_clk = std::set{}; - for (auto const& row : u8_rng_chks) { - for (auto const& [key, value] : row) { - custom_clk.insert(key); - } - } - - for (auto const& row : alu_trace_builder.u16_range_chk_counters) { - for (auto const& [key, value] : row) { - custom_clk.insert(key); - } - } - - for (auto row : u16_rng_chks) { - for (auto const& [key, value] : row.get()) { - custom_clk.insert(key); - } - } - - for (auto const& row : alu_trace_builder.div_u64_range_chk_counters) { - for (auto const& [key, value] : row) { - custom_clk.insert(key); - } - } - - for (auto const& [clk, count] : mem_trace_builder.m_tag_err_lookup_counts) { - custom_clk.insert(clk); - } + std::array input = vec_to_arr(input_vec); + // Increment the clock by 7 since (25 reads / 4 reads per row = 7) + clk += num_main_rows; - auto old_size = main_trace.size(); - for (auto const& clk : custom_clk) { - if (clk >= old_size) { - main_trace.push_back(Row{ .main_clk = FF(clk) }); - } + // Now that we have read all the values, we can perform the operation to get the resulting witness. + // Note: We use the keccak_op_clk to ensure that the keccakf1600 operation is performed at the same clock cycle + // as the main trace that has the selector + std::array result = keccak_trace_builder.keccakf1600(keccak_op_clk, input); + // We convert the results to field elements here + std::vector ff_result; + for (uint32_t i = 0; i < 25; i++) { + ff_result.emplace_back(result[i]); } - return static_cast(main_trace.size()); + // Write the result to memory after + write_slice_to_memory( + call_ptr, clk, resolved_output_offset, AvmMemoryTag::U0, AvmMemoryTag::U64, FF(internal_return_ptr), ff_result); } -} // anonymous namespace + +/************************************************************************************************** + * FINALIZE + **************************************************************************************************/ /** * @brief Finalisation of the memory trace and incorporating it to the main trace. @@ -4556,4 +4675,25 @@ std::vector AvmTraceBuilder::finalize(uint32_t min_trace_size, bool range_c return trace; } +/** + * @brief Resetting the internal state so that a new trace can be rebuilt using the same object. + * + */ +void AvmTraceBuilder::reset() +{ + main_trace.clear(); + mem_trace_builder.reset(); + alu_trace_builder.reset(); + bin_trace_builder.reset(); + kernel_trace_builder.reset(); + gas_trace_builder.reset(); + conversion_trace_builder.reset(); + sha256_trace_builder.reset(); + poseidon2_trace_builder.reset(); + keccak_trace_builder.reset(); + pedersen_trace_builder.reset(); + + external_call_counter = 0; +} + } // namespace bb::avm_trace diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.hpp index 307943c8940..51572ae3bfe 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.hpp @@ -42,108 +42,81 @@ class AvmTraceBuilder { uint32_t side_effect_counter = 0, std::vector calldata = {}); - std::vector finalize(uint32_t min_trace_size = 0, bool range_check_required = ENABLE_PROVING); - void reset(); - uint32_t getPc() const { return pc; } - // Addition with direct or indirect memory access. + // Compute - Arithmetic void op_add(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Subtraction with direct or indirect memory access. void op_sub(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Multiplication with direct or indirect memory access. void op_mul(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Integer Division with direct or indirect memory access. void op_div(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Finite field division with direct or indirect memory access. void op_fdiv(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset); - // Equality with direct or indirect memory access. + // Compute - Comparators void op_eq(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Less Than with direct or indirect memory access. void op_lt(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Less Than or Equal to with direct or indirect memory access. void op_lte(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Bitwise and with direct or indirect memory access. + // Compute - Bitwise void op_and(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Bitwise or with direct or indirect memory access. void op_or(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Bitwise xor with direct or indirect memory access. void op_xor(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Bitwise not with direct or indirect memory access. void op_not(uint8_t indirect, uint32_t a_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Shift Left with direct or indirect memory access. void op_shl(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Shift Right with direct or indirect memory access. void op_shr(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t dst_offset, AvmMemoryTag in_tag); - // Cast an element pointed by the address a_offset into type specified by dst_tag and - // store the result in address given by dst_offset. + // Compute - Type Conversions void op_cast(uint8_t indirect, uint32_t a_offset, uint32_t dst_offset, AvmMemoryTag dst_tag); - // Context - Environment - void op_sender(uint8_t indirect, uint32_t dst_offset); + // Execution Environment void op_address(uint8_t indirect, uint32_t dst_offset); void op_storage_address(uint8_t indirect, uint32_t dst_offset); - void op_transaction_fee(uint8_t indirect, uint32_t dst_offset); + void op_sender(uint8_t indirect, uint32_t dst_offset); void op_function_selector(uint8_t indirect, uint32_t dst_offset); + void op_transaction_fee(uint8_t indirect, uint32_t dst_offset); - // Context - Environment - Globals + // Execution Environment - Globals void op_chain_id(uint8_t indirect, uint32_t dst_offset); void op_version(uint8_t indirect, uint32_t dst_offset); void op_block_number(uint8_t indirect, uint32_t dst_offset); - void op_coinbase(uint8_t indirect, uint32_t dst_offset); void op_timestamp(uint8_t indirect, uint32_t dst_offset); - // Context - Environment - Globals - Gas - void op_fee_per_da_gas(uint8_t indirect, uint32_t dst_offset); + void op_coinbase(uint8_t indirect, uint32_t dst_offset); void op_fee_per_l2_gas(uint8_t indirect, uint32_t dst_offset); + void op_fee_per_da_gas(uint8_t indirect, uint32_t dst_offset); - // Context - Environment - Calldata - // CALLDATACOPY opcode with direct/indirect memory access, i.e., - // direct: M[dst_offset:dst_offset+copy_size] = calldata[cd_offset:cd_offset+copy_size] - // indirect: M[M[dst_offset]:M[dst_offset]+copy_size] = calldata[cd_offset:cd_offset+copy_size] + // Execution Environment - Calldata void op_calldata_copy(uint8_t indirect, uint32_t cd_offset, uint32_t copy_size, uint32_t dst_offset); - // Context - Machine State - Gas + // Machine State - Gas void op_l2gasleft(uint8_t indirect, uint32_t dst_offset); void op_dagasleft(uint8_t indirect, uint32_t dst_offset); - // Jump to a given program counter. + // Machine State - Internal Control Flow void op_jump(uint32_t jmp_dest); - // Jump conditionally to a given program counter. void op_jumpi(uint8_t indirect, uint32_t jmp_dest, uint32_t cond_offset); - // Jump to a given program counter; storing the return location on a call stack. // TODO(md): this program counter MUST be an operand to the OPCODE. void op_internal_call(uint32_t jmp_dest); - // Return from an internal call. void op_internal_return(); - // Set a constant from bytecode with direct or indirect memory access. + // Machine State - Memory void op_set(uint8_t indirect, uint128_t val, uint32_t dst_offset, AvmMemoryTag in_tag); - // Move (copy) the value and tag of a memory cell to another one. void op_mov(uint8_t indirect, uint32_t src_offset, uint32_t dst_offset); - // Move (copy) the value and tag of a memory cell to another one whereby the source - // is determined conditionally based on a conditional value determined by cond_offset. void op_cmov(uint8_t indirect, uint32_t a_offset, uint32_t b_offset, uint32_t cond_offset, uint32_t dst_offset); - // Side Effects - Public Storage + // World State void op_sload(uint8_t indirect, uint32_t slot_offset, uint32_t size, uint32_t dest_offset); void op_sstore(uint8_t indirect, uint32_t src_offset, uint32_t size, uint32_t slot_offset); - - // With single output values + void op_note_hash_exists(uint8_t indirect, uint32_t note_hash_offset, uint32_t dest_offset); void op_emit_note_hash(uint8_t indirect, uint32_t note_hash_offset); + void op_nullifier_exists(uint8_t indirect, uint32_t nullifier_offset, uint32_t dest_offset); void op_emit_nullifier(uint8_t indirect, uint32_t nullifier_offset); - void op_emit_unencrypted_log(uint8_t indirect, uint32_t log_offset, uint32_t log_size_offset); - void op_emit_l2_to_l1_msg(uint8_t indirect, uint32_t recipient_offset, uint32_t content_offset); + void op_l1_to_l2_msg_exists(uint8_t indirect, uint32_t log_offset, uint32_t dest_offset); void op_get_contract_instance(uint8_t indirect, uint32_t address_offset, uint32_t dst_offset); - // With additional metadata output - void op_l1_to_l2_msg_exists(uint8_t indirect, uint32_t log_offset, uint32_t dest_offset); - void op_note_hash_exists(uint8_t indirect, uint32_t note_hash_offset, uint32_t dest_offset); - void op_nullifier_exists(uint8_t indirect, uint32_t nullifier_offset, uint32_t dest_offset); + // Accrued Substate + void op_emit_unencrypted_log(uint8_t indirect, uint32_t log_offset, uint32_t log_size_offset); + void op_emit_l2_to_l1_msg(uint8_t indirect, uint32_t recipient_offset, uint32_t content_offset); - // Calls + // Control Flow - Contract Calls void op_call(uint8_t indirect, uint32_t gas_offset, uint32_t addr_offset, @@ -153,39 +126,19 @@ class AvmTraceBuilder { uint32_t ret_size, uint32_t success_offset, uint32_t function_selector_offset); - - // RETURN opcode with direct and indirect memory access, i.e., - // direct: return(M[ret_offset:ret_offset+ret_size]) - // indirect: return(M[M[ret_offset]:M[ret_offset]+ret_size]) std::vector op_return(uint8_t indirect, uint32_t ret_offset, uint32_t ret_size); // REVERT Opcode (that just call return under the hood for now) std::vector op_revert(uint8_t indirect, uint32_t ret_offset, uint32_t ret_size); - // (not an opcode) Halt -> stop program execution. - void halt(); - // Gadgets - // --- Conversions - // To Radix LE conversion operation. - void op_to_radix_le(uint8_t indirect, uint32_t src_offset, uint32_t dst_offset, uint32_t radix, uint32_t num_limbs); - // --- Hashing - // Sha256 compression operation - void op_sha256_compression(uint8_t indirect, uint32_t output_offset, uint32_t h_init_offset, uint32_t input_offset); - // Poseidon2 Permutation operation - void op_poseidon2_permutation(uint8_t indirect, uint32_t input_offset, uint32_t output_offset); - // Keccakf1600 operation - interface will likely change (e..g no input size offset) - void op_keccakf1600(uint8_t indirect, uint32_t output_offset, uint32_t input_offset, uint32_t input_size_offset); - // Keccak operation - temporary while we transition to keccakf1600 void op_keccak(uint8_t indirect, uint32_t output_offset, uint32_t input_offset, uint32_t input_size_offset); - // SHA256 operation - temporary while we transition to sha256_compression + void op_poseidon2_permutation(uint8_t indirect, uint32_t input_offset, uint32_t output_offset); void op_sha256(uint8_t indirect, uint32_t output_offset, uint32_t input_offset, uint32_t input_size_offset); - // Pedersen Hash operation void op_pedersen_hash(uint8_t indirect, uint32_t gen_ctx_offset, uint32_t output_offset, uint32_t input_offset, uint32_t input_size_offset); - // Embedded EC Add - the offsets are temporary void op_ec_add(uint8_t indirect, uint32_t lhs_x_offset, uint32_t lhs_y_offset, @@ -199,7 +152,18 @@ class AvmTraceBuilder { uint32_t scalars_offset, uint32_t output_offset, uint32_t point_length_offset); + // Conversions + void op_to_radix_le(uint8_t indirect, uint32_t src_offset, uint32_t dst_offset, uint32_t radix, uint32_t num_limbs); + + // Future Gadgets -- pending changes in noir + void op_sha256_compression(uint8_t indirect, uint32_t output_offset, uint32_t h_init_offset, uint32_t input_offset); + void op_keccakf1600(uint8_t indirect, uint32_t output_offset, uint32_t input_offset, uint32_t input_size_offset); + std::vector finalize(uint32_t min_trace_size = 0, bool range_check_required = ENABLE_PROVING); + void reset(); + + // (not an opcode) Halt -> stop program execution. + void halt(); struct MemOp { bool is_indirect; uint32_t indirect_address; @@ -225,46 +189,11 @@ class AvmTraceBuilder { std::vector calldata{}; - /** - * @brief Create a kernel lookup opcode object - * - * Used for looking up into the kernel inputs (context) - {caller, address, etc.} - * - * @param indirect - Perform indirect memory resolution - * @param dst_offset - Memory address to write the lookup result to - * @param selector - The index of the kernel input lookup column - * @param value - The value read from the memory address - * @param w_tag - The memory tag of the value read - * @return Row - */ Row create_kernel_lookup_opcode( uint8_t indirect, uint32_t dst_offset, uint32_t selector, FF value, AvmMemoryTag w_tag); - /** - * @brief Create a kernel output opcode object - * - * Used for writing to the kernel app outputs - {new_note_hash, new_nullifier, etc.} - * - * @param indirect - Perform indirect memory resolution - * @param clk - The trace clk - * @param data_offset - The memory address to read the output from - * @return Row - */ Row create_kernel_output_opcode(uint8_t indirect, uint32_t clk, uint32_t data_offset); - /** - * @brief Create a kernel output opcode with metadata object - * - * Used for writing to the kernel app outputs with extra metadata - {sload, sstore} (value, slot) - * - * @param indirect - Perform indirect memory resolution - * @param clk - The trace clk - * @param data_offset - The offset of the main value to output - * @param data_r_tag - The data type of the value - * @param metadata_offset - The offset of the metadata (slot in the sload example) - * @param metadata_r_tag - The data type of the metadata - * @return Row - */ Row create_kernel_output_opcode_with_metadata(uint8_t indirect, uint32_t clk, uint32_t data_offset, @@ -272,35 +201,11 @@ class AvmTraceBuilder { uint32_t metadata_offset, AvmMemoryTag metadata_r_tag); - /** - * @brief Create a kernel output opcode with set metadata output object - * - * Used for writing output opcode where one metadata value is written and comes from a hint - * {note_hash_exists, nullifier_exists, etc. } Where a boolean output if it exists must also be written - * - * @param indirect - Perform indirect memory resolution - * @param clk - The trace clk - * @param data_offset - The offset of the main value to output - * @param metadata_offset - The offset of the metadata (slot in the sload example) - * @return Row - */ Row create_kernel_output_opcode_with_set_metadata_output_from_hint(uint8_t indirect, uint32_t clk, uint32_t data_offset, uint32_t metadata_offset); - /** - * @brief Create a kernel output opcode with set metadata output object - * - * Used for writing output opcode where one value is written and comes from a hint - * {sload} - * - * @param indirect - Perform indirect memory resolution - * @param clk - The trace clk - * @param data_offset - The offset of the main value to output - * @param metadata_offset - The offset of the metadata (slot in the sload example) - * @return Row - */ Row create_kernel_output_opcode_with_set_value_from_hint(uint8_t indirect, uint32_t clk, uint32_t data_offset,