wasmer_compiler/engine/
tunables.rs

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