Skip to main content

miden_debug/exec/
config.rs

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