midenc_debug/
config.rs

1use std::{
2    collections::BTreeMap,
3    ffi::OsStr,
4    path::{Path, PathBuf},
5};
6
7use miden_processor::{AdviceInputs, ExecutionOptions, Felt as RawFelt, StackInputs};
8use serde::Deserialize;
9
10use crate::Felt;
11
12#[derive(Debug, Clone, Default, Deserialize)]
13#[serde(try_from = "DebuggerConfigFile")]
14pub struct DebuggerConfig {
15    pub inputs: StackInputs,
16    pub advice_inputs: AdviceInputs,
17    pub options: ExecutionOptions,
18}
19
20impl TryFrom<DebuggerConfigFile> for DebuggerConfig {
21    type Error = String;
22
23    #[inline]
24    fn try_from(mut file: DebuggerConfigFile) -> Result<Self, Self::Error> {
25        Self::from_inputs_file(file, None)
26    }
27}
28
29impl DebuggerConfig {
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 = toml::from_str::<DebuggerConfigFile>(&content).map_err(std::io::Error::other)?;
38        Self::from_inputs_file(file, path.parent().map(|p| p.to_path_buf()))
39            .map_err(std::io::Error::other)
40    }
41
42    pub fn parse_str(content: &str) -> Result<Self, String> {
43        let file = toml::from_str::<DebuggerConfigFile>(content).map_err(|err| err.to_string())?;
44
45        Self::from_inputs_file(file, None)
46    }
47
48    fn from_inputs_file(
49        mut file: DebuggerConfigFile,
50        _cwd: Option<PathBuf>,
51    ) -> Result<Self, String> {
52        let inputs = StackInputs::new(file.inputs.stack.into_iter().map(|felt| felt.0).collect())
53            .map_err(|err| format!("invalid value for 'stack': {err}"))?;
54        let advice_inputs = AdviceInputs::default()
55            .with_stack(file.inputs.advice.stack.into_iter().rev().map(|felt| felt.0))
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 DebuggerConfigFile {
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<crate::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<crate::Felt>,
90    /// Entries to populate the advice map with
91    map: Vec<AdviceMapEntry>,
92}
93
94#[derive(Debug, Clone, Deserialize)]
95struct AdviceMapEntry {
96    digest: Digest,
97    /// Values that will be pushed to the advice stack when this entry is requested
98    #[serde(default)]
99    values: Vec<crate::Felt>,
100}
101
102impl clap::builder::ValueParserFactory for DebuggerConfig {
103    type Parser = DebuggerConfigParser;
104
105    fn value_parser() -> Self::Parser {
106        DebuggerConfigParser
107    }
108}
109
110#[doc(hidden)]
111#[derive(Clone)]
112pub struct DebuggerConfigParser;
113impl clap::builder::TypedValueParser for DebuggerConfigParser {
114    type Value = DebuggerConfig;
115
116    fn parse_ref(
117        &self,
118        _cmd: &clap::Command,
119        _arg: Option<&clap::Arg>,
120        value: &OsStr,
121    ) -> Result<Self::Value, clap::error::Error> {
122        use clap::error::{Error, ErrorKind};
123
124        let inputs_path = Path::new(value);
125        if !inputs_path.is_file() {
126            return Err(Error::raw(
127                ErrorKind::InvalidValue,
128                format!("invalid inputs file: '{}' is not a file", inputs_path.display()),
129            ));
130        }
131
132        let content = std::fs::read_to_string(inputs_path).map_err(|err| {
133            Error::raw(ErrorKind::ValueValidation, format!("failed to read inputs file: {err}"))
134        })?;
135        let inputs_file = toml::from_str::<DebuggerConfigFile>(&content).map_err(|err| {
136            Error::raw(ErrorKind::ValueValidation, format!("invalid inputs file: {err}"))
137        })?;
138
139        DebuggerConfig::from_inputs_file(inputs_file, Some(inputs_path.to_path_buf())).map_err(
140            |err| Error::raw(ErrorKind::ValueValidation, format!("invalid inputs file: {err}")),
141        )
142    }
143}
144
145#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
146struct Digest(miden_processor::Digest);
147impl<'de> Deserialize<'de> for Digest {
148    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
149    where
150        D: serde::Deserializer<'de>,
151    {
152        let digest = String::deserialize(deserializer)?;
153        miden_processor::Digest::try_from(&digest)
154            .map_err(|err| serde::de::Error::custom(format!("invalid digest: {err}")))
155            .map(Self)
156    }
157}
158
159fn deserialize_rodata_bytes<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
160where
161    D: serde::Deserializer<'de>,
162{
163    use midenc_hir::constants::ConstantData;
164
165    String::deserialize(deserializer).and_then(|hex| {
166        ConstantData::from_str_be(hex.as_str())
167            .map_err(|err| serde::de::Error::custom(format!("invalid rodata: {err}")))
168            .map(Vec::<u8>::from)
169    })
170}
171
172fn deserialize_execution_options<'de, D>(deserializer: D) -> Result<ExecutionOptions, D::Error>
173where
174    D: serde::Deserializer<'de>,
175{
176    #[derive(Default, Deserialize)]
177    #[serde(default)]
178    struct ExecOptions {
179        max_cycles: Option<u32>,
180        expected_cycles: u32,
181    }
182
183    ExecOptions::deserialize(deserializer).and_then(|opts| {
184        ExecutionOptions::new(
185            opts.max_cycles,
186            opts.expected_cycles,
187            /* enable_tracing= */ true,
188            /* enable_debugging= */ true,
189        )
190        .map(|exec_opts| exec_opts.with_debugging(true))
191        .map_err(|err| serde::de::Error::custom(format!("invalid execution options: {err}")))
192    })
193}
194
195#[cfg(test)]
196mod tests {
197    use toml::toml;
198
199    use super::*;
200
201    #[test]
202    fn debugger_config_empty() {
203        let text = toml::to_string_pretty(&toml! {
204            [inputs]
205            [options]
206        })
207        .unwrap();
208
209        let file = toml::from_str::<DebuggerConfig>(&text).unwrap();
210        let expected_inputs = StackInputs::new(vec![]).unwrap();
211        assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
212        assert!(file.advice_inputs.stack().is_empty());
213        assert!(file.options.enable_tracing());
214        assert!(file.options.enable_debugging());
215        assert_eq!(file.options.max_cycles(), u32::MAX);
216        assert_eq!(file.options.expected_cycles(), 64);
217    }
218
219    #[test]
220    fn debugger_config_with_options() {
221        let text = toml::to_string_pretty(&toml! {
222            [inputs]
223            [options]
224            max_cycles = 1000
225        })
226        .unwrap();
227
228        let file = DebuggerConfig::parse_str(&text).unwrap();
229        let expected_inputs = StackInputs::new(vec![]).unwrap();
230        assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
231        assert!(file.advice_inputs.stack().is_empty());
232        assert!(file.options.enable_tracing());
233        assert!(file.options.enable_debugging());
234        assert_eq!(file.options.max_cycles(), 1000);
235        assert_eq!(file.options.expected_cycles(), 64);
236    }
237
238    #[test]
239    fn debugger_config_with_operands() {
240        let text = toml::to_string_pretty(&toml! {
241            [inputs]
242            stack = [1, 2, 3]
243
244            [options]
245            max_cycles = 1000
246        })
247        .unwrap();
248
249        let file = DebuggerConfig::parse_str(&text).unwrap();
250        let expected_inputs =
251            StackInputs::new(vec![RawFelt::new(1), RawFelt::new(2), RawFelt::new(3)]).unwrap();
252        assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
253        assert!(file.advice_inputs.stack().is_empty());
254        assert!(file.options.enable_tracing());
255        assert!(file.options.enable_debugging());
256        assert_eq!(file.options.max_cycles(), 1000);
257        assert_eq!(file.options.expected_cycles(), 64);
258    }
259
260    #[test]
261    fn debugger_config_with_advice() {
262        let text = toml::to_string_pretty(&toml! {
263            [inputs]
264            stack = [1, 2, 0x3]
265
266            [inputs.advice]
267            stack = [1, 2, 3, 4]
268
269            [[inputs.advice.map]]
270            digest = "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63"
271            values = [1, 2, 3, 4]
272
273            [options]
274            max_cycles = 1000
275        })
276        .unwrap();
277        let digest = miden_processor::Digest::try_from(
278            "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63",
279        )
280        .unwrap();
281        let file = DebuggerConfig::parse_str(&text).unwrap_or_else(|err| panic!("{err}"));
282        let expected_inputs =
283            StackInputs::new(vec![RawFelt::new(1), RawFelt::new(2), RawFelt::new(3)]).unwrap();
284        assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
285        assert_eq!(
286            file.advice_inputs.stack(),
287            &[RawFelt::new(4), RawFelt::new(3), RawFelt::new(2), RawFelt::new(1)]
288        );
289        assert_eq!(
290            file.advice_inputs.mapped_values(&digest),
291            Some([RawFelt::new(1), RawFelt::new(2), RawFelt::new(3), RawFelt::new(4)].as_slice())
292        );
293        assert!(file.options.enable_tracing());
294        assert!(file.options.enable_debugging());
295        assert_eq!(file.options.max_cycles(), 1000);
296        assert_eq!(file.options.expected_cycles(), 64);
297    }
298}