1 use crate::prelude::*;
2 use crate::runtime::vm::const_expr::{ConstEvalContext, ConstExprEvaluator};
3 use crate::runtime::vm::imports::Imports;
4 use crate::runtime::vm::instance::{Instance, InstanceHandle};
5 use crate::runtime::vm::memory::Memory;
6 use crate::runtime::vm::mpk::ProtectionKey;
7 use crate::runtime::vm::table::Table;
8 use crate::runtime::vm::{CompiledModuleId, ModuleRuntimeInfo};
9 use crate::store::{Asyncness, InstanceId, StoreOpaque, StoreResourceLimiter};
10 use crate::{OpaqueRootScope, Val};
11 use core::future::Future;
12 use core::pin::Pin;
13 use core::{mem, ptr};
14 use wasmtime_environ::{
15 DefinedMemoryIndex, DefinedTableIndex, HostPtr, InitMemory, MemoryInitialization,
16 MemoryInitializer, Module, SizeOverflow, TableInitialValue, Trap, VMOffsets,
17 };
18
19 #[cfg(feature = "gc")]
20 use crate::runtime::vm::{GcHeap, GcRuntime};
21
22 #[cfg(feature = "component-model")]
23 use wasmtime_environ::{
24 StaticModuleIndex,
25 component::{Component, VMComponentOffsets},
26 };
27
28 mod on_demand;
29 pub use self::on_demand::OnDemandInstanceAllocator;
30
31 #[cfg(feature = "pooling-allocator")]
32 mod pooling;
33 #[cfg(feature = "pooling-allocator")]
34 pub use self::pooling::{
35 InstanceLimits, PoolConcurrencyLimitError, PoolingAllocatorMetrics, PoolingInstanceAllocator,
36 PoolingInstanceAllocatorConfig,
37 };
38
39 /// Represents a request for a new runtime instance.
40 pub struct InstanceAllocationRequest<'a, 'b> {
41 /// The instance id that this will be assigned within the store once the
42 /// allocation has finished.
43 pub id: InstanceId,
44
45 /// The info related to the compiled version of this module,
46 /// needed for instantiation: function metadata, JIT code
47 /// addresses, precomputed images for lazy memory and table
48 /// initialization, and the like. This Arc is cloned and held for
49 /// the lifetime of the instance.
50 pub runtime_info: &'a ModuleRuntimeInfo,
51
52 /// The imports to use for the instantiation.
53 pub imports: Imports<'a>,
54
55 /// The store that this instance is being allocated into.
56 pub store: &'a StoreOpaque,
57
58 /// The store's resource limiter, if configured by the embedder.
59 pub limiter: Option<&'a mut StoreResourceLimiter<'b>>,
60 }
61
62 /// The index of a memory allocation within an `InstanceAllocator`.
63 #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
64 pub struct MemoryAllocationIndex(u32);
65
66 impl Default for MemoryAllocationIndex {
default() -> Self67 fn default() -> Self {
68 // A default `MemoryAllocationIndex` that can be used with
69 // `InstanceAllocator`s that don't actually need indices.
70 MemoryAllocationIndex(u32::MAX)
71 }
72 }
73
74 impl MemoryAllocationIndex {
75 /// Get the underlying index of this `MemoryAllocationIndex`.
76 #[cfg(feature = "pooling-allocator")]
index(&self) -> usize77 pub fn index(&self) -> usize {
78 self.0 as usize
79 }
80 }
81
82 /// The index of a table allocation within an `InstanceAllocator`.
83 #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
84 pub struct TableAllocationIndex(u32);
85
86 impl Default for TableAllocationIndex {
default() -> Self87 fn default() -> Self {
88 // A default `TableAllocationIndex` that can be used with
89 // `InstanceAllocator`s that don't actually need indices.
90 TableAllocationIndex(u32::MAX)
91 }
92 }
93
94 impl TableAllocationIndex {
95 /// Get the underlying index of this `TableAllocationIndex`.
96 #[cfg(feature = "pooling-allocator")]
index(&self) -> usize97 pub fn index(&self) -> usize {
98 self.0 as usize
99 }
100 }
101
102 /// The index of a table allocation within an `InstanceAllocator`.
103 #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
104 pub struct GcHeapAllocationIndex(u32);
105
106 impl Default for GcHeapAllocationIndex {
default() -> Self107 fn default() -> Self {
108 // A default `GcHeapAllocationIndex` that can be used with
109 // `InstanceAllocator`s that don't actually need indices.
110 GcHeapAllocationIndex(u32::MAX)
111 }
112 }
113
114 impl GcHeapAllocationIndex {
115 /// Get the underlying index of this `GcHeapAllocationIndex`.
index(&self) -> usize116 pub fn index(&self) -> usize {
117 self.0 as usize
118 }
119 }
120
121 /// Trait that represents the hooks needed to implement an instance allocator.
122 ///
123 /// Implement this trait when implementing new instance allocators, but don't
124 /// use this trait when you need an instance allocator. Instead use the
125 /// `InstanceAllocator` trait for that, which has additional helper methods and
126 /// a blanket implementation for all types that implement this trait.
127 ///
128 /// # Safety
129 ///
130 /// This trait is unsafe as it requires knowledge of Wasmtime's runtime
131 /// internals to implement correctly.
132 pub unsafe trait InstanceAllocator: Send + Sync {
133 /// Validate whether a component (including all of its contained core
134 /// modules) is allocatable by this instance allocator.
135 #[cfg(feature = "component-model")]
validate_component<'a>( &self, component: &Component, offsets: &VMComponentOffsets<HostPtr>, get_module: &'a dyn Fn(StaticModuleIndex) -> &'a Module, ) -> Result<()>136 fn validate_component<'a>(
137 &self,
138 component: &Component,
139 offsets: &VMComponentOffsets<HostPtr>,
140 get_module: &'a dyn Fn(StaticModuleIndex) -> &'a Module,
141 ) -> Result<()>;
142
143 /// Validate whether a module is allocatable by this instance allocator.
validate_module(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()>144 fn validate_module(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()>;
145
146 /// Validate whether a memory is allocatable by this instance allocator.
147 #[cfg(feature = "gc")]
validate_memory(&self, memory: &wasmtime_environ::Memory) -> Result<()>148 fn validate_memory(&self, memory: &wasmtime_environ::Memory) -> Result<()>;
149
150 /// Increment the count of concurrent component instances that are currently
151 /// allocated, if applicable.
152 ///
153 /// Not all instance allocators will have limits for the maximum number of
154 /// concurrent component instances that can be live at the same time, and
155 /// these allocators may implement this method with a no-op.
156 //
157 // Note: It would be nice to have an associated type that on construction
158 // does the increment and on drop does the decrement but there are two
159 // problems with this:
160 //
161 // 1. This trait's implementations are always used as trait objects, and
162 // associated types are not object safe.
163 //
164 // 2. We would want a parameterized `Drop` implementation so that we could
165 // pass in the `InstanceAllocator` on drop, but this doesn't exist in
166 // Rust. Therefore, we would be forced to add reference counting and
167 // stuff like that to keep a handle on the instance allocator from this
168 // theoretical type. That's a bummer.
169 #[cfg(feature = "component-model")]
increment_component_instance_count(&self) -> Result<()>170 fn increment_component_instance_count(&self) -> Result<()>;
171
172 /// The dual of `increment_component_instance_count`.
173 #[cfg(feature = "component-model")]
decrement_component_instance_count(&self)174 fn decrement_component_instance_count(&self);
175
176 /// Increment the count of concurrent core module instances that are
177 /// currently allocated, if applicable.
178 ///
179 /// Not all instance allocators will have limits for the maximum number of
180 /// concurrent core module instances that can be live at the same time, and
181 /// these allocators may implement this method with a no-op.
increment_core_instance_count(&self) -> Result<()>182 fn increment_core_instance_count(&self) -> Result<()>;
183
184 /// The dual of `increment_core_instance_count`.
decrement_core_instance_count(&self)185 fn decrement_core_instance_count(&self);
186
187 /// Allocate a memory for an instance.
188 ///
189 /// Returns `Err(OutOfMemory)` if boxing the future fails. The inner
190 /// `Result` covers other allocation errors (e.g. resource limits).
allocate_memory<'a, 'b: 'a, 'c: 'a>( &'a self, request: &'a mut InstanceAllocationRequest<'b, 'c>, ty: &'a wasmtime_environ::Memory, memory_index: Option<DefinedMemoryIndex>, ) -> Pin<Box<dyn Future<Output = Result<(MemoryAllocationIndex, Memory)>> + Send + 'a>>191 fn allocate_memory<'a, 'b: 'a, 'c: 'a>(
192 &'a self,
193 request: &'a mut InstanceAllocationRequest<'b, 'c>,
194 ty: &'a wasmtime_environ::Memory,
195 memory_index: Option<DefinedMemoryIndex>,
196 ) -> Pin<Box<dyn Future<Output = Result<(MemoryAllocationIndex, Memory)>> + Send + 'a>>;
197
198 /// Deallocate an instance's previously allocated memory.
199 ///
200 /// # Unsafety
201 ///
202 /// The memory must have previously been allocated by
203 /// `Self::allocate_memory`, be at the given index, and must currently be
204 /// allocated. It must never be used again.
deallocate_memory( &self, memory_index: Option<DefinedMemoryIndex>, allocation_index: MemoryAllocationIndex, memory: Memory, )205 unsafe fn deallocate_memory(
206 &self,
207 memory_index: Option<DefinedMemoryIndex>,
208 allocation_index: MemoryAllocationIndex,
209 memory: Memory,
210 );
211
212 /// Allocate a table for an instance.
213 ///
214 /// Returns `Err(OutOfMemory)` if boxing the future fails. The inner
215 /// `Result` covers other allocation errors (e.g. resource limits).
allocate_table<'a, 'b: 'a, 'c: 'a>( &'a self, req: &'a mut InstanceAllocationRequest<'b, 'c>, table: &'a wasmtime_environ::Table, table_index: DefinedTableIndex, ) -> Pin<Box<dyn Future<Output = Result<(TableAllocationIndex, Table)>> + Send + 'a>>216 fn allocate_table<'a, 'b: 'a, 'c: 'a>(
217 &'a self,
218 req: &'a mut InstanceAllocationRequest<'b, 'c>,
219 table: &'a wasmtime_environ::Table,
220 table_index: DefinedTableIndex,
221 ) -> Pin<Box<dyn Future<Output = Result<(TableAllocationIndex, Table)>> + Send + 'a>>;
222
223 /// Deallocate an instance's previously allocated table.
224 ///
225 /// # Unsafety
226 ///
227 /// The table must have previously been allocated by `Self::allocate_table`,
228 /// be at the given index, and must currently be allocated. It must never be
229 /// used again.
deallocate_table( &self, table_index: DefinedTableIndex, allocation_index: TableAllocationIndex, table: Table, )230 unsafe fn deallocate_table(
231 &self,
232 table_index: DefinedTableIndex,
233 allocation_index: TableAllocationIndex,
234 table: Table,
235 );
236
237 /// Allocates a fiber stack for calling async functions on.
238 #[cfg(feature = "async")]
allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack>239 fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack>;
240
241 /// Deallocates a fiber stack that was previously allocated with
242 /// `allocate_fiber_stack`.
243 ///
244 /// # Safety
245 ///
246 /// The provided stack is required to have been allocated with
247 /// `allocate_fiber_stack`.
248 #[cfg(feature = "async")]
deallocate_fiber_stack(&self, stack: wasmtime_fiber::FiberStack)249 unsafe fn deallocate_fiber_stack(&self, stack: wasmtime_fiber::FiberStack);
250
251 /// Allocate a GC heap for allocating Wasm GC objects within.
252 #[cfg(feature = "gc")]
allocate_gc_heap( &self, engine: &crate::Engine, gc_runtime: &dyn GcRuntime, memory_alloc_index: MemoryAllocationIndex, memory: Memory, ) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)>253 fn allocate_gc_heap(
254 &self,
255 engine: &crate::Engine,
256 gc_runtime: &dyn GcRuntime,
257 memory_alloc_index: MemoryAllocationIndex,
258 memory: Memory,
259 ) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)>;
260
261 /// Deallocate a GC heap that was previously allocated with
262 /// `allocate_gc_heap`.
263 #[cfg(feature = "gc")]
264 #[must_use = "it is the caller's responsibility to deallocate the GC heap's underlying memory \
265 storage after the GC heap is deallocated"]
deallocate_gc_heap( &self, allocation_index: GcHeapAllocationIndex, gc_heap: Box<dyn GcHeap>, ) -> (MemoryAllocationIndex, Memory)266 fn deallocate_gc_heap(
267 &self,
268 allocation_index: GcHeapAllocationIndex,
269 gc_heap: Box<dyn GcHeap>,
270 ) -> (MemoryAllocationIndex, Memory);
271
272 /// Purges all lingering resources related to `module` from within this
273 /// allocator.
274 ///
275 /// Primarily present for the pooling allocator to remove mappings of
276 /// this module from slots in linear memory.
purge_module(&self, module: CompiledModuleId)277 fn purge_module(&self, module: CompiledModuleId);
278
279 /// Use the next available protection key.
280 ///
281 /// The pooling allocator can use memory protection keys (MPK) for
282 /// compressing the guard regions protecting against OOB. Each
283 /// pool-allocated store needs its own key.
next_available_pkey(&self) -> Option<ProtectionKey>284 fn next_available_pkey(&self) -> Option<ProtectionKey>;
285
286 /// Restrict access to memory regions protected by `pkey`.
287 ///
288 /// This is useful for the pooling allocator, which can use memory
289 /// protection keys (MPK). Note: this may still allow access to other
290 /// protection keys, such as the default kernel key; see implementations of
291 /// this.
restrict_to_pkey(&self, pkey: ProtectionKey)292 fn restrict_to_pkey(&self, pkey: ProtectionKey);
293
294 /// Allow access to memory regions protected by any protection key.
allow_all_pkeys(&self)295 fn allow_all_pkeys(&self);
296
297 /// Returns `Some(&PoolingInstanceAllocator)` if this is one.
298 #[cfg(feature = "pooling-allocator")]
as_pooling(&self) -> Option<&PoolingInstanceAllocator>299 fn as_pooling(&self) -> Option<&PoolingInstanceAllocator> {
300 None
301 }
302 }
303
304 impl dyn InstanceAllocator + '_ {
305 /// Allocates a fresh `InstanceHandle` for the `req` given.
306 ///
307 /// This will allocate memories and tables internally from this allocator
308 /// and weave that altogether into a final and complete `InstanceHandle`
309 /// ready to be registered with a store.
310 ///
311 /// Note that the returned instance must still have `.initialize(..)` called
312 /// on it to complete the instantiation process.
313 ///
314 /// # Safety
315 ///
316 /// The `request` provided must be valid, e.g. the imports within are
317 /// correctly sized/typed for the instance being created.
allocate_module( &self, mut request: InstanceAllocationRequest<'_, '_>, ) -> Result<InstanceHandle>318 pub(crate) async unsafe fn allocate_module(
319 &self,
320 mut request: InstanceAllocationRequest<'_, '_>,
321 ) -> Result<InstanceHandle> {
322 let module = request.runtime_info.env_module();
323
324 if cfg!(debug_assertions) {
325 InstanceAllocator::validate_module(self, module, request.runtime_info.offsets())
326 .expect("module should have already been validated before allocation");
327 }
328
329 self.increment_core_instance_count()?;
330
331 let num_defined_memories = module.num_defined_memories();
332 let num_defined_tables = module.num_defined_tables();
333
334 let mut guard = DeallocateOnDrop {
335 run_deallocate: true,
336 memories: TryPrimaryMap::with_capacity(num_defined_memories)?,
337 tables: TryPrimaryMap::with_capacity(num_defined_tables)?,
338 allocator: self,
339 };
340
341 self.allocate_memories(&mut request, &mut guard.memories)
342 .await?;
343 self.allocate_tables(&mut request, &mut guard.tables)
344 .await?;
345 guard.run_deallocate = false;
346 // SAFETY: memories/tables were just allocated from the store within
347 // `request` and this function's own contract requires that the
348 // imports are valid.
349 return unsafe {
350 Ok(Instance::new(
351 request,
352 mem::take(&mut guard.memories),
353 mem::take(&mut guard.tables),
354 )?)
355 };
356
357 struct DeallocateOnDrop<'a> {
358 run_deallocate: bool,
359 memories: TryPrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>,
360 tables: TryPrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>,
361 allocator: &'a (dyn InstanceAllocator + 'a),
362 }
363
364 impl Drop for DeallocateOnDrop<'_> {
365 fn drop(&mut self) {
366 if !self.run_deallocate {
367 return;
368 }
369 // SAFETY: these were previously allocated by this allocator
370 unsafe {
371 self.allocator.deallocate_memories(&mut self.memories);
372 self.allocator.deallocate_tables(&mut self.tables);
373 }
374 self.allocator.decrement_core_instance_count();
375 }
376 }
377 }
378
379 /// Deallocates the provided instance.
380 ///
381 /// This will null-out the pointer within `handle` and otherwise reclaim
382 /// resources such as tables, memories, and the instance memory itself.
383 ///
384 /// # Unsafety
385 ///
386 /// The instance must have previously been allocated by `Self::allocate`.
deallocate_module(&self, handle: &mut InstanceHandle)387 pub(crate) unsafe fn deallocate_module(&self, handle: &mut InstanceHandle) {
388 // SAFETY: the contract of `deallocate_*` is itself a contract of this
389 // function, that the memories/tables were previously allocated from
390 // here.
391 unsafe {
392 self.deallocate_memories(handle.get_mut().memories_mut());
393 self.deallocate_tables(handle.get_mut().tables_mut());
394 }
395
396 self.decrement_core_instance_count();
397 }
398
399 /// Allocate the memories for the given instance allocation request, pushing
400 /// them into `memories`.
allocate_memories( &self, request: &mut InstanceAllocationRequest<'_, '_>, memories: &mut TryPrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>, ) -> Result<()>401 async fn allocate_memories(
402 &self,
403 request: &mut InstanceAllocationRequest<'_, '_>,
404 memories: &mut TryPrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>,
405 ) -> Result<()> {
406 let module = request.runtime_info.env_module();
407
408 if cfg!(debug_assertions) {
409 InstanceAllocator::validate_module(self, module, request.runtime_info.offsets())
410 .expect("module should have already been validated before allocation");
411 }
412
413 for (memory_index, ty) in module.memories.iter().skip(module.num_imported_memories) {
414 let memory_index = module
415 .defined_memory_index(memory_index)
416 .expect("should be a defined memory since we skipped imported ones");
417
418 let memory = self
419 .allocate_memory(request, ty, Some(memory_index))
420 .await?;
421 memories.push(memory)?;
422 }
423
424 Ok(())
425 }
426
427 /// Deallocate all the memories in the given primary map.
428 ///
429 /// # Unsafety
430 ///
431 /// The memories must have previously been allocated by
432 /// `Self::allocate_memories`.
deallocate_memories( &self, memories: &mut TryPrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>, )433 unsafe fn deallocate_memories(
434 &self,
435 memories: &mut TryPrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>,
436 ) {
437 for (memory_index, (allocation_index, memory)) in mem::take(memories) {
438 // Because deallocating memory is infallible, we don't need to worry
439 // about leaking subsequent memories if the first memory failed to
440 // deallocate. If deallocating memory ever becomes fallible, we will
441 // need to be careful here!
442 //
443 // SAFETY: the unsafe contract here is the same as the unsafe
444 // contract of this function, that the memories were previously
445 // allocated by this allocator.
446 unsafe {
447 self.deallocate_memory(Some(memory_index), allocation_index, memory);
448 }
449 }
450 }
451
452 /// Allocate tables for the given instance allocation request, pushing them
453 /// into `tables`.
allocate_tables( &self, request: &mut InstanceAllocationRequest<'_, '_>, tables: &mut TryPrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>, ) -> Result<()>454 async fn allocate_tables(
455 &self,
456 request: &mut InstanceAllocationRequest<'_, '_>,
457 tables: &mut TryPrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>,
458 ) -> Result<()> {
459 let module = request.runtime_info.env_module();
460
461 if cfg!(debug_assertions) {
462 InstanceAllocator::validate_module(self, module, request.runtime_info.offsets())
463 .expect("module should have already been validated before allocation");
464 }
465
466 for (index, table) in module.tables.iter().skip(module.num_imported_tables) {
467 let def_index = module
468 .defined_table_index(index)
469 .expect("should be a defined table since we skipped imported ones");
470
471 let table = self.allocate_table(request, table, def_index).await?;
472 tables.push(table)?;
473 }
474
475 Ok(())
476 }
477
478 /// Deallocate all the tables in the given primary map.
479 ///
480 /// # Unsafety
481 ///
482 /// The tables must have previously been allocated by
483 /// `Self::allocate_tables`.
deallocate_tables( &self, tables: &mut TryPrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>, )484 unsafe fn deallocate_tables(
485 &self,
486 tables: &mut TryPrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>,
487 ) {
488 for (table_index, (allocation_index, table)) in mem::take(tables) {
489 // SAFETY: the tables here were allocated from this allocator per
490 // the contract on this function itself.
491 unsafe {
492 self.deallocate_table(table_index, allocation_index, table);
493 }
494 }
495 }
496 }
497
check_table_init_bounds( store: &mut StoreOpaque, instance: InstanceId, module: &Module, context: &mut ConstEvalContext, const_evaluator: &mut ConstExprEvaluator, ) -> Result<()>498 fn check_table_init_bounds(
499 store: &mut StoreOpaque,
500 instance: InstanceId,
501 module: &Module,
502 context: &mut ConstEvalContext,
503 const_evaluator: &mut ConstExprEvaluator,
504 ) -> Result<()> {
505 let mut store = OpaqueRootScope::new(store);
506
507 for segment in module.table_initialization.segments.iter() {
508 let start = const_evaluator
509 .eval_int(&mut store, context, &segment.offset)
510 .expect("const expression should be valid");
511 let start = get_index(start, module.tables[segment.table_index].idx_type);
512 let end = start.checked_add(segment.elements.len());
513
514 let table = store.instance_mut(instance).get_table(segment.table_index);
515 match end {
516 Some(end) if end <= u64::try_from(table.size())? => {
517 // Initializer is in bounds
518 }
519 _ => {
520 bail!(Trap::TableOutOfBounds);
521 }
522 }
523 }
524
525 Ok(())
526 }
527
initialize_tables( store: &mut StoreOpaque, mut limiter: Option<&mut StoreResourceLimiter<'_>>, context: &mut ConstEvalContext, const_evaluator: &mut ConstExprEvaluator, module: &Module, ) -> Result<()>528 async fn initialize_tables(
529 store: &mut StoreOpaque,
530 mut limiter: Option<&mut StoreResourceLimiter<'_>>,
531 context: &mut ConstEvalContext,
532 const_evaluator: &mut ConstExprEvaluator,
533 module: &Module,
534 ) -> Result<()> {
535 let mut store = OpaqueRootScope::new(store);
536 for (table, init) in module.table_initialization.initial_values.iter() {
537 match init {
538 // Tables are always initially null-initialized at this time
539 TableInitialValue::Null { precomputed: _ } => {}
540
541 TableInitialValue::Expr(expr) => {
542 let init = const_evaluator
543 .eval(&mut store, limiter.as_deref_mut(), context, expr)
544 .await?;
545 let idx = module.table_index(table);
546 let id = store.id();
547 let table = store
548 .instance_mut(context.instance)
549 .get_exported_table(id, idx);
550 let size = table._size(&store);
551 table._fill(&mut store, 0, init.ref_().unwrap(), size)?;
552 }
553 }
554 }
555
556 // Note: if the module's table initializer state is in
557 // FuncTable mode, we will lazily initialize tables based on
558 // any statically-precomputed image of FuncIndexes, but there
559 // may still be "leftover segments" that could not be
560 // incorporated. So we have a unified handler here that
561 // iterates over all segments (Segments mode) or leftover
562 // segments (FuncTable mode) to initialize.
563 for segment in module.table_initialization.segments.iter() {
564 let start = const_evaluator
565 .eval_int(&mut store, context, &segment.offset)
566 .expect("const expression should be valid");
567 let start = get_index(start, module.tables[segment.table_index].idx_type);
568 Instance::table_init_segment(
569 &mut store,
570 limiter.as_deref_mut(),
571 context.asyncness,
572 context.instance,
573 const_evaluator,
574 segment.table_index,
575 &segment.elements,
576 start,
577 0,
578 segment.elements.len(),
579 )
580 .await?;
581 }
582
583 Ok(())
584 }
585
get_index(val: &Val, ty: wasmtime_environ::IndexType) -> u64586 fn get_index(val: &Val, ty: wasmtime_environ::IndexType) -> u64 {
587 match ty {
588 wasmtime_environ::IndexType::I32 => val.unwrap_i32().cast_unsigned().into(),
589 wasmtime_environ::IndexType::I64 => val.unwrap_i64().cast_unsigned(),
590 }
591 }
592
get_memory_init_start( store: &mut StoreOpaque, init: &MemoryInitializer, instance: InstanceId, context: &mut ConstEvalContext, const_evaluator: &mut ConstExprEvaluator, ) -> Result<u64>593 fn get_memory_init_start(
594 store: &mut StoreOpaque,
595 init: &MemoryInitializer,
596 instance: InstanceId,
597 context: &mut ConstEvalContext,
598 const_evaluator: &mut ConstExprEvaluator,
599 ) -> Result<u64> {
600 let mut store = OpaqueRootScope::new(store);
601 const_evaluator
602 .eval_int(&mut store, context, &init.offset)
603 .map(|v| {
604 get_index(
605 v,
606 store.instance(instance).env_module().memories[init.memory_index].idx_type,
607 )
608 })
609 }
610
check_memory_init_bounds( store: &mut StoreOpaque, instance: InstanceId, initializers: &[MemoryInitializer], context: &mut ConstEvalContext, const_evaluator: &mut ConstExprEvaluator, ) -> Result<()>611 fn check_memory_init_bounds(
612 store: &mut StoreOpaque,
613 instance: InstanceId,
614 initializers: &[MemoryInitializer],
615 context: &mut ConstEvalContext,
616 const_evaluator: &mut ConstExprEvaluator,
617 ) -> Result<()> {
618 for init in initializers {
619 let memory = store.instance_mut(instance).get_memory(init.memory_index);
620 let start = get_memory_init_start(store, init, instance, context, const_evaluator)?;
621 let end = usize::try_from(start)
622 .ok()
623 .and_then(|start| start.checked_add(init.data.len()));
624
625 match end {
626 Some(end) if end <= memory.current_length() => {
627 // Initializer is in bounds
628 }
629 _ => {
630 bail!(Trap::MemoryOutOfBounds);
631 }
632 }
633 }
634
635 Ok(())
636 }
637
initialize_memories( store: &mut StoreOpaque, context: &mut ConstEvalContext, const_evaluator: &mut ConstExprEvaluator, module: &Module, ) -> Result<()>638 fn initialize_memories(
639 store: &mut StoreOpaque,
640 context: &mut ConstEvalContext,
641 const_evaluator: &mut ConstExprEvaluator,
642 module: &Module,
643 ) -> Result<()> {
644 // Delegates to the `init_memory` method which is sort of a duplicate of
645 // `instance.memory_init_segment` but is used at compile-time in other
646 // contexts so is shared here to have only one method of memory
647 // initialization.
648 //
649 // This call to `init_memory` notably implements all the bells and whistles
650 // so errors only happen if an out-of-bounds segment is found, in which case
651 // a trap is returned.
652
653 struct InitMemoryAtInstantiation<'a> {
654 module: &'a Module,
655 store: &'a mut StoreOpaque,
656 context: &'a mut ConstEvalContext,
657 const_evaluator: &'a mut ConstExprEvaluator,
658 }
659
660 impl InitMemory for InitMemoryAtInstantiation<'_> {
661 fn memory_size_in_bytes(
662 &mut self,
663 memory: wasmtime_environ::MemoryIndex,
664 ) -> Result<u64, SizeOverflow> {
665 let len = self
666 .store
667 .instance(self.context.instance)
668 .get_memory(memory)
669 .current_length();
670 let len = u64::try_from(len).unwrap();
671 Ok(len)
672 }
673
674 fn eval_offset(
675 &mut self,
676 memory: wasmtime_environ::MemoryIndex,
677 expr: &wasmtime_environ::ConstExpr,
678 ) -> Option<u64> {
679 let mut store = OpaqueRootScope::new(&mut *self.store);
680 let val = self
681 .const_evaluator
682 .eval_int(&mut store, self.context, expr)
683 .expect("const expression should be valid");
684 Some(get_index(
685 val,
686 store.instance(self.context.instance).env_module().memories[memory].idx_type,
687 ))
688 }
689
690 fn write(
691 &mut self,
692 memory_index: wasmtime_environ::MemoryIndex,
693 init: &wasmtime_environ::StaticMemoryInitializer,
694 ) -> bool {
695 // If this initializer applies to a defined memory but that memory
696 // doesn't need initialization, due to something like copy-on-write
697 // pre-initializing it via mmap magic, then this initializer can be
698 // skipped entirely.
699 let instance = self.store.instance_mut(self.context.instance);
700 if let Some(memory_index) = self.module.defined_memory_index(memory_index) {
701 if !instance.memories[memory_index].1.needs_init() {
702 return true;
703 }
704 }
705 let memory = instance.get_memory(memory_index);
706
707 unsafe {
708 let src = instance.wasm_data(init.data.clone());
709 let offset = usize::try_from(init.offset).unwrap();
710 let dst = memory.base.as_ptr().add(offset);
711
712 assert!(offset + src.len() <= memory.current_length());
713
714 // FIXME audit whether this is safe in the presence of shared
715 // memory
716 // (https://github.com/bytecodealliance/wasmtime/issues/4203).
717 ptr::copy_nonoverlapping(src.as_ptr(), dst, src.len())
718 }
719 true
720 }
721 }
722
723 let ok = module
724 .memory_initialization
725 .init_memory(&mut InitMemoryAtInstantiation {
726 module,
727 store,
728 context,
729 const_evaluator,
730 });
731 if !ok {
732 return Err(Trap::MemoryOutOfBounds.into());
733 }
734
735 Ok(())
736 }
737
check_init_bounds( store: &mut StoreOpaque, instance: InstanceId, context: &mut ConstEvalContext, const_evaluator: &mut ConstExprEvaluator, module: &Module, ) -> Result<()>738 fn check_init_bounds(
739 store: &mut StoreOpaque,
740 instance: InstanceId,
741 context: &mut ConstEvalContext,
742 const_evaluator: &mut ConstExprEvaluator,
743 module: &Module,
744 ) -> Result<()> {
745 check_table_init_bounds(store, instance, module, context, const_evaluator)?;
746
747 match &module.memory_initialization {
748 MemoryInitialization::Segmented(initializers) => {
749 check_memory_init_bounds(store, instance, initializers, context, const_evaluator)?;
750 }
751 // Statically validated already to have everything in-bounds.
752 MemoryInitialization::Static { .. } => {}
753 }
754
755 Ok(())
756 }
757
initialize_globals( store: &mut StoreOpaque, mut limiter: Option<&mut StoreResourceLimiter<'_>>, context: &mut ConstEvalContext, const_evaluator: &mut ConstExprEvaluator, module: &Module, ) -> Result<()>758 async fn initialize_globals(
759 store: &mut StoreOpaque,
760 mut limiter: Option<&mut StoreResourceLimiter<'_>>,
761 context: &mut ConstEvalContext,
762 const_evaluator: &mut ConstExprEvaluator,
763 module: &Module,
764 ) -> Result<()> {
765 assert!(core::ptr::eq(
766 &**store.instance(context.instance).env_module(),
767 module
768 ));
769
770 let mut store = OpaqueRootScope::new(store);
771
772 for (index, init) in module.global_initializers.iter() {
773 // Attempt a simple, synchronous evaluation before hitting the
774 // general-purpose `.await` point below. This benchmarks ~15% faster in
775 // instantiation vs just falling through to `.await` below.
776 let val = if let Some(val) = const_evaluator.try_simple(init) {
777 val
778 } else {
779 const_evaluator
780 .eval(&mut store, limiter.as_deref_mut(), context, init)
781 .await?
782 };
783
784 let id = store.id();
785 let index = module.global_index(index);
786 let mut instance = store.instance_mut(context.instance);
787
788 #[cfg(feature = "wmemcheck")]
789 if index.as_u32() == 0
790 && module.globals[index].wasm_ty == wasmtime_environ::WasmValType::I32
791 {
792 if let Some(wmemcheck) = instance.as_mut().wmemcheck_state_mut() {
793 let size = usize::try_from(val.unwrap_i32()).unwrap();
794 wmemcheck.set_stack_size(size);
795 }
796 }
797
798 let global = instance.as_mut().get_exported_global(id, index);
799
800 // Note that mutability is bypassed here because this is, by definition,
801 // initialization of globals meaning that if it's an immutable global
802 // this is the one and only write.
803 //
804 // SAFETY: this is a valid module so `val` should have the correct type
805 // for this global, and it's safe to write to a global for the first
806 // time as-is happening here.
807 unsafe {
808 global.set_unchecked(&mut store, &val)?;
809 }
810 }
811 Ok(())
812 }
813
initialize_instance( store: &mut StoreOpaque, mut limiter: Option<&mut StoreResourceLimiter<'_>>, instance: InstanceId, module: &Module, is_bulk_memory: bool, asyncness: Asyncness, ) -> Result<()>814 pub async fn initialize_instance(
815 store: &mut StoreOpaque,
816 mut limiter: Option<&mut StoreResourceLimiter<'_>>,
817 instance: InstanceId,
818 module: &Module,
819 is_bulk_memory: bool,
820 asyncness: Asyncness,
821 ) -> Result<()> {
822 let mut context = ConstEvalContext::new(instance, asyncness);
823 let mut const_evaluator = ConstExprEvaluator::default();
824
825 // If bulk memory is not enabled, bounds check the data and element segments before
826 // making any changes. With bulk memory enabled, initializers are processed
827 // in-order and side effects are observed up to the point of an out-of-bounds
828 // initializer, so the early checking is not desired.
829 if !is_bulk_memory {
830 check_init_bounds(store, instance, &mut context, &mut const_evaluator, module)?;
831 }
832
833 initialize_globals(
834 store,
835 limiter.as_deref_mut(),
836 &mut context,
837 &mut const_evaluator,
838 module,
839 )
840 .await?;
841 initialize_tables(
842 store,
843 limiter.as_deref_mut(),
844 &mut context,
845 &mut const_evaluator,
846 module,
847 )
848 .await?;
849 initialize_memories(store, &mut context, &mut const_evaluator, &module)?;
850
851 Ok(())
852 }
853
854 #[cfg(test)]
855 mod tests {
856 use super::*;
857
858 #[test]
allocator_traits_are_object_safe()859 fn allocator_traits_are_object_safe() {
860 fn _instance_allocator(_: &dyn InstanceAllocator) {}
861 }
862 }
863