Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC Host yielding API #2219

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,948 changes: 0 additions & 2,948 deletions Cargo.lock

This file was deleted.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ default = [
"emscripten",
"middlewares",
]
async = ["wasmer/async"]
engine = []
jit = [
"wasmer-engine-jit",
Expand Down
3 changes: 2 additions & 1 deletion examples/tunables_limit_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,12 @@ impl<T: Tunables> Tunables for LimitingTunables<T> {
ty: &MemoryType,
style: &MemoryStyle,
vm_definition_location: NonNull<VMMemoryDefinition>,
src_memory: Option<&dyn vm::Memory>,
) -> Result<Arc<dyn vm::Memory>, MemoryError> {
let adjusted = self.adjust_memory(ty);
self.validate_memory(&adjusted)?;
self.base
.create_vm_memory(&adjusted, style, vm_definition_location)
.create_vm_memory(&adjusted, style, vm_definition_location, src_memory)
}

/// Create a table owned by the host given a [`TableType`] and a [`TableStyle`].
Expand Down
9 changes: 9 additions & 0 deletions lib/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ wat = { version = "1.0", optional = true }
thiserror = "1.0"
more-asserts = "0.2"
target-lexicon = { version = "0.11", default-features = false }
async-wormhole = { version="0.3", optional=true }
switcheroo = { version="0.2", optional=true }
loupe = "0.1"

[target.'cfg(target_os = "windows")'.dependencies]
Expand Down Expand Up @@ -75,6 +77,13 @@ llvm = [
"wasmer-compiler-llvm",
"compiler",
]
async = [
"async-wormhole",
"switcheroo",
"wasmer-derive/async",
"wasmer-vm/async",
"wasmer-engine/async",
]
# enables internal features used by the deprecated API.
deprecated = []
default-compiler = []
Expand Down
4 changes: 4 additions & 0 deletions lib/api/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ pub trait WasmerEnv: Clone + Send + Sync {
fn init_with_instance(&mut self, _instance: &Instance) -> Result<(), HostEnvInitError> {
Ok(())
}

#[cfg(feature = "async")]
/// Set the thread-specific yielder
fn set_yielder(&mut self, _: *const std::ffi::c_void) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably needs to be unsafe and have a # Safety section describing how to correctly call and/or implement this function

}

impl WasmerEnv for u8 {}
Expand Down
90 changes: 89 additions & 1 deletion lib/api/src/externals/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,53 @@ pub struct Function {
pub(crate) exported: ExportFunction,
}

#[cfg(feature = "async")]
fn build_export_function_metadata<Env>(
env: Env,
import_init_function_ptr: for<'a> fn(
&'a mut Env,
&'a crate::Instance,
) -> Result<(), crate::HostEnvInitError>,
host_env_set_yielder_fn: fn(*mut std::ffi::c_void, *const std::ffi::c_void),
) -> (*mut std::ffi::c_void, ExportFunctionMetadata)
where
Env: Clone + Sized + 'static + Send + Sync,
{
let import_init_function_ptr = Some(unsafe {
std::mem::transmute::<fn(_, _) -> Result<(), _>, fn(_, _) -> Result<(), _>>(
import_init_function_ptr,
)
});
let host_env_clone_fn: fn(*mut std::ffi::c_void) -> *mut std::ffi::c_void = |ptr| {
let env_ref: &Env = unsafe {
ptr.cast::<Env>()
.as_ref()
.expect("`ptr` to the environment is null when cloning it")
};
Box::into_raw(Box::new(env_ref.clone())) as _
};
let host_env_drop_fn: fn(*mut std::ffi::c_void) = |ptr| {
unsafe { Box::from_raw(ptr.cast::<Env>()) };
};
let env = Box::into_raw(Box::new(env)) as _;

// # Safety
// - All these functions work on all threads
// - The host env is `Send`.
let metadata = unsafe {
ExportFunctionMetadata::new(
env,
import_init_function_ptr,
host_env_clone_fn,
host_env_set_yielder_fn,
host_env_drop_fn,
)
};

(env, metadata)
}

