Skip to content
Merged
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
14 changes: 11 additions & 3 deletions crates/wasmtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
26 changes: 16 additions & 10 deletions crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -474,18 +474,21 @@ impl PoolingInstanceAllocator {
fn validate_component_instance_size(
&self,
offsets: &VMComponentOffsets<HostPtr>,
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
)
}
Expand Down Expand Up @@ -559,12 +562,10 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
offsets: &VMComponentOffsets<HostPtr>,
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;
Expand All @@ -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(_)
Expand Down Expand Up @@ -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(())
}

Expand Down
54 changes: 50 additions & 4 deletions tests/all/pooling_allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down Expand Up @@ -1120,6 +1121,51 @@ fn component_tables_limit() -> Result<()> {
Ok(())
}

#[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)
// 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::<Vec<String>>()
.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<()> {
Expand Down
Loading