passerine/vm/
stack.rs

1use std::{
2    mem,
3    cell::RefCell,
4    cmp::Ordering,
5    rc::Rc,
6};
7
8use crate::common::data::Data;
9
10use crate::vm::{
11    tag::Tagged,
12    slot::{Slot, Suspend},
13};
14
15/// A stack of `Tagged` `Data`.
16/// Note that in general the stack is expected to follow the following pattern:
17/// ```plain
18/// FV...V...F V...T... ...
19/// ```
20/// Or in other words, a frame followed by a block of *n* values that are locals
21/// followed by *n* temporaries, ad infinitum.
22#[derive(Debug)]
23pub struct Stack {
24    pub frames: Vec<usize>,
25    pub stack:  Vec<Tagged>
26}
27
28impl Stack {
29    /// Create a new `Stack` with a single frame.
30    pub fn init() -> Stack {
31        Stack {
32            frames: vec![0],
33            stack:  vec![Tagged::frame()],
34        }
35    }
36
37    /// Return the index of the topmost `Tagged(Slot::Frame)`.
38    #[inline]
39    fn frame_index(&self) -> usize {
40        *self.frames.last().unwrap()
41    }
42
43    /// Pop and return the topmost `Tagged` item.
44    #[inline]
45    fn pop(&mut self) -> Tagged {
46        self.stack.pop()
47            .expect("VM tried to pop empty stack, stack should never be empty")
48    }
49
50    /// Swaps out a `Tagged` item without another `Tagged` item, provided its index.
51    #[inline]
52    fn swap(&mut self, index: usize, tagged: Tagged) -> Tagged {
53        mem::replace(&mut self.stack[index], tagged)
54    }
55
56    /// Pushes some `Data` onto the `Stack`, tagging it along the way
57    #[inline]
58    pub fn push_data(&mut self, data: Data) {
59        self.stack.push(Tagged::new(Slot::Data(data)))
60    }
61
62    /// Pushes some `Tagged` `Data` onto the `Stack` without unwrapping it.
63    #[inline]
64    pub fn push_tagged(&mut self, tagged: Tagged) {
65        self.stack.push(tagged)
66    }
67
68    /// Pops some `Data` of the `Stack`, panicking if what it pops is not `Data`.
69    /// Note that this will never return a `Heaped` value, rather cloning the value inside.
70    #[inline]
71    pub fn pop_data(&mut self) -> Data {
72        let value = self.stack.pop()
73            .expect("VM tried to pop empty stack, stack should never be empty");
74
75        match value.slot().data() {
76            Data::Heaped(h) => h.borrow().clone(),
77            d => d,
78        }
79    }
80
81    /// Pops a stack frame from the `Stack`, restoring the previous frame.
82    /// Panics if there are no frames left on the stack.
83    #[inline]
84    pub fn pop_frame(&mut self) -> Suspend {
85        if let Slot::Frame = self.pop().slot() {} else {
86            unreachable!("Expected frame on top of stack");
87        }
88
89        self.frames.pop();
90        let old_slot = self.swap(self.frame_index(), Tagged::frame()).slot();
91
92        if let Slot::Suspend(s) = old_slot {
93            s
94        } else {
95            unreachable!("Expected frame on top of stack");
96        }
97    }
98
99    /// Pushes a new stack frame onto the `Stack`.
100    /// Takes the old suspended closure / ip, and stores that on the stack.
101    #[inline]
102    pub fn push_frame(&mut self, suspend: Suspend) {
103        let frame_index = self.frame_index();
104        self.stack[frame_index] = Tagged::new(Slot::Suspend(suspend));
105        self.frames.push(self.stack.len());
106        self.stack.push(Tagged::frame());
107    }
108
109    /// Shorcut for pushing a `Tagged(Slot::NotInit)` on top of the stack.
110    #[inline]
111    pub fn push_not_init(&mut self) {
112        self.stack.push(Tagged::not_init());
113    }
114
115    /// Shortcut for calling `push_not_init` N times.
116    #[inline]
117    pub fn declare(&mut self, decls: usize) {
118        for _ in 0..decls { self.push_not_init(); }
119    }
120
121    /// Wraps the top data value on the stack in `Data::Heaped`,
122    /// data must not already be on the heap
123    #[inline]
124    pub fn heapify(&mut self, index: usize) {
125        let local_index = self.frame_index() + index + 1;
126
127        let data = self.swap(local_index, Tagged::not_init()).slot().data();
128        let heaped = Slot::Data(Data::Heaped(Rc::new(RefCell::new(data))));
129        mem::drop(mem::replace(&mut self.stack[local_index], Tagged::new(heaped)));
130    }
131
132    /// Truncates the stack to the last frame.
133    /// Returns `true` if the stack can not be unwound further.
134    #[inline]
135    pub fn unwind_frame(&mut self) -> bool {
136        self.stack.truncate(self.frame_index() + 1);
137        self.frames.len() > 1
138    }
139
140    /// returns a copy of the `Slot` of a local variable on the stack.
141    pub fn local_slot(&mut self, index: usize) -> Slot {
142        let local_index = self.frame_index() + index + 1;
143
144        // a little bit of shuffling involved
145        // I know that something better than this can be done
146        let slot = self.swap(local_index, Tagged::not_init()).slot();
147        let copy = slot.clone();
148        mem::drop(self.swap(local_index, Tagged::new(slot)));
149
150        copy
151    }
152
153    /// Returns a copy of the `Data` stored in a local variable on the stack.
154    pub fn local_data(&mut self, index: usize) -> Data {
155        let local_index = self.frame_index() + index + 1;
156
157        // a little bit of shuffling involved
158        // I know that something better than this can be done
159        let data = self.swap(local_index, Tagged::not_init()).slot().data();
160        let copy = data.clone();
161        mem::drop(self.swap(local_index, Tagged::new(Slot::Data(data))));
162
163        copy
164    }
165
166    /// Sets a local - note that this function doesn't do much.
167    /// It's a simple swap-and-drop.
168    /// If a new local is being declared,
169    /// it's literally a bounds-check and no-op.
170    pub fn set_local(&mut self, index: usize) {
171        let local_index = self.frame_index() + index + 1;
172
173        match (self.stack.len() - 1).cmp(&local_index) {
174            Ordering::Greater => {
175                // get the old local
176                let slot = self.swap(local_index, Tagged::not_init()).slot();
177
178                // replace the old value with the new one if on the heap
179                let tagged = match slot {
180                    Slot::Frame => unreachable!("Expected data, found frame"),
181                    // if it is on the heap, we replace in the old value
182                    Slot::Data(Data::Heaped(ref cell)) => {
183                        // TODO: check types?
184                        mem::drop(cell.replace(self.pop_data()));
185                        Tagged::new(slot)
186                    }
187                    // if it's not on the heap, we assume it's data,
188                    // and do a quick swap-and-drop
189                    _ => self.stack.pop().unwrap(),
190                };
191
192                mem::drop(self.swap(local_index, tagged))
193            },
194            Ordering::Less => {
195                // println!("{} < {}", self.stack.len() - 1, local_index);
196                unreachable!("Can not set local that is not yet on stack");
197            },
198            _ => {
199                // local is already in the correct spot; we declare it
200            }
201        }
202    }
203}