#[cfg(not(feature = "async"))]
fn build_export_function_metadata<Env>(
env: Env,
import_init_function_ptr: for<'a> fn(
Expand Down Expand Up @@ -170,12 +217,19 @@ impl Function {
};
Box::into_raw(Box::new(duped_env)) as _
};

let host_env_drop_fn: fn(*mut c_void) = |ptr: *mut c_void| {
unsafe {
Box::from_raw(ptr as *mut VMDynamicFunctionContext<DynamicFunctionWithoutEnv>)
};
};

#[cfg(feature = "async")]
let host_env_set_yielder_fn = |_, _| {
//no-op
println!("No-op");
};

Self {
store: store.clone(),
definition: FunctionDefinition::Host(HostFunctionDefinition { has_env: false }),
Expand All @@ -189,6 +243,8 @@ impl Function {
host_env,
None,
host_env_clone_fn,
#[cfg(feature = "async")]
host_env_set_yielder_fn,
host_env_drop_fn,
)
},
Expand Down Expand Up @@ -270,10 +326,27 @@ impl Function {
Env::init_with_instance(&mut *env.ctx.env, instance)
};

#[cfg(feature = "async")]
let (host_env, metadata) = {
let host_env_set_yielder_fn = |env_ptr, yielder_ptr| {
let env: &mut Env = unsafe { std::mem::transmute(env_ptr) };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's nice to be super explicit about what exactly is being transmuted from what to what to make bugs and intent more obvious. I'd prefer all transmute being transmute::<A,B> with explicit types whenever possible.

env.set_yielder(yielder_ptr);
};

build_export_function_metadata::<VMDynamicFunctionContext<DynamicFunctionWithEnv<Env>>>(
dynamic_ctx,
import_init_function_ptr,
host_env_set_yielder_fn,
)
};

#[cfg(not(feature = "async"))]
let (host_env, metadata) = build_export_function_metadata::<
VMDynamicFunctionContext<DynamicFunctionWithEnv<Env>>,
>(dynamic_ctx, import_init_function_ptr);

// We don't yet have the address with the Wasm ABI signature.

