Skip to main content

miden_debug_engine/exec/
config.rs

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