diff --git a/barretenberg/cpp/src/barretenberg/bb/main.cpp b/barretenberg/cpp/src/barretenberg/bb/main.cpp index 55a938fe44d..d62b3326310 100644 --- a/barretenberg/cpp/src/barretenberg/bb/main.cpp +++ b/barretenberg/cpp/src/barretenberg/bb/main.cpp @@ -446,15 +446,25 @@ void gateCount(const std::string& bytecodePath, bool honk_recursion) size_t i = 0; for (auto constraint_system : constraint_systems) { acir_proofs::AcirComposer acir_composer(0, verbose_logging); - acir_composer.create_circuit(constraint_system); + acir_composer.create_circuit(constraint_system, {}, true); auto circuit_size = acir_composer.get_total_circuit_size(); // Build individual circuit report + std::string gates_per_opcode_str; + for (size_t j = 0; j < constraint_system.gates_per_opcode.size(); j++) { + gates_per_opcode_str += std::to_string(constraint_system.gates_per_opcode[j]); + if (j != constraint_system.gates_per_opcode.size() - 1) { + gates_per_opcode_str += ","; + } + } + auto result_string = format("{\n \"acir_opcodes\": ", constraint_system.num_acir_opcodes, ",\n \"circuit_size\": ", circuit_size, - "\n }"); + ",\n \"gates_per_opcode\": [", + gates_per_opcode_str, + "]\n }"); // Attach a comma if we still circuit reports to generate if (i != (constraint_systems.size() - 1)) { diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index bee50d76742..bcfcce6776f 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -1,5 +1,6 @@ #include "acir_format.hpp" #include "barretenberg/common/log.hpp" +#include "barretenberg/common/throw_or_abort.hpp" #include "barretenberg/stdlib/primitives/field/field_conversion.hpp" #include "barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp" #include "barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp" @@ -14,114 +15,215 @@ template class DSLBigInts; template void build_constraints(Builder& builder, - AcirFormat const& constraint_system, + AcirFormat& constraint_system, bool has_valid_witness_assignments, - bool honk_recursion) + bool honk_recursion, + bool collect_gates_per_opcode) { + if (collect_gates_per_opcode) { + constraint_system.gates_per_opcode.resize(constraint_system.num_acir_opcodes, 0); + } + size_t prev_gate_count = 0; + + auto compute_gate_diff = [&]() -> size_t { + if (!collect_gates_per_opcode) { + return 0; + } + size_t new_gate_count = builder.get_num_gates(); + size_t diff = new_gate_count - prev_gate_count; + prev_gate_count = new_gate_count; + return diff; + }; + + auto track_gate_diff = [&](std::vector& gates_per_opcode, size_t opcode_index) -> void { + if (collect_gates_per_opcode) { + gates_per_opcode[opcode_index] = compute_gate_diff(); + } + }; + // Add arithmetic gates - for (const auto& constraint : constraint_system.poly_triple_constraints) { + for (size_t i = 0; i < constraint_system.poly_triple_constraints.size(); ++i) { + const auto& constraint = constraint_system.poly_triple_constraints.at(i); builder.create_poly_gate(constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.poly_triple_constraints.at(i)); } - for (const auto& constraint : constraint_system.quad_constraints) { + + for (size_t i = 0; i < constraint_system.quad_constraints.size(); ++i) { + const auto& constraint = constraint_system.quad_constraints.at(i); builder.create_big_mul_gate(constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.quad_constraints.at(i)); } // Add logic constraint - for (const auto& constraint : constraint_system.logic_constraints) { + for (size_t i = 0; i < constraint_system.logic_constraints.size(); ++i) { + const auto& constraint = constraint_system.logic_constraints.at(i); create_logic_gate( builder, constraint.a, constraint.b, constraint.result, constraint.num_bits, constraint.is_xor_gate); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.logic_constraints.at(i)); } // Add range constraint - for (const auto& constraint : constraint_system.range_constraints) { + for (size_t i = 0; i < constraint_system.range_constraints.size(); ++i) { + const auto& constraint = constraint_system.range_constraints.at(i); builder.create_range_constraint(constraint.witness, constraint.num_bits, ""); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.range_constraints.at(i)); } // Add aes128 constraints - for (const auto& constraint : constraint_system.aes128_constraints) { + for (size_t i = 0; i < constraint_system.aes128_constraints.size(); ++i) { + const auto& constraint = constraint_system.aes128_constraints.at(i); create_aes128_constraints(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.aes128_constraints.at(i)); } // Add sha256 constraints - for (const auto& constraint : constraint_system.sha256_constraints) { + for (size_t i = 0; i < constraint_system.sha256_constraints.size(); ++i) { + const auto& constraint = constraint_system.sha256_constraints.at(i); create_sha256_constraints(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.sha256_constraints.at(i)); } - for (const auto& constraint : constraint_system.sha256_compression) { + + for (size_t i = 0; i < constraint_system.sha256_compression.size(); ++i) { + const auto& constraint = constraint_system.sha256_compression[i]; create_sha256_compression_constraints(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.sha256_compression[i]); } // Add schnorr constraints - for (const auto& constraint : constraint_system.schnorr_constraints) { + for (size_t i = 0; i < constraint_system.schnorr_constraints.size(); ++i) { + const auto& constraint = constraint_system.schnorr_constraints.at(i); create_schnorr_verify_constraints(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.schnorr_constraints.at(i)); } // Add ECDSA k1 constraints - for (const auto& constraint : constraint_system.ecdsa_k1_constraints) { + for (size_t i = 0; i < constraint_system.ecdsa_k1_constraints.size(); ++i) { + const auto& constraint = constraint_system.ecdsa_k1_constraints.at(i); create_ecdsa_k1_verify_constraints(builder, constraint, has_valid_witness_assignments); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.ecdsa_k1_constraints.at(i)); } // Add ECDSA r1 constraints - for (const auto& constraint : constraint_system.ecdsa_r1_constraints) { + for (size_t i = 0; i < constraint_system.ecdsa_r1_constraints.size(); ++i) { + const auto& constraint = constraint_system.ecdsa_r1_constraints.at(i); create_ecdsa_r1_verify_constraints(builder, constraint, has_valid_witness_assignments); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.ecdsa_r1_constraints.at(i)); } // Add blake2s constraints - for (const auto& constraint : constraint_system.blake2s_constraints) { + for (size_t i = 0; i < constraint_system.blake2s_constraints.size(); ++i) { + const auto& constraint = constraint_system.blake2s_constraints.at(i); create_blake2s_constraints(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.blake2s_constraints.at(i)); } // Add blake3 constraints - for (const auto& constraint : constraint_system.blake3_constraints) { + for (size_t i = 0; i < constraint_system.blake3_constraints.size(); ++i) { + const auto& constraint = constraint_system.blake3_constraints.at(i); create_blake3_constraints(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.blake3_constraints.at(i)); } // Add keccak constraints - for (const auto& constraint : constraint_system.keccak_constraints) { + for (size_t i = 0; i < constraint_system.keccak_constraints.size(); ++i) { + const auto& constraint = constraint_system.keccak_constraints.at(i); create_keccak_constraints(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.keccak_constraints.at(i)); } - for (const auto& constraint : constraint_system.keccak_permutations) { + + for (size_t i = 0; i < constraint_system.keccak_permutations.size(); ++i) { + const auto& constraint = constraint_system.keccak_permutations[i]; create_keccak_permutations(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.keccak_permutations[i]); } // Add pedersen constraints - for (const auto& constraint : constraint_system.pedersen_constraints) { + for (size_t i = 0; i < constraint_system.pedersen_constraints.size(); ++i) { + const auto& constraint = constraint_system.pedersen_constraints.at(i); create_pedersen_constraint(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.pedersen_constraints.at(i)); } - for (const auto& constraint : constraint_system.pedersen_hash_constraints) { + for (size_t i = 0; i < constraint_system.pedersen_hash_constraints.size(); ++i) { + const auto& constraint = constraint_system.pedersen_hash_constraints.at(i); create_pedersen_hash_constraint(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.pedersen_hash_constraints.at(i)); } - for (const auto& constraint : constraint_system.poseidon2_constraints) { + for (size_t i = 0; i < constraint_system.poseidon2_constraints.size(); ++i) { + const auto& constraint = constraint_system.poseidon2_constraints.at(i); create_poseidon2_permutations(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.poseidon2_constraints.at(i)); } // Add multi scalar mul constraints - for (const auto& constraint : constraint_system.multi_scalar_mul_constraints) { + for (size_t i = 0; i < constraint_system.multi_scalar_mul_constraints.size(); ++i) { + const auto& constraint = constraint_system.multi_scalar_mul_constraints.at(i); create_multi_scalar_mul_constraint(builder, constraint); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.multi_scalar_mul_constraints.at(i)); } // Add ec add constraints - for (const auto& constraint : constraint_system.ec_add_constraints) { + for (size_t i = 0; i < constraint_system.ec_add_constraints.size(); ++i) { + const auto& constraint = constraint_system.ec_add_constraints.at(i); create_ec_add_constraint(builder, constraint, has_valid_witness_assignments); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.ec_add_constraints.at(i)); } // Add block constraints - for (const auto& constraint : constraint_system.block_constraints) { + for (size_t i = 0; i < constraint_system.block_constraints.size(); ++i) { + const auto& constraint = constraint_system.block_constraints.at(i); create_block_constraints(builder, constraint, has_valid_witness_assignments); + if (collect_gates_per_opcode) { + size_t avg_gates_per_opcode = + compute_gate_diff() / constraint_system.original_opcode_indices.block_constraints.at(i).size(); + for (size_t opcode_index : constraint_system.original_opcode_indices.block_constraints.at(i)) { + constraint_system.gates_per_opcode[opcode_index] = avg_gates_per_opcode; + } + } } // Add big_int constraints DSLBigInts dsl_bigints; dsl_bigints.set_builder(&builder); - for (const auto& constraint : constraint_system.bigint_from_le_bytes_constraints) { + for (size_t i = 0; i < constraint_system.bigint_from_le_bytes_constraints.size(); ++i) { + const auto& constraint = constraint_system.bigint_from_le_bytes_constraints.at(i); create_bigint_from_le_bytes_constraint(builder, constraint, dsl_bigints); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.bigint_from_le_bytes_constraints.at(i)); } - for (const auto& constraint : constraint_system.bigint_operations) { + + for (size_t i = 0; i < constraint_system.bigint_operations.size(); ++i) { + const auto& constraint = constraint_system.bigint_operations[i]; create_bigint_operations_constraint(constraint, dsl_bigints, has_valid_witness_assignments); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.bigint_operations[i]); } - for (const auto& constraint : constraint_system.bigint_to_le_bytes_constraints) { + + for (size_t i = 0; i < constraint_system.bigint_to_le_bytes_constraints.size(); ++i) { + const auto& constraint = constraint_system.bigint_to_le_bytes_constraints.at(i); create_bigint_to_le_bytes_constraint(builder, constraint, dsl_bigints); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.bigint_to_le_bytes_constraints.at(i)); } // RecursionConstraint @@ -152,7 +254,9 @@ void build_constraints(Builder& builder, auto proof_size_no_pub_inputs = recursion_proof_size_without_public_inputs(); // Add recursion constraints - for (auto constraint : constraint_system.recursion_constraints) { + for (size_t constraint_idx = 0; constraint_idx < constraint_system.recursion_constraints.size(); + ++constraint_idx) { + auto constraint = constraint_system.recursion_constraints[constraint_idx]; // A proof passed into the constraint should be stripped of its public inputs, except in the case where a // proof contains an aggregation object itself. We refer to this as the `nested_aggregation_object`. The // verifier circuit requires that the indices to a nested proof aggregation state are a circuit constant. @@ -187,12 +291,15 @@ void build_constraints(Builder& builder, constraint.proof.begin() + static_cast(RecursionConstraint::AGGREGATION_OBJECT_SIZE)); } + current_output_aggregation_object = create_recursion_constraints(builder, constraint, current_input_aggregation_object, nested_aggregation_object, has_valid_witness_assignments); current_input_aggregation_object = current_output_aggregation_object; + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.recursion_constraints[constraint_idx]); } // Now that the circuit has been completely built, we add the output aggregation as public @@ -232,11 +339,13 @@ void build_constraints(Builder& builder, }; // Add recursion constraints - for (auto constraint : constraint_system.honk_recursion_constraints) { - // A proof passed into the constraint should be stripped of its inner public inputs, but not the nested - // aggregation object itself. The verifier circuit requires that the indices to a nested proof aggregation - // state are a circuit constant. The user tells us they how they want these constants set by keeping the - // nested aggregation object attached to the proof as public inputs. + + for (size_t i = 0; i < constraint_system.honk_recursion_constraints.size(); ++i) { + auto constraint = constraint_system.honk_recursion_constraints.at(i); + // A proof passed into the constraint should be stripped of its inner public inputs, but not the + // nested aggregation object itself. The verifier circuit requires that the indices to a nested + // proof aggregation state are a circuit constant. The user tells us they how they want these + // constants set by keeping the nested aggregation object attached to the proof as public inputs. std::array nested_aggregation_object = {}; for (size_t i = 0; i < HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { // Set the nested aggregation object indices to witness indices from the proof @@ -256,6 +365,8 @@ void build_constraints(Builder& builder, current_aggregation_object, nested_aggregation_object, has_valid_witness_assignments); + track_gate_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.honk_recursion_constraints.at(i)); } // Now that the circuit has been completely built, we add the output aggregation as public @@ -319,18 +430,20 @@ void build_constraints(Builder& builder, * @return Builder */ template <> -UltraCircuitBuilder create_circuit(const AcirFormat& constraint_system, +UltraCircuitBuilder create_circuit(AcirFormat& constraint_system, size_t size_hint, WitnessVector const& witness, bool honk_recursion, - [[maybe_unused]] std::shared_ptr) + [[maybe_unused]] std::shared_ptr, + bool collect_gates_per_opcode) { Builder builder{ size_hint, witness, constraint_system.public_inputs, constraint_system.varnum, constraint_system.recursive }; bool has_valid_witness_assignments = !witness.empty(); - build_constraints(builder, constraint_system, has_valid_witness_assignments, honk_recursion); + build_constraints( + builder, constraint_system, has_valid_witness_assignments, honk_recursion, collect_gates_per_opcode); return builder; }; @@ -345,22 +458,24 @@ UltraCircuitBuilder create_circuit(const AcirFormat& constraint_system, * @return Builder */ template <> -MegaCircuitBuilder create_circuit(const AcirFormat& constraint_system, +MegaCircuitBuilder create_circuit(AcirFormat& constraint_system, [[maybe_unused]] size_t size_hint, WitnessVector const& witness, bool honk_recursion, - std::shared_ptr op_queue) + std::shared_ptr op_queue, + bool collect_gates_per_opcode) { // Construct a builder using the witness and public input data from acir and with the goblin-owned op_queue auto builder = MegaCircuitBuilder{ op_queue, witness, constraint_system.public_inputs, constraint_system.varnum }; // Populate constraints in the builder via the data in constraint_system bool has_valid_witness_assignments = !witness.empty(); - acir_format::build_constraints(builder, constraint_system, has_valid_witness_assignments, honk_recursion); + acir_format::build_constraints( + builder, constraint_system, has_valid_witness_assignments, honk_recursion, collect_gates_per_opcode); return builder; }; -template void build_constraints(MegaCircuitBuilder&, AcirFormat const&, bool, bool); +template void build_constraints(MegaCircuitBuilder&, AcirFormat&, bool, bool, bool); } // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index 493cfe84061..80edf92270b 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -24,6 +24,44 @@ namespace acir_format { +/** + * @brief Indices of the original opcode that originated each constraint in AcirFormat. + * @details Contains one array of indices per opcode type. The length of each array is equal to the number of + * constraints of that type. The relationship between the opcodes and constraints is assumed to be one to one, except + * for block constraints. + */ +struct AcirFormatOriginalOpcodeIndices { + std::vector logic_constraints; + std::vector range_constraints; + std::vector aes128_constraints; + std::vector sha256_constraints; + std::vector sha256_compression; + std::vector schnorr_constraints; + std::vector ecdsa_k1_constraints; + std::vector ecdsa_r1_constraints; + std::vector blake2s_constraints; + std::vector blake3_constraints; + std::vector keccak_constraints; + std::vector keccak_permutations; + std::vector pedersen_constraints; + std::vector pedersen_hash_constraints; + std::vector poseidon2_constraints; + std::vector multi_scalar_mul_constraints; + std::vector ec_add_constraints; + std::vector recursion_constraints; + std::vector honk_recursion_constraints; + std::vector bigint_from_le_bytes_constraints; + std::vector bigint_to_le_bytes_constraints; + std::vector bigint_operations; + std::vector poly_triple_constraints; + std::vector quad_constraints; + // Multiple opcode indices per block: + std::vector> block_constraints; + + friend bool operator==(AcirFormatOriginalOpcodeIndices const& lhs, + AcirFormatOriginalOpcodeIndices const& rhs) = default; +}; + struct AcirFormat { // The number of witnesses in the circuit uint32_t varnum; @@ -72,6 +110,13 @@ struct AcirFormat { quad_constraints; std::vector block_constraints; + // Number of gates added to the circuit per original opcode. + // Has length equal to num_acir_opcodes. + std::vector gates_per_opcode = {}; + + // Indices of the original opcode that originated each constraint in AcirFormat. + AcirFormatOriginalOpcodeIndices original_opcode_indices; + // For serialization, update with any new fields MSGPACK_FIELDS(varnum, public_inputs, @@ -142,18 +187,21 @@ struct AcirProgramStack { }; template -Builder create_circuit(const AcirFormat& constraint_system, +Builder create_circuit(AcirFormat& constraint_system, size_t size_hint = 0, WitnessVector const& witness = {}, bool honk_recursion = false, - std::shared_ptr op_queue = std::make_shared()); + std::shared_ptr op_queue = std::make_shared(), + bool collect_gates_per_opcode = false); template -void build_constraints(Builder& builder, - AcirFormat const& constraint_system, - bool has_valid_witness_assignments, - bool honk_recursion = false); // honk_recursion means we will honk to recursively verify this - // circuit. This distinction is needed to not add the default - // aggregation object when we're not using the honk RV. +void build_constraints( + Builder& builder, + AcirFormat& constraint_system, + bool has_valid_witness_assignments, + bool honk_recursion = false, + bool collect_gates_per_opcode = false); // honk_recursion means we will honk to recursively verify this + // circuit. This distinction is needed to not add the default + // aggregation object when we're not using the honk RV. } // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index 0d1ef31040f..6085671fb0a 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -1,13 +1,16 @@ #include +#include #include #include "acir_format.hpp" +#include "acir_format_mocks.hpp" #include "barretenberg/common/streams.hpp" #include "barretenberg/crypto/schnorr/schnorr.hpp" #include "barretenberg/plonk/composer/standard_composer.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/plonk/proof_system/types/proof.hpp" #include "barretenberg/serialize/test_helper.hpp" +#include "barretenberg/stdlib_circuit_builders/op_queue/ecc_op_queue.hpp" #include "ecdsa_secp256k1.hpp" using namespace bb; @@ -64,8 +67,9 @@ TEST_F(AcirFormatTests, TestASingleConstraintNoPubInputs) .poly_triple_constraints = { constraint }, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; - + mock_opcode_indices(constraint_system); WitnessVector witness{ 0, 0, 1 }; auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness); @@ -154,35 +158,39 @@ TEST_F(AcirFormatTests, TestLogicGateFromNoirCircuit) // EXPR [ (1, _4, _6) (-1, _4) 0 ] // EXPR [ (-1, _6) 1 ] - AcirFormat constraint_system{ .varnum = 6, - .recursive = false, - .num_acir_opcodes = 7, - .public_inputs = { 1 }, - .logic_constraints = { logic_constraint }, - .range_constraints = { range_a, range_b }, - .aes128_constraints = {}, - .sha256_constraints = {}, - .sha256_compression = {}, - .schnorr_constraints = {}, - .ecdsa_k1_constraints = {}, - .ecdsa_r1_constraints = {}, - .blake2s_constraints = {}, - .blake3_constraints = {}, - .keccak_constraints = {}, - .keccak_permutations = {}, - .pedersen_constraints = {}, - .pedersen_hash_constraints = {}, - .poseidon2_constraints = {}, - .multi_scalar_mul_constraints = {}, - .ec_add_constraints = {}, - .recursion_constraints = {}, - .honk_recursion_constraints = {}, - .bigint_from_le_bytes_constraints = {}, - .bigint_to_le_bytes_constraints = {}, - .bigint_operations = {}, - .poly_triple_constraints = { expr_a, expr_b, expr_c, expr_d }, - .quad_constraints = {}, - .block_constraints = {} }; + AcirFormat constraint_system{ + .varnum = 6, + .recursive = false, + .num_acir_opcodes = 7, + .public_inputs = { 1 }, + .logic_constraints = { logic_constraint }, + .range_constraints = { range_a, range_b }, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = {}, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = {}, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = {}, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = {}, + .honk_recursion_constraints = {}, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = { expr_a, expr_b, expr_c, expr_d }, + .quad_constraints = {}, + .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + mock_opcode_indices(constraint_system); uint256_t inverse_of_five = fr(5).invert(); WitnessVector witness{ @@ -202,11 +210,14 @@ TEST_F(AcirFormatTests, TestLogicGateFromNoirCircuit) TEST_F(AcirFormatTests, TestSchnorrVerifyPass) { std::vector range_constraints; + std::vector range_opcode_indices; + size_t current_opcode = 0; for (uint32_t i = 0; i < 10; i++) { range_constraints.push_back(RangeConstraint{ .witness = i, .num_bits = 15, }); + range_opcode_indices.push_back(current_opcode++); } std::array signature; @@ -216,6 +227,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifyPass) .witness = value, .num_bits = 15, }); + range_opcode_indices.push_back(current_opcode++); } SchnorrConstraint schnorr_constraint{ @@ -225,44 +237,48 @@ TEST_F(AcirFormatTests, TestSchnorrVerifyPass) .result = 76, .signature = signature, }; - AcirFormat constraint_system{ .varnum = 81, - .recursive = false, - .num_acir_opcodes = 75, - .public_inputs = {}, - .logic_constraints = {}, - .range_constraints = range_constraints, - .aes128_constraints = {}, - .sha256_constraints = {}, - .sha256_compression = {}, - .schnorr_constraints = { schnorr_constraint }, - .ecdsa_k1_constraints = {}, - .ecdsa_r1_constraints = {}, - .blake2s_constraints = {}, - .blake3_constraints = {}, - .keccak_constraints = {}, - .keccak_permutations = {}, - .pedersen_constraints = {}, - .pedersen_hash_constraints = {}, - .poseidon2_constraints = {}, - .multi_scalar_mul_constraints = {}, - .ec_add_constraints = {}, - .recursion_constraints = {}, - .honk_recursion_constraints = {}, - .bigint_from_le_bytes_constraints = {}, - .bigint_to_le_bytes_constraints = {}, - .bigint_operations = {}, - .poly_triple_constraints = { poly_triple{ - .a = schnorr_constraint.result, - .b = schnorr_constraint.result, - .c = schnorr_constraint.result, - .q_m = 0, - .q_l = 0, - .q_r = 0, - .q_o = 1, - .q_c = fr::neg_one(), - } }, - .quad_constraints = {}, - .block_constraints = {} }; + AcirFormat constraint_system{ + .varnum = 81, + .recursive = false, + .num_acir_opcodes = 76, + .public_inputs = {}, + .logic_constraints = {}, + .range_constraints = range_constraints, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = {}, + .schnorr_constraints = { schnorr_constraint }, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = {}, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = {}, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = {}, + .honk_recursion_constraints = {}, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = { poly_triple{ + .a = schnorr_constraint.result, + .b = schnorr_constraint.result, + .c = schnorr_constraint.result, + .q_m = 0, + .q_l = 0, + .q_r = 0, + .q_o = 1, + .q_c = fr::neg_one(), + } }, + .quad_constraints = {}, + .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + mock_opcode_indices(constraint_system); std::string message_string = "tenletters"; schnorr_key_pair account; @@ -299,11 +315,15 @@ TEST_F(AcirFormatTests, TestSchnorrVerifyPass) TEST_F(AcirFormatTests, TestSchnorrVerifySmallRange) { std::vector range_constraints; + std::vector range_opcode_indices; + size_t current_opcode = 0; + for (uint32_t i = 0; i < 10; i++) { range_constraints.push_back(RangeConstraint{ .witness = i, .num_bits = 8, }); + range_opcode_indices.push_back(current_opcode++); } std::array signature; @@ -313,6 +333,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifySmallRange) .witness = value, .num_bits = 8, }); + range_opcode_indices.push_back(current_opcode++); } SchnorrConstraint schnorr_constraint{ @@ -325,7 +346,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifySmallRange) AcirFormat constraint_system{ .varnum = 81, .recursive = false, - .num_acir_opcodes = 75, + .num_acir_opcodes = 76, .public_inputs = {}, .logic_constraints = {}, .range_constraints = range_constraints, @@ -361,7 +382,9 @@ TEST_F(AcirFormatTests, TestSchnorrVerifySmallRange) } }, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); std::string message_string = "tenletters"; schnorr_key_pair account; @@ -469,7 +492,9 @@ TEST_F(AcirFormatTests, TestVarKeccak) .poly_triple_constraints = { dummy }, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); WitnessVector witness{ 4, 2, 6, 2 }; auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness); @@ -490,35 +515,39 @@ TEST_F(AcirFormatTests, TestKeccakPermutation) 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 }, }; - AcirFormat constraint_system{ .varnum = 51, - .recursive = false, - .num_acir_opcodes = 1, - .public_inputs = {}, - .logic_constraints = {}, - .range_constraints = {}, - .aes128_constraints = {}, - .sha256_constraints = {}, - .sha256_compression = {}, - .schnorr_constraints = {}, - .ecdsa_k1_constraints = {}, - .ecdsa_r1_constraints = {}, - .blake2s_constraints = {}, - .blake3_constraints = {}, - .keccak_constraints = {}, - .keccak_permutations = { keccak_permutation }, - .pedersen_constraints = {}, - .pedersen_hash_constraints = {}, - .poseidon2_constraints = {}, - .multi_scalar_mul_constraints = {}, - .ec_add_constraints = {}, - .recursion_constraints = {}, - .honk_recursion_constraints = {}, - .bigint_from_le_bytes_constraints = {}, - .bigint_to_le_bytes_constraints = {}, - .bigint_operations = {}, - .poly_triple_constraints = {}, - .quad_constraints = {}, - .block_constraints = {} }; + AcirFormat constraint_system{ + .varnum = 51, + .recursive = false, + .num_acir_opcodes = 1, + .public_inputs = {}, + .logic_constraints = {}, + .range_constraints = {}, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = {}, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = { keccak_permutation }, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = {}, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = {}, + .honk_recursion_constraints = {}, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = {}, + .quad_constraints = {}, + .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + mock_opcode_indices(constraint_system); WitnessVector witness{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, @@ -534,3 +563,70 @@ TEST_F(AcirFormatTests, TestKeccakPermutation) EXPECT_EQ(verifier.verify_proof(proof), true); } + +TEST_F(AcirFormatTests, TestCollectsGateCounts) +{ + + // Witness 0 + witness 1 = witness 2 + poly_triple first_gate{ + .a = 0, + .b = 1, + .c = 2, + .q_m = 0, + .q_l = 1, + .q_r = 1, + .q_o = -1, + .q_c = 0, + }; + + // Witness 1 = 27 + poly_triple second_gate{ + .a = 1, + .b = 0, + .c = 0, + .q_m = 0, + .q_l = 1, + .q_r = 0, + .q_o = 0, + .q_c = -27, + }; + + AcirFormat constraint_system{ + .varnum = 4, + .recursive = false, + .num_acir_opcodes = 2, + .public_inputs = {}, + .logic_constraints = {}, + .range_constraints = {}, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = {}, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = {}, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = {}, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = {}, + .honk_recursion_constraints = {}, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = { first_gate, second_gate }, + .quad_constraints = {}, + .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + mock_opcode_indices(constraint_system); + WitnessVector witness{ 5, 27, 32 }; + auto builder = + create_circuit(constraint_system, /*size_hint*/ 0, witness, false, std::make_shared(), true); + + EXPECT_EQ(constraint_system.gates_per_opcode, std::vector({ 2, 1 })); +} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format_mocks.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format_mocks.cpp new file mode 100644 index 00000000000..f53ff85d6e7 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format_mocks.cpp @@ -0,0 +1,118 @@ +#include "acir_format.hpp" + +acir_format::AcirFormatOriginalOpcodeIndices create_empty_original_opcode_indices() +{ + return acir_format::AcirFormatOriginalOpcodeIndices{ + .logic_constraints = {}, + .range_constraints = {}, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = {}, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = {}, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = {}, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = {}, + .honk_recursion_constraints = {}, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = {}, + .quad_constraints = {}, + .block_constraints = {}, + }; +} + +void mock_opcode_indices(acir_format::AcirFormat& constraint_system) +{ + size_t current_opcode = 0; + for (size_t i = 0; i < constraint_system.logic_constraints.size(); i++) { + constraint_system.original_opcode_indices.logic_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.range_constraints.size(); i++) { + constraint_system.original_opcode_indices.range_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.aes128_constraints.size(); i++) { + constraint_system.original_opcode_indices.aes128_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.sha256_constraints.size(); i++) { + constraint_system.original_opcode_indices.sha256_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.sha256_compression.size(); i++) { + constraint_system.original_opcode_indices.sha256_compression.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.schnorr_constraints.size(); i++) { + constraint_system.original_opcode_indices.schnorr_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.ecdsa_k1_constraints.size(); i++) { + constraint_system.original_opcode_indices.ecdsa_k1_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.ecdsa_r1_constraints.size(); i++) { + constraint_system.original_opcode_indices.ecdsa_r1_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.blake2s_constraints.size(); i++) { + constraint_system.original_opcode_indices.blake2s_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.blake3_constraints.size(); i++) { + constraint_system.original_opcode_indices.blake3_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.keccak_constraints.size(); i++) { + constraint_system.original_opcode_indices.keccak_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.keccak_permutations.size(); i++) { + constraint_system.original_opcode_indices.keccak_permutations.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.pedersen_constraints.size(); i++) { + constraint_system.original_opcode_indices.pedersen_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.pedersen_hash_constraints.size(); i++) { + constraint_system.original_opcode_indices.pedersen_hash_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.poseidon2_constraints.size(); i++) { + constraint_system.original_opcode_indices.poseidon2_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.multi_scalar_mul_constraints.size(); i++) { + constraint_system.original_opcode_indices.multi_scalar_mul_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.ec_add_constraints.size(); i++) { + constraint_system.original_opcode_indices.ec_add_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); i++) { + constraint_system.original_opcode_indices.recursion_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.honk_recursion_constraints.size(); i++) { + constraint_system.original_opcode_indices.honk_recursion_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.bigint_from_le_bytes_constraints.size(); i++) { + constraint_system.original_opcode_indices.bigint_from_le_bytes_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.bigint_to_le_bytes_constraints.size(); i++) { + constraint_system.original_opcode_indices.bigint_to_le_bytes_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.bigint_operations.size(); i++) { + constraint_system.original_opcode_indices.bigint_operations.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.poly_triple_constraints.size(); i++) { + constraint_system.original_opcode_indices.poly_triple_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.quad_constraints.size(); i++) { + constraint_system.original_opcode_indices.quad_constraints.push_back(current_opcode++); + } + for (size_t i = 0; i < constraint_system.block_constraints.size(); i++) { + std::vector block_indices; + for (size_t j = 0; j < constraint_system.block_constraints[i].trace.size(); j++) { + block_indices.push_back(current_opcode++); + } + constraint_system.original_opcode_indices.block_constraints.push_back(block_indices); + } + + constraint_system.num_acir_opcodes = static_cast(current_opcode); +} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format_mocks.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format_mocks.hpp new file mode 100644 index 00000000000..6a58c34aa0d --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format_mocks.hpp @@ -0,0 +1,5 @@ +#include "acir_format.hpp" + +acir_format::AcirFormatOriginalOpcodeIndices create_empty_original_opcode_indices(); + +void mock_opcode_indices(acir_format::AcirFormat& constraint_system); \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp index e292766d78f..09029ada3a1 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp @@ -1,5 +1,8 @@ #include "acir_to_constraint_buf.hpp" #include "barretenberg/common/container.hpp" +#include +#include +#include #ifndef __wasm__ #include "barretenberg/bb/get_bytecode.hpp" #endif @@ -164,7 +167,7 @@ mul_quad_ serialize_mul_quad_gate(Program::Expression const& arg) return quad; } -void handle_arithmetic(Program::Opcode::AssertZero const& arg, AcirFormat& af) +void handle_arithmetic(Program::Opcode::AssertZero const& arg, AcirFormat& af, size_t opcode_index) { if (arg.value.linear_combinations.size() <= 3) { poly_triple pt = serialize_arithmetic_gate(arg.value); @@ -174,15 +177,22 @@ void handle_arithmetic(Program::Opcode::AssertZero const& arg, AcirFormat& af) // gate instead. We could probably always use a width-4 gate in fact. if (pt == poly_triple{ 0, 0, 0, 0, 0, 0, 0, 0 }) { af.quad_constraints.push_back(serialize_mul_quad_gate(arg.value)); + af.original_opcode_indices.quad_constraints.push_back(opcode_index); + } else { af.poly_triple_constraints.push_back(pt); + af.original_opcode_indices.poly_triple_constraints.push_back(opcode_index); } } else { af.quad_constraints.push_back(serialize_mul_quad_gate(arg.value)); + af.original_opcode_indices.quad_constraints.push_back(opcode_index); } } -void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, AcirFormat& af, bool honk_recursion) +void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, + AcirFormat& af, + bool honk_recursion, + size_t opcode_index) { std::visit( [&](auto&& arg) { @@ -195,6 +205,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .num_bits = arg.lhs.num_bits, .is_xor_gate = false, }); + af.original_opcode_indices.logic_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.logic_constraints.push_back(LogicConstraint{ .a = arg.lhs.witness.value, @@ -203,11 +214,14 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .num_bits = arg.lhs.num_bits, .is_xor_gate = true, }); + af.original_opcode_indices.logic_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.range_constraints.push_back(RangeConstraint{ .witness = arg.input.witness.value, .num_bits = arg.input.num_bits, }); + af.original_opcode_indices.range_constraints.push_back(opcode_index); + } else if constexpr (std::is_same_v) { af.aes128_constraints.push_back(AES128Constraint{ .inputs = map(arg.inputs, @@ -233,6 +247,8 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci }), .outputs = map(arg.outputs, [](auto& e) { return e.value; }), }); + af.original_opcode_indices.aes128_constraints.push_back(opcode_index); + } else if constexpr (std::is_same_v) { af.sha256_constraints.push_back(Sha256Constraint{ .inputs = map(arg.inputs, @@ -244,6 +260,8 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci }), .result = map(arg.outputs, [](auto& e) { return e.value; }), }); + af.original_opcode_indices.sha256_constraints.push_back(opcode_index); + } else if constexpr (std::is_same_v) { af.sha256_compression.push_back(Sha256Compression{ .inputs = map(arg.inputs, @@ -262,6 +280,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci }), .result = map(arg.outputs, [](auto& e) { return e.value; }), }); + af.original_opcode_indices.sha256_compression.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.blake2s_constraints.push_back(Blake2sConstraint{ .inputs = map(arg.inputs, @@ -273,6 +292,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci }), .result = map(arg.outputs, [](auto& e) { return e.value; }), }); + af.original_opcode_indices.blake2s_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.blake3_constraints.push_back(Blake3Constraint{ .inputs = map(arg.inputs, @@ -284,6 +304,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci }), .result = map(arg.outputs, [](auto& e) { return e.value; }), }); + af.original_opcode_indices.blake3_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.schnorr_constraints.push_back(SchnorrConstraint{ .message = map(arg.message, [](auto& e) { return e.witness.value; }), @@ -292,6 +313,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .result = arg.output.value, .signature = map(arg.signature, [](auto& e) { return e.witness.value; }), }); + af.original_opcode_indices.schnorr_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.pedersen_constraints.push_back(PedersenConstraint{ .scalars = map(arg.inputs, [](auto& e) { return e.witness.value; }), @@ -299,12 +321,14 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .result_x = arg.outputs[0].value, .result_y = arg.outputs[1].value, }); + af.original_opcode_indices.pedersen_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.pedersen_hash_constraints.push_back(PedersenHashConstraint{ .scalars = map(arg.inputs, [](auto& e) { return e.witness.value; }), .hash_index = arg.domain_separator, .result = arg.output.value, }); + af.original_opcode_indices.pedersen_hash_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.ecdsa_k1_constraints.push_back(EcdsaSecp256k1Constraint{ .hashed_message = map(arg.hashed_message, [](auto& e) { return e.witness.value; }), @@ -313,6 +337,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .pub_y_indices = map(arg.public_key_y, [](auto& e) { return e.witness.value; }), .result = arg.output.value, }); + af.original_opcode_indices.ecdsa_k1_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.ecdsa_r1_constraints.push_back(EcdsaSecp256r1Constraint{ .hashed_message = map(arg.hashed_message, [](auto& e) { return e.witness.value; }), @@ -321,6 +346,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .result = arg.output.value, .signature = map(arg.signature, [](auto& e) { return e.witness.value; }), }); + af.original_opcode_indices.ecdsa_r1_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.multi_scalar_mul_constraints.push_back(MultiScalarMul{ .points = map(arg.points, [](auto& e) { return e.witness.value; }), @@ -329,6 +355,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .out_point_y = arg.outputs[1].value, .out_point_is_infinite = arg.outputs[2].value, }); + af.original_opcode_indices.multi_scalar_mul_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.ec_add_constraints.push_back(EcAdd{ .input1_x = arg.input1[0].witness.value, @@ -341,6 +368,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .result_y = arg.outputs[1].value, .result_infinite = arg.outputs[2].value, }); + af.original_opcode_indices.ec_add_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.keccak_constraints.push_back(KeccakConstraint{ .inputs = map(arg.inputs, @@ -353,11 +381,13 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .result = map(arg.outputs, [](auto& e) { return e.value; }), .var_message_size = arg.var_message_size.witness.value, }); + af.original_opcode_indices.keccak_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.keccak_permutations.push_back(Keccakf1600{ .state = map(arg.inputs, [](auto& e) { return e.witness.value; }), .result = map(arg.outputs, [](auto& e) { return e.value; }), }); + af.original_opcode_indices.keccak_permutations.push_back(opcode_index); } else if constexpr (std::is_same_v) { if (honk_recursion) { // if we're using the honk recursive verifier auto c = HonkRecursionConstraint{ @@ -366,6 +396,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .public_inputs = map(arg.public_inputs, [](auto& e) { return e.witness.value; }), }; af.honk_recursion_constraints.push_back(c); + af.original_opcode_indices.honk_recursion_constraints.push_back(opcode_index); } else { auto c = RecursionConstraint{ .key = map(arg.verification_key, [](auto& e) { return e.witness.value; }), @@ -374,6 +405,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .key_hash = arg.key_hash.witness.value, }; af.recursion_constraints.push_back(c); + af.original_opcode_indices.recursion_constraints.push_back(opcode_index); } } else if constexpr (std::is_same_v) { af.bigint_from_le_bytes_constraints.push_back(BigIntFromLeBytes{ @@ -381,11 +413,13 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .modulus = map(arg.modulus, [](auto& e) -> uint32_t { return e; }), .result = arg.output, }); + af.original_opcode_indices.bigint_from_le_bytes_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.bigint_to_le_bytes_constraints.push_back(BigIntToLeBytes{ .input = arg.input, .result = map(arg.outputs, [](auto& e) { return e.value; }), }); + af.original_opcode_indices.bigint_to_le_bytes_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.bigint_operations.push_back(BigIntOperation{ .lhs = arg.lhs, @@ -393,6 +427,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .result = arg.output, .opcode = BigIntOperationType::Add, }); + af.original_opcode_indices.bigint_operations.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.bigint_operations.push_back(BigIntOperation{ .lhs = arg.lhs, @@ -400,6 +435,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .result = arg.output, .opcode = BigIntOperationType::Sub, }); + af.original_opcode_indices.bigint_operations.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.bigint_operations.push_back(BigIntOperation{ .lhs = arg.lhs, @@ -407,6 +443,7 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .result = arg.output, .opcode = BigIntOperationType::Mul, }); + af.original_opcode_indices.bigint_operations.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.bigint_operations.push_back(BigIntOperation{ .lhs = arg.lhs, @@ -414,12 +451,14 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .result = arg.output, .opcode = BigIntOperationType::Div, }); + af.original_opcode_indices.bigint_operations.push_back(opcode_index); } else if constexpr (std::is_same_v) { af.poseidon2_constraints.push_back(Poseidon2Constraint{ .state = map(arg.inputs, [](auto& e) { return e.witness.value; }), .result = map(arg.outputs, [](auto& e) { return e.value; }), .len = arg.len, }); + af.original_opcode_indices.poseidon2_constraints.push_back(opcode_index); } }, arg.value.value); @@ -445,7 +484,8 @@ BlockConstraint handle_memory_init(Program::Opcode::MemoryInit const& mem_init) }); } - // Databus is only supported for Goblin, non Goblin builders will treat call_data and return_data as normal array. + // Databus is only supported for Goblin, non Goblin builders will treat call_data and return_data as normal + // array. if (IsMegaBuilder) { if (std::holds_alternative(mem_init.block_type.value)) { block.type = BlockType::CallData; @@ -490,32 +530,36 @@ AcirFormat circuit_serde_to_acir_format(Program::Circuit const& circuit, bool ho af.num_acir_opcodes = static_cast(circuit.opcodes.size()); af.public_inputs = join({ map(circuit.public_parameters.value, [](auto e) { return e.value; }), map(circuit.return_values.value, [](auto e) { return e.value; }) }); - std::map block_id_to_block_constraint; - for (auto gate : circuit.opcodes) { + // Map to a pair of: BlockConstraint, and list of opcodes associated with that BlockConstraint + std::unordered_map>> block_id_to_block_constraint; + for (size_t i = 0; i < circuit.opcodes.size(); ++i) { + auto gate = circuit.opcodes[i]; std::visit( [&](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { - handle_arithmetic(arg, af); + handle_arithmetic(arg, af, i); } else if constexpr (std::is_same_v) { - handle_blackbox_func_call(arg, af, honk_recursion); + handle_blackbox_func_call(arg, af, honk_recursion, i); } else if constexpr (std::is_same_v) { auto block = handle_memory_init(arg); uint32_t block_id = arg.block_id.value; - block_id_to_block_constraint[block_id] = block; + block_id_to_block_constraint[block_id] = std::make_pair(block, std::vector()); } else if constexpr (std::is_same_v) { auto block = block_id_to_block_constraint.find(arg.block_id.value); if (block == block_id_to_block_constraint.end()) { throw_or_abort("unitialized MemoryOp"); } - handle_memory_op(arg, block->second); + handle_memory_op(arg, block->second.first); + block->second.second.push_back(i); } }, gate.value); } for (const auto& [block_id, block] : block_id_to_block_constraint) { - if (!block.trace.empty()) { - af.block_constraints.push_back(block); + if (!block.first.trace.empty()) { + af.block_constraints.push_back(block.first); + af.original_opcode_indices.block_constraints.push_back(block.second); } } return af; @@ -523,9 +567,9 @@ AcirFormat circuit_serde_to_acir_format(Program::Circuit const& circuit, bool ho AcirFormat circuit_buf_to_acir_format(std::vector const& buf, bool honk_recursion) { - // TODO(https://github.com/AztecProtocol/barretenberg/issues/927): Move to using just `program_buf_to_acir_format` - // once Honk fully supports all ACIR test flows - // For now the backend still expects to work with a single ACIR function + // TODO(https://github.com/AztecProtocol/barretenberg/issues/927): Move to using just + // `program_buf_to_acir_format` once Honk fully supports all ACIR test flows For now the backend still expects + // to work with a single ACIR function auto circuit = Program::Program::bincodeDeserialize(buf).functions[0]; return circuit_serde_to_acir_format(circuit, honk_recursion); @@ -567,9 +611,9 @@ WitnessVector witness_map_to_witness_vector(WitnessStack::WitnessMap const& witn */ WitnessVector witness_buf_to_witness_data(std::vector const& buf) { - // TODO(https://github.com/AztecProtocol/barretenberg/issues/927): Move to using just `witness_buf_to_witness_stack` - // once Honk fully supports all ACIR test flows. - // For now the backend still expects to work with the stop of the `WitnessStack`. + // TODO(https://github.com/AztecProtocol/barretenberg/issues/927): Move to using just + // `witness_buf_to_witness_stack` once Honk fully supports all ACIR test flows. For now the backend still + // expects to work with the stop of the `WitnessStack`. auto witness_stack = WitnessStack::WitnessStack::bincodeDeserialize(buf); auto w = witness_stack.stack[witness_stack.stack.size() - 1].witness; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp index 69d40ffe965..4ce4f77b934 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp @@ -1,5 +1,6 @@ #include "bigint_constraint.hpp" #include "acir_format.hpp" +#include "acir_format_mocks.hpp" #include "barretenberg/circuit_checker/circuit_checker.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" @@ -199,12 +200,14 @@ TEST_F(BigIntTests, TestBigIntConstraintMultiple) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; apply_constraints(constraint_system, contraints); apply_constraints(constraint_system, contraints2); apply_constraints(constraint_system, contraints3); apply_constraints(constraint_system, contraints4); apply_constraints(constraint_system, contraints5); + mock_opcode_indices(constraint_system); constraint_system.varnum = static_cast(witness.size() + 1); auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness); @@ -270,8 +273,9 @@ TEST_F(BigIntTests, TestBigIntConstraintSimple) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, - + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); WitnessVector witness{ 0, 3, 6, 3, 0, @@ -326,6 +330,7 @@ TEST_F(BigIntTests, TestBigIntConstraintReuse) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; apply_constraints(constraint_system, contraints); apply_constraints(constraint_system, contraints2); @@ -336,6 +341,7 @@ TEST_F(BigIntTests, TestBigIntConstraintReuse) constraint_system.bigint_to_le_bytes_constraints.push_back(get<1>(contraints5)); constraint_system.bigint_operations.push_back(get<0>(contraints5)); constraint_system.varnum = static_cast(witness.size() + 1); + mock_opcode_indices(constraint_system); auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness); @@ -386,6 +392,7 @@ TEST_F(BigIntTests, TestBigIntConstraintReuse2) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; apply_constraints(constraint_system, contraints); apply_constraints(constraint_system, contraints2); @@ -396,6 +403,7 @@ TEST_F(BigIntTests, TestBigIntConstraintReuse2) constraint_system.bigint_to_le_bytes_constraints.push_back(get<1>(contraints5)); constraint_system.bigint_operations.push_back(get<0>(contraints5)); constraint_system.varnum = static_cast(witness.size() + 1); + mock_opcode_indices(constraint_system); auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness); @@ -467,8 +475,9 @@ TEST_F(BigIntTests, TestBigIntDIV) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, - + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); WitnessVector witness{ 0, 6, 3, 2, 0, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp index 5da46acc95e..9affaf9f9f5 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp @@ -1,5 +1,6 @@ #include "block_constraint.hpp" #include "acir_format.hpp" +#include "acir_format_mocks.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/plonk/proof_system/types/proof.hpp" #include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" @@ -167,7 +168,9 @@ TEST_F(UltraPlonkRAM, TestBlockConstraint) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = { block }, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness_values); @@ -216,7 +219,9 @@ TEST_F(MegaHonk, Databus) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = { block }, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); // Construct a bberg circuit from the acir representation auto circuit = acir_format::create_circuit(constraint_system, 0, witness_values); @@ -293,7 +298,7 @@ TEST_F(MegaHonk, DatabusReturn) AcirFormat constraint_system{ .varnum = static_cast(num_variables), .recursive = false, - .num_acir_opcodes = 1, + .num_acir_opcodes = 2, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, @@ -320,7 +325,9 @@ TEST_F(MegaHonk, DatabusReturn) .poly_triple_constraints = { assert_equal }, .quad_constraints = {}, .block_constraints = { block }, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); // Construct a bberg circuit from the acir representation auto circuit = acir_format::create_circuit(constraint_system, 0, witness_values); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp index c006186494d..1dacfe85a0f 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp @@ -1,5 +1,6 @@ #include "ec_operations.hpp" #include "acir_format.hpp" +#include "acir_format_mocks.hpp" #include "barretenberg/circuit_checker/circuit_checker.hpp" #include "barretenberg/plonk/composer/standard_composer.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" @@ -89,7 +90,9 @@ TEST_F(EcOperations, TestECOperations) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness_values); @@ -175,7 +178,9 @@ TEST_F(EcOperations, TestECMultiScalarMul) .poly_triple_constraints = { assert_equal }, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness_values); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp index 88140210210..6026f0cf31e 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -1,5 +1,6 @@ #include "ecdsa_secp256k1.hpp" #include "acir_format.hpp" +#include "acir_format_mocks.hpp" #include "barretenberg/crypto/ecdsa/ecdsa.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/plonk/proof_system/types/proof.hpp" @@ -121,7 +122,9 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintSucceed) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness_values); @@ -173,7 +176,9 @@ TEST_F(ECDSASecp256k1, TestECDSACompilesForVerifier) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); auto builder = create_circuit(constraint_system); } @@ -220,7 +225,9 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintFail) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness_values); EXPECT_EQ(builder.get_variable(ecdsa_k1_constraint.result), 0); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp index ac0d9a31938..68f0f416dbe 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp @@ -1,5 +1,6 @@ #include "ecdsa_secp256r1.hpp" #include "acir_format.hpp" +#include "acir_format_mocks.hpp" #include "barretenberg/crypto/ecdsa/ecdsa.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/plonk/proof_system/types/proof.hpp" @@ -155,7 +156,9 @@ TEST(ECDSASecp256r1, test_hardcoded) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); secp256r1::g1::affine_element pub_key = { pub_key_x, pub_key_y }; bool we_ballin = @@ -209,7 +212,9 @@ TEST(ECDSASecp256r1, TestECDSAConstraintSucceed) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness_values); @@ -261,7 +266,10 @@ TEST(ECDSASecp256r1, TestECDSACompilesForVerifier) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); + auto builder = create_circuit(constraint_system); } @@ -308,7 +316,9 @@ TEST(ECDSASecp256r1, TestECDSAConstraintFail) .poly_triple_constraints = {}, .quad_constraints = {}, .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), }; + mock_opcode_indices(constraint_system); auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness_values); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp index 3b653fecc18..61259910828 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp @@ -1,5 +1,6 @@ #include "honk_recursion_constraint.hpp" #include "acir_format.hpp" +#include "acir_format_mocks.hpp" #include "barretenberg/sumcheck/instance/prover_instance.hpp" #include "barretenberg/ultra_honk/ultra_prover.hpp" #include "barretenberg/ultra_honk/ultra_verifier.hpp" @@ -86,35 +87,39 @@ class AcirHonkRecursionConstraint : public ::testing::Test { .q_c = 1, }; - AcirFormat constraint_system{ .varnum = 6, - .recursive = true, - .num_acir_opcodes = 7, - .public_inputs = { 1, 2 }, - .logic_constraints = { logic_constraint }, - .range_constraints = { range_a, range_b }, - .aes128_constraints = {}, - .sha256_constraints = {}, - .sha256_compression = {}, - .schnorr_constraints = {}, - .ecdsa_k1_constraints = {}, - .ecdsa_r1_constraints = {}, - .blake2s_constraints = {}, - .blake3_constraints = {}, - .keccak_constraints = {}, - .keccak_permutations = {}, - .pedersen_constraints = {}, - .pedersen_hash_constraints = {}, - .poseidon2_constraints = {}, - .multi_scalar_mul_constraints = {}, - .ec_add_constraints = {}, - .recursion_constraints = {}, - .honk_recursion_constraints = {}, - .bigint_from_le_bytes_constraints = {}, - .bigint_to_le_bytes_constraints = {}, - .bigint_operations = {}, - .poly_triple_constraints = { expr_a, expr_b, expr_c, expr_d }, - .quad_constraints = {}, - .block_constraints = {} }; + AcirFormat constraint_system{ + .varnum = 6, + .recursive = true, + .num_acir_opcodes = 7, + .public_inputs = { 1, 2 }, + .logic_constraints = { logic_constraint }, + .range_constraints = { range_a, range_b }, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = {}, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = {}, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = {}, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = {}, + .honk_recursion_constraints = {}, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = { expr_a, expr_b, expr_c, expr_d }, + .quad_constraints = {}, + .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + mock_opcode_indices(constraint_system); uint256_t inverse_of_five = fr(5).invert(); WitnessVector witness{ @@ -244,36 +249,42 @@ class AcirHonkRecursionConstraint : public ::testing::Test { witness_offset = key_indices_start_idx + key_witnesses.size(); } - AcirFormat constraint_system{ .varnum = static_cast(witness.size()), - .recursive = false, - .num_acir_opcodes = static_cast(honk_recursion_constraints.size()), - .public_inputs = {}, - .logic_constraints = {}, - .range_constraints = {}, - .aes128_constraints = {}, - .sha256_constraints = {}, - .sha256_compression = {}, - .schnorr_constraints = {}, - .ecdsa_k1_constraints = {}, - .ecdsa_r1_constraints = {}, - .blake2s_constraints = {}, - .blake3_constraints = {}, - .keccak_constraints = {}, - .keccak_permutations = {}, - .pedersen_constraints = {}, - .pedersen_hash_constraints = {}, - .poseidon2_constraints = {}, - .multi_scalar_mul_constraints = {}, - .ec_add_constraints = {}, - .recursion_constraints = {}, - .honk_recursion_constraints = honk_recursion_constraints, - .bigint_from_le_bytes_constraints = {}, - .bigint_to_le_bytes_constraints = {}, - .bigint_operations = {}, - .poly_triple_constraints = {}, - .quad_constraints = {}, - .block_constraints = {} }; - + std::vector honk_recursion_opcode_indices(honk_recursion_constraints.size()); + std::iota(honk_recursion_opcode_indices.begin(), honk_recursion_opcode_indices.end(), 0); + + AcirFormat constraint_system{ + .varnum = static_cast(witness.size()), + .recursive = false, + .num_acir_opcodes = static_cast(honk_recursion_constraints.size()), + .public_inputs = {}, + .logic_constraints = {}, + .range_constraints = {}, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = {}, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = {}, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = {}, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = {}, + .honk_recursion_constraints = honk_recursion_constraints, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = {}, + .quad_constraints = {}, + .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + mock_opcode_indices(constraint_system); auto outer_circuit = create_circuit(constraint_system, /*size_hint*/ 0, witness, /*honk recursion*/ true); return outer_circuit; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/poseidon2_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/poseidon2_constraint.test.cpp index dd01d0b9b3e..cc144fa6fd9 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/poseidon2_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/poseidon2_constraint.test.cpp @@ -1,5 +1,6 @@ #include "poseidon2_constraint.hpp" #include "acir_format.hpp" +#include "acir_format_mocks.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/plonk/proof_system/types/proof.hpp" @@ -33,35 +34,39 @@ TEST_F(Poseidon2Tests, TestPoseidon2Permutation) .len = 4, }; - AcirFormat constraint_system{ .varnum = 9, - .recursive = false, - .num_acir_opcodes = 1, - .public_inputs = {}, - .logic_constraints = {}, - .range_constraints = {}, - .aes128_constraints = {}, - .sha256_constraints = {}, - .sha256_compression = {}, - .schnorr_constraints = {}, - .ecdsa_k1_constraints = {}, - .ecdsa_r1_constraints = {}, - .blake2s_constraints = {}, - .blake3_constraints = {}, - .keccak_constraints = {}, - .keccak_permutations = {}, - .pedersen_constraints = {}, - .pedersen_hash_constraints = {}, - .poseidon2_constraints = { poseidon2_constraint }, - .multi_scalar_mul_constraints = {}, - .ec_add_constraints = {}, - .recursion_constraints = {}, - .honk_recursion_constraints = {}, - .bigint_from_le_bytes_constraints = {}, - .bigint_to_le_bytes_constraints = {}, - .bigint_operations = {}, - .poly_triple_constraints = {}, - .quad_constraints = {}, - .block_constraints = {} }; + AcirFormat constraint_system{ + .varnum = 9, + .recursive = false, + .num_acir_opcodes = 1, + .public_inputs = {}, + .logic_constraints = {}, + .range_constraints = {}, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = {}, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = {}, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = { poseidon2_constraint }, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = {}, + .honk_recursion_constraints = {}, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = {}, + .quad_constraints = {}, + .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + mock_opcode_indices(constraint_system); WitnessVector witness{ 1, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index 2715bc873a9..72ea7e939ac 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -1,5 +1,6 @@ #include "recursion_constraint.hpp" #include "acir_format.hpp" +#include "acir_format_mocks.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/plonk/proof_system/types/proof.hpp" #include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" @@ -84,35 +85,39 @@ Builder create_inner_circuit() .q_c = 1, }; - AcirFormat constraint_system{ .varnum = 6, - .recursive = true, - .num_acir_opcodes = 7, - .public_inputs = { 1, 2 }, - .logic_constraints = { logic_constraint }, - .range_constraints = { range_a, range_b }, - .aes128_constraints = {}, - .sha256_constraints = {}, - .sha256_compression = {}, - .schnorr_constraints = {}, - .ecdsa_k1_constraints = {}, - .ecdsa_r1_constraints = {}, - .blake2s_constraints = {}, - .blake3_constraints = {}, - .keccak_constraints = {}, - .keccak_permutations = {}, - .pedersen_constraints = {}, - .pedersen_hash_constraints = {}, - .poseidon2_constraints = {}, - .multi_scalar_mul_constraints = {}, - .ec_add_constraints = {}, - .recursion_constraints = {}, - .honk_recursion_constraints = {}, - .bigint_from_le_bytes_constraints = {}, - .bigint_to_le_bytes_constraints = {}, - .bigint_operations = {}, - .poly_triple_constraints = { expr_a, expr_b, expr_c, expr_d }, - .quad_constraints = {}, - .block_constraints = {} }; + AcirFormat constraint_system{ + .varnum = 6, + .recursive = true, + .num_acir_opcodes = 7, + .public_inputs = { 1, 2 }, + .logic_constraints = { logic_constraint }, + .range_constraints = { range_a, range_b }, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = {}, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = {}, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = {}, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = {}, + .honk_recursion_constraints = {}, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = { expr_a, expr_b, expr_c, expr_d }, + .quad_constraints = {}, + .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + mock_opcode_indices(constraint_system); uint256_t inverse_of_five = fr(5).invert(); WitnessVector witness{ @@ -239,35 +244,42 @@ Builder create_outer_circuit(std::vector& inner_circuits) witness_offset = key_indices_start_idx + key_witnesses.size(); } - AcirFormat constraint_system{ .varnum = static_cast(witness.size()), - .recursive = false, - .num_acir_opcodes = static_cast(recursion_constraints.size()), - .public_inputs = {}, - .logic_constraints = {}, - .range_constraints = {}, - .aes128_constraints = {}, - .sha256_constraints = {}, - .sha256_compression = {}, - .schnorr_constraints = {}, - .ecdsa_k1_constraints = {}, - .ecdsa_r1_constraints = {}, - .blake2s_constraints = {}, - .blake3_constraints = {}, - .keccak_constraints = {}, - .keccak_permutations = {}, - .pedersen_constraints = {}, - .pedersen_hash_constraints = {}, - .poseidon2_constraints = {}, - .multi_scalar_mul_constraints = {}, - .ec_add_constraints = {}, - .recursion_constraints = recursion_constraints, - .honk_recursion_constraints = {}, - .bigint_from_le_bytes_constraints = {}, - .bigint_to_le_bytes_constraints = {}, - .bigint_operations = {}, - .poly_triple_constraints = {}, - .quad_constraints = {}, - .block_constraints = {} }; + std::vector recursion_opcode_indices(recursion_constraints.size()); + std::iota(recursion_opcode_indices.begin(), recursion_opcode_indices.end(), 0); + + AcirFormat constraint_system{ + .varnum = static_cast(witness.size()), + .recursive = false, + .num_acir_opcodes = static_cast(recursion_constraints.size()), + .public_inputs = {}, + .logic_constraints = {}, + .range_constraints = {}, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = {}, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = {}, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = {}, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = recursion_constraints, + .honk_recursion_constraints = {}, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = {}, + .quad_constraints = {}, + .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + mock_opcode_indices(constraint_system); auto outer_circuit = create_circuit(constraint_system, /*size_hint*/ 0, witness); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.test.cpp index 902ac2944e9..72936adc776 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.test.cpp @@ -1,5 +1,6 @@ #include "sha256_constraint.hpp" #include "acir_format.hpp" +#include "acir_format_mocks.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/plonk/proof_system/types/proof.hpp" #include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" @@ -34,35 +35,39 @@ TEST_F(Sha256Tests, TestSha256Compression) .result = { 25, 26, 27, 28, 29, 30, 31, 32 }, }; - AcirFormat constraint_system{ .varnum = 34, - .recursive = false, - .num_acir_opcodes = 1, - .public_inputs = {}, - .logic_constraints = {}, - .range_constraints = {}, - .aes128_constraints = {}, - .sha256_constraints = {}, - .sha256_compression = { sha256_compression }, - .schnorr_constraints = {}, - .ecdsa_k1_constraints = {}, - .ecdsa_r1_constraints = {}, - .blake2s_constraints = {}, - .blake3_constraints = {}, - .keccak_constraints = {}, - .keccak_permutations = {}, - .pedersen_constraints = {}, - .pedersen_hash_constraints = {}, - .poseidon2_constraints = {}, - .multi_scalar_mul_constraints = {}, - .ec_add_constraints = {}, - .recursion_constraints = {}, - .honk_recursion_constraints = {}, - .bigint_from_le_bytes_constraints = {}, - .bigint_to_le_bytes_constraints = {}, - .bigint_operations = {}, - .poly_triple_constraints = {}, - .quad_constraints = {}, - .block_constraints = {} }; + AcirFormat constraint_system{ + .varnum = 34, + .recursive = false, + .num_acir_opcodes = 1, + .public_inputs = {}, + .logic_constraints = {}, + .range_constraints = {}, + .aes128_constraints = {}, + .sha256_constraints = {}, + .sha256_compression = { sha256_compression }, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .blake3_constraints = {}, + .keccak_constraints = {}, + .keccak_permutations = {}, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .poseidon2_constraints = {}, + .multi_scalar_mul_constraints = {}, + .ec_add_constraints = {}, + .recursion_constraints = {}, + .honk_recursion_constraints = {}, + .bigint_from_le_bytes_constraints = {}, + .bigint_to_le_bytes_constraints = {}, + .bigint_operations = {}, + .poly_triple_constraints = {}, + .quad_constraints = {}, + .block_constraints = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + mock_opcode_indices(constraint_system); WitnessVector witness{ 0, 0, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index b54244cf9bb..3775ac65f83 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -28,10 +28,13 @@ AcirComposer::AcirComposer(size_t size_hint, bool verbose) * @param witness */ template -void AcirComposer::create_circuit(acir_format::AcirFormat& constraint_system, WitnessVector const& witness) +void AcirComposer::create_circuit(acir_format::AcirFormat& constraint_system, + WitnessVector const& witness, + bool collect_gates_per_opcode) { vinfo("building circuit..."); - builder_ = acir_format::create_circuit(constraint_system, size_hint_, witness); + builder_ = acir_format::create_circuit( + constraint_system, size_hint_, witness, false, std::make_shared(), collect_gates_per_opcode); vinfo("gates: ", builder_.get_total_circuit_size()); vinfo("circuit is recursive friendly: ", builder_.is_recursive_circuit); } @@ -144,6 +147,7 @@ std::vector AcirComposer::serialize_verification_key_into_fields() } template void AcirComposer::create_circuit(acir_format::AcirFormat& constraint_system, - WitnessVector const& witness); + WitnessVector const& witness, + bool collect_gates_per_opcode); } // namespace acir_proofs diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp index e15c59fa60d..f53011ec374 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp @@ -17,7 +17,9 @@ class AcirComposer { AcirComposer(size_t size_hint = 0, bool verbose = true); template - void create_circuit(acir_format::AcirFormat& constraint_system, WitnessVector const& witness = {}); + void create_circuit(acir_format::AcirFormat& constraint_system, + WitnessVector const& witness = {}, + bool collect_gates_per_opcode = false); std::shared_ptr init_proving_key(); diff --git a/noir-projects/noir-contracts/extractFunctionAsNoirArtifact.js b/noir-projects/noir-contracts/extractFunctionAsNoirArtifact.js new file mode 100644 index 00000000000..842645d860f --- /dev/null +++ b/noir-projects/noir-contracts/extractFunctionAsNoirArtifact.js @@ -0,0 +1,51 @@ +const path = require("path"); +const fs = require("fs").promises; + +// Simple script to extract a contract function as a separate Noir artifact. +// We need to use this since the transpiling that we do on public functions make the contract artifacts +// unreadable by noir tooling, since they are no longer following the noir artifact format. +async function main() { + let [contractArtifactPath, functionName] = process.argv.slice(2); + if (!contractArtifactPath || !functionName) { + console.log( + "Usage: node extractFunctionAsNoirArtifact.js " + ); + return; + } + + const contractArtifact = JSON.parse( + await fs.readFile(contractArtifactPath, "utf8") + ); + const func = contractArtifact.functions.find((f) => f.name === functionName); + if (!func) { + console.error( + `Function ${functionName} not found in ${contractArtifactPath}` + ); + return; + } + + const artifact = { + noir_version: contractArtifact.noir_version, + hash: 0, + abi: func.abi, + bytecode: func.bytecode, + debug_symbols: func.debug_symbols, + file_map: contractArtifact.file_map, + names: ["main"], + }; + + const outputDir = path.dirname(contractArtifactPath); + const outputName = + path.basename(contractArtifactPath, ".json") + `-${functionName}.json`; + + const outPath = path.join(outputDir, outputName); + + console.log(`Writing to ${outPath}`); + + await fs.writeFile(outPath, JSON.stringify(artifact, null, 2)); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index bd575d0dca2..a427e7cc298 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -1186,6 +1186,19 @@ dependencies = [ "syn 2.0.64", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.8", +] + [[package]] name = "debugid" version = "0.8.0" @@ -1382,6 +1395,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -2017,12 +2039,17 @@ dependencies = [ [[package]] name = "inferno" -version = "0.11.15" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb7c1b80a1dfa604bb4a649a5c5aeef3d913f7c520cb42b40e534e8a61bcdfc" +checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" dependencies = [ "ahash 0.8.11", - "indexmap 1.9.3", + "clap", + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "env_logger", + "indexmap 2.2.6", "is-terminal", "itoa", "log", @@ -2698,6 +2725,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "noir_profiler" +version = "0.30.0" +dependencies = [ + "acir", + "clap", + "codespan-reporting", + "color-eyre", + "const_format", + "fm", + "im", + "inferno", + "nargo", + "noirc_abi", + "noirc_driver", + "noirc_errors", + "serde", + "serde_json", + "tempfile", + "tracing-appender", + "tracing-subscriber", +] + [[package]] name = "noir_wasm" version = "0.30.0" diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index c08e9f9f38b..be4cd81ab58 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -22,6 +22,7 @@ members = [ "tooling/noirc_abi", "tooling/noirc_abi_wasm", "tooling/acvm_cli", + "tooling/profiler", # ACVM "acvm-repo/acir_field", "acvm-repo/acir", @@ -34,7 +35,7 @@ members = [ # Utility crates "utils/iter-extended", ] -default-members = ["tooling/nargo_cli", "tooling/acvm_cli"] +default-members = ["tooling/nargo_cli", "tooling/acvm_cli", "tooling/profiler"] resolver = "2" [workspace.package] @@ -80,7 +81,7 @@ acvm_cli = { path = "tooling/acvm_cli" } # Arkworks ark-bn254 = { version = "^0.4.0", default-features = false, features = ["curve"] } ark-bls12-381 = { version = "^0.4.0", default-features = false, features = ["curve"] } -grumpkin = { version = "0.1.0", package = "noir_grumpkin", features = ["std"] } +grumpkin = { version = "0.1.0", package = "noir_grumpkin", features = ["std"] } ark-ec = { version = "^0.4.0", default-features = false } ark-ff = { version = "^0.4.0", default-features = false } ark-std = { version = "^0.4.0", default-features = false } @@ -139,9 +140,9 @@ similar-asserts = "1.5.0" tempfile = "3.6.0" jsonrpc = { version = "0.16.0", features = ["minreq_http"] } flate2 = "1.0.24" +color-eyre = "0.6.2" rand = "0.8.5" proptest = "1.2.0" - im = { version = "15.1", features = ["serde"] } tracing = "0.1.40" tracing-web = "0.1.3" diff --git a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs index cb5abbe2079..42cab72345d 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs @@ -202,14 +202,14 @@ fn stack_trace<'files>( let path = files.name(call_item.file).expect("should get file path"); let source = files.source(call_item.file).expect("should get file source"); - let (line, column) = location(source.as_ref(), call_item.span.start()); + let (line, column) = line_and_column_from_span(source.as_ref(), &call_item.span); result += &format!("{}. {}:{}:{}\n", i + 1, path, line, column); } result } -fn location(source: &str, span_start: u32) -> (u32, u32) { +pub fn line_and_column_from_span(source: &str, span: &Span) -> (u32, u32) { let mut line = 1; let mut column = 0; @@ -221,7 +221,7 @@ fn location(source: &str, span_start: u32) -> (u32, u32) { column = 0; } - if span_start <= i as u32 { + if span.start() <= i as u32 { break; } } diff --git a/noir/noir-repo/tooling/acvm_cli/Cargo.toml b/noir/noir-repo/tooling/acvm_cli/Cargo.toml index 1cfd1f3b270..a592f2d65f3 100644 --- a/noir/noir-repo/tooling/acvm_cli/Cargo.toml +++ b/noir/noir-repo/tooling/acvm_cli/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] thiserror.workspace = true toml.workspace = true -color-eyre = "0.6.2" +color-eyre.workspace = true clap.workspace = true acvm.workspace = true nargo.workspace = true diff --git a/noir/noir-repo/tooling/nargo_cli/Cargo.toml b/noir/noir-repo/tooling/nargo_cli/Cargo.toml index a9946c8700c..7c7e7b9d5ca 100644 --- a/noir/noir-repo/tooling/nargo_cli/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_cli/Cargo.toml @@ -41,11 +41,16 @@ prettytable-rs = "0.10" rayon = "1.8.0" thiserror.workspace = true tower.workspace = true -async-lsp = { workspace = true, features = ["client-monitor", "stdio", "tracing", "tokio"] } +async-lsp = { workspace = true, features = [ + "client-monitor", + "stdio", + "tracing", + "tokio", +] } const_format.workspace = true similar-asserts.workspace = true termcolor = "1.1.2" -color-eyre = "0.6.2" +color-eyre.workspace = true tokio = { version = "1.0", features = ["io-std", "rt"] } dap.workspace = true clap-markdown = { git = "https://github.com/noir-lang/clap-markdown", rev = "450d759532c88f0dba70891ceecdbc9ff8f25d2b", optional = true } diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index 514fac2e73d..86c982b5b0d 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -248,7 +248,7 @@ pub struct AbiReturnType { pub visibility: AbiVisibility, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct Abi { /// An ordered list of the arguments to the program's `main` function, specifying their types and visibility. pub parameters: Vec, diff --git a/noir/noir-repo/tooling/profiler/Cargo.toml b/noir/noir-repo/tooling/profiler/Cargo.toml new file mode 100644 index 00000000000..baebe9292e6 --- /dev/null +++ b/noir/noir-repo/tooling/profiler/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "noir_profiler" +description = "Profiler for noir circuits" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "noir-profiler" +path = "src/main.rs" + +[dependencies] +color-eyre.workspace = true +clap.workspace = true +nargo.workspace = true +const_format.workspace = true +serde.workspace = true +serde_json.workspace = true +fm.workspace = true +codespan-reporting.workspace = true +inferno = "0.11.19" +im.workspace = true +acir.workspace = true +noirc_errors.workspace = true + +# Logs +tracing-subscriber.workspace = true +tracing-appender = "0.2.3" + +[dev-dependencies] +noirc_abi.workspace = true +noirc_driver.workspace = true +tempfile.workspace = true + +[features] +default = ["bn254"] +bn254 = ["acir/bn254"] diff --git a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs new file mode 100644 index 00000000000..4f51eed4ba3 --- /dev/null +++ b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs @@ -0,0 +1,486 @@ +use std::collections::BTreeMap; +use std::io::BufWriter; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use clap::Args; +use codespan_reporting::files::Files; +use color_eyre::eyre::{self, Context}; +use inferno::flamegraph::{from_lines, Options}; +use nargo::artifacts::debug::DebugArtifact; +use serde::{Deserialize, Serialize}; + +use acir::circuit::OpcodeLocation; +use nargo::artifacts::program::ProgramArtifact; +use nargo::errors::Location; +use noirc_errors::reporter::line_and_column_from_span; + +#[derive(Debug, Clone, Args)] +pub(crate) struct GatesFlamegraphCommand { + /// The path to the artifact JSON file + #[clap(long, short)] + artifact_path: String, + + /// Path to the noir backend binary + #[clap(long, short)] + backend_path: String, + + /// The output folder for the flamegraph svg files + #[clap(long, short)] + output: String, +} + +trait GatesProvider { + fn get_gates(&self, artifact_path: &Path) -> eyre::Result; +} + +struct BackendGatesProvider { + backend_path: PathBuf, +} + +impl GatesProvider for BackendGatesProvider { + fn get_gates(&self, artifact_path: &Path) -> eyre::Result { + let backend_gates_response = + Command::new(&self.backend_path).arg("gates").arg("-b").arg(artifact_path).output()?; + + // Parse the backend gates command stdout as json + let backend_gates_response: BackendGatesResponse = + serde_json::from_slice(&backend_gates_response.stdout)?; + Ok(backend_gates_response) + } +} + +trait FlamegraphGenerator { + fn generate_flamegraph<'lines, I: IntoIterator>( + &self, + folded_lines: I, + artifact_name: &str, + function_name: &str, + output_path: &Path, + ) -> eyre::Result<()>; +} + +struct InfernoFlamegraphGenerator {} + +impl FlamegraphGenerator for InfernoFlamegraphGenerator { + fn generate_flamegraph<'lines, I: IntoIterator>( + &self, + folded_lines: I, + artifact_name: &str, + function_name: &str, + output_path: &Path, + ) -> eyre::Result<()> { + let flamegraph_file = std::fs::File::create(output_path)?; + let flamegraph_writer = BufWriter::new(flamegraph_file); + + let mut options = Options::default(); + options.hash = true; + options.deterministic = true; + options.title = format!("{}-{}", artifact_name, function_name); + options.subtitle = Some("Sample = Gate".to_string()); + options.frame_height = 24; + options.color_diffusion = true; + + from_lines(&mut options, folded_lines, flamegraph_writer)?; + + Ok(()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct BackendGatesReport { + acir_opcodes: usize, + circuit_size: usize, + gates_per_opcode: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct BackendGatesResponse { + functions: Vec, +} + +struct FoldedStackItem { + total_gates: usize, + nested_items: BTreeMap, +} + +pub(crate) fn run(args: GatesFlamegraphCommand) -> eyre::Result<()> { + run_with_provider( + &PathBuf::from(args.artifact_path), + &BackendGatesProvider { backend_path: PathBuf::from(args.backend_path) }, + &InfernoFlamegraphGenerator {}, + &PathBuf::from(args.output), + ) +} + +fn run_with_provider( + artifact_path: &Path, + gates_provider: &Provider, + flamegraph_generator: &Generator, + output_path: &Path, +) -> eyre::Result<()> { + let program = + read_program_from_file(artifact_path).context("Error reading program from file")?; + + let backend_gates_response = + gates_provider.get_gates(artifact_path).context("Error querying backend for gates")?; + + let function_names = program.names.clone(); + + let debug_artifact: DebugArtifact = program.into(); + + for (func_idx, (func_gates, func_name)) in + backend_gates_response.functions.into_iter().zip(function_names).enumerate() + { + println!( + "Opcode count: {}, Total gates by opcodes: {}, Circuit size: {}", + func_gates.acir_opcodes, + func_gates.gates_per_opcode.iter().sum::(), + func_gates.circuit_size + ); + + // Create a nested hashmap with the stack items, folding the gates for all the callsites that are equal + let mut folded_stack_items = BTreeMap::new(); + + func_gates.gates_per_opcode.into_iter().enumerate().for_each(|(opcode_index, gates)| { + let call_stack = &debug_artifact.debug_symbols[func_idx] + .locations + .get(&OpcodeLocation::Acir(opcode_index)); + let location_names = if let Some(call_stack) = call_stack { + call_stack + .iter() + .map(|location| location_to_callsite_label(*location, &debug_artifact)) + .collect::>() + } else { + vec!["unknown".to_string()] + }; + + add_locations_to_folded_stack_items(&mut folded_stack_items, location_names, gates); + }); + let folded_lines = to_folded_sorted_lines(&folded_stack_items, Default::default()); + + flamegraph_generator.generate_flamegraph( + folded_lines.iter().map(|as_string| as_string.as_str()), + artifact_path.to_str().unwrap(), + &func_name, + &Path::new(&output_path).join(Path::new(&format!("{}.svg", &func_name))), + )?; + } + + Ok(()) +} + +pub(crate) fn read_program_from_file>( + circuit_path: P, +) -> eyre::Result { + let file_path = circuit_path.as_ref().with_extension("json"); + + let input_string = std::fs::read(file_path)?; + let program = serde_json::from_slice(&input_string)?; + + Ok(program) +} + +fn location_to_callsite_label<'files>( + location: Location, + files: &'files impl Files<'files, FileId = fm::FileId>, +) -> String { + let filename = + Path::new(&files.name(location.file).expect("should have a file path").to_string()) + .file_name() + .map(|os_str| os_str.to_string_lossy().to_string()) + .unwrap_or("invalid_path".to_string()); + let source = files.source(location.file).expect("should have a file source"); + + let code_slice = source + .as_ref() + .chars() + .skip(location.span.start() as usize) + .take(location.span.end() as usize - location.span.start() as usize) + .collect::(); + + // ";" is used for frame separation, and is not allowed by inferno + // Check code slice for ";" and replace it with 'GREEK QUESTION MARK' (U+037E) + let code_slice = code_slice.replace(';', "\u{037E}"); + + let (line, column) = line_and_column_from_span(source.as_ref(), &location.span); + + format!("{}:{}:{}::{}", filename, line, column, code_slice) +} + +fn add_locations_to_folded_stack_items( + stack_items: &mut BTreeMap, + locations: Vec, + gates: usize, +) { + let mut child_map = stack_items; + for (index, location) in locations.iter().enumerate() { + let current_item = child_map + .entry(location.clone()) + .or_insert(FoldedStackItem { total_gates: 0, nested_items: BTreeMap::new() }); + + child_map = &mut current_item.nested_items; + + if index == locations.len() - 1 { + current_item.total_gates += gates; + } + } +} + +/// Creates a vector of lines in the format that inferno expects from a nested hashmap of stack items +/// The lines have to be sorted in the following way, exploring the graph in a depth-first manner: +/// main 100 +/// main::foo 0 +/// main::foo::bar 200 +/// main::baz 27 +/// main::baz::qux 800 +fn to_folded_sorted_lines( + folded_stack_items: &BTreeMap, + parent_stacks: im::Vector, +) -> Vec { + folded_stack_items + .iter() + .flat_map(move |(location, folded_stack_item)| { + let frame_list: Vec = + parent_stacks.iter().cloned().chain(std::iter::once(location.clone())).collect(); + let line: String = + format!("{} {}", frame_list.join(";"), folded_stack_item.total_gates); + + let mut new_parent_stacks = parent_stacks.clone(); + new_parent_stacks.push_back(location.clone()); + + let child_lines: Vec = + to_folded_sorted_lines(&folded_stack_item.nested_items, new_parent_stacks); + + std::iter::once(line).chain(child_lines) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use acir::circuit::{OpcodeLocation, Program}; + use color_eyre::eyre::{self}; + use fm::{FileId, FileManager}; + use nargo::artifacts::program::ProgramArtifact; + use noirc_driver::DebugFile; + use noirc_errors::{ + debug_info::{DebugInfo, ProgramDebugInfo}, + Location, Span, + }; + use std::{ + cell::RefCell, + collections::{BTreeMap, HashMap}, + path::{Path, PathBuf}, + }; + use tempfile::TempDir; + + use super::{BackendGatesReport, BackendGatesResponse, GatesProvider}; + + struct TestGateProvider { + mock_responses: HashMap, + } + + impl GatesProvider for TestGateProvider { + fn get_gates(&self, artifact_path: &std::path::Path) -> eyre::Result { + let response = self + .mock_responses + .get(artifact_path) + .expect("should have a mock response for the artifact path"); + + Ok(response.clone()) + } + } + + #[derive(Default)] + struct TestFlamegraphGenerator { + lines_received: RefCell>>, + } + + impl super::FlamegraphGenerator for TestFlamegraphGenerator { + fn generate_flamegraph<'lines, I: IntoIterator>( + &self, + folded_lines: I, + _artifact_name: &str, + _function_name: &str, + _output_path: &std::path::Path, + ) -> eyre::Result<()> { + let lines = folded_lines.into_iter().map(|line| line.to_string()).collect(); + self.lines_received.borrow_mut().push(lines); + Ok(()) + } + } + + fn find_spans_for(source: &str, needle: &str) -> Vec { + let mut spans = Vec::new(); + let mut start = 0; + while let Some(start_idx) = source[start..].find(needle) { + let start_idx = start + start_idx; + let end_idx = start_idx + needle.len(); + spans.push(Span::inclusive(start_idx as u32, end_idx as u32 - 1)); + start = end_idx; + } + spans + } + + struct TestCase { + expected_folded_sorted_lines: Vec>, + debug_symbols: ProgramDebugInfo, + file_map: BTreeMap, + gates_report: BackendGatesResponse, + } + + fn simple_test_case(temp_dir: &TempDir) -> TestCase { + let source_code = r##" + fn main() { + foo(); + bar(); + whatever(); + } + fn foo() { + baz(); + } + fn bar () { + whatever() + } + fn baz () { + whatever() + } + "##; + + let source_file_name = Path::new("main.nr"); + let mut fm = FileManager::new(temp_dir.path()); + let file_id = fm.add_file_with_source(source_file_name, source_code.to_string()).unwrap(); + + let main_declaration_location = + Location::new(find_spans_for(source_code, "fn main()")[0], file_id); + let main_foo_call_location = + Location::new(find_spans_for(source_code, "foo()")[0], file_id); + let main_bar_call_location = + Location::new(find_spans_for(source_code, "bar()")[0], file_id); + let main_whatever_call_location = + Location::new(find_spans_for(source_code, "whatever()")[0], file_id); + let foo_baz_call_location = Location::new(find_spans_for(source_code, "baz()")[0], file_id); + let bar_whatever_call_location = + Location::new(find_spans_for(source_code, "whatever()")[1], file_id); + let baz_whatever_call_location = + Location::new(find_spans_for(source_code, "whatever()")[2], file_id); + + let mut opcode_locations = BTreeMap::>::new(); + // main::foo::baz::whatever + opcode_locations.insert( + OpcodeLocation::Acir(0), + vec![ + main_declaration_location, + main_foo_call_location, + foo_baz_call_location, + baz_whatever_call_location, + ], + ); + + // main::bar::whatever + opcode_locations.insert( + OpcodeLocation::Acir(1), + vec![main_declaration_location, main_bar_call_location, bar_whatever_call_location], + ); + // main::whatever + opcode_locations.insert( + OpcodeLocation::Acir(2), + vec![main_declaration_location, main_whatever_call_location], + ); + + let file_map = BTreeMap::from_iter(vec![( + file_id, + DebugFile { source: source_code.to_string(), path: source_file_name.to_path_buf() }, + )]); + + let debug_symbols = ProgramDebugInfo { + debug_infos: vec![DebugInfo::new( + opcode_locations, + BTreeMap::default(), + BTreeMap::default(), + BTreeMap::default(), + )], + }; + + let backend_gates_response = BackendGatesResponse { + functions: vec![BackendGatesReport { + acir_opcodes: 3, + circuit_size: 100, + gates_per_opcode: vec![10, 20, 30], + }], + }; + + let expected_folded_sorted_lines = vec![ + "main.nr:2:9::fn main() 0".to_string(), + "main.nr:2:9::fn main();main.nr:3:13::foo() 0".to_string(), + "main.nr:2:9::fn main();main.nr:3:13::foo();main.nr:8:13::baz() 0".to_string(), + "main.nr:2:9::fn main();main.nr:3:13::foo();main.nr:8:13::baz();main.nr:14:13::whatever() 10".to_string(), + "main.nr:2:9::fn main();main.nr:4:13::bar() 0".to_string(), + "main.nr:2:9::fn main();main.nr:4:13::bar();main.nr:11:13::whatever() 20".to_string(), + "main.nr:2:9::fn main();main.nr:5:13::whatever() 30".to_string(), + ]; + + TestCase { + expected_folded_sorted_lines: vec![expected_folded_sorted_lines], + debug_symbols, + file_map, + gates_report: backend_gates_response, + } + } + + #[test] + fn test_flamegraph() { + let temp_dir = tempfile::tempdir().unwrap(); + + let test_cases = vec![simple_test_case(&temp_dir)]; + let artifact_names: Vec<_> = + test_cases.iter().enumerate().map(|(idx, _)| format!("test{}.json", idx)).collect(); + + let test_cases_with_names: Vec<_> = test_cases.into_iter().zip(artifact_names).collect(); + + let mut mock_responses: HashMap = HashMap::new(); + // Collect mock responses + for (test_case, artifact_name) in test_cases_with_names.iter() { + mock_responses.insert( + temp_dir.path().join(artifact_name.clone()), + test_case.gates_report.clone(), + ); + } + + let provider = TestGateProvider { mock_responses }; + + for (test_case, artifact_name) in test_cases_with_names.iter() { + let artifact_path = temp_dir.path().join(artifact_name.clone()); + + let artifact = ProgramArtifact { + noir_version: "0.0.0".to_string(), + hash: 27, + abi: noirc_abi::Abi::default(), + bytecode: Program::default(), + debug_symbols: test_case.debug_symbols.clone(), + file_map: test_case.file_map.clone(), + names: vec!["main".to_string()], + }; + + // Write the artifact to a file + let artifact_file = std::fs::File::create(&artifact_path).unwrap(); + serde_json::to_writer(artifact_file, &artifact).unwrap(); + + let flamegraph_generator = TestFlamegraphGenerator::default(); + + super::run_with_provider( + &artifact_path, + &provider, + &flamegraph_generator, + temp_dir.path(), + ) + .expect("should run without errors"); + + // Check that the flamegraph generator was called with the correct folded sorted lines + let calls_received = flamegraph_generator.lines_received.borrow().clone(); + + assert_eq!(calls_received, test_case.expected_folded_sorted_lines); + } + } +} diff --git a/noir/noir-repo/tooling/profiler/src/cli/mod.rs b/noir/noir-repo/tooling/profiler/src/cli/mod.rs new file mode 100644 index 00000000000..d54a3f6167c --- /dev/null +++ b/noir/noir-repo/tooling/profiler/src/cli/mod.rs @@ -0,0 +1,33 @@ +use clap::{Parser, Subcommand}; +use color_eyre::eyre; +use const_format::formatcp; + +mod gates_flamegraph_cmd; + +const PROFILER_VERSION: &str = env!("CARGO_PKG_VERSION"); + +static VERSION_STRING: &str = formatcp!("version = {}\n", PROFILER_VERSION,); + +#[derive(Parser, Debug)] +#[command(name="Noir profiler", author, version=VERSION_STRING, about, long_about = None)] +struct ProfilerCli { + #[command(subcommand)] + command: GatesFlamegraphCommand, +} + +#[non_exhaustive] +#[derive(Subcommand, Clone, Debug)] +enum GatesFlamegraphCommand { + GatesFlamegraph(gates_flamegraph_cmd::GatesFlamegraphCommand), +} + +pub(crate) fn start_cli() -> eyre::Result<()> { + let ProfilerCli { command } = ProfilerCli::parse(); + + match command { + GatesFlamegraphCommand::GatesFlamegraph(args) => gates_flamegraph_cmd::run(args), + } + .map_err(|err| eyre::eyre!("{}", err))?; + + Ok(()) +} diff --git a/noir/noir-repo/tooling/profiler/src/main.rs b/noir/noir-repo/tooling/profiler/src/main.rs new file mode 100644 index 00000000000..8e08644de23 --- /dev/null +++ b/noir/noir-repo/tooling/profiler/src/main.rs @@ -0,0 +1,35 @@ +#![forbid(unsafe_code)] +#![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] +#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] + +mod cli; + +use std::env; + +use tracing_appender::rolling; +use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; + +fn main() { + // Setup tracing + if let Ok(log_dir) = env::var("PROFILER_LOG_DIR") { + let debug_file = rolling::daily(log_dir, "profiler-log"); + tracing_subscriber::fmt() + .with_span_events(FmtSpan::ACTIVE) + .with_writer(debug_file) + .with_ansi(false) + .with_env_filter(EnvFilter::from_default_env()) + .init(); + } else { + tracing_subscriber::fmt() + .with_span_events(FmtSpan::ACTIVE) + .with_ansi(true) + .with_env_filter(EnvFilter::from_env("NOIR_LOG")) + .init(); + } + + if let Err(report) = cli::start_cli() { + eprintln!("{report}"); + std::process::exit(1); + } +}