From 7abbb39e11232bb260fa603a362ab4d071c4162c Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Wed, 7 Feb 2024 23:27:42 +0200 Subject: [PATCH] Refactor cairo1-run crate (#1601) * Create a cairo_run for cairo1-run crate + Refactor cairo1 execution into different functions * Clippy * Merge imports + Impl Default for Cairo1RunConfig * Remove allow unused imports * Add changelog entry * Use FuncArg slice instead of FuncArgs wrapper in public api * Update changelog --- CHANGELOG.md | 4 + cairo1-run/src/cairo_run.rs | 681 ++++++++++++++++++++++++++++++++++ cairo1-run/src/main.rs | 705 ++---------------------------------- 3 files changed, 722 insertions(+), 668 deletions(-) create mode 100644 cairo1-run/src/cairo_run.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b31f8c77..2b31bd6a31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ #### Upcoming Changes +* refactor: Refactor `cairo1-run` crate [#1601](https://github.com/lambdaclass/cairo-vm/pull/1601) + * Add function `cairo_run_program` & struct `Cairo1RunConfig` in `cairo1-run::cairo_run` module. + * Function `serialize_output` & structs `FuncArg` and `Error` in crate `cairo1-run` are now public. + * feat(BREAKING): Add `allow_missing_builtins` flag [#1600](https://github.com/lambdaclass/cairo-vm/pull/1600) This new flag will skip the check that all builtins used by the program need to be present in the selected layout if enabled. It will also be enabled by default when running in proof_mode. diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs new file mode 100644 index 0000000000..c24695a1c9 --- /dev/null +++ b/cairo1-run/src/cairo_run.rs @@ -0,0 +1,681 @@ +use cairo_lang_casm::{casm, casm_extend, hints::Hint, instructions::Instruction}; +use cairo_lang_sierra::{ + extensions::{ + bitwise::BitwiseType, + core::{CoreLibfunc, CoreType}, + ec::EcOpType, + gas::{CostTokenType, GasBuiltinType}, + pedersen::PedersenType, + poseidon::PoseidonType, + range_check::RangeCheckType, + segment_arena::SegmentArenaType, + starknet::syscalls::SystemType, + ConcreteType, NamedType, + }, + ids::ConcreteTypeId, + program::{Function, Program as SierraProgram}, + program_registry::ProgramRegistry, +}; +use cairo_lang_sierra_ap_change::calc_ap_changes; +use cairo_lang_sierra_gas::gas_info::GasInfo; +use cairo_lang_sierra_to_casm::{ + compiler::CairoProgram, + metadata::{calc_metadata, Metadata, MetadataComputationConfig, MetadataError}, +}; +use cairo_lang_sierra_type_size::get_type_size_map; +use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use cairo_vm::{ + hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, + serde::deserialize_program::{ + ApTracking, BuiltinName, FlowTrackingData, HintParams, ReferenceManager, + }, + types::{program::Program, relocatable::MaybeRelocatable}, + vm::{ + errors::{runner_errors::RunnerError, vm_errors::VirtualMachineError}, + runners::{ + builtin_runner::{ + BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, + POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, + }, + cairo_runner::{CairoRunner, RunResources, RunnerMode}, + }, + vm_core::VirtualMachine, + }, + Felt252, +}; +use itertools::chain; +use std::collections::HashMap; + +use crate::{Error, FuncArg}; + +#[derive(Debug)] +pub struct Cairo1RunConfig<'a> { + pub args: &'a [FuncArg], + pub trace_enabled: bool, + pub relocate_mem: bool, + pub layout: &'a str, + pub proof_mode: bool, + // Should be true if either air_public_input or cairo_pie_output are needed + // Sets builtins stop_ptr by calling `final_stack` on each builtin + pub finalize_builtins: bool, +} + +impl Default for Cairo1RunConfig<'_> { + fn default() -> Self { + Self { + args: Default::default(), + trace_enabled: false, + relocate_mem: false, + layout: "plain", + proof_mode: false, + finalize_builtins: false, + } + } +} + +// Runs a Cairo 1 program +// Returns the runner & VM after execution + the return values +pub fn cairo_run_program( + sierra_program: &SierraProgram, + cairo_run_config: Cairo1RunConfig, +) -> Result<(CairoRunner, VirtualMachine, Vec), Error> { + let metadata = create_metadata(sierra_program, Some(Default::default()))?; + let sierra_program_registry = ProgramRegistry::::new(sierra_program)?; + let type_sizes = + get_type_size_map(sierra_program, &sierra_program_registry).unwrap_or_default(); + let casm_program = + cairo_lang_sierra_to_casm::compiler::compile(sierra_program, &metadata, true)?; + + let main_func = find_function(sierra_program, "::main")?; + + let initial_gas = 9999999999999_usize; + + // Modified entry code to be compatible with custom cairo1 Proof Mode. + // This adds code that's needed for dictionaries, adjusts ap for builtin pointers, adds initial gas for the gas builtin if needed, and sets up other necessary code for cairo1 + let (entry_code, builtins) = create_entry_code( + &sierra_program_registry, + &casm_program, + &type_sizes, + main_func, + initial_gas, + cairo_run_config.proof_mode, + cairo_run_config.args, + )?; + + // Fetch return type data + let return_type_id = main_func + .signature + .ret_types + .last() + .ok_or(Error::NoRetTypesInSignature)?; + let return_type_size = type_sizes + .get(return_type_id) + .cloned() + .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; + + // This footer is used by lib funcs + let libfunc_footer = create_code_footer(); + + // Header used to initiate the infinite loop after executing the program + // Also appends return values to output segment + let proof_mode_header = if cairo_run_config.proof_mode { + create_proof_mode_header(builtins.len() as i16, return_type_size) + } else { + casm! {}.instructions + }; + + // This is the program we are actually running/proving + // With (embedded proof mode), cairo1 header and the libfunc footer + let instructions = chain!( + proof_mode_header.iter(), + entry_code.iter(), + casm_program.instructions.iter(), + libfunc_footer.iter(), + ); + + let (processor_hints, program_hints) = build_hints_vec(instructions.clone()); + + let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default()); + + let data: Vec = instructions + .flat_map(|inst| inst.assemble().encode()) + .map(|x| Felt252::from(&x)) + .map(MaybeRelocatable::from) + .collect(); + + let data_len = data.len(); + + let program = if cairo_run_config.proof_mode { + Program::new_for_proof( + builtins, + data, + 0, + // Proof mode is on top + // jmp rel 0 is on PC == 2 + 2, + program_hints, + ReferenceManager { + references: Vec::new(), + }, + HashMap::new(), + vec![], + None, + )? + } else { + Program::new( + builtins, + data, + Some(0), + program_hints, + ReferenceManager { + references: Vec::new(), + }, + HashMap::new(), + vec![], + None, + )? + }; + + let runner_mode = if cairo_run_config.proof_mode { + RunnerMode::ProofModeCairo1 + } else { + RunnerMode::ExecutionMode + }; + + let mut runner = CairoRunner::new_v2(&program, cairo_run_config.layout, runner_mode)?; + let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled); + let end = runner.initialize(&mut vm, cairo_run_config.proof_mode)?; + + additional_initialization(&mut vm, data_len)?; + + // Run it until the end / infinite loop in proof_mode + runner.run_until_pc(end, &mut vm, &mut hint_processor)?; + if cairo_run_config.proof_mode { + // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 + // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size + vm.segments + .segment_sizes + .insert(2, return_type_size as usize); + } + runner.end_run(false, false, &mut vm, &mut hint_processor)?; + + // Fetch return values + let return_values = fetch_return_values(return_type_size, return_type_id, &vm)?; + + // Set stop pointers for builtins so we can obtain the air public input + if cairo_run_config.finalize_builtins { + finalize_builtins( + cairo_run_config.proof_mode, + &main_func.signature.ret_types, + &type_sizes, + &mut vm, + )?; + + // Build execution public memory + if cairo_run_config.proof_mode { + // As the output builtin is not used by the program we need to compute it's stop ptr manually + vm.set_output_stop_ptr_offset(return_type_size as usize); + + runner.finalize_segments(&mut vm)?; + } + } + + runner.relocate(&mut vm, true)?; + + Ok((runner, vm, return_values)) +} + +fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { + // Create the builtin cost segment + let builtin_cost_segment = vm.add_memory_segment(); + for token_type in CostTokenType::iter_precost() { + vm.insert_value( + (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)) + .map_err(VirtualMachineError::Math)?, + Felt252::default(), + )? + } + // Put a pointer to the builtin cost segment at the end of the program (after the + // additional `ret` statement). + vm.insert_value( + (vm.get_pc() + data_len).map_err(VirtualMachineError::Math)?, + builtin_cost_segment, + )?; + + Ok(()) +} + +#[allow(clippy::type_complexity)] +fn build_hints_vec<'b>( + instructions: impl Iterator, +) -> (Vec<(usize, Vec)>, HashMap>) { + let mut hints: Vec<(usize, Vec)> = Vec::new(); + let mut program_hints: HashMap> = HashMap::new(); + + let mut hint_offset = 0; + + for instruction in instructions { + if !instruction.hints.is_empty() { + hints.push((hint_offset, instruction.hints.clone())); + program_hints.insert( + hint_offset, + vec![HintParams { + code: hint_offset.to_string(), + accessible_scopes: Vec::new(), + flow_tracking_data: FlowTrackingData { + ap_tracking: ApTracking::default(), + reference_ids: HashMap::new(), + }, + }], + ); + } + hint_offset += instruction.body.op_size(); + } + (hints, program_hints) +} + +/// Finds first function ending with `name_suffix`. +fn find_function<'a>( + sierra_program: &'a SierraProgram, + name_suffix: &'a str, +) -> Result<&'a Function, RunnerError> { + sierra_program + .funcs + .iter() + .find(|f| { + if let Some(name) = &f.id.debug_name { + name.ends_with(name_suffix) + } else { + false + } + }) + .ok_or_else(|| RunnerError::MissingMain) +} + +/// Creates a list of instructions that will be appended to the program's bytecode. +fn create_code_footer() -> Vec { + casm! { + // Add a `ret` instruction used in libfuncs that retrieve the current value of the `fp` + // and `pc` registers. + ret; + } + .instructions +} + +// Create proof_mode specific instructions +// Including the "canonical" proof mode instructions (the ones added by the compiler in cairo 0) +// wich call the firt program instruction and then initiate an infinite loop. +// And also appending the return values to the output builtin's memory segment +fn create_proof_mode_header(builtin_count: i16, return_type_size: i16) -> Vec { + // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty + // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases + let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments + + // The pc offset where the original program should start + // Without this header it should start at 0, but we add 2 for each call and jump instruction (as both of them use immediate values) + // and also 1 for each instruction added to copy each return value into the output segment + let program_start_offset: i16 = 4 + return_type_size; + + let mut ctx = casm! {}; + casm_extend! {ctx, + call rel program_start_offset; // Begin program execution by calling the first instruction in the original program + }; + // Append each return value to the output segment + for (i, j) in (1..return_type_size + 1).rev().enumerate() { + casm_extend! {ctx, + // [ap -j] is where each return value is located in memory + // [[fp + output_fp_offet] + 0] is the base of the output segment + [ap - j] = [[fp + output_fp_offset] + i as i16]; + }; + } + casm_extend! {ctx, + jmp rel 0; // Infinite loop + }; + ctx.instructions +} + +/// Returns the instructions to add to the beginning of the code to successfully call the main +/// function, as well as the builtins required to execute the program. +fn create_entry_code( + sierra_program_registry: &ProgramRegistry, + casm_program: &CairoProgram, + type_sizes: &UnorderedHashMap, + func: &Function, + initial_gas: usize, + proof_mode: bool, + args: &[FuncArg], +) -> Result<(Vec, Vec), Error> { + let mut ctx = casm! {}; + // The builtins in the formatting expected by the runner. + let (builtins, builtin_offset) = get_function_builtins(func, proof_mode); + + // Load all vecs to memory. + // Load all array args content to memory. + let mut array_args_data = vec![]; + let mut ap_offset: i16 = 0; + for arg in args { + let FuncArg::Array(values) = arg else { continue }; + array_args_data.push(ap_offset); + casm_extend! {ctx, + %{ memory[ap + 0] = segments.add() %} + ap += 1; + } + for (i, v) in values.iter().enumerate() { + let arr_at = (i + 1) as i16; + casm_extend! {ctx, + [ap + 0] = (v.to_bigint()); + [ap + 0] = [[ap - arr_at] + (i as i16)], ap++; + }; + } + ap_offset += (1 + values.len()) as i16; + } + let mut array_args_data_iter = array_args_data.iter(); + let after_arrays_data_offset = ap_offset; + let mut arg_iter = args.iter().enumerate(); + let mut param_index = 0; + let mut expected_arguments_size = 0; + if func.signature.param_types.iter().any(|ty| { + get_info(sierra_program_registry, ty) + .map(|x| x.long_id.generic_id == SegmentArenaType::ID) + .unwrap_or_default() + }) { + casm_extend! {ctx, + // SegmentArena segment. + %{ memory[ap + 0] = segments.add() %} + // Infos segment. + %{ memory[ap + 1] = segments.add() %} + ap += 2; + [ap + 0] = 0, ap++; + // Write Infos segment, n_constructed (0), and n_destructed (0) to the segment. + [ap - 2] = [[ap - 3]]; + [ap - 1] = [[ap - 3] + 1]; + [ap - 1] = [[ap - 3] + 2]; + } + ap_offset += 3; + } + for ty in func.signature.param_types.iter() { + let info = get_info(sierra_program_registry, ty) + .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; + let generic_ty = &info.long_id.generic_id; + if let Some(offset) = builtin_offset.get(generic_ty) { + let mut offset = *offset; + if proof_mode { + // Everything is off by 2 due to the proof mode header + offset += 2; + } + casm_extend! {ctx, + [ap + 0] = [fp - offset], ap++; + } + ap_offset += 1; + } else if generic_ty == &SystemType::ID { + casm_extend! {ctx, + %{ memory[ap + 0] = segments.add() %} + ap += 1; + } + ap_offset += 1; + } else if generic_ty == &GasBuiltinType::ID { + casm_extend! {ctx, + [ap + 0] = initial_gas, ap++; + } + ap_offset += 1; + } else if generic_ty == &SegmentArenaType::ID { + let offset = -ap_offset + after_arrays_data_offset; + casm_extend! {ctx, + [ap + 0] = [ap + offset] + 3, ap++; + } + ap_offset += 1; + } else { + let ty_size = type_sizes[ty]; + let param_ap_offset_end = ap_offset + ty_size; + expected_arguments_size += ty_size; + while ap_offset < param_ap_offset_end { + let Some((arg_index, arg)) = arg_iter.next() else { + break; + }; + match arg { + FuncArg::Single(value) => { + casm_extend! {ctx, + [ap + 0] = (value.to_bigint()), ap++; + } + ap_offset += 1; + } + FuncArg::Array(values) => { + let offset = -ap_offset + array_args_data_iter.next().unwrap(); + casm_extend! {ctx, + [ap + 0] = [ap + (offset)], ap++; + [ap + 0] = [ap - 1] + (values.len()), ap++; + } + ap_offset += 2; + if ap_offset > param_ap_offset_end { + return Err(Error::ArgumentUnaligned { + param_index, + arg_index, + }); + } + } + } + } + param_index += 1; + }; + } + let actual_args_size = args + .iter() + .map(|arg| match arg { + FuncArg::Single(_) => 1, + FuncArg::Array(_) => 2, + }) + .sum::(); + if expected_arguments_size != actual_args_size { + return Err(Error::ArgumentsSizeMismatch { + expected: expected_arguments_size, + actual: actual_args_size, + }); + } + + let before_final_call = ctx.current_code_offset; + let final_call_size = 3; + let offset = final_call_size + + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; + + casm_extend! {ctx, + call rel offset; + ret; + } + assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); + + Ok((ctx.instructions, builtins)) +} + +fn get_info<'a>( + sierra_program_registry: &'a ProgramRegistry, + ty: &'a cairo_lang_sierra::ids::ConcreteTypeId, +) -> Option<&'a cairo_lang_sierra::extensions::types::TypeInfo> { + sierra_program_registry + .get_type(ty) + .ok() + .map(|ctc| ctc.info()) +} + +/// Creates the metadata required for a Sierra program lowering to casm. +fn create_metadata( + sierra_program: &cairo_lang_sierra::program::Program, + metadata_config: Option, +) -> Result { + if let Some(metadata_config) = metadata_config { + calc_metadata(sierra_program, metadata_config).map_err(|err| match err { + MetadataError::ApChangeError(_) => VirtualMachineError::Unexpected, + MetadataError::CostError(_) => VirtualMachineError::Unexpected, + }) + } else { + Ok(Metadata { + ap_change_info: calc_ap_changes(sierra_program, |_, _| 0) + .map_err(|_| VirtualMachineError::Unexpected)?, + gas_info: GasInfo { + variable_values: Default::default(), + function_costs: Default::default(), + }, + }) + } +} + +/// Type representing the Output builtin. +#[derive(Default)] +pub struct OutputType {} +impl cairo_lang_sierra::extensions::NoGenericArgsGenericType for OutputType { + const ID: cairo_lang_sierra::ids::GenericTypeId = + cairo_lang_sierra::ids::GenericTypeId::new_inline("Output"); + const STORABLE: bool = true; + const DUPLICATABLE: bool = false; + const DROPPABLE: bool = false; + const ZERO_SIZED: bool = false; +} + +fn get_function_builtins( + func: &Function, + proof_mode: bool, +) -> ( + Vec, + HashMap, +) { + let entry_params = &func.signature.param_types; + let mut builtins = Vec::new(); + let mut builtin_offset: HashMap = HashMap::new(); + let mut current_offset = 3; + // Fetch builtins from the entry_params in the standard order + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Poseidon".into())) + { + builtins.push(BuiltinName::poseidon); + builtin_offset.insert(PoseidonType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("EcOp".into())) + { + builtins.push(BuiltinName::ec_op); + builtin_offset.insert(EcOpType::ID, current_offset); + current_offset += 1 + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Bitwise".into())) + { + builtins.push(BuiltinName::bitwise); + builtin_offset.insert(BitwiseType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("RangeCheck".into())) + { + builtins.push(BuiltinName::range_check); + builtin_offset.insert(RangeCheckType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Pedersen".into())) + { + builtins.push(BuiltinName::pedersen); + builtin_offset.insert(PedersenType::ID, current_offset); + current_offset += 1; + } + // Force an output builtin so that we can write the program output into it's segment + if proof_mode { + builtins.push(BuiltinName::output); + builtin_offset.insert(OutputType::ID, current_offset); + } + builtins.reverse(); + (builtins, builtin_offset) +} + +fn fetch_return_values( + return_type_size: i16, + return_type_id: &ConcreteTypeId, + vm: &VirtualMachine, +) -> Result, Error> { + let mut return_values = vm.get_return_values(return_type_size as usize)?; + // Check if this result is a Panic result + if return_type_id + .debug_name + .as_ref() + .ok_or_else(|| Error::TypeIdNoDebugName(return_type_id.clone()))? + .starts_with("core::panics::PanicResult::") + { + // Check the failure flag (aka first return value) + if return_values.first() != Some(&MaybeRelocatable::from(0)) { + // In case of failure, extract the error from the return values (aka last two values) + let panic_data_end = return_values + .last() + .ok_or(Error::FailedToExtractReturnValues)? + .get_relocatable() + .ok_or(Error::FailedToExtractReturnValues)?; + let panic_data_start = return_values + .get(return_values.len() - 2) + .ok_or(Error::FailedToExtractReturnValues)? + .get_relocatable() + .ok_or(Error::FailedToExtractReturnValues)?; + let panic_data = vm.get_integer_range( + panic_data_start, + (panic_data_end - panic_data_start).map_err(VirtualMachineError::Math)?, + )?; + return Err(Error::RunPanic( + panic_data.iter().map(|c| *c.as_ref()).collect(), + )); + } else { + if return_values.len() < 3 { + return Err(Error::FailedToExtractReturnValues); + } + return_values = return_values[2..].to_vec() + } + } + Ok(return_values) +} + +// Calculates builtins' final_stack setting each stop_ptr +// Calling this function is a must if either air_public_input or cairo_pie are needed +fn finalize_builtins( + proof_mode: bool, + main_ret_types: &[ConcreteTypeId], + type_sizes: &UnorderedHashMap, + vm: &mut VirtualMachine, +) -> Result<(), Error> { + // Set stop pointers for builtins so we can obtain the air public input + // Cairo 1 programs have other return values aside from the used builtin's final pointers, so we need to hand-pick them + let ret_types_sizes = main_ret_types + .iter() + .map(|id| type_sizes.get(id).cloned().unwrap_or_default()); + let ret_types_and_sizes = main_ret_types.iter().zip(ret_types_sizes.clone()); + + let full_ret_types_size: i16 = ret_types_sizes.sum(); + let mut stack_pointer = (vm.get_ap() - (full_ret_types_size as usize).saturating_sub(1)) + .map_err(VirtualMachineError::Math)?; + + // Calculate the stack_ptr for each return builtin in the return values + let mut builtin_name_to_stack_pointer = HashMap::new(); + for (id, size) in ret_types_and_sizes { + if let Some(ref name) = id.debug_name { + let builtin_name = match &*name.to_string() { + "RangeCheck" => RANGE_CHECK_BUILTIN_NAME, + "Poseidon" => POSEIDON_BUILTIN_NAME, + "EcOp" => EC_OP_BUILTIN_NAME, + "Bitwise" => BITWISE_BUILTIN_NAME, + "Pedersen" => HASH_BUILTIN_NAME, + "Output" => OUTPUT_BUILTIN_NAME, + "Ecdsa" => SIGNATURE_BUILTIN_NAME, + _ => { + stack_pointer.offset += size as usize; + continue; + } + }; + builtin_name_to_stack_pointer.insert(builtin_name, stack_pointer); + } + stack_pointer.offset += size as usize; + } + + // Set stop pointer for each builtin + vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, proof_mode)?; + Ok(()) +} diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs index 3d1bf8fdca..a04df32369 100644 --- a/cairo1-run/src/main.rs +++ b/cairo1-run/src/main.rs @@ -1,80 +1,33 @@ -#![allow(unused_imports)] use bincode::enc::write::Writer; -use cairo_lang_casm::casm; -use cairo_lang_casm::casm_extend; -use cairo_lang_casm::hints::Hint; -use cairo_lang_casm::instructions::Instruction; -use cairo_lang_compiler::db; use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; -use cairo_lang_sierra::extensions::bitwise::BitwiseType; -use cairo_lang_sierra::extensions::core::{CoreLibfunc, CoreType}; -use cairo_lang_sierra::extensions::ec::EcOpType; -use cairo_lang_sierra::extensions::gas::GasBuiltinType; -use cairo_lang_sierra::extensions::pedersen::PedersenType; -use cairo_lang_sierra::extensions::poseidon::PoseidonType; -use cairo_lang_sierra::extensions::range_check::RangeCheckType; -use cairo_lang_sierra::extensions::segment_arena::SegmentArenaType; -use cairo_lang_sierra::extensions::starknet::syscalls::SystemType; -use cairo_lang_sierra::extensions::ConcreteType; -use cairo_lang_sierra::extensions::NamedType; -use cairo_lang_sierra::ids::ConcreteTypeId; -use cairo_lang_sierra::program::Function; -use cairo_lang_sierra::program::Program as SierraProgram; -use cairo_lang_sierra::program_registry::{ProgramRegistry, ProgramRegistryError}; -use cairo_lang_sierra::{extensions::gas::CostTokenType, ProgramParser}; -use cairo_lang_sierra_ap_change::calc_ap_changes; -use cairo_lang_sierra_gas::gas_info::GasInfo; -use cairo_lang_sierra_to_casm::compiler::CairoProgram; -use cairo_lang_sierra_to_casm::compiler::CompilationError; -use cairo_lang_sierra_to_casm::metadata::Metadata; -use cairo_lang_sierra_to_casm::metadata::MetadataComputationConfig; -use cairo_lang_sierra_to_casm::metadata::MetadataError; -use cairo_lang_sierra_to_casm::{compiler::compile, metadata::calc_metadata}; -use cairo_lang_sierra_type_size::get_type_size_map; -use cairo_lang_utils::extract_matches; -use cairo_lang_utils::ordered_hash_map::OrderedHashMap; -use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; -use cairo_vm::air_public_input::PublicInputError; -use cairo_vm::cairo_run; -use cairo_vm::cairo_run::EncodeTraceError; -use cairo_vm::hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor; -use cairo_vm::serde::deserialize_program::BuiltinName; -use cairo_vm::serde::deserialize_program::{ApTracking, FlowTrackingData, HintParams}; -use cairo_vm::types::errors::program_errors::ProgramError; -use cairo_vm::types::relocatable::Relocatable; -use cairo_vm::vm::decoding::decoder::decode_instruction; -use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; -use cairo_vm::vm::errors::memory_errors::MemoryError; -use cairo_vm::vm::errors::runner_errors::RunnerError; -use cairo_vm::vm::errors::trace_errors::TraceError; -use cairo_vm::vm::errors::vm_errors::VirtualMachineError; -use cairo_vm::vm::runners::builtin_runner::{ - BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, - POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, -}; -use cairo_vm::vm::runners::cairo_runner::CairoArg; -use cairo_vm::vm::runners::cairo_runner::RunnerMode; -use cairo_vm::vm::vm_memory::memory::Memory; +use cairo_lang_sierra::{ids::ConcreteTypeId, program_registry::ProgramRegistryError}; +use cairo_lang_sierra_to_casm::{compiler::CompilationError, metadata::MetadataError}; +use cairo_run::Cairo1RunConfig; use cairo_vm::{ - serde::deserialize_program::ReferenceManager, - types::{program::Program, relocatable::MaybeRelocatable}, + air_public_input::PublicInputError, + cairo_run::EncodeTraceError, + types::{errors::program_errors::ProgramError, relocatable::MaybeRelocatable}, vm::{ - runners::cairo_runner::{CairoRunner, RunResources}, + errors::{ + memory_errors::MemoryError, runner_errors::RunnerError, trace_errors::TraceError, + vm_errors::VirtualMachineError, + }, vm_core::VirtualMachine, }, Felt252, }; -use clap::{CommandFactory, Parser, ValueHint}; -use itertools::{chain, Itertools}; -use std::borrow::Cow; -use std::io::BufWriter; -use std::io::Write; -use std::iter::Peekable; -use std::path::PathBuf; -use std::slice::Iter; -use std::{collections::HashMap, io, path::Path}; +use clap::{Parser, ValueHint}; +use itertools::Itertools; +use std::{ + io::{self, Write}, + iter::Peekable, + path::PathBuf, + slice::Iter, +}; use thiserror::Error; +pub mod cairo_run; + #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { @@ -111,7 +64,7 @@ struct Args { } #[derive(Debug, Clone)] -enum FuncArg { +pub enum FuncArg { Array(Vec), Single(Felt252), } @@ -170,7 +123,7 @@ fn validate_layout(value: &str) -> Result { } #[derive(Debug, Error)] -enum Error { +pub enum Error { #[error("Invalid arguments")] Cli(#[from] clap::Error), #[error("Failed to interact with the file system")] @@ -254,217 +207,25 @@ impl FileWriter { fn run(args: impl Iterator) -> Result, Error> { let args = Args::try_parse_from(args)?; + let cairo_run_config = Cairo1RunConfig { + proof_mode: args.proof_mode, + relocate_mem: args.memory_file.is_some() || args.air_public_input.is_some(), + layout: &args.layout, + trace_enabled: args.trace_file.is_some() || args.air_public_input.is_some(), + args: &args.args.0, + finalize_builtins: args.air_private_input.is_some() || args.cairo_pie_output.is_some(), + }; + let compiler_config = CompilerConfig { replace_ids: true, ..CompilerConfig::default() }; + let sierra_program = compile_cairo_project_at_path(&args.filename, compiler_config) .map_err(|err| Error::SierraCompilation(err.to_string()))?; - let metadata_config = Some(Default::default()); - - let gas_usage_check = metadata_config.is_some(); - let metadata = create_metadata(&sierra_program, metadata_config)?; - let sierra_program_registry = ProgramRegistry::::new(&sierra_program)?; - let type_sizes = - get_type_size_map(&sierra_program, &sierra_program_registry).unwrap_or_default(); - let casm_program = - cairo_lang_sierra_to_casm::compiler::compile(&sierra_program, &metadata, gas_usage_check)?; - - let main_func = find_function(&sierra_program, "::main")?; - - let initial_gas = 9999999999999_usize; - - // Modified entry code to be compatible with custom cairo1 Proof Mode. - // This adds code that's needed for dictionaries, adjusts ap for builtin pointers, adds initial gas for the gas builtin if needed, and sets up other necessary code for cairo1 - let (entry_code, builtins) = create_entry_code( - &sierra_program_registry, - &casm_program, - &type_sizes, - main_func, - initial_gas, - args.proof_mode, - &args.args.0, - )?; - - // Get the user program instructions - let program_instructions = casm_program.instructions.iter(); - - // Fetch return type data - let return_type_id = main_func - .signature - .ret_types - .last() - .ok_or(Error::NoRetTypesInSignature)?; - let return_type_size = type_sizes - .get(return_type_id) - .cloned() - .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; - - // This footer is used by lib funcs - let libfunc_footer = create_code_footer(); - - let proof_mode_header = if args.proof_mode { - println!("Compiling with proof mode and running ..."); - - // This information can be useful for the users using the prover. - println!("Builtins used: {:?}", builtins); - - // Create proof_mode specific instructions - // Including the "canonical" proof mode instructions (the ones added by the compiler in cairo 0) - // wich call the firt program instruction and then initiate an infinite loop. - // And also appending the return values to the output builtin's memory segment - - // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty - // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases - let output_fp_offset: i16 = -(builtins.len() as i16 + 2); // The 2 here represents the return_fp & end segments - - // The pc offset where the original program should start - // Without this header it should start at 0, but we add 2 for each call and jump instruction (as both of them use immediate values) - // and also 1 for each instruction added to copy each return value into the output segment - let program_start_offset: i16 = 4 + return_type_size; - - let mut ctx = casm! {}; - casm_extend! {ctx, - call rel program_start_offset; // Begin program execution by calling the first instruction in the original program - }; - // Append each return value to the output segment - for (i, j) in (1..return_type_size + 1).rev().enumerate() { - casm_extend! {ctx, - // [ap -j] is where each return value is located in memory - // [[fp + output_fp_offet] + 0] is the base of the output segment - [ap - j] = [[fp + output_fp_offset] + i as i16]; - }; - } - casm_extend! {ctx, - jmp rel 0; // Infinite loop - }; - ctx.instructions - } else { - casm! {}.instructions - }; - - // This is the program we are actually running/proving - // With (embedded proof mode), cairo1 header and the libfunc footer - let instructions = chain!( - proof_mode_header.iter(), - entry_code.iter(), - program_instructions, - libfunc_footer.iter(), - ); - - let (processor_hints, program_hints) = build_hints_vec(instructions.clone()); - - let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default()); - - let data: Vec = instructions - .flat_map(|inst| inst.assemble().encode()) - .map(|x| Felt252::from(&x)) - .map(MaybeRelocatable::from) - .collect(); - - let data_len = data.len(); - - let program = if args.proof_mode { - Program::new_for_proof( - builtins, - data, - 0, - // Proof mode is on top - // jmp rel 0 is on PC == 2 - 2, - program_hints, - ReferenceManager { - references: Vec::new(), - }, - HashMap::new(), - vec![], - None, - )? - } else { - Program::new( - builtins, - data, - Some(0), - program_hints, - ReferenceManager { - references: Vec::new(), - }, - HashMap::new(), - vec![], - None, - )? - }; - - let runner_mode = if args.proof_mode { - RunnerMode::ProofModeCairo1 - } else { - RunnerMode::ExecutionMode - }; - - let mut runner = CairoRunner::new_v2(&program, &args.layout, runner_mode)?; - let mut vm = VirtualMachine::new(args.trace_file.is_some() || args.air_public_input.is_some()); - let end = runner.initialize(&mut vm, args.proof_mode)?; - - additional_initialization(&mut vm, data_len)?; - - // Run it until the end/ infinite loop in proof_mode - runner.run_until_pc(end, &mut vm, &mut hint_processor)?; - if args.proof_mode { - // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 - // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size - vm.segments - .segment_sizes - .insert(2, return_type_size as usize); - } - runner.end_run(false, false, &mut vm, &mut hint_processor)?; - - // Fetch return type data - let return_type_id = main_func - .signature - .ret_types - .last() - .ok_or(Error::NoRetTypesInSignature)?; - let return_type_size = type_sizes - .get(return_type_id) - .cloned() - .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; - - let mut return_values = vm.get_return_values(return_type_size as usize)?; - // Check if this result is a Panic result - if return_type_id - .debug_name - .as_ref() - .ok_or_else(|| Error::TypeIdNoDebugName(return_type_id.clone()))? - .starts_with("core::panics::PanicResult::") - { - // Check the failure flag (aka first return value) - if return_values.first() != Some(&MaybeRelocatable::from(0)) { - // In case of failure, extract the error from the return values (aka last two values) - let panic_data_end = return_values - .last() - .ok_or(Error::FailedToExtractReturnValues)? - .get_relocatable() - .ok_or(Error::FailedToExtractReturnValues)?; - let panic_data_start = return_values - .get(return_values.len() - 2) - .ok_or(Error::FailedToExtractReturnValues)? - .get_relocatable() - .ok_or(Error::FailedToExtractReturnValues)?; - let panic_data = vm.get_integer_range( - panic_data_start, - (panic_data_end - panic_data_start).map_err(VirtualMachineError::Math)?, - )?; - return Err(Error::RunPanic( - panic_data.iter().map(|c| *c.as_ref()).collect(), - )); - } else { - if return_values.len() < 3 { - return Err(Error::FailedToExtractReturnValues); - } - return_values = return_values[2..].to_vec() - } - } + let (runner, vm, return_values) = + cairo_run::cairo_run_program(&sierra_program, cairo_run_config)?; let output_string = if args.print_output { Some(serialize_output(&vm, &return_values)) @@ -472,63 +233,6 @@ fn run(args: impl Iterator) -> Result, Error> { None }; - // Set stop pointers for builtins so we can obtain the air public input - if args.air_public_input.is_some() || args.cairo_pie_output.is_some() { - // Cairo 1 programs have other return values aside from the used builtin's final pointers, so we need to hand-pick them - let ret_types_sizes = main_func - .signature - .ret_types - .iter() - .map(|id| type_sizes.get(id).cloned().unwrap_or_default()); - let ret_types_and_sizes = main_func - .signature - .ret_types - .iter() - .zip(ret_types_sizes.clone()); - - let full_ret_types_size: i16 = ret_types_sizes.sum(); - let mut stack_pointer = (vm.get_ap() - (full_ret_types_size as usize).saturating_sub(1)) - .map_err(VirtualMachineError::Math)?; - - // Calculate the stack_ptr for each return builtin in the return values - let mut builtin_name_to_stack_pointer = HashMap::new(); - for (id, size) in ret_types_and_sizes { - if let Some(ref name) = id.debug_name { - let builtin_name = match &*name.to_string() { - "RangeCheck" => RANGE_CHECK_BUILTIN_NAME, - "Poseidon" => POSEIDON_BUILTIN_NAME, - "EcOp" => EC_OP_BUILTIN_NAME, - "Bitwise" => BITWISE_BUILTIN_NAME, - "Pedersen" => HASH_BUILTIN_NAME, - "Output" => OUTPUT_BUILTIN_NAME, - "Ecdsa" => SIGNATURE_BUILTIN_NAME, - _ => { - stack_pointer.offset += size as usize; - continue; - } - }; - builtin_name_to_stack_pointer.insert(builtin_name, stack_pointer); - } - stack_pointer.offset += size as usize; - } - - // Set stop pointer for each builtin - vm.builtins_final_stack_from_stack_pointer_dict( - &builtin_name_to_stack_pointer, - args.proof_mode, - )?; - - // Build execution public memory - if args.proof_mode { - // As the output builtin is not used by the program we need to compute it's stop ptr manually - vm.set_output_stop_ptr_offset(return_type_size as usize); - - runner.finalize_segments(&mut vm)?; - } - } - - runner.relocate(&mut vm, true)?; - if let Some(file_path) = args.air_public_input { let json = runner.get_air_public_input(&vm)?.serialize_json()?; std::fs::write(file_path, json)?; @@ -573,7 +277,7 @@ fn run(args: impl Iterator) -> Result, Error> { let mut trace_writer = FileWriter::new(io::BufWriter::with_capacity(3 * 1024 * 1024, trace_file)); - cairo_run::write_encoded_trace(&relocated_trace, &mut trace_writer)?; + cairo_vm::cairo_run::write_encoded_trace(&relocated_trace, &mut trace_writer)?; trace_writer.flush()?; } if let Some(memory_path) = args.memory_file { @@ -581,33 +285,13 @@ fn run(args: impl Iterator) -> Result, Error> { let mut memory_writer = FileWriter::new(io::BufWriter::with_capacity(5 * 1024 * 1024, memory_file)); - cairo_run::write_encoded_memory(&runner.relocated_memory, &mut memory_writer)?; + cairo_vm::cairo_run::write_encoded_memory(&runner.relocated_memory, &mut memory_writer)?; memory_writer.flush()?; } Ok(output_string) } -fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { - // Create the builtin cost segment - let builtin_cost_segment = vm.add_memory_segment(); - for token_type in CostTokenType::iter_precost() { - vm.insert_value( - (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)) - .map_err(VirtualMachineError::Math)?, - Felt252::default(), - )? - } - // Put a pointer to the builtin cost segment at the end of the program (after the - // additional `ret` statement). - vm.insert_value( - (vm.get_pc() + data_len).map_err(VirtualMachineError::Math)?, - builtin_cost_segment, - )?; - - Ok(()) -} - fn main() -> Result<(), Error> { match run(std::env::args()) { Err(Error::Cli(err)) => err.exit(), @@ -639,321 +323,7 @@ fn main() -> Result<(), Error> { } } -#[allow(clippy::type_complexity)] -fn build_hints_vec<'b>( - instructions: impl Iterator, -) -> (Vec<(usize, Vec)>, HashMap>) { - let mut hints: Vec<(usize, Vec)> = Vec::new(); - let mut program_hints: HashMap> = HashMap::new(); - - let mut hint_offset = 0; - - for instruction in instructions { - if !instruction.hints.is_empty() { - hints.push((hint_offset, instruction.hints.clone())); - program_hints.insert( - hint_offset, - vec![HintParams { - code: hint_offset.to_string(), - accessible_scopes: Vec::new(), - flow_tracking_data: FlowTrackingData { - ap_tracking: ApTracking::default(), - reference_ids: HashMap::new(), - }, - }], - ); - } - hint_offset += instruction.body.op_size(); - } - (hints, program_hints) -} - -/// Finds first function ending with `name_suffix`. -fn find_function<'a>( - sierra_program: &'a SierraProgram, - name_suffix: &'a str, -) -> Result<&'a Function, RunnerError> { - sierra_program - .funcs - .iter() - .find(|f| { - if let Some(name) = &f.id.debug_name { - name.ends_with(name_suffix) - } else { - false - } - }) - .ok_or_else(|| RunnerError::MissingMain) -} - -/// Creates a list of instructions that will be appended to the program's bytecode. -fn create_code_footer() -> Vec { - casm! { - // Add a `ret` instruction used in libfuncs that retrieve the current value of the `fp` - // and `pc` registers. - ret; - } - .instructions -} - -/// Returns the instructions to add to the beginning of the code to successfully call the main -/// function, as well as the builtins required to execute the program. -fn create_entry_code( - sierra_program_registry: &ProgramRegistry, - casm_program: &CairoProgram, - type_sizes: &UnorderedHashMap, - func: &Function, - initial_gas: usize, - proof_mode: bool, - args: &Vec, -) -> Result<(Vec, Vec), Error> { - let mut ctx = casm! {}; - // The builtins in the formatting expected by the runner. - let (builtins, builtin_offset) = get_function_builtins(func, proof_mode); - - // Load all vecs to memory. - // Load all array args content to memory. - let mut array_args_data = vec![]; - let mut ap_offset: i16 = 0; - for arg in args { - let FuncArg::Array(values) = arg else { continue }; - array_args_data.push(ap_offset); - casm_extend! {ctx, - %{ memory[ap + 0] = segments.add() %} - ap += 1; - } - for (i, v) in values.iter().enumerate() { - let arr_at = (i + 1) as i16; - casm_extend! {ctx, - [ap + 0] = (v.to_bigint()); - [ap + 0] = [[ap - arr_at] + (i as i16)], ap++; - }; - } - ap_offset += (1 + values.len()) as i16; - } - let mut array_args_data_iter = array_args_data.iter(); - let after_arrays_data_offset = ap_offset; - let mut arg_iter = args.iter().enumerate(); - let mut param_index = 0; - let mut expected_arguments_size = 0; - if func.signature.param_types.iter().any(|ty| { - get_info(sierra_program_registry, ty) - .map(|x| x.long_id.generic_id == SegmentArenaType::ID) - .unwrap_or_default() - }) { - casm_extend! {ctx, - // SegmentArena segment. - %{ memory[ap + 0] = segments.add() %} - // Infos segment. - %{ memory[ap + 1] = segments.add() %} - ap += 2; - [ap + 0] = 0, ap++; - // Write Infos segment, n_constructed (0), and n_destructed (0) to the segment. - [ap - 2] = [[ap - 3]]; - [ap - 1] = [[ap - 3] + 1]; - [ap - 1] = [[ap - 3] + 2]; - } - ap_offset += 3; - } - for ty in func.signature.param_types.iter() { - let info = get_info(sierra_program_registry, ty) - .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; - let generic_ty = &info.long_id.generic_id; - if let Some(offset) = builtin_offset.get(generic_ty) { - let mut offset = *offset; - if proof_mode { - // Everything is off by 2 due to the proof mode header - offset += 2; - } - casm_extend! {ctx, - [ap + 0] = [fp - offset], ap++; - } - ap_offset += 1; - } else if generic_ty == &SystemType::ID { - casm_extend! {ctx, - %{ memory[ap + 0] = segments.add() %} - ap += 1; - } - ap_offset += 1; - } else if generic_ty == &GasBuiltinType::ID { - casm_extend! {ctx, - [ap + 0] = initial_gas, ap++; - } - ap_offset += 1; - } else if generic_ty == &SegmentArenaType::ID { - let offset = -ap_offset + after_arrays_data_offset; - casm_extend! {ctx, - [ap + 0] = [ap + offset] + 3, ap++; - } - ap_offset += 1; - } else { - let ty_size = type_sizes[ty]; - let param_ap_offset_end = ap_offset + ty_size; - expected_arguments_size += ty_size; - while ap_offset < param_ap_offset_end { - let Some((arg_index, arg)) = arg_iter.next() else { - break; - }; - match arg { - FuncArg::Single(value) => { - casm_extend! {ctx, - [ap + 0] = (value.to_bigint()), ap++; - } - ap_offset += 1; - } - FuncArg::Array(values) => { - let offset = -ap_offset + array_args_data_iter.next().unwrap(); - casm_extend! {ctx, - [ap + 0] = [ap + (offset)], ap++; - [ap + 0] = [ap - 1] + (values.len()), ap++; - } - ap_offset += 2; - if ap_offset > param_ap_offset_end { - return Err(Error::ArgumentUnaligned { - param_index, - arg_index, - }); - } - } - } - } - param_index += 1; - }; - } - let actual_args_size = args - .iter() - .map(|arg| match arg { - FuncArg::Single(_) => 1, - FuncArg::Array(_) => 2, - }) - .sum::(); - if expected_arguments_size != actual_args_size { - return Err(Error::ArgumentsSizeMismatch { - expected: expected_arguments_size, - actual: actual_args_size, - }); - } - - let before_final_call = ctx.current_code_offset; - let final_call_size = 3; - let offset = final_call_size - + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; - - casm_extend! {ctx, - call rel offset; - ret; - } - assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); - - Ok((ctx.instructions, builtins)) -} - -fn get_info<'a>( - sierra_program_registry: &'a ProgramRegistry, - ty: &'a cairo_lang_sierra::ids::ConcreteTypeId, -) -> Option<&'a cairo_lang_sierra::extensions::types::TypeInfo> { - sierra_program_registry - .get_type(ty) - .ok() - .map(|ctc| ctc.info()) -} - -/// Creates the metadata required for a Sierra program lowering to casm. -fn create_metadata( - sierra_program: &cairo_lang_sierra::program::Program, - metadata_config: Option, -) -> Result { - if let Some(metadata_config) = metadata_config { - calc_metadata(sierra_program, metadata_config).map_err(|err| match err { - MetadataError::ApChangeError(_) => VirtualMachineError::Unexpected, - MetadataError::CostError(_) => VirtualMachineError::Unexpected, - }) - } else { - Ok(Metadata { - ap_change_info: calc_ap_changes(sierra_program, |_, _| 0) - .map_err(|_| VirtualMachineError::Unexpected)?, - gas_info: GasInfo { - variable_values: Default::default(), - function_costs: Default::default(), - }, - }) - } -} - -/// Type representing the Output builtin. -#[derive(Default)] -pub struct OutputType {} -impl cairo_lang_sierra::extensions::NoGenericArgsGenericType for OutputType { - const ID: cairo_lang_sierra::ids::GenericTypeId = - cairo_lang_sierra::ids::GenericTypeId::new_inline("Output"); - const STORABLE: bool = true; - const DUPLICATABLE: bool = false; - const DROPPABLE: bool = false; - const ZERO_SIZED: bool = false; -} - -fn get_function_builtins( - func: &Function, - proof_mode: bool, -) -> ( - Vec, - HashMap, -) { - let entry_params = &func.signature.param_types; - let mut builtins = Vec::new(); - let mut builtin_offset: HashMap = HashMap::new(); - let mut current_offset = 3; - // Fetch builtins from the entry_params in the standard order - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Poseidon".into())) - { - builtins.push(BuiltinName::poseidon); - builtin_offset.insert(PoseidonType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("EcOp".into())) - { - builtins.push(BuiltinName::ec_op); - builtin_offset.insert(EcOpType::ID, current_offset); - current_offset += 1 - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Bitwise".into())) - { - builtins.push(BuiltinName::bitwise); - builtin_offset.insert(BitwiseType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("RangeCheck".into())) - { - builtins.push(BuiltinName::range_check); - builtin_offset.insert(RangeCheckType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Pedersen".into())) - { - builtins.push(BuiltinName::pedersen); - builtin_offset.insert(PedersenType::ID, current_offset); - current_offset += 1; - } - // Force an output builtin so that we can write the program output into it's segment - if proof_mode { - builtins.push(BuiltinName::output); - builtin_offset.insert(OutputType::ID, current_offset); - } - builtins.reverse(); - (builtins, builtin_offset) -} - -fn serialize_output(vm: &VirtualMachine, return_values: &[MaybeRelocatable]) -> String { +pub fn serialize_output(vm: &VirtualMachine, return_values: &[MaybeRelocatable]) -> String { let mut output_string = String::new(); let mut return_values_iter: Peekable> = return_values.iter().peekable(); serialize_output_inner(&mut return_values_iter, &mut output_string, vm); @@ -997,7 +367,6 @@ fn serialize_output(vm: &VirtualMachine, return_values: &[MaybeRelocatable]) -> #[cfg(test)] mod tests { - #![allow(clippy::too_many_arguments)] use super::*; use assert_matches::assert_matches; use rstest::rstest;