diff --git a/Makefile b/Makefile index 905f924a6..33403699d 100644 --- a/Makefile +++ b/Makefile @@ -130,6 +130,15 @@ test-capi: test-capi-singlepass test-capi-cranelift test-capi-llvm test-capi-ems # Packaging # ############# +package-wapm: + mkdir -p "package/bin" +ifeq ($(OS), Windows_NT) + echo "" +else + echo "#!/bin/bash\nwapm execute \"\$$@\"" > package/bin/wax + chmod +x package/bin/wax +endif + package-wasmer: mkdir -p "package/bin" ifeq ($(OS), Windows_NT) @@ -171,7 +180,7 @@ package-docs: build-docs build-docs-capi echo '' > package/docs/index.html echo '' > package/docs/crates/index.html -package: package-wasmer package-capi +package: package-wapm package-wasmer package-capi cp LICENSE package/LICENSE cp ATTRIBUTIONS.md package/ATTRIBUTIONS mkdir -p dist diff --git a/README.md b/README.md index 360c6e490..1a04a8440 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ Build Status - - Slack channel - License + + Slack channel +

@@ -21,18 +21,14 @@ Docs - Blog - - Slack - - Twitter + Chat


-[Wasmer](https://wasmer.io/) is a standalone [WebAssembly](https://webassembly.org/) runtime: +[Wasmer](https://wasmer.io/) is the fastest and most popular [WebAssembly](https://webassembly.org/) runtime: * **Universal**: Wasmer is available in *Linux, macOS and Windows* (for both Desktop and [ARM](https://medium.com/wasmer/running-webassembly-on-arm-7d365ed0e50c)) * **Fast**: Wasmer aims to run WebAssembly at near-native speed * **Pluggable**: Wasmer can be used from almost **any programming language** diff --git a/lib/api/src/import_object.rs b/lib/api/src/import_object.rs index 8da2bf766..bdb9fd848 100644 --- a/lib/api/src/import_object.rs +++ b/lib/api/src/import_object.rs @@ -184,8 +184,8 @@ impl IntoIterator for ImportObject { /// /// [`ImportObject`]: struct.ImportObject.html /// +/// # Usage /// -/// # Usage: /// ``` /// # use wasmer::{Function, Store}; /// # let store = Store::default(); @@ -194,7 +194,7 @@ impl IntoIterator for ImportObject { /// let import_object = imports! { /// "env" => { /// "foo" => Function::new(&store, foo) -/// } +/// }, /// }; /// /// fn foo(n: i32) -> i32 { @@ -202,43 +202,45 @@ impl IntoIterator for ImportObject { /// } /// ``` #[macro_export] -// TOOD: port of lost fixes of imports macro from wasmer master/imports macro tests macro_rules! imports { - ( $( $ns_name:expr => $ns:tt ),* $(,)? ) => {{ - use $crate::ImportObject; + ( $( $ns_name:expr => $ns:tt ),* $(,)? ) => { + { + let mut import_object = $crate::ImportObject::new(); - let mut import_object = ImportObject::new(); + $({ + let namespace = $crate::import_namespace!($ns); - $({ - let ns = $crate::import_namespace!($ns); + import_object.register($ns_name, namespace); + })* - import_object.register($ns_name, ns); - })* - - import_object - }}; + import_object + } + }; } #[macro_export] #[doc(hidden)] macro_rules! namespace { - ($( $imp_name:expr => $import_item:expr ),* $(,)? ) => { - $crate::import_namespace!({ $( $imp_name => $import_item, )* }) + ($( $import_name:expr => $import_item:expr ),* $(,)? ) => { + $crate::import_namespace!( { $( $import_name => $import_item, )* } ) }; } #[macro_export] #[doc(hidden)] macro_rules! import_namespace { - ( { $( $imp_name:expr => $import_item:expr ),* $(,)? } ) => {{ - let mut ns = $crate::Exports::new(); + ( { $( $import_name:expr => $import_item:expr ),* $(,)? } ) => {{ + let mut namespace = $crate::Exports::new(); + $( - ns.insert($imp_name, $import_item); + namespace.insert($import_name, $import_item); )* - ns + + namespace }}; - ($ns:ident) => { - $ns + + ( $namespace:ident ) => { + $namespace }; } @@ -354,4 +356,56 @@ mod test { false }); } + + #[test] + fn imports_macro_allows_trailing_comma_and_none() { + use crate::Function; + + let store = Default::default(); + + fn func(arg: i32) -> i32 { + arg + 1 + } + + let _ = imports! { + "env" => { + "func" => Function::new(&store, func), + }, + }; + let _ = imports! { + "env" => { + "func" => Function::new(&store, func), + } + }; + let _ = imports! { + "env" => { + "func" => Function::new(&store, func), + }, + "abc" => { + "def" => Function::new(&store, func), + } + }; + let _ = imports! { + "env" => { + "func" => Function::new(&store, func) + }, + }; + let _ = imports! { + "env" => { + "func" => Function::new(&store, func) + } + }; + let _ = imports! { + "env" => { + "func1" => Function::new(&store, func), + "func2" => Function::new(&store, func) + } + }; + let _ = imports! { + "env" => { + "func1" => Function::new(&store, func), + "func2" => Function::new(&store, func), + } + }; + } } diff --git a/lib/compiler-llvm/src/config.rs b/lib/compiler-llvm/src/config.rs index 8d03c44e3..2d147841a 100644 --- a/lib/compiler-llvm/src/config.rs +++ b/lib/compiler-llvm/src/config.rs @@ -69,26 +69,6 @@ impl LLVMConfig { /// Creates a new configuration object with the default configuration /// specified. pub fn new(features: Features, target: Target) -> Self { - let operating_system = - if target.triple().operating_system == wasmer_compiler::OperatingSystem::Darwin { - // LLVM detects static relocation + darwin + 64-bit and - // force-enables PIC because MachO doesn't support that - // combination. They don't check whether they're targeting - // MachO, they check whether the OS is set to Darwin. - // - // Since both linux and darwin use SysV ABI, this should work. - wasmer_compiler::OperatingSystem::Linux - } else { - target.triple().operating_system - }; - let triple = Triple { - architecture: target.triple().architecture, - vendor: target.triple().vendor.clone(), - operating_system, - environment: target.triple().environment, - binary_format: target_lexicon::BinaryFormat::Elf, - }; - let target = Target::new(triple, *target.cpu_features()); Self { enable_nan_canonicalization: true, enable_verifier: false, @@ -112,7 +92,27 @@ impl LLVMConfig { } pub fn target_triple(&self) -> TargetTriple { - TargetTriple::create(&self.target().triple().to_string()) + let target = self.target(); + let operating_system = + if target.triple().operating_system == wasmer_compiler::OperatingSystem::Darwin { + // LLVM detects static relocation + darwin + 64-bit and + // force-enables PIC because MachO doesn't support that + // combination. They don't check whether they're targeting + // MachO, they check whether the OS is set to Darwin. + // + // Since both linux and darwin use SysV ABI, this should work. + wasmer_compiler::OperatingSystem::Linux + } else { + target.triple().operating_system + }; + let triple = Triple { + architecture: target.triple().architecture, + vendor: target.triple().vendor.clone(), + operating_system, + environment: target.triple().environment, + binary_format: target_lexicon::BinaryFormat::Elf, + }; + TargetTriple::create(&triple.to_string()) } /// Generates the target machine for the current target @@ -149,10 +149,11 @@ impl LLVMConfig { .map(|feature| format!("+{}", feature.to_string())) .join(","); - let llvm_target = InkwellTarget::from_triple(&self.target_triple()).unwrap(); + let target_triple = self.target_triple(); + let llvm_target = InkwellTarget::from_triple(&target_triple).unwrap(); llvm_target .create_target_machine( - &self.target_triple(), + &target_triple, "generic", &llvm_cpu_features, self.opt_level, diff --git a/lib/engine-native/src/artifact.rs b/lib/engine-native/src/artifact.rs index c4d3c2666..cb82d8669 100644 --- a/lib/engine-native/src/artifact.rs +++ b/lib/engine-native/src/artifact.rs @@ -294,38 +294,38 @@ impl NativeArtifact { let shared_file = NamedTempFile::new().map_err(to_compile_error)?; let (_file, shared_filepath) = shared_file.keep().map_err(to_compile_error)?; - let wasmer_symbols = libcalls - .iter() - .map(|libcall| { - match target_triple.binary_format { - BinaryFormat::Macho => format!("-Wl,-U,_{}", libcall), - BinaryFormat::Elf => format!("-Wl,--undefined={}", libcall), - _ => { - // We should already be filtering only valid binary formats before - // so this should never happen. - unreachable!("Incorrect binary format") - } - } - }) - .collect::>(); - let is_cross_compiling = target_triple != Triple::host(); + let host_target = Triple::host(); + let is_cross_compiling = target_triple != host_target; let cross_compiling_args = if is_cross_compiling { vec![ format!("--target={}", target_triple), "-fuse-ld=lld".to_string(), + "-nodefaultlibs".to_string(), + "-nostdlib".to_string(), ] } else { vec![] }; - let output = Command::new("gcc") + trace!( + "Compiling for target {} from host {}", + target_triple.to_string(), + host_target.to_string() + ); + + let linker = if is_cross_compiling { + "clang-10" + } else { + "gcc" + }; + + let output = Command::new(linker) .arg(&filepath) .arg("-nostartfiles") - .arg("-nodefaultlibs") - .arg("-nostdlib") .arg("-o") .arg(&shared_filepath) - .args(&wasmer_symbols) + .arg("-Wl,-undefined,dynamic_lookup") + // .args(&wasmer_symbols) .arg("-shared") .args(&cross_compiling_args) .arg("-v") diff --git a/lib/runtime/src/libcalls.rs b/lib/runtime/src/libcalls.rs index 4a5122c44..09ab8167b 100644 --- a/lib/runtime/src/libcalls.rs +++ b/lib/runtime/src/libcalls.rs @@ -405,36 +405,36 @@ pub unsafe extern "C" fn wasmer_probestack() { /// This list is likely to grow over time. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum LibCall { + /// ceil.f32 + CeilF32, + + /// ceil.f64 + CeilF64, + + /// floor.f32 + FloorF32, + + /// floor.f64 + FloorF64, + + /// nearest.f32 + NearestF32, + + /// nearest.f64 + NearestF64, + /// probe for stack overflow. These are emitted for functions which need /// when the `enable_probestack` setting is true. Probestack, - /// ceil.f32 - CeilF32, - /// ceil.f64 - CeilF64, - /// floor.f32 - FloorF32, - /// floor.f64 - FloorF64, - /// trunc.f32 - TruncF32, - /// frunc.f64 - TruncF64, - /// nearest.f32 - NearestF32, - /// nearest.f64 - NearestF64, + /// A custom trap RaiseTrap, - // /// libc.memcpy - // Memcpy, - // /// libc.memset - // Memset, - // /// libc.memmove - // Memmove, - // /// Elf __tls_get_addr - // ElfTlsGetAddr, + /// trunc.f32 + TruncF32, + + /// frunc.f64 + TruncF64, } impl LibCall { @@ -442,16 +442,31 @@ impl LibCall { pub fn function_pointer(self) -> usize { match self { Self::CeilF32 => wasmer_f32_ceil as usize, - Self::FloorF32 => wasmer_f32_floor as usize, - Self::TruncF32 => wasmer_f32_trunc as usize, - Self::NearestF32 => wasmer_f32_nearest as usize, Self::CeilF64 => wasmer_f64_ceil as usize, + Self::FloorF32 => wasmer_f32_floor as usize, Self::FloorF64 => wasmer_f64_floor as usize, - Self::TruncF64 => wasmer_f64_trunc as usize, + Self::NearestF32 => wasmer_f32_nearest as usize, Self::NearestF64 => wasmer_f64_nearest as usize, + Self::Probestack => wasmer_probestack as usize, Self::RaiseTrap => wasmer_raise_trap as usize, - Self::Probestack => PROBESTACK as usize, - // other => panic!("unexpected libcall: {}", other), + Self::TruncF32 => wasmer_f32_trunc as usize, + Self::TruncF64 => wasmer_f64_trunc as usize, + } + } + + /// Return the function name associated to the libcall. + pub fn to_function_name(&self) -> &str { + match self { + Self::CeilF32 => "wasmer_f32_ceil", + Self::CeilF64 => "wasmer_f64_ceil", + Self::FloorF32 => "wasmer_f32_floor", + Self::FloorF64 => "wasmer_f64_floor", + Self::NearestF32 => "wasmer_f32_nearest", + Self::NearestF64 => "wasmer_f64_nearest", + Self::Probestack => "wasmer_probestack", + Self::RaiseTrap => "wasmer_raise_trap", + Self::TruncF32 => "wasmer_f32_trunc", + Self::TruncF64 => "wasmer_f64_trunc", } } } diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 41ea47b82..f3def12ab 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -26,7 +26,7 @@ pub use crate::syscalls::types; pub use crate::utils::{get_wasi_version, is_wasi_module, WasiVersion}; use thiserror::Error; -use wasmer::{imports, Function, ImportObject, Memory, Store}; +use wasmer::{imports, Function, ImportObject, Memory, Module, Store}; /// This is returned in `RuntimeError`. /// Use `downcast` or `downcast_ref` to retrieve the `ExitCode`. @@ -34,6 +34,8 @@ use wasmer::{imports, Function, ImportObject, Memory, Store}; pub enum WasiError { #[error("WASI exited with code: {0}")] Exit(syscalls::types::__wasi_exitcode_t), + #[error("The WASI version could not be determined")] + UnknownWasiVersion, } /// The environment provided to the WASI imports. @@ -51,6 +53,15 @@ impl<'a> WasiEnv<'a> { } } + pub fn import_object(&mut self, module: &Module) -> Result { + let wasi_version = get_wasi_version(module, false).ok_or(WasiError::UnknownWasiVersion)?; + Ok(generate_import_object_from_env( + module.store(), + self, + wasi_version, + )) + } + /// Set the state pub fn set_memory(&mut self, memory: &'a Memory) { self.memory = Some(memory); diff --git a/lib/wasi/src/state/builder.rs b/lib/wasi/src/state/builder.rs index 8a05023ba..d9403ca41 100644 --- a/lib/wasi/src/state/builder.rs +++ b/lib/wasi/src/state/builder.rs @@ -2,6 +2,7 @@ use crate::state::{WasiFile, WasiFs, WasiFsError, WasiState}; use crate::syscalls::types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO}; +use crate::WasiEnv; use std::path::{Path, PathBuf}; use thiserror::Error; @@ -388,6 +389,14 @@ impl WasiStateBuilder { envs: self.envs.clone(), }) } + + /// Consumes the [`WasiStateBuilder`] and produces a [`WasiEnv`] + /// + /// Returns the error from `WasiFs::new` if there's an error + pub fn finalize(&mut self) -> Result { + let state = self.build()?; + Ok(WasiEnv::new(state)) + } } /// Builder for preopened directories. diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index 9416f282b..4ae2ef03a 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -1481,8 +1481,8 @@ impl WasiState { /// Create a [`WasiStateBuilder`] to construct a validated instance of /// [`WasiState`]. #[allow(clippy::new_ret_no_self)] - pub fn new(program_name: &str) -> WasiStateBuilder { - create_wasi_state(program_name) + pub fn new(program_name: impl AsRef) -> WasiStateBuilder { + create_wasi_state(program_name.as_ref()) } /// Turn the WasiState into bytes diff --git a/src/commands/run.rs b/src/commands/run.rs index ce2a2763e..059002cf8 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -109,7 +109,7 @@ impl Run { #[cfg(feature = "wasi")] { let wasi_version = Wasi::get_version(&module); - if let Some(version) = wasi_version { + if wasi_version.is_some() { let program_name = self .command_name .clone() @@ -121,7 +121,7 @@ impl Run { .unwrap_or_default(); return self .wasi - .execute(module, version, program_name, self.args.clone()) + .execute(module, program_name, self.args.clone()) .with_context(|| "WASI execution failed"); } } diff --git a/src/commands/run/wasi.rs b/src/commands/run/wasi.rs index cea6ddb64..e2449498c 100644 --- a/src/commands/run/wasi.rs +++ b/src/commands/run/wasi.rs @@ -1,10 +1,8 @@ +use crate::utils::{parse_envvar, parse_mapdir}; use anyhow::{Context, Result}; use std::path::PathBuf; - -use wasmer::{Function, Instance, Memory, Module}; -use wasmer_wasi::{ - generate_import_object_from_env, get_wasi_version, WasiEnv, WasiState, WasiVersion, -}; +use wasmer::{Instance, Module}; +use wasmer_wasi::{get_wasi_version, WasiState, WasiVersion}; use structopt::StructOpt; @@ -12,16 +10,16 @@ use structopt::StructOpt; /// WASI Options pub struct Wasi { /// WASI pre-opened directory - #[structopt(long = "dir", multiple = true, group = "wasi")] + #[structopt(long = "dir", name = "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, + #[structopt(long = "mapdir", name = "GUEST_DIR:HOST_DIR", multiple = true, parse(try_from_str = parse_mapdir))] + mapped_dirs: Vec<(String, PathBuf)>, /// Pass custom environment variables - #[structopt(long = "env", multiple = true)] - env_vars: Vec, + #[structopt(long = "env", name = "KEY=VALUE", multiple = true, parse(try_from_str = parse_envvar))] + env_vars: Vec<(String, String)>, /// Enable experimental IO devices #[cfg(feature = "experimental-io-devices")] @@ -30,44 +28,6 @@ pub struct Wasi { } 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); - } - md.push((alias.to_string(), pb)); - continue; - } - bail!( - "Directory mappings must consist of two paths separate by a colon. Found {}", - &entry - ); - } - Ok(md) - } - - 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 the wasi version on strict mode, so no other imports are @@ -76,25 +36,15 @@ impl Wasi { } /// Helper function for executing Wasi from the `Run` command. - pub fn execute( - &self, - module: Module, - wasi_version: WasiVersion, - program_name: String, - args: Vec, - ) -> Result<()> { - let mut wasi_state_builder = WasiState::new(&program_name); - + pub fn execute(&self, module: Module, program_name: String, args: Vec) -> Result<()> { 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()?; + let mut wasi_state_builder = WasiState::new(program_name); wasi_state_builder .args(args) - .envs(env_vars) - .preopen_dirs(preopened_files)? - .map_dirs(mapped_dirs)?; + .envs(self.env_vars.clone()) + .preopen_dirs(self.pre_opened_directories.clone())? + .map_dirs(self.mapped_dirs.clone())?; #[cfg(feature = "experimental-io-devices")] { @@ -104,18 +54,13 @@ impl Wasi { } } - 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 mut wasi_env = wasi_state_builder.finalize()?; + let import_object = wasi_env.import_object(&module)?; let instance = Instance::new(&module, &import_object)?; - let memory: &Memory = instance.exports.get("memory")?; - wasi_env.set_memory(memory); - - let start: &Function = instance.exports.get("_start")?; + wasi_env.set_memory(instance.exports.get_memory("memory")?); + let start = instance.exports.get_function("_start")?; start .call(&[]) .with_context(|| "failed to run WASI `_start` function")?; diff --git a/src/utils.rs b/src/utils.rs index 6a0fd056e..697caffad 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,7 @@ //! Utility functions for the WebAssembly module +use anyhow::{bail, Result}; use std::env; +use std::path::PathBuf; /// Whether or not Wasmer should print with color pub fn wasmer_should_print_color() -> bool { @@ -8,3 +10,34 @@ pub fn wasmer_should_print_color() -> bool { .and_then(|inner| inner.parse::().ok()) .unwrap_or_else(|| atty::is(atty::Stream::Stdout)) } + +/// Parses a mapdir from a string +pub fn parse_mapdir(entry: &str) -> Result<(String, PathBuf)> { + 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); + } + return Ok((alias.to_string(), pb)); + } + bail!( + "Directory mappings must consist of two paths separate by a colon. Found {}", + &entry + ) +} + +/// Parses a mapdir from an env var +pub fn parse_envvar(entry: &str) -> Result<(String, String)> { + if let [env_var, value] = entry.split('=').collect::>()[..] { + return Ok((env_var.to_string(), value.to_string())); + } else { + bail!( + "Env vars must be of the form =. Found {}", + &entry + ); + } +}