From f84650e7dbc8c05f07d04df8e5c2f41bacd0dd5d Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Tue, 3 Mar 2026 21:04:40 +0000 Subject: [PATCH 1/3] Include core instance sizes in component_instance_size limit There exist several knobs for limiting the memory that might be consumed for metadata for components. For core module instances within a component, the two that previously existed to control metadata allocations have been: - A: max_core_instances_per_component - B: component_instance_size These allow for an embedder to set an upper bound on memory used by a component's instances to A * B. This value could be quite large for some systems and it would be nice to be able to set a cap on the total memory that might be used for metadata across all instances while still allowing for a greater number of instances with the potential for a subset of those instances to be relatively large. To allow for aggregate control over memory used within the runtime for componenets, the existing `max_component_instance_size` limit is extended to consider both the `VMComponentCtx` size as well as the aggregate size of all core instances in the component. --- crates/wasmtime/src/config.rs | 14 ++++-- .../runtime/vm/instance/allocator/pooling.rs | 26 ++++++----- tests/all/pooling_allocator.rs | 44 +++++++++++++++++++ 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 26b5ccd3952e..1e0857010c3e 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -3650,7 +3650,8 @@ impl PoolingAllocationConfig { } /// The maximum size, in bytes, allocated for a component instance's - /// `VMComponentContext` metadata. + /// `VMComponentContext` metadata as well as the aggregate size of this + /// component's core instances `VMContext` metadata. /// /// The [`wasmtime::component::Instance`][crate::component::Instance] type /// has a static size but its internal `VMComponentContext` is dynamically @@ -3667,10 +3668,17 @@ impl PoolingAllocationConfig { /// module will fail at runtime with an error indicating how many bytes were /// needed. /// + /// In addition to the memory in the runtime for the component itself, + /// components contain one or more core module instances. Each of these + /// require some memory in the runtime as described in + /// [`PoolingAllocationConfig::max_core_instance_size`]. The limit here + /// applies against the sum of all of these individual allocations. + /// /// The default value for this is 1MiB. /// - /// This provides an upper-bound on the total size of component - /// metadata-related allocations, along with + /// This provides an upper-bound on the total size of all component's + /// metadata-related allocations (for both the component and its embedded + /// core module instances), along with /// [`PoolingAllocationConfig::total_component_instances`]. The upper bound is /// /// ```text diff --git a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs index 3c40b8fa4700..ba6dd8d5cfcd 100644 --- a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs +++ b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs @@ -100,8 +100,8 @@ pub struct InstanceLimits { /// concurrently. pub total_component_instances: u32, - /// The maximum size of a component's `VMComponentContext`, not including - /// any of its inner core modules' `VMContext` sizes. + /// The maximum size of a component's `VMComponentContext`, including + /// the aggregate size of all its inner core modules' `VMContext` sizes. pub component_instance_size: usize, /// The maximum number of core module instances that may be allocated @@ -474,18 +474,21 @@ impl PoolingInstanceAllocator { fn validate_component_instance_size( &self, offsets: &VMComponentOffsets, + core_instances_aggregate_size: usize, ) -> Result<()> { - if usize::try_from(offsets.size_of_vmctx()).unwrap() <= self.limits.component_instance_size - { + let vmcomponentctx_size = usize::try_from(offsets.size_of_vmctx()).unwrap(); + let total_instance_size = core_instances_aggregate_size.saturating_add(vmcomponentctx_size); + if total_instance_size <= self.limits.component_instance_size { return Ok(()); } // TODO: Add context with detailed accounting of what makes up all the // `VMComponentContext`'s space like we do for module instances. bail!( - "instance allocation for this component requires {} bytes of `VMComponentContext` \ - space which exceeds the configured maximum of {} bytes", - offsets.size_of_vmctx(), + "instance allocation for this component requires {total_instance_size} bytes of `VMComponentContext` \ + and aggregated core instance runtime space which exceeds the configured maximum of {} bytes. \ + `VMComponentContext` used {vmcomponentctx_size} bytes, `core module instances` used \ + {core_instances_aggregate_size} bytes.", self.limits.component_instance_size ) } @@ -559,12 +562,10 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator { offsets: &VMComponentOffsets, get_module: &'a dyn Fn(StaticModuleIndex) -> &'a Module, ) -> Result<()> { - self.validate_component_instance_size(offsets) - .context("component instance size does not fit in pooling allocator requirements")?; - let mut num_core_instances = 0; let mut num_memories = 0; let mut num_tables = 0; + let mut core_instances_aggregate_size: usize = 0; for init in &component.initializers { use wasmtime_environ::component::GlobalInitializer::*; use wasmtime_environ::component::InstantiateModule; @@ -577,10 +578,12 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator { InstantiateModule(InstantiateModule::Static(static_module_index, _), _) => { let module = get_module(*static_module_index); let offsets = VMOffsets::new(HostPtr, &module); + let layout = Instance::alloc_layout(&offsets); self.validate_module(module, &offsets)?; num_core_instances += 1; num_memories += module.num_defined_memories(); num_tables += module.num_defined_tables(); + core_instances_aggregate_size += layout.size(); } LowerImport { .. } | ExtractMemory(_) @@ -618,6 +621,9 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator { ); } + self.validate_component_instance_size(offsets, core_instances_aggregate_size) + .context("component instance size does not fit in pooling allocator requirements")?; + Ok(()) } diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs index a02e4b0d2822..9ba2c2b1af16 100644 --- a/tests/all/pooling_allocator.rs +++ b/tests/all/pooling_allocator.rs @@ -1120,6 +1120,50 @@ fn component_tables_limit() -> Result<()> { Ok(()) } +#[test] +#[cfg(feature = "component-model")] +fn component_core_instances_aggregate_size() -> Result<()> { + let mut pool = crate::small_pool_config(); + pool.max_core_instances_per_component(100) + // x86_64 requires 23824 bytes; we exceed this by a fair bit as there will + // be differences by arch. + .max_component_instance_size(1024); + + let mut config = Config::new(); + config.wasm_component_model(true); + config.allocation_strategy(pool); + let engine = Engine::new(&config)?; + + let core_instances = (1..100) + .map(|i| format!("(core instance $i{i} (instantiate $m))")) + .collect::>() + .join("\n"); + + match wasmtime::component::Component::new( + &engine, + format!( + " + (component + (core module $m + (func (export \"f\") (result i32) + i32.const 42 + ) + ) + {core_instances} + ) + " + ), + ) { + Ok(_) => panic!("should have hit aggregate size limit"), + Err(e) => { + e.assert_contains("instance allocation for this component requires"); + e.assert_contains("exceeds the configured maximum"); + } + } + + Ok(()) +} + #[test] #[cfg_attr(miri, ignore)] fn total_memories_limit() -> Result<()> { From 42758b86bec107e3847de32078b203698a6484a8 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Fri, 13 Mar 2026 16:03:56 +0000 Subject: [PATCH 2/3] Fix err msg checks for component_instance_size_limit test --- tests/all/pooling_allocator.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs index 9ba2c2b1af16..de33f70197f6 100644 --- a/tests/all/pooling_allocator.rs +++ b/tests/all/pooling_allocator.rs @@ -858,10 +858,11 @@ fn component_instance_size_limit() -> Result<()> { match wasmtime::component::Component::new(&engine, "(component)") { Ok(_) => panic!("should have hit limit"), - Err(e) => e.assert_contains( - "instance allocation for this component requires 64 bytes of \ - `VMComponentContext` space which exceeds the configured maximum of 1 bytes", - ), + Err(e) => { + e.assert_contains("instance allocation for this component requires 64 bytes"); + e.assert_contains("which exceeds the configured maximum of 1 bytes"); + e.assert_contains("`VMComponentContext` used 64 bytes"); + } } Ok(()) From 9a668cc28faa4e23b4a92f489e388492e288fd23 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Fri, 13 Mar 2026 19:55:13 +0000 Subject: [PATCH 3/3] Miri ignore component_core_instances_aggregate_size --- tests/all/pooling_allocator.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs index de33f70197f6..860970a1ac7d 100644 --- a/tests/all/pooling_allocator.rs +++ b/tests/all/pooling_allocator.rs @@ -1123,6 +1123,7 @@ fn component_tables_limit() -> Result<()> { #[test] #[cfg(feature = "component-model")] +#[cfg_attr(miri, ignore)] fn component_core_instances_aggregate_size() -> Result<()> { let mut pool = crate::small_pool_config(); pool.max_core_instances_per_component(100)