Skip to main content

hara_native/vm/
frame.rs

1//! One execution frame: the local slot array plus its operand-stack base.
2
3use super::slot::VmSlot;
4
5#[derive(Debug)]
6pub struct Frame {
7    locals: Vec<VmSlot>,
8    base: usize,
9}
10
11impl Frame {
12    /// The frame the machine starts with: all slots initialized to `nil`.
13    pub(crate) fn entry(local_count: usize) -> Frame {
14        Frame {
15            locals: vec![VmSlot::Nil; local_count],
16            base: 0,
17        }
18    }
19
20    /// The frame for a function call: `args` fill the parameter slots
21    /// `0..arity`, `captures` the capture slots directly above them, and
22    /// the remaining slots start as `nil`. Out-of-range writes are dropped
23    /// rather than panicking (the validator guarantees they fit; this
24    /// defends hand-built programs).
25    pub(crate) fn call(
26        local_count: usize,
27        arity: usize,
28        mut args: Vec<VmSlot>,
29        captures: Vec<VmSlot>,
30        base: usize,
31    ) -> Frame {
32        Self::call_reusing(Vec::new(), local_count, arity, &mut args, captures, base)
33    }
34
35    pub(crate) fn call_reusing(
36        mut locals: Vec<VmSlot>,
37        local_count: usize,
38        arity: usize,
39        args: &mut Vec<VmSlot>,
40        captures: Vec<VmSlot>,
41        base: usize,
42    ) -> Frame {
43        locals.clear();
44        locals.resize(local_count, VmSlot::Nil);
45        for (index, value) in args.drain(..).enumerate() {
46            if let Some(cell) = locals.get_mut(index) {
47                *cell = value;
48            }
49        }
50        for (index, value) in captures.into_iter().enumerate() {
51            if let Some(cell) = locals.get_mut(arity + index) {
52                *cell = value;
53            }
54        }
55        Frame { locals, base }
56    }
57
58    /// Builds a capture-free frame by moving the top `argc` operands
59    /// directly into its parameter slots. Static named calls use this path
60    /// so arguments do not make an otherwise redundant trip through a
61    /// temporary argument vector before entering the callee.
62    pub(crate) fn call_static_reusing(
63        mut locals: Vec<VmSlot>,
64        local_count: usize,
65        stack: &mut Vec<VmSlot>,
66        argc: usize,
67    ) -> Frame {
68        let base = stack.len() - argc;
69        locals.clear();
70        locals.resize(local_count, VmSlot::Nil);
71        for (index, value) in stack.drain(base..).enumerate() {
72            locals[index] = value;
73        }
74        Frame { locals, base }
75    }
76
77    pub(crate) fn into_locals(self) -> Vec<VmSlot> {
78        self.locals
79    }
80
81    pub(crate) fn local(&self, slot: u16) -> Option<&VmSlot> {
82        self.locals.get(usize::from(slot))
83    }
84
85    /// Clones the `count` slots starting at `start`; `None` when the range
86    /// exceeds the frame (rejected by validation, defended here).
87    pub(crate) fn slot_range(&self, start: usize, count: usize) -> Option<Vec<VmSlot>> {
88        let end = start.checked_add(count)?;
89        if end > self.locals.len() {
90            return None;
91        }
92        Some(self.locals[start..end].to_vec())
93    }
94
95    /// Stores a value into a slot; false when the slot is out of range
96    /// (rejected by validation, defended here so the machine never
97    /// panics on malformed programs).
98    pub(crate) fn store(&mut self, slot: u16, value: VmSlot) -> bool {
99        match self.locals.get_mut(usize::from(slot)) {
100            Some(cell) => {
101                *cell = value;
102                true
103            }
104            None => false,
105        }
106    }
107
108    /// Borrowed local slots for bounded machine observations.
109    pub(crate) fn locals(&self) -> &[VmSlot] {
110        &self.locals
111    }
112
113    /// Operand-stack base at which this frame was entered.
114    pub(crate) fn base(&self) -> usize {
115        self.base
116    }
117
118    #[cfg(feature = "tracing-jit")]
119    pub(crate) fn trace_locals(&self) -> (Vec<crate::jit::TraceValue>, Vec<bool>) {
120        let mut scalar = Vec::with_capacity(self.locals.len());
121        let mut writable = Vec::with_capacity(self.locals.len());
122        for value in &self.locals {
123            match value {
124                VmSlot::Number(value) => {
125                    scalar.push(crate::jit::TraceValue::I64(*value));
126                    writable.push(true);
127                }
128                VmSlot::Bool(value) => {
129                    scalar.push(crate::jit::TraceValue::Bool(*value));
130                    writable.push(true);
131                }
132                VmSlot::Nil => {
133                    scalar.push(crate::jit::TraceValue::Nil);
134                    writable.push(true);
135                }
136                VmSlot::Value(value)
137                    if matches!(
138                        value.as_ref(),
139                        crate::core::Value::Tuple(_) | crate::core::Value::Vector(_)
140                    ) =>
141                {
142                    scalar.push(crate::jit::TraceValue::Indexed(Box::new(
143                        value.as_ref().clone(),
144                    )));
145                    writable.push(true);
146                }
147                _ => {
148                    scalar.push(crate::jit::TraceValue::Unsupported);
149                    writable.push(false);
150                }
151            }
152        }
153        (scalar, writable)
154    }
155
156    #[cfg(feature = "tracing-jit")]
157    pub(crate) fn apply_trace_locals(
158        &mut self,
159        values: &[crate::jit::TraceValue],
160        writable: &[bool],
161    ) {
162        for (index, (value, writable)) in values.iter().zip(writable).enumerate() {
163            if !writable {
164                continue;
165            }
166            self.locals[index] = match value {
167                crate::jit::TraceValue::I64(value) => VmSlot::Number(*value),
168                crate::jit::TraceValue::Bool(value) => VmSlot::Bool(*value),
169                crate::jit::TraceValue::Nil => VmSlot::Nil,
170                crate::jit::TraceValue::Indexed(value) => {
171                    VmSlot::Value(std::rc::Rc::new(value.as_ref().clone()))
172                }
173                crate::jit::TraceValue::VectorSlice(slice) => {
174                    VmSlot::Value(std::rc::Rc::new(crate::core::Value::Vector(
175                        slice.values[slice.start..]
176                            .iter()
177                            .copied()
178                            .map(crate::core::Value::Number)
179                            .collect(),
180                    )))
181                }
182                crate::jit::TraceValue::Unsupported => continue,
183            };
184        }
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::{Frame, VmSlot};
191
192    #[test]
193    fn static_call_moves_arguments_and_preserves_caller_stack() {
194        let mut stack = vec![VmSlot::Number(9), VmSlot::Number(20), VmSlot::Number(22)];
195        let frame = Frame::call_static_reusing(Vec::new(), 3, &mut stack, 2);
196        assert!(matches!(stack.as_slice(), [VmSlot::Number(9)]));
197        assert_eq!(frame.base(), 1);
198        assert!(matches!(frame.local(0), Some(VmSlot::Number(20))));
199        assert!(matches!(frame.local(1), Some(VmSlot::Number(22))));
200        assert!(matches!(frame.local(2), Some(VmSlot::Nil)));
201    }
202}