use std::{path::PathBuf, time::Instant};
use clap::Parser;
use miden_assembly::diagnostics::{IntoDiagnostic, Report, WrapErr};
use miden_core_lib::CoreLibrary;
use miden_processor::{
DefaultHost, ExecutionOptions, FastProcessor,
trace::{ExecutionTrace, build_trace},
};
use miden_vm::internal::InputFile;
use tracing::instrument;
use super::{
data::{Libraries, OutputFile},
utils::{get_masm_program, get_masp_program},
};
#[derive(Debug, Clone, Parser)]
#[command(about = "Run a Miden program")]
pub struct RunCmd {
#[arg(value_parser)]
program_file: PathBuf,
#[arg(short = 'e', long = "exp-cycles", default_value = "64")]
expected_cycles: u32,
#[arg(short = 'i', long = "input", value_parser)]
input_file: Option<PathBuf>,
#[arg(short = 'l', long = "libraries", value_parser)]
library_paths: Vec<PathBuf>,
#[arg(short = 'm', long = "max-cycles", default_value_t = ExecutionOptions::MAX_CYCLES)]
max_cycles: u32,
#[arg(short = 'n', long = "num-outputs", default_value = "16")]
num_outputs: usize,
#[arg(short = 'o', long = "output", value_parser)]
output_file: Option<PathBuf>,
#[arg(short = 't', long = "trace")]
trace: bool,
#[arg(short = 'r', long = "release")]
release: bool,
#[arg(long = "kernel", value_parser)]
kernel_file: Option<PathBuf>,
}
impl RunCmd {
pub fn execute(&self) -> Result<(), Report> {
println!("===============================================================================");
println!("Run program: {}", self.program_file.display());
println!("-------------------------------------------------------------------------------");
let ext = self
.program_file
.extension()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_lowercase();
let now = Instant::now();
let (trace, program_hash) = match ext.as_str() {
"masp" => run_masp_program(self)?,
"masm" => run_masm_program(self)?,
_ => return Err(Report::msg("The provided file must have a .masm or .masp extension")),
};
println!(
"Executed the program with hash {} in {} ms",
hex::encode(program_hash),
now.elapsed().as_millis()
);
if let Some(output_path) = &self.output_file {
OutputFile::write(trace.stack_outputs(), output_path).map_err(Report::msg)?;
} else {
println!("Output: {:?}", trace.stack_outputs().get_num_elements(self.num_outputs));
}
let padding_percentage = (trace.trace_len_summary().padded_trace_len()
- trace.trace_len_summary().trace_len())
* 100
/ trace.trace_len_summary().padded_trace_len();
println!(
"VM cycles: {} extended to {} steps ({}% padding).
├── Stack rows: {}
├── Range checker rows: {}
└── Chiplets rows: {}
├── Hash chiplet rows: {}
├── Bitwise chiplet rows: {}
├── Memory chiplet rows: {}
└── Kernel ROM rows: {}",
trace.trace_len_summary().trace_len(),
trace.trace_len_summary().padded_trace_len(),
padding_percentage,
trace.trace_len_summary().main_trace_len(),
trace.trace_len_summary().range_trace_len(),
trace.trace_len_summary().chiplets_trace_len().trace_len(),
trace.trace_len_summary().chiplets_trace_len().hash_chiplet_len(),
trace.trace_len_summary().chiplets_trace_len().bitwise_chiplet_len(),
trace.trace_len_summary().chiplets_trace_len().memory_chiplet_len(),
trace.trace_len_summary().chiplets_trace_len().kernel_rom_len(),
);
Ok(())
}
}
#[instrument(name = "run_program", skip_all)]
fn run_masp_program(params: &RunCmd) -> Result<(ExecutionTrace, [u8; 32]), Report> {
let program = get_masp_program(¶ms.program_file)?;
let input_data = InputFile::read(¶ms.input_file, ¶ms.program_file)?;
let stack_inputs = input_data.parse_stack_inputs().map_err(Report::msg)?;
let advice_inputs = input_data.parse_advice_inputs().map_err(Report::msg)?;
let mut host = DefaultHost::default().with_library(&CoreLibrary::default())?;
let program_hash: [u8; 32] = program.hash().into();
let exec_options = ExecutionOptions::new(
Some(params.max_cycles),
params.expected_cycles,
ExecutionOptions::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
params.trace,
!params.release,
)
.map_err(|err| Report::msg(format!("{err}")))?;
let processor = FastProcessor::new(stack_inputs)
.with_advice(advice_inputs)
.with_options(exec_options);
let (execution_output, trace_generation_context) = processor
.execute_for_trace_sync(&program, &mut host)
.wrap_err("Failed to execute program")?;
let trace = build_trace(execution_output, trace_generation_context, program.to_info())
.wrap_err("Failed to build trace")?;
Ok((trace, program_hash))
}
#[instrument(name = "run_program", skip_all)]
fn run_masm_program(params: &RunCmd) -> Result<(ExecutionTrace, [u8; 32]), Report> {
for lib in ¶ms.library_paths {
if !lib.is_file() {
let name = lib.display();
return Err(Report::msg(format!("{name} must be a file.")));
}
}
let libraries = Libraries::new(¶ms.library_paths)?;
if let Some(ref kernel_path) = params.kernel_file
&& !kernel_path.is_file()
{
return Err(Report::msg(format!(
"Kernel file `{}` must be a file.",
kernel_path.display()
)));
}
let (program, source_manager) = get_masm_program(
¶ms.program_file,
&libraries,
!params.release,
params.kernel_file.as_deref(),
)?;
let input_data = InputFile::read(¶ms.input_file, ¶ms.program_file)?;
let stack_inputs = input_data.parse_stack_inputs().map_err(Report::msg)?;
let advice_inputs = input_data.parse_advice_inputs().map_err(Report::msg)?;
let mut host = DefaultHost::default().with_source_manager(source_manager);
host.load_library(&CoreLibrary::default())
.into_diagnostic()
.wrap_err("Failed to load core library")?;
for lib in libraries.libraries {
host.load_library(lib.mast_forest())
.into_diagnostic()
.wrap_err("Failed to load library")?;
}
let program_hash: [u8; 32] = program.hash().into();
let exec_options = ExecutionOptions::new(
Some(params.max_cycles),
params.expected_cycles,
ExecutionOptions::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
params.trace,
!params.release,
)
.map_err(|err| Report::msg(format!("{err}")))?;
let processor = FastProcessor::new(stack_inputs)
.with_advice(advice_inputs)
.with_options(exec_options);
let (execution_output, trace_generation_context) = processor
.execute_for_trace_sync(&program, &mut host)
.wrap_err("Failed to execute program")?;
let trace = build_trace(execution_output, trace_generation_context, program.to_info())
.wrap_err("Failed to build trace")?;
Ok((trace, program_hash))
}