Skip to content

Commit

Permalink
Added support for WASI
Browse files Browse the repository at this point in the history
  • Loading branch information
syrusakbary committed Apr 30, 2020
1 parent 9946d75 commit 8417b3b
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 100 deletions.
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,12 @@ wasmer-wast = { path = "tests/lib/wast" }
# Don't add the backend features in default, please add them on the Makefile
# since we might want to autoconfigure them depending on the availability on the host.
# default = ["wasi"]
default = ["wast", "compiler-cranelift"]
default = ["wast", "wasi", "compiler-cranelift"]
wast = ["wasmer-wast"]
wasi = ["wasmer-wasi"]
experimental-io-devices = [
# "wasmer-wasi-experimental-io-devices"
]
compiler-singlepass = [
"test-utils/compiler-singlepass",
"wasmer/compiler-singlepass",
Expand All @@ -71,5 +75,3 @@ compiler-llvm = [
"wasmer/compiler-llvm",
"wasmer-compiler-llvm",
]
# wasi = ["wasmer-wasi"]
# experimental-io-devices = ["wasmer-wasi-experimental-io-devices"]
8 changes: 7 additions & 1 deletion lib/jit/src/trap/error.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use super::frame_info::{FrameInfo, GlobalFrameInfo, FRAME_INFO};
use backtrace::Backtrace;
use std::error::Error;
use std::fmt;
use std::sync::Arc;
use wasmer_runtime::{Trap, TrapCode};
use wasmer_runtime::{raise_user_trap, Trap, TrapCode};

/// A struct representing an aborted instruction execution, with a message
/// indicating the cause.
Expand Down Expand Up @@ -60,6 +61,11 @@ impl RuntimeError {
}
}

/// Raises a custom user Error
pub fn raise(error: Box<dyn Error + Send + Sync>) -> ! {
unsafe { raise_user_trap(error) }
}

