Skip to main content

miden_debug_engine/exec/
config.rs

1#[cfg(feature = "tui")]
2use std::{ffi::OsStr, path::Path};
3
4use miden_processor::{
5    ExecutionOptions, StackInputs,
6    advice::{AdviceInputs, AdviceStack},
7};
8use serde::Deserialize;
9
10use crate::felt::Felt;
11
12#[derive(Debug, Clone, Default, Deserialize)]
13#[serde(try_from = "ExecutionConfigFile")]
14pub struct ExecutionConfig {
15    pub inputs: StackInputs,
16    pub advice_inputs: AdviceInputs,
17    pub options: ExecutionOptions,
18}
19
20impl TryFrom<ExecutionConfigFile> for ExecutionConfig {
21    type Error = String;
22
23    #[inline]
24    fn try_from(file: ExecutionConfigFile) -> Result<Self, Self::Error> {
25        Self::from_inputs_file(file)
26    }
27}
28
29impl ExecutionConfig {
30    pub fn parse_file<P>(path: P) -> std::io::Result<Self>
31    where
32        P: AsRef<std::path::Path>,
33    {
34        let path = path.as_ref();
35        let content = std::fs::read_to_string(path)?;
36
37        let file =
38            toml::from_str::<ExecutionConfigFile>(&content).map_err(std::io::Error::other)?;
39        Self::from_inputs_file(file).map_err(std::io::Error::other)
40    }
41
42    pub fn parse_str(content: &str) -> Result<Self, String> {
43        let file = toml::from_str::<ExecutionConfigFile>(content).map_err(|err| err.to_string())?;
44
45        Self::from_inputs_file(file)
46    }
47
48    fn from_inputs_file(file: ExecutionConfigFile) -> Result<Self, String> {
49        let felts: Vec<_> = file.inputs.stack.into_iter().map(|felt| felt.0).collect();
50        let inputs =
51            StackInputs::new(&felts).map_err(|err| format!("invalid value for 'stack': {err}"))?;
52        let advice_inputs = AdviceInputs::default()
53            .with_advice_stack(
54                file.inputs.advice.stack.into_iter().map(|felt| felt.0).collect::<AdviceStack>(),
55            )
56            .with_map(file.inputs.advice.map.into_iter().map(|entry| {
57                (entry.digest.0, entry.values.into_iter().map(|felt| felt.0).collect::<Vec<_>>())
58            }));
59
60        Ok(Self {
61            inputs,
62            advice_inputs,
63            options: file.options,
64        })
65    }
66}
67
68#[derive(Debug, Clone, Default, Deserialize)]
69#[serde(default)]
70struct ExecutionConfigFile {
71    inputs: Inputs,
72    #[serde(deserialize_with = "deserialize_execution_options")]
73    options: ExecutionOptions,
74}
75
76#[derive(Debug, Clone, Default, Deserialize)]
77#[serde(default)]
78struct Inputs {
79    /// The contents of the operand stack, top is leftmost
80    stack: Vec<Felt>,
81    /// The inputs to the advice provider
82    advice: Advice,
83}
84
85#[derive(Debug, Clone, Default, Deserialize)]
86#[serde(default)]
87struct Advice {
88    /// The contents of the advice stack, top is leftmost
89    stack: Vec<Felt>,
90    /// Entries to populate the advice map with
91    map: Vec<AdviceMapEntry>,
92}
93
94#[derive(Debug, Clone, Deserialize)]
95struct AdviceMapEntry {
96    digest: Word,
97    /// Values that will be pushed to the advice stack when this entry is requested
98    values: Vec<Felt>,
99}
100
101#[cfg(feature = "tui")]
102impl clap::builder::ValueParserFactory for ExecutionConfig {
103    type Parser = ExecutionConfigParser;
104
105    fn value_parser() -> Self::Parser {
106        ExecutionConfigParser
107    }
108}
109
110#[cfg(feature = "tui")]
111#[doc(hidden)]
112#[derive(Clone)]
113pub struct ExecutionConfigParser;
114
115#[cfg(feature = "tui")]
116impl clap::builder::TypedValueParser for ExecutionConfigParser {
117    type Value = ExecutionConfig;
118
119    fn parse_ref(
120        &self,
121        _cmd: &clap::Command,
122        _arg: Option<&clap::Arg>,
123        value: &OsStr,
124    ) -> Result<Self::Value, clap::error::Error> {
125        use clap::error::{Error, ErrorKind};
126
127        let inputs_path = Path::new(value);
128        if !inputs_path.is_file() {
129            return Err(Error::raw(
130                ErrorKind::InvalidValue,
131                format!("invalid inputs file: '{}' is not a file", inputs_path.display()),
132            ));
133        }
134
135        let content = std::fs::read_to_string(inputs_path).map_err(|err| {
136            Error::raw(ErrorKind::ValueValidation, format!("failed to read inputs file: {err}"))
137        })?;
138        let inputs_file = toml::from_str::<ExecutionConfigFile>(&content).map_err(|err| {
139            Error::raw(ErrorKind::ValueValidation, format!("invalid inputs file: {err}"))
140        })?;
141
142        ExecutionConfig::from_inputs_file(inputs_file).map_err(|err| {
143            Error::raw(ErrorKind::ValueValidation, format!("invalid inputs file: {err}"))
144        })
145    }
146}
147
148#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
149struct Word(miden_core::Word);
150impl<'de> Deserialize<'de> for Word {
151    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152    where
153        D: serde::Deserializer<'de>,
154    {
155        let digest = String::deserialize(deserializer)?;
156        miden_core::Word::try_from(&digest)
157            .map_err(|err| serde::de::Error::custom(format!("invalid digest: {err}")))
158            .map(Self)
159    }
160}
161
162fn deserialize_execution_options<'de, D>(deserializer: D) -> Result<ExecutionOptions, D::Error>
163where
164    D: serde::Deserializer<'de>,
165{
166    #[derive(Default, Deserialize)]
167    #[serde(default)]
168    struct ExecOptions {
169        max_cycles: Option<u32>,
170        expected_cycles: u32,
171    }
172
173    ExecOptions::deserialize(deserializer).and_then(|opts| {
174        ExecutionOptions::new(
175            opts.max_cycles,
176            opts.expected_cycles,
177            ExecutionOptions::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
178        )
179        .map_err(|err| serde::de::Error::custom(format!("invalid execution options: {err}")))
180    })
181}
182
183#[cfg(test)]
184mod tests {
185    use miden_processor::Felt as RawFelt;
186    use toml::toml;
187
188    use super::{ExecutionConfig, *};
189
190    #[test]
191    fn execution_config_empty() {
192        let text = toml::to_string_pretty(&toml! {
193            [inputs]
194            [options]
195        })
196        .unwrap();
197
198        let file = toml::from_str::<ExecutionConfig>(&text).unwrap();
199        let expected_inputs = StackInputs::new(&[]).unwrap();
200        assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
201        assert!(file.advice_inputs.advice_stack().is_empty());
202        assert_eq!(file.options.max_cycles(), ExecutionOptions::MAX_CYCLES);
203        assert_eq!(file.options.expected_cycles(), ExecutionOptions::default().expected_cycles());
204    }
205
206    #[test]
207    fn execution_config_with_options() {
208        let text = toml::to_string_pretty(&toml! {
209            [inputs]
210            [options]
211            max_cycles = 100000
212        })
213        .unwrap();
214
215        let file = ExecutionConfig::parse_str(&text).unwrap();
216        let expected_inputs = StackInputs::new(&[]).unwrap();
217        assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
218        assert!(file.advice_inputs.advice_stack().is_empty());
219        assert_eq!(file.options.max_cycles(), 100000);
220        assert_eq!(file.options.expected_cycles(), ExecutionOptions::default().expected_cycles());
221    }
222
223    #[test]
224    fn execution_config_with_operands() {
225        let text = toml::to_string_pretty(&toml! {
226            [inputs]
227            stack = [1, 2, 3]
228
229            [options]
230            max_cycles = 100000
231        })
232        .unwrap();
233
234        let file = ExecutionConfig::parse_str(&text).unwrap();
235        let expected_inputs = StackInputs::new(&[
236            RawFelt::new(1).expect("value exceeds field modulus"),
237            RawFelt::new(2).expect("value exceeds field modulus"),
238            RawFelt::new(3).expect("value exceeds field modulus"),
239        ])
240        .unwrap();
241        assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
242        assert!(file.advice_inputs.advice_stack().is_empty());
243        assert_eq!(file.options.max_cycles(), 100000);
244        assert_eq!(file.options.expected_cycles(), ExecutionOptions::default().expected_cycles());
245    }
246
247    #[test]
248    fn execution_config_with_advice() {
249        let text = toml::to_string_pretty(&toml! {
250            [inputs]
251            stack = [1, 2, 0x3]
252
253            [inputs.advice]
254            stack = [1, 2, 3, 4]
255
256            [[inputs.advice.map]]
257            digest = "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63"
258            values = [1, 2, 3, 4]
259
260            [options]
261            max_cycles = 100000
262        })
263        .unwrap();
264        let digest = miden_core::Word::try_from(
265            "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63",
266        )
267        .unwrap();
268        let file = ExecutionConfig::parse_str(&text).unwrap_or_else(|err| panic!("{err}"));
269        let expected_inputs = StackInputs::new(&[
270            RawFelt::new(1).expect("value exceeds field modulus"),
271            RawFelt::new(2).expect("value exceeds field modulus"),
272            RawFelt::new(3).expect("value exceeds field modulus"),
273        ])
274        .unwrap();
275        assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
276        assert_eq!(
277            file.advice_inputs.advice_stack().into_elements(),
278            &[
279                RawFelt::new(1).expect("value exceeds field modulus"),
280                RawFelt::new(2).expect("value exceeds field modulus"),
281                RawFelt::new(3).expect("value exceeds field modulus"),
282                RawFelt::new(4).expect("value exceeds field modulus")
283            ]
284        );
285        assert_eq!(
286            file.advice_inputs.map.get(&digest).map(|value| value.as_ref()),
287            Some(
288                [
289                    RawFelt::new(1).expect("value exceeds field modulus"),
290                    RawFelt::new(2).expect("value exceeds field modulus"),
291                    RawFelt::new(3).expect("value exceeds field modulus"),
292                    RawFelt::new(4).expect("value exceeds field modulus")
293                ]
294                .as_slice()
295            )
296        );
297        assert_eq!(file.options.max_cycles(), 100000);
298        assert_eq!(file.options.expected_cycles(), ExecutionOptions::default().expected_cycles());
299    }
300}