diff --git a/Cargo.toml b/Cargo.toml index 506fb75d5e1..48d068e7799 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,8 +54,12 @@ wasmer-wast = { path = "tests/lib/wast" } # Don't add the backend features in default, please add them on the Makefile # since we might want to autoconfigure them depending on the availability on the host. # default = ["wasi"] -default = ["wast", "compiler-cranelift"] +default = ["wast", "wasi", "compiler-cranelift"] wast = ["wasmer-wast"] +wasi = ["wasmer-wasi"] +experimental-io-devices = [ + # "wasmer-wasi-experimental-io-devices" +] compiler-singlepass = [ "test-utils/compiler-singlepass", "wasmer/compiler-singlepass", @@ -71,5 +75,3 @@ compiler-llvm = [ "wasmer/compiler-llvm", "wasmer-compiler-llvm", ] -# wasi = ["wasmer-wasi"] -# experimental-io-devices = ["wasmer-wasi-experimental-io-devices"] diff --git a/lib/jit/src/trap/error.rs b/lib/jit/src/trap/error.rs index d6e23e84c1d..bb72c161537 100644 --- a/lib/jit/src/trap/error.rs +++ b/lib/jit/src/trap/error.rs @@ -1,8 +1,9 @@ use super::frame_info::{FrameInfo, GlobalFrameInfo, FRAME_INFO}; use backtrace::Backtrace; +use std::error::Error; use std::fmt; use std::sync::Arc; -use wasmer_runtime::{Trap, TrapCode}; +use wasmer_runtime::{raise_user_trap, Trap, TrapCode}; /// A struct representing an aborted instruction execution, with a message /// indicating the cause. @@ -60,6 +61,11 @@ impl RuntimeError { } } + /// Raises a custom user Error + pub fn raise(error: Box) -> ! { + unsafe { raise_user_trap(error) } + } + fn new_wasm( info: &GlobalFrameInfo, trap_pc: Option, diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 27ece96fb43..4574fa7ac4c 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -12,30 +12,31 @@ //! [WASI plugin example](https://github.com/wasmerio/wasmer/blob/master/examples/plugin.rs) //! for an example of how to extend WASI using the WASI FS API. -#[cfg(target = "windows")] -extern crate winapi; #[macro_use] extern crate log; #[macro_use] mod macros; mod ptr; -pub mod state; +mod state; mod syscalls; mod utils; -use self::state::{WasiFs, WasiState}; +pub use self::state::WasiState; pub use self::syscalls::types; use self::syscalls::*; pub use self::utils::{get_wasi_version, is_wasi_module, WasiVersion}; +use thiserror::Error; use wasmer::{imports, Func, ImportObject, Memory, Store}; /// This is returned in `RuntimeError`. /// Use `downcast` or `downcast_ref` to retrieve the `ExitCode`. -pub struct ExitCode { - pub code: syscalls::types::__wasi_exitcode_t, +#[derive(Error, Debug)] +pub enum WasiError { + #[error("WASI exited with code: {0}")] + Exit(syscalls::types::__wasi_exitcode_t), } /// The environment provided to the WASI imports. @@ -70,7 +71,7 @@ impl<'a> WasiEnv<'a> { /// Get a reference to the memory pub fn memory(&self) -> &Memory { - self.memory.as_ref().unwrap() + self.memory.as_ref().expect("The expected Memory is not attached to the `WasiEnv`. Did you forgot to call wasi_env.set_memory(...)?") } pub(crate) fn get_memory_and_wasi_state( @@ -102,6 +103,7 @@ pub fn generate_import_object_from_env( fn generate_import_object_snapshot0(store: &Store, env: &mut WasiEnv) -> ImportObject { imports! { "wasi_unstable" => { + "args_get" => Func::new_env(store, env, args_get), "args_sizes_get" => Func::new_env(store, env, args_sizes_get), "clock_res_get" => Func::new_env(store, env, clock_res_get), "clock_time_get" => Func::new_env(store, env, clock_time_get), diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 6aa1059a101..fd2559ba84f 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -20,13 +20,13 @@ use crate::{ InodeVal, Kind, PollEvent, PollEventBuilder, WasiFile, WasiFsError, WasiState, MAX_SYMLINKS, }, - ExitCode, WasiEnv, + WasiEnv, WasiError, }; use std::borrow::Borrow; use std::cell::Cell; use std::convert::{Infallible, TryInto}; use std::io::{self, Read, Seek, Write}; -use wasmer::Memory; +use wasmer::{Memory, RuntimeError}; #[cfg(any( target_os = "freebsd", @@ -2498,8 +2498,8 @@ pub fn poll_oneoff( pub fn proc_exit(env: &mut WasiEnv, code: __wasi_exitcode_t) { debug!("wasi::proc_exit, {}", code); - panic!("Proc exit"); - // ExitCode { code } + RuntimeError::raise(Box::new(WasiError::Exit(code))); + unreachable!(); } pub fn proc_raise(env: &mut WasiEnv, sig: __wasi_signal_t) -> __wasi_errno_t { diff --git a/src/commands/run/llvm.rs b/src/backends/llvm.rs similarity index 100% rename from src/commands/run/llvm.rs rename to src/backends/llvm.rs diff --git a/src/commands/run.rs b/src/commands/run.rs index a36deeb8e6c..8aeaba6e7df 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -6,6 +6,12 @@ use wasmer::*; use structopt::StructOpt; +#[cfg(feature = "wasi")] +mod wasi; + +#[cfg(feature = "wasi")] +use wasi::Wasi; + #[derive(Debug, StructOpt, Clone)] /// The options for the `wasmer run` subcommand pub struct Run { @@ -36,11 +42,10 @@ pub struct Run { #[structopt(flatten)] compiler: CompilerOptions, - // #[structopt(flatten)] - // llvm: LLVMCLIOptions, + #[cfg(feature = "wasi")] + #[structopt(flatten)] + wasi: Wasi, - // #[structopt(flatten)] - // wasi: WasiOptions, /// Enable non-standard experimental IO devices #[cfg(feature = "io-devices")] #[structopt(long = "enable-io-devices")] @@ -63,10 +68,10 @@ impl Run { let engine = Engine::new(&*compiler_config); let store = Store::new(&engine); let module = Module::from_file(&store, &self.path)?; - let imports = imports! {}; - let instance = Instance::new(&module, &imports)?; if let Some(ref invoke) = self.invoke { + let imports = imports! {}; + let instance = Instance::new(&module, &imports)?; let result = self .invoke_function(&instance, &invoke, &self.args) .with_context(|| format!("Failed to invoke `{}`", invoke))?; @@ -80,9 +85,26 @@ impl Run { ); return Ok(()); } + #[cfg(feature = "wasi")] + { + let wasi_version = Wasi::get_version(&module); + if let Some(version) = wasi_version { + let program_name = self + .command_name + .clone() + .or_else(|| { + self.path + .file_name() + .map(|f| f.to_string_lossy().to_string()) + }) + .unwrap_or("".to_string()); + self.wasi + .execute(module, version, program_name, self.args.clone())?; + } + } - let start: &Func = instance.exports.get("_start")?; - start.call(&[])?; + // let start: &Func = instance.exports.get("_start")?; + // start.call(&[])?; Ok(()) } diff --git a/src/commands/run/wasi.rs b/src/commands/run/wasi.rs index 0d57e72842e..07a01bc8f31 100644 --- a/src/commands/run/wasi.rs +++ b/src/commands/run/wasi.rs @@ -1,9 +1,16 @@ -use wasmer_wasi; +use anyhow::{bail, Result}; +use std::path::PathBuf; +use wasmer::{Func, Instance, Memory, Module}; +use wasmer_wasi::{ + generate_import_object_from_env, get_wasi_version, WasiEnv, WasiState, WasiVersion, +}; + +use structopt::StructOpt; #[derive(Debug, StructOpt, Clone)] /// WASI Options -pub struct WasiOptions { +pub struct Wasi { /// WASI pre-opened directory #[structopt(long = "dir", multiple = true, group = "wasi")] pre_opened_directories: Vec, @@ -15,91 +22,103 @@ pub struct WasiOptions { /// Pass custom environment variables #[structopt(long = "env", multiple = true)] env_vars: Vec, -} + /// Enable experimental IO devices + #[cfg(feature = "experimental-io-devices")] + #[structopt(long = "enable-experimental-io-devices")] + enable_experimental_io_devices: bool, +} -fn get_mapped_dirs(input: &[String]) -> Result, String> { - let mut md = vec![]; - for entry in input.iter() { - if let [alias, real_dir] = entry.split(':').collect::>()[..] { - let pb = PathBuf::from(&real_dir); - if let Ok(pb_metadata) = pb.metadata() { - if !pb_metadata.is_dir() { - return Err(format!( - "\"{}\" exists, but it is not a directory", - &real_dir - )); +impl Wasi { + fn get_mapped_dirs(&self) -> Result> { + let mut md = vec![]; + for entry in self.mapped_dirs.iter() { + if let [alias, real_dir] = entry.split(':').collect::>()[..] { + let pb = PathBuf::from(&real_dir); + if let Ok(pb_metadata) = pb.metadata() { + if !pb_metadata.is_dir() { + bail!("\"{}\" exists, but it is not a directory", &real_dir); + } + } else { + bail!("Directory \"{}\" does not exist", &real_dir); } - } else { - return Err(format!("Directory \"{}\" does not exist", &real_dir)); + md.push((alias.to_string(), pb)); + continue; } - md.push((alias.to_string(), pb)); - continue; + bail!( + "Directory mappings must consist of two paths separate by a colon. Found {}", + &entry + ); } - return Err(format!( - "Directory mappings must consist of two paths separate by a colon. Found {}", - &entry - )); + Ok(md) } - Ok(md) -} -fn get_env_var_args(input: &[String]) -> Result, String> { - let mut ev = vec![]; - for entry in input.iter() { - if let [env_var, value] = entry.split('=').collect::>()[..] { - ev.push((env_var, value)); - } else { - return Err(format!( - "Env vars must be of the form =. Found {}", - &entry - )); + fn get_env_vars(&self) -> Result> { + let mut env = vec![]; + for entry in self.env_vars.iter() { + if let [env_var, value] = entry.split('=').collect::>()[..] { + env.push((env_var, value)); + } else { + bail!( + "Env vars must be of the form =. Found {}", + &entry + ); + } } + Ok(env) + } + + /// Gets the WASI version (if any) for the provided module + pub fn get_version(module: &Module) -> Option { + get_wasi_version(&module, true) } - Ok(ev) -} -/// Helper function for `execute_wasm` (the `Run` command) -fn execute_wasi( - wasi_version: wasmer_wasi::WasiVersion, - options: &Run, - env_vars: Vec<(&str, &str)>, - module: wasmer_runtime_core::Module, - mapped_dirs: Vec<(String, PathBuf)>, - _wasm_binary: &[u8], -) -> Result<()> { - let name = if let Some(cn) = &options.command_name { - cn.clone() - } else { - options.path.to_str().unwrap().to_owned() - }; - - let args = options.args.iter().cloned().map(|arg| arg.into_bytes()); - let preopened_files = options.pre_opened_directories.clone(); - let mut wasi_state_builder = wasmer_wasi::state::WasiState::new(&name); - wasi_state_builder - .args(args) - .envs(env_vars) - .preopen_dirs(preopened_files) - .map_err(|e| format!("Failed to preopen directories: {:?}", e))? - .map_dirs(mapped_dirs) - .map_err(|e| format!("Failed to preopen mapped directories: {:?}", e))?; + /// Helper function for executing Wasi from the `Run` command. + pub fn execute( + &self, + module: Module, + wasi_version: WasiVersion, + program_name: String, + args: Vec, + ) -> Result<()> { + // Get the wasi version on strict mode, so no other imports are + // allowed. + // let wasi_version: WasiVersion = get_wasi_version(&module, true); + let mut wasi_state_builder = WasiState::new(&program_name); - #[cfg(feature = "experimental-io-devices")] - { - if options.enable_experimental_io_devices { - wasi_state_builder.setup_fs(Box::new(wasmer_wasi_experimental_io_devices::initialize)); + let args = args.iter().cloned().map(|arg| arg.into_bytes()); + let env_vars = self.get_env_vars()?; + let preopened_files = self.pre_opened_directories.clone(); + let mapped_dirs = self.get_mapped_dirs()?; + + wasi_state_builder + .args(args) + .envs(env_vars) + .preopen_dirs(preopened_files)? + .map_dirs(mapped_dirs)?; + + #[cfg(feature = "experimental-io-devices")] + { + if self.enable_experimental_io_devices { + wasi_state_builder + .setup_fs(Box::new(wasmer_wasi_experimental_io_devices::initialize)); + } } + + let wasi_state = wasi_state_builder.build()?; + let mut wasi_env = WasiEnv::new(wasi_state); + let import_object = + generate_import_object_from_env(module.store(), &mut wasi_env, wasi_version); + + let instance = Instance::new(&module, &import_object)?; + + let memory: &Memory = instance.exports.get("memory")?; + wasi_env.set_memory(memory); + + let start: &Func = instance.exports.get("_start")?; + + start.call(&[])?; + + Ok(()) } - let wasi_state = wasi_state_builder.build()?; - let import_object = wasmer_wasi::generate_import_object_from_state(wasi_state, wasi_version); - - let mut instance = module - .instantiate(&import_object) - .map_err(|e| format!("Can't instantiate WASI module: {:?}", e))?; - - let start: &Func = instance - .exports - .get("_start")?; - Ok(()) -} \ No newline at end of file +} diff --git a/src/compiler.rs b/src/compiler.rs index 8d9ff4fa665..d92556526fe 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -23,6 +23,8 @@ pub struct CompilerOptions { #[structopt(flatten)] features: WasmFeatures, + // #[structopt(flatten)] + // llvm_options: LLVMCLIOptions, } #[derive(Debug)]