fn new_wasm(
info: &GlobalFrameInfo,
trap_pc: Option<usize>,
Expand Down
16 changes: 9 additions & 7 deletions lib/wasi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,31 @@
//! [WASI plugin example](https://github.com/wasmerio/wasmer/blob/master/examples/plugin.rs)
//! for an example of how to extend WASI using the WASI FS API.
#[cfg(target = "windows")]
extern crate winapi;
#[macro_use]
extern crate log;

#[macro_use]
mod macros;
mod ptr;
pub mod state;
mod state;
mod syscalls;
mod utils;

use self::state::{WasiFs, WasiState};
pub use self::state::WasiState;
pub use self::syscalls::types;
use self::syscalls::*;

pub use self::utils::{get_wasi_version, is_wasi_module, WasiVersion};

use thiserror::Error;
use wasmer::{imports, Func, ImportObject, Memory, Store};

/// This is returned in `RuntimeError`.
/// Use `downcast` or `downcast_ref` to retrieve the `ExitCode`.
pub struct ExitCode {
pub code: syscalls::types::__wasi_exitcode_t,
#[derive(Error, Debug)]
pub enum WasiError {
#[error("WASI exited with code: {0}")]
Exit(syscalls::types::__wasi_exitcode_t),
}

/// The environment provided to the WASI imports.
Expand Down Expand Up @@ -70,7 +71,7 @@ impl<'a> WasiEnv<'a> {

/// Get a reference to the memory
pub fn memory(&self) -> &Memory {
self.memory.as_ref().unwrap()
self.memory.as_ref().expect("The expected Memory is not attached to the `WasiEnv`. Did you forgot to call wasi_env.set_memory(...)?")
}

pub(crate) fn get_memory_and_wasi_state(
Expand Down Expand Up @@ -102,6 +103,7 @@ pub fn generate_import_object_from_env(
fn generate_import_object_snapshot0(store: &Store, env: &mut WasiEnv) -> ImportObject {
imports! {
"wasi_unstable" => {
"args_get" => Func::new_env(store, env, args_get),
"args_sizes_get" => Func::new_env(store, env, args_sizes_get),
"clock_res_get" => Func::new_env(store, env, clock_res_get),
"clock_time_get" => Func::new_env(store, env, clock_time_get),
Expand Down
8 changes: 4 additions & 4 deletions lib/wasi/src/syscalls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ use crate::{
InodeVal, Kind, PollEvent, PollEventBuilder, WasiFile, WasiFsError, WasiState,
MAX_SYMLINKS,
},
ExitCode, WasiEnv,
WasiEnv, WasiError,
};
use std::borrow::Borrow;
use std::cell::Cell;
use std::convert::{Infallible, TryInto};
use std::io::{self, Read, Seek, Write};
use wasmer::Memory;
use wasmer::{Memory, RuntimeError};

#[cfg(any(
target_os = "freebsd",
Expand Down Expand Up @@ -2498,8 +2498,8 @@ pub fn poll_oneoff(

pub fn proc_exit(env: &mut WasiEnv, code: __wasi_exitcode_t) {
debug!("wasi::proc_exit, {}", code);
panic!("Proc exit");
// ExitCode { code }
RuntimeError::raise(Box::new(WasiError::Exit(code)));
unreachable!();
}

pub fn proc_raise(env: &mut WasiEnv, sig: __wasi_signal_t) -> __wasi_errno_t {
Expand Down
File renamed without changes.
38 changes: 30 additions & 8 deletions src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ use wasmer::*;

use structopt::StructOpt;

#[cfg(feature = "wasi")]
mod wasi;

#[cfg(feature = "wasi")]
use wasi::Wasi;

#[derive(Debug, StructOpt, Clone)]
/// The options for the `wasmer run` subcommand
pub struct Run {
Expand Down Expand Up @@ -36,11 +42,10 @@ pub struct Run {
#[structopt(flatten)]
compiler: CompilerOptions,

// #[structopt(flatten)]
// llvm: LLVMCLIOptions,
#[cfg(feature = "wasi")]
#[structopt(flatten)]
wasi: Wasi,

// #[structopt(flatten)]
// wasi: WasiOptions,
/// Enable non-standard experimental IO devices
#[cfg(feature = "io-devices")]
#[structopt(long = "enable-io-devices")]
Expand All @@ -63,10 +68,10 @@ impl Run {
let engine = Engine::new(&*compiler_config);
let store = Store::new(&engine);
let module = Module::from_file(&store, &self.path)?;
let imports = imports! {};
let instance = Instance::new(&module, &imports)?;

if let Some(ref invoke) = self.invoke {
let imports = imports! {};
let instance = Instance::new(&module, &imports)?;
let result = self
.invoke_function(&instance, &invoke, &self.args)
.with_context(|| format!("Failed to invoke `{}`", invoke))?;
Expand All @@ -80,9 +85,26 @@ impl Run {
);
return Ok(());
}
#[cfg(feature = "wasi")]
{
let wasi_version = Wasi::get_version(&module);
if let Some(version) = wasi_version {
let program_name = self
.command_name
.clone()
.or_else(|| {
self.path
.file_name()
.map(|f| f.to_string_lossy().to_string())
})
.unwrap_or("".to_string());
self.wasi
.execute(module, version, program_name, self.args.clone())?;
}
}

let start: &Func = instance.exports.get("_start")?;
start.call(&[])?;
// let start: &Func = instance.exports.get("_start")?;
// start.call(&[])?;

Ok(())
}
Expand Down
173 changes: 96 additions & 77 deletions src/commands/run/wasi.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
use wasmer_wasi;
use anyhow::{bail, Result};
use std::path::PathBuf;

use wasmer::{Func, Instance, Memory, Module};
use wasmer_wasi::{
generate_import_object_from_env, get_wasi_version, WasiEnv, WasiState, WasiVersion,
};

use structopt::StructOpt;

#[derive(Debug, StructOpt, Clone)]
/// WASI Options
pub struct WasiOptions {
pub struct Wasi {
/// WASI pre-opened directory
#[structopt(long = "dir", multiple = true, group = "wasi")]
pre_opened_directories: Vec<PathBuf>,
Expand All @@ -15,91 +22,103 @@ pub struct WasiOptions {
/// Pass custom environment variables
#[structopt(long = "env", multiple = true)]
env_vars: Vec<String>,
}

/// Enable experimental IO devices
#[cfg(feature = "experimental-io-devices")]
#[structopt(long = "enable-experimental-io-devices")]
enable_experimental_io_devices: bool,
}

fn get_mapped_dirs(input: &[String]) -> Result<Vec<(String, PathBuf)>, String> {
let mut md = vec![];
for entry in input.iter() {
if let [alias, real_dir] = entry.split(':').collect::<Vec<&str>>()[..] {
let pb = PathBuf::from(&real_dir);
if let Ok(pb_metadata) = pb.metadata() {
if !pb_metadata.is_dir() {
return Err(format!(
"\"{}\" exists, but it is not a directory",
&real_dir
));
impl Wasi {
fn get_mapped_dirs(&self) -> Result<Vec<(String, PathBuf)>> {
let mut md = vec![];
for entry in self.mapped_dirs.iter() {
if let [alias, real_dir] = entry.split(':').collect::<Vec<&str>>()[..] {
let pb = PathBuf::from(&real_dir);
if let Ok(pb_metadata) = pb.metadata() {
if !pb_metadata.is_dir() {
bail!("\"{}\" exists, but it is not a directory", &real_dir);
}
} else {
bail!("Directory \"{}\" does not exist", &real_dir);
}
} else {
return Err(format!("Directory \"{}\" does not exist", &real_dir));
md.push((alias.to_string(), pb));
continue;
}
md.push((alias.to_string(), pb));
continue;
bail!(
"Directory mappings must consist of two paths separate by a colon. Found {}",
&entry
);
}
return Err(format!(
"Directory mappings must consist of two paths separate by a colon. Found {}",
&entry
));
Ok(md)
}
Ok(md)
}

fn get_env_var_args(input: &[String]) -> Result<Vec<(&str, &str)>, String> {
let mut ev = vec![];
for entry in input.iter() {
if let [env_var, value] = entry.split('=').collect::<Vec<&str>>()[..] {
ev.push((env_var, value));
} else {
return Err(format!(
"Env vars must be of the form <var_name>=<value>. Found {}",
&entry
));
fn get_env_vars(&self) -> Result<Vec<(&str, &str)>> {
let mut env = vec![];
for entry in self.env_vars.iter() {
if let [env_var, value] = entry.split('=').collect::<Vec<&str>>()[..] {
env.push((env_var, value));
} else {
bail!(
"Env vars must be of the form <var_name>=<value>. Found {}",
&entry
);
}
}
Ok(env)
}

/// Gets the WASI version (if any) for the provided module
pub fn get_version(module: &Module) -> Option<WasiVersion> {
get_wasi_version(&module, true)
}
Ok(ev)
}

/// Helper function for `execute_wasm` (the `Run` command)
fn execute_wasi(
wasi_version: wasmer_wasi::WasiVersion,
options: &Run,
env_vars: Vec<(&str, &str)>,
module: wasmer_runtime_core::Module,
mapped_dirs: Vec<(String, PathBuf)>,
_wasm_binary: &[u8],
) -> Result<()> {
let name = if let Some(cn) = &options.command_name {
cn.clone()
} else {
options.path.to_str().unwrap().to_owned()
};

let args = options.args.iter().cloned().map(|arg| arg.into_bytes());
let preopened_files = options.pre_opened_directories.clone();
let mut wasi_state_builder = wasmer_wasi::state::WasiState::new(&name);
wasi_state_builder
.args(args)
.envs(env_vars)
.preopen_dirs(preopened_files)
.map_err(|e| format!("Failed to preopen directories: {:?}", e))?
.map_dirs(mapped_dirs)
.map_err(|e| format!("Failed to preopen mapped directories: {:?}", e))?;
/// Helper function for executing Wasi from the `Run` command.
pub fn execute(
&self,
module: Module,
wasi_version: WasiVersion,
program_name: String,
args: Vec<String>,
) -> Result<()> {
// Get the wasi version on strict mode, so no other imports are
// allowed.
// let wasi_version: WasiVersion = get_wasi_version(&module, true);
let mut wasi_state_builder = WasiState::new(&program_name);

#[cfg(feature = "experimental-io-devices")]
{
if options.enable_experimental_io_devices {
wasi_state_builder.setup_fs(Box::new(wasmer_wasi_experimental_io_devices::initialize));
let args = args.iter().cloned().map(|arg| arg.into_bytes());
let env_vars = self.get_env_vars()?;
let preopened_files = self.pre_opened_directories.clone();
let mapped_dirs = self.get_mapped_dirs()?;

wasi_state_builder
.args(args)
.envs(env_vars)
.preopen_dirs(preopened_files)?
.map_dirs(mapped_dirs)?;

#[cfg(feature = "experimental-io-devices")]
{
if self.enable_experimental_io_devices {
wasi_state_builder
.setup_fs(Box::new(wasmer_wasi_experimental_io_devices::initialize));
}
}

let wasi_state = wasi_state_builder.build()?;
let mut wasi_env = WasiEnv::new(wasi_state);
let import_object =
generate_import_object_from_env(module.store(), &mut wasi_env, wasi_version);

let instance = Instance::new(&module, &import_object)?;

let memory: &Memory = instance.exports.get("memory")?;
wasi_env.set_memory(memory);

let start: &Func = instance.exports.get("_start")?;

start.call(&[])?;

Ok(())
}
let wasi_state = wasi_state_builder.build()?;
let import_object = wasmer_wasi::generate_import_object_from_state(wasi_state, wasi_version);

let mut instance = module
.instantiate(&import_object)
.map_err(|e| format!("Can't instantiate WASI module: {:?}", e))?;

let start: &Func = instance
.exports
.get("_start")?;
Ok(())
}
}
Loading

0 comments on commit 8417b3b

Please sign in to comment.