1 //! Tuning Wasmtime for fast instantiation.
2 
3 use wasmtime::format_err;
4 use wasmtime::{
5     Config, Engine, InstanceAllocationStrategy, Linker, Module, PoolingAllocationConfig, Result,
6     Store,
7 };
8 
main() -> Result<()>9 fn main() -> Result<()> {
10     let mut config = Config::new();
11 
12     // Configure and enable the pooling allocator with space for 100 memories of
13     // up to 2GiB in size, 100 tables holding up to 5000 elements, and with a
14     // limit of no more than 100 concurrent instances.
15     let mut pool = PoolingAllocationConfig::new();
16     pool.total_memories(100);
17     pool.max_memory_size(1 << 31); // 2 GiB
18     pool.total_tables(100);
19     pool.table_elements(5000);
20     pool.total_core_instances(100);
21     config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool));
22 
23     // Enable copy-on-write heap images.
24     config.memory_init_cow(true);
25 
26     // Create an engine with our configuration.
27     let engine = Engine::new(&config)?;
28 
29     // Create a linker and populate it with all the imports needed for the Wasm
30     // programs we will run. In a more realistic Wasmtime embedding, this would
31     // probably involve adding WASI functions to the linker, for example.
32     let mut linker = Linker::<()>::new(&engine);
33     linker.func_wrap("math", "add", |a: u32, b: u32| -> u32 { a + b })?;
34 
35     // Create a new module, load a pre-compiled module from disk, or etc...
36     let module = Module::new(
37         &engine,
38         r#"
39             (module
40                 (import "math" "add" (func $add (param i32 i32) (result i32)))
41                 (func (export "run")
42                     (call $add (i32.const 29) (i32.const 13))
43                 )
44             )
45         "#,
46     )?;
47 
48     // Create an `InstancePre` for our module, doing import resolution and
49     // type-checking ahead-of-time and removing it from the instantiation
50     // critical path.
51     let instance_pre = linker.instantiate_pre(&module)?;
52 
53     // Now we can very quickly instantiate our module, so long as we have no
54     // more than 100 concurrent instances at a time!
55     //
56     // For example, we can spawn 100 threads and have each of them instantiate
57     // and run our Wasm module in a loop.
58     //
59     // In a real Wasmtime embedding, this would be doing something like handling
60     // new HTTP requests, game events, or etc... instead of just calling the
61     // same function. A production embedding would likely also be using async,
62     // in which case it would want some sort of back-pressure mechanism (like a
63     // semaphore) on incoming tasks to avoid attempting to allocate more than
64     // the pool's maximum-supported concurrent instances (at which point,
65     // instantiation will start returning errors).
66     let handles: Vec<std::thread::JoinHandle<Result<()>>> = (0..100)
67         .map(|_| {
68             let engine = engine.clone();
69             let instance_pre = instance_pre.clone();
70             std::thread::spawn(move || -> Result<()> {
71                 for _ in 0..999 {
72                     // Create a new store for this instance.
73                     let mut store = Store::new(&engine, ());
74                     // Instantiate our module in this store.
75                     let instance = instance_pre.instantiate(&mut store)?;
76                     // Call the instance's `run` function!
77                     let _result = instance
78                         .get_typed_func::<(), i32>(&mut store, "run")?
79                         .call(&mut store, ());
80                 }
81                 Ok(())
82             })
83         })
84         .collect();
85 
86     // Wait for the threads to finish.
87     for h in handles.into_iter() {
88         h.join().map_err(|_| format_err!("thread panicked!"))??;
89     }
90 
91     Ok(())
92 }
93