winch_codegen/codegen/
env.rs

1use crate::{
2    abi::{wasm_sig, ABISig, ABI},
3    codegen::{control, BlockSig, BuiltinFunction, BuiltinFunctions, OperandSize},
4    isa::TargetIsa,
5};
6use cranelift_codegen::ir::{UserExternalName, UserExternalNameRef};
7use std::collections::{
8    hash_map::Entry::{Occupied, Vacant},
9    HashMap,
10};
11use std::mem;
12use wasmparser::BlockType;
13use wasmtime_environ::{
14    BuiltinFunctionIndex, FuncIndex, GlobalIndex, MemoryIndex, MemoryPlan, MemoryStyle,
15    ModuleTranslation, ModuleTypesBuilder, PrimaryMap, PtrSize, TableIndex, TablePlan, TypeConvert,
16    TypeIndex, VMOffsets, WasmHeapType, WasmValType,
17};
18
19#[derive(Debug, Clone, Copy)]
20pub struct GlobalData {
21    /// The offset of the global.
22    pub offset: u32,
23    /// True if the global is imported.
24    pub imported: bool,
25    /// The WebAssembly type of the global.
26    pub ty: WasmValType,
27}
28
29/// Table metadata.
30#[derive(Debug, Copy, Clone)]
31pub struct TableData {
32    /// The offset to the base of the table.
33    pub offset: u32,
34    /// The offset to the current elements field.
35    pub current_elems_offset: u32,
36    /// If the table is imported, this field contains the offset to locate the
37    /// base of the table data.
38    pub import_from: Option<u32>,
39    /// The size of the table elements.
40    pub(crate) element_size: OperandSize,
41    /// The size of the current elements field.
42    pub(crate) current_elements_size: OperandSize,
43}
44
45/// Style of the heap.
46#[derive(Debug, Copy, Clone)]
47pub enum HeapStyle {
48    /// Static heap, which has a fixed address.
49    Static {
50        /// The heap bound in bytes, not including the bytes for the offset
51        /// guard pages.
52        bound: u64,
53    },
54    /// Dynamic heap, which can be relocated to a different address when grown.
55    /// The bounds are calculated at runtime on every access.
56    Dynamic,
57}
58
59/// Heap metadata.
60///
61/// Heaps represent a WebAssembly linear memory.
62#[derive(Debug, Copy, Clone)]
63pub struct HeapData {
64    /// The offset to the base of the heap.
65    /// Relative to the VMContext pointer if the WebAssembly memory is locally
66    /// defined. Else this is relative to the location of the imported WebAssembly
67    /// memory location.
68    pub offset: u32,
69    /// The offset to the current length field.
70    pub current_length_offset: u32,
71    /// If the WebAssembly memory is imported, this field contains the offset to locate the
72    /// base of the heap.
73    pub import_from: Option<u32>,
74    /// The memory type (32 or 64).
75    pub ty: WasmValType,
76    /// The style of the heap.
77    pub style: HeapStyle,
78    /// The guaranteed minimum size, in bytes.
79    pub min_size: u64,
80    /// The maximum heap size in bytes.
81    pub max_size: Option<u64>,
82    /// The log2 of this memory's page size, in bytes.
83    ///
84    /// By default the page size is 64KiB (0x10000; 2**16; 1<<16; 65536) but the
85    /// custom-page-sizes proposal allows opting into a page size of `1`.
86    pub page_size_log2: u8,
87    /// Size in bytes of the offset guard pages, located after the heap bounds.
88    pub offset_guard_size: u64,
89}
90
91/// A function callee.
92/// It categorizes how the callee should be treated
93/// when performing the call.
94#[derive(Clone)]
95pub(crate) enum Callee {
96    /// Locally defined function.
97    Local(FuncIndex),
98    /// Imported function.
99    Import(FuncIndex),
100    /// Function reference.
101    FuncRef(TypeIndex),
102    /// A built-in function.
103    Builtin(BuiltinFunction),
104}
105
106/// The function environment.
107///
108/// Contains all information about the module and runtime that is accessible to
109/// to a particular function during code generation.
110pub struct FuncEnv<'a, 'translation: 'a, 'data: 'translation, P: PtrSize> {
111    /// Offsets to the fields within the `VMContext` ptr.
112    pub vmoffsets: &'a VMOffsets<P>,
113    /// Metadata about the translation process of a WebAssembly module.
114    pub translation: &'translation ModuleTranslation<'data>,
115    /// The module's function types.
116    pub types: &'translation ModuleTypesBuilder,
117    /// The built-in functions available to the JIT code.
118    pub builtins: &'translation mut BuiltinFunctions,
119    /// Track resolved table information.
120    resolved_tables: HashMap<TableIndex, TableData>,
121    /// Track resolved heap information.
122    resolved_heaps: HashMap<MemoryIndex, HeapData>,
123    /// A map from [FunctionIndex] to [ABISig], to keep track of the resolved
124    /// function callees.
125    resolved_callees: HashMap<FuncIndex, ABISig>,
126    /// A map from [TypeIndex] to [ABISig], to keep track of the resolved
127    /// indirect function signatures.
128    resolved_sigs: HashMap<TypeIndex, ABISig>,
129    /// A map from [GlobalIndex] to [GlobalData].
130    resolved_globals: HashMap<GlobalIndex, GlobalData>,
131    /// Pointer size represented as a WebAssembly type.
132    ptr_type: WasmValType,
133    /// Whether or not to enable Spectre mitigation on heap bounds checks.
134    heap_access_spectre_mitigation: bool,
135    /// Whether or not to enable Spectre mitigation on table element accesses.
136    table_access_spectre_mitigation: bool,
137    name_map: PrimaryMap<UserExternalNameRef, UserExternalName>,
138    name_intern: HashMap<UserExternalName, UserExternalNameRef>,
139}
140
141pub fn ptr_type_from_ptr_size(size: u8) -> WasmValType {
142    (size == 8)
143        .then(|| WasmValType::I64)
144        .unwrap_or_else(|| unimplemented!("Support for non-64-bit architectures"))
145}
146
147impl<'a, 'translation, 'data, P: PtrSize> FuncEnv<'a, 'translation, 'data, P> {
148    /// Create a new function environment.
149    pub fn new(
150        vmoffsets: &'a VMOffsets<P>,
151        translation: &'translation ModuleTranslation<'data>,
152        types: &'translation ModuleTypesBuilder,
153        builtins: &'translation mut BuiltinFunctions,
154        isa: &dyn TargetIsa,
155        ptr_type: WasmValType,
156    ) -> Self {
157        Self {
158            vmoffsets,
159            translation,
160            types,
161            resolved_tables: HashMap::new(),
162            resolved_heaps: HashMap::new(),
163            resolved_callees: HashMap::new(),
164            resolved_sigs: HashMap::new(),
165            resolved_globals: HashMap::new(),
166            ptr_type,
167            heap_access_spectre_mitigation: isa.flags().enable_heap_access_spectre_mitigation(),
168            table_access_spectre_mitigation: isa.flags().enable_table_access_spectre_mitigation(),
169            builtins,
170            name_map: Default::default(),
171            name_intern: Default::default(),
172        }
173    }
174
175    /// Derive the [`WasmType`] from the pointer size.
176    pub(crate) fn ptr_type(&self) -> WasmValType {
177        self.ptr_type
178    }
179
180    /// Resolves a [`Callee::FuncRef`] from a type index.
181    pub(crate) fn funcref(&mut self, idx: TypeIndex) -> Callee {
182        Callee::FuncRef(idx)
183    }
184
185    /// Resolves a function [`Callee`] from an index.
186    pub(crate) fn callee_from_index(&mut self, idx: FuncIndex) -> Callee {
187        let import = self.translation.module.is_imported_function(idx);
188        if import {
189            Callee::Import(idx)
190        } else {
191            Callee::Local(idx)
192        }
193    }
194
195    /// Converts a [wasmparser::BlockType] into a [BlockSig].
196    pub(crate) fn resolve_block_sig(&self, ty: BlockType) -> BlockSig {
197        use BlockType::*;
198        match ty {
199            Empty => BlockSig::new(control::BlockType::void()),
200            Type(ty) => {
201                let ty = TypeConverter::new(self.translation, self.types).convert_valtype(ty);
202                BlockSig::new(control::BlockType::single(ty))
203            }
204            FuncType(idx) => {
205                let sig_index = self.translation.module.types[TypeIndex::from_u32(idx)];
206                let sig = self.types[sig_index].unwrap_func();
207                BlockSig::new(control::BlockType::func(sig.clone()))
208            }
209        }
210    }
211
212    /// Resolves `GlobalData` of a global at the given index.
213    pub fn resolve_global(&mut self, index: GlobalIndex) -> GlobalData {
214        let ty = self.translation.module.globals[index].wasm_ty;
215        let val = || match self.translation.module.defined_global_index(index) {
216            Some(defined_index) => GlobalData {
217                offset: self.vmoffsets.vmctx_vmglobal_definition(defined_index),
218                imported: false,
219                ty,
220            },
221            None => GlobalData {
222                offset: self.vmoffsets.vmctx_vmglobal_import_from(index),
223                imported: true,
224                ty,
225            },
226        };
227
228        *self.resolved_globals.entry(index).or_insert_with(val)
229    }
230
231    /// Returns the table information for the given table index.
232    pub fn resolve_table_data(&mut self, index: TableIndex) -> TableData {
233        match self.resolved_tables.entry(index) {
234            Occupied(entry) => *entry.get(),
235            Vacant(entry) => {
236                let (from_offset, base_offset, current_elems_offset) =
237                    match self.translation.module.defined_table_index(index) {
238                        Some(defined) => (
239                            None,
240                            self.vmoffsets.vmctx_vmtable_definition_base(defined),
241                            self.vmoffsets
242                                .vmctx_vmtable_definition_current_elements(defined),
243                        ),
244                        None => (
245                            Some(self.vmoffsets.vmctx_vmtable_import_from(index)),
246                            self.vmoffsets.vmtable_definition_base().into(),
247                            self.vmoffsets.vmtable_definition_current_elements().into(),
248                        ),
249                    };
250
251                *entry.insert(TableData {
252                    import_from: from_offset,
253                    offset: base_offset,
254                    current_elems_offset,
255                    element_size: OperandSize::from_bytes(self.vmoffsets.ptr.size()),
256                    current_elements_size: OperandSize::from_bytes(
257                        self.vmoffsets.size_of_vmtable_definition_current_elements(),
258                    ),
259                })
260            }
261        }
262    }
263
264    /// Resolve a `HeapData` from a [MemoryIndex].
265    // TODO: (@saulecabrera)
266    // Handle shared memories when implementing support for Wasm Threads.
267    pub fn resolve_heap(&mut self, index: MemoryIndex) -> HeapData {
268        match self.resolved_heaps.entry(index) {
269            Occupied(entry) => *entry.get(),
270            Vacant(entry) => {
271                let (import_from, base_offset, current_length_offset) =
272                    match self.translation.module.defined_memory_index(index) {
273                        Some(defined) => {
274                            let owned = self.translation.module.owned_memory_index(defined);
275                            (
276                                None,
277                                self.vmoffsets.vmctx_vmmemory_definition_base(owned),
278                                self.vmoffsets
279                                    .vmctx_vmmemory_definition_current_length(owned),
280                            )
281                        }
282                        None => (
283                            Some(self.vmoffsets.vmctx_vmmemory_import_from(index)),
284                            self.vmoffsets.ptr.vmmemory_definition_base().into(),
285                            self.vmoffsets
286                                .ptr
287                                .vmmemory_definition_current_length()
288                                .into(),
289                        ),
290                    };
291
292                let plan = &self.translation.module.memory_plans[index];
293                let (min_size, max_size) = heap_limits(&plan);
294                let (style, offset_guard_size) = heap_style_and_offset_guard_size(&plan);
295
296                *entry.insert(HeapData {
297                    offset: base_offset,
298                    import_from,
299                    current_length_offset,
300                    style,
301                    ty: if plan.memory.memory64 {
302                        WasmValType::I64
303                    } else {
304                        WasmValType::I32
305                    },
306                    min_size,
307                    max_size,
308                    page_size_log2: plan.memory.page_size_log2,
309                    offset_guard_size,
310                })
311            }
312        }
313    }
314
315    /// Get a [`TablePlan`] from a [`TableIndex`].
316    pub fn table_plan(&mut self, index: TableIndex) -> &TablePlan {
317        &self.translation.module.table_plans[index]
318    }
319
320    /// Returns true if Spectre mitigations are enabled for heap bounds check.
321    pub fn heap_access_spectre_mitigation(&self) -> bool {
322        self.heap_access_spectre_mitigation
323    }
324
325    /// Returns true if Spectre mitigations are enabled for table element
326    /// accesses.
327    pub fn table_access_spectre_mitigation(&self) -> bool {
328        self.table_access_spectre_mitigation
329    }
330
331    pub(crate) fn callee_sig<'b, A>(&'b mut self, callee: &'b Callee) -> &'b ABISig
332    where
333        A: ABI,
334    {
335        match callee {
336            Callee::Local(idx) | Callee::Import(idx) => {
337                let types = self.translation.get_types();
338                let ty = types[types.core_function_at(idx.as_u32())].unwrap_func();
339                let val = || {
340                    let converter = TypeConverter::new(self.translation, self.types);
341                    let ty = converter.convert_func_type(&ty);
342                    wasm_sig::<A>(&ty)
343                };
344                self.resolved_callees.entry(*idx).or_insert_with(val)
345            }
346            Callee::FuncRef(idx) => {
347                let val = || {
348                    let sig_index = self.translation.module.types[*idx];
349                    let ty = self.types[sig_index].unwrap_func();
350                    let sig = wasm_sig::<A>(ty);
351                    sig
352                };
353                self.resolved_sigs.entry(*idx).or_insert_with(val)
354            }
355            Callee::Builtin(b) => b.sig(),
356        }
357    }
358
359    /// Creates a name to reference the `builtin` provided.
360    pub fn name_builtin(&mut self, builtin: BuiltinFunctionIndex) -> UserExternalNameRef {
361        self.intern_name(UserExternalName {
362            namespace: wasmtime_cranelift::NS_WASMTIME_BUILTIN,
363            index: builtin.index(),
364        })
365    }
366
367    /// Creates a name to reference the wasm function `index` provided.
368    pub fn name_wasm(&mut self, index: FuncIndex) -> UserExternalNameRef {
369        self.intern_name(UserExternalName {
370            namespace: wasmtime_cranelift::NS_WASM_FUNC,
371            index: index.as_u32(),
372        })
373    }
374
375    /// Interns `name` into a `UserExternalNameRef` and ensures that duplicate
376    /// instances of `name` are given a unique name ref index.
377    fn intern_name(&mut self, name: UserExternalName) -> UserExternalNameRef {
378        *self
379            .name_intern
380            .entry(name.clone())
381            .or_insert_with(|| self.name_map.push(name))
382    }
383
384    /// Extracts the name map that was created while translating this function.
385    pub fn take_name_map(&mut self) -> PrimaryMap<UserExternalNameRef, UserExternalName> {
386        self.name_intern.clear();
387        mem::take(&mut self.name_map)
388    }
389}
390
391/// A wrapper struct over a reference to a [ModuleTranslation] and
392/// [ModuleTypesBuilder].
393pub(crate) struct TypeConverter<'a, 'data: 'a> {
394    translation: &'a ModuleTranslation<'data>,
395    types: &'a ModuleTypesBuilder,
396}
397
398impl TypeConvert for TypeConverter<'_, '_> {
399    fn lookup_heap_type(&self, idx: wasmparser::UnpackedIndex) -> WasmHeapType {
400        wasmtime_environ::WasmparserTypeConverter::new(self.types, &self.translation.module)
401            .lookup_heap_type(idx)
402    }
403
404    fn lookup_type_index(
405        &self,
406        index: wasmparser::UnpackedIndex,
407    ) -> wasmtime_environ::EngineOrModuleTypeIndex {
408        wasmtime_environ::WasmparserTypeConverter::new(self.types, &self.translation.module)
409            .lookup_type_index(index)
410    }
411}
412
413impl<'a, 'data> TypeConverter<'a, 'data> {
414    pub fn new(translation: &'a ModuleTranslation<'data>, types: &'a ModuleTypesBuilder) -> Self {
415        Self { translation, types }
416    }
417}
418
419fn heap_style_and_offset_guard_size(plan: &MemoryPlan) -> (HeapStyle, u64) {
420    match plan {
421        MemoryPlan {
422            style: MemoryStyle::Static { byte_reservation },
423            offset_guard_size,
424            ..
425        } => (
426            HeapStyle::Static {
427                bound: *byte_reservation,
428            },
429            *offset_guard_size,
430        ),
431
432        MemoryPlan {
433            style: MemoryStyle::Dynamic { .. },
434            offset_guard_size,
435            ..
436        } => (HeapStyle::Dynamic, *offset_guard_size),
437    }
438}
439
440fn heap_limits(plan: &MemoryPlan) -> (u64, Option<u64>) {
441    (
442        plan.memory.minimum_byte_size().unwrap_or_else(|_| {
443            // 2^64 as a minimum doesn't fin in a 64 bit integer.
444            // So in this case, the minimum is clamped to u64::MAX.
445            u64::MAX
446        }),
447        plan.memory.maximum_byte_size().ok(),
448    )
449}