/build/source/src/execution/instructions/mod.rs
Line | Count | Source |
1 | | //! This module contains the actual interpretation loop |
2 | | |
3 | | use alloc::vec::Vec; |
4 | | use core::{array, num::NonZeroU64, ops::ControlFlow}; |
5 | | |
6 | | use crate::{ |
7 | | core::{ |
8 | | decoding::reader::WasmDecoder, |
9 | | sidetable::Sidetable, |
10 | | structure::{ |
11 | | modules::indices::{DataIdx, ElemIdx, MemIdx, TableIdx}, |
12 | | types::MemArg, |
13 | | }, |
14 | | utils::ToUsizeExt, |
15 | | }, |
16 | | execution::{ |
17 | | assert_validated::UnwrapValidatedExt, |
18 | | config::Config, |
19 | | instructions::dispatch_tables::{ |
20 | | HasBaseDispatchTable, HasFcDispatchTable, HasFdDispatchTable, |
21 | | }, |
22 | | numerics::representations::LittleEndianBytes, |
23 | | runtime_structure::{ |
24 | | data_instances::DataInst, |
25 | | element_instances::ElemInst, |
26 | | function_instances::FuncInst, |
27 | | memory_instances::MemInst, |
28 | | module_instances::ModuleInst, |
29 | | store::{Hostcode, StoreInner}, |
30 | | table_instances::TableInst, |
31 | | value_stack::Stack, |
32 | | }, |
33 | | }, |
34 | | unreachable_validated, AddrVec, DataAddr, ElemAddr, FuncAddr, MemAddr, ModuleAddr, |
35 | | RuntimeError, Store, TableAddr, TrapError, Value, WasmResumable, |
36 | | }; |
37 | | |
38 | | mod control; |
39 | | mod memory; |
40 | | mod numeric; |
41 | | mod parametric; |
42 | | mod reference; |
43 | | mod table; |
44 | | mod variable; |
45 | | mod vector; |
46 | | |
47 | | pub mod const_interpreter_loop; |
48 | | mod dispatch_tables; |
49 | | |
50 | | /// A non-error outcome of execution of the interpreter loop |
51 | | pub enum InterpreterLoopOutcome { |
52 | | /// Execution has returned normally, i.e. the end of the bottom-most |
53 | | /// function on the stack was reached. |
54 | | ExecutionReturned, |
55 | | /// Execution was preempted because there was not enough fuel in the |
56 | | /// [`WasmResumable`] object. |
57 | | /// |
58 | | OutOfFuel { |
59 | | /// The amount of fuel required to continue execution at least the next |
60 | | /// instruction. |
61 | | required_fuel: NonZeroU64, |
62 | | }, |
63 | | HostCalled { |
64 | | func_addr: FuncAddr, |
65 | | // TODO this allocation might be preventable. mutably borrow the stack |
66 | | // instead |
67 | | params: Vec<Value>, |
68 | | hostcode: Hostcode, |
69 | | }, |
70 | | } |
71 | | |
72 | | type InstructionHandlerFn = |
73 | | for<'wasm, 'modules> unsafe fn( |
74 | | wasm: &mut WasmDecoder<'wasm>, |
75 | | resumable: &mut WasmResumable, |
76 | | current_sidetable: &mut &'modules Sidetable, |
77 | | store_inner: &mut StoreInner, |
78 | | modules: &'modules AddrVec<ModuleAddr, ModuleInst<'wasm>>, |
79 | | current_module: &mut ModuleAddr, |
80 | | current_function_end_marker: &mut usize, |
81 | | ) |
82 | | -> Result<ControlFlow<InterpreterLoopOutcome>, RuntimeError>; |
83 | | |
84 | | // A placeholder instruction for unassigned instruction bytes. This function is by definition dead |
85 | | // code! |
86 | 0 | define_instruction_fn! {unset, fuel_check = omit, |Args { .. }| { |
87 | | // Access T to circumvent warning that it is unused by this function. #[allow] does not work for |
88 | | // macros. |
89 | 0 | let _ = T::DISPATCH_TABLE; |
90 | 0 | unreachable_validated!(); |
91 | | }} |
92 | | |
93 | | /// Interprets wasm native functions. Wasm parameters and Wasm return values are passed on the stack. |
94 | | /// Returns `Ok(ControlFlow::Continue(()))` in case execution successfully terminates, `Ok(Some(required_fuel))` if execution |
95 | | /// terminates due to insufficient fuel, indicating how much fuel is required to resume with `required_fuel`, |
96 | | /// and `[Error::RuntimeError]` otherwise. |
97 | | /// |
98 | | /// # Safety |
99 | | /// |
100 | | /// The given resumable must be valid in the given [`Store`]. |
101 | | #[inline(never)] |
102 | 54.7k | pub(super) unsafe fn run<T: Config>( |
103 | 54.7k | resumable: &mut WasmResumable, |
104 | 54.7k | store: &mut Store<T>, |
105 | 54.7k | ) -> Result<InterpreterLoopOutcome, RuntimeError> { |
106 | 54.7k | let current_func_addr = resumable.current_func_addr; |
107 | 54.7k | let pc = resumable.pc; |
108 | | // SAFETY: The caller ensures that the resumable and thus also its function |
109 | | // address is valid in the current store. |
110 | 54.7k | let func_inst = unsafe { store.inner.functions.get(current_func_addr) }; |
111 | 54.7k | let FuncInst::WasmFunc(wasm_func_inst) = &func_inst else { |
112 | 0 | unreachable!( |
113 | | "the interpreter loop shall only be executed with native wasm functions as root call" |
114 | | ); |
115 | | }; |
116 | 54.7k | let mut current_module = wasm_func_inst.module_addr; |
117 | | |
118 | | // Start reading the function's instructions |
119 | | // SAFETY: This module address was just read from the current store. Every |
120 | | // store guarantees all addresses contained in it to be valid within itself. |
121 | 54.7k | let module = unsafe { store.modules.get(current_module) }; |
122 | 54.7k | let wasm_bytecode = module.wasm_bytecode; |
123 | 54.7k | let wasm = &mut WasmDecoder::new(wasm_bytecode); |
124 | | |
125 | 54.7k | let mut current_sidetable: &Sidetable = &module.sidetable; |
126 | | |
127 | 54.7k | let mut current_function_end_marker = |
128 | 54.7k | wasm_func_inst.code_expr.from() + wasm_func_inst.code_expr.len(); |
129 | | |
130 | 54.7k | let store_inner = &mut store.inner; |
131 | | |
132 | | // local variable for holding where the function code ends (last END instr address + 1) to avoid lookup at every END instr |
133 | | |
134 | 54.7k | wasm.pc = pc; |
135 | | |
136 | | loop { |
137 | | // call the instruction hook |
138 | 16.4M | store.user_data.instruction_hook(wasm_bytecode, wasm.pc); |
139 | | |
140 | 16.4M | let prev_pc = wasm.pc; |
141 | | |
142 | 16.4M | let first_instr_byte = wasm.decode_u8().unwrap_validated(); |
143 | | |
144 | | #[cfg(debug_assertions)] |
145 | 16.4M | trace!( |
146 | | "Executing instruction {}", |
147 | | crate::instructions::instruction_byte_to_str(first_instr_byte) |
148 | | ); |
149 | | |
150 | 16.4M | let instruction_fn = T::DISPATCH_TABLE |
151 | 16.4M | .get(usize::from(first_instr_byte)) |
152 | 16.4M | .expect("the instruction to be valid because the code is validated"); |
153 | | |
154 | | // SAFETY: All possible instruction handler functions use the same safety requirements, as |
155 | | // they are defined through the same macro: The caller ensures that the resumable is valid |
156 | | // in the current store. Also all other address types passed via the `Args` must come from |
157 | | // the current store itself. Therefore, they are automatically valid in this store. |
158 | 16.4M | let instruction_result = unsafe { |
159 | 16.4M | instruction_fn( |
160 | 16.4M | wasm, |
161 | 16.4M | resumable, |
162 | 16.4M | &mut current_sidetable, |
163 | 16.4M | store_inner, |
164 | 16.4M | &store.modules, |
165 | 16.4M | &mut current_module, |
166 | 16.4M | &mut current_function_end_marker, |
167 | 16.4M | ) |
168 | | }; |
169 | | |
170 | 16.4M | if let ControlFlow::Break(interpreter_loop_outcome51.6k ) = instruction_result?3.09k { |
171 | 51.6k | if let InterpreterLoopOutcome::OutOfFuel { .. } = interpreter_loop_outcome { |
172 | 54 | wasm.pc = prev_pc; |
173 | 51.5k | } |
174 | | |
175 | 51.6k | resumable.pc = wasm.pc; |
176 | 51.6k | return Ok(interpreter_loop_outcome); |
177 | 16.4M | } |
178 | | } |
179 | 54.7k | } |
180 | | |
181 | | //helper function for avoiding code duplication at intraprocedural jumps |
182 | 1.56M | fn do_sidetable_control_transfer( |
183 | 1.56M | wasm: &mut WasmDecoder, |
184 | 1.56M | stack: &mut Stack, |
185 | 1.56M | current_stp: &mut usize, |
186 | 1.56M | current_sidetable: &Sidetable, |
187 | 1.56M | ) -> Result<(), RuntimeError> { |
188 | 1.56M | let sidetable_entry = ¤t_sidetable[*current_stp]; |
189 | | |
190 | 1.56M | stack.remove_in_between(sidetable_entry.popcnt, sidetable_entry.valcnt); |
191 | | |
192 | 1.56M | *current_stp = sidetable_entry.stp; |
193 | 1.56M | wasm.pc = sidetable_entry.pc; |
194 | | |
195 | 1.56M | Ok(()) |
196 | 1.56M | } |
197 | | |
198 | | #[inline(always)] |
199 | 931k | fn calculate_mem_address(memarg: &MemArg, relative_address: u32) -> Result<usize, RuntimeError> { |
200 | 931k | memarg |
201 | 931k | .offset |
202 | | // The spec states that this should be a 33 bit integer, e.g. it is not legal to wrap if the |
203 | | // sum of offset and relative_address exceeds u32::MAX. To emulate this behavior, we use a |
204 | | // checked addition. |
205 | | // See: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions |
206 | 931k | .checked_add(relative_address) |
207 | 931k | .ok_or(TrapError::MemoryOrDataAccessOutOfBounds)?43 |
208 | 931k | .try_into() |
209 | 931k | .map_err(|_| TrapError::MemoryOrDataAccessOutOfBounds0 .into0 ()) |
210 | 931k | } |
211 | | |
212 | | //helpers for avoiding code duplication during module instantiation |
213 | | /// # Safety |
214 | | /// |
215 | | /// 1. The module address `current_module` must be valid in `store_modules` for a module instance `module_inst`. |
216 | | /// 2. The table index `table_idx` must be valid in `module_inst` for a table address `table_addr`. |
217 | | /// 3. `table_addr` must be valid in `store_tables`. |
218 | | /// 4. The element index `elem_idx` must be valid in `module_inst` for an element address `elem_addr`. |
219 | | /// 5. `elem_addr` must be valid in `store_elements`. |
220 | | // TODO instead of passing all module instances and the current module addr |
221 | | // separately, directly pass a `&ModuleInst`. |
222 | | #[inline(always)] |
223 | | #[allow(clippy::too_many_arguments)] |
224 | 473 | pub(super) unsafe fn table_init( |
225 | 473 | store_modules: &AddrVec<ModuleAddr, ModuleInst>, |
226 | 473 | store_tables: &mut AddrVec<TableAddr, TableInst>, |
227 | 473 | store_elements: &AddrVec<ElemAddr, ElemInst>, |
228 | 473 | current_module: ModuleAddr, |
229 | 473 | elem_idx: ElemIdx, |
230 | 473 | table_idx: TableIdx, |
231 | 473 | n: u32, |
232 | 473 | s: i32, |
233 | 473 | d: i32, |
234 | 473 | ) -> Result<(), RuntimeError> { |
235 | 473 | let n = n.into_usize(); |
236 | 473 | let s = s.cast_unsigned().into_usize(); |
237 | 473 | let d = d.cast_unsigned().into_usize(); |
238 | | |
239 | | // SAFETY: The caller ensures that this module address is valid in this |
240 | | // address vector (1). |
241 | 473 | let module_inst = unsafe { store_modules.get(current_module) }; |
242 | | // SAFETY: The caller ensures that `table_idx` is valid for this specific |
243 | | // `IdxVec` (2). |
244 | 473 | let table_addr = *unsafe { module_inst.table_addrs.get(table_idx) }; |
245 | | // SAFETY: The caller ensures that `elem_idx` is valid for this specific |
246 | | // `IdxVec` (4). |
247 | 473 | let elem_addr = *unsafe { module_inst.elem_addrs.get(elem_idx) }; |
248 | | // SAFETY: The caller ensures that this table address is valid in this |
249 | | // address vector (3). |
250 | 473 | let tab = unsafe { store_tables.get_mut(table_addr) }; |
251 | | // SAFETY: The caller ensures that this element address is valid in this |
252 | | // address vector (5). |
253 | 473 | let elem = unsafe { store_elements.get(elem_addr) }; |
254 | | |
255 | 473 | trace!( |
256 | | "Instruction: table.init '{}' '{}' [{} {} {}] -> []", |
257 | | elem_idx, |
258 | | table_idx, |
259 | | d, |
260 | | s, |
261 | | n |
262 | | ); |
263 | | |
264 | 473 | let final_src_offset444 = s |
265 | 473 | .checked_add(n) |
266 | 473 | .filter(|&res| res <= elem.len()) |
267 | 473 | .ok_or(TrapError::TableOrElementAccessOutOfBounds)?29 ; |
268 | | |
269 | 444 | if d.checked_add(n).filter(|&res| res <= tab.len()).is_none() { |
270 | 29 | return Err(TrapError::TableOrElementAccessOutOfBounds.into()); |
271 | 415 | } |
272 | | |
273 | 415 | let dest = &mut tab.elem[d..]; |
274 | 415 | let src = &elem.references[s..final_src_offset]; |
275 | 415 | dest[..src.len()].copy_from_slice(src); |
276 | 415 | Ok(()) |
277 | 473 | } |
278 | | |
279 | | /// # Safety |
280 | | /// |
281 | | /// 1. The module address `current_module` must be valid in `store_modules` for some module instance `module_inst`. |
282 | | /// 2. The element index `elem_idx` must be valid in `module_inst` for some element address `elem_addr`. |
283 | | /// 3. `elem_addr` must be valid in `store_elements`. |
284 | | #[inline(always)] |
285 | 409 | pub(super) unsafe fn elem_drop( |
286 | 409 | store_modules: &AddrVec<ModuleAddr, ModuleInst>, |
287 | 409 | store_elements: &mut AddrVec<ElemAddr, ElemInst>, |
288 | 409 | current_module: ModuleAddr, |
289 | 409 | elem_idx: ElemIdx, |
290 | 409 | ) { |
291 | | // WARN: i'm not sure if this is okay or not |
292 | | |
293 | | // SAFETY: The caller ensures that this module address is valid in this |
294 | | // address vector (1). |
295 | 409 | let module_inst = unsafe { store_modules.get(current_module) }; |
296 | | // SAFETY: The caller ensures that `elem_idx` is valid for this specific |
297 | | // `IdxVec` (2). |
298 | 409 | let elem_addr = *unsafe { module_inst.elem_addrs.get(elem_idx) }; |
299 | | |
300 | | // SAFETY: The caller ensures that this element address is valid in this |
301 | | // address vector (3). |
302 | 409 | let elem = unsafe { store_elements.get_mut(elem_addr) }; |
303 | | |
304 | 409 | elem.references.clear(); |
305 | 409 | } |
306 | | |
307 | | /// # Safety |
308 | | /// |
309 | | /// 1. The module address `current_module` must be valid in `store_modules` for some module instance `module_inst`. |
310 | | /// 2. The memory index `mem_idx` must be valid in `module_inst` for some memory address `mem_addr`. |
311 | | /// 3. `mem_addr` must be valid in `store_memories` for some memory instance `mem`. |
312 | | /// 4. The data index `data_idx` must be valid in `module_inst` for some data address `data_addr`. |
313 | | /// 5. `data_addr` must be valid in `store_data`. |
314 | | #[inline(always)] |
315 | | #[allow(clippy::too_many_arguments)] |
316 | 262 | pub(super) unsafe fn memory_init( |
317 | 262 | store_modules: &AddrVec<ModuleAddr, ModuleInst>, |
318 | 262 | store_memories: &mut AddrVec<MemAddr, MemInst>, |
319 | 262 | store_data: &AddrVec<DataAddr, DataInst>, |
320 | 262 | current_module: ModuleAddr, |
321 | 262 | data_idx: DataIdx, |
322 | 262 | mem_idx: MemIdx, |
323 | 262 | n: u32, |
324 | 262 | s: u32, |
325 | 262 | d: u32, |
326 | 262 | ) -> Result<(), RuntimeError> { |
327 | 262 | let n = n.into_usize(); |
328 | 262 | let s = s.into_usize(); |
329 | 262 | let d = d.into_usize(); |
330 | | |
331 | | // SAFETY: The caller ensures that this is module address is valid in this |
332 | | // address vector (1). |
333 | 262 | let module_inst = unsafe { store_modules.get(current_module) }; |
334 | | // SAFETY: The caller ensures that `mem_idx` is valid for this specific |
335 | | // `IdxVec` (2). |
336 | 262 | let mem_addr = *unsafe { module_inst.mem_addrs.get(mem_idx) }; |
337 | | // SAFETY: The caller ensures that this memory address is valid in this |
338 | | // address vector (3). |
339 | 262 | let mem = unsafe { store_memories.get(mem_addr) }; |
340 | | // SAFETY: The caller ensures that `data_idx` is valid for this specific |
341 | | // `IdxVec` (4). |
342 | 262 | let data_addr = *unsafe { module_inst.data_addrs.get(data_idx) }; |
343 | | // SAFETY: The caller ensures that this data address is valid in this |
344 | | // address vector (5). |
345 | 262 | let data = unsafe { store_data.get(data_addr) }; |
346 | | |
347 | 262 | mem.mem.init(d, &data.data, s, n)?39 ; |
348 | | |
349 | 223 | trace!("Instruction: memory.init"); |
350 | 223 | Ok(()) |
351 | 262 | } |
352 | | |
353 | | /// # Safety |
354 | | /// |
355 | | /// 1. The module address `current_module` must be valid in `store_modules` for some module instance `module_inst`. |
356 | | /// 2. The data index `data_idx` must be valid in `module_inst` for some data address `data_addr`. |
357 | | /// 3. `data_addr` must be valid in `store_data`. |
358 | | #[inline(always)] |
359 | 212 | pub(super) unsafe fn data_drop( |
360 | 212 | store_modules: &AddrVec<ModuleAddr, ModuleInst>, |
361 | 212 | store_data: &mut AddrVec<DataAddr, DataInst>, |
362 | 212 | current_module: ModuleAddr, |
363 | 212 | data_idx: DataIdx, |
364 | 212 | ) { |
365 | | // Here is debatable |
366 | | // If we were to be on par with the spec we'd have to use a DataInst struct |
367 | | // But since memory.init is specifically made for Passive data segments |
368 | | // I thought that using DataMode would be better because we can see if the |
369 | | // data segment is passive or active |
370 | | |
371 | | // Also, we should set data to null here (empty), which we do by clearing it |
372 | | // SAFETY: The caller guarantees this module to be valid in this address |
373 | | // vector (1). |
374 | 212 | let module_inst = unsafe { store_modules.get(current_module) }; |
375 | | // SAFETY: The caller ensures that `data_idx` is valid for this specific |
376 | | // `IdxVec` (2). |
377 | 212 | let data_addr = *unsafe { module_inst.data_addrs.get(data_idx) }; |
378 | | // SAFETY: The caller ensures that this data address is valid in this |
379 | | // address vector (3). |
380 | 212 | let data = unsafe { store_data.get_mut(data_addr) }; |
381 | | |
382 | 212 | data.data.clear(); |
383 | 212 | } |
384 | | |
385 | | #[inline(always)] |
386 | 45.4k | pub(crate) fn to_lanes<const M: usize, const N: usize, T: LittleEndianBytes<M>>( |
387 | 45.4k | data: [u8; 16], |
388 | 45.4k | ) -> [T; N] { |
389 | 45.4k | assert_eq!(M * N, 16); |
390 | | |
391 | 45.4k | let mut lanes = data |
392 | 45.4k | .chunks(M) |
393 | 185k | .map45.4k (|chunk| T::from_le_bytes(chunk.try_into().unwrap())); |
394 | 185k | array::from_fn45.4k (|_| lanes.next().unwrap()) |
395 | 45.4k | } |
396 | | |
397 | | #[inline(always)] |
398 | 23.7k | pub(crate) fn from_lanes<const M: usize, const N: usize, T: LittleEndianBytes<M>>( |
399 | 23.7k | lanes: [T; N], |
400 | 23.7k | ) -> [u8; 16] { |
401 | 23.7k | assert_eq!(M * N, 16); |
402 | | |
403 | 23.7k | let mut bytes = lanes.into_iter().flat_map(T::to_le_bytes); |
404 | 380k | array::from_fn23.7k (|_| bytes.next().unwrap()) |
405 | 23.7k | } |
406 | | |
407 | | pub(crate) struct Args<'a, 'sidetable, 'wasm, 'other, 'resumable> { |
408 | | wasm: &'a mut WasmDecoder<'wasm>, |
409 | | resumable: &'resumable mut WasmResumable, |
410 | | current_sidetable: &'a mut &'sidetable Sidetable, |
411 | | store_inner: &'other mut StoreInner, |
412 | | modules: &'sidetable AddrVec<ModuleAddr, ModuleInst<'wasm>>, |
413 | | current_module: &'a mut ModuleAddr, |
414 | | current_function_end_marker: &'a mut usize, |
415 | | } |
416 | | |
417 | | macro_rules! define_instruction_fn { |
418 | | ($name:ident, fuel_check = omit, $contents:expr) => { |
419 | | /// # Safety |
420 | | /// |
421 | | /// The given [`WasmResumable`](crate::execution::resumable::WasmResumable) and all address |
422 | | /// types contained in the [`Args`](crate::execution::instructions::Args) must be valid |
423 | | /// in the [`StoreInner`](crate::execution::runtime_structure::store::StoreInner) that is also contained in the |
424 | | /// [`Args`](crate::execution::instructions::Args). |
425 | | // Disable inlining to inspect the emitted code of individual instruction handlers: |
426 | | // #[inline(never)] |
427 | 16.5M | pub(crate) unsafe fn $name<'wasm, 'modules, T: $crate::execution::config::Config>( |
428 | 16.5M | wasm: &mut $crate::core::decoding::reader::WasmDecoder<'wasm>, |
429 | 16.5M | resumable: &mut $crate::execution::resumable::WasmResumable, |
430 | 16.5M | current_sidetable: &mut &'modules $crate::core::sidetable::Sidetable, |
431 | 16.5M | store_inner: &mut $crate::execution::runtime_structure::store::StoreInner, |
432 | 16.5M | modules: &'modules $crate::execution::runtime_structure::addresses::AddrVec< |
433 | 16.5M | $crate::execution::runtime_structure::addresses::ModuleAddr, |
434 | 16.5M | $crate::execution::runtime_structure::module_instances::ModuleInst<'wasm>, |
435 | 16.5M | >, |
436 | 16.5M | current_module: &mut $crate::execution::runtime_structure::addresses::ModuleAddr, |
437 | 16.5M | current_function_end_marker: &mut usize, |
438 | 16.5M | ) -> Result< |
439 | 16.5M | core::ops::ControlFlow<$crate::execution::instructions::InterpreterLoopOutcome>, |
440 | 16.5M | $crate::RuntimeError, |
441 | 16.5M | > { |
442 | 16.5M | let args = $crate::execution::instructions::Args { |
443 | 16.5M | store_inner, |
444 | 16.5M | modules, |
445 | 16.5M | wasm, |
446 | 16.5M | current_module, |
447 | 16.5M | current_function_end_marker, |
448 | 16.5M | current_sidetable, |
449 | 16.5M | resumable, |
450 | 16.5M | }; |
451 | | |
452 | 16.5M | $contents(args) |
453 | 16.5M | } |
454 | | }; |
455 | | |
456 | | ($name:ident, fuel_check = flat($instruction:expr), $contents:expr) => { |
457 | | define_instruction_fn! { |
458 | | $name, |
459 | | fuel_check = omit, |
460 | 16.4M | |args: $crate::execution::instructions::Args| { |
461 | 53 | if let core::ops::ControlFlow::Break(outcome) = |
462 | 16.4M | $crate::execution::instructions::decrement_fuel( |
463 | 16.4M | T::get_flat_cost($instruction), |
464 | 16.4M | &mut args.resumable.maybe_fuel, |
465 | 16.4M | ) |
466 | | { |
467 | 53 | return Ok(core::ops::ControlFlow::Break(outcome)); |
468 | 16.4M | } |
469 | | |
470 | 16.4M | $contents(args) |
471 | 16.4M | } |
472 | | } |
473 | | }; |
474 | | |
475 | | ($name: ident, fuel_check = flat_fc($instruction: expr), $contents:expr) => { |
476 | | define_instruction_fn! { |
477 | | $name, |
478 | | fuel_check = omit, |
479 | 26.2k | |args: $crate::execution::instructions::Args| { |
480 | 0 | if let core::ops::ControlFlow::Break(outcome) = |
481 | 26.2k | $crate::execution::instructions::decrement_fuel( |
482 | 26.2k | T::get_fc_extension_flat_cost($instruction), |
483 | 26.2k | &mut args.resumable.maybe_fuel, |
484 | 26.2k | ) |
485 | | { |
486 | 0 | return Ok(core::ops::ControlFlow::Break(outcome)); |
487 | 26.2k | } |
488 | | |
489 | 26.2k | $contents(args) |
490 | 26.2k | } |
491 | | } |
492 | | }; |
493 | | |
494 | | ($name: ident, fuel_check = flat_fd($instruction: expr), $contents:expr) => { |
495 | | define_instruction_fn! { |
496 | | $name, |
497 | | fuel_check = omit, |
498 | | |args: $crate::execution::instructions::Args| { |
499 | | if let core::ops::ControlFlow::Break(outcome) = |
500 | | $crate::execution::instructions::decrement_fuel( |
501 | | T::get_fd_extension_flat_cost($instruction), |
502 | | &mut args.resumable.maybe_fuel, |
503 | | ) |
504 | | { |
505 | | return Ok(core::ops::ControlFlow::Break(outcome)); |
506 | | } |
507 | | |
508 | | $contents(args) |
509 | | } |
510 | | } |
511 | | }; |
512 | | } |
513 | | |
514 | | pub(crate) use define_instruction_fn; |
515 | | |
516 | | #[inline(always)] |
517 | 16.4M | fn decrement_fuel(cost: u64, maybe_fuel: &mut Option<u64>) -> ControlFlow<InterpreterLoopOutcome> { |
518 | 16.4M | if let Some(fuel275 ) = maybe_fuel { |
519 | 275 | if *fuel >= cost { |
520 | 222 | *fuel -= cost; |
521 | 222 | } else { |
522 | 53 | return ControlFlow::Break(InterpreterLoopOutcome::OutOfFuel { |
523 | 53 | required_fuel: NonZeroU64::new(cost - *fuel) |
524 | 53 | .expect("the last check guarantees that the current fuel is smaller than cost"), |
525 | 53 | }); |
526 | | } |
527 | 16.4M | } |
528 | | |
529 | 16.4M | ControlFlow::Continue(()) |
530 | 16.4M | } |
531 | | |
532 | 1.00k | define_instruction_fn! {fc_extensions, fuel_check = omit, |args: Args| { |
533 | | // should we call instruction hook here as well? multibyte instruction |
534 | 1.00k | let second_instr = args.wasm.decode_var_u32().unwrap_validated(); |
535 | | |
536 | 1.00k | let instruction_fn = T::FC_DISPATCH_TABLE |
537 | 1.00k | .get(second_instr.into_usize()) |
538 | 1.00k | .expect("the instruction to be valid because the code is validated"); |
539 | | |
540 | | // SAFETY: All possible instruction handler functions use the same safety requirements, as |
541 | | // they are defined through the same macro: The caller ensures that the resumable is valid |
542 | | // in the current store. Also all other address types passed via the `Args` must come from |
543 | | // the current store itself. Therefore, they are automatically valid in this store. |
544 | | unsafe { |
545 | 1.00k | instruction_fn( |
546 | 1.00k | args.wasm, |
547 | 1.00k | args.resumable, |
548 | 1.00k | args.current_sidetable, |
549 | 1.00k | args.store_inner, |
550 | 1.00k | args.modules, |
551 | 1.00k | args.current_module, |
552 | 1.00k | args.current_function_end_marker, |
553 | 1.00k | ) |
554 | | } |
555 | 1.00k | }} |
556 | | |
557 | 25.8k | define_instruction_fn! {fd_extensions, fuel_check = omit, |args: Args| { |
558 | | // Should we call instruction hook here as well? Multibyte instruction |
559 | 25.8k | let second_instr = args.wasm.decode_var_u32().unwrap_validated(); |
560 | | |
561 | 25.8k | let instruction_fn = T::FD_DISPATCH_TABLE |
562 | 25.8k | .get(second_instr.into_usize()) |
563 | 25.8k | .expect("the instruction to be valid because the code is validated"); |
564 | | |
565 | | // SAFETY: All possible instruction handler functions use the same safety requirements, as |
566 | | // they are defined through the same macro: The caller ensures that the resumable is valid |
567 | | // in the current store. Also all other address types passed via the `Args` must come from |
568 | | // the current store itself. Therefore, they are automatically valid in this store. |
569 | | unsafe { |
570 | 25.8k | instruction_fn( |
571 | 25.8k | args.wasm, |
572 | 25.8k | args.resumable, |
573 | 25.8k | args.current_sidetable, |
574 | 25.8k | args.store_inner, |
575 | 25.8k | args.modules, |
576 | 25.8k | args.current_module, |
577 | 25.8k | args.current_function_end_marker, |
578 | 25.8k | ) |
579 | | } |
580 | 25.8k | }} |