1use std::path::PathBuf;
2
3use clap::{Parser, ValueEnum};
4use eyre::Result;
5use openvm_circuit::arch::{instructions::exe::VmExe, OPENVM_DEFAULT_INIT_FILE_NAME};
6use openvm_sdk::{config::SdkVmConfig, fs::read_object_from_file, keygen::AppProvingKey, Sdk, F};
7
8use super::{build, BuildArgs, BuildCargoArgs};
9use crate::{
10 commands::keygen::keygen,
11 input::{read_to_stdin, Input},
12 util::{
13 get_app_pk_path, get_app_vk_path, get_manifest_path_and_dir, get_single_target_name,
14 get_target_dir, read_config_toml_or_default,
15 },
16};
17
18#[derive(Clone, Debug, ValueEnum)]
19pub enum ExecutionMode {
20 Pure,
22 Meter,
24 Segment,
27}
28
29#[derive(Parser)]
30#[command(name = "run", about = "Run an OpenVM program")]
31pub struct RunCmd {
32 #[clap(flatten)]
33 run_args: RunArgs,
34
35 #[clap(flatten)]
36 cargo_args: RunCargoArgs,
37}
38
39#[derive(Clone, Parser)]
40pub struct RunArgs {
41 #[arg(
42 long,
43 action,
44 help = "Path to OpenVM executable, if specified build will be skipped",
45 help_heading = "OpenVM Options"
46 )]
47 pub exe: Option<PathBuf>,
48
49 #[arg(
50 long,
51 help = "Path to the OpenVM config .toml file that specifies the VM extensions, by default will search for the file at ${manifest_dir}/openvm.toml",
52 help_heading = "OpenVM Options"
53 )]
54 pub config: Option<PathBuf>,
55
56 #[arg(
57 long,
58 help = "Output directory that OpenVM proving artifacts will be copied to",
59 help_heading = "OpenVM Options"
60 )]
61 pub output_dir: Option<PathBuf>,
62
63 #[arg(
64 long,
65 value_parser,
66 help = "Input to OpenVM program",
67 help_heading = "OpenVM Options"
68 )]
69 pub input: Option<Input>,
70
71 #[arg(
72 long,
73 default_value = OPENVM_DEFAULT_INIT_FILE_NAME,
74 help = "Name of the init file",
75 help_heading = "OpenVM Options"
76 )]
77 pub init_file_name: String,
78
79 #[arg(
80 long,
81 value_enum,
82 default_value = "pure",
83 help = "Execution mode",
84 help_heading = "OpenVM Options"
85 )]
86 pub mode: ExecutionMode,
87}
88
89impl From<RunArgs> for BuildArgs {
90 fn from(args: RunArgs) -> Self {
91 BuildArgs {
92 config: args.config,
93 output_dir: args.output_dir,
94 init_file_name: args.init_file_name,
95 ..Default::default()
96 }
97 }
98}
99
100#[derive(Clone, Parser)]
101pub struct RunCargoArgs {
102 #[arg(
103 long,
104 short = 'p',
105 value_name = "PACKAGES",
106 help = "The package to run; by default is the package in the current workspace",
107 help_heading = "Package Selection"
108 )]
109 pub package: Option<String>,
110
111 #[arg(
112 long,
113 value_name = "BIN",
114 help = "Run the specified binary",
115 help_heading = "Target Selection"
116 )]
117 pub bin: Vec<String>,
118
119 #[arg(
120 long,
121 value_name = "EXAMPLE",
122 help = "Run the specified example",
123 help_heading = "Target Selection"
124 )]
125 pub example: Vec<String>,
126
127 #[arg(
128 long,
129 short = 'F',
130 value_name = "FEATURES",
131 value_delimiter = ',',
132 help = "Space/comma separated list of features to activate",
133 help_heading = "Feature Selection"
134 )]
135 pub features: Vec<String>,
136
137 #[arg(
138 long,
139 help = "Activate all available features of all selected packages",
140 help_heading = "Feature Selection"
141 )]
142 pub all_features: bool,
143
144 #[arg(
145 long,
146 help = "Do not activate the `default` feature of the selected packages",
147 help_heading = "Feature Selection"
148 )]
149 pub no_default_features: bool,
150
151 #[arg(
152 long,
153 value_name = "NAME",
154 default_value = "release",
155 help = "Run with the given profile",
156 help_heading = "Compilation Options"
157 )]
158 pub profile: String,
159
160 #[arg(
161 long,
162 value_name = "DIR",
163 help = "Directory for all generated artifacts and intermediate files",
164 help_heading = "Output Options"
165 )]
166 pub target_dir: Option<PathBuf>,
167
168 #[arg(
169 long,
170 short = 'v',
171 help = "Use verbose output",
172 help_heading = "Display Options"
173 )]
174 pub verbose: bool,
175
176 #[arg(
177 long,
178 short = 'q',
179 help = "Do not print cargo log messages",
180 help_heading = "Display Options"
181 )]
182 pub quiet: bool,
183
184 #[arg(
185 long,
186 value_name = "WHEN",
187 default_value = "always",
188 help = "Control when colored output is used",
189 help_heading = "Display Options"
190 )]
191 pub color: String,
192
193 #[arg(
194 long,
195 value_name = "PATH",
196 help = "Path to the Cargo.toml file, by default searches for the file in the current or any parent directory",
197 help_heading = "Manifest Options"
198 )]
199 pub manifest_path: Option<PathBuf>,
200
201 #[arg(
202 long,
203 help = "Ignore rust-version specification in packages",
204 help_heading = "Manifest Options"
205 )]
206 pub ignore_rust_version: bool,
207
208 #[arg(
209 long,
210 help = "Asserts same dependencies and versions are used as when the existing Cargo.lock file was originally generated",
211 help_heading = "Manifest Options"
212 )]
213 pub locked: bool,
214
215 #[arg(
216 long,
217 help = "Prevents Cargo from accessing the network for any reason",
218 help_heading = "Manifest Options"
219 )]
220 pub offline: bool,
221
222 #[arg(
223 long,
224 help = "Equivalent to specifying both --locked and --offline",
225 help_heading = "Manifest Options"
226 )]
227 pub frozen: bool,
228}
229
230impl From<RunCargoArgs> for BuildCargoArgs {
231 fn from(args: RunCargoArgs) -> Self {
232 BuildCargoArgs {
233 package: args.package.into_iter().collect(),
234 bin: args.bin,
235 example: args.example,
236 features: args.features,
237 all_features: args.all_features,
238 no_default_features: args.no_default_features,
239 profile: args.profile,
240 target_dir: args.target_dir,
241 verbose: args.verbose,
242 quiet: args.quiet,
243 color: args.color,
244 manifest_path: args.manifest_path,
245 ignore_rust_version: args.ignore_rust_version,
246 locked: args.locked,
247 offline: args.offline,
248 frozen: args.frozen,
249 ..Default::default()
250 }
251 }
252}
253
254impl RunCmd {
255 pub fn run(&self) -> Result<()> {
256 let exe_path = if let Some(exe) = &self.run_args.exe {
257 exe
258 } else {
259 let target_name = get_single_target_name(&self.cargo_args)?;
261 let build_args = self.run_args.clone().into();
262 let cargo_args = self.cargo_args.clone().into();
263 let output_dir = build(&build_args, &cargo_args)?;
264 &output_dir.join(target_name.with_extension("vmexe"))
265 };
266
267 let (manifest_path, manifest_dir) =
268 get_manifest_path_and_dir(&self.cargo_args.manifest_path)?;
269 let config_path = self
270 .run_args
271 .config
272 .to_owned()
273 .unwrap_or_else(|| manifest_dir.join("openvm.toml"));
274 let app_config = read_config_toml_or_default(&config_path)?;
275 let exe: VmExe<F> = read_object_from_file(exe_path)?;
276 let inputs = read_to_stdin(&self.run_args.input)?;
277
278 let sdk = Sdk::new(app_config)?;
280
281 if matches!(
283 self.run_args.mode,
284 ExecutionMode::Segment | ExecutionMode::Meter
285 ) {
286 let target_dir = get_target_dir(&self.cargo_args.target_dir, &manifest_path);
287 let app_pk_path = get_app_pk_path(&target_dir);
288 let app_vk_path = get_app_vk_path(&target_dir);
289
290 if !app_pk_path.exists() {
292 let config_path = self
293 .run_args
294 .config
295 .to_owned()
296 .unwrap_or_else(|| manifest_dir.join("openvm.toml"));
297 keygen(&config_path, &app_pk_path, &app_vk_path, None::<&str>)?;
298 }
299
300 let app_pk: AppProvingKey<SdkVmConfig> = read_object_from_file(&app_pk_path)?;
302 sdk.set_app_pk(app_pk)
303 .map_err(|_| eyre::eyre!("Failed to set app pk"))?;
304 }
305
306 match self.run_args.mode {
307 ExecutionMode::Pure => {
308 let output = sdk.execute(exe, inputs)?;
309 println!("Execution output: {:?}", output);
310 }
311 ExecutionMode::Meter => {
312 let (output, (cost, instret)) = sdk.execute_metered_cost(exe, inputs)?;
313 println!("Execution output: {:?}", output);
314
315 println!("Number of instructions executed: {}", instret);
316 println!("Total cost: {}", cost);
317 }
318 ExecutionMode::Segment => {
319 let (output, segments) = sdk.execute_metered(exe, inputs)?;
320 println!("Execution output: {:?}", output);
321
322 let total_instructions: u64 = segments.iter().map(|s| s.num_insns).sum();
323 println!("Number of instructions executed: {}", total_instructions);
324 println!("Total segments: {}", segments.len());
325 }
326 }
327
328 Ok(())
329 }
330}