Skip to main content

vihaco_stdlib/
execution_state.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4//! Checked invocation state and execution of an independently owned module.
5//!
6//! The host owns program data. `ExecutionState` owns frames, locals, operands,
7//! the private PC, and execution flags. A runner borrows both the module and the
8//! machine; instructions borrow the module while authored Steps mutate devices.
9//! Component authors choose messages/effects, without implementing stack logic.
10//!
11//! ```
12//! use vihaco_abi::{Type, Value};
13//! use vihaco_module::module::{LocalModule, FunctionInfo, Signature};
14//! use vihaco_runtime::{composite, field, Apply, BuildMessage, Execute, HasInstructionSet, Step};
15//! use vihaco_stdlib::{ExecutionState, Image, execution_state::execute, program::program};
16//! use vihaco_cpu::{CPU, cpu};
17//!
18//! #[composite]
19//! struct Machine {
20//!     #[device(0)] #[program] execution: vihaco_stdlib::ExecutionState,
21//!     #[device(1)] cpu: vihaco_cpu::CPU,
22//! }
23//! type Instruction = <Machine as HasInstructionSet>::Runtime;
24//! type Program = LocalModule<Instruction, Value, Type>;
25//!
26//! impl<I> Step<field!(Machine::execution), I, Program> for Machine
27//! where
28//!     ExecutionState: Execute<I, Effect = (), Error = eyre::Report>,
29//!     Self: BuildMessage<field!(Machine::execution), I, Program>,
30//! {
31//!     fn step(&mut self, instruction: &I, module: &Program) -> eyre::Result<()> {
32//!         let message = <Self as BuildMessage<field!(Machine::execution), I, Program>>
33//!             ::build_message(self, instruction, module)?;
34//!         self.execution.execute(instruction, message)
35//!     }
36//! }
37//! impl<I> Step<field!(Machine::cpu), I, Program> for Machine
38//! where
39//!     CPU: Execute<I, Error = eyre::Report>,
40//!     Self: BuildMessage<field!(Machine::cpu), I, Program>,
41//!     ExecutionState: Apply<<CPU as Execute<I>>::Effect>,
42//! {
43//!     fn step(&mut self, instruction: &I, module: &Program) -> eyre::Result<()> {
44//!         let message = <Self as BuildMessage<field!(Machine::cpu), I, Program>>
45//!             ::build_message(self, instruction, module)?;
46//!         let effect = self.cpu.execute(instruction, message)?;
47//!         self.execution.apply(effect);
48//!         Ok(())
49//!     }
50//! }
51//!
52//! let module: Program = LocalModule {
53//!     code: vec![
54//!         Instruction::V_execution__program__ConstU64(program::ConstU64(20)),
55//!         Instruction::V_execution__program__ConstU64(program::ConstU64(22)),
56//!         Instruction::V_cpu__cpu__AddU64(cpu::AddU64),
57//!         Instruction::V_execution__program__Return(program::Return(1)),
58//!     ],
59//!     main_function: Some(0),
60//!     functions: vec![FunctionInfo {
61//!         name: 0, signature: Signature { params: vec![], ret: vec![Type::U64] },
62//!         local_count: 0, start_address: 0, end_address: 4, file: 0,
63//!     }],
64//!     ..Default::default()
65//! };
66//! // An ordinary outer owner keeps immutable program data beside its machine.
67//! struct Loaded {
68//!     image: Image<Instruction>,
69//!     machine: Machine,
70//! }
71//! impl Loaded {
72//!     fn run(&mut self) -> eyre::Result<()> {
73//!         execute(&self.image.module, &mut self.machine, &[])
74//!     }
75//! }
76//! let mut loaded = Loaded {
77//!     image: Image::new(module),
78//!     machine: Machine { execution: ExecutionState::new(), cpu: CPU::default() },
79//! };
80//! loaded.run()?;
81//! assert_eq!(loaded.machine.execution.stack(), &[42]);
82//! # Ok::<(), eyre::Report>(())
83//! ```
84use crate::program::program;
85use vihaco_abi::{frame::Frame, word::Word};
86use vihaco_module::module::LocalModule;
87
88mod execution;
89mod messages;
90mod runner;
91mod storage;
92
93pub use runner::{execute, resume};
94
95#[vihaco_runtime::component]
96#[instructions { program }]
97/// Frames, locals, operands, and private execution state for one invocation.
98///
99/// The module is supplied by reference at entry and is never stored here.
100#[derive(Debug)]
101pub struct ExecutionState {
102    stack: Vec<Word>,
103    frames: Vec<Frame>,
104    pc: u32,
105    halted: bool,
106    executing: bool,
107}
108
109impl Default for ExecutionState {
110    fn default() -> Self {
111        Self {
112            stack: Vec::new(),
113            frames: Vec::new(),
114            pc: 0,
115            halted: true,
116            executing: false,
117        }
118    }
119}
120
121impl ExecutionState {
122    pub fn new() -> Self {
123        Self::default()
124    }
125}
126
127impl Clone for ExecutionState {
128    fn clone(&self) -> Self {
129        Self {
130            stack: self.stack.clone(),
131            frames: self.frames.clone(),
132            pc: self.pc,
133            halted: self.halted,
134            executing: false,
135        }
136    }
137}
138
139#[cold]
140#[inline(never)]
141fn instruction_out_of_bounds(pc: u32, len: usize) -> eyre::Report {
142    eyre::eyre!("instruction index out of bounds: {} (max {})", pc, len)
143}
144
145fn function_entry<I, V, Ty, Info>(
146    module: &LocalModule<I, V, Ty, Info>,
147    index: usize,
148) -> eyre::Result<(usize, usize, u32)> {
149    let function = module
150        .functions
151        .get(index)
152        .ok_or_else(|| unknown_function(index))?;
153    if (function.local_count as usize) < function.signature.params.len() {
154        return Err(invalid_local_count());
155    }
156    if (function.start_address as usize) >= module.code.len() {
157        return Err(invalid_call_target());
158    }
159    Ok((
160        function.signature.params.len(),
161        function.local_count as usize,
162        function.start_address,
163    ))
164}
165
166#[cold]
167#[inline(never)]
168fn unknown_function(index: usize) -> eyre::Report {
169    eyre::eyre!("unknown function reference {index}")
170}
171
172#[cold]
173#[inline(never)]
174fn invalid_local_count() -> eyre::Report {
175    eyre::eyre!("local count must be at least arity")
176}
177
178#[cold]
179#[inline(never)]
180fn invalid_call_target() -> eyre::Report {
181    eyre::eyre!("call target out of bounds")
182}