Skip to main content

miden_debug_engine/exec/
config.rs

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