/build/source/src/execution/instructions/mod.rs
Line | Count | Source |
1 | | //! This module contains common definitions required by the instruction handlers, which exist in |
2 | | //! submodules of this module. |
3 | | //! |
4 | | //! The logic for dispatching the execution of instruction handlers resides in the [`dispatch`] |
5 | | //! submodule, which itself provides multiple dispatch mechanisms. There execution is started via |
6 | | //! [`dispatch::run`]. |
7 | | //! |
8 | | //! Additionally, the [`const_interpreter_loop`] submodule contains the execution logic for const |
9 | | //! expressions. |
10 | | |
11 | | use alloc::vec::Vec; |
12 | | use core::{array, num::NonZeroU64, ops::ControlFlow}; |
13 | | |
14 | | use crate::{ |
15 | | core::{ |
16 | | decoding::decoder::WasmDecoder, |
17 | | sidetable::Sidetable, |
18 | | structure::{ |
19 | | modules::indices::{DataIdx, ElemIdx, MemIdx, TableIdx}, |
20 | | types::MemArg, |
21 | | }, |
22 | | utils::ToUsizeExt, |
23 | | }, |
24 | | execution::{ |
25 | | numerics::representations::LittleEndianBytes, |
26 | | runtime_structure::{ |
27 | | data_instances::DataInst, |
28 | | element_instances::ElemInst, |
29 | | memory_instances::MemInst, |
30 | | module_instances::ModuleInst, |
31 | | store::{Hostcode, StoreInner}, |
32 | | table_instances::TableInst, |
33 | | value_stack::Stack, |
34 | | }, |
35 | | }, |
36 | | AddrVec, DataAddr, ElemAddr, FuncAddr, MemAddr, ModuleAddr, RuntimeError, TableAddr, TrapError, |
37 | | Value, WasmResumable, |
38 | | }; |
39 | | |
40 | | mod control; |
41 | | mod memory; |
42 | | mod numeric; |
43 | | mod parametric; |
44 | | mod reference; |
45 | | mod table; |
46 | | mod variable; |
47 | | mod vector; |
48 | | |
49 | | pub mod const_interpreter_loop; |
50 | | pub(crate) mod dispatch; |
51 | | |
52 | | /// A non-error outcome of interpretation |
53 | | pub enum InterpreterLoopOutcome { |
54 | | /// Execution has returned normally, i.e. the end of the bottom-most function on the stack was |
55 | | /// reached. The return values for the initially invoked function are on the stack. |
56 | | ExecutionReturned, |
57 | | /// Execution was preempted because there was not enough fuel in the [`WasmResumable`] object. |
58 | | OutOfFuel { |
59 | | /// The amount of fuel required to continue execution at least the next instruction. |
60 | | required_fuel: NonZeroU64, |
61 | | }, |
62 | | /// A host function instance was called. The arguments for the host function call have been |
63 | | /// collected into `params` already. |
64 | | HostCalled { |
65 | | func_addr: FuncAddr, |
66 | | // TODO this allocation might be preventable. mutably borrow the stack instead |
67 | | params: Vec<Value>, |
68 | | hostcode: Hostcode, |
69 | | }, |
70 | | } |
71 | | |
72 | | /// The execution state interacted with by all instructions. |
73 | | /// |
74 | | /// # Safety |
75 | | /// |
76 | | /// - The [`WasmDecoder`] must point to the Wasm code for the module of the current module instance. |
77 | | /// - The [`WasmDecoder`] must point into Wasm code of the current function as set in |
78 | | /// `resumable.current_func_addr`. |
79 | | /// - The [`StoreInner`] must be valid. |
80 | | /// - The [`WasmResumable`] must be valid in [`StoreInner`]. |
81 | | /// - All address types contained in this struct must be valid in the [`StoreInner`]. |
82 | | /// - The current sidetable must be correct for the module of the current module instance. |
83 | | /// - The end marker for the current function must point to the end index of the current function in |
84 | | /// the current module's bytecode. |
85 | | // TODO possibly improve safety requirements |
86 | | pub(crate) struct State<'a, 'sidetable, 'wasm> { |
87 | | wasm: &'a mut WasmDecoder<'wasm>, |
88 | | resumable: &'a mut WasmResumable, |
89 | | current_sidetable: &'a mut &'sidetable Sidetable, |
90 | | store_inner: &'a mut StoreInner, |
91 | | modules: &'sidetable AddrVec<ModuleAddr, ModuleInst<'wasm>>, |
92 | | current_module: &'a mut ModuleAddr, |
93 | | current_function_end_marker: &'a mut usize, |
94 | | } |
95 | | |
96 | | //helper function for avoiding code duplication at intraprocedural jumps |
97 | 1.56M | fn do_sidetable_control_transfer( |
98 | 1.56M | wasm: &mut WasmDecoder, |
99 | 1.56M | stack: &mut Stack, |
100 | 1.56M | current_stp: &mut usize, |
101 | 1.56M | current_sidetable: &Sidetable, |
102 | 1.56M | ) -> Result<(), RuntimeError> { |
103 | 1.56M | let sidetable_entry = ¤t_sidetable[*current_stp]; |
104 | | |
105 | 1.56M | stack.remove_in_between(sidetable_entry.popcnt, sidetable_entry.valcnt); |
106 | | |
107 | 1.56M | *current_stp = sidetable_entry.stp; |
108 | 1.56M | wasm.pc = sidetable_entry.pc; |
109 | | |
110 | 1.56M | Ok(()) |
111 | 1.56M | } |
112 | | |
113 | | #[inline(always)] |
114 | 936k | fn calculate_mem_address(memarg: &MemArg, relative_address: u32) -> Result<usize, RuntimeError> { |
115 | | // The spec states that this should be a 33 bit integer, e.g. it is not legal to wrap if the |
116 | | // sum of offset and relative_address exceeds u32::MAX. To emulate this behavior, we use a |
117 | | // checked addition. |
118 | | // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions |
119 | 936k | let effective_address936k = memarg |
120 | 936k | .offset |
121 | 936k | .checked_add(relative_address) |
122 | 936k | .ok_or(TrapError::MemoryOrDataAccessOutOfBounds)?43 ; |
123 | | |
124 | 936k | Ok(effective_address.into_usize()) |
125 | 936k | } |
126 | | |
127 | | //helpers for avoiding code duplication during module instantiation |
128 | | /// # Safety |
129 | | /// |
130 | | /// 1. The module address `current_module` must be valid in `store_modules` for a module instance `module_inst`. |
131 | | /// 2. The table index `table_idx` must be valid in `module_inst` for a table address `table_addr`. |
132 | | /// 3. `table_addr` must be valid in `store_tables`. |
133 | | /// 4. The element index `elem_idx` must be valid in `module_inst` for an element address `elem_addr`. |
134 | | /// 5. `elem_addr` must be valid in `store_elements`. |
135 | | // TODO instead of passing all module instances and the current module addr |
136 | | // separately, directly pass a `&ModuleInst`. |
137 | | #[inline(always)] |
138 | | #[allow(clippy::too_many_arguments)] |
139 | 473 | pub(super) unsafe fn table_init( |
140 | 473 | store_modules: &AddrVec<ModuleAddr, ModuleInst>, |
141 | 473 | store_tables: &mut AddrVec<TableAddr, TableInst>, |
142 | 473 | store_elements: &AddrVec<ElemAddr, ElemInst>, |
143 | 473 | current_module: ModuleAddr, |
144 | 473 | elem_idx: ElemIdx, |
145 | 473 | table_idx: TableIdx, |
146 | 473 | n: u32, |
147 | 473 | s: i32, |
148 | 473 | d: i32, |
149 | 473 | ) -> Result<(), RuntimeError> { |
150 | 473 | let n = n.into_usize(); |
151 | 473 | let s = s.cast_unsigned().into_usize(); |
152 | 473 | let d = d.cast_unsigned().into_usize(); |
153 | | |
154 | | // SAFETY: The caller ensures that this module address is valid in this |
155 | | // address vector (1). |
156 | 473 | let module_inst = unsafe { store_modules.get(current_module) }; |
157 | | // SAFETY: The caller ensures that `table_idx` is valid for this specific |
158 | | // `IdxVec` (2). |
159 | 473 | let table_addr = *unsafe { module_inst.table_addrs.get(table_idx) }; |
160 | | // SAFETY: The caller ensures that `elem_idx` is valid for this specific |
161 | | // `IdxVec` (4). |
162 | 473 | let elem_addr = *unsafe { module_inst.elem_addrs.get(elem_idx) }; |
163 | | // SAFETY: The caller ensures that this table address is valid in this |
164 | | // address vector (3). |
165 | 473 | let tab = unsafe { store_tables.get_mut(table_addr) }; |
166 | | // SAFETY: The caller ensures that this element address is valid in this |
167 | | // address vector (5). |
168 | 473 | let elem = unsafe { store_elements.get(elem_addr) }; |
169 | | |
170 | 473 | let final_src_offset444 = s |
171 | 473 | .checked_add(n) |
172 | 473 | .filter(|&res| res <= elem.len()) |
173 | 473 | .ok_or(TrapError::TableOrElementAccessOutOfBounds)?29 ; |
174 | | |
175 | 444 | if d.checked_add(n) |
176 | 444 | .filter(|&res| res <= tab.len().into_usize()) |
177 | 444 | .is_none() |
178 | | { |
179 | 29 | return Err(TrapError::TableOrElementAccessOutOfBounds.into()); |
180 | 415 | } |
181 | | |
182 | 415 | let dest = &mut tab.elem[d..]; |
183 | 415 | let src = &elem.references[s..final_src_offset]; |
184 | 415 | dest[..src.len()].copy_from_slice(src); |
185 | 415 | Ok(()) |
186 | 473 | } |
187 | | |
188 | | /// # Safety |
189 | | /// |
190 | | /// 1. The module address `current_module` must be valid in `store_modules` for some module instance `module_inst`. |
191 | | /// 2. The element index `elem_idx` must be valid in `module_inst` for some element address `elem_addr`. |
192 | | /// 3. `elem_addr` must be valid in `store_elements`. |
193 | | #[inline(always)] |
194 | 409 | pub(super) unsafe fn elem_drop( |
195 | 409 | store_modules: &AddrVec<ModuleAddr, ModuleInst>, |
196 | 409 | store_elements: &mut AddrVec<ElemAddr, ElemInst>, |
197 | 409 | current_module: ModuleAddr, |
198 | 409 | elem_idx: ElemIdx, |
199 | 409 | ) { |
200 | | // WARN: i'm not sure if this is okay or not |
201 | | |
202 | | // SAFETY: The caller ensures that this module address is valid in this |
203 | | // address vector (1). |
204 | 409 | let module_inst = unsafe { store_modules.get(current_module) }; |
205 | | // SAFETY: The caller ensures that `elem_idx` is valid for this specific |
206 | | // `IdxVec` (2). |
207 | 409 | let elem_addr = *unsafe { module_inst.elem_addrs.get(elem_idx) }; |
208 | | |
209 | | // SAFETY: The caller ensures that this element address is valid in this |
210 | | // address vector (3). |
211 | 409 | let elem = unsafe { store_elements.get_mut(elem_addr) }; |
212 | | |
213 | 409 | elem.references.clear(); |
214 | 409 | } |
215 | | |
216 | | /// # Safety |
217 | | /// |
218 | | /// 1. The module address `current_module` must be valid in `store_modules` for some module instance `module_inst`. |
219 | | /// 2. The memory index `mem_idx` must be valid in `module_inst` for some memory address `mem_addr`. |
220 | | /// 3. `mem_addr` must be valid in `store_memories` for some memory instance `mem`. |
221 | | /// 4. The data index `data_idx` must be valid in `module_inst` for some data address `data_addr`. |
222 | | /// 5. `data_addr` must be valid in `store_data`. |
223 | | #[inline(always)] |
224 | | #[allow(clippy::too_many_arguments)] |
225 | 304 | pub(super) unsafe fn memory_init( |
226 | 304 | store_modules: &AddrVec<ModuleAddr, ModuleInst>, |
227 | 304 | store_memories: &mut AddrVec<MemAddr, MemInst>, |
228 | 304 | store_data: &AddrVec<DataAddr, DataInst>, |
229 | 304 | current_module: ModuleAddr, |
230 | 304 | data_idx: DataIdx, |
231 | 304 | mem_idx: MemIdx, |
232 | 304 | n: u32, |
233 | 304 | s: u32, |
234 | 304 | d: u32, |
235 | 304 | ) -> Result<(), RuntimeError> { |
236 | 304 | let n = n.into_usize(); |
237 | 304 | let s = s.into_usize(); |
238 | 304 | let d = d.into_usize(); |
239 | | |
240 | | // SAFETY: The caller ensures that this is module address is valid in this |
241 | | // address vector (1). |
242 | 304 | let module_inst = unsafe { store_modules.get(current_module) }; |
243 | | // SAFETY: The caller ensures that `mem_idx` is valid for this specific |
244 | | // `IdxVec` (2). |
245 | 304 | let mem_addr = *unsafe { module_inst.mem_addrs.get(mem_idx) }; |
246 | | // SAFETY: The caller ensures that this memory address is valid in this |
247 | | // address vector (3). |
248 | 304 | let mem = unsafe { store_memories.get_mut(mem_addr) }; |
249 | | // SAFETY: The caller ensures that `data_idx` is valid for this specific |
250 | | // `IdxVec` (4). |
251 | 304 | let data_addr = *unsafe { module_inst.data_addrs.get(data_idx) }; |
252 | | // SAFETY: The caller ensures that this data address is valid in this |
253 | | // address vector (5). |
254 | 304 | let data = unsafe { store_data.get(data_addr) }; |
255 | | |
256 | 304 | match mem { |
257 | 268 | MemInst::Unshared(unshared_mem) => { |
258 | 268 | unshared_mem.mem.init(d, &data.data, s, n)?42 ; |
259 | | } |
260 | 36 | MemInst::Shared(shared_mem) => { |
261 | 36 | shared_mem.mem.init(d, &data.data, s, n)?0 ; |
262 | | } |
263 | | } |
264 | | |
265 | 262 | Ok(()) |
266 | 304 | } |
267 | | |
268 | | /// # Safety |
269 | | /// |
270 | | /// 1. The module address `current_module` must be valid in `store_modules` for some module instance `module_inst`. |
271 | | /// 2. The data index `data_idx` must be valid in `module_inst` for some data address `data_addr`. |
272 | | /// 3. `data_addr` must be valid in `store_data`. |
273 | | #[inline(always)] |
274 | 250 | pub(super) unsafe fn data_drop( |
275 | 250 | store_modules: &AddrVec<ModuleAddr, ModuleInst>, |
276 | 250 | store_data: &mut AddrVec<DataAddr, DataInst>, |
277 | 250 | current_module: ModuleAddr, |
278 | 250 | data_idx: DataIdx, |
279 | 250 | ) { |
280 | | // Here is debatable |
281 | | // If we were to be on par with the spec we'd have to use a DataInst struct |
282 | | // But since memory.init is specifically made for Passive data segments |
283 | | // I thought that using DataMode would be better because we can see if the |
284 | | // data segment is passive or active |
285 | | |
286 | | // Also, we should set data to null here (empty), which we do by clearing it |
287 | | // SAFETY: The caller guarantees this module to be valid in this address |
288 | | // vector (1). |
289 | 250 | let module_inst = unsafe { store_modules.get(current_module) }; |
290 | | // SAFETY: The caller ensures that `data_idx` is valid for this specific |
291 | | // `IdxVec` (2). |
292 | 250 | let data_addr = *unsafe { module_inst.data_addrs.get(data_idx) }; |
293 | | // SAFETY: The caller ensures that this data address is valid in this |
294 | | // address vector (3). |
295 | 250 | let data = unsafe { store_data.get_mut(data_addr) }; |
296 | | |
297 | 250 | data.data.clear(); |
298 | 250 | } |
299 | | |
300 | | #[inline(always)] |
301 | 45.4k | pub(crate) fn to_lanes<const M: usize, const N: usize, T: LittleEndianBytes<M>>( |
302 | 45.4k | data: [u8; 16], |
303 | 45.4k | ) -> [T; N] { |
304 | 45.4k | assert_eq!(M * N, 16); |
305 | | |
306 | 45.4k | let mut lanes = data |
307 | 45.4k | .chunks(M) |
308 | 185k | .map45.4k (|chunk| T::from_le_bytes(chunk.try_into().unwrap())); |
309 | 185k | array::from_fn45.4k (|_| lanes.next().unwrap()) |
310 | 45.4k | } |
311 | | |
312 | | #[inline(always)] |
313 | 23.7k | pub(crate) fn from_lanes<const M: usize, const N: usize, T: LittleEndianBytes<M>>( |
314 | 23.7k | lanes: [T; N], |
315 | 23.7k | ) -> [u8; 16] { |
316 | 23.7k | assert_eq!(M * N, 16); |
317 | | |
318 | 23.7k | let mut bytes = lanes.into_iter().flat_map(T::to_le_bytes); |
319 | 380k | array::from_fn23.7k (|_| bytes.next().unwrap()) |
320 | 23.7k | } |
321 | | |
322 | | #[inline(always)] |
323 | 16.4M | fn decrement_fuel(cost: u64, maybe_fuel: &mut Option<u64>) -> ControlFlow<InterpreterLoopOutcome> { |
324 | 16.4M | if let Some(fuel282 ) = maybe_fuel { |
325 | 282 | if *fuel >= cost { |
326 | 226 | *fuel -= cost; |
327 | 226 | } else { |
328 | 56 | return ControlFlow::Break(InterpreterLoopOutcome::OutOfFuel { |
329 | 56 | required_fuel: NonZeroU64::new(cost - *fuel) |
330 | 56 | .expect("the last check guarantees that the current fuel is smaller than cost"), |
331 | 56 | }); |
332 | | } |
333 | 16.4M | } |
334 | | |
335 | 16.4M | ControlFlow::Continue(()) |
336 | 16.4M | } |