Skip to main content

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