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::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 2 GiB of address space lets us translate wasm
217                //   offsets into x86 offsets as aggressively as we can.
218                PointerWidth::U64 => (0x1_0000.into(), 0x8000_0000),
219            };
220
221        // Allocate a small guard to optimize common cases but without
222        // wasting too much memory.
223        // The Windows memory manager seems more laxed than the other ones
224        // And a guard of just 1 page may not be enough is some borderline cases
225        // So using 2 pages for guard on this platform
226        #[cfg(target_os = "windows")]
227        let dynamic_memory_offset_guard_size: u64 = 0x2_0000;
228        #[cfg(not(target_os = "windows"))]
229        let dynamic_memory_offset_guard_size: u64 = 0x1_0000;
230
231        Self {
232            static_memory_bound,
233            static_memory_offset_guard_size,
234            dynamic_memory_offset_guard_size,
235        }
236    }
237}
238
239impl Tunables for BaseTunables {
240    /// Get a `MemoryStyle` for the provided `MemoryType`
241    fn memory_style(&self, memory: &MemoryType) -> MemoryStyle {
242        // A heap with a maximum that doesn't exceed the static memory bound specified by the
243        // tunables make it static.
244        //
245        // If the module doesn't declare an explicit maximum treat it as 4GiB.
246        let maximum = memory.maximum.unwrap_or_else(Pages::max_value);
247        if maximum <= self.static_memory_bound {
248            MemoryStyle::Static {
249                // Bound can be larger than the maximum for performance reasons
250                bound: self.static_memory_bound,
251                offset_guard_size: self.static_memory_offset_guard_size,
252            }
253        } else {
254            MemoryStyle::Dynamic {
255                offset_guard_size: self.dynamic_memory_offset_guard_size,
256            }
257        }
258    }
259
260    /// Get a [`TableStyle`] for the provided [`TableType`].
261    fn table_style(&self, _table: &TableType) -> TableStyle {
262        TableStyle::CallerChecksSignature
263    }
264
265    /// Create a memory owned by the host given a [`MemoryType`] and a [`MemoryStyle`].
266    fn create_host_memory(
267        &self,
268        ty: &MemoryType,
269        style: &MemoryStyle,
270    ) -> Result<VMMemory, MemoryError> {
271        VMMemory::new(ty, style)
272    }
273
274    /// Create a memory owned by the VM given a [`MemoryType`] and a [`MemoryStyle`].
275    ///
276    /// # Safety
277    /// - `vm_definition_location` must point to a valid, owned `VMMemoryDefinition`,
278    ///   for example in `VMContext`.
279    unsafe fn create_vm_memory(
280        &self,
281        ty: &MemoryType,
282        style: &MemoryStyle,
283        vm_definition_location: NonNull<VMMemoryDefinition>,
284    ) -> Result<VMMemory, MemoryError> {
285        unsafe { VMMemory::from_definition(ty, style, vm_definition_location) }
286    }
287
288    /// Create a table owned by the host given a [`TableType`] and a [`TableStyle`].
289    fn create_host_table(&self, ty: &TableType, style: &TableStyle) -> Result<VMTable, String> {
290        VMTable::new(ty, style)
291    }
292
293    /// Create a table owned by the VM given a [`TableType`] and a [`TableStyle`].
294    ///
295    /// # Safety
296    /// - `vm_definition_location` must point to a valid, owned `VMTableDefinition`,
297    ///   for example in `VMContext`.
298    unsafe fn create_vm_table(
299        &self,
300        ty: &TableType,
301        style: &TableStyle,
302        vm_definition_location: NonNull<VMTableDefinition>,
303    ) -> Result<VMTable, String> {
304        unsafe { VMTable::from_definition(ty, style, vm_definition_location) }
305    }
306}
307
308impl Tunables for Box<dyn Tunables + Send + Sync> {
309    fn memory_style(&self, memory: &MemoryType) -> MemoryStyle {
310        self.as_ref().memory_style(memory)
311    }
312
313    fn table_style(&self, table: &TableType) -> TableStyle {
314        self.as_ref().table_style(table)
315    }
316
317    fn create_host_memory(
318        &self,
319        ty: &MemoryType,
320        style: &MemoryStyle,
321    ) -> Result<VMMemory, MemoryError> {
322        self.as_ref().create_host_memory(ty, style)
323    }
324
325    unsafe fn create_vm_memory(
326        &self,
327        ty: &MemoryType,
328        style: &MemoryStyle,
329        vm_definition_location: NonNull<VMMemoryDefinition>,
330    ) -> Result<VMMemory, MemoryError> {
331        unsafe {
332            self.as_ref()
333                .create_vm_memory(ty, style, vm_definition_location)
334        }
335    }
336
337    fn create_host_table(&self, ty: &TableType, style: &TableStyle) -> Result<VMTable, String> {
338        self.as_ref().create_host_table(ty, style)
339    }
340
341    unsafe fn create_vm_table(
342        &self,
343        ty: &TableType,
344        style: &TableStyle,
345        vm_definition_location: NonNull<VMTableDefinition>,
346    ) -> Result<VMTable, String> {
347        unsafe {
348            self.as_ref()
349                .create_vm_table(ty, style, vm_definition_location)
350        }
351    }
352}
353
354impl Tunables for std::sync::Arc<dyn Tunables + Send + Sync> {
355    fn memory_style(&self, memory: &MemoryType) -> MemoryStyle {
356        self.as_ref().memory_style(memory)
357    }
358
359    fn table_style(&self, table: &TableType) -> TableStyle {
360        self.as_ref().table_style(table)
361    }
362
363    fn create_host_memory(
364        &self,
365        ty: &MemoryType,
366        style: &MemoryStyle,
367    ) -> Result<VMMemory, MemoryError> {
368        self.as_ref().create_host_memory(ty, style)
369    }
370
371    unsafe fn create_vm_memory(
372        &self,
373        ty: &MemoryType,
374        style: &MemoryStyle,
375        vm_definition_location: NonNull<VMMemoryDefinition>,
376    ) -> Result<VMMemory, MemoryError> {
377        unsafe {
378            self.as_ref()
379                .create_vm_memory(ty, style, vm_definition_location)
380        }
381    }
382
383    fn create_host_table(&self, ty: &TableType, style: &TableStyle) -> Result<VMTable, String> {
384        self.as_ref().create_host_table(ty, style)
385    }
386
387    unsafe fn create_vm_table(
388        &self,
389        ty: &TableType,
390        style: &TableStyle,
391        vm_definition_location: NonNull<VMTableDefinition>,
392    ) -> Result<VMTable, String> {
393        unsafe {
394            self.as_ref()
395                .create_vm_table(ty, style, vm_definition_location)
396        }
397    }
398}