wasmer_compiler/engine/
tunables.rs

1use crate::engine::error::LinkError;
2use std::ptr::NonNull;
3use wasmer_types::entity::{EntityRef, PrimaryMap};
4use wasmer_types::{
5    target::{PointerWidth, Target},
6    FunctionType, GlobalType, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, LocalTagIndex,
7    MemoryIndex, MemoryType, ModuleInfo, Pages, TableIndex, TableType, TagKind,
8};
9use wasmer_vm::{InternalStoreHandle, MemoryError, StoreObjects, VMTag};
10use wasmer_vm::{MemoryStyle, TableStyle};
11use wasmer_vm::{VMConfig, VMGlobal, VMMemory, VMTable};
12use wasmer_vm::{VMMemoryDefinition, VMTableDefinition};
13
14/// An engine delegates the creation of memories, tables, and globals
15/// to a foreign implementor of this trait.
16pub trait Tunables {
17    /// Construct a `MemoryStyle` for the provided `MemoryType`
18    fn memory_style(&self, memory: &MemoryType) -> MemoryStyle;
19
20    /// Construct a `TableStyle` for the provided `TableType`
21    fn table_style(&self, table: &TableType) -> TableStyle;
22
23    /// Create a memory owned by the host given a [`MemoryType`] and a [`MemoryStyle`].
24    fn create_host_memory(
25        &self,
26        ty: &MemoryType,
27        style: &MemoryStyle,
28    ) -> Result<VMMemory, MemoryError>;
29
30    /// Create a memory owned by the VM given a [`MemoryType`] and a [`MemoryStyle`].
31    ///
32    /// # Safety
33    /// - `vm_definition_location` must point to a valid location in VM memory.
34    unsafe fn create_vm_memory(
35        &self,
36        ty: &MemoryType,
37        style: &MemoryStyle,
38        vm_definition_location: NonNull<VMMemoryDefinition>,
39    ) -> Result<VMMemory, MemoryError>;
40
41    /// Create a table owned by the host given a [`TableType`] and a [`TableStyle`].
42    fn create_host_table(&self, ty: &TableType, style: &TableStyle) -> Result<VMTable, String>;
43
44    /// Create a table owned by the VM given a [`TableType`] and a [`TableStyle`].
45    ///
46    /// # Safety
47    /// - `vm_definition_location` must point to a valid location in VM memory.
48    unsafe fn create_vm_table(
49        &self,
50        ty: &TableType,
51        style: &TableStyle,
52        vm_definition_location: NonNull<VMTableDefinition>,
53    ) -> Result<VMTable, String>;
54
55    /// Create a global with an unset value.
56    fn create_global(&self, ty: GlobalType) -> Result<VMGlobal, String> {
57        Ok(VMGlobal::new(ty))
58    }
59
60    /// Create a new tag.
61    fn create_tag(&self, kind: TagKind, ty: FunctionType) -> Result<VMTag, String> {
62        Ok(VMTag::new(kind, ty))
63    }
64
65    /// Allocate memory for just the memories of the current module.
66    ///
67    /// # Safety
68    /// - `memory_definition_locations` must point to a valid locations in VM memory.
69    #[allow(clippy::result_large_err)]
70    unsafe fn create_memories(
71        &self,
72        context: &mut StoreObjects,
73        module: &ModuleInfo,
74        memory_styles: &PrimaryMap<MemoryIndex, MemoryStyle>,
75        memory_definition_locations: &[NonNull<VMMemoryDefinition>],
76    ) -> Result<PrimaryMap<LocalMemoryIndex, InternalStoreHandle<VMMemory>>, LinkError> {
77        let num_imports = module.num_imported_memories;
78        let mut memories: PrimaryMap<LocalMemoryIndex, _> =
79            PrimaryMap::with_capacity(module.memories.len() - num_imports);
80        for (index, mdl) in memory_definition_locations
81            .iter()
82            .enumerate()
83            .take(module.memories.len())
84            .skip(num_imports)
85        {
86            let mi = MemoryIndex::new(index);
87            let ty = &module.memories[mi];
88            let style = &memory_styles[mi];
89            memories.push(InternalStoreHandle::new(
90                context,
91                self.create_vm_memory(ty, style, *mdl)
92                    .map_err(|e| LinkError::Resource(format!("Failed to create memory: {e}")))?,
93            ));
94        }
95        Ok(memories)
96    }
97
98    /// Allocate memory for just the tables of the current module.
99    ///
100    /// # Safety
101    ///
102    /// To be done
103    #[allow(clippy::result_large_err)]
104    unsafe fn create_tables(
105        &self,
106        context: &mut StoreObjects,
107        module: &ModuleInfo,
108        table_styles: &PrimaryMap<TableIndex, TableStyle>,
109        table_definition_locations: &[NonNull<VMTableDefinition>],
110    ) -> Result<PrimaryMap<LocalTableIndex, InternalStoreHandle<VMTable>>, LinkError> {
111        let num_imports = module.num_imported_tables;
112        let mut tables: PrimaryMap<LocalTableIndex, _> =
113            PrimaryMap::with_capacity(module.tables.len() - num_imports);
114        for (index, tdl) in table_definition_locations
115            .iter()
116            .enumerate()
117            .take(module.tables.len())
118            .skip(num_imports)
119        {
120            let ti = TableIndex::new(index);
121            let ty = &module.tables[ti];
122            let style = &table_styles[ti];
123            tables.push(InternalStoreHandle::new(
124                context,
125                self.create_vm_table(ty, style, *tdl)
126                    .map_err(LinkError::Resource)?,
127            ));
128        }
129        Ok(tables)
130    }
131
132    /// Allocate memory for just the tags of the current module,
133    /// with initializers applied.
134    #[allow(clippy::result_large_err)]
135    fn create_tags(
136        &self,
137        context: &mut StoreObjects,
138        module: &ModuleInfo,
139    ) -> Result<PrimaryMap<LocalTagIndex, InternalStoreHandle<VMTag>>, LinkError> {
140        let num_imports = module.num_imported_tags;
141        let mut vmctx_tags = PrimaryMap::with_capacity(module.tags.len() - num_imports);
142
143        for &tag_type in module.tags.values().skip(num_imports) {
144            let sig_ty = if let Some(sig_ty) = module.signatures.get(tag_type) {
145                sig_ty
146            } else {
147                return Err(LinkError::Resource(format!(
148                    "Could not find matching signature for tag index {tag_type:?}"
149                )));
150            };
151            vmctx_tags.push(InternalStoreHandle::new(
152                context,
153                self.create_tag(wasmer_types::TagKind::Exception, sig_ty.clone())
154                    .map_err(LinkError::Resource)?,
155            ));
156        }
157
158        Ok(vmctx_tags)
159    }
160
161    /// Allocate memory for just the globals of the current module,
162    /// with initializers applied.
163    #[allow(clippy::result_large_err)]
164    fn create_globals(
165        &self,
166        context: &mut StoreObjects,
167        module: &ModuleInfo,
168    ) -> Result<PrimaryMap<LocalGlobalIndex, InternalStoreHandle<VMGlobal>>, LinkError> {
169        let num_imports = module.num_imported_globals;
170        let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len() - num_imports);
171
172        for &global_type in module.globals.values().skip(num_imports) {
173            vmctx_globals.push(InternalStoreHandle::new(
174                context,
175                self.create_global(global_type)
176                    .map_err(LinkError::Resource)?,
177            ));
178        }
179
180        Ok(vmctx_globals)
181    }
182
183    /// Get the VMConfig for this tunables
184    /// Currently, VMConfig have optional Stack size
185    /// If wasm_stack_size is left to None (the default value)
186    /// then the global stack size will be use
187    /// Else the defined stack size will be used. Size is in byte
188    /// and the value might be rounded to sane value is needed.
189    fn vmconfig(&self) -> &VMConfig {
190        &VMConfig {
191            wasm_stack_size: None,
192        }
193    }
194}
195
196/// Tunable parameters for WebAssembly compilation.
197/// This is the reference implementation of the `Tunables` trait,
198/// used by default.
199///
200/// You can use this as a template for creating a custom Tunables
201/// implementation or use composition to wrap your Tunables around
202/// this one. The later approach is demonstrated in the
203/// tunables-limit-memory example.
204#[derive(Clone)]
205pub struct BaseTunables {
206    /// For static heaps, the size in wasm pages of the heap protected by bounds checking.
207    pub static_memory_bound: Pages,
208
209    /// The size in bytes of the offset guard for static heaps.
210    pub static_memory_offset_guard_size: u64,
211
212    /// The size in bytes of the offset guard for dynamic heaps.
213    pub dynamic_memory_offset_guard_size: u64,
214}
215
216impl BaseTunables {
217    /// Get the `BaseTunables` for a specific Target
218    pub fn for_target(target: &Target) -> Self {
219        let triple = target.triple();
220        let pointer_width: PointerWidth = triple.pointer_width().unwrap();
221        let (static_memory_bound, static_memory_offset_guard_size): (Pages, u64) =
222            match pointer_width {
223                PointerWidth::U16 => (0x400.into(), 0x1000),
224                PointerWidth::U32 => (0x4000.into(), 0x1_0000),
225                // Static Memory Bound:
226                //   Allocating 4 GiB of address space let us avoid the
227                //   need for explicit bounds checks.
228                // Static Memory Guard size:
229                //   Allocating 2 GiB of address space lets us translate wasm
230                //   offsets into x86 offsets as aggressively as we can.
231                PointerWidth::U64 => (0x1_0000.into(), 0x8000_0000),
232            };
233
234        // Allocate a small guard to optimize common cases but without
235        // wasting too much memory.
236        // The Windows memory manager seems more laxed than the other ones
237        // And a guard of just 1 page may not be enough is some borderline cases
238        // So using 2 pages for guard on this platform
239        #[cfg(target_os = "windows")]
240        let dynamic_memory_offset_guard_size: u64 = 0x2_0000;
241        #[cfg(not(target_os = "windows"))]
242        let dynamic_memory_offset_guard_size: u64 = 0x1_0000;
243
244        Self {
245            static_memory_bound,
246            static_memory_offset_guard_size,
247            dynamic_memory_offset_guard_size,
248        }
249    }
250}
251
252impl Tunables for BaseTunables {
253    /// Get a `MemoryStyle` for the provided `MemoryType`
254    fn memory_style(&self, memory: &MemoryType) -> MemoryStyle {
255        // A heap with a maximum that doesn't exceed the static memory bound specified by the
256        // tunables make it static.
257        //
258        // If the module doesn't declare an explicit maximum treat it as 4GiB.
259        let maximum = memory.maximum.unwrap_or_else(Pages::max_value);
260        if maximum <= self.static_memory_bound {
261            MemoryStyle::Static {
262                // Bound can be larger than the maximum for performance reasons
263                bound: self.static_memory_bound,
264                offset_guard_size: self.static_memory_offset_guard_size,
265            }
266        } else {
267            MemoryStyle::Dynamic {
268                offset_guard_size: self.dynamic_memory_offset_guard_size,
269            }
270        }
271    }
272
273    /// Get a [`TableStyle`] for the provided [`TableType`].
274    fn table_style(&self, _table: &TableType) -> TableStyle {
275        TableStyle::CallerChecksSignature
276    }
277
278    /// Create a memory owned by the host given a [`MemoryType`] and a [`MemoryStyle`].
279    fn create_host_memory(
280        &self,
281        ty: &MemoryType,
282        style: &MemoryStyle,
283    ) -> Result<VMMemory, MemoryError> {
284        VMMemory::new(ty, style)
285    }
286
287    /// Create a memory owned by the VM given a [`MemoryType`] and a [`MemoryStyle`].
288    ///
289    /// # Safety
290    /// - `vm_definition_location` must point to a valid, owned `VMMemoryDefinition`,
291    ///   for example in `VMContext`.
292    unsafe fn create_vm_memory(
293        &self,
294        ty: &MemoryType,
295        style: &MemoryStyle,
296        vm_definition_location: NonNull<VMMemoryDefinition>,
297    ) -> Result<VMMemory, MemoryError> {
298        VMMemory::from_definition(ty, style, vm_definition_location)
299    }
300
301    /// Create a table owned by the host given a [`TableType`] and a [`TableStyle`].
302    fn create_host_table(&self, ty: &TableType, style: &TableStyle) -> Result<VMTable, String> {
303        VMTable::new(ty, style)
304    }
305
306    /// Create a table owned by the VM given a [`TableType`] and a [`TableStyle`].
307    ///
308    /// # Safety
309    /// - `vm_definition_location` must point to a valid, owned `VMTableDefinition`,
310    ///   for example in `VMContext`.
311    unsafe fn create_vm_table(
312        &self,
313        ty: &TableType,
314        style: &TableStyle,
315        vm_definition_location: NonNull<VMTableDefinition>,
316    ) -> Result<VMTable, String> {
317        VMTable::from_definition(ty, style, vm_definition_location)
318    }
319}
320
321impl Tunables for Box<dyn Tunables + Send + Sync> {
322    fn memory_style(&self, memory: &MemoryType) -> MemoryStyle {
323        self.as_ref().memory_style(memory)
324    }
325
326    fn table_style(&self, table: &TableType) -> TableStyle {
327        self.as_ref().table_style(table)
328    }
329
330    fn create_host_memory(
331        &self,
332        ty: &MemoryType,
333        style: &MemoryStyle,
334    ) -> Result<VMMemory, MemoryError> {
335        self.as_ref().create_host_memory(ty, style)
336    }
337
338    unsafe fn create_vm_memory(
339        &self,
340        ty: &MemoryType,
341        style: &MemoryStyle,
342        vm_definition_location: NonNull<VMMemoryDefinition>,
343    ) -> Result<VMMemory, MemoryError> {
344        self.as_ref()
345            .create_vm_memory(ty, style, vm_definition_location)
346    }
347
348    fn create_host_table(&self, ty: &TableType, style: &TableStyle) -> Result<VMTable, String> {
349        self.as_ref().create_host_table(ty, style)
350    }
351
352    unsafe fn create_vm_table(
353        &self,
354        ty: &TableType,
355        style: &TableStyle,
356        vm_definition_location: NonNull<VMTableDefinition>,
357    ) -> Result<VMTable, String> {
358        self.as_ref()
359            .create_vm_table(ty, style, vm_definition_location)
360    }
361}
362
363impl Tunables for std::sync::Arc<dyn Tunables + Send + Sync> {
364    fn memory_style(&self, memory: &MemoryType) -> MemoryStyle {
365        self.as_ref().memory_style(memory)
366    }
367
368    fn table_style(&self, table: &TableType) -> TableStyle {
369        self.as_ref().table_style(table)
370    }
371
372    fn create_host_memory(
373        &self,
374        ty: &MemoryType,
375        style: &MemoryStyle,
376    ) -> Result<VMMemory, MemoryError> {
377        self.as_ref().create_host_memory(ty, style)
378    }
379
380    unsafe fn create_vm_memory(
381        &self,
382        ty: &MemoryType,
383        style: &MemoryStyle,
384        vm_definition_location: NonNull<VMMemoryDefinition>,
385    ) -> Result<VMMemory, MemoryError> {
386        self.as_ref()
387            .create_vm_memory(ty, style, vm_definition_location)
388    }
389
390    fn create_host_table(&self, ty: &TableType, style: &TableStyle) -> Result<VMTable, String> {
391        self.as_ref().create_host_table(ty, style)
392    }
393
394    unsafe fn create_vm_table(
395        &self,
396        ty: &TableType,
397        style: &TableStyle,
398        vm_definition_location: NonNull<VMTableDefinition>,
399    ) -> Result<VMTable, String> {
400        self.as_ref()
401            .create_vm_table(ty, style, vm_definition_location)
402    }
403}