cairo_lang_sierra_to_casm/
metadata.rs

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use cairo_lang_sierra::extensions::gas::CostTokenType;
use cairo_lang_sierra::ids::FunctionId;
use cairo_lang_sierra::program::Program;
use cairo_lang_sierra_ap_change::ap_change_info::ApChangeInfo;
use cairo_lang_sierra_ap_change::compute::calc_ap_changes as linear_calc_ap_changes;
use cairo_lang_sierra_ap_change::{calc_ap_changes, ApChangeError};
use cairo_lang_sierra_gas::gas_info::GasInfo;
use cairo_lang_sierra_gas::objects::ConstCost;
use cairo_lang_sierra_gas::{
    calc_gas_postcost_info, calc_gas_precost_info, compute_postcost_info, compute_precost_info,
    CostError,
};
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use thiserror::Error;

#[derive(Default)]
/// Metadata provided with a Sierra program to simplify the compilation to casm.
pub struct Metadata {
    /// AP changes information for Sierra user functions.
    pub ap_change_info: ApChangeInfo,
    /// Gas information for validating Sierra code and taking the appropriate amount of gas.
    pub gas_info: GasInfo,
}

/// Error for metadata calculations.
#[derive(Debug, Error, Eq, PartialEq)]
pub enum MetadataError {
    #[error(transparent)]
    ApChangeError(#[from] ApChangeError),
    #[error(transparent)]
    CostError(#[from] CostError),
}

/// Configuration for metadata computation.
#[derive(Clone)]
pub struct MetadataComputationConfig {
    /// Functions to enforce costs for, as well as the costs to enforce.
    pub function_set_costs: OrderedHashMap<FunctionId, OrderedHashMap<CostTokenType, i32>>,
    /// If true, uses a linear-time algorithm for calculating the gas, instead of solving
    /// equations.
    pub linear_gas_solver: bool,
    /// If true, uses a linear-time algorithm for calculating ap changes, instead of solving
    /// equations.
    pub linear_ap_change_solver: bool,
    /// When running the non-linear solver do not check for contradictions with the linear
    /// solution. Used for testing only.
    pub skip_non_linear_solver_comparisons: bool,
    /// If true, compute the runtime cost token types (steps, holes and range-checks) in addition
    /// to the usual gas costs (used in Sierra-to-casm compilation).
    pub compute_runtime_costs: bool,
}

impl Default for MetadataComputationConfig {
    fn default() -> Self {
        Self {
            function_set_costs: Default::default(),
            linear_gas_solver: true,
            linear_ap_change_solver: true,
            skip_non_linear_solver_comparisons: false,
            compute_runtime_costs: false,
        }
    }
}

/// Calculates the metadata for a Sierra program, with ap change info only.
pub fn calc_metadata_ap_change_only(program: &Program) -> Result<Metadata, MetadataError> {
    Ok(Metadata {
        ap_change_info: calc_ap_changes(program, |_, _| 0)?,
        gas_info: GasInfo {
            variable_values: Default::default(),
            function_costs: Default::default(),
        },
    })
}

/// Calculates the metadata for a Sierra program.
///
/// `no_eq_solver` uses a linear-time algorithm for calculating the gas, instead of solving
/// equations.
pub fn calc_metadata(
    program: &Program,
    config: MetadataComputationConfig,
) -> Result<Metadata, MetadataError> {
    let pre_function_set_costs = config
        .function_set_costs
        .iter()
        .map(|(func, costs)| {
            (
                func.clone(),
                CostTokenType::iter_precost()
                    .filter_map(|token| costs.get(token).map(|v| (*token, *v)))
                    .collect(),
            )
        })
        .collect();
    let pre_gas_info_new = compute_precost_info(program)?;
    let pre_gas_info = if config.linear_gas_solver {
        pre_gas_info_new
    } else {
        let pre_gas_info_old = calc_gas_precost_info(program, pre_function_set_costs)?;
        if !config.skip_non_linear_solver_comparisons {
            pre_gas_info_old.assert_eq_variables(&pre_gas_info_new, program);
            pre_gas_info_old.assert_eq_functions(&pre_gas_info_new);
        }
        pre_gas_info_old
    };

    let ap_change_info =
        if config.linear_ap_change_solver { linear_calc_ap_changes } else { calc_ap_changes }(
            program,
            |idx, token_type| pre_gas_info.variable_values[&(idx, token_type)] as usize,
        )?;

    let mut post_gas_info = if config.linear_gas_solver {
        let enforced_function_costs: OrderedHashMap<FunctionId, i32> = config
            .function_set_costs
            .iter()
            .map(|(func, costs)| (func.clone(), costs[&CostTokenType::Const]))
            .collect();
        compute_postcost_info(
            program,
            &|idx| ap_change_info.variable_values.get(idx).copied().unwrap_or_default(),
            &pre_gas_info,
            &enforced_function_costs,
        )
    } else {
        let post_function_set_costs = config
            .function_set_costs
            .iter()
            .map(|(func, costs)| {
                (
                    func.clone(),
                    [CostTokenType::Const]
                        .iter()
                        .filter_map(|token| costs.get(token).map(|v| (*token, *v)))
                        .collect(),
                )
            })
            .collect();
        calc_gas_postcost_info(program, post_function_set_costs, &pre_gas_info, |idx| {
            ap_change_info.variable_values.get(&idx).copied().unwrap_or_default()
        })
    }?;

    if config.compute_runtime_costs {
        let post_gas_info_runtime = compute_postcost_info::<ConstCost>(
            program,
            &|idx| ap_change_info.variable_values.get(idx).copied().unwrap_or_default(),
            &pre_gas_info,
            &Default::default(),
        )?;
        post_gas_info = post_gas_info.combine(post_gas_info_runtime);
    }

    Ok(Metadata { ap_change_info, gas_info: pre_gas_info.combine(post_gas_info) })
}