Skip to main content

vihaco_stdlib/execution_state/
storage.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4//! Checked word backing for frame locals and typed operands.
5
6use super::{ExecutionState, function_entry};
7use vihaco_abi::{frame::Frame, traits::Reset, word::*};
8use vihaco_module::module::LocalModule;
9use vihaco_module::{PopStack, PushStack};
10
11impl ExecutionState {
12    /// Materialized locals and operands, or results after the outermost return.
13    pub fn stack(&self) -> &[Word] {
14        &self.stack
15    }
16
17    /// Active call frames, from main to the current function.
18    pub fn frames(&self) -> &[Frame] {
19        &self.frames
20    }
21
22    /// True before entry, after Halt, or after the outermost return.
23    pub fn is_halted(&self) -> bool {
24        self.halted
25    }
26
27    /// Enter main using its module metadata and host-supplied arguments.
28    ///
29    /// Entry creates the outermost frame, zeroes non-argument locals, selects
30    /// the function's first instruction, and clears the halt flag. A halted
31    /// invocation can be replaced while reusing allocated storage.
32    ///
33    /// # Errors
34    /// Rejects an active invocation, invalid metadata/target, a wrong argument
35    /// count, or allocation failure, preserving the preceding logical state.
36    pub fn start<I, V, Ty, Info>(
37        &mut self,
38        module: &LocalModule<I, V, Ty, Info>,
39        arguments: &[Word],
40    ) -> eyre::Result<()> {
41        eyre::ensure!(
42            self.halted && !self.executing,
43            "a function is already active"
44        );
45        let function = module
46            .main_function
47            .ok_or_else(|| eyre::eyre!("module has no main function"))?;
48        let (arity, locals, target) = function_entry(module, function as usize)?;
49        eyre::ensure!(arguments.len() == arity, "entry argument count mismatch");
50        self.stack
51            .try_reserve(locals.saturating_sub(self.stack.len()))?;
52        if self.frames.capacity() == 0 {
53            self.frames.try_reserve(1)?;
54        }
55        self.stack.clear();
56        self.stack.extend_from_slice(arguments);
57        self.stack.resize(locals, 0);
58        self.frames.clear();
59        self.frames.push(Frame {
60            base: 0,
61            local_count: locals,
62            // Frame's shared record retains this legacy field; this path does
63            // not track source spans or implement a Span instruction.
64            span: (0, 0, 0),
65            function: Some(function as usize),
66            ret_pc: 0,
67        });
68        self.pc = target;
69        self.halted = false;
70        Ok(())
71    }
72
73    /// Current continuation, or the fault/terminal position after execution stops.
74    pub fn pc(&self) -> u32 {
75        self.pc
76    }
77
78    #[inline(always)]
79    pub(super) fn ensure_running(&self) -> eyre::Result<()> {
80        eyre::ensure!(
81            !self.halted && !self.frames.is_empty(),
82            "program is not running"
83        );
84        Ok(())
85    }
86
87    #[inline]
88    pub(super) fn require_operands(&self, count: usize) -> eyre::Result<()> {
89        self.ensure_running()?;
90        let frame = self
91            .frames
92            .last()
93            .ok_or_else(|| eyre::eyre!("no current frame"))?;
94        eyre::ensure!(
95            self.stack.len().saturating_sub(frame.operands_index()) >= count,
96            "stack underflow"
97        );
98        Ok(())
99    }
100
101    #[inline]
102    pub(super) fn peek_operand(&self) -> eyre::Result<Word> {
103        self.require_operands(1)?;
104        self.stack
105            .last()
106            .copied()
107            .ok_or_else(|| eyre::eyre!("stack underflow"))
108    }
109
110    #[inline]
111    pub(super) fn local_address(&self, index: u32) -> eyre::Result<usize> {
112        self.ensure_running()?;
113        let frame = self
114            .frames
115            .last()
116            .ok_or_else(|| eyre::eyre!("no current frame"))?;
117        eyre::ensure!(
118            (index as usize) < frame.local_count,
119            "local index out of bounds"
120        );
121        let address = frame
122            .base
123            .checked_add(index as usize)
124            .ok_or_else(|| eyre::eyre!("local address overflow"))?;
125        eyre::ensure!(address < self.stack.len(), "local index out of bounds");
126        Ok(address)
127    }
128}
129
130impl Reset for ExecutionState {
131    fn reset(&mut self) {
132        self.stack.clear();
133        self.frames.clear();
134        self.pc = 0;
135        self.halted = true;
136    }
137}
138
139impl PopStack<Word> for ExecutionState {
140    #[inline]
141    fn pop(&mut self) -> eyre::Result<Word> {
142        self.require_operands(1)?;
143        self.stack
144            .pop()
145            .ok_or_else(|| eyre::eyre!("stack underflow"))
146    }
147}
148
149// Insertion is storage plumbing, not execution: it never starts an invocation
150// or changes PC. Step wiring supplies results only after successful preparation.
151impl PushStack<Word> for ExecutionState {
152    #[inline]
153    fn push(&mut self, value: Word) {
154        self.stack.push(value);
155    }
156}
157
158macro_rules! float_access {
159    ($ty:ty, $decode:ident, $encode:ident) => {
160        impl PopStack<$ty> for ExecutionState {
161            #[inline]
162            fn pop(&mut self) -> eyre::Result<$ty> {
163                <Self as PopStack<Word>>::pop(self).map($decode)
164            }
165        }
166        impl PushStack<$ty> for ExecutionState {
167            #[inline]
168            fn push(&mut self, value: $ty) {
169                <Self as PushStack<Word>>::push(self, $encode(value));
170            }
171        }
172    };
173}
174
175float_access!(f32, decode_f32, encode_f32);
176
177float_access!(f64, decode_f64, encode_f64);