use crate::{
args::Args,
display::{DisplayExportedFuncs, DisplayFuncType, DisplaySequence, DisplayValue},
};
use anyhow::{anyhow, bail, Error, Result};
use clap::Parser;
use context::Context;
use std::{path::Path, process};
use wasmi::{Func, FuncType, Val};
mod args;
mod context;
mod display;
mod utils;
#[cfg(test)]
mod tests;
fn main() -> Result<()> {
let args = Args::parse();
let wasm_file = args.wasm_file();
let wasi_ctx = args.wasi_context()?;
let mut ctx = Context::new(wasm_file, wasi_ctx, args.fuel(), args.compilation_mode())?;
let (func_name, func) = get_invoked_func(&args, &ctx)?;
let ty = func.ty(ctx.store());
let func_args = utils::decode_func_args(&ty, args.func_args())?;
let mut func_results = utils::prepare_func_results(&ty);
typecheck_args(&func_name, &ty, &func_args)?;
if args.verbose() {
print_execution_start(args.wasm_file(), &func_name, &func_args);
}
if args.invoked().is_some() && ty.params().len() != args.func_args().len() {
bail!(
"invalid amount of arguments given to function {}. expected {} but received {}",
DisplayFuncType::new(&func_name, &ty),
ty.params().len(),
args.func_args().len()
)
}
match func.call(ctx.store_mut(), &func_args, &mut func_results) {
Ok(()) => {
print_remaining_fuel(&args, &ctx);
print_pretty_results(&func_results);
Ok(())
}
Err(error) => {
if let Some(exit_code) = error.i32_exit_status() {
print_remaining_fuel(&args, &ctx);
print_pretty_results(&func_results);
process::exit(exit_code)
}
bail!("failed during execution of {func_name}: {error}")
}
}
}
fn print_remaining_fuel(args: &Args, ctx: &Context) {
if let Some(given_fuel) = args.fuel() {
let remaining = ctx
.store()
.get_fuel()
.unwrap_or_else(|error| panic!("could not get the remaining fuel: {error}"));
let consumed = given_fuel.saturating_sub(remaining);
println!("fuel consumed: {consumed}, fuel remaining: {remaining}");
}
}
fn typecheck_args(func_name: &str, func_ty: &FuncType, args: &[Val]) -> Result<(), Error> {
if func_ty.params().len() != args.len() {
bail!(
"invalid amount of arguments given to function {}. expected {} but received {}",
DisplayFuncType::new(func_name, func_ty),
func_ty.params().len(),
args.len()
)
}
Ok(())
}
fn get_invoked_func(args: &Args, ctx: &Context) -> Result<(String, Func), Error> {
match args.invoked() {
Some(func_name) => {
let func = ctx
.get_func(func_name)
.map_err(|error| anyhow!("{error}\n\n{}", DisplayExportedFuncs::from(ctx)))?;
let func_name = func_name.into();
Ok((func_name, func))
}
None => {
if let Ok(func) = ctx.get_func("") {
Ok(("".into(), func))
} else if let Ok(func) = ctx.get_func("_start") {
Ok(("_start".into(), func))
} else {
bail!(
"did not specify `--invoke` and could not find exported WASI entry point functions\n\n{}",
DisplayExportedFuncs::from(ctx)
)
}
}
}
}
fn print_execution_start(wasm_file: &Path, func_name: &str, func_args: &[Val]) {
println!(
"executing File({wasm_file:?})::{func_name}({}) ...",
DisplaySequence::new(", ", func_args.iter().map(DisplayValue::from))
);
}
fn print_pretty_results(results: &[Val]) {
match results.len() {
0 => {}
1 => {
println!("{}", DisplayValue::from(&results[0]));
}
_ => {
println!(
"[{}]",
DisplaySequence::new(", ", results.iter().map(DisplayValue::from))
);
}
}
}