miden_debug/exec/
config.rs

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