Skip to content

Commit

Permalink
Wasmtime: Introduce {Module,Component}::resources_required (bytecod…
Browse files Browse the repository at this point in the history
…ealliance#6789)

Returns a summary of the resources required to instantiate this module or
component.

Potential uses of the returned information:

* Determining whether your pooling allocator configuration supports
  instantiating this module.

* Deciding how many of which `Module` you want to instantiate within a fixed
  amount of resources, e.g. determining whether to create 5 instances of module X
  or 10 instances of module Y.

Part of bytecodealliance#6627.
  • Loading branch information
fitzgen authored and eduardomourar committed Aug 18, 2023
1 parent 31639dd commit 53e5897
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 3 deletions.
91 changes: 89 additions & 2 deletions crates/wasmtime/src/component/component.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::code::CodeObject;
use crate::signatures::SignatureCollection;
use crate::{Engine, Module};
use crate::{Engine, Module, ResourcesRequired};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use std::fs;
Expand All @@ -9,7 +9,8 @@ use std::path::Path;
use std::ptr::NonNull;
use std::sync::Arc;
use wasmtime_environ::component::{
AllCallFunc, ComponentTypes, StaticModuleIndex, TrampolineIndex, Translator,
AllCallFunc, ComponentTypes, GlobalInitializer, InstantiateModule, StaticModuleIndex,
TrampolineIndex, Translator,
};
use wasmtime_environ::{FunctionLoc, ObjectKind, PrimaryMap, ScopeVec};
use wasmtime_jit::{CodeMemory, CompiledModuleInfo};
Expand Down Expand Up @@ -361,6 +362,92 @@ impl Component {
..*dtor.func_ref()
}
}

/// Returns a summary of the resources required to instantiate this
/// [`Component`][crate::component::Component].
///
/// Note that when a component imports and instantiates another component or
/// core module, we cannot determine ahead of time how many resources
/// instantiating this component will require, and therefore this method
/// will return `None` in these scenarios.
///
/// Potential uses of the returned information:
///
/// * Determining whether your pooling allocator configuration supports
/// instantiating this component.
///
/// * Deciding how many of which `Component` you want to instantiate within
/// a fixed amount of resources, e.g. determining whether to create 5
/// instances of component X or 10 instances of component Y.
///
/// # Example
///
/// ```
/// # fn main() -> wasmtime::Result<()> {
/// use wasmtime::{Config, Engine, component::Component};
///
/// let mut config = Config::new();
/// config.wasm_multi_memory(true);
/// config.wasm_component_model(true);
/// let engine = Engine::new(&config)?;
///
/// let component = Component::new(&engine, &r#"
/// (component
/// ;; Define a core module that uses two memories.
/// (core module $m
/// (memory 1)
/// (memory 6)
/// )
///
/// ;; Instantiate that core module three times.
/// (core instance $i1 (instantiate (module $m)))
/// (core instance $i2 (instantiate (module $m)))
/// (core instance $i3 (instantiate (module $m)))
/// )
/// "#)?;
///
/// let resources = component.resources_required()
/// .expect("this component does not import any core modules or instances");
///
/// // Instantiating the component will require allocating two memories per
/// // core instance, and there are three instances, so six total memories.
/// assert_eq!(resources.num_memories, 6);
/// assert_eq!(resources.max_initial_memory_size, Some(6));
///
/// // The component doesn't need any tables.
/// assert_eq!(resources.num_tables, 0);
/// assert_eq!(resources.max_initial_table_size, None);
/// # Ok(()) }
/// ```
pub fn resources_required(&self) -> Option<ResourcesRequired> {
let mut resources = ResourcesRequired {
num_memories: 0,
max_initial_memory_size: None,
num_tables: 0,
max_initial_table_size: None,
};
for init in &self.env_component().initializers {
match init {
GlobalInitializer::InstantiateModule(inst) => match inst {
InstantiateModule::Static(index, _) => {
let module = self.static_module(*index);
resources.add(&module.resources_required());
}
InstantiateModule::Import(_, _) => {
// We can't statically determine the resources required
// to instantiate this component.
return None;
}
},
GlobalInitializer::LowerImport { .. }
| GlobalInitializer::ExtractMemory(_)
| GlobalInitializer::ExtractRealloc(_)
| GlobalInitializer::ExtractPostReturn(_)
| GlobalInitializer::Resource(_) => {}
}
}
Some(resources)
}
}

