Skip to content

Commit

Permalink
Try #2251:
Browse files Browse the repository at this point in the history
  • Loading branch information
bors[bot] authored Apr 27, 2021
2 parents a00f3ee + 1e9603e commit 49f9d84
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Looking for changes that affect our C API? See the [C API Changelog](lib/c-api/C
- [#2135](https://github.com/wasmerio/wasmer/pull/2135) [Documentation](./PACKAGING.md) for linux distribution maintainers

### Changed
- [#2251](https://github.com/wasmerio/wasmer/pull/2251) Wasmer CLI will now execute WASI modules with multiple WASI namespaces in them by default. Use `--allow-multiple-wasi-versions` to suppress the warning and use `--deny-multiple-wasi-versions` to make it an error.
- [#2201](https://github.com/wasmerio/wasmer/pull/2201) Implement `loupe::MemoryUsage` for `wasmer::Instance`.
- [#2200](https://github.com/wasmerio/wasmer/pull/2200) Implement `loupe::MemoryUsage` for `wasmer::Module`.
- [#2199](https://github.com/wasmerio/wasmer/pull/2199) Implement `loupe::MemoryUsage` for `wasmer::Store`.
Expand Down
24 changes: 22 additions & 2 deletions lib/cli/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,28 @@ impl Run {
// If WASI is enabled, try to execute it with it
#[cfg(feature = "wasi")]
{
let wasi_version = Wasi::get_version(&module);
if wasi_version.is_some() {
use std::collections::BTreeSet;
use wasmer_wasi::WasiVersion;

let wasi_versions = Wasi::get_versions(&module);
if let Some(wasi_versions) = wasi_versions {
if wasi_versions.len() >= 2 {
let get_version_list = |versions: &BTreeSet<WasiVersion>| -> String {
versions
.iter()
.map(|v| format!("`{}`", v.get_namespace_str()))
.collect::<Vec<String>>()
.join(", ")
};
if self.wasi.deny_multiple_wasi_versions {
let version_list = get_version_list(&wasi_versions);
bail!("Found more than 1 WASI version in this module ({}) and `--deny-multiple-wasi-versions` is enabled.", version_list);
} else if !self.wasi.allow_multiple_wasi_versions {
let version_list = get_version_list(&wasi_versions);
warning!("Found more than 1 WASI version in this module ({}). If this is intentional, pass `--allow-multiple-wasi-versions` to suppress this warning.", version_list);
}
}

let program_name = self
.command_name
.clone()
Expand Down
23 changes: 16 additions & 7 deletions lib/cli/src/commands/run/wasi.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::utils::{parse_envvar, parse_mapdir};
use anyhow::{Context, Result};
use std::collections::BTreeSet;
use std::path::PathBuf;
use wasmer::{Instance, Module};
use wasmer_wasi::{get_wasi_version, WasiError, WasiState, WasiVersion};
use wasmer_wasi::{get_wasi_versions, WasiError, WasiState, WasiVersion};

use clap::Clap;

Expand All @@ -13,7 +14,7 @@ pub struct Wasi {
#[clap(long = "dir", name = "DIR", multiple = true, group = "wasi")]
pre_opened_directories: Vec<PathBuf>,

/// Map a host directory to a different location for the wasm module
/// Map a host directory to a different location for the Wasm module
#[clap(long = "mapdir", name = "GUEST_DIR:HOST_DIR", multiple = true, parse(try_from_str = parse_mapdir))]
mapped_dirs: Vec<(String, PathBuf)>,

Expand All @@ -25,22 +26,30 @@ pub struct Wasi {
#[cfg(feature = "experimental-io-devices")]
#[clap(long = "enable-experimental-io-devices")]
enable_experimental_io_devices: bool,

/// Allow WASI modules to import multiple versions of WASI without a warning.
#[clap(long = "allow-multiple-wasi-versions")]
pub allow_multiple_wasi_versions: bool,

/// Require WASI modules to only import 1 version of WASI.
#[clap(long = "deny-multiple-wasi-versions")]
pub deny_multiple_wasi_versions: bool,
}

#[allow(dead_code)]
impl Wasi {
/// Gets the WASI version (if any) for the provided module
pub fn get_version(module: &Module) -> Option<WasiVersion> {
pub fn get_versions(module: &Module) -> Option<BTreeSet<WasiVersion>> {
// Get the wasi version in strict mode, so no other imports are
// allowed.
get_wasi_version(&module, true)
get_wasi_versions(&module, true)
}

/// Checks if a given module has any WASI imports at all.
pub fn has_wasi_imports(module: &Module) -> bool {
// Get the wasi version in non-strict mode, so no other imports
// are allowed
get_wasi_version(&module, false).is_some()
get_wasi_versions(&module, false).is_some()
}

/// Helper function for executing Wasi from the `Run` command.
Expand All @@ -63,8 +72,8 @@ impl Wasi {
}

let mut wasi_env = wasi_state_builder.finalize()?;
let import_object = wasi_env.import_object(&module)?;
let instance = Instance::new(&module, &import_object)?;
let resolver = wasi_env.import_object_for_all_wasi_versions(&module)?;
let instance = Instance::new(&module, &resolver)?;

let start = instance.exports.get_function("_start")?;
let result = start.call(&[]);
Expand Down
33 changes: 31 additions & 2 deletions lib/wasi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ pub use crate::state::{
WasiStateCreationError, ALL_RIGHTS, VIRTUAL_ROOT_FD,
};
pub use crate::syscalls::types;
pub use crate::utils::{get_wasi_version, is_wasi_module, WasiVersion};
pub use crate::utils::{get_wasi_version, get_wasi_versions, is_wasi_module, WasiVersion};

use thiserror::Error;
use wasmer::{imports, Function, ImportObject, LazyInit, Memory, Module, Store, WasmerEnv};
use wasmer::{
imports, ChainableNamedResolver, Function, ImportObject, LazyInit, Memory, Module,
NamedResolver, Store, WasmerEnv,
};
#[cfg(all(target_os = "macos", target_arch = "aarch64",))]
use wasmer::{FunctionType, ValType};

Expand Down Expand Up @@ -67,6 +70,7 @@ impl WasiEnv {
}
}

/// Get an `ImportObject` for a specific version of WASI detected in the module.
pub fn import_object(&mut self, module: &Module) -> Result<ImportObject, WasiError> {
let wasi_version = get_wasi_version(module, false).ok_or(WasiError::UnknownWasiVersion)?;
Ok(generate_import_object_from_env(
Expand All @@ -76,6 +80,31 @@ impl WasiEnv {
))
}

/// Like `import_object` but containing all the WASI versions detected in
/// the module.
pub fn import_object_for_all_wasi_versions(
&mut self,
module: &Module,
) -> Result<Box<dyn NamedResolver>, WasiError> {
let wasi_versions =
get_wasi_versions(module, false).ok_or(WasiError::UnknownWasiVersion)?;
let mut version_iter = wasi_versions.iter();
let mut resolver: Box<dyn NamedResolver> = {
let version = version_iter.next().ok_or(WasiError::UnknownWasiVersion)?;
Box::new(generate_import_object_from_env(
module.store(),
self.clone(),
*version,
))
};
for version in version_iter {
let new_import_object =
generate_import_object_from_env(module.store(), self.clone(), *version);
resolver = Box::new(new_import_object.chain_front(resolver));
}
Ok(resolver)
}

/// Get the WASI state
///
/// Be careful when using this in host functions that call into Wasm:
Expand Down
123 changes: 116 additions & 7 deletions lib/wasi/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use wasmer::{ExternType, Module};
use std::collections::BTreeSet;
use wasmer::Module;

#[allow(dead_code)]
/// Check if a provided module is compiled for some version of WASI.
Expand All @@ -9,7 +10,7 @@ pub fn is_wasi_module(module: &Module) -> bool {

/// The version of WASI. This is determined by the imports namespace
/// string.
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, Eq)]
pub enum WasiVersion {
/// `wasi_unstable`.
Snapshot0,
Expand All @@ -30,6 +31,52 @@ pub enum WasiVersion {
Latest,
}

impl WasiVersion {
/// Get the version as its namespace str as it appears in Wasm modules.
pub const fn get_namespace_str(&self) -> &'static str {
match *self {
WasiVersion::Snapshot0 => SNAPSHOT0_NAMESPACE,
WasiVersion::Snapshot1 => SNAPSHOT1_NAMESPACE,
WasiVersion::Latest => SNAPSHOT1_NAMESPACE,
}
}
}

impl PartialEq<WasiVersion> for WasiVersion {
fn eq(&self, other: &Self) -> bool {
match (*self, *other) {
(Self::Snapshot1, Self::Latest)
| (Self::Latest, Self::Snapshot1)
| (Self::Latest, Self::Latest)
| (Self::Snapshot0, Self::Snapshot0)
| (Self::Snapshot1, Self::Snapshot1) => true,
_ => false,
}
}
}

impl PartialOrd for WasiVersion {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for WasiVersion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if self == other {
return std::cmp::Ordering::Equal;
}
match (*self, *other) {
// if snapshot0 is not equal, it must be less
(Self::Snapshot0, _) => std::cmp::Ordering::Less,
(Self::Snapshot1, Self::Snapshot0) | (Self::Latest, Self::Snapshot0) => {
std::cmp::Ordering::Greater
}
_ => unreachable!("Missing info about ordering of WasiVerison"),
}
}
}

/// Namespace for the `Snapshot0` version.
const SNAPSHOT0_NAMESPACE: &str = "wasi_unstable";

Expand All @@ -41,13 +88,10 @@ const SNAPSHOT1_NAMESPACE: &str = "wasi_snapshot_preview1";
///
/// A strict detection expects that all imports live in a single WASI
/// namespace. A non-strict detection expects that at least one WASI
/// namespace exits to detect the version. Note that the strict
/// namespace exists to detect the version. Note that the strict
/// detection is faster than the non-strict one.
pub fn get_wasi_version(module: &Module, strict: bool) -> Option<WasiVersion> {
let mut imports = module.imports().filter_map(|extern_| match extern_.ty() {
ExternType::Function(_f) => Some(extern_.module().to_owned()),
_ => None,
});
let mut imports = module.imports().functions().map(|f| f.module().to_owned());

if strict {
let first_module = imports.next()?;
Expand All @@ -70,3 +114,68 @@ pub fn get_wasi_version(module: &Module, strict: bool) -> Option<WasiVersion> {
})
}
}

/// Like [`get_wasi_version`] but detects multiple WASI versions in a single module.
/// Thus `strict` behaves differently in this function as multiple versions are
/// always supported. `strict` indicates whether non-WASI imports should trigger a
/// failure or be ignored.
pub fn get_wasi_versions(module: &Module, strict: bool) -> Option<BTreeSet<WasiVersion>> {
let mut out = BTreeSet::new();
let imports = module.imports().functions().map(|f| f.module().to_owned());

let mut non_wasi_seen = false;
for ns in imports {
match ns.as_str() {
SNAPSHOT0_NAMESPACE => {
out.insert(WasiVersion::Snapshot0);
}
SNAPSHOT1_NAMESPACE => {
out.insert(WasiVersion::Snapshot1);
}
_ => {
non_wasi_seen = true;
}
}
}
if strict && non_wasi_seen {
None
} else {
Some(out)
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn wasi_version_equality() {
assert_eq!(WasiVersion::Snapshot0, WasiVersion::Snapshot0);
assert_eq!(WasiVersion::Snapshot1, WasiVersion::Snapshot1);
assert_eq!(WasiVersion::Snapshot1, WasiVersion::Latest);
assert_eq!(WasiVersion::Latest, WasiVersion::Snapshot1);
assert_eq!(WasiVersion::Latest, WasiVersion::Latest);
assert!(WasiVersion::Snapshot0 != WasiVersion::Snapshot1);
assert!(WasiVersion::Snapshot1 != WasiVersion::Snapshot0);
assert!(WasiVersion::Snapshot0 != WasiVersion::Latest);
assert!(WasiVersion::Latest != WasiVersion::Snapshot0);
}

#[test]
fn wasi_version_ordering() {
assert!(WasiVersion::Snapshot0 <= WasiVersion::Snapshot0);
assert!(WasiVersion::Snapshot1 <= WasiVersion::Snapshot1);
assert!(WasiVersion::Latest <= WasiVersion::Latest);
assert!(WasiVersion::Snapshot0 >= WasiVersion::Snapshot0);
assert!(WasiVersion::Snapshot1 >= WasiVersion::Snapshot1);
assert!(WasiVersion::Latest >= WasiVersion::Latest);

assert!(WasiVersion::Snapshot0 < WasiVersion::Snapshot1);
assert!(WasiVersion::Snapshot1 > WasiVersion::Snapshot0);
assert!(WasiVersion::Snapshot0 < WasiVersion::Latest);
assert!(WasiVersion::Latest > WasiVersion::Snapshot0);

assert!(!(WasiVersion::Snapshot1 < WasiVersion::Latest));
assert!(!(WasiVersion::Latest > WasiVersion::Snapshot1));
}
}

0 comments on commit 49f9d84

Please sign in to comment.