Skip to main content

cairo_program_runner_lib/hints/
utils.rs

1use std::any::Any;
2use std::cmp::min;
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6use super::types::Task;
7use super::PROGRAM_INPUT;
8use crate::hints::fact_topologies::GPS_FACT_TOPOLOGY;
9use crate::hints::types::ProgramIdentifiers;
10use crate::utils::ProgramInput;
11use cairo_vm::hint_processor::builtin_hint_processor::hint_utils::get_ptr_from_var_name;
12use cairo_vm::hint_processor::hint_processor_definition::HintReference;
13use cairo_vm::serde::deserialize_program::{ApTracking, Identifier};
14use cairo_vm::types::exec_scope::ExecutionScopes;
15use cairo_vm::types::program::Program;
16use cairo_vm::types::relocatable::{MaybeRelocatable, Relocatable};
17use cairo_vm::vm::errors::hint_errors::HintError;
18use cairo_vm::vm::errors::memory_errors::MemoryError;
19use cairo_vm::vm::runners::builtin_runner::OutputBuiltinRunner;
20use cairo_vm::vm::runners::cairo_pie::StrippedProgram;
21use cairo_vm::vm::vm_core::VirtualMachine;
22use serde::de::DeserializeOwned;
23
24#[macro_export]
25macro_rules! maybe_relocatable_box {
26    ($val:expr) => {
27        Box::new(MaybeRelocatable::from($val)) as Box<dyn Any>
28    };
29}
30
31/// Retrieves program identifiers from the execution scopes.
32///
33/// # Arguments
34/// * `exec_scopes` - A reference to `ExecutionScopes`, which holds the execution environment
35///   variables.
36/// * `program` - A `&str` representing the name of the program whose identifiers are being sought.
37///
38/// # Returns
39/// * `Result<ProgramIdentifiers, HintError>` - Returns a `HashMap` containing the program
40///   identifiers (each as a key-value pair where both key and value are cloned as `String`), or a
41///   `HintError` if the specified program is not found in `exec_scopes`.
42///
43/// # Errors
44/// * `HintError::VariableNotInScopeError` - Returned if the specified `program` is not found in
45///   `exec_scopes`.
46pub fn get_program_identifies(
47    exec_scopes: &ExecutionScopes,
48    program: &str,
49) -> Result<ProgramIdentifiers, HintError> {
50    if let Ok(program) = exec_scopes.get::<Program>(program) {
51        return Ok(program
52            .iter_identifiers()
53            .map(|(k, v)| (k.to_string(), v.clone()))
54            .collect());
55    }
56
57    Err(HintError::VariableNotInScopeError(
58        program.to_string().into_boxed_str(),
59    ))
60}
61
62fn parse_program_input_from_str<T: DeserializeOwned>(json: &str) -> Result<T, HintError> {
63    serde_json::from_str(json).map_err(|e| {
64        HintError::CustomHint(format!("Failed to parse program input JSON: {e}").into())
65    })
66}
67
68fn parse_program_input_from_path<T: DeserializeOwned>(path: &PathBuf) -> Result<T, HintError> {
69    let json = std::fs::read_to_string(path).map_err(|e| {
70        HintError::CustomHint(format!("Failed to read program input from {path:?}: {e}").into())
71    })?;
72    parse_program_input_from_str(&json)
73}
74
75pub fn get_program_input_value<T>(exec_scopes: &ExecutionScopes) -> Result<T, HintError>
76where
77    T: DeserializeOwned + Clone + 'static,
78{
79    let program_input = exec_scopes
80        .get_ref::<ProgramInput>(PROGRAM_INPUT)
81        .map_err(|_| {
82            HintError::CustomHint("Program input was not found in execution scopes.".into())
83        })?;
84    match program_input {
85        ProgramInput::Json(json) => parse_program_input_from_str(json),
86        ProgramInput::Path(path) => parse_program_input_from_path(path),
87        ProgramInput::Value(value) => {
88            if let Some(typed) = value.downcast_ref::<T>() {
89                // TODO: avoid clone by returning a borrowed value after refactor.
90                return Ok(typed.clone());
91            }
92            Err(HintError::CustomHint(
93                "Program input value has unsupported in-memory type.".into(),
94            ))
95        }
96    }
97}
98
99/// Fetches a specific identifier's program counter (PC) from a given identifiers map.
100///
101/// # Arguments
102/// * `identifiers` - A reference to a `HashMap` where each key is an identifier's name and each
103///   value is an `Identifier` containing details about that identifier.
104/// * `name` - A `&str` representing the name of the identifier whose program counter is being
105///   sought.
106///
107/// # Returns
108/// * `Result<usize, HintError>` - Returns the program counter (`pc`) of the specified identifier if
109///   it exists and has an associated `pc`, otherwise returns a `HintError`.
110///
111/// # Errors
112/// * `HintError::VariableNotInScopeError` - Returned if the specified `name` is not found in
113///   `identifiers` or does not contain a program counter.
114pub fn get_identifier(
115    identifiers: &HashMap<String, Identifier>,
116    name: &str,
117) -> Result<usize, HintError> {
118    if let Some(identifier) = identifiers.get(name) {
119        if let Some(pc) = identifier.pc {
120            return Ok(pc);
121        }
122    }
123
124    Err(HintError::VariableNotInScopeError(
125        name.to_string().into_boxed_str(),
126    ))
127}
128
129/// Mimics the behaviour of the Python VM `gen_arg`.
130///
131/// Creates a new segment for each vector encountered in `args`. For each new
132/// segment, the pointer to the segment will be added to the current segment.
133///
134/// Example: `vec![1, 2, vec![3, 4]]`
135/// -> Allocates segment N, starts writing at offset 0:
136/// (N, 0): 1       # Write the values of the vector one by one
137/// (N, 1): 2
138/// -> a vector is encountered, allocate a new segment
139/// (N, 2): N+1     # Pointer to the new segment
140/// (N+1, 0): 3     # Write the values of the nested vector
141/// (N+1, 1): 4
142pub fn gen_arg(
143    vm: &mut VirtualMachine,
144    args: &Vec<Box<dyn Any>>,
145) -> Result<Relocatable, MemoryError> {
146    let base = vm.segments.add();
147    let mut ptr = base;
148    for arg in args {
149        if let Some(value) = arg.downcast_ref::<MaybeRelocatable>() {
150            ptr = vm.segments.load_data(ptr, std::slice::from_ref(value))?;
151        } else if let Some(vector) = arg.downcast_ref::<Vec<Box<dyn Any>>>() {
152            let nested_base = gen_arg(vm, vector)?;
153            ptr = vm.segments.load_data(ptr, &[nested_base.into()])?;
154        } else {
155            return Err(MemoryError::GenArgInvalidType);
156        }
157    }
158
159    Ok(base)
160}
161
162pub fn get_program_from_task(task: &Task) -> Result<StrippedProgram, HintError> {
163    task.get_program()
164        .map_err(|e| HintError::CustomHint(e.to_string().into_boxed_str()))
165}
166
167// Splits the outputs into pages of a given size, starting from `output_start` and
168// ending at `output_ptr`. Returns the number of pages created.
169pub fn split_outputs_to_pages(
170    output_start: Relocatable,
171    output_ptr: Relocatable,
172    output_builtin: &mut OutputBuiltinRunner,
173    page_size: usize,
174) -> Result<usize, HintError> {
175    let mut next_page_start = min((output_start + page_size)?, output_ptr);
176    let mut next_page_id = 1;
177    while next_page_start < output_ptr {
178        let current_page_size = min(output_ptr.offset - next_page_start.offset, page_size);
179
180        output_builtin
181            .add_page(next_page_id, next_page_start, current_page_size)
182            .map_err(|e| {
183                HintError::CustomHint(format!("Failed to add page to output builtin: {e:?}").into())
184            })?;
185
186        next_page_start = (next_page_start + page_size)?;
187        next_page_id += 1;
188    }
189    Ok(next_page_id)
190}
191
192// Adds a fact topology to the output builtin runner according to the number of pages.
193pub fn add_fact_topology(output_builtin: &mut OutputBuiltinRunner, n_pages: usize) {
194    if n_pages == 1 {
195        output_builtin.add_attribute(GPS_FACT_TOPOLOGY.into(), [1, 0].to_vec());
196    } else {
197        output_builtin.add_attribute(
198            GPS_FACT_TOPOLOGY.into(),
199            [n_pages, n_pages - 1, 0, 2].to_vec(),
200        );
201    }
202}
203
204// Splits the outputs into pages of a given size, and adds fact topology to the builtin runner.
205pub fn output_builtin_set_pages_by_size_and_fact_topology(
206    vm: &mut VirtualMachine,
207    ids_data: &HashMap<String, HintReference>,
208    ap_tracking: &ApTracking,
209    page_size: usize,
210) -> Result<(), HintError> {
211    let output_start = get_ptr_from_var_name("output_start", vm, ids_data, ap_tracking)?;
212    let output_ptr = get_ptr_from_var_name("output_ptr", vm, ids_data, ap_tracking)?;
213    let output_builtin = vm.get_output_builtin_mut()?;
214    let n_pages = split_outputs_to_pages(output_start, output_ptr, output_builtin, page_size)?;
215    add_fact_topology(output_builtin, n_pages);
216    Ok(())
217}