From 2aa1f31106b9c95500633f5bd9d5f041d408d3e3 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 3 May 2026 18:09:28 +0200 Subject: docs: add more doctests, reduce minumum fuel usage Signed-off-by: Henry --- crates/tinywasm/src/engine.rs | 26 +++++++ crates/tinywasm/src/func.rs | 67 +++++++++++++++-- crates/tinywasm/src/imports.rs | 18 +++-- crates/tinywasm/src/instance.rs | 111 ++++++++++++++++++++++++++++ crates/tinywasm/src/interpreter/executor.rs | 6 +- crates/tinywasm/src/reference.rs | 18 +++++ crates/tinywasm/src/store/mod.rs | 14 +++- examples/wasm-rust.rs | 1 - rustfmt.toml | 2 + 9 files changed, 242 insertions(+), 21 deletions(-) diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs index 652a07c..dfb4d15 100644 --- a/crates/tinywasm/src/engine.rs +++ b/crates/tinywasm/src/engine.rs @@ -4,6 +4,19 @@ pub use crate::store::{LazyLinearMemory, LinearMemory, MemoryBackend, PagedMemor /// Global configuration for the WebAssembly interpreter /// /// Can be cheaply cloned and shared across multiple executions and threads. +/// +/// ## Example +/// ```rust +/// use tinywasm::engine::{Config, StackConfig}; +/// use tinywasm::{Engine, Store}; +/// +/// let config = Config::new() +/// .with_value_stack(StackConfig::dynamic(1024, 16 * 1024)) +/// .with_call_stack(StackConfig::fixed(256)); +/// let engine = Engine::new(config); +/// let store = Store::new(engine); +/// # _ = store; +/// ``` #[derive(Clone, Default)] #[cfg_attr(feature = "debug", derive(Debug))] pub struct Engine { @@ -72,6 +85,19 @@ impl StackConfig { } /// Configuration for the WebAssembly interpreter +/// +/// ## Example +/// ```rust +/// use tinywasm::engine::{Config, FuelPolicy, MemoryBackend, StackConfig}; +/// +/// let config = Config::new() +/// .with_fuel_policy(FuelPolicy::Weighted) +/// .with_value_stack(StackConfig::dynamic(1024, 16 * 1024)) +/// .with_memory_backend(MemoryBackend::paged(64 * 1024)) +/// .with_trap_on_oom(true); +/// +/// assert!(matches!(config.fuel_policy(), FuelPolicy::Weighted)); +/// ``` #[derive(Clone)] #[cfg_attr(feature = "debug", derive(Debug))] #[non_exhaustive] diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index fa8e755..e3d1d2a 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -120,6 +120,37 @@ impl HostFunction { } /// Create a new untyped host function import. + /// + /// ## Example + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// # use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store}; + /// # use tinywasm::types::{FuncType, WasmType, WasmValue}; + /// # let wasm = wat::parse_str(r#" + /// # (module + /// # (import "host" "add_one" (func $add_one (param i32) (result i32))) + /// # (func (export "call") (param i32) (result i32) + /// # local.get 0 + /// # call $add_one)) + /// # "#).expect("valid wat"); + /// # let module = tinywasm::parse_bytes(&wasm)?; + /// let mut store = Store::default(); + /// let ty = FuncType::new(&[WasmType::I32], &[WasmType::I32]); + /// let add_one = HostFunction::from_untyped(&mut store, &ty, |_ctx: FuncContext<'_>, args| { + /// let WasmValue::I32(value) = args[0] else { + /// return Err(tinywasm::Error::Other("expected i32".into())); + /// }; + /// Ok(vec![WasmValue::I32(value + 1)]) + /// }); + /// + /// let mut imports = Imports::new(); + /// imports.define("host", "add_one", add_one); + /// # let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + /// # let call = instance.func::(&store, "call")?; + /// # assert_eq!(call.call(&mut store, 41)?, 42); + /// # Ok(()) + /// # } + /// ``` pub fn from_untyped( store: &mut Store, ty: &FuncType, @@ -150,6 +181,30 @@ impl HostFunction { } /// Create a new typed host function import. + /// + /// ## Example + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// # use tinywasm::{HostFunction, Imports, ModuleInstance, Store}; + /// # let wasm = wat::parse_str(r#" + /// # (module + /// # (import "host" "add_one" (func $add_one (param i32) (result i32))) + /// # (func (export "call") (param i32) (result i32) + /// # local.get 0 + /// # call $add_one)) + /// # "#).expect("valid wat"); + /// # let module = tinywasm::parse_bytes(&wasm)?; + /// let mut store = Store::default(); + /// let add_one = HostFunction::from(&mut store, |_ctx, value: i32| Ok(value + 1)); + /// + /// let mut imports = Imports::new(); + /// imports.define("host", "add_one", add_one); + /// # let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + /// # let call = instance.func::(&store, "call")?; + /// # assert_eq!(call.call(&mut store, 41)?, 42); + /// # Ok(()) + /// # } + /// ``` pub fn from(store: &mut Store, func: impl Fn(FuncContext<'_>, P) -> Result + 'static) -> Function where P: FromWasmValues + ToWasmTypes, @@ -604,14 +659,10 @@ impl_tuple!(impl_tuple_traits); /// # let mut store = Store::default(); /// # let instance = ModuleInstance::instantiate(&mut store, &module, None)?; /// -/// type Params = WasmTupleChain< -/// (i32, i32, i32, i32, i32, i32), -/// (i32, i32, i32, i32, i32, i32, i32), -/// >; -/// type Results = WasmTupleChain< -/// (i32, i32, i32, i32, i32, i32), -/// (i32, i32, i32, i32, i32, i32, i32), -/// >; +/// type Params = +/// WasmTupleChain<(i32, i32, i32, i32, i32, i32), (i32, i32, i32, i32, i32, i32, i32)>; +/// type Results = +/// WasmTupleChain<(i32, i32, i32, i32, i32, i32), (i32, i32, i32, i32, i32, i32, i32)>; /// /// let echo13 = instance.func::(&store, "echo13")?; /// let result = echo13.call(&mut store, ((1, 2, 3, 4, 5, 6), (7, 8, 9, 10, 11, 12, 13)).into())?; diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index e9f1029..900b8bd 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -67,24 +67,30 @@ impl From<&Import> for ExternName { /// ```rust /// # use log; /// # fn main() -> tinywasm::Result<()> { +/// use tinywasm::types::{GlobalType, MemoryType, TableType, WasmType, WasmValue}; /// use tinywasm::{Global, HostFunction, Imports, Memory, ModuleInstance, Store, Table}; -/// use tinywasm::types::{WasmType, TableType, MemoryType, WasmValue}; /// # let wasm = wat::parse_str("(module)").expect("valid wat"); /// # let module = tinywasm::parse_bytes(&wasm)?; /// # let mut store = Store::default(); /// # let my_other_instance = ModuleInstance::instantiate(&mut store, &module, None)?; /// let mut imports = Imports::new(); /// -/// // function args can be either a single -/// // value that implements `TryFrom` or a tuple of them /// let print_i32 = HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, arg: i32| { /// log::debug!("print_i32: {}", arg); /// Ok(()) /// }); /// -/// let table = Table::new(&mut store, TableType::new(WasmType::RefFunc, 10, Some(20)), WasmValue::default_for(WasmType::RefFunc))?; -/// let memory = Memory::new(&mut store, MemoryType::default().with_page_count_initial(1).with_page_count_max(Some(2)))?; -/// let global_i32 = Global::new(&mut store, tinywasm::types::GlobalType::default().with_ty(WasmType::I32), WasmValue::I32(666))?; +/// let table = Table::new( +/// &mut store, +/// TableType::new(WasmType::RefFunc, 10, Some(20)), +/// WasmValue::default_for(WasmType::RefFunc), +/// )?; +/// let memory = Memory::new( +/// &mut store, +/// MemoryType::default().with_page_count_initial(1).with_page_count_max(Some(2)), +/// )?; +/// let global_i32 = +/// Global::new(&mut store, GlobalType::default().with_ty(WasmType::I32), WasmValue::I32(666))?; /// /// imports /// .define("my_module", "print_i32", print_i32) diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index b050f0b..179f0c7 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -193,6 +193,30 @@ impl ModuleInstance { /// Instantiate the module in the given store (without running the start function) /// + /// ## Example + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// # use tinywasm::{ModuleInstance, Store}; + /// # let wasm = wat::parse_str(r#" + /// # (module + /// # (global $g (mut i32) (i32.const 0)) + /// # (func $start + /// # i32.const 42 + /// # global.set $g) + /// # (start $start) + /// # (export "g" (global $g))) + /// # "#).expect("valid wat"); + /// # let module = tinywasm::parse_bytes(&wasm)?; + /// let mut store = Store::default(); + /// let instance = ModuleInstance::instantiate_no_start(&mut store, &module, None)?; + /// + /// assert_eq!(instance.global_get(&store, "g")?, 0.into()); + /// instance.start(&mut store)?; + /// assert_eq!(instance.global_get(&store, "g")?, 42.into()); + /// # Ok(()) + /// # } + /// ``` + /// /// See pub fn instantiate_no_start(store: &mut Store, module: &Module, imports: Option) -> Result { let idx = store.next_module_instance_idx(); @@ -251,6 +275,33 @@ impl ModuleInstance { } /// Returns an iterator over all exported extern values for this instance. + /// + /// ## Example + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// # use tinywasm::{ExternItem, ModuleInstance, Store}; + /// # let wasm = wat::parse_str(r#" + /// # (module + /// # (func (export "f")) + /// # (memory (export "mem") 1)) + /// # "#).expect("valid wat"); + /// # let module = tinywasm::parse_bytes(&wasm)?; + /// # let mut store = Store::default(); + /// let instance = ModuleInstance::instantiate(&mut store, &module, None)?; + /// + /// let mut saw_func = false; + /// let mut saw_memory = false; + /// for (name, item) in instance.exports() { + /// match (name, item) { + /// ("f", ExternItem::Func(_)) => saw_func = true, + /// ("mem", ExternItem::Memory(_)) => saw_memory = true, + /// _ => {} + /// } + /// } + /// assert!(saw_func && saw_memory); + /// # Ok(()) + /// # } + /// ``` pub fn exports(&self) -> impl Iterator + '_ { self.0.exports.iter().map(move |export| { let item = match export.kind { @@ -302,6 +353,26 @@ impl ModuleInstance { } /// Get any exported extern value by name. + /// + /// ## Example + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// # use tinywasm::{ExternItem, ModuleInstance, Store}; + /// # let wasm = wat::parse_str(r#" + /// # (module + /// # (global (export "answer") i32 (i32.const 42))) + /// # "#).expect("valid wat"); + /// # let module = tinywasm::parse_bytes(&wasm)?; + /// # let mut store = Store::default(); + /// let instance = ModuleInstance::instantiate(&mut store, &module, None)?; + /// + /// let ExternItem::Global(global) = instance.extern_item("answer")? else { + /// panic!("expected global export"); + /// }; + /// assert_eq!(global.get(&store)?, 42.into()); + /// # Ok(()) + /// # } + /// ``` pub fn extern_item(&self, name: &str) -> Result { match self.require_export(name)? { ExternVal::Func(addr) => { @@ -530,6 +601,23 @@ impl ModuleInstance { /// Returns None if the module has no start function /// If no start function is specified, also checks for a `_start` function in the exports /// + /// ## Example + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// # use tinywasm::{ModuleInstance, Store}; + /// # let wasm = wat::parse_str(r#" + /// # (module + /// # (func (export "_start")) + /// # ) + /// # "#).expect("valid wat"); + /// # let module = tinywasm::parse_bytes(&wasm)?; + /// # let mut store = Store::default(); + /// let instance = ModuleInstance::instantiate_no_start(&mut store, &module, None)?; + /// assert!(instance.start_func(&store)?.is_some()); + /// # Ok(()) + /// # } + /// ``` + /// /// See pub fn start_func(&self, store: &Store) -> Result> { self.validate_store(store)?; @@ -564,6 +652,29 @@ impl ModuleInstance { /// /// Returns `None` if the module has no start function /// + /// ## Example + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// # use tinywasm::{ModuleInstance, Store}; + /// # let wasm = wat::parse_str(r#" + /// # (module + /// # (global $g (mut i32) (i32.const 0)) + /// # (func (export "_start") + /// # i32.const 7 + /// # global.set $g) + /// # (export "g" (global $g))) + /// # "#).expect("valid wat"); + /// # let module = tinywasm::parse_bytes(&wasm)?; + /// let mut store = Store::default(); + /// let instance = ModuleInstance::instantiate_no_start(&mut store, &module, None)?; + /// + /// assert_eq!(instance.global_get(&store, "g")?, 0.into()); + /// assert_eq!(instance.start(&mut store)?, Some(())); + /// assert_eq!(instance.global_get(&store, "g")?, 7.into()); + /// # Ok(()) + /// # } + /// ``` + /// /// See pub fn start(&self, store: &mut Store) -> Result> { match self.start_func(store)? { diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 4c941c8..f4e1352 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -1568,7 +1568,7 @@ impl<'store> Executor<'store, false> { } loop { - for _ in 0..1024 { + for _ in 0..128 { if self.exec()?.is_some() { return Ok(ExecState::Completed); } @@ -1590,13 +1590,13 @@ impl<'store> Executor<'store, true> { } loop { - for _ in 0..1024 { + for _ in 0..128 { if self.exec()?.is_some() { return Ok(ExecState::Completed); } } - self.store.execution_fuel = self.store.execution_fuel.saturating_sub(1024_u32); + self.store.execution_fuel = self.store.execution_fuel.saturating_sub(128); if self.store.execution_fuel == 0 { return Ok(ExecState::Suspended(self.cf)); } diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 8f02317..8541062 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -33,6 +33,24 @@ impl StoreItem { } /// A memory instance in a store. +/// +/// ## Example +/// ```rust +/// # fn main() -> tinywasm::Result<()> { +/// use tinywasm::types::MemoryType; +/// use tinywasm::{Memory, Store}; +/// +/// let mut store = Store::default(); +/// let memory = Memory::new(&mut store, MemoryType::default().with_page_count_initial(1))?; +/// +/// memory.copy_from_slice(&mut store, 0, b"hi")?; +/// assert_eq!(memory.read_vec(&store, 0, 2)?, b"hi"); +/// assert_eq!(memory.page_count(&store)?, 1); +/// memory.grow(&mut store, 1)?; +/// assert_eq!(memory.page_count(&store)?, 2); +/// # Ok(()) +/// # } +/// ``` #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "debug", derive(Debug))] pub struct Memory(pub(crate) StoreItem); diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index a4d26e3..446c9ed 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -24,11 +24,19 @@ static STORE_ID: AtomicUsize = AtomicUsize::new(0); /// Global state that can be manipulated by WebAssembly programs /// -/// Data should only be addressable by the module that owns it -/// /// Note that the state doesn't do any garbage collection - so it will grow /// indefinitely if you keep adding modules to it. When calling temporary -/// functions, you should create a new store and then drop it when you're done (e.g. in a request handler) +/// functions, you should create a new store and then drop it when you're done (e.g. in a request handler). +/// +/// ## Example +/// ```rust +/// use tinywasm::engine::{Config, StackConfig}; +/// use tinywasm::{Engine, Store}; +/// +/// let engine = Engine::new(Config::new().with_call_stack(StackConfig::dynamic(64, 512))); +/// let store = Store::new(engine); +/// # _ = store; +/// ``` /// /// See pub struct Store { diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 7240bef..e0b9968 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -19,7 +19,6 @@ use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store}; /// `rustup target add wasm32-unknown-unknown`. /// /// -/// fn main() -> Result<()> { pretty_env_logger::init(); diff --git a/rustfmt.toml b/rustfmt.toml index 589b2d2..0fd4e90 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,4 @@ +unstable_features=true +format_code_in_doc_comments=true max_width=120 use_small_heuristics="Max" -- cgit v1.3.1