diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 56c2874fa8d6..9aefd48d8d75 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -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, /// Parameters to the function represented as a tuple. diff --git a/crates/environ/src/component/types_builder.rs b/crates/environ/src/component/types_builder.rs index a9822ea1afdc..a1b4da20885d 100644 --- a/crates/environ/src/component/types_builder.rs +++ b/crates/environ/src/component/types_builder.rs @@ -230,16 +230,18 @@ impl ComponentTypesBuilder { .params .iter() .map(|(_name, ty)| self.valtype(types, ty)) - .collect::>()?; + .collect::>>()?; let results = ty .result .iter() .map(|ty| self.valtype(types, ty)) .collect::>()?; - 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, diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index a5a34e481557..f2096ddcd38e 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -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>> { + #[inline] + pub(crate) fn host_task_create( + &mut self, + track_scope: bool, + ) -> Result>> { 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()?; @@ -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(()); @@ -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>) -> Result<()> { + #[inline] + pub(crate) fn host_task_delete( + &mut self, + task: Option>, + 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(()) @@ -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> { if !self.concurrency_support() { return self.current_scope_id_not_concurrent(); diff --git a/crates/wasmtime/src/runtime/component/concurrent_disabled.rs b/crates/wasmtime/src/runtime/component/concurrent_disabled.rs index a476bfb869ad..da10e1a0b1be 100644 --- a/crates/wasmtime/src/runtime/component/concurrent_disabled.rs +++ b/crates/wasmtime/src/runtime/component/concurrent_disabled.rs @@ -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<()> { diff --git a/crates/wasmtime/src/runtime/component/func/host.rs b/crates/wasmtime/src/runtime/component/func/host.rs index b1d8a931f8bd..426c3827d818 100644 --- a/crates/wasmtime/src/runtime/component/func/host.rs +++ b/crates/wasmtime/src/runtime/component/func/host.rs @@ -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>, @@ -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 @@ -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")] @@ -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 }; @@ -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(()) @@ -414,6 +425,7 @@ 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>, @@ -421,8 +433,13 @@ where ty: TypeFuncIndex, options: OptionsIndex, storage: &mut [MaybeUninit], + 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) { @@ -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<'_>, @@ -565,6 +583,7 @@ 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, @@ -572,8 +591,11 @@ where 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 diff --git a/crates/wasmtime/src/runtime/component/func/options.rs b/crates/wasmtime/src/runtime/component/func/options.rs index 6538ab0837cf..0bea3eb14a26 100644 --- a/crates/wasmtime/src/runtime/component/func/options.rs +++ b/crates/wasmtime/src/runtime/component/func/options.rs @@ -338,10 +338,36 @@ impl<'a> LiftContext<'a> { store: &'a mut StoreOpaque, options: OptionsIndex, instance_handle: Instance, + ) -> Result> { + 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> { + 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> { 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 diff --git a/crates/wasmtime/src/runtime/component/store.rs b/crates/wasmtime/src/runtime/component/store.rs index 2dccbb41d712..6a670aaa259b 100644 --- a/crates/wasmtime/src/runtime/component/store.rs +++ b/crates/wasmtime/src/runtime/component/store.rs @@ -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) @@ -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()) } @@ -356,6 +358,7 @@ impl StoreOpaque { support } + #[inline] pub(crate) fn lift_context_parts( &mut self, instance: Instance, @@ -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, @@ -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, @@ -459,6 +464,7 @@ impl StoreOpaque { } } + #[inline] pub(crate) fn current_scope_id_not_concurrent(&mut self) -> Result> { match &mut self.component_data_mut().task_state { ComponentTaskState::NotConcurrent(state) => match state.scopes.len().checked_sub(1) {