cargo_openvm/commands/
run.rs

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