diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b08c1bd3e..88222ccef69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## **[Unreleased]** +- [#1357](https://github.com/wasmerio/wasmer/pull/1357) Refactored bin commands into separate files - [#1331](https://github.com/wasmerio/wasmer/pull/1331) Implement the `record` type and instrutions for WIT - [#1345](https://github.com/wasmerio/wasmer/pull/1345) Adding ARM testing in Azure Pipelines - [#1335](https://github.com/wasmerio/wasmer/pull/1335) Change mutability of `memory` to `const` in `wasmer_memory_data_length` in the C API diff --git a/Makefile b/Makefile index 76c90e56a93..70c77e40a01 100644 --- a/Makefile +++ b/Makefile @@ -354,7 +354,7 @@ docs-capi: cd lib/runtime-c-api/ && doxygen doxyfile docs: docs-capi - cargo doc --features=backend-singlepass,backend-cranelift,backend-llvm,docs,wasi,managed --workspace --document-private-items --no-deps + cargo doc --release --features=backend-singlepass,backend-cranelift,backend-llvm,docs,wasi,managed --workspace --document-private-items --no-deps mkdir -p api-docs mkdir -p api-docs/c cp -R target/doc api-docs/crates diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index 7c4b9a3466f..607a7c1e022 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -7,62 +7,15 @@ unused_unsafe, unreachable_patterns )] -extern crate structopt; -#[macro_use] -extern crate log; - -use std::collections::HashMap; use std::env; -use std::fs::{read_to_string, File}; -use std::io; -use std::io::Read; -use std::path::PathBuf; -use std::process::exit; -use std::str::FromStr; +#[cfg(all(target_os = "linux", feature = "loader-kernel"))] +use wasmer_bin::commands::Kernel; +use wasmer_bin::commands::{Cache, Run, SelfUpdate, Validate}; use structopt::{clap, StructOpt}; -use wasmer_bin::*; -#[cfg(feature = "backend-cranelift")] -use wasmer_clif_backend::CraneliftCompiler; -#[cfg(feature = "backend-llvm")] -use wasmer_llvm_backend::{ - InkwellMemoryBuffer, InkwellModule, LLVMBackendConfig, LLVMCallbacks, LLVMCompiler, -}; -use wasmer_runtime::{ - cache::{Cache as BaseCache, FileSystemCache, WasmHash}, - Backend, DynFunc, Value, VERSION, -}; -#[cfg(feature = "managed")] -use wasmer_runtime_core::tiering::{run_tiering, InteractiveShellContext, ShellExitOperation}; -use wasmer_runtime_core::{ - self, - backend::{Compiler, CompilerConfig, Features, MemoryBoundCheckMode}, - loader::{Instance as LoadedInstance, LocalLoader}, - Module, -}; -#[cfg(unix)] -use wasmer_runtime_core::{ - fault::{pop_code_version, push_code_version}, - state::CodeVersion, -}; -#[cfg(feature = "wasi")] -use wasmer_wasi; - -#[cfg(feature = "backend-llvm")] -use std::{cell::RefCell, io::Write, rc::Rc}; -#[cfg(feature = "backend-llvm")] -use wasmer_runtime_core::backend::BackendCompilerConfig; - -#[cfg(not(any( - feature = "backend-cranelift", - feature = "backend-llvm", - feature = "backend-singlepass" -)))] -compile_error!("Please enable one or more of the compiler backends"); - #[derive(Debug, StructOpt)] -#[structopt(name = "wasmer", about = "Wasm execution runtime.", author)] +#[structopt(name = "wasmer", about = "WebAssembly standalone runtime.", author)] /// The options for the wasmer Command Line Interface enum CLIOptions { /// Run a WebAssembly file. Formats accepted: wasm, wat @@ -80,1045 +33,11 @@ enum CLIOptions { /// Update wasmer to the latest version #[structopt(name = "self-update")] SelfUpdate, -} - -#[derive(Debug, StructOpt, Clone)] -struct PrestandardFeatures { - /// Enable support for the SIMD proposal. - #[structopt(long = "enable-simd")] - simd: bool, - - /// Enable support for the threads proposal. - #[structopt(long = "enable-threads")] - threads: bool, - - /// Enable support for all pre-standard proposals. - #[structopt(long = "enable-all")] - all: bool, -} - -impl PrestandardFeatures { - /// Generate [`wabt::Features`] struct from CLI options - #[cfg(feature = "wabt")] - pub fn into_wabt_features(&self) -> wabt::Features { - let mut features = wabt::Features::new(); - if self.simd || self.all { - features.enable_simd(); - } - if self.threads || self.all { - features.enable_threads(); - } - features.enable_sign_extension(); - features.enable_sat_float_to_int(); - features - } - - /// Generate [`Features`] struct from CLI options - pub fn into_backend_features(&self) -> Features { - Features { - simd: self.simd || self.all, - threads: self.threads || self.all, - } - } -} - -#[cfg(feature = "backend-llvm")] -#[derive(Debug, StructOpt, Clone)] -/// LLVM backend flags. -pub struct LLVMCLIOptions { - /// Emit LLVM IR before optimization pipeline. - #[structopt(long = "llvm-pre-opt-ir", parse(from_os_str))] - pre_opt_ir: Option, - - /// Emit LLVM IR after optimization pipeline. - #[structopt(long = "llvm-post-opt-ir", parse(from_os_str))] - post_opt_ir: Option, - - /// Emit LLVM generated native code object file. - #[structopt(long = "llvm-object-file", parse(from_os_str))] - obj_file: Option, -} - -#[derive(Debug, StructOpt, Clone)] -struct Run { - /// Disable the cache - #[structopt(long = "disable-cache")] - disable_cache: bool, - - /// Input file - #[structopt(parse(from_os_str))] - path: PathBuf, - - /// Name of the backend to use (x86_64) - #[cfg(target_arch = "x86_64")] - #[structopt( - long = "backend", - default_value = "auto", - case_insensitive = true, - possible_values = Backend::variants(), - )] - backend: Backend, - - /// Name of the backend to use (aarch64) - #[cfg(target_arch = "aarch64")] - #[structopt( - long = "backend", - default_value = "singlepass", - case_insensitive = true, - possible_values = Backend::variants(), - )] - backend: Backend, - - /// Invoke a specified function - #[structopt(long = "invoke", short = "i")] - invoke: Option, - - /// Emscripten symbol map - #[structopt(long = "em-symbol-map", parse(from_os_str), group = "emscripten")] - em_symbol_map: Option, - - /// Begin execution at the specified symbol - #[structopt(long = "em-entrypoint", group = "emscripten")] - em_entrypoint: Option, - - /// WASI pre-opened directory - #[structopt(long = "dir", multiple = true, group = "wasi")] - pre_opened_directories: Vec, - - /// Map a host directory to a different location for the wasm module - #[structopt(long = "mapdir", multiple = true)] - mapped_dirs: Vec, - - /// Pass custom environment variables - #[structopt(long = "env", multiple = true)] - env_vars: Vec, - - /// Custom code loader - #[structopt( - long = "loader", - case_insensitive = true, - possible_values = LoaderName::variants(), - )] - loader: Option, - - /// Path to previously saved instance image to resume. - #[cfg(feature = "managed")] - #[structopt(long = "resume")] - resume: Option, - - /// Optimized backends for higher tiers. - #[cfg(feature = "managed")] - #[structopt( - long = "optimized-backends", - multiple = true, - case_insensitive = true, - possible_values = Backend::variants(), - )] - optimized_backends: Vec, - - /// Whether or not state tracking should be disabled during compilation. - /// State tracking is necessary for tier switching and backtracing. - #[structopt(long = "track-state")] - track_state: bool, - - // Enable the CallTrace middleware. - #[structopt(long = "call-trace")] - call_trace: bool, - - // Enable the BlockTrace middleware. - #[structopt(long = "block-trace")] - block_trace: bool, - - /// The command name is a string that will override the first argument passed - /// to the wasm program. This is used in wapm to provide nicer output in - /// help commands and error messages of the running wasm program - #[structopt(long = "command-name", hidden = true)] - command_name: Option, - - /// A prehashed string, used to speed up start times by avoiding hashing the - /// wasm module. If the specified hash is not found, Wasmer will hash the module - /// as if no `cache-key` argument was passed. - #[structopt(long = "cache-key", hidden = true)] - cache_key: Option, - - #[cfg(feature = "backend-llvm")] - #[structopt(flatten)] - backend_llvm_options: LLVMCLIOptions, - - #[structopt(flatten)] - features: PrestandardFeatures, - - /// Enable non-standard experimental IO devices - #[cfg(feature = "experimental-io-devices")] - #[structopt(long = "enable-experimental-io-devices")] - enable_experimental_io_devices: bool, - - /// Enable debug output - #[cfg(feature = "debug")] - #[structopt(long = "debug", short = "d")] - debug: bool, - - /// Generate debug information for use in a debugger - #[structopt(long = "generate-debug-info", short = "g")] - generate_debug_info: bool, - - /// Application arguments - #[structopt(name = "--", multiple = true)] - args: Vec, -} - -impl Run { - /// Used with the `invoke` argument - fn parse_args(&self, module: &Module, fn_name: &str) -> Result, String> { - utils::parse_args(module, fn_name, &self.args) - .map_err(|e| format!("Invoke failed: {:?}", e)) - } -} - -#[allow(dead_code)] -#[derive(Debug, Copy, Clone)] -enum LoaderName { - Local, - #[cfg(feature = "loader-kernel")] - Kernel, -} - -impl LoaderName { - pub fn variants() -> &'static [&'static str] { - &[ - "local", - #[cfg(feature = "loader-kernel")] - "kernel", - ] - } -} - -impl FromStr for LoaderName { - type Err = String; - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "local" => Ok(LoaderName::Local), - #[cfg(feature = "loader-kernel")] - "kernel" => Ok(LoaderName::Kernel), - _ => Err(format!("The loader {} doesn't exist", s)), - } - } -} - -#[derive(Debug, StructOpt)] -enum Cache { - /// Clear the cache - #[structopt(name = "clean")] - Clean, - - /// Display the location of the cache - #[structopt(name = "dir")] - Dir, -} - -#[derive(Debug, StructOpt)] -struct Validate { - /// Input file - #[structopt(parse(from_os_str))] - path: PathBuf, - - #[structopt(flatten)] - features: PrestandardFeatures, -} - -/// Read the contents of a file -fn read_file_contents(path: &PathBuf) -> Result, io::Error> { - let mut buffer: Vec = Vec::new(); - let mut file = File::open(path)?; - file.read_to_end(&mut buffer)?; - // We force to close the file - drop(file); - Ok(buffer) -} - -fn get_cache_dir() -> PathBuf { - match env::var("WASMER_CACHE_DIR") { - Ok(dir) => { - let mut path = PathBuf::from(dir); - path.push(VERSION); - path - } - Err(_) => { - // We use a temporal directory for saving cache files - let mut temp_dir = env::temp_dir(); - temp_dir.push("wasmer"); - temp_dir.push(VERSION); - temp_dir - } - } -} - -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 - )); - } - } else { - return Err(format!("Directory \"{}\" does not exist", &real_dir)); - } - md.push((alias.to_string(), pb)); - continue; - } - return Err(format!( - "Directory mappings must consist of two paths separate by a colon. Found {}", - &entry - )); - } - Ok(md) -} - -#[cfg(feature = "wasi")] -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 - )); - } - } - Ok(ev) -} - -/// Helper function for `execute_wasm` (the `Run` command) -#[cfg(feature = "wasi")] -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<(), String> { - 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))?; - - #[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 wasi_state = wasi_state_builder.build().map_err(|e| format!("{:?}", e))?; - - let import_object = wasmer_wasi::generate_import_object_from_state(wasi_state, wasi_version); - - #[allow(unused_mut)] // mut used in feature - let mut instance = module - .instantiate(&import_object) - .map_err(|e| format!("Can't instantiate WASI module: {:?}", e))?; - - let start: wasmer_runtime::Func<(), ()> = instance - .exports - .get("_start") - .map_err(|e| format!("{:?}", e))?; - - #[cfg(feature = "managed")] - { - let start_raw: extern "C" fn(&mut wasmer_runtime_core::vm::Ctx) = - unsafe { ::std::mem::transmute(start.get_vm_func()) }; - - unsafe { - run_tiering( - module.info(), - &_wasm_binary, - if let Some(ref path) = options.resume { - let mut f = File::open(path).unwrap(); - let mut out: Vec = vec![]; - f.read_to_end(&mut out).unwrap(); - Some( - wasmer_runtime_core::state::InstanceImage::from_bytes(&out) - .map_err(|_| format!("failed to decode image"))?, - ) - } else { - None - }, - &import_object, - start_raw, - &mut instance, - options.backend.to_string(), - options - .optimized_backends - .iter() - .map( - |&backend| -> (Backend, Box Box + Send>) { - let options = options.clone(); - ( - backend.to_string(), - Box::new(move || { - get_compiler_by_backend(backend, &options).unwrap() - }), - ) - }, - ) - .collect(), - interactive_shell, - )? - }; - } - - #[cfg(not(feature = "managed"))] - { - let result; - - #[cfg(unix)] - let cv_pushed = if let Some(msm) = instance.module.runnable_module.get_module_state_map() { - push_code_version(CodeVersion { - baseline: true, - msm: msm, - base: instance.module.runnable_module.get_code().unwrap().as_ptr() as usize, - backend: options.backend.to_string(), - runnable_module: instance.module.runnable_module.clone(), - }); - true - } else { - false - }; - - if let Some(invoke_fn) = options.invoke.as_ref() { - eprintln!("WARNING: Invoking aribtrary functions with WASI is not officially supported in the WASI standard yet. Use this feature at your own risk!"); - let args = options.parse_args(&module, invoke_fn)?; - let invoke_result = instance - .exports - .get::(invoke_fn) - .map_err(|e| format!("Invoke failed: {:?}", e))? - .call(&args) - .map_err(|e| format!("Calling invoke fn failed: {:?}", e))?; - println!("{}({:?}) returned {:?}", invoke_fn, args, invoke_result); - return Ok(()); - } else { - result = start.call(); - } - - #[cfg(unix)] - { - if cv_pushed { - pop_code_version().unwrap(); - } - } - if let Err(ref err) = result { - if let Some(error_code) = err.0.downcast_ref::() { - std::process::exit(error_code.code as i32) - } - return Err(format!("error: {:?}", err)); - } - } - Ok(()) -} - -#[cfg(feature = "backend-llvm")] -impl LLVMCallbacks for LLVMCLIOptions { - fn preopt_ir_callback(&mut self, module: &InkwellModule) { - if let Some(filename) = &self.pre_opt_ir { - module.print_to_file(filename).unwrap(); - } - } - - fn postopt_ir_callback(&mut self, module: &InkwellModule) { - if let Some(filename) = &self.post_opt_ir { - module.print_to_file(filename).unwrap(); - } - } - - fn obj_memory_buffer_callback(&mut self, memory_buffer: &InkwellMemoryBuffer) { - if let Some(filename) = &self.obj_file { - let mem_buf_slice = memory_buffer.as_slice(); - let mut file = File::create(filename).unwrap(); - let mut pos = 0; - while pos < mem_buf_slice.len() { - pos += file.write(&mem_buf_slice[pos..]).unwrap(); - } - } - } -} - -/// Execute a wasm/wat file -fn execute_wasm(options: &Run) -> Result<(), String> { - if options.generate_debug_info && options.backend != Backend::Cranelift { - return Err("Generating debug information is currently only available with the `cranelift` backend.".to_owned()); - } - - let disable_cache = options.disable_cache; - - let mapped_dirs = get_mapped_dirs(&options.mapped_dirs[..])?; - #[cfg(feature = "wasi")] - let env_vars = get_env_var_args(&options.env_vars[..])?; - let wasm_path = &options.path; - - #[allow(unused_mut)] - let mut wasm_binary: Vec = read_file_contents(wasm_path).map_err(|err| { - format!( - "Can't read the file {}: {}", - wasm_path.as_os_str().to_string_lossy(), - err - ) - })?; - - let em_symbol_map = if let Some(em_symbol_map_path) = options.em_symbol_map.clone() { - let em_symbol_map_content: String = read_to_string(&em_symbol_map_path) - .map_err(|err| { - format!( - "Can't read symbol map file {}: {}", - em_symbol_map_path.as_os_str().to_string_lossy(), - err, - ) - })? - .to_owned(); - let mut em_symbol_map = HashMap::new(); - for line in em_symbol_map_content.lines() { - let mut split = line.split(':'); - let num_str = if let Some(ns) = split.next() { - ns - } else { - return Err( - "Can't parse symbol map (expected each entry to be of the form: `0:func_name`)" - .to_string(), - ); - }; - let num: u32 = num_str.parse::().map_err(|err| { - format!( - "Failed to parse {} as a number in symbol map: {}", - num_str, err - ) - })?; - let name_str: String = if let Some(name_str) = split.next() { - name_str - } else { - return Err( - "Can't parse symbol map (expected each entry to be of the form: `0:func_name`)" - .to_string(), - ); - } - .to_owned(); - - em_symbol_map.insert(num, name_str); - } - Some(em_symbol_map) - } else { - None - }; - - // Don't error on --enable-all for other backends. - if options.features.simd { - #[cfg(feature = "backend-llvm")] - { - if options.backend != Backend::LLVM { - return Err("SIMD is only supported in the LLVM backend for now".to_string()); - } - } - #[cfg(not(feature = "backend-llvm"))] - return Err("SIMD is not supported in this backend".to_string()); - } - - if !utils::is_wasm_binary(&wasm_binary) { - #[cfg(feature = "wabt")] - { - let features = options.features.into_wabt_features(); - wasm_binary = wabt::wat2wasm_with_features(wasm_binary, features).map_err(|e| { - format!( - "Can't convert from wast to wasm because \"{}\"{}", - e, - match e.kind() { - wabt::ErrorKind::Deserialize(s) - | wabt::ErrorKind::Parse(s) - | wabt::ErrorKind::ResolveNames(s) - | wabt::ErrorKind::Validate(s) => format!(":\n\n{}", s), - wabt::ErrorKind::Nul - | wabt::ErrorKind::WriteText - | wabt::ErrorKind::NonUtf8Result - | wabt::ErrorKind::WriteBinary => "".to_string(), - } - ) - })?; - } - - #[cfg(not(feature = "wabt"))] - { - return Err( - "Input is not a wasm binary and the `wabt` feature is not enabled".to_string(), - ); - } - } - - let compiler: Box = get_compiler_by_backend(options.backend, options) - .ok_or_else(|| { - format!( - "the requested backend, \"{}\", is not enabled", - options.backend.to_string() - ) - })?; - - #[allow(unused_mut)] - let mut backend_specific_config = None; - #[cfg(feature = "backend-llvm")] - { - if options.backend == Backend::LLVM { - backend_specific_config = Some(BackendCompilerConfig(Box::new(LLVMBackendConfig { - callbacks: Some(Rc::new(RefCell::new(options.backend_llvm_options.clone()))), - }))) - } - } - - let track_state = options.track_state; - - #[cfg(feature = "loader-kernel")] - let is_kernel_loader = if let Some(LoaderName::Kernel) = options.loader { - true - } else { - false - }; - - #[cfg(not(feature = "loader-kernel"))] - let is_kernel_loader = false; - - let module = if is_kernel_loader { - webassembly::compile_with_config_with( - &wasm_binary[..], - CompilerConfig { - symbol_map: em_symbol_map.clone(), - memory_bound_check_mode: MemoryBoundCheckMode::Disable, - enforce_stack_check: true, - - // Kernel loader does not support explicit preemption checkpoints. - full_preemption: false, - - track_state, - features: options.features.into_backend_features(), - backend_specific_config, - ..Default::default() - }, - &*compiler, - ) - .map_err(|e| format!("Can't compile module: {:?}", e))? - } else if disable_cache { - webassembly::compile_with_config_with( - &wasm_binary[..], - CompilerConfig { - symbol_map: em_symbol_map.clone(), - track_state, - - // Enable full preemption if state tracking is enabled. - // Preemption only makes sense with state information. - full_preemption: track_state, - - features: options.features.into_backend_features(), - backend_specific_config, - generate_debug_info: options.generate_debug_info, - ..Default::default() - }, - &*compiler, - ) - .map_err(|e| format!("Can't compile module: {:?}", e))? - } else { - // If we have cache enabled - let wasmer_cache_dir = get_cache_dir(); - - // We create a new cache instance. - // It could be possible to use any other kinds of caching, as long as they - // implement the Cache trait (with save and load functions) - let mut cache = unsafe { - FileSystemCache::new(wasmer_cache_dir).map_err(|e| format!("Cache error: {:?}", e))? - }; - let load_cache_key = || -> Result<_, String> { - if let Some(ref prehashed_cache_key) = options.cache_key { - if let Ok(module) = - WasmHash::decode(prehashed_cache_key).and_then(|prehashed_key| { - cache.load_with_backend(prehashed_key, options.backend) - }) - { - debug!("using prehashed key: {}", prehashed_cache_key); - return Ok(module); - } - } - // We generate a hash for the given binary, so we can use it as key - // for the Filesystem cache - let hash = WasmHash::generate(&wasm_binary); - - // cache.load will return the Module if it's able to deserialize it properly, and an error if: - // * The file is not found - // * The file exists, but it's corrupted or can't be converted to a module - match cache.load_with_backend(hash, options.backend) { - Ok(module) => { - // We are able to load the module from cache - Ok(module) - } - Err(_) => { - let module = webassembly::compile_with_config_with( - &wasm_binary[..], - CompilerConfig { - symbol_map: em_symbol_map.clone(), - track_state, - features: options.features.into_backend_features(), - backend_specific_config, - ..Default::default() - }, - &*compiler, - ) - .map_err(|e| format!("Can't compile module: {:?}", e))?; - // We try to save the module into a cache file - cache.store(hash, module.clone()).unwrap_or_default(); - - Ok(module) - } - } - }; - - load_cache_key()? - }; - - if let Some(loader) = options.loader { - let mut import_object = wasmer_runtime_core::import::ImportObject::new(); - import_object.allow_missing_functions = true; // Import initialization might be left to the loader. - let instance = module - .instantiate(&import_object) - .map_err(|e| format!("Can't instantiate loader module: {:?}", e))?; - - let mut args: Vec = Vec::new(); - for arg in options.args.iter() { - let x = arg.as_str().parse().map_err(|_| { - format!( - "Can't parse the provided argument {:?} as a integer", - arg.as_str() - ) - })?; - args.push(Value::I32(x)); - } - - let index = instance.resolve_func("_start").map_err(|_| { - format!("The loader requires a _start function to be present in the module") - })?; - - let mut ins: Box> = match loader { - LoaderName::Local => Box::new( - instance - .load(LocalLoader) - .map_err(|e| format!("Can't use the local loader: {:?}", e))?, - ), - #[cfg(feature = "loader-kernel")] - LoaderName::Kernel => Box::new( - instance - .load(::wasmer_kernel_loader::KernelLoader) - .map_err(|e| format!("Can't use the kernel loader: {:?}", e))?, - ), - }; - println!("{:?}", ins.call(index, &args)); - return Ok(()); - } - - // TODO: refactor this - if wasmer_emscripten::is_emscripten_module(&module) { - let mut emscripten_globals = wasmer_emscripten::EmscriptenGlobals::new(&module)?; - let import_object = wasmer_emscripten::generate_emscripten_env(&mut emscripten_globals); - let mut instance = module - .instantiate(&import_object) - .map_err(|e| format!("Can't instantiate emscripten module: {:?}", e))?; - - wasmer_emscripten::run_emscripten_instance( - &module, - &mut instance, - &mut emscripten_globals, - if let Some(cn) = &options.command_name { - cn - } else { - options.path.to_str().unwrap() - }, - options.args.iter().map(|arg| arg.as_str()).collect(), - options.em_entrypoint.clone(), - mapped_dirs, - ) - .map_err(|e| format!("{:?}", e))?; - } else { - #[cfg(feature = "wasi")] - let wasi_version = wasmer_wasi::get_wasi_version(&module, true); - #[cfg(feature = "wasi")] - let is_wasi = wasi_version.is_some(); - #[cfg(not(feature = "wasi"))] - let is_wasi = false; - - if is_wasi { - #[cfg(feature = "wasi")] - execute_wasi( - wasi_version.unwrap(), - options, - env_vars, - module, - mapped_dirs, - &wasm_binary, - )?; - } else { - let import_object = wasmer_runtime_core::import::ImportObject::new(); - let instance = module - .instantiate(&import_object) - .map_err(|e| format!("Can't instantiate module: {:?}", e))?; - - let invoke_fn = match options.invoke.as_ref() { - Some(fun) => fun, - _ => "main", - }; - let args = options.parse_args(&module, invoke_fn)?; - - #[cfg(unix)] - let cv_pushed = - if let Some(msm) = instance.module.runnable_module.get_module_state_map() { - push_code_version(CodeVersion { - baseline: true, - msm: msm, - base: instance.module.runnable_module.get_code().unwrap().as_ptr() as usize, - backend: options.backend.to_string(), - runnable_module: instance.module.runnable_module.clone(), - }); - true - } else { - false - }; - - let result = instance - .exports - .get::(&invoke_fn) - .map_err(|e| format!("{:?}", e))? - .call(&args) - .map_err(|e| format!("{:?}", e))?; - - #[cfg(unix)] - { - if cv_pushed { - pop_code_version().unwrap(); - } - } - println!("{}({:?}) returned {:?}", invoke_fn, args, result); - } - } - - Ok(()) -} - -#[cfg(feature = "managed")] -fn interactive_shell(mut ctx: InteractiveShellContext) -> ShellExitOperation { - use std::io::Write; - - let mut stdout = ::std::io::stdout(); - let stdin = ::std::io::stdin(); - - loop { - print!("Wasmer> "); - stdout.flush().unwrap(); - let mut line = String::new(); - stdin.read_line(&mut line).unwrap(); - let mut parts = line.split(" ").filter(|x| x.len() > 0).map(|x| x.trim()); - - let cmd = parts.next(); - if cmd.is_none() { - println!("Command required"); - continue; - } - let cmd = cmd.unwrap(); - - match cmd { - "snapshot" => { - let path = parts.next(); - if path.is_none() { - println!("Usage: snapshot [out_path]"); - continue; - } - let path = path.unwrap(); - - if let Some(ref image) = ctx.image { - let buf = image.to_bytes(); - let mut f = match File::create(path) { - Ok(x) => x, - Err(e) => { - println!("Cannot open output file at {}: {:?}", path, e); - continue; - } - }; - if let Err(e) = f.write_all(&buf) { - println!("Cannot write to output file at {}: {:?}", path, e); - continue; - } - println!("Done"); - } else { - println!("Program state not available"); - } - } - "continue" | "c" => { - if let Some(image) = ctx.image.take() { - return ShellExitOperation::ContinueWith(image); - } else { - println!("Program state not available, cannot continue execution"); - } - } - "backtrace" | "bt" => { - if let Some(ref image) = ctx.image { - println!("{}", image.execution_state.output()); - } else { - println!("State not available"); - } - } - "exit" | "quit" => { - exit(0); - } - "" => {} - _ => { - println!("Unknown command: {}", cmd); - } - } - } -} - -#[allow(unused_variables, unreachable_code)] -fn get_backend(backend: Backend, path: &PathBuf) -> Backend { - // Update backend when a backend flag is `auto`. - // Use the Singlepass backend if it's enabled and the file provided is larger - // than 10MiB (10485760 bytes), or it's enabled and the target architecture - // is AArch64. Otherwise, use the Cranelift backend. - match backend { - Backend::Auto => { - #[cfg(feature = "backend-singlepass")] - { - let binary_size = match &path.metadata() { - Ok(wasm_binary) => wasm_binary.len(), - Err(_e) => 0, - }; - if binary_size > 10485760 || cfg!(target_arch = "aarch64") { - return Backend::Singlepass; - } - } - - #[cfg(feature = "backend-cranelift")] - { - return Backend::Cranelift; - } - - #[cfg(feature = "backend-llvm")] - { - return Backend::LLVM; - } - - panic!("Can't find any backend"); - } - backend => backend, - } -} - -fn run(options: &mut Run) { - options.backend = get_backend(options.backend, &options.path); - - #[cfg(any(feature = "debug", feature = "trace"))] - { - if options.debug { - logging::set_up_logging().expect("failed to set up logging"); - } - } - match execute_wasm(options) { - Ok(()) => {} - Err(message) => { - eprintln!("Error: {}", message); - exit(1); - } - } -} - -fn validate_wasm(validate: Validate) -> Result<(), String> { - let wasm_path = validate.path; - let wasm_path_as_str = wasm_path.to_str().unwrap(); - - let wasm_binary: Vec = read_file_contents(&wasm_path).map_err(|err| { - format!( - "Can't read the file {}: {}", - wasm_path.as_os_str().to_string_lossy(), - err - ) - })?; - - if !utils::is_wasm_binary(&wasm_binary) { - return Err(format!( - "Cannot recognize \"{}\" as a WASM binary", - wasm_path_as_str, - )); - } - - wasmer_runtime_core::validate_and_report_errors_with_features( - &wasm_binary, - validate.features.into_backend_features(), - ) - .map_err(|err| format!("Validation failed: {}", err))?; - - Ok(()) -} - -/// Runs logic for the `validate` subcommand -fn validate(validate: Validate) { - match validate_wasm(validate) { - Err(message) => { - eprintln!("Error: {}", message); - exit(-1); - } - _ => (), - } -} - -fn get_compiler_by_backend(backend: Backend, _opts: &Run) -> Option> { - Some(match backend { - #[cfg(feature = "backend-singlepass")] - Backend::Singlepass => { - use wasmer_runtime_core::codegen::MiddlewareChain; - use wasmer_runtime_core::codegen::StreamingCompiler; - use wasmer_singlepass_backend::ModuleCodeGenerator as SinglePassMCG; - - let opts = _opts.clone(); - let middlewares_gen = move || { - let mut middlewares = MiddlewareChain::new(); - if opts.call_trace { - use wasmer_middleware_common::call_trace::CallTrace; - middlewares.push(CallTrace::new()); - } - if opts.block_trace { - use wasmer_middleware_common::block_trace::BlockTrace; - middlewares.push(BlockTrace::new()); - } - middlewares - }; - - let c: StreamingCompiler = - StreamingCompiler::new(middlewares_gen); - Box::new(c) - } - #[cfg(feature = "backend-cranelift")] - Backend::Cranelift => Box::new(CraneliftCompiler::new()), - #[cfg(feature = "backend-llvm")] - Backend::LLVM => Box::new(LLVMCompiler::new()), - _ => return None, - }) + /// The Wasm kernel loader + #[cfg(all(target_os = "linux", feature = "loader-kernel"))] + #[structopt(name = "self-update")] + Kernel(Kernel), } fn main() { @@ -1126,7 +45,8 @@ fn main() { // Eg. `wasmer ` // In case that fails, we fallback trying the Run subcommand directly. // Eg. `wasmer myfile.wasm --dir=.` - let options = CLIOptions::from_iter_safe(env::args()).unwrap_or_else(|e| { + let args = env::args(); + let options = CLIOptions::from_iter_safe(args).unwrap_or_else(|e| { match e.kind { // This fixes a issue that: // 1. Shows the version twice when doing `wasmer -V` @@ -1135,38 +55,13 @@ fn main() { _ => CLIOptions::Run(Run::from_args()), } }); + match options { - CLIOptions::Run(mut options) => run(&mut options), - #[cfg(not(target_os = "windows"))] - CLIOptions::SelfUpdate => update::self_update(), - #[cfg(target_os = "windows")] - CLIOptions::SelfUpdate => { - println!("Self update is not supported on Windows. Use install instructions on the Wasmer homepage: https://wasmer.io"); - } - CLIOptions::Cache(cache) => match cache { - Cache::Clean => { - use std::fs; - let cache_dir = get_cache_dir(); - if cache_dir.exists() { - fs::remove_dir_all(cache_dir.clone()).expect("Can't remove cache dir"); - } - fs::create_dir_all(cache_dir.clone()).expect("Can't create cache dir"); - } - Cache::Dir => { - println!("{}", get_cache_dir().to_string_lossy()); - } - }, - CLIOptions::Validate(validate_options) => { - validate(validate_options); - } + CLIOptions::Run(mut options) => options.execute(), + CLIOptions::SelfUpdate => SelfUpdate::execute(), + CLIOptions::Cache(cache) => cache.execute(), + CLIOptions::Validate(validate) => validate.execute(), + #[cfg(all(target_os = "linux", feature = "loader-kernel"))] + CLIOptions::Kernel(kernel) => kernel.execute(), } } - -#[test] -fn filesystem_cache_should_work() -> Result<(), String> { - let wasmer_cache_dir = get_cache_dir(); - - unsafe { FileSystemCache::new(wasmer_cache_dir).map_err(|e| format!("Cache error: {:?}", e))? }; - - Ok(()) -} diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 00000000000..03de432b3a4 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,10 @@ +mod cache; +#[cfg(all(target_os = "linux", feature = "loader-kernel"))] +mod kernel; +mod run; +mod selfupdate; +mod validate; + +#[cfg(all(target_os = "linux", feature = "loader-kernel"))] +pub use kernel::*; +pub use {cache::*, run::*, selfupdate::*, validate::*}; diff --git a/src/commands/cache.rs b/src/commands/cache.rs new file mode 100644 index 00000000000..84afa3cbdc1 --- /dev/null +++ b/src/commands/cache.rs @@ -0,0 +1,31 @@ +use crate::common::get_cache_dir; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +pub enum Cache { + /// Clear the cache + #[structopt(name = "clean")] + Clean, + + /// Display the location of the cache + #[structopt(name = "dir")] + Dir, +} + +impl Cache { + pub fn execute(&self) { + match &self { + Cache::Clean => { + use std::fs; + let cache_dir = get_cache_dir(); + if cache_dir.exists() { + fs::remove_dir_all(cache_dir.clone()).expect("Can't remove cache dir"); + } + fs::create_dir_all(cache_dir.clone()).expect("Can't create cache dir"); + } + Cache::Dir => { + println!("{}", get_cache_dir().to_string_lossy()); + } + } + } +} diff --git a/src/bin/kwasmd.rs b/src/commands/kernel.rs similarity index 80% rename from src/bin/kwasmd.rs rename to src/commands/kernel.rs index 46492b416f9..04840e7d2df 100644 --- a/src/bin/kwasmd.rs +++ b/src/commands/kernel.rs @@ -1,44 +1,27 @@ -#![deny( - dead_code, - nonstandard_style, - unused_imports, - unused_mut, - unused_variables, - unused_unsafe, - unreachable_patterns -)] -extern crate byteorder; -extern crate structopt; - use structopt::StructOpt; -#[cfg(feature = "loader-kernel")] +use wasmer_runtime_core::loader::Instance; use wasmer_singlepass_backend::SinglePassCompiler; -#[cfg(feature = "loader-kernel")] use std::os::unix::net::{UnixListener, UnixStream}; #[derive(Debug, StructOpt)] -#[structopt(name = "kwasmd", about = "Kernel-mode WebAssembly service.")] -enum CLIOptions { +#[structopt(name = "kernel", about = "Kernel-mode WebAssembly service.")] +pub enum Kernel { #[structopt(name = "listen")] Listen(Listen), } #[derive(Debug, StructOpt)] -struct Listen { +pub struct Listen { #[structopt(long = "socket")] socket: String, } -#[cfg(feature = "loader-kernel")] const CMD_RUN_CODE: u32 = 0x901; -#[cfg(feature = "loader-kernel")] const CMD_READ_MEMORY: u32 = 0x902; -#[cfg(feature = "loader-kernel")] const CMD_WRITE_MEMORY: u32 = 0x903; -#[cfg(feature = "loader-kernel")] fn handle_client(mut stream: UnixStream) { use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use std::io::{Read, Write}; @@ -50,12 +33,8 @@ fn handle_client(mut stream: UnixStream) { let mut wasm_binary: Vec = Vec::with_capacity(binary_size as usize); unsafe { wasm_binary.set_len(binary_size as usize) }; stream.read_exact(&mut wasm_binary).unwrap(); - use wasmer_bin::webassembly; - use wasmer_runtime_core::{ - backend::{CompilerConfig, MemoryBoundCheckMode}, - loader::Instance, - }; - let module = webassembly::compile_with_config_with( + use wasmer_runtime_core::backend::{CompilerConfig, MemoryBoundCheckMode}; + let module = wasmer_runtime::compile_with_config_with( &wasm_binary[..], CompilerConfig { symbol_map: None, @@ -72,7 +51,7 @@ fn handle_client(mut stream: UnixStream) { let mut import_object = wasmer_runtime_core::import::ImportObject::new(); import_object.allow_missing_functions = true; // Import initialization might be left to the loader. let instance = module.instantiate(&import_object).unwrap(); - let mut ins = instance.load(::wasmer_kernel_loader::KernelLoader).unwrap(); + let mut ins = instance.load(wasmer_kernel_loader::KernelLoader).unwrap(); loop { let cmd = stream.read_u32::().unwrap(); @@ -141,8 +120,7 @@ fn handle_client(mut stream: UnixStream) { } } -#[cfg(feature = "loader-kernel")] -fn run_listen(opts: Listen) { +fn run_listen(opts: &Listen) { let listener = UnixListener::bind(&opts.socket).unwrap(); use std::thread; for stream in listener.incoming() { @@ -164,18 +142,12 @@ fn run_listen(opts: Listen) { } } -#[cfg(feature = "loader-kernel")] -fn main() { - panic!("Kwasm not updated for 128-bit support, yet. Sorry!"); - let options = CLIOptions::from_args(); - match options { - CLIOptions::Listen(listen) => { - run_listen(listen); +impl Kernel { + pub fn execute(&self) { + match &self { + Kernel::Listen(listen) => { + run_listen(listen); + } } } } - -#[cfg(not(feature = "loader-kernel"))] -fn main() { - panic!("Kwasm loader is not enabled during compilation."); -} diff --git a/src/commands/run.rs b/src/commands/run.rs new file mode 100644 index 00000000000..cab0d6fa8a2 --- /dev/null +++ b/src/commands/run.rs @@ -0,0 +1,980 @@ +use crate::common::{get_cache_dir, PrestandardFeatures}; +use crate::utils::read_file_contents; +use std::collections::HashMap; +use std::fs::read_to_string; +use std::fs::File; +use std::io::{Read, Write}; +use std::path::PathBuf; +use std::process::exit; +use std::str::FromStr; +#[cfg(feature = "backend-llvm")] +use std::{cell::RefCell, rc::Rc}; + +use structopt::StructOpt; + +use wasmer_runtime::{ + cache::{Cache as BaseCache, FileSystemCache, WasmHash}, + compile_with_config_with, Backend, DynFunc, Value, +}; +use wasmer_runtime_core::{ + self, + backend::{Compiler, CompilerConfig, MemoryBoundCheckMode}, + loader::{Instance as LoadedInstance, LocalLoader}, + Module, +}; + +#[cfg(unix)] +use wasmer_runtime_core::{ + fault::{pop_code_version, push_code_version}, + state::CodeVersion, +}; + +#[cfg(feature = "backend-cranelift")] +use wasmer_clif_backend::CraneliftCompiler; + +#[cfg(feature = "backend-llvm")] +use wasmer_llvm_backend::{ + InkwellMemoryBuffer, InkwellModule, LLVMBackendConfig, LLVMCallbacks, LLVMCompiler, +}; +#[cfg(feature = "backend-llvm")] +use wasmer_runtime_core::backend::BackendCompilerConfig; + +#[cfg(feature = "managed")] +use wasmer_runtime_core::tiering::{run_tiering, InteractiveShellContext, ShellExitOperation}; + +#[cfg(feature = "wasi")] +use wasmer_wasi; + +#[cfg(not(any( + feature = "backend-cranelift", + feature = "backend-llvm", + feature = "backend-singlepass" +)))] +compile_error!("Please enable one or more of the compiler backends"); + +#[cfg(feature = "backend-llvm")] +#[derive(Debug, StructOpt, Clone)] +/// LLVM backend flags. +pub struct LLVMCLIOptions { + /// Emit LLVM IR before optimization pipeline. + #[structopt(long = "llvm-pre-opt-ir", parse(from_os_str))] + pre_opt_ir: Option, + + /// Emit LLVM IR after optimization pipeline. + #[structopt(long = "llvm-post-opt-ir", parse(from_os_str))] + post_opt_ir: Option, + + /// Emit LLVM generated native code object file. + #[structopt(long = "llvm-object-file", parse(from_os_str))] + obj_file: Option, +} + +#[derive(Debug, StructOpt, Clone)] +pub struct Run { + /// Disable the cache + #[structopt(long = "disable-cache")] + disable_cache: bool, + + /// Input file + #[structopt(parse(from_os_str))] + path: PathBuf, + + /// Name of the backend to use (x86_64) + #[cfg(target_arch = "x86_64")] + #[structopt( + long = "backend", + default_value = "auto", + case_insensitive = true, + possible_values = Backend::variants(), + )] + backend: Backend, + + /// Name of the backend to use (aarch64) + #[cfg(target_arch = "aarch64")] + #[structopt( + long = "backend", + default_value = "singlepass", + case_insensitive = true, + possible_values = Backend::variants(), + )] + backend: Backend, + + /// Invoke a specified function + #[structopt(long = "invoke", short = "i")] + invoke: Option, + + /// Emscripten symbol map + #[structopt(long = "em-symbol-map", parse(from_os_str), group = "emscripten")] + em_symbol_map: Option, + + /// Begin execution at the specified symbol + #[structopt(long = "em-entrypoint", group = "emscripten")] + em_entrypoint: Option, + + /// WASI pre-opened directory + #[structopt(long = "dir", multiple = true, group = "wasi")] + pre_opened_directories: Vec, + + /// Map a host directory to a different location for the wasm module + #[structopt(long = "mapdir", multiple = true)] + mapped_dirs: Vec, + + /// Pass custom environment variables + #[structopt(long = "env", multiple = true)] + env_vars: Vec, + + /// Custom code loader + #[structopt( + long = "loader", + case_insensitive = true, + possible_values = LoaderName::variants(), + )] + loader: Option, + + /// Path to previously saved instance image to resume. + #[cfg(feature = "managed")] + #[structopt(long = "resume")] + resume: Option, + + /// Optimized backends for higher tiers. + #[cfg(feature = "managed")] + #[structopt( + long = "optimized-backends", + multiple = true, + case_insensitive = true, + possible_values = Backend::variants(), + )] + optimized_backends: Vec, + + /// Whether or not state tracking should be disabled during compilation. + /// State tracking is necessary for tier switching and backtracing. + #[structopt(long = "track-state")] + track_state: bool, + + // Enable the CallTrace middleware. + #[structopt(long = "call-trace")] + call_trace: bool, + + // Enable the BlockTrace middleware. + #[structopt(long = "block-trace")] + block_trace: bool, + + /// The command name is a string that will override the first argument passed + /// to the wasm program. This is used in wapm to provide nicer output in + /// help commands and error messages of the running wasm program + #[structopt(long = "command-name", hidden = true)] + command_name: Option, + + /// A prehashed string, used to speed up start times by avoiding hashing the + /// wasm module. If the specified hash is not found, Wasmer will hash the module + /// as if no `cache-key` argument was passed. + #[structopt(long = "cache-key", hidden = true)] + cache_key: Option, + + #[cfg(feature = "backend-llvm")] + #[structopt(flatten)] + backend_llvm_options: LLVMCLIOptions, + + #[structopt(flatten)] + features: PrestandardFeatures, + + /// Enable non-standard experimental IO devices + #[cfg(feature = "experimental-io-devices")] + #[structopt(long = "enable-experimental-io-devices")] + enable_experimental_io_devices: bool, + + /// Enable debug output + #[cfg(feature = "debug")] + #[structopt(long = "debug", short = "d")] + debug: bool, + + /// Generate debug information for use in a debugger + #[structopt(long = "generate-debug-info", short = "g")] + generate_debug_info: bool, + + /// Application arguments + #[structopt(name = "--", multiple = true)] + args: Vec, +} + +impl Run { + pub fn execute(&mut self) { + self.backend = get_backend(self.backend, &self.path); + + #[cfg(any(feature = "debug", feature = "trace"))] + { + if self.debug { + logging::set_up_logging().expect("failed to set up logging"); + } + } + match execute_wasm(self) { + Ok(()) => {} + Err(message) => { + eprintln!("Error: {}", message); + exit(1); + } + } + } + + /// Used with the `invoke` argument + pub fn parse_args(&self, module: &Module, fn_name: &str) -> Result, String> { + crate::utils::parse_args(module, fn_name, &self.args) + .map_err(|e| format!("Invoke failed: {:?}", e)) + } +} + +#[allow(dead_code)] +#[derive(Debug, Copy, Clone)] +enum LoaderName { + Local, + #[cfg(feature = "loader-kernel")] + Kernel, +} + +impl LoaderName { + pub fn variants() -> &'static [&'static str] { + &[ + "local", + #[cfg(feature = "loader-kernel")] + "kernel", + ] + } +} + +impl FromStr for LoaderName { + type Err = String; + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "local" => Ok(LoaderName::Local), + #[cfg(feature = "loader-kernel")] + "kernel" => Ok(LoaderName::Kernel), + _ => Err(format!("The loader {} doesn't exist", s)), + } + } +} + +#[derive(Debug, StructOpt)] +enum Cache { + /// Clear the cache + #[structopt(name = "clean")] + Clean, + + /// Display the location of the cache + #[structopt(name = "dir")] + Dir, +} + +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 + )); + } + } else { + return Err(format!("Directory \"{}\" does not exist", &real_dir)); + } + md.push((alias.to_string(), pb)); + continue; + } + return Err(format!( + "Directory mappings must consist of two paths separate by a colon. Found {}", + &entry + )); + } + Ok(md) +} + +#[cfg(feature = "wasi")] +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 + )); + } + } + Ok(ev) +} + +/// Helper function for `execute_wasm` (the `Run` command) +#[cfg(feature = "wasi")] +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<(), String> { + 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))?; + + #[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 wasi_state = wasi_state_builder.build().map_err(|e| format!("{:?}", e))?; + + let import_object = wasmer_wasi::generate_import_object_from_state(wasi_state, wasi_version); + + #[allow(unused_mut)] // mut used in feature + let mut instance = module + .instantiate(&import_object) + .map_err(|e| format!("Can't instantiate WASI module: {:?}", e))?; + + let start: wasmer_runtime::Func<(), ()> = instance + .exports + .get("_start") + .map_err(|e| format!("{:?}", e))?; + + #[cfg(feature = "managed")] + { + let start_raw: extern "C" fn(&mut wasmer_runtime_core::vm::Ctx) = + unsafe { ::std::mem::transmute(start.get_vm_func()) }; + + unsafe { + run_tiering( + module.info(), + &_wasm_binary, + if let Some(ref path) = options.resume { + let mut f = File::open(path).unwrap(); + let mut out: Vec = vec![]; + f.read_to_end(&mut out).unwrap(); + wasmer_runtime_core::state::InstanceImage::from_bytes(&out) + } else { + None + }, + &import_object, + start_raw, + &mut instance, + options.backend.to_string(), + options + .optimized_backends + .iter() + .map( + |&backend| -> (&str, Box Box + Send>) { + let options = options.clone(); + ( + backend.to_string(), + Box::new(move || { + get_compiler_by_backend(backend, &options).unwrap() + }), + ) + }, + ) + .collect(), + interactive_shell, + )? + }; + } + + #[cfg(not(feature = "managed"))] + { + let result; + + #[cfg(unix)] + let cv_pushed = if let Some(msm) = instance.module.runnable_module.get_module_state_map() { + push_code_version(CodeVersion { + baseline: true, + msm: msm, + base: instance.module.runnable_module.get_code().unwrap().as_ptr() as usize, + backend: options.backend.to_string(), + runnable_module: instance.module.runnable_module.clone(), + }); + true + } else { + false + }; + + if let Some(invoke_fn) = options.invoke.as_ref() { + eprintln!("WARNING: Invoking aribtrary functions with WASI is not officially supported in the WASI standard yet. Use this feature at your own risk!"); + let args = options.parse_args(&module, invoke_fn)?; + let invoke_result = instance + .exports + .get::(invoke_fn) + .map_err(|e| format!("Invoke failed: {:?}", e))? + .call(&args) + .map_err(|e| format!("Calling invoke fn failed: {:?}", e))?; + println!("{}({:?}) returned {:?}", invoke_fn, args, invoke_result); + return Ok(()); + } else { + result = start.call(); + } + + #[cfg(unix)] + { + if cv_pushed { + pop_code_version().unwrap(); + } + } + + if let Err(ref err) = result { + if let Some(error_code) = err.0.downcast_ref::() { + std::process::exit(error_code.code as i32) + } + return Err(format!("error: {:?}", err)); + } + } + Ok(()) +} + +#[cfg(feature = "backend-llvm")] +impl LLVMCallbacks for LLVMCLIOptions { + fn preopt_ir_callback(&mut self, module: &InkwellModule) { + if let Some(filename) = &self.pre_opt_ir { + module.print_to_file(filename).unwrap(); + } + } + + fn postopt_ir_callback(&mut self, module: &InkwellModule) { + if let Some(filename) = &self.post_opt_ir { + module.print_to_file(filename).unwrap(); + } + } + + fn obj_memory_buffer_callback(&mut self, memory_buffer: &InkwellMemoryBuffer) { + if let Some(filename) = &self.obj_file { + let mem_buf_slice = memory_buffer.as_slice(); + let mut file = File::create(filename).unwrap(); + let mut pos = 0; + while pos < mem_buf_slice.len() { + pos += file.write(&mem_buf_slice[pos..]).unwrap(); + } + } + } +} + +/// Execute a wasm/wat file +fn execute_wasm(options: &Run) -> Result<(), String> { + if options.generate_debug_info && options.backend != Backend::Cranelift { + return Err("Generating debug information is currently only available with the `cranelift` backend.".to_owned()); + } + + let disable_cache = options.disable_cache; + + let mapped_dirs = get_mapped_dirs(&options.mapped_dirs[..])?; + #[cfg(feature = "wasi")] + let env_vars = get_env_var_args(&options.env_vars[..])?; + let wasm_path = &options.path; + + #[allow(unused_mut)] + let mut wasm_binary: Vec = read_file_contents(wasm_path).map_err(|err| { + format!( + "Can't read the file {}: {}", + wasm_path.as_os_str().to_string_lossy(), + err + ) + })?; + + let em_symbol_map = if let Some(em_symbol_map_path) = options.em_symbol_map.clone() { + let em_symbol_map_content: String = read_to_string(&em_symbol_map_path) + .map_err(|err| { + format!( + "Can't read symbol map file {}: {}", + em_symbol_map_path.as_os_str().to_string_lossy(), + err, + ) + })? + .to_owned(); + let mut em_symbol_map = HashMap::new(); + for line in em_symbol_map_content.lines() { + let mut split = line.split(':'); + let num_str = if let Some(ns) = split.next() { + ns + } else { + return Err( + "Can't parse symbol map (expected each entry to be of the form: `0:func_name`)" + .to_string(), + ); + }; + let num: u32 = num_str.parse::().map_err(|err| { + format!( + "Failed to parse {} as a number in symbol map: {}", + num_str, err + ) + })?; + let name_str: String = if let Some(name_str) = split.next() { + name_str + } else { + return Err( + "Can't parse symbol map (expected each entry to be of the form: `0:func_name`)" + .to_string(), + ); + } + .to_owned(); + + em_symbol_map.insert(num, name_str); + } + Some(em_symbol_map) + } else { + None + }; + + // Don't error on --enable-all for other backends. + if options.features.simd { + #[cfg(feature = "backend-llvm")] + { + if options.backend != Backend::LLVM { + return Err("SIMD is only supported in the LLVM backend for now".to_string()); + } + } + #[cfg(not(feature = "backend-llvm"))] + return Err("SIMD is not supported in this backend".to_string()); + } + + if !crate::utils::is_wasm_binary(&wasm_binary) { + #[cfg(feature = "wabt")] + { + let features = options.features.into_wabt_features(); + wasm_binary = wabt::wat2wasm_with_features(wasm_binary, features).map_err(|e| { + format!( + "Can't convert from wast to wasm because \"{}\"{}", + e, + match e.kind() { + wabt::ErrorKind::Deserialize(s) + | wabt::ErrorKind::Parse(s) + | wabt::ErrorKind::ResolveNames(s) + | wabt::ErrorKind::Validate(s) => format!(":\n\n{}", s), + wabt::ErrorKind::Nul + | wabt::ErrorKind::WriteText + | wabt::ErrorKind::NonUtf8Result + | wabt::ErrorKind::WriteBinary => "".to_string(), + } + ) + })?; + } + + #[cfg(not(feature = "wabt"))] + { + return Err( + "Input is not a wasm binary and the `wabt` feature is not enabled".to_string(), + ); + } + } + + let compiler: Box = get_compiler_by_backend(options.backend, options) + .ok_or_else(|| { + format!( + "the requested backend, \"{}\", is not enabled", + options.backend.to_string() + ) + })?; + + #[allow(unused_mut)] + let mut backend_specific_config = None; + #[cfg(feature = "backend-llvm")] + { + if options.backend == Backend::LLVM { + backend_specific_config = Some(BackendCompilerConfig(Box::new(LLVMBackendConfig { + callbacks: Some(Rc::new(RefCell::new(options.backend_llvm_options.clone()))), + }))) + } + } + + let track_state = options.track_state; + + #[cfg(feature = "loader-kernel")] + let is_kernel_loader = if let Some(LoaderName::Kernel) = options.loader { + true + } else { + false + }; + + #[cfg(not(feature = "loader-kernel"))] + let is_kernel_loader = false; + + let module = if is_kernel_loader { + compile_with_config_with( + &wasm_binary[..], + CompilerConfig { + symbol_map: em_symbol_map.clone(), + memory_bound_check_mode: MemoryBoundCheckMode::Disable, + enforce_stack_check: true, + + // Kernel loader does not support explicit preemption checkpoints. + full_preemption: false, + + track_state, + features: options.features.into_backend_features(), + backend_specific_config, + ..Default::default() + }, + &*compiler, + ) + .map_err(|e| format!("Can't compile module: {:?}", e))? + } else if disable_cache { + compile_with_config_with( + &wasm_binary[..], + CompilerConfig { + symbol_map: em_symbol_map.clone(), + track_state, + + // Enable full preemption if state tracking is enabled. + // Preemption only makes sense with state information. + full_preemption: track_state, + + features: options.features.into_backend_features(), + backend_specific_config, + generate_debug_info: options.generate_debug_info, + ..Default::default() + }, + &*compiler, + ) + .map_err(|e| format!("Can't compile module: {:?}", e))? + } else { + // If we have cache enabled + let wasmer_cache_dir = get_cache_dir(); + + // We create a new cache instance. + // It could be possible to use any other kinds of caching, as long as they + // implement the Cache trait (with save and load functions) + let mut cache = unsafe { + FileSystemCache::new(wasmer_cache_dir).map_err(|e| format!("Cache error: {:?}", e))? + }; + let load_cache_key = || -> Result<_, String> { + if let Some(ref prehashed_cache_key) = options.cache_key { + if let Ok(module) = + WasmHash::decode(prehashed_cache_key).and_then(|prehashed_key| { + cache.load_with_backend(prehashed_key, options.backend) + }) + { + // debug!("using prehashed key: {}", prehashed_cache_key); + return Ok(module); + } + } + // We generate a hash for the given binary, so we can use it as key + // for the Filesystem cache + let hash = WasmHash::generate(&wasm_binary); + + // cache.load will return the Module if it's able to deserialize it properly, and an error if: + // * The file is not found + // * The file exists, but it's corrupted or can't be converted to a module + match cache.load_with_backend(hash, options.backend) { + Ok(module) => { + // We are able to load the module from cache + Ok(module) + } + Err(_) => { + let module = compile_with_config_with( + &wasm_binary[..], + CompilerConfig { + symbol_map: em_symbol_map.clone(), + track_state, + features: options.features.into_backend_features(), + backend_specific_config, + ..Default::default() + }, + &*compiler, + ) + .map_err(|e| format!("Can't compile module: {:?}", e))?; + // We try to save the module into a cache file + cache.store(hash, module.clone()).unwrap_or_default(); + + Ok(module) + } + } + }; + + load_cache_key()? + }; + + if let Some(loader) = options.loader { + let mut import_object = wasmer_runtime_core::import::ImportObject::new(); + import_object.allow_missing_functions = true; // Import initialization might be left to the loader. + let instance = module + .instantiate(&import_object) + .map_err(|e| format!("Can't instantiate loader module: {:?}", e))?; + + let mut args: Vec = Vec::new(); + for arg in options.args.iter() { + let x = arg.as_str().parse().map_err(|_| { + format!( + "Can't parse the provided argument {:?} as a integer", + arg.as_str() + ) + })?; + args.push(Value::I32(x)); + } + + let index = instance.resolve_func("_start").map_err(|_| { + format!("The loader requires a _start function to be present in the module") + })?; + + let mut ins: Box> = match loader { + LoaderName::Local => Box::new( + instance + .load(LocalLoader) + .map_err(|e| format!("Can't use the local loader: {:?}", e))?, + ), + #[cfg(feature = "loader-kernel")] + LoaderName::Kernel => Box::new( + instance + .load(::wasmer_kernel_loader::KernelLoader) + .map_err(|e| format!("Can't use the kernel loader: {:?}", e))?, + ), + }; + println!("{:?}", ins.call(index, &args)); + return Ok(()); + } + + // TODO: refactor this + if wasmer_emscripten::is_emscripten_module(&module) { + let mut emscripten_globals = wasmer_emscripten::EmscriptenGlobals::new(&module)?; + let import_object = wasmer_emscripten::generate_emscripten_env(&mut emscripten_globals); + let mut instance = module + .instantiate(&import_object) + .map_err(|e| format!("Can't instantiate emscripten module: {:?}", e))?; + + wasmer_emscripten::run_emscripten_instance( + &module, + &mut instance, + &mut emscripten_globals, + if let Some(cn) = &options.command_name { + cn + } else { + options.path.to_str().unwrap() + }, + options.args.iter().map(|arg| arg.as_str()).collect(), + options.em_entrypoint.clone(), + mapped_dirs, + ) + .map_err(|e| format!("{:?}", e))?; + } else { + #[cfg(feature = "wasi")] + let wasi_version = wasmer_wasi::get_wasi_version(&module, true); + #[cfg(feature = "wasi")] + let is_wasi = wasi_version.is_some(); + #[cfg(not(feature = "wasi"))] + let is_wasi = false; + + if is_wasi { + #[cfg(feature = "wasi")] + execute_wasi( + wasi_version.unwrap(), + options, + env_vars, + module, + mapped_dirs, + &wasm_binary, + )?; + } else { + let import_object = wasmer_runtime_core::import::ImportObject::new(); + let instance = module + .instantiate(&import_object) + .map_err(|e| format!("Can't instantiate module: {:?}", e))?; + + let invoke_fn = match options.invoke.as_ref() { + Some(fun) => fun, + _ => "main", + }; + let args = options.parse_args(&module, invoke_fn)?; + + #[cfg(unix)] + let cv_pushed = + if let Some(msm) = instance.module.runnable_module.get_module_state_map() { + push_code_version(CodeVersion { + baseline: true, + msm: msm, + base: instance.module.runnable_module.get_code().unwrap().as_ptr() as usize, + backend: options.backend.to_string(), + runnable_module: instance.module.runnable_module.clone(), + }); + true + } else { + false + }; + + let result = instance + .exports + .get::(&invoke_fn) + .map_err(|e| format!("{:?}", e))? + .call(&args) + .map_err(|e| format!("{:?}", e))?; + + #[cfg(unix)] + { + if cv_pushed { + pop_code_version().unwrap(); + } + } + println!("{}({:?}) returned {:?}", invoke_fn, args, result); + } + } + + Ok(()) +} + +#[cfg(feature = "managed")] +fn interactive_shell(mut ctx: InteractiveShellContext) -> ShellExitOperation { + let mut stdout = ::std::io::stdout(); + let stdin = ::std::io::stdin(); + + loop { + print!("Wasmer> "); + stdout.flush().unwrap(); + let mut line = String::new(); + stdin.read_line(&mut line).unwrap(); + let mut parts = line.split(" ").filter(|x| x.len() > 0).map(|x| x.trim()); + + let cmd = parts.next(); + if cmd.is_none() { + println!("Command required"); + continue; + } + let cmd = cmd.unwrap(); + + match cmd { + "snapshot" => { + let path = parts.next(); + if path.is_none() { + println!("Usage: snapshot [out_path]"); + continue; + } + let path = path.unwrap(); + + if let Some(ref image) = ctx.image { + let buf = image.to_bytes(); + let mut f = match File::create(path) { + Ok(x) => x, + Err(e) => { + println!("Cannot open output file at {}: {:?}", path, e); + continue; + } + }; + if let Err(e) = f.write_all(&buf) { + println!("Cannot write to output file at {}: {:?}", path, e); + continue; + } + println!("Done"); + } else { + println!("Program state not available"); + } + } + "continue" | "c" => { + if let Some(image) = ctx.image.take() { + return ShellExitOperation::ContinueWith(image); + } else { + println!("Program state not available, cannot continue execution"); + } + } + "backtrace" | "bt" => { + if let Some(ref image) = ctx.image { + println!("{}", image.execution_state.output()); + } else { + println!("State not available"); + } + } + "exit" | "quit" => { + exit(0); + } + "" => {} + _ => { + println!("Unknown command: {}", cmd); + } + } + } +} + +#[allow(unused_variables, unreachable_code)] +fn get_backend(backend: Backend, path: &PathBuf) -> Backend { + // Update backend when a backend flag is `auto`. + // Use the Singlepass backend if it's enabled and the file provided is larger + // than 10MiB (10485760 bytes), or it's enabled and the target architecture + // is AArch64. Otherwise, use the Cranelift backend. + match backend { + Backend::Auto => { + #[cfg(feature = "backend-singlepass")] + { + let binary_size = match &path.metadata() { + Ok(wasm_binary) => wasm_binary.len(), + Err(_e) => 0, + }; + if binary_size > 10485760 || cfg!(target_arch = "aarch64") { + return Backend::Singlepass; + } + } + + #[cfg(feature = "backend-cranelift")] + { + return Backend::Cranelift; + } + + #[cfg(feature = "backend-llvm")] + { + return Backend::LLVM; + } + + panic!("Can't find any backend"); + } + backend => backend, + } +} + +fn get_compiler_by_backend(backend: Backend, _opts: &Run) -> Option> { + Some(match backend { + #[cfg(feature = "backend-singlepass")] + Backend::Singlepass => { + use wasmer_runtime_core::codegen::MiddlewareChain; + use wasmer_runtime_core::codegen::StreamingCompiler; + use wasmer_singlepass_backend::ModuleCodeGenerator as SinglePassMCG; + + let opts = _opts.clone(); + let middlewares_gen = move || { + let mut middlewares = MiddlewareChain::new(); + if opts.call_trace { + use wasmer_middleware_common::call_trace::CallTrace; + middlewares.push(CallTrace::new()); + } + if opts.block_trace { + use wasmer_middleware_common::block_trace::BlockTrace; + middlewares.push(BlockTrace::new()); + } + middlewares + }; + + let c: StreamingCompiler = + StreamingCompiler::new(middlewares_gen); + Box::new(c) + } + #[cfg(feature = "backend-cranelift")] + Backend::Cranelift => Box::new(CraneliftCompiler::new()), + #[cfg(feature = "backend-llvm")] + Backend::LLVM => Box::new(LLVMCompiler::new()), + _ => return None, + }) +} + +#[test] +fn filesystem_cache_should_work() -> Result<(), String> { + let wasmer_cache_dir = get_cache_dir(); + + unsafe { FileSystemCache::new(wasmer_cache_dir).map_err(|e| format!("Cache error: {:?}", e))? }; + + Ok(()) +} diff --git a/src/commands/selfupdate.rs b/src/commands/selfupdate.rs new file mode 100644 index 00000000000..70587e3bf34 --- /dev/null +++ b/src/commands/selfupdate.rs @@ -0,0 +1,32 @@ +//! When wasmer self-update is executed, this is what gets executed +#[cfg(not(target_os = "windows"))] +use std::process::{Command, Stdio}; + +pub struct SelfUpdate; + +impl SelfUpdate { + #[cfg(not(target_os = "windows"))] + pub fn execute() { + println!("Fetching latest installer"); + let cmd = Command::new("curl") + .arg("https://get.wasmer.io") + .arg("-sSfL") + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + let mut process = Command::new("sh") + .stdin(cmd.stdout.unwrap()) + .stdout(Stdio::inherit()) + .spawn() + .ok() + .expect("Failed to execute."); + + process.wait().unwrap(); + } + + #[cfg(target_os = "windows")] + pub fn execute() { + println!("Self update is not supported on Windows. Use install instructions on the Wasmer homepage: https://wasmer.io"); + } +} diff --git a/src/commands/validate.rs b/src/commands/validate.rs new file mode 100644 index 00000000000..adc0f787efe --- /dev/null +++ b/src/commands/validate.rs @@ -0,0 +1,56 @@ +use crate::common::PrestandardFeatures; +use crate::utils::{is_wasm_binary, read_file_contents}; +use std::path::PathBuf; +use std::process::exit; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +pub struct Validate { + /// Input file + #[structopt(parse(from_os_str))] + path: PathBuf, + + #[structopt(flatten)] + features: PrestandardFeatures, +} + +impl Validate { + /// Runs logic for the `validate` subcommand + pub fn execute(self) { + match validate_wasm(self) { + Err(message) => { + eprintln!("Error: {}", message); + exit(-1); + } + _ => (), + } + } +} + +fn validate_wasm(validate: Validate) -> Result<(), String> { + let wasm_path = validate.path; + let wasm_path_as_str = wasm_path.to_str().unwrap(); + + let wasm_binary: Vec = read_file_contents(&wasm_path).map_err(|err| { + format!( + "Can't read the file {}: {}", + wasm_path.as_os_str().to_string_lossy(), + err + ) + })?; + + if !is_wasm_binary(&wasm_binary) { + return Err(format!( + "Cannot recognize \"{}\" as a WASM binary", + wasm_path_as_str, + )); + } + + wasmer_runtime_core::validate_and_report_errors_with_features( + &wasm_binary, + validate.features.into_backend_features(), + ) + .map_err(|err| format!("Validation failed: {}", err))?; + + Ok(()) +} diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 00000000000..14b865c6f08 --- /dev/null +++ b/src/common.rs @@ -0,0 +1,62 @@ +use std::env; +use std::path::PathBuf; +use structopt::StructOpt; +use wasmer_runtime::{Features, VERSION}; + +#[derive(Debug, StructOpt, Clone)] +pub struct PrestandardFeatures { + /// Enable support for the SIMD proposal. + #[structopt(long = "enable-simd")] + pub simd: bool, + + /// Enable support for the threads proposal. + #[structopt(long = "enable-threads")] + pub threads: bool, + + /// Enable support for all pre-standard proposals. + #[structopt(long = "enable-all")] + pub all: bool, +} + +impl PrestandardFeatures { + /// Generate [`wabt::Features`] struct from CLI options + #[cfg(feature = "wabt")] + pub fn into_wabt_features(&self) -> wabt::Features { + let mut features = wabt::Features::new(); + if self.simd || self.all { + features.enable_simd(); + } + if self.threads || self.all { + features.enable_threads(); + } + features.enable_sign_extension(); + features.enable_sat_float_to_int(); + features + } + + /// Generate [`Features`] struct from CLI options + pub fn into_backend_features(&self) -> Features { + Features { + simd: self.simd || self.all, + threads: self.threads || self.all, + } + } +} + +/// Get the cache dir +pub fn get_cache_dir() -> PathBuf { + match env::var("WASMER_CACHE_DIR") { + Ok(dir) => { + let mut path = PathBuf::from(dir); + path.push(VERSION); + path + } + Err(_) => { + // We use a temporal directory for saving cache files + let mut temp_dir = env::temp_dir(); + temp_dir.push("wasmer"); + temp_dir.push(VERSION); + temp_dir + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 72a758a182c..caf578e6fab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ #![deny( dead_code, nonstandard_style, - unused_imports, unused_mut, unused_variables, unused_unsafe, @@ -10,13 +9,8 @@ #![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] #![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] -extern crate wasmer_runtime_core; -#[macro_use] -extern crate log; - -#[macro_use] -pub mod update; +pub mod commands; +pub mod common; #[cfg(feature = "debug")] pub mod logging; pub mod utils; -pub mod webassembly; diff --git a/src/update.rs b/src/update.rs deleted file mode 100644 index df3bcfe289e..00000000000 --- a/src/update.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! When wasmer self-update is executed, this is what gets executed -use std::process::{Command, Stdio}; - -pub fn self_update() { - println!("Fetching latest installer"); - let cmd = Command::new("curl") - .arg("https://get.wasmer.io") - .arg("-sSfL") - .stdout(Stdio::piped()) - .spawn() - .unwrap(); - - let mut the_process = Command::new("sh") - .stdin(cmd.stdout.unwrap()) - .stdout(Stdio::inherit()) - .spawn() - .ok() - .expect("Failed to execute."); - - the_process.wait().unwrap(); -} diff --git a/src/utils.rs b/src/utils.rs index 20abb91d51a..5b082b3a75b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,9 @@ //! Utility functions for the WebAssembly module +use std::fs::File; +use std::io; +use std::io::Read; +use std::path::PathBuf; use wasmer_runtime::{types::Type, Module, Value}; use wasmer_runtime_core::{backend::SigRegistry, module::ExportIndex}; @@ -120,6 +124,14 @@ pub fn parse_args( } } +/// Read the contents of a file +pub fn read_file_contents(path: &PathBuf) -> Result, io::Error> { + let mut buffer: Vec = Vec::new(); + let mut file = File::open(path)?; + file.read_to_end(&mut buffer)?; + Ok(buffer) +} + /// Whether or not Wasmer should print with color pub fn wasmer_should_print_color() -> bool { std::env::var("WASMER_COLOR") diff --git a/src/webassembly.rs b/src/webassembly.rs deleted file mode 100644 index 0df8b59e8b1..00000000000 --- a/src/webassembly.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::panic; -pub use wasmer_runtime::compile_with_config_with; -use wasmer_runtime::{self as runtime, error::Result, ImportObject, Instance, Module}; - -pub struct ResultObject { - /// A webassembly::Module object representing the compiled WebAssembly module. - /// This Module can be instantiated again - pub module: Module, - /// A webassembly::Instance object that contains all the Exported WebAssembly - /// functions. - pub instance: Box, -} - -#[derive(PartialEq)] -pub enum InstanceABI { - Emscripten, - WASI, - None, -} - -/// The webassembly::instantiate() function allows you to compile and -/// instantiate WebAssembly code -/// Params: -/// * `buffer_source`: A `Vec` containing the -/// binary code of the .wasm module you want to compile. -/// * `import_object`: An object containing the values to be imported -/// into the newly-created Instance, such as functions or -/// webassembly::Memory objects. There must be one matching property -/// for each declared import of the compiled module or else a -/// webassembly::LinkError is thrown. -/// Errors: -/// If the operation fails, the Result rejects with a -/// webassembly::CompileError, webassembly::LinkError, or -/// webassembly::RuntimeError, depending on the cause of the failure. -pub fn instantiate(buffer_source: &[u8], import_object: ImportObject) -> Result { - debug!("webassembly - compiling module"); - let module = compile(&buffer_source[..])?; - - debug!("webassembly - instantiating"); - let instance = module.instantiate(&import_object)?; - - debug!("webassembly - instance created"); - Ok(ResultObject { - module, - instance: Box::new(instance), - }) -} - -/// The webassembly::instantiate_streaming() function compiles and instantiates -/// a WebAssembly module directly from a streamed underlying source. -/// This is the most efficient, optimized way to load wasm code. -pub fn instantiate_streaming( - _buffer_source: Vec, - _import_object: ImportObject, -) -> Result { - unimplemented!(); -} - -/// The webassembly::compile() function compiles a webassembly::Module -/// from WebAssembly binary code. This function is useful if it -/// is necessary to a compile a module before it can be instantiated -/// (otherwise, the webassembly::instantiate() function should be used). -/// Params: -/// * `buffer_source`: A `Vec` containing the -/// binary code of the .wasm module you want to compile. -/// Errors: -/// If the operation fails, the Result rejects with a -/// webassembly::CompileError. -pub fn compile(buffer_source: &[u8]) -> Result { - let module = runtime::compile(buffer_source)?; - Ok(module) -}