Skip to content

Commit

Permalink
Add WasiRunner and EmscriptenRunner
Browse files Browse the repository at this point in the history
  • Loading branch information
fschutt committed Oct 27, 2022
1 parent 968d74c commit acc5ac1
Show file tree
Hide file tree
Showing 5 changed files with 451 additions and 0 deletions.
4 changes: 4 additions & 0 deletions lib/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ walkdir = "2.3.2"
regex = "1.6.0"
toml = "0.5.9"
url = "2.3.1"
webc = { version = "3.0.0", optional = true, default-features = false, features = ["std", "mmap"] }
serde_cbor = { version = "0.11.2", optional = true }

[build-dependencies]
chrono = { version = "^0.4", default-features = false, features = [ "std", "clock" ] }
Expand Down Expand Up @@ -95,6 +97,7 @@ default = [
"http",
"cache",
"wasi",
"webc_runner",
"emscripten",
"compiler",
"wasmer-artifact-create",
Expand All @@ -106,6 +109,7 @@ wast = ["wasmer-wast"]
wasi = ["wasmer-wasi"]
emscripten = ["wasmer-emscripten"]
wat = ["wasmer/wat"]
webc_runner = ["webc", "serde_cbor"]
compiler = [
"wasmer-compiler/translator",
"wasmer-compiler/compiler",
Expand Down
3 changes: 3 additions & 0 deletions lib/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ pub mod logging;
pub mod store;
pub mod suggestions;
pub mod utils;
/// Runners for webc files (WASI / Emscripten)
#[cfg(feature = "webc_runner")]
pub mod runners;

/// Version number of this crate.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
119 changes: 119 additions & 0 deletions lib/cli/src/runners/emscripten.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#![cfg(feature = "rt-emscripten")]
//! WebC container support for running Emscripten modules
use crate::vfs::VirtualFileSystem;
use crate::WapmContainer;
use anyhow::anyhow;
use serde_derive::{Deserialize, Serialize};
use std::error::Error as StdError;
use std::sync::Arc;
use wasmer::{Cranelift, FunctionEnv, Instance, Module, Store};
use wasmer_emscripten::{
generate_emscripten_env, is_emscripten_module, run_emscripten_instance, EmEnv,
EmscriptenGlobals,
};
use webc::{Command, WebCMmap};

#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Hash, Serialize, Deserialize)]
pub struct EmscriptenRunner {
args: Vec<String>,
}

impl EmscriptenRunner {
pub fn set_args(&mut self, args: Vec<String>) {
self.args = args;
}
}

impl crate::Runner for EmscriptenRunner {
type Output = ();

fn can_run_command(&self, _: &str, command: &Command) -> Result<bool, Box<dyn StdError>> {
Ok(command
.runner
.starts_with("https://webc.org/runner/emscripten"))
}

fn run_command(
&mut self,
command_name: &str,
command: &Command,
container: &WapmContainer,
) -> Result<Self::Output, Box<dyn StdError>> {
let atom_name = container.get_atom_name_for_command("emscripten", command_name)?;
let main_args = container.get_main_args_for_command(command_name);
let atom_bytes = container.get_atom(&container.get_package_name(), &atom_name)?;

let compiler = Cranelift::default();
let mut store = Store::new(compiler);
let mut module = Module::new(&store, atom_bytes)?;
module.set_name(&atom_name);

let (mut globals, mut env) =
prepare_emscripten_env(&mut store, &module, container.webc.clone(), &atom_name)?;

exec_module(
&mut store,
&module,
&mut globals,
env,
container.webc.clone(),
&atom_name,
main_args.unwrap_or_default(),
)?;

Ok(())
}
}

fn prepare_emscripten_env(
store: &mut Store,
module: &Module,
atom: Arc<WebCMmap>,
name: &str,
) -> Result<(EmscriptenGlobals, FunctionEnv<EmEnv>), anyhow::Error> {
if !is_emscripten_module(&module) {
return Err(anyhow!("Atom {name:?} is not an emscripten module"));
}

#[cfg(feature = "wasi")]
if wasmer_wasi::Wasi::has_wasi_imports(&module) {
return Err(anyhow!(
"Unsupported: atom {name:?} has both emscripten and WASI imports."
));
}

let mut env = FunctionEnv::new(store, EmEnv::new());
let emscripten_globals = EmscriptenGlobals::new(store, &env, &module);
let mut emscripten_globals = emscripten_globals.map_err(|e| anyhow!("{}", e))?;
env.as_mut(store)
.set_data(&emscripten_globals.data, Default::default());

Ok((emscripten_globals, env))
}

fn exec_module(
store: &mut Store,
module: &Module,
globals: &mut EmscriptenGlobals,
em_env: FunctionEnv<EmEnv>,
atom: Arc<WebCMmap>,
name: &str,
args: Vec<String>,
) -> Result<(), anyhow::Error> {
let import_object = generate_emscripten_env(store, &em_env, globals);

let mut instance = Instance::new(store, module, &import_object)
.map_err(|e| anyhow!("Cant instantiate emscripten module {name:?}: {e}"))?;

run_emscripten_instance(
&mut instance,
em_env.into_mut(store),
globals,
name,
args.iter().map(|arg| arg.as_str()).collect(),
None,
)?;

Ok(())
}
211 changes: 211 additions & 0 deletions lib/cli/src/runners/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#![cfg(feature = "webc_runner")]

use std::error::Error as StdError;
use std::sync::Arc;
use std::path::PathBuf;
use webc::*;

pub mod wasi;
pub mod emscripten;

/// Parsed WAPM file, memory-mapped to an on-disk path
#[derive(Debug, Clone)]
pub struct WapmContainer {
/// WebC container
pub webc: Arc<WebCMmap>,
}

impl core::ops::Deref for WapmContainer {
type Target = webc::WebC<'static>;
fn deref<'a>(&'a self) -> &WebC<'static> {
&self.webc.webc
}
}

