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
4 changes: 4 additions & 0 deletions crates/environ/src/component/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,10 @@ pub struct TypeComponentInstance {
pub struct TypeFunc {
/// Whether or not this is an async function.
pub async_: bool,
/// Whether any parameter (transitively) contains a `borrow` handle,
/// letting the runtime skip borrow-scope tracking when lending is
/// impossible. (Borrows are only valid in parameters, not results.)
pub contains_borrow: bool,
/// Names of parameters.
pub param_names: Vec<String>,
/// Parameters to the function represented as a tuple.
Expand Down
6 changes: 4 additions & 2 deletions crates/environ/src/component/types_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,16 +230,18 @@ impl ComponentTypesBuilder {
.params
.iter()
.map(|(_name, ty)| self.valtype(types, ty))
.collect::<Result<_>>()?;
.collect::<Result<Vec<_>>>()?;
let results = ty
.result
.iter()
.map(|ty| self.valtype(types, ty))
.collect::<Result<_>>()?;
let params = self.new_tuple_type(params);
let contains_borrow = params.iter().any(|ty| self.ty_contains_borrow_resource(ty));
let params = self.new_tuple_type(params.into());
let results = self.new_tuple_type(results);
let ty = TypeFunc {
async_: ty.async_,
contains_borrow,
param_names,
params,
results,
Expand Down
26 changes: 22 additions & 4 deletions crates/wasmtime/src/runtime/component/concurrent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1812,9 +1812,18 @@ impl StoreOpaque {
/// relatively expensive table manipulations. This would ideally be
/// optimized to avoid the full allocation of a `HostTask` in at least some
/// situations.
pub(crate) fn host_task_create(&mut self) -> Result<Option<TableId<HostTask>>> {
#[inline]
pub(crate) fn host_task_create(
&mut self,
track_scope: bool,
) -> Result<Option<TableId<HostTask>>> {
if !self.concurrency_support() {
self.enter_call_not_concurrent()?;
// Resource-borrow scope tracking is only needed when the
// function's parameters can actually contain `borrow` handles;
// this is precomputed per function type.
if track_scope {
self.enter_call_not_concurrent()?;
}
return Ok(None);
}
let caller = self.current_guest_thread()?;
Expand All @@ -1830,6 +1839,7 @@ impl StoreOpaque {
/// This is used to update the current thread annotations within the store
/// to ensure that it reflects the guest task, not the host task, since
/// lowering may execute guest code.
#[inline]
pub fn host_task_reenter_caller(&mut self) -> Result<()> {
if !self.concurrency_support() {
return Ok(());
Expand All @@ -1846,14 +1856,21 @@ impl StoreOpaque {
/// Note that this isn't invoked when the host is invoked asynchronously and
/// the host isn't complete yet. In that situation the host task persists
/// and will be cleaned up separately in `subtask_drop`
pub(crate) fn host_task_delete(&mut self, task: Option<TableId<HostTask>>) -> Result<()> {
#[inline]
pub(crate) fn host_task_delete(
&mut self,
task: Option<TableId<HostTask>>,
track_scope: bool,
) -> Result<()> {
match task {
Some(task) => {
log::trace!("delete host task {task:?}");
self.concurrent_state_mut()?.delete(task)?;
}
None => {
self.exit_call_not_concurrent();
if track_scope {
self.exit_call_not_concurrent();
}
}
}
Ok(())
Expand Down Expand Up @@ -2341,6 +2358,7 @@ impl StoreOpaque {

/// Used by `ResourceTables` to record the scope of a borrow to get undone
/// in the future.
#[inline]
pub(crate) fn current_scope_id(&mut self) -> Result<Option<u32>> {
if !self.concurrency_support() {
return self.current_scope_id_not_concurrent();
Expand Down
14 changes: 10 additions & 4 deletions crates/wasmtime/src/runtime/component/concurrent_disabled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,22 @@ impl StoreOpaque {
Ok(self.exit_call_not_concurrent())
}

pub(crate) fn host_task_create(&mut self) -> Result<()> {
self.enter_call_not_concurrent()
pub(crate) fn host_task_create(&mut self, track_scope: bool) -> Result<()> {
if track_scope {
self.enter_call_not_concurrent()?;
}
Ok(())
}

pub(crate) fn host_task_reenter_caller(&mut self) -> Result<()> {
Ok(())
}

pub(crate) fn host_task_delete(&mut self, (): ()) -> Result<()> {
Ok(self.exit_call_not_concurrent())
pub(crate) fn host_task_delete(&mut self, (): (), track_scope: bool) -> Result<()> {
if track_scope {
self.exit_call_not_concurrent();
}
Ok(())
}

pub(crate) fn check_blocking(&mut self) -> crate::Result<()> {
Expand Down
34 changes: 28 additions & 6 deletions crates/wasmtime/src/runtime/component/func/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ where

/// "Rust" entrypoint after panic-handling infrastructure is set up and raw
/// arguments are translated to Rust types.
#[inline]
fn entrypoint(
&self,
mut store: StoreContextMut<'_, T>,
Expand All @@ -369,6 +370,9 @@ where
) -> Result<()> {
let vminstance = instance.id().get(store.0);
let async_ = vminstance.component().env_component().options[options].async_;
// Skip resource-borrow scope tracking when the signature makes
// lending impossible (precomputed at compile time).
let track_scope = vminstance.component().types()[ty].contains_borrow;

// If this is a synchronous-lower of a host-async function, then the
// guest is blocking. Test, in the context of the guest task, if that's
Expand All @@ -378,7 +382,7 @@ where
}

// Enter the host by pushing a `HostTask` into the concurrent state.
let host_task = store.0.host_task_create()?;
let host_task = store.0.host_task_create(track_scope)?;

let host_task_complete = if async_ {
#[cfg(feature = "component-model-async")]
Expand All @@ -391,7 +395,14 @@ where
when `component-model-async` feature disabled"
);
} else {
self.call_sync_lower(store.as_context_mut(), instance, ty, options, storage)?;
self.call_sync_lower(
store.as_context_mut(),
instance,
ty,
options,
storage,
track_scope,
)?;
true
};

Expand All @@ -401,7 +412,7 @@ where
// function transitively would have updated the current guest thread to
// the caller of this host function.
if host_task_complete {
store.0.host_task_delete(host_task)?;
store.0.host_task_delete(host_task, track_scope)?;
}

Ok(())
Expand All @@ -414,15 +425,21 @@ where
/// the `async` option when lowered. Note that the host function itself
/// can still be async, in which case this will block here waiting for it
/// to finish.
#[inline]
fn call_sync_lower(
&self,
mut store: StoreContextMut<'_, T>,
instance: Instance,
ty: TypeFuncIndex,
options: OptionsIndex,
storage: &mut [MaybeUninit<ValRaw>],
track_scope: bool,
) -> Result<()> {
let mut lift = LiftContext::new(store.0.store_opaque_mut(), options, instance)?;
let mut lift = if track_scope {
LiftContext::new(store.0.store_opaque_mut(), options, instance)?
} else {
LiftContext::new_without_scope(store.0.store_opaque_mut(), options, instance)?
};
let (params, rest) = self.load_params(&mut lift, ty, MAX_FLAT_PARAMS, storage)?;

let ret = match self.run(store.as_context_mut(), params) {
Expand Down Expand Up @@ -530,6 +547,7 @@ where
///
/// This will internally decide the ABI source of the parameters and use
/// `storage` appropriately.
#[inline]
fn load_params<'a>(
&self,
lift: &mut LiftContext<'_>,
Expand Down Expand Up @@ -565,15 +583,19 @@ where
}

/// Stores the result `ret` into `dst` which is calculated per the ABI.
#[inline]
fn lower_result_and_exit_call(
lower: &mut LowerContext<'_, T>,
ty: TypeFuncIndex,
ret: Option<R>,
dst: Destination<'_>,
) -> Result<()> {
// Before lowering below semantically ensure that the caller has dropped
// all of its borrows and such.
lower.validate_scope_exit()?;
// all of its borrows and such. Skipped when the signature cannot
// contain borrows (in which case no scope was entered either).
if lower.types[ty].contains_borrow {
lower.validate_scope_exit()?;
}

// At this point we're transitioning back to the caller task which means
// that the current task needs to be updated. This will restore the
Expand Down
28 changes: 27 additions & 1 deletion crates/wasmtime/src/runtime/component/func/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,10 +338,36 @@ impl<'a> LiftContext<'a> {
store: &'a mut StoreOpaque,
options: OptionsIndex,
instance_handle: Instance,
) -> Result<LiftContext<'a>> {
Self::new_impl(store, options, instance_handle, true)
}

/// Same as [`Self::new`] but without resolving the current resource
/// scope, for calls whose signature statically cannot contain `borrow`
/// handles (the only consumers of the scope id).
#[inline]
pub(crate) fn new_without_scope(
store: &'a mut StoreOpaque,
options: OptionsIndex,
instance_handle: Instance,
) -> Result<LiftContext<'a>> {
Self::new_impl(store, options, instance_handle, false)
}

#[inline]
fn new_impl(
store: &'a mut StoreOpaque,
options: OptionsIndex,
instance_handle: Instance,
want_scope: bool,
) -> Result<LiftContext<'a>> {
let store_id = store.id();
let hostcall_fuel = store.hostcall_fuel();
let current_scope_id = store.current_scope_id()?;
let current_scope_id = if want_scope {
store.current_scope_id()?
} else {
None
};
// From `&mut StoreOpaque` provided the goal here is to project out
// three different disjoint fields owned by the store: memory,
// `CallContexts`, and `HandleTable`. There's no native API for that
Expand Down
6 changes: 6 additions & 0 deletions crates/wasmtime/src/runtime/component/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ impl StoreComponentInstanceId {
/// # Panics
///
/// Panics if `self` does not belong to `store`.
#[inline]
pub(crate) fn get<'a>(&self, store: &'a StoreOpaque) -> &'a ComponentInstance {
self.assert_belongs_to(store.id());
store.component_instance(self.instance)
Expand All @@ -228,6 +229,7 @@ impl StoreComponentInstanceId {
/// # Panics
///
/// Panics if `self` does not belong to `store`.
#[inline]
pub(crate) fn get_mut<'a>(&self, store: &'a mut StoreOpaque) -> Pin<&'a mut ComponentInstance> {
self.from_data_get_mut(store.store_data_mut())
}
Expand Down Expand Up @@ -356,6 +358,7 @@ impl StoreOpaque {
support
}

#[inline]
pub(crate) fn lift_context_parts(
&mut self,
instance: Instance,
Expand Down Expand Up @@ -420,6 +423,7 @@ impl StoreOpaque {
))
}

#[inline]
pub(crate) fn enter_call_not_concurrent(&mut self) -> Result<()> {
let state = match &mut self.component_data_mut().task_state {
ComponentTaskState::NotConcurrent(state) => state,
Expand All @@ -430,6 +434,7 @@ impl StoreOpaque {
Ok(())
}

#[inline]
pub(crate) fn exit_call_not_concurrent(&mut self) {
let state = match &mut self.component_data_mut().task_state {
ComponentTaskState::NotConcurrent(state) => state,
Expand Down Expand Up @@ -459,6 +464,7 @@ impl StoreOpaque {
}
}

#[inline]
pub(crate) fn current_scope_id_not_concurrent(&mut self) -> Result<Option<u32>> {
match &mut self.component_data_mut().task_state {
ComponentTaskState::NotConcurrent(state) => match state.scopes.len().checked_sub(1) {
Expand Down
Loading