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