/// Error that ocurred while parsing the .webc file
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum WebcParseError {
/// Parse error
Parse(webc::Error),
}

impl From<webc::Error> for WebcParseError {
fn from(e: webc::Error) -> Self {
WebcParseError::Parse(e)
}
}

impl WapmContainer {
/// Parses a .webc container file. Since .webc files
/// can be very large, only file paths are allowed.
pub fn new(path: PathBuf) -> std::result::Result<Self, WebcParseError> {
let webc = webc::WebCMmap::parse(path, &webc::ParseOptions::default())?;
Ok(Self {
webc: Arc::new(webc),
})
}

/// Returns the bytes of a file or a stringified error
pub fn get_file<'b>(&'b self, path: &str) -> Result<&'b [u8], String> {
self.webc
.get_file(&self.webc.get_package_name(), path)
.map_err(|e| e.0)
}

/// Returns a list of volumes in this container
pub fn get_volumes(&self) -> Vec<String> {
self.webc.volumes.keys().cloned().collect::<Vec<_>>()
}

/// Lookup .wit bindings by name and parse them
pub fn get_bindings<T: Bindings>(
&self,
bindings: &str,
) -> std::result::Result<T, ParseBindingsError> {
let bindings = self
.webc
.manifest
.bindings
.iter()
.find(|b| b.name == bindings)
.ok_or(ParseBindingsError::NoBindings(bindings.to_string()))?;

T::parse_bindings(self, &bindings.annotations).map_err(ParseBindingsError::ParseBindings)
}
}

/// Error that happened while parsing .wit bindings
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
pub enum ParseBindingsError {
/// No bindings are available for the given lookup key
NoBindings(String),
/// Error happened during parsing
ParseBindings(String),
}

/// Trait to parse bindings (any kind of bindings) for container .wasm files (usually .wit format)
pub trait Bindings {
/// Function that takes annotations in a free-form `Value` struct and returns the parsed bindings or an error
fn parse_bindings(_: &WapmContainer, _: &serde_cbor::Value) -> Result<Self, String>
where
Self: Sized;
}

/// WIT bindings
#[derive(Default, Debug, Copy, Clone)]
pub struct WitBindings {}

impl WitBindings {
/// Unused: creates default wit bindings
pub fn from_str(_s: &str) -> Result<Self, String> {
Ok(Self::default())
}
}

impl Bindings for WitBindings {
fn parse_bindings(
container: &WapmContainer,
value: &serde_cbor::Value,
) -> Result<Self, String> {
let value: webc::WitBindingsExtended =
serde_cbor::from_slice(&serde_cbor::to_vec(value).unwrap())
.map_err(|e| format!("could not parse WitBindings annotations: {e}"))?;

let mut wit_bindgen_filepath = value.wit.exports;

for v in container.get_volumes() {
let schema = format!("{v}://");
if wit_bindgen_filepath.starts_with(&schema) {
wit_bindgen_filepath = wit_bindgen_filepath.replacen(&schema, "", 1);
break;
}
}

let wit_bindings = container
.get_file(&wit_bindgen_filepath)
.map_err(|e| format!("could not get WitBindings file {wit_bindgen_filepath:?}: {e}"))?;

let wit_bindings_str = std::str::from_utf8(wit_bindings)
.map_err(|e| format!("could not get WitBindings file {wit_bindgen_filepath:?}: {e}"))?;

Self::from_str(wit_bindings_str)
}
}

/// Trait that all runners have to implement
pub trait Runner {
/// The return value of the output of the runner
type Output;

/// Returns whether the Runner will be able to run the `Command`
fn can_run_command(
&self,
command_name: &str,
command: &Command,
) -> Result<bool, Box<dyn StdError>>;

/// Implementation to run the given command
///
/// - use `cmd.annotations` to get the metadata for the given command
/// - use `container.get_atom()` to get the
fn run_command(
&mut self,
command_name: &str,
cmd: &Command,
container: &WapmContainer,
) -> Result<Self::Output, Box<dyn StdError>>;

/// Runs the container if the container has an `entrypoint` in the manifest
fn run(&mut self, container: &WapmContainer) -> Result<Self::Output, Box<dyn StdError>> {
let cmd = match container.webc.webc.manifest.entrypoint.as_ref() {
Some(s) => s,
None => {
let path = format!("{}", container.webc.path.display());
return Err(Box::new(webc::Error(format!(
"Cannot run {path:?}: not executable (no entrypoint in manifest)"
))));
}
};

self.run_cmd(container, cmd)
}

/// Runs the given `cmd` on the container
fn run_cmd(
&mut self,
container: &WapmContainer,
cmd: &str,
) -> Result<Self::Output, Box<dyn StdError>> {
let path = format!("{}", container.webc.path.display());
let command_to_exec =
container
.webc
.webc
.manifest
.commands
.get(cmd)
.ok_or(anyhow::anyhow!(
"{path}: command {cmd:?} not found in manifest"
))?;

let _path = format!("{}", container.webc.path.display());

match self.can_run_command(cmd, command_to_exec) {
Ok(true) => {}
Ok(false) => {
return Err(Box::new(webc::Error(format!(
"Cannot run command {cmd:?} with runner {:?}",
command_to_exec.runner
))));
}
Err(e) => {
return Err(Box::new(webc::Error(format!(
"Cannot run command {cmd:?} with runner {:?}: {e}",
command_to_exec.runner
))));
}
}

self.run_command(cmd, command_to_exec, container)
}
}
Loading

0 comments on commit acc5ac1

Please sign in to comment.