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