Skip to main content

vihaco_runtime/
stack.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4//! Framework stack preparation and effect-application protocols.
5
6use crate::{Composite, Execute, Field, Message, PopStack, PrepareMessage, PushStack, StackValue};
7
8/// Copied operands prepared for an instruction, distinct from its payload.
9///
10/// Tuple builders support arities 0–16. Fields retain their declared order,
11/// while extraction proceeds right to left: `(lhs, rhs)` pops `rhs` first.
12/// Successful pops remain consumed if a later pop or CPU execution fails.
13/// The empty tuple consumes nothing and does not access the stack capability.
14/// Tuple construction requires no allocation or stack borrow. Homogeneous
15/// [`crate::StackBatch`] inputs use [`StackInputCount`] and inline storage with
16/// heap fallback; their owned message likewise retains no stack borrow.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct PopFromStack<Values>(pub Values);
19
20impl<Values: 'static> Message for PopFromStack<Values> {}
21
22/// Results to insert during the framework's effect-application phase.
23///
24/// [`Apply`] supports tuples of arity 0–16 (including heterogeneous items) and
25/// homogeneous arrays of any length. Values are inserted left to right, so the
26/// last value becomes the top. Empty tuples/arrays insert nothing. The wrapper
27/// owns values, not storage references, and does not apply itself automatically.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct PushToStack<Values>(pub Values);
30
31/// Framework wiring from composite instruction preparation to operand storage.
32///
33/// A composite with a `#[program]` device supplies this connection automatically.
34/// Ordinary component/composite authors select `PopFromStack` and `PushToStack`
35/// types; they do not implement stack storage or transfer logic.
36///
37/// Opting into this capability selects the global tuple [`crate::BuildMessage`]
38/// implementations for compatible child message types. A field name or merely
39/// containing an ExecutionState does not establish the capability. Custom
40/// preparation can use a distinct message type with its own builder.
41///
42/// No canonical-Boolean validation policy is needed: [`StackValue`] Boolean
43/// extraction interprets every nonzero word as true.
44pub trait HasOperandStack<I> {
45    /// The checked operand source used for this instruction.
46    type Source;
47
48    /// Borrow the operand source for the supplied operand count.
49    ///
50    /// The arity allows a later cached implementation to prepare storage once.
51    /// This accessor must not consume operands, bulk-precheck their availability,
52    /// or otherwise change the sequential partial-consumption semantics.
53    /// Empty tuple and immediate empty batch builders do not invoke it.
54    fn operand_stack(&mut self, arity: usize) -> &mut Self::Source;
55}
56
57/// Framework hook to apply one effect during [`crate::Step`].
58///
59/// Authors can apply `PushToStack` effects to their program inside authored Steps.
60/// The framework does not impose effect handling. Implementations are
61/// extension plumbing for storage/effects, not required component boilerplate.
62///
63/// Stack output implementations use direct typed insertion. This trait neither
64/// executes instructions nor supplies a Step or observer callbacks. The caller
65/// handles [`crate::Effects`] in order, applying each effect after execution.
66/// Applied values are not
67/// rolled back if later work fails. Custom fallible effect handling remains in
68/// Step; stack insertion follows [`PushStack`]'s ordinary
69/// allocation behavior.
70pub trait Apply<E> {
71    /// Apply a single effect to this destination.
72    fn apply(&mut self, effect: E);
73}
74
75impl<C, F, I, Context: ?Sized> PrepareMessage<C, F, I, Context> for PopFromStack<()>
76where
77    C: Composite + Field<F> + HasOperandStack<I>,
78    C::Child: Execute<I, Message = PopFromStack<()>>,
79{
80    #[inline]
81    fn prepare(_: &mut C, _: &I, _: &Context) -> eyre::Result<Self> {
82        Ok(PopFromStack(()))
83    }
84}
85
86impl<D> Apply<PushToStack<()>> for D {
87    #[inline]
88    fn apply(&mut self, _: PushToStack<()>) {}
89}
90
91impl<D, T: StackValue, const N: usize> Apply<PushToStack<[T; N]>> for D
92where
93    D: PushStack<T::Storage>,
94{
95    #[inline]
96    fn apply(&mut self, effect: PushToStack<[T; N]>) {
97        for value in effect.0 {
98            value.push_to(self);
99        }
100    }
101}
102
103// Expand once here into ordinary global per-arity impls, never per instruction
104// or composite. The explicit message parameter keeps these disjoint from the
105// NoMessage blanket and consumers' distinct custom-message implementations.
106macro_rules! stack_tuple {
107    (@pop $source:ident;) => {};
108    (@pop $source:ident; $ty:ident:$value:ident $(, $rest:ident:$other:ident)*) => {
109        stack_tuple!(@pop $source; $($rest:$other),*);
110        let $value = $ty::pop_from($source)?;
111    };
112    ($($ty:ident:$value:ident),+) => {
113        impl<Parent, Route, Inst, Context: ?Sized, $($ty: StackValue),+>
114            PrepareMessage<Parent, Route, Inst, Context> for PopFromStack<($($ty,)+)>
115        where
116            Parent: Composite + Field<Route> + HasOperandStack<Inst>,
117            Parent::Child: Execute<Inst, Message = PopFromStack<($($ty,)+)>>,
118            Parent::Source: $(PopStack<$ty::Storage> +)+ Sized,
119        {
120            #[inline]
121            fn prepare(parent: &mut Parent, _: &Inst, _: &Context) -> eyre::Result<Self> {
122                let source = parent.operand_stack([$(stringify!($ty)),+].len());
123                stack_tuple!(@pop source; $($ty:$value),+);
124                Ok(PopFromStack(($($value,)+)))
125            }
126        }
127
128        impl<Destination, $($ty: StackValue),+>
129            Apply<PushToStack<($($ty,)+)>> for Destination
130        where
131            Destination: $(PushStack<$ty::Storage> +)+ Sized,
132        {
133            #[inline]
134            fn apply(&mut self, effect: PushToStack<($($ty,)+)>) {
135                let ($($value,)+) = effect.0;
136                $($value.push_to(self);)+
137            }
138        }
139    };
140}
141
142stack_tuple!(A:a);
143
144stack_tuple!(A:a, B:b);
145
146stack_tuple!(A:a, B:b, C:c);
147
148stack_tuple!(A:a, B:b, C:c, D:d);
149
150stack_tuple!(A:a, B:b, C:c, D:d, E:e);
151
152stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f);
153
154stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f, G:g);
155
156stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f, G:g, H:h);
157
158stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f, G:g, H:h, I:i);
159
160stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f, G:g, H:h, I:i, J:j);
161
162stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f, G:g, H:h, I:i, J:j, K:k);
163
164stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f, G:g, H:h, I:i, J:j, K:k, L:l);
165
166stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f, G:g, H:h, I:i, J:j, K:k, L:l, M:m);
167
168stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f, G:g, H:h, I:i, J:j, K:k, L:l, M:m, N:n);
169
170stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f, G:g, H:h, I:i, J:j, K:k, L:l, M:m, N:n, O:o);
171
172stack_tuple!(A:a, B:b, C:c, D:d, E:e, F:f, G:g, H:h, I:i, J:j, K:k, L:l, M:m, N:n, O:o, P:p);
173
174/// Where a homogeneous batch's operand count is obtained.
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum InputCount {
177    /// A count computed from the current instruction's payload.
178    Immediate(usize),
179    /// Pop a word count above the batch, checking that it fits `usize`.
180    FromStack,
181}
182
183/// Count metadata for a component/instruction pair using [`crate::StackBatch`].
184///
185/// Components describe the count source; vihaco performs all stack operations.
186/// This belongs to the component so external instruction vocabularies are usable.
187pub trait StackInputCount<I> {
188    fn input_count(instruction: &I) -> eyre::Result<InputCount>;
189}
190
191impl<C, F, I, T, Context: ?Sized> PrepareMessage<C, F, I, Context>
192    for PopFromStack<crate::StackBatch<T>>
193where
194    C: Composite + Field<F> + HasOperandStack<I>,
195    C::Child: Execute<I, Message = PopFromStack<crate::StackBatch<T>>> + StackInputCount<I>,
196    T: StackValue + 'static,
197    C::Source: PopStack<T::Storage> + PopStack<vihaco_abi::word::Word>,
198{
199    fn prepare(parent: &mut C, instruction: &I, _: &Context) -> eyre::Result<Self> {
200        let count = match C::Child::input_count(instruction)? {
201            InputCount::Immediate(count) => count,
202            InputCount::FromStack => {
203                let word = PopStack::<vihaco_abi::word::Word>::pop(parent.operand_stack(1))?;
204                usize::try_from(word)?
205            }
206        };
207        let mut values = smallvec::SmallVec::new();
208        // Reserve before value consumption. A count popped above remains consumed
209        // if reservation fails, just as earlier tuple pops remain consumed.
210        values
211            .try_reserve_exact(count)
212            .map_err(|error| eyre::eyre!("cannot reserve operand batch: {error:?}"))?;
213        if count != 0 {
214            let source = parent.operand_stack(count);
215            for _ in 0..count {
216                values.push(T::pop_from(source)?);
217            }
218            values.reverse();
219        }
220        Ok(PopFromStack(crate::StackBatch { values }))
221    }
222}
223
224// Selecting a program device also selects its operand storage. This connection
225// supplies no Step or effect policy.
226impl<C: crate::HasProgram, I> HasOperandStack<I> for C {
227    type Source = C::Program;
228    #[inline]
229    fn operand_stack(&mut self, _: usize) -> &mut Self::Source {
230        self.program_mut()
231    }
232}