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 stack: Vec<Felt>,
75 advice: Advice,
77}
78
79#[derive(Debug, Clone, Default, Deserialize)]
80#[serde(default)]
81struct Advice {
82 stack: Vec<Felt>,
84 map: Vec<AdviceMapEntry>,
86}
87
88#[derive(Debug, Clone, Deserialize)]
89struct AdviceMapEntry {
90 digest: Word,
91 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 true,
173 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}