1use std::sync::Arc;
28
29use javm_cap::{CNodeCap, CapHash, CapHashOrRef, SlotIdx};
30use javm_exec::{GasCounter, Mem, PvmProgram, Regs};
31
32use crate::error::VmError;
33
34pub const DEFAULT_MAX_DEPTH: usize = 256;
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum EntryStatus {
42 Running,
43 Waiting,
44}
45
46pub struct InstanceEntry {
53 pub instance_ref: CapHashOrRef,
57 pub image_hash_chain: CapHash,
59 pub image_hash: CapHash,
61 pub program: Arc<PvmProgram>,
63 pub root_cnode: CNodeCap,
66 pub yield_marker_slot: Option<SlotIdx>,
68 pub pinned_slots: Vec<SlotIdx>,
71 pub regs: Regs,
73 pub mem: Mem,
75 pub gas: GasCounter,
78 pub status: EntryStatus,
80}
81
82impl std::fmt::Debug for InstanceEntry {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.debug_struct("InstanceEntry")
85 .field("image_hash_chain", &short_hex(&self.image_hash_chain))
86 .field("image_hash", &short_hex(&self.image_hash))
87 .field("pc", &self.regs.pc)
88 .field("status", &self.status)
89 .field("cnode.size_log", &self.root_cnode.size_log)
90 .finish_non_exhaustive()
91 }
92}
93
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99pub struct ReferenceEntry {
100 pub target_position: usize,
102 pub status: EntryStatus,
104}
105
106#[derive(Debug)]
113pub enum Entry {
114 Instance(Box<InstanceEntry>),
115 Reference(ReferenceEntry),
116}
117
118impl Entry {
119 pub fn status(&self) -> EntryStatus {
120 match self {
121 Entry::Instance(e) => e.status,
122 Entry::Reference(e) => e.status,
123 }
124 }
125
126 pub fn set_status(&mut self, s: EntryStatus) {
127 match self {
128 Entry::Instance(e) => e.status = s,
129 Entry::Reference(e) => e.status = s,
130 }
131 }
132
133 pub fn is_instance(&self) -> bool {
134 matches!(self, Entry::Instance(_))
135 }
136
137 pub fn as_instance(&self) -> Option<&InstanceEntry> {
138 match self {
139 Entry::Instance(e) => Some(e.as_ref()),
140 _ => None,
141 }
142 }
143
144 pub fn as_instance_mut(&mut self) -> Option<&mut InstanceEntry> {
145 match self {
146 Entry::Instance(e) => Some(e.as_mut()),
147 _ => None,
148 }
149 }
150}
151
152pub struct CallStack {
159 entries: Vec<Entry>,
160 max_depth: usize,
161}
162
163impl CallStack {
164 pub fn new(max_depth: usize) -> Self {
165 Self {
166 entries: Vec::new(),
167 max_depth,
168 }
169 }
170
171 pub fn with_default_depth() -> Self {
172 Self::new(DEFAULT_MAX_DEPTH)
173 }
174
175 pub fn len(&self) -> usize {
176 self.entries.len()
177 }
178
179 pub fn is_empty(&self) -> bool {
180 self.entries.is_empty()
181 }
182
183 pub fn entries(&self) -> &[Entry] {
184 &self.entries
185 }
186
187 pub fn entries_mut(&mut self) -> &mut [Entry] {
192 &mut self.entries
193 }
194
195 pub fn push_instance(&mut self, mut entry: InstanceEntry) -> Result<(), VmError> {
198 if self.entries.len() >= self.max_depth {
199 return Err(VmError::CallStackFull);
200 }
201 if let Some(top) = self.entries.last_mut() {
202 top.set_status(EntryStatus::Waiting);
203 }
204 entry.status = EntryStatus::Running;
205 self.entries.push(Entry::Instance(Box::new(entry)));
206 Ok(())
207 }
208
209 pub fn push_reference(&mut self, target_position: usize) -> Result<(), VmError> {
213 if self.entries.len() >= self.max_depth {
214 return Err(VmError::CallStackFull);
215 }
216 if target_position >= self.entries.len() {
217 return Err(VmError::ReferenceOutOfRange(target_position));
218 }
219 if !self.entries[target_position].is_instance() {
220 return Err(VmError::ReferenceNonInstance(target_position));
221 }
222 if let Some(top) = self.entries.last_mut() {
223 top.set_status(EntryStatus::Waiting);
224 }
225 self.entries.push(Entry::Reference(ReferenceEntry {
226 target_position,
227 status: EntryStatus::Running,
228 }));
229 Ok(())
230 }
231
232 pub fn pop(&mut self) -> Option<Entry> {
235 let popped = self.entries.pop();
236 if let Some(top) = self.entries.last_mut() {
237 top.set_status(EntryStatus::Running);
238 }
239 popped
240 }
241
242 pub fn running(&self) -> Option<&Entry> {
244 self.entries.last()
245 }
246
247 pub fn running_mut(&mut self) -> Option<&mut Entry> {
248 self.entries.last_mut()
249 }
250
251 pub fn running_instance(&self) -> Option<&InstanceEntry> {
256 match self.entries.last()? {
257 Entry::Instance(e) => Some(e.as_ref()),
258 Entry::Reference(r) => match self.entries.get(r.target_position)? {
259 Entry::Instance(e) => Some(e.as_ref()),
260 Entry::Reference(_) => None, },
262 }
263 }
264
265 pub fn running_instance_mut(&mut self) -> Option<&mut InstanceEntry> {
266 let last_idx = self.entries.len().checked_sub(1)?;
267 let target_idx = match &self.entries[last_idx] {
268 Entry::Instance(_) => last_idx,
269 Entry::Reference(r) => r.target_position,
270 };
271 match self.entries.get_mut(target_idx)? {
272 Entry::Instance(e) => Some(e.as_mut()),
273 Entry::Reference(_) => None,
274 }
275 }
276
277 pub fn enforce_invariants(&self) -> Result<(), VmError> {
281 if self.entries.is_empty() {
282 return Ok(());
283 }
284 for (i, e) in self.entries.iter().enumerate() {
286 let expected = if i == self.entries.len() - 1 {
287 EntryStatus::Running
288 } else {
289 EntryStatus::Waiting
290 };
291 if e.status() != expected {
292 return Err(VmError::Invariant(
293 "exactly one Running entry, at the stack top",
294 ));
295 }
296 }
297 for (i, e) in self.entries.iter().enumerate() {
299 if let Entry::Reference(r) = e {
300 if r.target_position >= i {
301 return Err(VmError::ReferenceOutOfRange(r.target_position));
302 }
303 if !matches!(self.entries[r.target_position], Entry::Instance(_)) {
304 return Err(VmError::ReferenceNonInstance(r.target_position));
305 }
306 }
307 }
308 Ok(())
309 }
310}
311
312impl std::fmt::Debug for CallStack {
313 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314 f.debug_struct("CallStack")
315 .field("len", &self.entries.len())
316 .field("max_depth", &self.max_depth)
317 .finish()
318 }
319}
320
321fn short_hex(bytes: &[u8]) -> String {
322 bytes.iter().take(4).map(|b| format!("{:02x}", b)).collect()
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use javm_exec::PvmProgram;
329
330 fn make_entry(tag: u8) -> InstanceEntry {
331 let prog = Arc::new(PvmProgram::new(vec![0u8], vec![1u8], vec![], 25).unwrap());
332 let cnode = CNodeCap::new(8).unwrap();
333 InstanceEntry {
334 instance_ref: CapHashOrRef::Hash([tag; 32]),
335 image_hash_chain: [tag; 32],
336 image_hash: [tag.wrapping_add(0x10); 32],
337 program: prog,
338 root_cnode: cnode,
339 yield_marker_slot: None,
340 pinned_slots: Vec::new(),
341 regs: Regs::new(),
342 mem: Mem::new(),
343 gas: GasCounter::new(1000),
344 status: EntryStatus::Waiting,
345 }
346 }
347
348 #[test]
349 fn empty_stack_invariants_hold() {
350 let s = CallStack::with_default_depth();
351 assert!(s.is_empty());
352 assert!(s.running().is_none());
353 s.enforce_invariants().unwrap();
354 }
355
356 #[test]
357 fn push_instance_makes_it_running() {
358 let mut s = CallStack::with_default_depth();
359 s.push_instance(make_entry(1)).unwrap();
360 assert_eq!(s.len(), 1);
361 assert_eq!(s.running().unwrap().status(), EntryStatus::Running);
362 s.enforce_invariants().unwrap();
363 }
364
365 #[test]
366 fn push_two_instances_top_running_rest_waiting() {
367 let mut s = CallStack::with_default_depth();
368 s.push_instance(make_entry(1)).unwrap();
369 s.push_instance(make_entry(2)).unwrap();
370 assert_eq!(s.len(), 2);
371 assert_eq!(s.entries()[0].status(), EntryStatus::Waiting);
372 assert_eq!(s.entries()[1].status(), EntryStatus::Running);
373 s.enforce_invariants().unwrap();
374 }
375
376 #[test]
377 fn pop_promotes_next() {
378 let mut s = CallStack::with_default_depth();
379 s.push_instance(make_entry(1)).unwrap();
380 s.push_instance(make_entry(2)).unwrap();
381 let popped = s.pop().unwrap();
382 assert_eq!(popped.status(), EntryStatus::Running);
383 assert_eq!(s.running().unwrap().status(), EntryStatus::Running);
385 s.enforce_invariants().unwrap();
386 }
387
388 #[test]
389 fn pop_last_leaves_empty() {
390 let mut s = CallStack::with_default_depth();
391 s.push_instance(make_entry(1)).unwrap();
392 s.pop().unwrap();
393 assert!(s.is_empty());
394 s.enforce_invariants().unwrap();
395 }
396
397 #[test]
398 fn push_reference_targets_earlier_instance() {
399 let mut s = CallStack::with_default_depth();
400 s.push_instance(make_entry(1)).unwrap();
401 s.push_instance(make_entry(2)).unwrap();
402 s.push_reference(0).unwrap();
403 assert_eq!(s.len(), 3);
404 assert!(matches!(s.entries()[0].status(), EntryStatus::Waiting));
406 assert!(matches!(s.entries()[1].status(), EntryStatus::Waiting));
407 assert!(matches!(s.entries()[2].status(), EntryStatus::Running));
408 s.enforce_invariants().unwrap();
409 }
410
411 #[test]
412 fn push_reference_out_of_range_rejected() {
413 let mut s = CallStack::with_default_depth();
414 s.push_instance(make_entry(1)).unwrap();
415 let res = s.push_reference(5);
416 assert!(matches!(res, Err(VmError::ReferenceOutOfRange(5))));
417 }
418
419 #[test]
420 fn push_reference_targeting_reference_rejected() {
421 let mut s = CallStack::with_default_depth();
422 s.push_instance(make_entry(1)).unwrap();
423 s.push_reference(0).unwrap();
424 let res = s.push_reference(1);
427 assert!(matches!(res, Err(VmError::ReferenceNonInstance(1))));
428 }
429
430 #[test]
431 fn push_beyond_max_depth_rejected() {
432 let mut s = CallStack::new(2);
433 s.push_instance(make_entry(1)).unwrap();
434 s.push_instance(make_entry(2)).unwrap();
435 let res = s.push_instance(make_entry(3));
436 assert!(matches!(res, Err(VmError::CallStackFull)));
437 }
438
439 #[test]
440 fn running_instance_resolves_through_reference() {
441 let mut s = CallStack::with_default_depth();
442 s.push_instance(make_entry(1)).unwrap();
443 s.push_reference(0).unwrap();
444 let ic = s.running_instance().unwrap();
445 assert_eq!(ic.image_hash_chain, [1u8; 32]);
447 }
448}