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}