diff --git a/cranelift/entity/src/primary.rs b/cranelift/entity/src/primary.rs index b47e8073db9c..6336ec3fb2a5 100644 --- a/cranelift/entity/src/primary.rs +++ b/cranelift/entity/src/primary.rs @@ -72,6 +72,17 @@ where self.elems.get_mut(k.index()) } + /// Get the element at `k` if it exists, mutable version. + pub fn get_mut_or_insert_with(&mut self, k: K, f: impl FnOnce() -> V) -> &mut V { + if self.elems.get(k.index()).is_none() { + self.elems.insert(k.index(), f()); + } + + self.elems + .get_mut(k.index()) + .expect("missing existing element") + } + /// Is this map completely empty? pub fn is_empty(&self) -> bool { self.elems.is_empty() diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index afd95ba7c6b8..8f193e94b332 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -1288,7 +1288,7 @@ impl<'a> TrampolineCompiler<'a> { fn translate_error_context_call( &mut self, - ty: TypeErrorContextTableIndex, + ty: TypeComponentLocalErrorContextTableIndex, options: &CanonicalOptions, offset: u32, params: Vec, @@ -1379,7 +1379,7 @@ impl<'a> TrampolineCompiler<'a> { } } - fn translate_error_context_drop_call(&mut self, ty: TypeErrorContextTableIndex) { + fn translate_error_context_drop_call(&mut self, ty: TypeComponentLocalErrorContextTableIndex) { match self.abi { Abi::Wasm => {} diff --git a/crates/environ/src/component/dfg.rs b/crates/environ/src/component/dfg.rs index 355fa7453344..010e74edabb6 100644 --- a/crates/environ/src/component/dfg.rs +++ b/crates/environ/src/component/dfg.rs @@ -354,15 +354,15 @@ pub enum Trampoline { ty: TypeFutureTableIndex, }, ErrorContextNew { - ty: TypeErrorContextTableIndex, + ty: TypeComponentLocalErrorContextTableIndex, options: CanonicalOptions, }, ErrorContextDebugMessage { - ty: TypeErrorContextTableIndex, + ty: TypeComponentLocalErrorContextTableIndex, options: CanonicalOptions, }, ErrorContextDrop { - ty: TypeErrorContextTableIndex, + ty: TypeComponentLocalErrorContextTableIndex, }, ResourceTransferOwn, ResourceTransferBorrow, diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index 0539c0f6c98e..d4da14e5d6c1 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -849,7 +849,7 @@ pub enum Trampoline { /// specified debug message. ErrorContextNew { /// The table index for the `error-context` type in the caller instance. - ty: TypeErrorContextTableIndex, + ty: TypeComponentLocalErrorContextTableIndex, /// String encoding, memory, etc. to use when loading debug message. options: CanonicalOptions, }, @@ -861,7 +861,7 @@ pub enum Trampoline { /// to `error.new`. ErrorContextDebugMessage { /// The table index for the `error-context` type in the caller instance. - ty: TypeErrorContextTableIndex, + ty: TypeComponentLocalErrorContextTableIndex, /// String encoding, memory, etc. to use when storing debug message. options: CanonicalOptions, }, @@ -869,7 +869,7 @@ pub enum Trampoline { /// A `error-context.drop` intrinsic to drop a specified `error-context`. ErrorContextDrop { /// The table index for the `error-context` type in the caller instance. - ty: TypeErrorContextTableIndex, + ty: TypeComponentLocalErrorContextTableIndex, }, /// An intrinsic used by FACT-generated modules which will transfer an owned diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 7516c4425963..32644eff923f 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -103,11 +103,19 @@ indices! { /// This is analogous to `TypeResourceTableIndex` in that it tracks /// ownership of stream within each (sub)component instance. pub struct TypeStreamTableIndex(u32); + /// Index pointing to a error context table within a component. /// /// This is analogous to `TypeResourceTableIndex` in that it tracks /// ownership of error contexts within each (sub)component instance. - pub struct TypeErrorContextTableIndex(u32); + pub struct TypeComponentLocalErrorContextTableIndex(u32); + + /// Index pointing to a (component) globally tracked error context table entry + /// + /// Unlike [`TypeComponentLocalErrorContextTableIndex`], this index refers to + /// the global state table for error contexts at the level of the entire component, + /// not just a subcomponent. + pub struct TypeComponentGlobalErrorContextTableIndex(u32); /// Index pointing to an interned `task.return` type within a component. pub struct TypeTaskReturnIndex(u32); @@ -267,7 +275,7 @@ pub struct ComponentTypes { pub(super) future_tables: PrimaryMap, pub(super) streams: PrimaryMap, pub(super) stream_tables: PrimaryMap, - pub(super) error_context_tables: PrimaryMap, + pub(super) error_context_tables: PrimaryMap, pub(super) task_returns: PrimaryMap, } @@ -357,7 +365,7 @@ impl_index! { impl Index for ComponentTypes { TypeStream => streams } impl Index for ComponentTypes { TypeFutureTable => future_tables } impl Index for ComponentTypes { TypeStreamTable => stream_tables } - impl Index for ComponentTypes { TypeErrorContextTable => error_context_tables } + impl Index for ComponentTypes { TypeErrorContextTable => error_context_tables } } // Additionally forward anything that can index `ModuleTypes` to `ModuleTypes` @@ -518,7 +526,7 @@ pub enum InterfaceType { Borrow(TypeResourceTableIndex), Future(TypeFutureTableIndex), Stream(TypeStreamTableIndex), - ErrorContext(TypeErrorContextTableIndex), + ErrorContext(TypeComponentLocalErrorContextTableIndex), } impl From<&wasmparser::PrimitiveValType> for InterfaceType { diff --git a/crates/environ/src/component/types_builder.rs b/crates/environ/src/component/types_builder.rs index 1bc47c785584..254c5f7d7616 100644 --- a/crates/environ/src/component/types_builder.rs +++ b/crates/environ/src/component/types_builder.rs @@ -49,7 +49,7 @@ pub struct ComponentTypesBuilder { streams: HashMap, future_tables: HashMap, stream_tables: HashMap, - error_context_tables: HashMap, + error_context_tables: HashMap, task_returns: HashMap, component_types: ComponentTypes, @@ -210,7 +210,7 @@ impl ComponentTypesBuilder { } /// Returns the number of error-context tables allocated so far, or the maximum - /// `TypeErrorContextTableIndex`. + /// `TypeComponentLocalErrorContextTableIndex`. pub fn num_error_context_tables(&self) -> usize { self.component_types.error_context_tables.len() } @@ -444,7 +444,7 @@ impl ComponentTypesBuilder { } /// Retrieve Wasmtime's type representation of the `error-context` type. - pub fn error_context_type(&mut self) -> Result { + pub fn error_context_type(&mut self) -> Result { self.error_context_table_type() } @@ -599,7 +599,7 @@ impl ComponentTypesBuilder { })) } - fn error_context_table_type(&mut self) -> Result { + fn error_context_table_type(&mut self) -> Result { Ok(self.add_error_context_table_type(TypeErrorContextTable { instance: self.resources.get_current_instance().unwrap(), })) @@ -694,7 +694,7 @@ impl ComponentTypesBuilder { pub fn add_error_context_table_type( &mut self, ty: TypeErrorContextTable, - ) -> TypeErrorContextTableIndex { + ) -> TypeComponentLocalErrorContextTableIndex { intern( &mut self.error_context_tables, &mut self.component_types.error_context_tables, diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs index bfc20fefcd5d..a9a3a65d40ab 100644 --- a/crates/environ/src/fact/trampoline.rs +++ b/crates/environ/src/fact/trampoline.rs @@ -17,7 +17,7 @@ use crate::component::{ CanonicalAbiInfo, ComponentTypesBuilder, FixedEncoding as FE, FlatType, InterfaceType, - StringEncoding, Transcode, TypeEnumIndex, TypeErrorContextTableIndex, TypeFlagsIndex, + StringEncoding, Transcode, TypeEnumIndex, TypeComponentLocalErrorContextTableIndex, TypeFlagsIndex, TypeFutureTableIndex, TypeListIndex, TypeOptionIndex, TypeRecordIndex, TypeResourceTableIndex, TypeResultIndex, TypeStreamTableIndex, TypeTupleIndex, TypeVariantIndex, VariantInfo, FLAG_MAY_ENTER, FLAG_MAY_LEAVE, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, @@ -2935,7 +2935,7 @@ impl Compiler<'_, '_> { fn translate_error_context_context( &mut self, - src_ty: TypeErrorContextTableIndex, + src_ty: TypeComponentLocalErrorContextTableIndex, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination, diff --git a/crates/misc/component-async-tests/src/lib.rs b/crates/misc/component-async-tests/src/lib.rs index 2f9b77f660ae..548c6ad7e62c 100644 --- a/crates/misc/component-async-tests/src/lib.rs +++ b/crates/misc/component-async-tests/src/lib.rs @@ -1289,4 +1289,9 @@ mod test { &fs::read(test_programs_artifacts::ASYNC_HTTP_MIDDLEWARE_COMPONENT).await?; test_http_echo(&compose(middleware, echo).await?, true).await } + + #[tokio::test] + async fn async_error_context() -> Result<()> { + test_run(&fs::read(test_programs_artifacts::ASYNC_ERROR_CONTEXT_COMPONENT).await?).await + } } diff --git a/crates/misc/component-async-tests/wit/test.wit b/crates/misc/component-async-tests/wit/test.wit index 51d8c95acca2..9574ebad8942 100644 --- a/crates/misc/component-async-tests/wit/test.wit +++ b/crates/misc/component-async-tests/wit/test.wit @@ -129,3 +129,7 @@ world borrowing-host { import borrowing-types; export run-bool; } + +world error-context-usage { + export run; +} diff --git a/crates/test-programs/Cargo.toml b/crates/test-programs/Cargo.toml index 40210dc8a518..58414c46ccf9 100644 --- a/crates/test-programs/Cargo.toml +++ b/crates/test-programs/Cargo.toml @@ -15,7 +15,8 @@ anyhow = { workspace = true, features = ['std'] } wasi = "0.11.0" wasi-nn = "0.6.0" wit-bindgen = { workspace = true, features = ['default'] } -wit-bindgen-rt = { workspace = true } +#wit-bindgen-rt = { workspace = true } +wit-bindgen-rt = { path = "../../../wit-bindgen/crates/guest-rust/rt", features = [ 'async'] } libc = { workspace = true } getrandom = "0.2.9" futures = { workspace = true, default-features = false, features = ['alloc'] } diff --git a/crates/test-programs/src/bin/async_error_context.rs b/crates/test-programs/src/bin/async_error_context.rs new file mode 100644 index 000000000000..5a5998b4624e --- /dev/null +++ b/crates/test-programs/src/bin/async_error_context.rs @@ -0,0 +1,29 @@ +mod bindings { + wit_bindgen::generate!({ + path: "../misc/component-async-tests/wit", + world: "error-context-usage", + async: { + exports: [ + "local:local/run#run", + ], + } + }); + + use super::Component; + export!(Component); +} +use bindings::exports::local::local::run::Guest; + +use wit_bindgen_rt::async_support::error_context_new; + +struct Component; + +impl Guest for Component { + async fn run() { + let err_ctx = error_context_new("error".into()); + _ = err_ctx.debug_message(); + } +} + +// Unused function; required since this file is built as a `bin`: +fn main() {} diff --git a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs index 8542e36d9878..6a6cac85bbac 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs @@ -5,14 +5,16 @@ use { }, crate::{ component::{ - func::{self, LiftContext, LowerContext, Options}, + func::{self, Lift, LiftContext, LowerContext, Options}, matching::InstanceType, values::{ErrorContextAny, FutureAny, StreamAny}, - Val, WasmList, + Lower, Val, WasmList, WasmStr, }, vm::{ component::{ - ComponentInstance, StateTable, StreamFutureState, VMComponentContext, WaitableState, + ComponentInstance, ErrorContextState, GlobalErrorContextRefCount, + LocalErrorContextRefCount, StateTable, StreamFutureState, VMComponentContext, + WaitableState, }, SendSyncPtr, VMFuncRef, VMMemoryDefinition, VMOpaqueContext, VMStore, }, @@ -35,7 +37,8 @@ use { }, wasmtime_environ::component::{ CanonicalAbiInfo, ComponentTypes, InterfaceType, StringEncoding, - TypeErrorContextTableIndex, TypeFutureTableIndex, TypeStreamTableIndex, + TypeComponentGlobalErrorContextTableIndex, TypeComponentLocalErrorContextTableIndex, + TypeFutureTableIndex, TypeStreamTableIndex, }, }; @@ -1046,13 +1049,24 @@ impl ErrorContext { fn lower_to_index(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result { match ty { InterfaceType::ErrorContext(dst) => { - let dst = unsafe { &mut (*cx.instance).component_error_context_tables()[dst] }; + let tbl = unsafe { + &mut (*cx.instance) + .component_error_context_tables() + .get_mut(dst) + .expect("error context table index present in (sub)component table during lower") + }; - if let Some((dst_idx, dst_state)) = dst.get_mut_by_rep(self.rep) { - *dst_state += 1; + if let Some((dst_idx, (_, dst_state))) = tbl.get_mut_by_rep(self.rep) { + dst_state.0 += 1; Ok(dst_idx) } else { - dst.insert(self.rep, 1) + tbl.insert( + self.rep, + ( + TypeComponentGlobalErrorContextTableIndex::from_u32(self.rep), + LocalErrorContextRefCount(1), + ), + ) } } _ => func::bad_type_info(), @@ -1063,7 +1077,13 @@ impl ErrorContext { match ty { InterfaceType::ErrorContext(src) => { let (rep, _) = unsafe { - (*cx.instance).component_error_context_tables()[src].get_mut_by_index(index)? + (*cx.instance) + .component_error_context_tables() + .get_mut(src) + .expect( + "error context table index present in (sub)component table during lift", + ) + .get_mut_by_index(index)? }; Ok(Self { rep }) @@ -1996,27 +2016,87 @@ pub(crate) extern "C" fn flat_stream_read( ) } +/// Create a new error context for the given component pub(crate) extern "C" fn error_context_new( vmctx: *mut VMOpaqueContext, memory: *mut VMMemoryDefinition, realloc: *mut VMFuncRef, string_encoding: u8, - ty: TypeErrorContextTableIndex, - address: u32, - count: u32, + ty: TypeComponentLocalErrorContextTableIndex, + debug_msg_address: u32, + debug_msg_len: u32, ) -> u64 { unsafe { call_host_and_handle_result::(vmctx, || { - _ = ( - vmctx, - memory, - realloc, - StringEncoding::from_u8(string_encoding).unwrap(), - ty, - address, - count, + // Retrieve the component instance + let cx = VMComponentContext::from_opaque(vmctx); + let instance = (*cx).instance(); + + // Read string from guest memory + let mut cx = StoreContextMut::(&mut *(*instance).store().cast()); + let options = Options::new( + cx.0.id(), + NonNull::new(memory), + NonNull::new(realloc), + StringEncoding::from_u8(string_encoding).ok_or_else(|| { + anyhow::anyhow!("failed to convert u8 string encoding [{string_encoding}]") + })?, + false, + None, ); - bail!("todo: `error.new` not yet implemented"); + let lift_ctx = + &mut LiftContext::new(cx.0, &options, (*instance).component_types(), instance); + let s = { + let address = usize::try_from(debug_msg_address)?; + let len = usize::try_from(debug_msg_len)?; + WasmStr::load( + lift_ctx, + InterfaceType::String, + &lift_ctx + .memory() + .get(address..) + .and_then(|b| b.get(..len)) + .map(|_| { + [debug_msg_address.to_le_bytes(), debug_msg_len.to_le_bytes()].concat() + }) + .ok_or_else(|| { + anyhow::anyhow!("invalid debug message pointer: out of bounds") + })?, + )? + }; + + // Create a new ErrorContext that is tracked along with other concurrent state + let err_ctx = ErrorContextState { + debug_msg: s.to_str(&cx)?.to_string(), + }; + let table_id = cx.concurrent_state().table.push(err_ctx)?; + let global_ref_count_idx = + TypeComponentGlobalErrorContextTableIndex::from_u32(table_id.rep()); + + // Add to the global error context ref counts + let _ = (*instance) + .component_global_error_context_ref_counts() + .insert(global_ref_count_idx, GlobalErrorContextRefCount(1)); + + // Error context are tracked both locally (to a single component instance) and globally + // the counts for both must stay in sync. + // + // Here we reflect the newly created global concurrent error context state into the + // component instance's locally tracked count, along with the appropriate key into the global + // ref tracking data structures to enable later lookup + let local_tbl = (*instance) + .component_error_context_tables() + .get_mut_or_insert_with(ty, || StateTable::default()); + assert!( + !local_tbl.has_handle(table_id.rep()), + "newly created error context state already tracked by component" + ); + let local_idx = local_tbl.insert( + table_id.rep(), + (global_ref_count_idx, LocalErrorContextRefCount(1)), + )?; + + Ok(local_idx) }) } } @@ -2026,42 +2106,114 @@ pub(crate) extern "C" fn error_context_debug_message( memory: *mut VMMemoryDefinition, realloc: *mut VMFuncRef, string_encoding: u8, - ty: TypeErrorContextTableIndex, - handle: u32, - address: u32, + ty: TypeComponentLocalErrorContextTableIndex, + err_ctx_handle: u32, + debug_msg_address: u32, ) -> bool { unsafe { call_host_and_handle_result::(vmctx, || { - _ = ( - vmctx, - memory, - realloc, - StringEncoding::from_u8(string_encoding).unwrap(), - ty, - handle, - address, + // Retrieve the component instance + let cx = VMComponentContext::from_opaque(vmctx); + let instance = (*cx).instance(); + let mut cx = StoreContextMut::(&mut *(*instance).store().cast()); + let store_id = cx.0.id(); + + // Retrieve the error context and internal debug message + let (state_table_id_rep, _) = (*instance) + .component_error_context_tables() + .get_mut(ty) + .context( + "error context table index present in (sub)component lookup during debug_msg", + )? + .get_mut_by_index(err_ctx_handle)?; + + // Get the state associated with the error context + let ErrorContextState { debug_msg } = cx + .concurrent_state() + .table + .get_mut(TableId::::new(state_table_id_rep))?; + let debug_msg = debug_msg.clone(); + + // Lower the string into the component's memory + let options = Options::new( + store_id, + NonNull::new(memory), + NonNull::new(realloc), + StringEncoding::from_u8(string_encoding).ok_or_else(|| { + anyhow::anyhow!("failed to convert u8 string encoding [{string_encoding}]") + })?, + false, + None, ); - bail!("todo: `error.debug-message` not yet implemented"); + let lower_cx = + &mut LowerContext::new(cx, &options, (*instance).component_types(), instance); + let debug_msg_address = usize::try_from(debug_msg_address)?; + let offset = lower_cx + .as_slice_mut() + .get(debug_msg_address..) + .and_then(|b| b.get(..debug_msg.bytes().len())) + .map(|_| debug_msg_address) + .ok_or_else(|| anyhow::anyhow!("invalid debug message pointer: out of bounds"))?; + debug_msg + .as_str() + .store(lower_cx, InterfaceType::String, offset)?; + + Ok(()) }) } } pub(crate) extern "C" fn error_context_drop( vmctx: *mut VMOpaqueContext, - ty: TypeErrorContextTableIndex, + ty: TypeComponentLocalErrorContextTableIndex, error_context: u32, ) -> bool { unsafe { call_host_and_handle_result::(vmctx, || { let cx = VMComponentContext::from_opaque(vmctx); let instance = (*cx).instance(); - let (_, count) = - (*instance).component_error_context_tables()[ty].get_mut_by_index(error_context)?; - assert!(*count > 0); - *count -= 1; + let local_state_table = (*instance) + .component_error_context_tables() + .get_mut(ty) + .context("error context table index present in (sub)component table during drop")?; + + // Reduce the local (sub)component ref count, removing tracking if necessary + let (rep, ref_count_idx, local_ref_removed) = { + let (rep, (ref_count_idx, LocalErrorContextRefCount(local_ref_count))) = + local_state_table.get_mut_by_index(error_context)?; + let ref_count_idx = *ref_count_idx; + assert!(*local_ref_count > 0); + *local_ref_count -= 1; + let mut local_ref_removed = false; + if *local_ref_count == 0 { + local_ref_removed = true; + local_state_table + .remove_by_index(error_context) + .context("removing error context from component-local tracking")?; + } + (rep, ref_count_idx, local_ref_removed) + }; + + let GlobalErrorContextRefCount(global_ref_count) = (*instance) + .component_global_error_context_ref_counts() + .get_mut(&ref_count_idx) + .expect("retrieve concurrent state for error context during drop"); + + // Reduce the component-global ref count, removing tracking if necessary + assert!(*global_ref_count >= 1); + *global_ref_count -= 1; + if *global_ref_count == 0 { + assert!(local_ref_removed); + let mut cx = StoreContextMut::(&mut *(*instance).store().cast()); - if *count == 0 { - (*instance).component_error_context_tables()[ty].remove_by_index(error_context)?; + (*instance) + .component_global_error_context_ref_counts() + .remove(&ref_count_idx); + + cx.concurrent_state() + .table + .delete(TableId::::new(rep)) + .context("deleting component-global error context data")?; } Ok(()) diff --git a/crates/wasmtime/src/runtime/component/mod.rs b/crates/wasmtime/src/runtime/component/mod.rs index 25ab366f6fc9..b3f8abd5b26f 100644 --- a/crates/wasmtime/src/runtime/component/mod.rs +++ b/crates/wasmtime/src/runtime/component/mod.rs @@ -697,7 +697,7 @@ pub(crate) mod concurrent { task::{Context, Poll, Waker}, }, wasmtime_environ::component::{ - InterfaceType, RuntimeComponentInstanceIndex, TypeErrorContextTableIndex, + InterfaceType, RuntimeComponentInstanceIndex, TypeComponentLocalErrorContextTableIndex, TypeFutureTableIndex, TypeStreamTableIndex, TypeTaskReturnIndex, }, }; @@ -981,7 +981,7 @@ pub(crate) mod concurrent { _memory: *mut VMMemoryDefinition, _realloc: *mut VMFuncRef, _string_encoding: u8, - _ty: TypeErrorContextTableIndex, + _ty: TypeComponentLocalErrorContextTableIndex, _address: u32, _count: u32, ) -> u64 { @@ -993,7 +993,7 @@ pub(crate) mod concurrent { _memory: *mut VMMemoryDefinition, _realloc: *mut VMFuncRef, _string_encoding: u8, - _ty: TypeErrorContextTableIndex, + _ty: TypeComponentLocalErrorContextTableIndex, _handle: u32, _address: u32, ) -> bool { @@ -1002,7 +1002,7 @@ pub(crate) mod concurrent { pub(crate) extern "C" fn error_context_drop( _vmctx: *mut VMOpaqueContext, - _ty: TypeErrorContextTableIndex, + _ty: TypeComponentLocalErrorContextTableIndex, _error: u32, ) -> bool { unreachable!() diff --git a/crates/wasmtime/src/runtime/vm/component.rs b/crates/wasmtime/src/runtime/vm/component.rs index 896d887acc67..df1cbd7098c2 100644 --- a/crates/wasmtime/src/runtime/vm/component.rs +++ b/crates/wasmtime/src/runtime/vm/component.rs @@ -12,6 +12,7 @@ use crate::runtime::vm::{ VMOpaqueContext, VMStore, VMWasmCallFunction, ValRaw, }; use alloc::alloc::Layout; +use alloc::collections::BTreeMap; use alloc::sync::Arc; use core::any::Any; use core::marker; @@ -27,10 +28,12 @@ use wasmtime_environ::{HostPtr, PrimaryMap, VMSharedTypeIndex}; // 32-bit platforms const INVALID_PTR: usize = 0xdead_dead_beef_beef_u64 as usize; +mod error_contexts; mod libcalls; mod resources; mod states; +pub use self::error_contexts::{GlobalErrorContextRefCount, LocalErrorContextRefCount}; pub use self::resources::{CallContexts, ResourceTable, ResourceTables}; pub use self::states::StateTable; @@ -64,7 +67,38 @@ pub struct ComponentInstance { component_resource_tables: PrimaryMap, component_waitable_tables: PrimaryMap>, - component_error_context_tables: PrimaryMap>, + + /// (Sub)Component specific error context tracking + /// + /// At the component level, only the number of references (`usize`) to a given error context is tracked, + /// with state related to the error context being held at the component model level, in concurrent + /// state. + /// + /// The state tables in the (sub)component local tracking must contain a pointer into the global + /// error context lookups in order to ensure that in contexts where only the local reference is present + /// the global state can still be maintained/updated. + component_error_context_tables: PrimaryMap< + TypeComponentLocalErrorContextTableIndex, + StateTable<( + TypeComponentGlobalErrorContextTableIndex, + LocalErrorContextRefCount, + )>, + >, + + /// Reference counts for all component error contexts + /// + /// NOTE: it is possible the global ref count to be *greater* than the sum of + /// (sub)component ref counts as tracked by `component_error_context_tables`, for + /// example when the host holds one or more references to error contexts. + /// + /// The key of this primary map is often referred to as the "rep" (i.e. host-side + /// component-wide representation) of the index into concurrent state for a given + /// stored `ErrorContext`. + /// + /// Stated another way, `TypeComponentGlobalErrorContextTableIndex` is essentially the same + /// as a `TableId`. + component_global_error_context_ref_counts: + BTreeMap, /// Storage for the type information about resources within this component /// instance. @@ -297,7 +331,7 @@ pub type VMErrorContextNewCallback = extern "C" fn( memory: *mut VMMemoryDefinition, realloc: *mut VMFuncRef, string_encoding: u8, - ty: TypeErrorContextTableIndex, + ty: TypeComponentLocalErrorContextTableIndex, address: u32, count: u32, ) -> u64; @@ -309,14 +343,17 @@ pub type VMErrorContextDebugMessageCallback = extern "C" fn( memory: *mut VMMemoryDefinition, realloc: *mut VMFuncRef, string_encoding: u8, - ty: TypeErrorContextTableIndex, + ty: TypeComponentLocalErrorContextTableIndex, handle: u32, address: u32, ) -> bool; /// Type signature for the host-defined `error-context.drop` built-in function. -pub type VMErrorContextDropCallback = - extern "C" fn(vmctx: *mut VMOpaqueContext, ty: TypeErrorContextTableIndex, handle: u32) -> bool; +pub type VMErrorContextDropCallback = extern "C" fn( + vmctx: *mut VMOpaqueContext, + ty: TypeComponentLocalErrorContextTableIndex, + handle: u32, +) -> bool; /// This is a marker type to represent the underlying allocation of a /// `VMComponentContext`. @@ -360,6 +397,13 @@ pub enum WaitableState { Future(TypeFutureTableIndex, StreamFutureState), } +/// Represents the state associated with an error context +#[derive(Debug, PartialEq, Eq, PartialOrd)] +pub struct ErrorContextState { + /// Debug message associated with the error context + pub(crate) debug_msg: String, +} + impl ComponentInstance { /// Converts the `vmctx` provided into a `ComponentInstance` and runs the /// provided closure with that instance. @@ -423,12 +467,19 @@ impl ComponentInstance { } let num_error_context_tables = runtime_info.component().num_error_context_tables; - let mut component_error_context_tables = - PrimaryMap::with_capacity(num_error_context_tables); + let mut component_error_context_tables = PrimaryMap::< + TypeComponentLocalErrorContextTableIndex, + StateTable<( + TypeComponentGlobalErrorContextTableIndex, + LocalErrorContextRefCount, + )>, + >::with_capacity(num_error_context_tables); for _ in 0..num_error_context_tables { component_error_context_tables.push(StateTable::default()); } + let component_global_error_context_ref_counts = BTreeMap::new(); + ptr::write( ptr.as_ptr(), ComponentInstance { @@ -444,6 +495,7 @@ impl ComponentInstance { component_resource_tables, component_waitable_tables, component_error_context_tables, + component_global_error_context_ref_counts, runtime_info, resource_types, vmctx: VMComponentContext { @@ -749,8 +801,7 @@ impl ComponentInstance { stream_close_readable; *self.vmctx_plus_offset_mut(self.offsets.flat_stream_write()) = flat_stream_write; *self.vmctx_plus_offset_mut(self.offsets.flat_stream_read()) = flat_stream_read; - *self.vmctx_plus_offset_mut(self.offsets.error_context_debug_message()) = - error_context_new; + *self.vmctx_plus_offset_mut(self.offsets.error_context_new()) = error_context_new; *self.vmctx_plus_offset_mut(self.offsets.error_context_debug_message()) = error_context_debug_message; *self.vmctx_plus_offset_mut(self.offsets.error_context_drop()) = error_context_drop; @@ -908,10 +959,24 @@ impl ComponentInstance { /// counts with respect to the components which own them. pub fn component_error_context_tables( &mut self, - ) -> &mut PrimaryMap> { + ) -> &mut PrimaryMap< + TypeComponentLocalErrorContextTableIndex, + StateTable<( + TypeComponentGlobalErrorContextTableIndex, + LocalErrorContextRefCount, + )>, + > { &mut self.component_error_context_tables } + /// Retrieves the tables for tracking component-global error-context handles + /// and their reference counts with respect to the components which own them. + pub fn component_global_error_context_ref_counts( + &mut self, + ) -> &mut BTreeMap { + &mut self.component_global_error_context_ref_counts + } + /// Returns the destructor and instance flags for the specified resource /// table type. /// @@ -1063,21 +1128,46 @@ impl ComponentInstance { } } + /// Transfer the state of a given error context from one component to another pub(crate) fn error_context_transfer( &mut self, src_idx: u32, - src: TypeErrorContextTableIndex, - dst: TypeErrorContextTableIndex, + src: TypeComponentLocalErrorContextTableIndex, + dst: TypeComponentLocalErrorContextTableIndex, ) -> Result { - let (rep, _) = self.component_error_context_tables[src].get_mut_by_index(src_idx)?; - let dst = &mut self.component_error_context_tables[dst]; - - if let Some((dst_idx, dst_state)) = dst.get_mut_by_rep(rep) { - *dst_state += 1; - Ok(dst_idx) + let (rep, global_idx) = { + let (rep, (global_idx, _)) = self + .component_error_context_tables + .get_mut(src) + .context("error context table index present in (sub)component lookup")? + .get_mut_by_index(src_idx)?; + (rep, global_idx.clone()) + }; + let dst = self + .component_error_context_tables + .get_mut(dst) + .context("error context table index present in (sub)component lookup")?; + + // Update the component local for the destination + let updated_count = if let Some((dst_idx, (_, count))) = dst.get_mut_by_rep(rep.clone()) { + (*count).0 += 1; + dst_idx } else { - dst.insert(rep, 1) - } + dst.insert(rep, (global_idx.clone(), LocalErrorContextRefCount(1)))? + }; + + // Update the global (cross-subcomponent) count for error contexts + // as the new component has essentially created a new reference that will + // be dropped/handled independently + let global_ref_count = self + .component_global_error_context_ref_counts + // TODO: can be use the rep here instead of a new idx? + // if the rep is always present during a subcomponent lookup, then we can always use it to access the global?? + .get_mut(&global_idx) + .context("global ref count present for existing (sub)component error context")?; + global_ref_count.0 += 1; + + Ok(updated_count) } } diff --git a/crates/wasmtime/src/runtime/vm/component/error_contexts.rs b/crates/wasmtime/src/runtime/vm/component/error_contexts.rs new file mode 100644 index 000000000000..435197f79e5e --- /dev/null +++ b/crates/wasmtime/src/runtime/vm/component/error_contexts.rs @@ -0,0 +1,16 @@ +/// Error context reference count local to a given (sub)component +/// +/// This reference count is localized to a single (sub)component, +/// rather than the global cross-component count (i.e. that determines +/// when a error context can be completely removed) +#[repr(transparent)] +pub struct LocalErrorContextRefCount(pub(crate) usize); + +/// Error context reference count across a [`ComponentInstance`] +/// +/// Contrasted to `LocalErrorContextRefCount`, this count is maintained +/// across all sub-components in a given component. +/// +/// When this count is zero it is *definitely* safe to remove an error context. +#[repr(transparent)] +pub struct GlobalErrorContextRefCount(pub(crate) usize); diff --git a/crates/wasmtime/src/runtime/vm/component/libcalls.rs b/crates/wasmtime/src/runtime/vm/component/libcalls.rs index ef3b6f60b236..348ac9d8e420 100644 --- a/crates/wasmtime/src/runtime/vm/component/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/component/libcalls.rs @@ -7,7 +7,7 @@ use core::cell::Cell; use core::convert::Infallible; use core::slice; use wasmtime_environ::component::{ - TypeErrorContextTableIndex, TypeFutureTableIndex, TypeResourceTableIndex, TypeStreamTableIndex, + TypeComponentLocalErrorContextTableIndex, TypeFutureTableIndex, TypeResourceTableIndex, TypeStreamTableIndex, }; const UTF16_TAG: usize = 1 << 31; @@ -592,8 +592,8 @@ unsafe fn error_context_transfer( src_table: u32, dst_table: u32, ) -> Result { - let src_table = TypeErrorContextTableIndex::from_u32(src_table); - let dst_table = TypeErrorContextTableIndex::from_u32(dst_table); + let src_table = TypeComponentLocalErrorContextTableIndex::from_u32(src_table); + let dst_table = TypeComponentLocalErrorContextTableIndex::from_u32(dst_table); ComponentInstance::from_vmctx(vmctx, |instance| { instance.error_context_transfer(src_idx, src_table, dst_table) }) diff --git a/crates/wasmtime/src/runtime/vm/component/states.rs b/crates/wasmtime/src/runtime/vm/component/states.rs index 8a8a9a39ee6a..2f09de1eea93 100644 --- a/crates/wasmtime/src/runtime/vm/component/states.rs +++ b/crates/wasmtime/src/runtime/vm/component/states.rs @@ -90,6 +90,14 @@ impl StateTable { } } + pub fn has_handle(&self, idx: u32) -> bool { + matches!( + self.handle_index_to_table_index(idx) + .and_then(|i| self.slots.get(i)), + Some(Slot::Occupied { .. }) + ) + } + pub fn get_mut_by_index(&mut self, idx: u32) -> Result<(u32, &mut T)> { let slot = self .handle_index_to_table_index(idx)