#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/54989751?s=200&v=4")]
#[macro_use]
extern crate log;
mod callbacks;
pub mod errors;
mod modreg;
pub mod prelude;
pub type Result<T> = std::result::Result<T, errors::Error>;
use crate::modreg::ModuleRegistry;
use std::sync::atomic::{AtomicU64, Ordering};
use wasmtime::Func;
use wasmtime::Instance;
use std::cell::RefCell;
use crate::callbacks::Callback;
use crate::callbacks::ModuleState;
use std::rc::Rc;
use wasmtime::*;
macro_rules! call {
($func:expr, $($p:expr),*) => {
match $func.borrow().call(&[$($p.into()),*]) {
Ok(result) => {
let result: i32 = result[0].i32().unwrap();
result
}
Err(e) => {
error!("Failure invoking guest module handler: {:?}", e);
0
}
}
}
}
static GLOBAL_MODULE_COUNT: AtomicU64 = AtomicU64::new(1);
const HOST_NAMESPACE: &str = "wapc";
const HOST_CONSOLE_LOG: &str = "__console_log";
const HOST_CALL: &str = "__host_call";
const GUEST_REQUEST_FN: &str = "__guest_request";
const HOST_RESPONSE_FN: &str = "__host_response";
const HOST_RESPONSE_LEN_FN: &str = "__host_response_len";
const GUEST_RESPONSE_FN: &str = "__guest_response";
const GUEST_ERROR_FN: &str = "__guest_error";
const HOST_ERROR_FN: &str = "__host_error";
const HOST_ERROR_LEN_FN: &str = "__host_error_len";
const GUEST_CALL: &str = "__guest_call";
const WASI_UNSTABLE_NAMESPACE: &str = "wasi_unstable";
const WASI_SNAPSHOT_PREVIEW1_NAMESPACE: &str = "wasi_snapshot_preview1";
type HostCallback = dyn Fn(u64, &str, &str, &str, &[u8]) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error>>
+ Sync
+ Send
+ 'static;
type LogCallback = dyn Fn(u64, &str) -> std::result::Result<(), Box<dyn std::error::Error>>
+ Sync
+ Send
+ 'static;
#[derive(Debug, Clone)]
struct Invocation {
operation: String,
msg: Vec<u8>,
}
impl Invocation {
fn new(op: &str, msg: Vec<u8>) -> Invocation {
Invocation {
operation: op.to_string(),
msg,
}
}
}
#[derive(Debug, Default)]
pub struct WasiParams {
argv: Vec<String>,
map_dirs: Vec<(String, String)>,
env_vars: Vec<(String, String)>,
preopened_dirs: Vec<String>,
}
impl WasiParams {
pub fn new(
argv: Vec<String>,
map_dirs: Vec<(String, String)>,
env_vars: Vec<(String, String)>,
preopened_dirs: Vec<String>,
) -> Self {
WasiParams {
argv,
map_dirs,
preopened_dirs,
env_vars,
}
}
}
pub struct WapcHost {
state: Rc<RefCell<ModuleState>>,
instance: Rc<RefCell<Option<Instance>>>,
wasidata: Option<WasiParams>,
}
impl WapcHost {
pub fn new<F>(host_callback: F, buf: &[u8], wasi: Option<WasiParams>) -> Result<Self>
where
F: Fn(
u64,
&str,
&str,
&str,
&[u8],
) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error>>
+ Sync
+ Send
+ 'static,
{
let id = GLOBAL_MODULE_COUNT.fetch_add(1, Ordering::SeqCst);
let state = Rc::new(RefCell::new(ModuleState::new(id, Box::new(host_callback))));
let instance_ref = Rc::new(RefCell::new(None));
let instance =
WapcHost::instance_from_buffer(buf, &wasi, instance_ref.clone(), state.clone())?;
instance_ref.replace(Some(instance));
let mh = WapcHost {
state,
instance: instance_ref,
wasidata: wasi,
};
mh.initialize()?;
Ok(mh)
}
pub fn new_with_logger<F, G>(
host_callback: F,
buf: &[u8],
logger: G,
wasi: Option<WasiParams>,
) -> Result<Self>
where
F: Fn(
u64,
&str,
&str,
&str,
&[u8],
) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error>>
+ Sync
+ Send
+ 'static,
G: Fn(u64, &str) -> std::result::Result<(), Box<dyn std::error::Error>>
+ Sync
+ Send
+ 'static,
{
let id = GLOBAL_MODULE_COUNT.fetch_add(1, Ordering::SeqCst);
let state = Rc::new(RefCell::new(ModuleState::new_with_logger(
id,
Box::new(host_callback),
Box::new(logger),
)));
let instance_ref = Rc::new(RefCell::new(None));
let instance =
WapcHost::instance_from_buffer(buf, &wasi, instance_ref.clone(), state.clone())?;
instance_ref.replace(Some(instance));
let mh = WapcHost {
state,
instance: instance_ref,
wasidata: wasi,
};
mh.initialize()?;
Ok(mh)
}
pub fn id(&self) -> u64 {
self.state.borrow().id
}
pub fn call(&mut self, op: &str, payload: &[u8]) -> Result<Vec<u8>> {
let inv = Invocation::new(op, payload.to_vec());
{
let mut state = self.state.borrow_mut();
state.guest_response = None;
state.guest_request = Some((inv).clone());
state.guest_error = None;
}
let callresult: i32 = call!(
self.guest_call_fn()?,
inv.operation.len() as i32,
inv.msg.len() as i32
);
if callresult == 0 {
match self.state.borrow().guest_error {
Some(ref s) => Err(errors::new(errors::ErrorKind::GuestCallFailure(s.clone()))),
None => Err(errors::new(errors::ErrorKind::GuestCallFailure(
"No error message set for call failure".to_string(),
))),
}
} else {
match self.state.borrow().guest_response {
Some(ref e) => Ok(e.clone()),
None => match self.state.borrow().guest_error {
Some(ref s) => Err(errors::new(errors::ErrorKind::GuestCallFailure(s.clone()))),
None => Err(errors::new(errors::ErrorKind::GuestCallFailure(
"No error message OR response set for call success".to_string(),
))),
},
}
}
}
pub fn replace_module(&self, module: &[u8]) -> Result<()> {
info!(
"HOT SWAP - Replacing existing WebAssembly module with new buffer, {} bytes",
module.len()
);
let state = self.state.clone();
let new_instance =
WapcHost::instance_from_buffer(module, &self.wasidata, self.instance.clone(), state)?;
self.instance.borrow_mut().replace(new_instance);
self.initialize()
}
fn instance_from_buffer(
buf: &[u8],
wasi: &Option<WasiParams>,
instance_ref: Rc<RefCell<Option<Instance>>>,
state: Rc<RefCell<ModuleState>>,
) -> Result<Instance> {
let engine = Engine::default();
let store = Store::new(&engine);
let module = Module::new(&store, buf).unwrap();
let d = WasiParams::default();
let wasi = match wasi {
Some(w) => w,
None => &d,
};
let preopen_dirs =
modreg::compute_preopen_dirs(&wasi.preopened_dirs, &wasi.map_dirs).unwrap();
let argv = vec![];
let module_registry =
ModuleRegistry::new(&store, &preopen_dirs, &argv, &wasi.env_vars).unwrap();
let imports = arrange_imports(
&module,
state.clone(),
instance_ref.clone(),
store.clone(),
&module_registry,
);
Ok(wasmtime::Instance::new(&module, imports?.as_slice()).unwrap())
}
fn guest_call_fn(&self) -> Result<HostRef<Func>> {
if let Some(ext) = self
.instance
.borrow()
.as_ref()
.unwrap()
.get_export(GUEST_CALL)
{
Ok(HostRef::new(ext.func().unwrap().clone()))
} else {
Err(errors::new(errors::ErrorKind::GuestCallFailure(
"Guest module did not export __guest_call function!".to_string(),
)))
}
}
fn initialize(&self) -> Result<()> {
if let Some(ext) = self
.instance
.borrow()
.as_ref()
.unwrap()
.get_export("_start")
{
ext.func().unwrap().call(&[]).map(|_| ()).map_err(|_err| {
errors::new(errors::ErrorKind::GuestCallFailure(
"Error invoking _start function!".to_string(),
))
})
} else {
Ok(())
}
}
}
fn arrange_imports(
module: &Module,
state: Rc<RefCell<ModuleState>>,
instance: Rc<RefCell<Option<Instance>>>,
store: Store,
mod_registry: &ModuleRegistry,
) -> Result<Vec<Extern>> {
Ok(module
.imports()
.iter()
.filter_map(|imp| {
if let ExternType::Func(_) = imp.ty() {
match imp.module() {
HOST_NAMESPACE => Some(callback_for_import(
imp.name(),
state.clone(),
instance.clone(),
store.clone(),
)),
WASI_UNSTABLE_NAMESPACE => {
let f = Extern::from(
mod_registry
.wasi_unstable
.get_export(imp.name())
.unwrap()
.clone(),
);
Some(f)
}
WASI_SNAPSHOT_PREVIEW1_NAMESPACE => {
let f: Extern = Extern::from(
mod_registry
.wasi_snapshot_preview1
.get_export(imp.name())
.unwrap()
.clone(),
);
Some(f)
}
other => panic!("import module `{}` was not found", other),
}
} else {
None
}
})
.collect())
}
fn callback_for_import(
import: &str,
state: Rc<RefCell<ModuleState>>,
instance_ref: Rc<RefCell<Option<Instance>>>,
store: Store,
) -> Extern {
match import {
HOST_CONSOLE_LOG => {
callbacks::ConsoleLog::as_func(state.clone(), instance_ref.clone(), store).into()
}
HOST_CALL => {
callbacks::HostCall::as_func(state.clone(), instance_ref.clone(), store).into()
}
GUEST_REQUEST_FN => {
callbacks::GuestRequest::as_func(state.clone(), instance_ref.clone(), store).into()
}
HOST_RESPONSE_FN => {
callbacks::HostResponse::as_func(state.clone(), instance_ref.clone(), store).into()
}
HOST_RESPONSE_LEN_FN => {
callbacks::HostResponseLen::as_func(state.clone(), instance_ref.clone(), store).into()
}
GUEST_RESPONSE_FN => {
callbacks::GuestResponse::as_func(state.clone(), instance_ref.clone(), store).into()
}
GUEST_ERROR_FN => {
callbacks::GuestError::as_func(state.clone(), instance_ref.clone(), store).into()
}
HOST_ERROR_FN => {
callbacks::HostError::as_func(state.clone(), instance_ref.clone(), store).into()
}
HOST_ERROR_LEN_FN => {
callbacks::HostErrorLen::as_func(state.clone(), instance_ref.clone(), store).into()
}
_ => unreachable!(),
}
}