// We don't yet have the address with the Wasm ABI signature.
// The engine linker will replace the address with one pointing to a
// generated dynamic trampoline.
Expand Down Expand Up @@ -387,6 +460,21 @@ impl Function {
let function = inner::Function::<Args, Rets>::new(func);
let address = function.address();

#[cfg(feature = "async")]
let (host_env, metadata) = {
let host_env_set_yielder_fn = |env_ptr, yielder_ptr| {
let env: &mut Env = unsafe { std::mem::transmute(env_ptr) };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

env.set_yielder(yielder_ptr);
};

build_export_function_metadata::<Env>(
env,
Env::init_with_instance,
host_env_set_yielder_fn,
)
};

#[cfg(not(feature = "async"))]
let (host_env, metadata) =
build_export_function_metadata::<Env>(env, Env::init_with_instance);

Expand Down Expand Up @@ -484,7 +572,7 @@ impl Function {
&self.store
}

fn call_wasm(
pub(crate) fn call_wasm(
&self,
func: &WasmFunctionDefinition,
params: &[Val],
Expand Down
6 changes: 6 additions & 0 deletions lib/api/src/externals/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ mod global;
mod memory;
mod table;

#[cfg(feature = "async")]
mod yielder;

pub use self::function::{
FromToNativeWasmType, Function, HostFunction, WasmTypeList, WithEnv, WithoutEnv,
};
Expand All @@ -13,6 +16,9 @@ pub use self::global::Global;
pub use self::memory::Memory;
pub use self::table::Table;

#[cfg(feature = "async")]
pub use self::yielder::Yielder;

use crate::exports::{ExportError, Exportable};
use crate::store::{Store, StoreObject};
use crate::ExternType;
Expand Down
28 changes: 28 additions & 0 deletions lib/api/src/externals/yielder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use async_wormhole::AsyncYielder;

use crate::{RuntimeError, Val};

/// Wrapper around `async-wormhole`'s yielder
#[derive(Clone)]
pub struct Yielder {
inner: *const std::ffi::c_void,
}

impl Yielder {
/// Create a new instance from a raw pointer to a yielder
pub fn new(inner: *const std::ffi::c_void) -> Self {
Self { inner }
}

/// Get the `AsyncYielder`
pub fn get(&self) -> &mut AsyncYielder<Result<Box<[Val]>, RuntimeError>> {
let yielder: &mut AsyncYielder<Result<Box<[Val]>, RuntimeError>> =
unsafe { std::mem::transmute(self.inner) };

yielder
}
}

//FIXME
unsafe impl Send for Yielder {}
unsafe impl Sync for Yielder {}
78 changes: 78 additions & 0 deletions lib/api/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ impl Instance {
pub fn new(module: &Module, resolver: &dyn Resolver) -> Result<Self, InstantiationError> {
let store = module.store();
let handle = module.instantiate(resolver)?;

let exports = module
.exports()
.map(|export| {
Expand Down Expand Up @@ -155,6 +156,83 @@ impl Instance {
&self.module
}

///TODO
#[cfg(feature = "async")]
pub async fn call_with_stack<Stack: switcheroo::stack::Stack + Unpin>(
&self,
func_name: &str,
stack: Stack,
) -> Result<Box<[crate::Val]>, RuntimeError> {
use crate::externals::function::FunctionDefinition;
use crate::Val;

let mut task = async_wormhole::AsyncWormhole::new(
stack,
|yielder| -> Result<Box<[crate::Val]>, RuntimeError> {
let yielder_ptr: *mut std::ffi::c_void = unsafe { std::mem::transmute(&yielder) };

let func = self
.exports
.get_function(func_name)
.expect("No such function");
{
let hdl = self.handle.lock().unwrap();
hdl.set_yielder(yielder_ptr);
}

let params = &[];

let mut results = vec![Val::null(); func.result_arity()];

match &func.definition {
FunctionDefinition::Wasm(wasm) => {
let res = func.call_wasm(&wasm, params, &mut results);

if let Err(e) = res {
return Err(e);
}
}
_ => unimplemented!("The function definition isn't supported for the moment"),
}

Ok(results.into_boxed_slice())
},
)
.expect("Failed to create async function call");

{
use std::cell::Cell;
use wasmer_vm::{take_tls, restore_tls};

let ptr: u64 = {
let ptr = take_tls().into_inner();
unsafe{ std::mem::transmute(ptr) }
};

let tls_store = Mutex::new((false, ptr));

task.set_pre_post_poll(move || {
let mut tls_store = tls_store.lock().unwrap();

if tls_store.0 {
let ptr = take_tls().into_inner();
tls_store.1 = unsafe{ std::mem::transmute(ptr) };
tls_store.0 = false;
} else {
let mut value = 0;
std::mem::swap(&mut value, &mut tls_store.1);

let value = unsafe{ std::mem::transmute(value) };
restore_tls(Cell::new(value));

tls_store.0 = true;
}
});
}

task.await
}

/// Returns the [`Store`] where the `Instance` belongs.
pub fn store(&self) -> &Store {
self.module.store()
Expand Down
2 changes: 2 additions & 0 deletions lib/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ pub mod internals {

pub use crate::env::{HostEnvInitError, LazyInit, WasmerEnv};
pub use crate::exports::{ExportError, Exportable, Exports, ExportsIterator};
#[cfg(feature = "async")]
pub use crate::externals::Yielder;
pub use crate::externals::{
Extern, FromToNativeWasmType, Function, Global, HostFunction, Memory, Table, WasmTypeList,
};
Expand Down
1 change: 1 addition & 0 deletions lib/compiler-llvm/src/trampoline/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl FuncTrampoline {
trampoline_func
.as_global_value()
.set_dll_storage_class(DLLStorageClass::Export);

self.generate_trampoline(trampoline_func, ty, &callee_attrs, &self.ctx, &intrinsics)?;

if let Some(ref callbacks) = config.callbacks {
Expand Down
4 changes: 4 additions & 0 deletions lib/derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ proc-macro-error = "1.0.0"
[dev-dependencies]
wasmer = { path = "../api", version = "1.0.2" }
compiletest_rs = "0.6"

[features]
default = []
async = []
Loading