wasm/execution/interpreter_loop/
control.rs

1use core::ops::ControlFlow;
2
3use crate::{
4    assert_validated::UnwrapValidatedExt,
5    core::{
6        error::DecodingError,
7        indices::{read_label_idx_unchecked, FuncIdx, TableIdx, TypeIdx},
8        reader::types::{opcode, BlockType},
9        utils::ToUsizeExt,
10    },
11    execution::interpreter_loop::{
12        define_instruction_fn, do_sidetable_control_transfer, Args, InterpreterLoopOutcome,
13    },
14    instances::FuncInst,
15    unreachable_validated,
16    value::Ref,
17    TrapError,
18};
19
20define_instruction_fn! {nop, fuel_check = flat(opcode::NOP), |_args| Ok(
21    ControlFlow::Continue(())
22)}
23
24define_instruction_fn! {
25    unreachable,
26    fuel_check = flat(opcode::UNREACHABLE),
27    |Args { .. }| { Err(TrapError::ReachedUnreachable.into()) }
28}
29
30define_instruction_fn! {
31    block,
32    fuel_check = flat(opcode::BLOCK),
33    |Args { wasm, .. }| {
34        // SAFETY: Validation guarantess there to be a valid block type
35        // next.
36        let _ = unsafe { BlockType::read_unchecked(wasm) };
37        Ok(ControlFlow::Continue(()))
38    }
39}
40
41define_instruction_fn! {
42    end,
43    fuel_check = flat(opcode::END),
44    |Args {
45         store_inner,
46         modules,
47         resumable,
48         wasm,
49         current_module,
50         current_function_end_marker,
51         current_sidetable,
52         ..
53     }| {
54        // There might be multiple ENDs in a single function. We want to
55        // exit only when the outermost block (aka function block) ends.
56        if wasm.pc != *current_function_end_marker {
57            return Ok(ControlFlow::Continue(()));
58        }
59
60        let Some((maybe_return_func_addr, maybe_return_address, maybe_return_stp)) =
61            resumable.stack.pop_call_frame()
62        else {
63            // We finished this entire invocation if this was the base call frame.
64            return Ok(ControlFlow::Break(
65                InterpreterLoopOutcome::ExecutionReturned,
66            ));
67        };
68        // If there are one or more call frames, we need to continue
69        // from where the callee was called from.
70
71        trace!("end of function reached, returning to previous call frame");
72        resumable.current_func_addr = maybe_return_func_addr;
73
74        // SAFETY: The current function address must come from the given
75        // resumable or the current store, because these are the only
76        // parameters to this function. The resumable, including its
77        // function address, is guaranteed to be valid in the current
78        // store by the caller, and the store can only contain addresses
79        // that are valid within itself.
80        let current_function = unsafe { store_inner.functions.get(resumable.current_func_addr) };
81        let FuncInst::WasmFunc(current_wasm_func_inst) = current_function else {
82            unreachable!(
83                "function addresses on the stack always correspond to native wasm functions"
84            )
85        };
86        *current_module = current_wasm_func_inst.module_addr;
87
88        // SAFETY: The current module address must come from the current
89        // store, because it is the only parameter to this function that
90        // can contain module addresses. All stores guarantee all
91        // addresses in them to be valid within themselves.
92        let module = unsafe { modules.get(*current_module) };
93
94        wasm.full_wasm_binary = module.wasm_bytecode;
95        wasm.pc = maybe_return_address;
96        resumable.stp = maybe_return_stp;
97
98        *current_sidetable = &module.sidetable;
99
100        *current_function_end_marker =
101            current_wasm_func_inst.code_expr.from() + current_wasm_func_inst.code_expr.len();
102
103        trace!("Instruction: END");
104
105        Ok(ControlFlow::Continue(()))
106    }
107}
108
109define_instruction_fn! {
110    r#loop,
111    fuel_check = flat(opcode::LOOP),
112    |Args { wasm, .. }| {
113        // SAFETY: Validation guarantees there to be a valid block type
114        // next.
115        let _ = unsafe { BlockType::read_unchecked(wasm) };
116        Ok(ControlFlow::Continue(()))
117    }
118}
119
120define_instruction_fn! {
121    r#if,
122    fuel_check = flat(opcode::IF),
123    |Args {
124         resumable,
125         wasm,
126         current_sidetable,
127         ..
128     }| {
129        // SAFETY: Validation guarantees there to be a valid block type
130        // next.
131        let _block_type = unsafe { BlockType::read_unchecked(wasm) };
132
133        let test_val: i32 = resumable.stack.pop_value().try_into().unwrap_validated();
134
135        if test_val != 0 {
136            resumable.stp += 1;
137        } else {
138            do_sidetable_control_transfer(
139                wasm,
140                &mut resumable.stack,
141                &mut resumable.stp,
142                current_sidetable,
143            )?;
144        }
145        trace!("Instruction: IF");
146
147        Ok(ControlFlow::Continue(()))
148    }
149}
150
151define_instruction_fn! {
152    r#else,
153    fuel_check = flat(opcode::ELSE),
154    |Args {
155         wasm,
156         resumable,
157         current_sidetable,
158         ..
159     }| {
160        do_sidetable_control_transfer(
161            wasm,
162            &mut resumable.stack,
163            &mut resumable.stp,
164            current_sidetable,
165        )?;
166        Ok(ControlFlow::Continue(()))
167    }
168}
169
170define_instruction_fn! {
171    br,
172    fuel_check = flat(opcode::BR),
173    |Args {
174         resumable,
175         wasm,
176         current_sidetable,
177         ..
178     }| {
179        // SAFETY: Validation guarantees there to be a valid label index
180        // next.
181        let _label_idx = unsafe { read_label_idx_unchecked(wasm) };
182        do_sidetable_control_transfer(
183            wasm,
184            &mut resumable.stack,
185            &mut resumable.stp,
186            current_sidetable,
187        )?;
188        Ok(ControlFlow::Continue(()))
189    }
190}
191
192define_instruction_fn! {
193    br_if,
194    fuel_check = flat(opcode::BR_IF),
195    |Args {
196         resumable,
197         wasm,
198         current_sidetable,
199         ..
200     }| {
201        // SAFETY: Validation guarantees there to be a valid label index
202        // next.
203        let _label_idx = unsafe { read_label_idx_unchecked(wasm) };
204
205        let test_val: i32 = resumable.stack.pop_value().try_into().unwrap_validated();
206
207        if test_val != 0 {
208            do_sidetable_control_transfer(
209                wasm,
210                &mut resumable.stack,
211                &mut resumable.stp,
212                current_sidetable,
213            )?;
214        } else {
215            resumable.stp += 1;
216        }
217        trace!("Instruction: BR_IF");
218        Ok(ControlFlow::Continue(()))
219    }
220}
221
222define_instruction_fn! {
223    br_table,
224    fuel_check = flat(opcode::BR_TABLE),
225    |Args {
226         resumable,
227         wasm,
228         current_sidetable,
229         ..
230     }| {
231        let label_vec = wasm
232            .read_vec::<_, _, DecodingError>(|wasm| {
233                // SAFETY: Validation guarantees that there is a
234                // valid vec of label indices.
235                Ok(unsafe { read_label_idx_unchecked(wasm) })
236            }).unwrap();
237
238        // SAFETY: Validation guarantees there to be another label index
239        // for the default case.
240        let _default_label_idx = unsafe { read_label_idx_unchecked(wasm) };
241
242        // TODO is this correct?
243        let case_val_i32: i32 = resumable.stack.pop_value().try_into().unwrap_validated();
244        let case_val = case_val_i32.cast_unsigned().into_usize();
245
246        if case_val >= label_vec.len() {
247            resumable.stp += label_vec.len();
248        } else {
249            resumable.stp += case_val;
250        }
251
252        do_sidetable_control_transfer(
253            wasm,
254            &mut resumable.stack,
255            &mut resumable.stp,
256            current_sidetable,
257        )?;
258        Ok(ControlFlow::Continue(()))
259    }
260}
261
262define_instruction_fn! {
263    r#return,
264    fuel_check = flat(opcode::RETURN),
265    |Args {
266         resumable,
267         wasm,
268         current_sidetable,
269         ..
270     }| {
271        // same as BR
272        do_sidetable_control_transfer(
273            wasm,
274            &mut resumable.stack,
275            &mut resumable.stp,
276            current_sidetable,
277        )?;
278        Ok(ControlFlow::Continue(()))
279    }
280}
281
282define_instruction_fn! {
283    call,
284    fuel_check = flat(opcode::CALL),
285    |Args {
286         store_inner,
287         modules,
288         resumable,
289         wasm,
290         current_module,
291         current_function_end_marker,
292         current_sidetable,
293         ..
294     }| {
295        // SAFETY: Validation guarantees there to be a valid function
296        // index next.
297        let func_idx = unsafe { FuncIdx::read_unchecked(wasm) };
298
299        // SAFETY: The current function address must come from the given
300        // resumable or the current store, because these are the only
301        // parameters to this function. The resumable, including its
302        // function address, is guaranteed to be valid in the current
303        // store by the caller, and the store can only contain addresses
304        // that are valid within itself.
305        let FuncInst::WasmFunc(current_wasm_func_inst) =
306            (unsafe { store_inner.functions.get(resumable.current_func_addr) })
307        else {
308            unreachable!()
309        };
310
311        // SAFETY: The current module address must come from the current
312        // store, because it is the only parameter to this function that
313        // can contain module addresses. All stores guarantee all
314        // addresses in them to be valid within themselves.
315        let current_module_inst = unsafe { modules.get(current_wasm_func_inst.module_addr) };
316
317        // SAFETY: Validation guarantees the function index to be
318        // valid in the current module.
319        let func_to_call_addr = unsafe { current_module_inst.func_addrs.get(func_idx) };
320
321        // SAFETY: This function address just came from the current
322        // store. Therefore, it must be valid in the current store.
323        let func_to_call_inst = unsafe { store_inner.functions.get(*func_to_call_addr) };
324
325        trace!("Instruction: call [{func_to_call_addr:?}]");
326
327        match func_to_call_inst {
328            FuncInst::HostFunc(host_func_to_call_inst) => {
329                let params = resumable
330                    .stack
331                    .pop_tail_iter(host_func_to_call_inst.function_type.params.valtypes.len());
332
333                return Ok(ControlFlow::Break(InterpreterLoopOutcome::HostCalled {
334                    params,
335                    func_addr: *func_to_call_addr,
336                    hostcode: host_func_to_call_inst.hostcode,
337                }));
338            }
339            FuncInst::WasmFunc(wasm_func_to_call_inst) => {
340                let remaining_locals = &wasm_func_to_call_inst.locals;
341
342                resumable.stack.push_call_frame::<T>(
343                    resumable.current_func_addr,
344                    &wasm_func_to_call_inst.function_type,
345                    remaining_locals,
346                    wasm.pc,
347                    resumable.stp,
348                )?;
349
350                resumable.current_func_addr = *func_to_call_addr;
351                *current_module = wasm_func_to_call_inst.module_addr;
352
353                // SAFETY: The current module address was just set to an
354                // address that came from the current store. Therefore,
355                // this address must automatically be valid in the
356                // current store.
357                let module = unsafe { modules.get(*current_module) };
358
359                wasm.full_wasm_binary = module.wasm_bytecode;
360                wasm.move_start_to(wasm_func_to_call_inst.code_expr)
361                    .expect("code expression spans to always be valid");
362
363                resumable.stp = wasm_func_to_call_inst.stp;
364                *current_sidetable = &module.sidetable;
365                *current_function_end_marker = wasm_func_to_call_inst.code_expr.from()
366                    + wasm_func_to_call_inst.code_expr.len();
367            }
368        }
369        trace!("Instruction: CALL");
370
371        Ok(ControlFlow::Continue(()))
372    }
373}
374
375// TODO: fix push_call_frame, because the func idx that you get from the table is global func idx
376define_instruction_fn! {
377    call_indirect,
378    fuel_check = flat(opcode::CALL_INDIRECT),
379    |Args {
380         store_inner,
381         modules,
382         resumable,
383         wasm,
384         current_module,
385         current_function_end_marker,
386         current_sidetable,
387         ..
388     }| {
389        // SAFETY: Validation guarantees there to be a valid type index
390        // next.
391        let given_type_idx = unsafe { TypeIdx::read_unchecked(wasm) };
392        // SAFETY: Validation guarantees there to be a valid table index
393        // next.
394        let table_idx = unsafe { TableIdx::read_unchecked(wasm) };
395
396        // SAFETY: The current module address must come from the current
397        // store, because it is the only parameter to this function that
398        // can contain module addresses. All stores guarantee all
399        // addresses in them to be valid within themselves.
400        let module = unsafe { modules.get(*current_module) };
401
402        // SAFETY: Validation guarantees the table index to be valid in
403        // the current module.
404        let table_addr = unsafe { module.table_addrs.get(table_idx) };
405        // SAFETY: This table address was just read from the current
406        // store. Therefore, it is valid in the current store.
407        let tab = unsafe { store_inner.tables.get(*table_addr) };
408        // SAFETY: Validation guarantees the type index to be valid in
409        // the current module.
410        let func_ty = unsafe { module.types.get(given_type_idx) };
411
412        let i: u32 = resumable.stack.pop_value().try_into().unwrap_validated();
413
414        let r = tab
415            .elem
416            .get(i.into_usize())
417            .ok_or(TrapError::TableAccessOutOfBounds)
418            .and_then(|r| {
419                if matches!(r, Ref::Null(_)) {
420                    trace!("table_idx ({table_idx}) --- element index in table ({i})");
421                    Err(TrapError::UninitializedElement)
422                } else {
423                    Ok(r)
424                }
425            })?;
426
427        let func_to_call_addr = match *r {
428            Ref::Func(func_addr) => func_addr,
429            Ref::Null(_) => return Err(TrapError::IndirectCallNullFuncRef.into()),
430            Ref::Extern(_) => unreachable_validated!(),
431        };
432
433        // SAFETY: This function address just came from a table of the
434        // current store. Therefore, it must be valid in the current
435        // store.
436        let func_to_call_inst = unsafe { store_inner.functions.get(func_to_call_addr) };
437
438        if func_ty != func_to_call_inst.ty() {
439            return Err(TrapError::SignatureMismatch.into());
440        }
441
442        trace!("Instruction: call [{func_to_call_addr:?}]");
443
444        match func_to_call_inst {
445            FuncInst::HostFunc(host_func_to_call_inst) => {
446                let params = resumable
447                    .stack
448                    .pop_tail_iter(host_func_to_call_inst.function_type.params.valtypes.len());
449
450                return Ok(ControlFlow::Break(InterpreterLoopOutcome::HostCalled {
451                    params,
452                    func_addr: func_to_call_addr,
453                    hostcode: host_func_to_call_inst.hostcode,
454                }));
455            }
456            FuncInst::WasmFunc(wasm_func_to_call_inst) => {
457                let remaining_locals = &wasm_func_to_call_inst.locals;
458
459                resumable.stack.push_call_frame::<T>(
460                    resumable.current_func_addr,
461                    &wasm_func_to_call_inst.function_type,
462                    remaining_locals,
463                    wasm.pc,
464                    resumable.stp,
465                )?;
466
467                resumable.current_func_addr = func_to_call_addr;
468                *current_module = wasm_func_to_call_inst.module_addr;
469
470                // SAFETY: The current module address was just set to an
471                // address that came from the current store. Therefore,
472                // this address must automatically be valid in the
473                // current store.
474                let module = unsafe { modules.get(*current_module) };
475                wasm.full_wasm_binary = module.wasm_bytecode;
476                wasm.move_start_to(wasm_func_to_call_inst.code_expr)
477                    .expect("code expression spans to always be valid");
478
479                resumable.stp = wasm_func_to_call_inst.stp;
480                *current_sidetable = &module.sidetable;
481                *current_function_end_marker = wasm_func_to_call_inst.code_expr.from()
482                    + wasm_func_to_call_inst.code_expr.len();
483            }
484        }
485        trace!("Instruction: CALL_INDIRECT");
486        Ok(ControlFlow::Continue(()))
487    }
488}