impl ComponentRuntimeInfo for ComponentInner {
Expand Down
2 changes: 2 additions & 0 deletions crates/wasmtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ mod memory;
mod module;
mod profiling;
mod r#ref;
mod resources;
mod signatures;
mod store;
mod trampoline;
Expand All @@ -424,6 +425,7 @@ pub use crate::memory::*;
pub use crate::module::Module;
pub use crate::profiling::GuestProfiler;
pub use crate::r#ref::ExternRef;
pub use crate::resources::*;
#[cfg(feature = "async")]
pub use crate::store::CallHookHandler;
pub use crate::store::{
Expand Down
72 changes: 71 additions & 1 deletion crates/wasmtime/src/module.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::code::CodeObject;
use crate::{
code::CodeObject,
resources::ResourcesRequired,
signatures::SignatureCollection,
types::{ExportType, ExternType, ImportType},
Engine,
Expand Down Expand Up @@ -904,6 +905,75 @@ impl Module {
&self.inner.engine
}

/// Returns a summary of the resources required to instantiate this
/// [`Module`].
///
/// Potential uses of the returned information:
///
/// * Determining whether your pooling allocator configuration supports
/// instantiating this module.
///
/// * Deciding how many of which `Module` you want to instantiate within a
/// fixed amount of resources, e.g. determining whether to create 5
/// instances of module X or 10 instances of module Y.
///
/// # Example
///
/// ```
/// # fn main() -> wasmtime::Result<()> {
/// use wasmtime::{Config, Engine, Module};
///
/// let mut config = Config::new();
/// config.wasm_multi_memory(true);
/// let engine = Engine::new(&config)?;
///
/// let module = Module::new(&engine, r#"
/// (module
/// ;; Import a memory. Doesn't count towards required resources.
/// (import "a" "b" (memory 10))
/// ;; Define two local memories. These count towards the required
/// ;; resources.
/// (memory 1)
/// (memory 6)
/// )
/// "#)?;
///
/// let resources = module.resources_required();
///
/// // Instantiating the module will require allocating two memories, and
/// // the maximum initial memory size is six Wasm pages.
/// assert_eq!(resources.num_memories, 2);
/// assert_eq!(resources.max_initial_memory_size, Some(6));
///
/// // The module doesn't need any tables.
/// assert_eq!(resources.num_tables, 0);
/// assert_eq!(resources.max_initial_table_size, None);
/// # Ok(()) }
/// ```
pub fn resources_required(&self) -> ResourcesRequired {
let em = self.env_module();
let num_memories = u32::try_from(em.memory_plans.len() - em.num_imported_memories).unwrap();
let max_initial_memory_size = em
.memory_plans
.values()
.skip(em.num_imported_memories)
.map(|plan| plan.memory.minimum)
.max();
let num_tables = u32::try_from(em.table_plans.len() - em.num_imported_tables).unwrap();
let max_initial_table_size = em
.table_plans
.values()
.skip(em.num_imported_tables)
.map(|plan| plan.table.minimum)
.max();
ResourcesRequired {
num_memories,
max_initial_memory_size,
num_tables,
max_initial_table_size,
}
}

/// Returns the `ModuleInner` cast as `ModuleRuntimeInfo` for use
/// by the runtime.
pub(crate) fn runtime_info(&self) -> Arc<dyn wasmtime_runtime::ModuleRuntimeInfo> {
Expand Down
33 changes: 33 additions & 0 deletions crates/wasmtime/src/resources.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/// A summary of the amount of resources required to instantiate a particular
/// [`Module`][crate::Module] or [`Component`][crate::component::Component].
///
/// Example uses of this information:
///
/// * Determining whether your pooling allocator configuration supports
/// instantiating this module.
///
/// * Deciding how many of which `Module` you want to instantiate within a
/// fixed amount of resources, e.g. determining whether to create 5
/// instances of module `X` or 10 instances of module `Y`.
pub struct ResourcesRequired {
/// The number of memories that are required.
pub num_memories: u32,
/// The maximum initial size required by any memory, in units of Wasm pages.
pub max_initial_memory_size: Option<u64>,
/// The number of tables that are required.
pub num_tables: u32,
/// The maximum initial size required by any table.
pub max_initial_table_size: Option<u32>,
}

impl ResourcesRequired {
#[cfg(feature = "component-model")]
pub(crate) fn add(&mut self, other: &ResourcesRequired) {
self.num_memories += other.num_memories;
self.max_initial_memory_size =
std::cmp::max(self.max_initial_memory_size, other.max_initial_memory_size);
self.num_tables += other.num_tables;
self.max_initial_table_size =
std::cmp::max(self.max_initial_table_size, other.max_initial_table_size);
}
}

0 comments on commit 53e5897

Please sign in to comment.