tinywasm/interpreter/stack/
call_stack.rs

1use core::ops::ControlFlow;
2
3use super::BlockType;
4use crate::interpreter::values::*;
5use crate::Trap;
6use crate::{unlikely, Error};
7
8use alloc::boxed::Box;
9use alloc::{rc::Rc, vec, vec::Vec};
10use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmValue};
11
12pub(crate) const MAX_CALL_STACK_SIZE: usize = 1024;
13
14#[derive(Debug)]
15pub(crate) struct CallStack {
16    stack: Vec<CallFrame>,
17}
18
19impl CallStack {
20    #[inline]
21    pub(crate) fn new(initial_frame: CallFrame) -> Self {
22        Self { stack: vec![initial_frame] }
23    }
24
25    #[inline(always)]
26    pub(crate) fn pop(&mut self) -> Option<CallFrame> {
27        self.stack.pop()
28    }
29
30    #[inline(always)]
31    pub(crate) fn push(&mut self, call_frame: CallFrame) -> ControlFlow<Option<Error>> {
32        if unlikely((self.stack.len() + 1) >= MAX_CALL_STACK_SIZE) {
33            return ControlFlow::Break(Some(Trap::CallStackOverflow.into()));
34        }
35        self.stack.push(call_frame);
36        ControlFlow::Continue(())
37    }
38}
39
40#[derive(Debug)]
41pub(crate) struct CallFrame {
42    instr_ptr: usize,
43    func_instance: Rc<WasmFunction>,
44    block_ptr: u32,
45    module_addr: ModuleInstanceAddr,
46    pub(crate) locals: Locals,
47}
48
49#[derive(Debug)]
50pub(crate) struct Locals {
51    pub(crate) locals_32: Box<[Value32]>,
52    pub(crate) locals_64: Box<[Value64]>,
53    pub(crate) locals_128: Box<[Value128]>,
54    pub(crate) locals_ref: Box<[ValueRef]>,
55}
56
57impl Locals {
58    pub(crate) fn get<T: InternalValue>(&self, local_index: LocalAddr) -> T {
59        T::local_get(self, local_index)
60    }
61
62    pub(crate) fn set<T: InternalValue>(&mut self, local_index: LocalAddr, value: T) {
63        T::local_set(self, local_index, value)
64    }
65}
66
67impl CallFrame {
68    #[inline(always)]
69    pub(crate) fn instr_ptr(&self) -> usize {
70        self.instr_ptr
71    }
72
73    #[inline(always)]
74    pub(crate) fn incr_instr_ptr(&mut self) {
75        self.instr_ptr += 1;
76    }
77
78    #[inline(always)]
79    pub(crate) fn jump(&mut self, offset: usize) {
80        self.instr_ptr += offset;
81    }
82
83    #[inline(always)]
84    pub(crate) fn module_addr(&self) -> ModuleInstanceAddr {
85        self.module_addr
86    }
87
88    #[inline(always)]
89    pub(crate) fn block_ptr(&self) -> u32 {
90        self.block_ptr
91    }
92
93    #[inline(always)]
94    pub(crate) fn fetch_instr(&self) -> &Instruction {
95        &self.func_instance.instructions[self.instr_ptr]
96    }
97
98    /// Break to a block at the given index (relative to the current frame)
99    /// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller)
100    #[inline(always)]
101    pub(crate) fn break_to(
102        &mut self,
103        break_to_relative: u32,
104        values: &mut super::ValueStack,
105        blocks: &mut super::BlockStack,
106    ) -> Option<()> {
107        let break_to = blocks.get_relative_to(break_to_relative, self.block_ptr)?;
108
109        // instr_ptr points to the label instruction, but the next step
110        // will increment it by 1 since we're changing the "current" instr_ptr
111        match break_to.ty {
112            BlockType::Loop => {
113                // this is a loop, so we want to jump back to the start of the loop
114                self.instr_ptr = break_to.instr_ptr;
115
116                // We also want to push the params to the stack
117                values.truncate_keep(break_to.stack_ptr, break_to.params);
118
119                // check if we're breaking to the loop
120                if break_to_relative != 0 {
121                    // we also want to trim the label stack to the loop (but not including the loop)
122                    blocks.truncate(blocks.len() as u32 - break_to_relative);
123                    return Some(());
124                }
125            }
126
127            BlockType::Block | BlockType::If | BlockType::Else => {
128                // this is a block, so we want to jump to the next instruction after the block ends
129                // We also want to push the block's results to the stack
130                values.truncate_keep(break_to.stack_ptr, break_to.results);
131
132                // (the inst_ptr will be incremented by 1 before the next instruction is executed)
133                self.instr_ptr = break_to.instr_ptr + break_to.end_instr_offset as usize;
134
135                // we also want to trim the label stack, including the block
136                blocks.truncate(blocks.len() as u32 - (break_to_relative + 1));
137            }
138        }
139
140        Some(())
141    }
142
143    #[inline(always)]
144    pub(crate) fn new(
145        wasm_func_inst: Rc<WasmFunction>,
146        owner: ModuleInstanceAddr,
147        params: &[WasmValue],
148        block_ptr: u32,
149    ) -> Self {
150        let locals = {
151            let mut locals_32 = Vec::new();
152            locals_32.reserve_exact(wasm_func_inst.locals.c32 as usize);
153            let mut locals_64 = Vec::new();
154            locals_64.reserve_exact(wasm_func_inst.locals.c64 as usize);
155            let mut locals_128 = Vec::new();
156            locals_128.reserve_exact(wasm_func_inst.locals.c128 as usize);
157            let mut locals_ref = Vec::new();
158            locals_ref.reserve_exact(wasm_func_inst.locals.cref as usize);
159
160            for p in params {
161                match p.into() {
162                    TinyWasmValue::Value32(v) => locals_32.push(v),
163                    TinyWasmValue::Value64(v) => locals_64.push(v),
164                    TinyWasmValue::Value128(v) => locals_128.push(v),
165                    TinyWasmValue::ValueRef(v) => locals_ref.push(v),
166                }
167            }
168
169            locals_32.resize_with(wasm_func_inst.locals.c32 as usize, Default::default);
170            locals_64.resize_with(wasm_func_inst.locals.c64 as usize, Default::default);
171            locals_128.resize_with(wasm_func_inst.locals.c128 as usize, Default::default);
172            locals_ref.resize_with(wasm_func_inst.locals.cref as usize, Default::default);
173
174            Locals {
175                locals_32: locals_32.into_boxed_slice(),
176                locals_64: locals_64.into_boxed_slice(),
177                locals_128: locals_128.into_boxed_slice(),
178                locals_ref: locals_ref.into_boxed_slice(),
179            }
180        };
181
182        Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals }
183    }
184
185    #[inline]
186    pub(crate) fn new_raw(
187        wasm_func_inst: Rc<WasmFunction>,
188        owner: ModuleInstanceAddr,
189        locals: Locals,
190        block_ptr: u32,
191    ) -> Self {
192        Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals }
193    }
194
195    #[inline(always)]
196    pub(crate) fn instructions(&self) -> &[Instruction] {
197        &self.func_instance.instructions
198    }
199}