Skip to content
Open
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
35 changes: 26 additions & 9 deletions benches/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,15 @@ impl IsAsync {
}
}

fn engines() -> Vec<(Engine, IsAsync)> {
fn engines(concurrency_support: bool) -> Vec<(Engine, IsAsync)> {
let mut config = Config::new();

#[cfg(feature = "component-model")]
config.wasm_component_model(true);

#[cfg(feature = "component-model-async")]
config.concurrency_support(concurrency_support);

let mut pool = PoolingAllocationConfig::default();
if std::env::var("WASMTIME_TEST_FORCE_MPK").is_ok() {
pool.memory_protection_keys(Enabled::Yes);
Expand Down Expand Up @@ -79,7 +82,7 @@ fn engines() -> Vec<(Engine, IsAsync)> {
/// Benchmarks the overhead of calling WebAssembly from the host in various
/// configurations.
fn host_to_wasm(c: &mut Criterion) {
for (engine, is_async) in engines() {
for (engine, is_async) in engines(false) {
let mut store = Store::new(&engine, ());
let module = Module::new(
&engine,
Expand Down Expand Up @@ -249,7 +252,7 @@ fn wasm_to_host(c: &mut Criterion) {

)"#;

for (engine, is_async) in engines() {
for (engine, is_async) in engines(false) {
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, module).unwrap();

Expand Down Expand Up @@ -548,8 +551,22 @@ mod component {
tuples!(A B);
tuples!(A B C);

fn engines() -> Vec<(String, Engine, IsAsync)> {
let mut result: Vec<_> = super::engines(false)
.into_iter()
.map(|(e, a)| ("no-concurrent".to_string(), e, a))
.collect();
#[cfg(feature = "component-model-async")]
result.extend(
super::engines(true)
.into_iter()
.map(|(e, a)| ("concurrent".to_string(), e, a)),
);
result
}

fn host_to_wasm(c: &mut Criterion) {
for (engine, is_async) in engines() {
for (concurrent, engine, is_async) in engines() {
let mut store = Store::new(&engine, ());

let component = Component::new(
Expand Down Expand Up @@ -599,12 +616,12 @@ mod component {
};

// Bench once without any call hooks configured
let name = format!("{}/no-hook", is_async.desc());
let name = format!("{}/{}/no-hook", concurrent, is_async.desc());
bench_calls(&mut c.benchmark_group(&name), &mut store);

// Bench again with a "call hook" enabled
store.call_hook(|_, _| Ok(()));
let name = format!("{}/hook-sync", is_async.desc());
let name = format!("{}/{}/hook-sync", concurrent, is_async.desc());
bench_calls(&mut c.benchmark_group(&name), &mut store);
}
}
Expand Down Expand Up @@ -738,19 +755,19 @@ mod component {
)
"#;

for (engine, is_async) in engines() {
for (concurrent, engine, is_async) in engines() {
let mut store = Store::new(&engine, ());
let component = component::Component::new(&engine, module).unwrap();

bench_calls(
&mut c.benchmark_group(&format!("{}/no-hook", is_async.desc())),
&mut c.benchmark_group(&format!("{}/{}/no-hook", concurrent, is_async.desc())),
&mut store,
&component,
is_async,
);
store.call_hook(|_, _| Ok(()));
bench_calls(
&mut c.benchmark_group(&format!("{}/hook-sync", is_async.desc())),
&mut c.benchmark_group(&format!("{}/{}/hook-sync", concurrent, is_async.desc())),
&mut store,
&component,
is_async,
Expand Down
15 changes: 15 additions & 0 deletions crates/c-api/include/wasmtime/component/func.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ typedef struct wasmtime_component_func {

/// Private internal wasmtime information.
uint32_t __private2;

/// Private internal wasmtime information.
uint32_t __private3;

/// Private internal wasmtime information.
uint32_t __private4;

/// Private internal wasmtime information.
uint8_t __private5;

/// Private internal wasmtime information.
void *__private6;

/// Private internal wasmtime information.
void *__private7;
} wasmtime_component_func_t;

/// \brief Returns the type of this function.
Expand Down
4 changes: 2 additions & 2 deletions crates/wasmtime/src/runtime/component/concurrent/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ impl Func {
})
},
move |func, store, results| {
let max_flat = if func.abi_async(store) {
let max_flat = if func.abi_async() {
MAX_FLAT_PARAMS
} else {
MAX_FLAT_RESULTS
Expand Down Expand Up @@ -431,7 +431,7 @@ where
} else {
1
};
let max_results = if self.func().abi_async(store.0) {
let max_results = if self.func().abi_async() {
MAX_FLAT_PARAMS
} else {
MAX_FLAT_RESULTS
Expand Down
125 changes: 80 additions & 45 deletions crates/wasmtime/src/runtime/component/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::component::types::ComponentFunc;
use crate::component::values::Val;
use crate::prelude::*;
use crate::runtime::vm::component::{ComponentInstance, InstanceFlags};
use crate::runtime::vm::{Export, VMFuncRef};
use crate::runtime::vm::{Export, SendSyncPtr, VMFuncRef};
use crate::store::StoreOpaque;
use crate::{AsContext, AsContextMut, StoreContextMut, ValRaw};
use core::mem::{self, MaybeUninit};
Expand Down Expand Up @@ -33,24 +33,80 @@ pub use self::typed::*;
#[repr(C)] // here for the C API.
pub struct Func {
instance: Instance,
index: ExportIndex,

/// The component type index of this lifted function.
ty: TypeFuncIndex,

/// The index of the canonical `options` for this lifted function.
options: OptionsIndex,

/// Whether this lifted function uses the async canonical ABI.
abi_async: bool,

/// The resolved core `VMFuncRef` for this lifted function, whose lifetime
/// is bound to the `Store` this `Func` belongs to.
///
/// Note that this field has an `unsafe_*` prefix to discourage use of it.
/// This is only safe to read/use if the store that owns `instance`
/// (identified by `instance.id().store_id()`) is in scope. Use the
/// `self.lifted_core_func()` method instead of this field to perform this
/// check.
unsafe_func_ref: SendSyncPtr<VMFuncRef>,

/// The resolved core `VMFuncRef` for this function's `post-return`, if any.
///
/// Same store-lifetime rules as `unsafe_func_ref`: only valid to read while
/// the owning store is in scope. Read via [`Func::post_return_core_func`],
/// which validates the store id first.
post_return_func_ref: Option<SendSyncPtr<VMFuncRef>>,
}

// Double-check that the C representation in `component/instance.h` matches our
// Double-check that the C representation in `component/func.h` matches our
// in-Rust representation here in terms of size/alignment/etc.
const _: () = {
#[repr(C)]
struct T(u64, u32);
#[repr(C)]
struct C(T, u32);
struct C(T, u32, u32, u32, bool, *mut u8, *mut u8);
assert!(core::mem::size_of::<C>() == core::mem::size_of::<Func>());
assert!(core::mem::align_of::<C>() == core::mem::align_of::<Func>());
assert!(core::mem::offset_of!(Func, instance) == 0);
};

impl Func {
pub(crate) fn from_lifted_func(instance: Instance, index: ExportIndex) -> Func {
Func { instance, index }
pub(crate) fn from_lifted_func(
store: &mut StoreOpaque,
instance: Instance,
index: ExportIndex,
) -> Func {
let def = {
let vminstance = instance.id().get(store);
let (_ty, def, _options) = vminstance.component().export_lifted_function(index);
def.clone()
};
let unsafe_func_ref = match instance.lookup_vmdef(store, &def) {
Export::Function(f) => f.vm_func_ref(store),
_ => unreachable!(),
}
.into();

let vminstance = instance.id().get(store);
let component = vminstance.component();
let (ty, _def, options) = component.export_lifted_function(index);
let raw_options = &component.env_component().options[options];
let abi_async = raw_options.async_;
let post_return_func_ref = raw_options
.post_return
.map(|i| SendSyncPtr::from(vminstance.runtime_post_return(i)));

Func {
instance,
ty,
options,
abi_async,
unsafe_func_ref,
post_return_func_ref,
}
}

/// Attempt to cast this [`Func`] to a statically typed [`TypedFunc`] with
Expand Down Expand Up @@ -167,7 +223,7 @@ impl Func {
Return: ComponentNamedList + Lift,
{
let cx = InstanceType::new(instance.unwrap_or_else(|| self.instance.id().get(store)));
let ty = &cx.types[self.ty_index(store)];
let ty = &cx.types[self.ty];

Params::typecheck(&InterfaceType::Tuple(ty.params), &cx)
.context("type mismatch with parameters")?;
Expand All @@ -184,14 +240,7 @@ impl Func {

fn ty_(&self, store: &StoreOpaque) -> ComponentFunc {
let cx = InstanceType::new(self.instance.id().get(store));
let ty = self.ty_index(store);
ComponentFunc::from(ty, &cx)
}

fn ty_index(&self, store: &StoreOpaque) -> TypeFuncIndex {
let instance = self.instance.id().get(store);
let (ty, _, _) = instance.component().export_lifted_function(self.index);
ty
ComponentFunc::from(self.ty, &cx)
}

/// Invokes this function with the `params` given and returns the result.
Expand Down Expand Up @@ -313,7 +362,7 @@ impl Func {

self.check_params_results(store.as_context_mut(), params, results)?;

if self.abi_async(store.0) {
if self.abi_async() {
unreachable!(
"async-lifted exports should have failed validation \
when `component-model-async` feature disabled"
Expand Down Expand Up @@ -361,31 +410,20 @@ impl Func {
self.post_return_impl(store, post_return_arg)
}

pub(crate) fn lifted_core_func(&self, store: &mut StoreOpaque) -> NonNull<VMFuncRef> {
let def = {
let instance = self.instance.id().get(store);
let (_ty, def, _options) = instance.component().export_lifted_function(self.index);
def.clone()
};
match self.instance.lookup_vmdef(store, &def) {
Export::Function(f) => f.vm_func_ref(store),
_ => unreachable!(),
}
#[inline]
pub(crate) fn lifted_core_func(&self, store: &StoreOpaque) -> NonNull<VMFuncRef> {
self.instance.id().assert_belongs_to(store.id());
self.unsafe_func_ref.as_non_null()
}

#[inline]
pub(crate) fn post_return_core_func(&self, store: &StoreOpaque) -> Option<NonNull<VMFuncRef>> {
let instance = self.instance.id().get(store);
let component = instance.component();
let (_ty, _def, options) = component.export_lifted_function(self.index);
let post_return = component.env_component().options[options].post_return;
post_return.map(|i| instance.runtime_post_return(i))
self.instance.id().assert_belongs_to(store.id());
self.post_return_func_ref.map(|p| p.as_non_null())
}

pub(crate) fn abi_async(&self, store: &StoreOpaque) -> bool {
let instance = self.instance.id().get(store);
let component = instance.component();
let (_ty, _def, options) = component.export_lifted_function(self.index);
component.env_component().options[options].async_
pub(crate) fn abi_async(&self) -> bool {
self.abi_async
}

pub(crate) fn abi_info<'a>(
Expand All @@ -398,13 +436,11 @@ impl Func {
&'a CanonicalOptions,
) {
let vminstance = self.instance.id().get(store);
let component = vminstance.component();
let (ty, _def, options_index) = component.export_lifted_function(self.index);
let raw_options = &component.env_component().options[options_index];
let raw_options = &vminstance.component().env_component().options[self.options];
(
options_index,
self.options,
vminstance.instance_flags(raw_options.instance),
ty,
self.ty,
raw_options,
)
}
Expand Down Expand Up @@ -446,7 +482,7 @@ impl Func {
bail!(crate::Trap::CannotEnterComponent);
}

let async_type = self.abi_async(store.0);
let async_type = self.abi_async();
store.0.enter_guest_sync_call(None, async_type, instance)?;

#[repr(C)]
Expand Down Expand Up @@ -550,12 +586,11 @@ impl Func {
pub(crate) fn post_return_impl(&self, mut store: impl AsContextMut, arg: ValRaw) -> Result<()> {
let mut store = store.as_context_mut();

let index = self.index;
let vminstance = self.instance.id().get(store.0);
let component = vminstance.component();
let (_ty, _def, options) = component.export_lifted_function(index);
let post_return = self.post_return_core_func(store.0);
let flags = vminstance.instance_flags(component.env_component().options[options].instance);
let flags =
vminstance.instance_flags(component.env_component().options[self.options].instance);

unsafe {
call_post_return(&mut store, post_return, arg, flags)?;
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/component/func/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ where
fn call_impl(&self, mut store: impl AsContextMut, params: Params) -> Result<Return> {
let mut store = store.as_context_mut();

if self.func.abi_async(store.0) {
if self.func.abi_async() {
bail!("must enable the `component-model-async` feature to call async-lifted exports")
}

Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/component/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ impl Instance {
}

// And package up the indices!
Some(Func::from_lifted_func(*self, index))
Some(Func::from_lifted_func(store, *self, index))
}

/// Looks up an exported [`Func`] value by name and with its type.
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/runtime/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ pub struct Func {
/// Note that this field has an `unsafe_*` prefix to discourage use of it.
/// This is only safe to read/use if `self.store` is validated to belong to
/// an ambiently provided `StoreOpaque` or similar. Use the
/// `self.func_ref()` method instead of this field to perform this check.
/// `self.vm_func_ref()` method instead of this field to perform this check.
unsafe_func_ref: SendSyncPtr<VMFuncRef>,
}

Expand Down
Loading