Coverage Report

Created: 2026-08-27 12:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 = &current_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
}