1use crate::executor::wasmtime::{
10 host::HostState,
11 instance_wrapper::{EntryPoint, InstanceWrapper, MemoryWrapper},
12 util::{self, replace_strategy_if_broken},
13};
14
15use crate::executor::common::{
16 error::{Error, Result, WasmError},
17 runtime_blob::RuntimeBlob,
18 util::checked_range,
19 wasm_runtime::{HeapAllocStrategy, WasmInstance, WasmModule},
20};
21use parking_lot::Mutex;
22use std::{
23 path::{Path, PathBuf},
24 sync::{
25 atomic::{AtomicBool, Ordering},
26 Arc,
27 },
28};
29use subsoil::allocator::{AllocationStats, FreeingBumpHeapAllocator};
30use subsoil::runtime_interface::unpack_ptr_and_len;
31use subsoil::wasm_interface::{HostFunctions, Pointer, WordSize};
32use wasmtime::{AsContext, Cache, CacheConfig, Engine, Memory};
33
34const MAX_INSTANCE_COUNT: u32 = 64;
35
36#[derive(Default)]
37pub(crate) struct StoreData {
38 pub(crate) host_state: Option<HostState>,
40 pub(crate) memory: Option<Memory>,
42}
43
44impl StoreData {
45 pub fn host_state_mut(&mut self) -> Option<&mut HostState> {
47 self.host_state.as_mut()
48 }
49
50 pub fn memory(&self) -> Memory {
52 self.memory.expect("memory is always set; qed")
53 }
54}
55
56pub(crate) type Store = wasmtime::Store<StoreData>;
57
58enum Strategy {
59 RecreateInstance(InstanceCreator),
60}
61
62struct InstanceCreator {
63 engine: Engine,
64 instance_pre: Arc<wasmtime::InstancePre<StoreData>>,
65 instance_counter: Arc<InstanceCounter>,
66}
67
68impl InstanceCreator {
69 fn instantiate(&mut self) -> Result<InstanceWrapper> {
70 InstanceWrapper::new(&self.engine, &self.instance_pre, self.instance_counter.clone())
71 }
72}
73
74pub(crate) struct ReleaseInstanceHandle {
76 counter: Arc<InstanceCounter>,
77}
78
79impl Drop for ReleaseInstanceHandle {
80 fn drop(&mut self) {
81 {
82 let mut counter = self.counter.counter.lock();
83 *counter = counter.saturating_sub(1);
84 }
85
86 self.counter.wait_for_instance.notify_one();
87 }
88}
89
90#[derive(Default)]
97pub(crate) struct InstanceCounter {
98 counter: Mutex<u32>,
99 wait_for_instance: parking_lot::Condvar,
100}
101
102impl InstanceCounter {
103 pub fn acquire_instance(self: Arc<Self>) -> ReleaseInstanceHandle {
110 let mut counter = self.counter.lock();
111
112 while *counter >= MAX_INSTANCE_COUNT {
113 self.wait_for_instance.wait(&mut counter);
114 }
115 *counter += 1;
116
117 ReleaseInstanceHandle { counter: self.clone() }
118 }
119}
120
121pub struct WasmtimeRuntime {
124 engine: Engine,
125 instance_pre: Arc<wasmtime::InstancePre<StoreData>>,
126 instantiation_strategy: InternalInstantiationStrategy,
127 instance_counter: Arc<InstanceCounter>,
128}
129
130impl WasmModule for WasmtimeRuntime {
131 fn new_instance(&self) -> Result<Box<dyn WasmInstance>> {
132 let strategy = match self.instantiation_strategy {
133 InternalInstantiationStrategy::Builtin => Strategy::RecreateInstance(InstanceCreator {
134 engine: self.engine.clone(),
135 instance_pre: self.instance_pre.clone(),
136 instance_counter: self.instance_counter.clone(),
137 }),
138 };
139
140 Ok(Box::new(WasmtimeInstance { strategy }))
141 }
142}
143
144pub struct WasmtimeInstance {
147 strategy: Strategy,
148}
149
150impl WasmtimeInstance {
151 fn call_impl(
152 &mut self,
153 method: &str,
154 data: &[u8],
155 allocation_stats: &mut Option<AllocationStats>,
156 ) -> Result<Vec<u8>> {
157 match &mut self.strategy {
158 Strategy::RecreateInstance(ref mut instance_creator) => {
159 let mut instance_wrapper = instance_creator.instantiate()?;
160 let heap_base = instance_wrapper.extract_heap_base()?;
161 let entrypoint = instance_wrapper.resolve_entrypoint(method)?;
162 let allocator = FreeingBumpHeapAllocator::new(heap_base);
163
164 perform_call(data, &mut instance_wrapper, entrypoint, allocator, allocation_stats)
165 },
166 }
167 }
168}
169
170impl WasmInstance for WasmtimeInstance {
171 fn call_with_allocation_stats(
172 &mut self,
173 method: &str,
174 data: &[u8],
175 ) -> (Result<Vec<u8>>, Option<AllocationStats>) {
176 let mut allocation_stats = None;
177 let result = self.call_impl(method, data, &mut allocation_stats);
178 (result, allocation_stats)
179 }
180}
181
182fn setup_wasmtime_caching(
186 cache_path: &Path,
187 config: &mut wasmtime::Config,
188) -> std::result::Result<(), String> {
189 use std::fs;
190
191 let wasmtime_cache_root = cache_path.join("wasmtime");
192 fs::create_dir_all(&wasmtime_cache_root)
193 .map_err(|err| format!("cannot create the dirs to cache: {}", err))?;
194
195 let mut cache_config = CacheConfig::new();
196 cache_config.with_directory(cache_path);
197
198 let cache =
199 Cache::new(cache_config).map_err(|err| format!("failed to initiate Cache: {err:?}"))?;
200
201 config.cache(Some(cache));
202
203 Ok(())
204}
205
206fn common_config(semantics: &Semantics) -> std::result::Result<wasmtime::Config, WasmError> {
207 let mut config = wasmtime::Config::new();
208 config.cranelift_opt_level(wasmtime::OptLevel::SpeedAndSize);
209 config.cranelift_nan_canonicalization(semantics.canonicalize_nans);
210
211 let profiler = match std::env::var_os("WASMTIME_PROFILING_STRATEGY") {
212 Some(os_string) if os_string == "jitdump" => wasmtime::ProfilingStrategy::JitDump,
213 Some(os_string) if os_string == "perfmap" => wasmtime::ProfilingStrategy::PerfMap,
214 None => wasmtime::ProfilingStrategy::None,
215 Some(_) => {
216 static UNKNOWN_PROFILING_STRATEGY: AtomicBool = AtomicBool::new(false);
218 if !UNKNOWN_PROFILING_STRATEGY.swap(true, Ordering::Relaxed) {
220 log::warn!("WASMTIME_PROFILING_STRATEGY is set to unknown value, ignored.");
221 }
222 wasmtime::ProfilingStrategy::None
223 },
224 };
225 config.profiler(profiler);
226
227 let native_stack_max = match semantics.deterministic_stack_limit {
228 Some(DeterministicStackLimit { native_stack_max, .. }) => native_stack_max,
229
230 None => 1024 * 1024,
235 };
236
237 config.max_wasm_stack(native_stack_max as usize);
238
239 config.parallel_compilation(semantics.parallel_compilation);
240
241 config.wasm_reference_types(semantics.wasm_reference_types);
244 config.wasm_simd(semantics.wasm_simd);
245 config.wasm_relaxed_simd(semantics.wasm_simd);
246 config.wasm_bulk_memory(semantics.wasm_bulk_memory);
247 config.wasm_multi_value(semantics.wasm_multi_value);
248 config.wasm_multi_memory(false);
249 config.wasm_threads(false);
250 config.wasm_memory64(false);
251 config.wasm_tail_call(false);
252 config.wasm_extended_const(false);
253
254 let (use_pooling, use_cow) = match semantics.instantiation_strategy {
255 InstantiationStrategy::PoolingCopyOnWrite => (true, true),
256 InstantiationStrategy::Pooling => (true, false),
257 InstantiationStrategy::RecreateInstanceCopyOnWrite => (false, true),
258 InstantiationStrategy::RecreateInstance => (false, false),
259 };
260
261 const WASM_PAGE_SIZE: u64 = 65536;
262
263 config.memory_init_cow(use_cow);
264 config.memory_guaranteed_dense_image_size(match semantics.heap_alloc_strategy {
265 HeapAllocStrategy::Dynamic { maximum_pages } => {
266 maximum_pages.map(|p| p as u64 * WASM_PAGE_SIZE).unwrap_or(u64::MAX)
267 },
268 HeapAllocStrategy::Static { .. } => u64::MAX,
269 });
270
271 if use_pooling {
272 const MAX_WASM_PAGES: u64 = 0x10000;
273
274 let memory_pages = match semantics.heap_alloc_strategy {
275 HeapAllocStrategy::Dynamic { maximum_pages } => {
276 maximum_pages.map(|p| p as u64).unwrap_or(MAX_WASM_PAGES)
277 },
278 HeapAllocStrategy::Static { .. } => MAX_WASM_PAGES,
279 };
280
281 let mut pooling_config = wasmtime::PoolingAllocationConfig::default();
282 pooling_config
283 .max_unused_warm_slots(4)
284 .max_core_instance_size(512 * 1024)
292 .table_elements(8192)
293 .max_memory_size(memory_pages as usize * WASM_PAGE_SIZE as usize)
294 .total_tables(MAX_INSTANCE_COUNT)
295 .total_memories(MAX_INSTANCE_COUNT)
296 .total_core_instances(MAX_INSTANCE_COUNT);
299
300 config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling(pooling_config));
301 }
302
303 Ok(config)
304}
305
306#[derive(Clone)]
332pub struct DeterministicStackLimit {
333 pub logical_max: u32,
338 pub native_stack_max: u32,
349}
350
351#[non_exhaustive]
361#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
362pub enum InstantiationStrategy {
363 PoolingCopyOnWrite,
368
369 RecreateInstanceCopyOnWrite,
372
373 Pooling,
376
377 RecreateInstance,
379}
380
381enum InternalInstantiationStrategy {
382 Builtin,
383}
384
385#[derive(Clone)]
386pub struct Semantics {
387 pub instantiation_strategy: InstantiationStrategy,
389
390 pub deterministic_stack_limit: Option<DeterministicStackLimit>,
401
402 pub canonicalize_nans: bool,
415
416 pub parallel_compilation: bool,
418
419 pub heap_alloc_strategy: HeapAllocStrategy,
421
422 pub wasm_multi_value: bool,
424
425 pub wasm_bulk_memory: bool,
427
428 pub wasm_reference_types: bool,
430
431 pub wasm_simd: bool,
433}
434
435#[derive(Clone)]
436pub struct Config {
437 pub allow_missing_func_imports: bool,
442
443 pub cache_path: Option<PathBuf>,
445
446 pub semantics: Semantics,
448}
449
450enum CodeSupplyMode<'a> {
451 Fresh(RuntimeBlob),
453
454 Precompiled(&'a Path),
462
463 PrecompiledBytes(&'a [u8]),
468}
469
470pub fn create_runtime<H>(
476 blob: RuntimeBlob,
477 config: Config,
478) -> std::result::Result<WasmtimeRuntime, WasmError>
479where
480 H: HostFunctions,
481{
482 unsafe { do_create_runtime::<H>(CodeSupplyMode::Fresh(blob), config) }
484}
485
486pub unsafe fn create_runtime_from_artifact<H>(
503 compiled_artifact_path: &Path,
504 config: Config,
505) -> std::result::Result<WasmtimeRuntime, WasmError>
506where
507 H: HostFunctions,
508{
509 do_create_runtime::<H>(CodeSupplyMode::Precompiled(compiled_artifact_path), config)
510}
511
512pub unsafe fn create_runtime_from_artifact_bytes<H>(
528 compiled_artifact_bytes: &[u8],
529 config: Config,
530) -> std::result::Result<WasmtimeRuntime, WasmError>
531where
532 H: HostFunctions,
533{
534 do_create_runtime::<H>(CodeSupplyMode::PrecompiledBytes(compiled_artifact_bytes), config)
535}
536
537unsafe fn do_create_runtime<H>(
542 code_supply_mode: CodeSupplyMode<'_>,
543 mut config: Config,
544) -> std::result::Result<WasmtimeRuntime, WasmError>
545where
546 H: HostFunctions,
547{
548 replace_strategy_if_broken(&mut config.semantics.instantiation_strategy);
549
550 let mut wasmtime_config = common_config(&config.semantics)?;
551 if let Some(ref cache_path) = config.cache_path {
552 if let Err(reason) = setup_wasmtime_caching(cache_path, &mut wasmtime_config) {
553 log::warn!(
554 "failed to setup wasmtime cache. Performance may degrade significantly: {}.",
555 reason,
556 );
557 }
558 }
559
560 let engine = Engine::new(&wasmtime_config)
561 .map_err(|e| WasmError::Other(format!("cannot create the wasmtime engine: {:#}", e)))?;
562
563 let (module, instantiation_strategy) = match code_supply_mode {
564 CodeSupplyMode::Fresh(blob) => {
565 let blob = prepare_blob_for_compilation(blob, &config.semantics)?;
566 let serialized_blob = blob.clone().serialize();
567
568 let module = wasmtime::Module::new(&engine, &serialized_blob)
569 .map_err(|e| WasmError::Other(format!("cannot create module: {:#}", e)))?;
570
571 match config.semantics.instantiation_strategy {
572 InstantiationStrategy::Pooling
573 | InstantiationStrategy::PoolingCopyOnWrite
574 | InstantiationStrategy::RecreateInstance
575 | InstantiationStrategy::RecreateInstanceCopyOnWrite => {
576 (module, InternalInstantiationStrategy::Builtin)
577 },
578 }
579 },
580 CodeSupplyMode::Precompiled(compiled_artifact_path) => {
581 let module = wasmtime::Module::deserialize_file(&engine, compiled_artifact_path)
586 .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:#}", e)))?;
587
588 (module, InternalInstantiationStrategy::Builtin)
589 },
590 CodeSupplyMode::PrecompiledBytes(compiled_artifact_bytes) => {
591 let module = wasmtime::Module::deserialize(&engine, compiled_artifact_bytes)
596 .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:#}", e)))?;
597
598 (module, InternalInstantiationStrategy::Builtin)
599 },
600 };
601
602 let mut linker = wasmtime::Linker::new(&engine);
603 crate::executor::wasmtime::imports::prepare_imports::<H>(
604 &mut linker,
605 &module,
606 config.allow_missing_func_imports,
607 )?;
608
609 let instance_pre = linker
610 .instantiate_pre(&module)
611 .map_err(|e| WasmError::Other(format!("cannot preinstantiate module: {:#}", e)))?;
612
613 Ok(WasmtimeRuntime {
614 engine,
615 instance_pre: Arc::new(instance_pre),
616 instantiation_strategy,
617 instance_counter: Default::default(),
618 })
619}
620
621fn prepare_blob_for_compilation(
622 mut blob: RuntimeBlob,
623 semantics: &Semantics,
624) -> std::result::Result<RuntimeBlob, WasmError> {
625 if let Some(DeterministicStackLimit { logical_max, .. }) = semantics.deterministic_stack_limit {
626 blob = blob.inject_stack_depth_metering(logical_max)?;
627 }
628
629 blob.convert_memory_import_into_export()?;
634 blob.setup_memory_according_to_heap_alloc_strategy(semantics.heap_alloc_strategy)?;
635
636 Ok(blob)
637}
638
639pub fn prepare_runtime_artifact(
642 blob: RuntimeBlob,
643 semantics: &Semantics,
644) -> std::result::Result<Vec<u8>, WasmError> {
645 let mut semantics = semantics.clone();
646 replace_strategy_if_broken(&mut semantics.instantiation_strategy);
647
648 let blob = prepare_blob_for_compilation(blob, &semantics)?;
649
650 let engine = Engine::new(&common_config(&semantics)?)
651 .map_err(|e| WasmError::Other(format!("cannot create the engine: {:#}", e)))?;
652
653 engine
654 .precompile_module(&blob.serialize())
655 .map_err(|e| WasmError::Other(format!("cannot precompile module: {:#}", e)))
656}
657
658fn perform_call(
659 data: &[u8],
660 instance_wrapper: &mut InstanceWrapper,
661 entrypoint: EntryPoint,
662 mut allocator: FreeingBumpHeapAllocator,
663 allocation_stats: &mut Option<AllocationStats>,
664) -> Result<Vec<u8>> {
665 let (data_ptr, data_len) = inject_input_data(instance_wrapper, &mut allocator, data)?;
666
667 let host_state = HostState::new(allocator);
668
669 instance_wrapper.store_mut().data_mut().host_state = Some(host_state);
671
672 let ret = entrypoint
673 .call(instance_wrapper.store_mut(), data_ptr, data_len)
674 .map(unpack_ptr_and_len);
675
676 let host_state = instance_wrapper.store_mut().data_mut().host_state.take().expect(
678 "the host state is always set before calling into WASM so it can't be None here; qed",
679 );
680 *allocation_stats = Some(host_state.allocation_stats());
681
682 let (output_ptr, output_len) = ret?;
683 let output = extract_output_data(instance_wrapper, output_ptr, output_len)?;
684
685 Ok(output)
686}
687
688fn inject_input_data(
689 instance: &mut InstanceWrapper,
690 allocator: &mut FreeingBumpHeapAllocator,
691 data: &[u8],
692) -> Result<(Pointer<u8>, WordSize)> {
693 let mut ctx = instance.store_mut();
694 let memory = ctx.data().memory();
695 let data_len = data.len() as WordSize;
696 let data_ptr = allocator.allocate(&mut MemoryWrapper(&memory, &mut ctx), data_len)?;
697 util::write_memory_from(instance.store_mut(), data_ptr, data)?;
698 Ok((data_ptr, data_len))
699}
700
701fn extract_output_data(
702 instance: &InstanceWrapper,
703 output_ptr: u32,
704 output_len: u32,
705) -> Result<Vec<u8>> {
706 let ctx = instance.store();
707
708 let memory_size = ctx.as_context().data().memory().data_size(ctx);
714 if checked_range(output_ptr as usize, output_len as usize, memory_size).is_none() {
715 Err(Error::OutputExceedsBounds)?
716 }
717 let mut output = vec![0; output_len as usize];
718
719 util::read_memory_into(ctx, Pointer::new(output_ptr), &mut output)?;
720 Ok(output)
721}