1use std::collections::HashMap;
25
26use shape_value::VMError;
27
28use crate::bytecode::{Function, FunctionHash};
29
30pub(crate) fn resolve_function_identity(
35 function_id_by_hash: &HashMap<FunctionHash, u16>,
36 functions: &[Function],
37 blob_hash: Option<FunctionHash>,
38 function_id: Option<u16>,
39 function_name: Option<&str>,
40) -> Result<u16, VMError> {
41 if let Some(hash) = blob_hash {
43 let resolved = function_id_by_hash.get(&hash).copied().ok_or_else(|| {
44 VMError::RuntimeError(format!("unknown function blob hash: {}", hash))
45 })?;
46 if let Some(fid) = function_id {
48 if fid != resolved {
49 return Err(VMError::RuntimeError(format!(
50 "function_id/hash mismatch: frame id {} does not match hash {} (resolved id {})",
51 fid, hash, resolved
52 )));
53 }
54 }
55 return Ok(resolved);
56 }
57
58 if let Some(fid) = function_id {
60 if (fid as usize) < functions.len() {
61 return Ok(fid);
62 }
63 return Err(VMError::RuntimeError(format!(
64 "function_id {} out of range (program has {} functions)",
65 fid,
66 functions.len()
67 )));
68 }
69
70 if let Some(name) = function_name {
72 let matches: Vec<usize> = functions
73 .iter()
74 .enumerate()
75 .filter_map(|(idx, f)| if f.name == name { Some(idx) } else { None })
76 .collect();
77 return match matches.len() {
78 1 => Ok(matches[0] as u16),
79 0 => Err(VMError::RuntimeError(format!(
80 "no function named '{}'",
81 name
82 ))),
83 n => Err(VMError::RuntimeError(format!(
84 "ambiguous function name '{}' ({} matches)",
85 name, n
86 ))),
87 };
88 }
89
90 Err(VMError::RuntimeError(
92 "cannot resolve function identity: no hash, id, or name provided".into(),
93 ))
94}
95
96fn w17_snapshot_surface(op: &str) -> String {
103 format!(
104 "VirtualMachine::{op}: W17-snapshot-resume surface — \
105 kind-threaded `slot_to_serializable(bits, kind, store)` / \
106 inverse `serializable_to_slot(sv, expected_kind, store)` \
107 replacement for the deleted `nanboxed_to_serializable` / \
108 `serializable_to_nanboxed` pair has not landed. The design \
109 must (a) project every `NativeKind::Ptr(HeapKind::*)` slot to a \
110 `SerializableVMValue` arm of the right shape via \
111 `slot.as_heap_value()` + `HeapValue::*` match (§2.7.6 Q8 \
112 carrier-API bound), (b) reconstruct the parallel kind tracks \
113 from the persisted discriminator on restore (§2.7.7 / §2.7.8), \
114 (c) extend `SerializableVMValue` for the post-W14/W15 \
115 HeapKinds that have no current wire-format arm: HashSet, \
116 Iterator, Result, Option, Deque, Channel, PriorityQueue, \
117 Range, Reference, FilterExpr, SharedCell — the §2.7.5.1 \
118 wire-format extension question. Tracked as W17-snapshot-resume \
119 per docs/cluster-audits/phase-2d-playbook.md §3. ADR-006 \
120 §2.7.4 + §2.7.5.1.",
121 )
122}
123
124impl super::VirtualMachine {
125 pub fn snapshot(
140 &self,
141 store: &shape_runtime::snapshot::SnapshotStore,
142 ) -> Result<shape_runtime::snapshot::VmSnapshot, VMError> {
143 use shape_runtime::snapshot::{
144 SerializableExceptionHandler, SerializableLoopContext, VmSnapshot,
145 slot_to_serializable,
146 };
147
148 let mut stack: Vec<shape_runtime::snapshot::SerializableVMValue> =
153 Vec::with_capacity(self.sp);
154 for i in 0..self.sp {
155 let bits = self.stack[i];
156 let kind = self.kinds[i];
157 let sv = slot_to_serializable(bits, kind, store).map_err(|msg| {
158 VMError::NotImplemented(format!(
159 "VirtualMachine::snapshot stack[{i}] kind={kind:?}: {msg}"
160 ))
161 })?;
162 stack.push(sv);
163 }
164
165 let mb_len = self.module_bindings.len();
169 debug_assert_eq!(mb_len, self.module_binding_kinds.len());
170 let mut module_bindings: Vec<shape_runtime::snapshot::SerializableVMValue> =
171 Vec::with_capacity(mb_len);
172 for i in 0..mb_len {
173 let bits = self.module_bindings[i];
174 let kind = self.module_binding_kinds[i];
175 let sv = slot_to_serializable(bits, kind, store).map_err(|msg| {
176 VMError::NotImplemented(format!(
177 "VirtualMachine::snapshot module_binding[{i}] kind={kind:?}: {msg}"
178 ))
179 })?;
180 module_bindings.push(sv);
181 }
182
183 let locals: Vec<shape_runtime::snapshot::SerializableVMValue> = Vec::new();
189
190 let loop_stack = self.snapshot_loop_stack_for_export();
196 let timeframe_stack = self.snapshot_timeframe_stack_for_export();
197 let exception_handlers = self.snapshot_exception_handlers_for_export();
198 let call_stack = self.snapshot_call_stack_for_export();
199 let _: &Vec<SerializableLoopContext> = &loop_stack;
200 let _: &Vec<SerializableExceptionHandler> = &exception_handlers;
201
202 Ok(VmSnapshot {
203 ip: self.snapshot_ip(),
204 stack,
205 locals,
206 module_bindings,
207 call_stack,
208 loop_stack,
209 timeframe_stack,
210 exception_handlers,
211 ip_blob_hash: None,
212 ip_local_offset: None,
213 ip_function_id: None,
214 })
215 }
216
217 pub fn from_snapshot(
236 program: crate::bytecode::BytecodeProgram,
237 snapshot: &shape_runtime::snapshot::VmSnapshot,
238 store: &shape_runtime::snapshot::SnapshotStore,
239 ) -> Result<Self, VMError> {
240 use shape_runtime::snapshot::serializable_to_slot;
241 use shape_value::NativeKind;
242
243 let mut vm = super::VirtualMachine::new(crate::VMConfig::default());
244 vm.load_program(program);
245
246 for (i, sv) in snapshot.stack.iter().enumerate() {
253 let expected = expected_kind_from_serializable(sv);
254 let (bits, kind) = serializable_to_slot(sv, expected, store).map_err(|msg| {
255 VMError::NotImplemented(format!(
256 "VirtualMachine::from_snapshot stack[{i}]: {msg}"
257 ))
258 })?;
259 vm.push_kinded(bits, kind)?;
261 }
262
263 if !snapshot.module_bindings.is_empty() {
265 let needed = snapshot.module_bindings.len();
267 vm.module_binding_pad_to_kinded(needed);
268 for (i, sv) in snapshot.module_bindings.iter().enumerate() {
269 let expected = expected_kind_from_serializable(sv);
270 let (bits, kind) = serializable_to_slot(sv, expected, store).map_err(|msg| {
271 VMError::NotImplemented(format!(
272 "VirtualMachine::from_snapshot module_binding[{i}]: {msg}"
273 ))
274 })?;
275 vm.module_binding_write_kinded(i, bits, kind);
276 }
277 }
278
279 vm.snapshot_set_ip(snapshot.ip);
281
282 if !snapshot.call_stack.is_empty() {
301 vm.restore_call_stack(&snapshot.call_stack, store)?;
302 }
303
304 let _ = (
313 &snapshot.loop_stack,
314 &snapshot.timeframe_stack,
315 &snapshot.exception_handlers,
316 );
317
318 let _ = NativeKind::Int64; Ok(vm)
321 }
322
323 fn restore_call_stack(
343 &mut self,
344 frames: &[shape_runtime::snapshot::SerializableCallFrame],
345 store: &shape_runtime::snapshot::SnapshotStore,
346 ) -> Result<(), VMError> {
347 use shape_runtime::snapshot::serializable_to_slot;
348 use shape_value::NativeKind;
349 use shape_value::v2::closure_raw::{
350 OwnedClosureBlock, alloc_typed_closure, write_capture_raw_u64,
351 };
352
353 for (frame_idx, sframe) in frames.iter().enumerate() {
354 let function_id = sframe.function_id;
355 let mut closure_heap_bits: Option<u64> = None;
356 let mut closure_heap_kind: Option<NativeKind> = None;
357 let mut upvalues_raw: Option<Vec<u64>> = None;
358
359 if let (Some(svec), Some(fid)) = (sframe.upvalues.as_ref(), function_id) {
360 let layout_opt = self
364 .program
365 .closure_function_layouts
366 .get(fid as usize)
367 .and_then(|o| o.clone());
368 if let Some(layout) = layout_opt {
369 if layout.capture_count() != svec.len() {
370 return Err(VMError::NotImplemented(format!(
371 "VirtualMachine::from_snapshot frame[{frame_idx}]: \
372 W17-snapshot-roundtrip surface — upvalue count \
373 mismatch (snapshot: {}, layout.capture_count: {}). \
374 ADR-006 §2.7.5.1.",
375 svec.len(),
376 layout.capture_count(),
377 )));
378 }
379 let ptr = unsafe { alloc_typed_closure(fid, 0, &layout) };
383 for (i, sv) in svec.iter().enumerate() {
384 let expected = layout.capture_native_kind(i);
385 let (bits, _kind) =
386 serializable_to_slot(sv, expected, store).map_err(|msg| {
387 VMError::NotImplemented(format!(
388 "VirtualMachine::from_snapshot frame[{frame_idx}] \
389 upvalue[{i}]: {msg}"
390 ))
391 })?;
392 unsafe {
394 write_capture_raw_u64(ptr, &layout, i, bits);
395 }
396 }
397 let block = unsafe { OwnedClosureBlock::from_raw(ptr as *const u8, layout) };
404 closure_heap_bits = Some(block.as_ptr() as u64);
405 closure_heap_kind = Some(NativeKind::Ptr(
406 shape_value::HeapKind::Closure,
407 ));
408 std::mem::forget(block);
409 } else {
410 let mut raw: Vec<u64> = Vec::with_capacity(svec.len());
415 for sv in svec {
416 let expected = NativeKind::Bool;
421 let (bits, _) =
422 serializable_to_slot(sv, expected, store).unwrap_or((0, NativeKind::Bool));
423 raw.push(bits);
424 }
425 upvalues_raw = Some(raw);
426 }
427 }
428
429 let blob_hash = sframe
430 .blob_hash
431 .map(crate::bytecode::FunctionHash);
432
433 self.call_stack.push(super::CallFrame {
434 return_ip: sframe.return_ip,
435 base_pointer: sframe.locals_base,
436 locals_count: sframe.locals_count,
437 function_id,
438 upvalues: upvalues_raw,
439 blob_hash,
440 closure_heap_bits,
441 closure_heap_kind,
442 });
443 }
444 Ok(())
445 }
446
447 fn snapshot_ip(&self) -> usize {
455 self.ip
461 }
462
463 fn snapshot_set_ip(&mut self, ip: usize) {
464 self.ip = ip;
465 }
466
467 fn snapshot_loop_stack_for_export(
468 &self,
469 ) -> Vec<shape_runtime::snapshot::SerializableLoopContext> {
470 self.loop_stack
471 .iter()
472 .map(|lc| shape_runtime::snapshot::SerializableLoopContext {
473 start: lc.start,
474 end: lc.end,
475 })
476 .collect()
477 }
478
479 fn snapshot_timeframe_stack_for_export(
480 &self,
481 ) -> Vec<Option<shape_ast::data::Timeframe>> {
482 self.timeframe_stack.clone()
483 }
484
485 fn snapshot_exception_handlers_for_export(
486 &self,
487 ) -> Vec<shape_runtime::snapshot::SerializableExceptionHandler> {
488 self.exception_handlers
489 .iter()
490 .map(|h| shape_runtime::snapshot::SerializableExceptionHandler {
491 catch_ip: h.catch_ip,
492 stack_size: h.stack_size,
493 call_depth: h.call_depth,
494 })
495 .collect()
496 }
497
498 fn snapshot_call_stack_for_export(
499 &self,
500 ) -> Vec<shape_runtime::snapshot::SerializableCallFrame> {
501 let store = shape_runtime::snapshot::SnapshotStore::new(
515 std::env::temp_dir().join("shape-w17-snapshot-store"),
516 )
517 .ok();
518 self.call_stack
519 .iter()
520 .map(|frame| {
521 let upvalues = if let Some(ref s) = store {
522 snapshot_frame_upvalues_serializable(self, frame, s)
523 } else {
524 None
525 };
526 shape_runtime::snapshot::SerializableCallFrame {
527 return_ip: frame.return_ip,
528 locals_base: frame.base_pointer,
529 locals_count: frame.locals_count,
530 function_id: frame.function_id,
531 upvalues,
532 blob_hash: frame.blob_hash.map(|h| h.0),
533 local_ip: None,
534 }
535 })
536 .collect()
537 }
538}
539
540fn snapshot_frame_upvalues_serializable(
546 vm: &super::VirtualMachine,
547 frame: &super::CallFrame,
548 store: &shape_runtime::snapshot::SnapshotStore,
549) -> Option<Vec<shape_runtime::snapshot::SerializableVMValue>> {
550 use shape_runtime::snapshot::slot_to_serializable;
551 use shape_value::v2::closure_raw::{
552 OwnedClosureBlock, retain_typed_closure, typed_closure_function_id,
553 };
554
555 let bits = frame.closure_heap_bits?;
556 if bits == 0 {
557 return None;
558 }
559 let ptr = bits as *const u8;
560 let fn_id = unsafe { typed_closure_function_id(ptr) };
562 let layout = vm
563 .program
564 .closure_function_layouts
565 .get(fn_id as usize)
566 .and_then(|opt| opt.clone())?;
567 unsafe {
572 retain_typed_closure(ptr);
573 }
574 let block = unsafe { OwnedClosureBlock::from_raw(ptr, layout) };
575 let count = block.layout().capture_count();
576 let mut out: Vec<shape_runtime::snapshot::SerializableVMValue> = Vec::with_capacity(count);
577 for idx in 0..count {
578 let (cap_bits, cap_kind) = unsafe { block.read_capture_kinded(idx) };
580 let sv = match slot_to_serializable(cap_bits, cap_kind, store) {
581 Ok(v) => v,
582 Err(_) => {
583 shape_runtime::snapshot::SerializableVMValue::IteratorOpaque
588 }
589 };
590 out.push(sv);
591 }
592 Some(out)
593}
594
595fn expected_kind_from_serializable(
602 sv: &shape_runtime::snapshot::SerializableVMValue,
603) -> shape_value::NativeKind {
604 use shape_runtime::snapshot::SerializableVMValue as SV;
605 use shape_value::{HeapKind, NativeKind};
606 match sv {
607 SV::Int(_) => NativeKind::Int64,
608 SV::Number(_) => NativeKind::Float64,
609 SV::Bool(_) => NativeKind::Bool,
610 SV::String(_) => NativeKind::String,
611 SV::None | SV::Unit => NativeKind::Bool,
612 SV::Decimal(_) => NativeKind::Ptr(HeapKind::Decimal),
613 SV::BigInt(_) => NativeKind::Ptr(HeapKind::BigInt),
614 SV::Char(_) => NativeKind::Ptr(HeapKind::Char),
615 SV::HashSet { .. } => NativeKind::Ptr(HeapKind::HashSet),
616 SV::PriorityQueueHeap { .. } => NativeKind::Ptr(HeapKind::PriorityQueue),
617 SV::AtomicI64 { .. } => NativeKind::Ptr(HeapKind::Atomic),
618 SV::ResultData { .. } => NativeKind::Ptr(HeapKind::Result),
619 SV::OptionData { .. } => NativeKind::Ptr(HeapKind::Option),
620 SV::IteratorOpaque => NativeKind::Ptr(HeapKind::Iterator),
621 SV::DequeOpaque { .. } => NativeKind::Ptr(HeapKind::Deque),
622 SV::ChannelOpaque { .. } => NativeKind::Ptr(HeapKind::Channel),
623 SV::ReferenceOpaque => NativeKind::Ptr(HeapKind::Reference),
624 SV::FilterExprOpaque => NativeKind::Ptr(HeapKind::FilterExpr),
625 SV::SharedCellOpaque => NativeKind::Ptr(HeapKind::SharedCell),
626 SV::MutexOpaque { .. } => NativeKind::Ptr(HeapKind::Mutex),
627 SV::LazyOpaque { .. } => NativeKind::Ptr(HeapKind::Lazy),
628 _ => NativeKind::Bool,
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 fn make_function(name: &str) -> Function {
639 Function {
640 name: name.to_string(),
641 arity: 0,
642 param_names: Vec::new(),
643 locals_count: 0,
644 entry_point: 0,
645 body_length: 0,
646 is_closure: false,
647 captures_count: 0,
648 is_async: false,
649 ref_params: Vec::new(),
650 ref_mutates: Vec::new(),
651 mutable_captures: Vec::new(),
652 frame_descriptor: None,
653 osr_entry_points: Vec::new(),
654 mir_data: None,
655 }
656 }
657
658 fn make_hash(seed: u8) -> FunctionHash {
659 FunctionHash([seed; 32])
660 }
661
662 #[test]
663 fn test_resolve_by_hash() {
664 let hash = make_hash(0xAB);
665 let mut by_hash = HashMap::new();
666 by_hash.insert(hash, 3u16);
667 let funcs = vec![
668 make_function("a"),
669 make_function("b"),
670 make_function("c"),
671 make_function("d"),
672 ];
673
674 let result = resolve_function_identity(&by_hash, &funcs, Some(hash), None, None);
675 assert_eq!(result.unwrap(), 3);
676 }
677
678 #[test]
679 fn test_resolve_hash_not_found_is_error() {
680 let hash = make_hash(0xAB);
681 let by_hash = HashMap::new(); let funcs = vec![make_function("a")];
683
684 let result = resolve_function_identity(&by_hash, &funcs, Some(hash), None, None);
685 assert!(result.is_err());
686 let msg = result.unwrap_err().to_string();
687 assert!(msg.contains("unknown function blob hash"), "got: {}", msg);
688 }
689
690 #[test]
691 fn test_resolve_hash_function_id_mismatch_is_error() {
692 let hash = make_hash(0xCD);
693 let mut by_hash = HashMap::new();
694 by_hash.insert(hash, 2u16); let funcs = vec![make_function("a"), make_function("b"), make_function("c")];
696
697 let result = resolve_function_identity(&by_hash, &funcs, Some(hash), Some(5), None);
699 assert!(result.is_err());
700 let msg = result.unwrap_err().to_string();
701 assert!(msg.contains("mismatch"), "got: {}", msg);
702 }
703
704 #[test]
705 fn test_resolve_hash_function_id_agree() {
706 let hash = make_hash(0xEF);
707 let mut by_hash = HashMap::new();
708 by_hash.insert(hash, 1u16);
709 let funcs = vec![make_function("a"), make_function("b")];
710
711 let result = resolve_function_identity(&by_hash, &funcs, Some(hash), Some(1), None);
713 assert_eq!(result.unwrap(), 1);
714 }
715
716 #[test]
717 fn test_resolve_by_function_id() {
718 let by_hash = HashMap::new();
719 let funcs = vec![make_function("a"), make_function("b"), make_function("c")];
720
721 let result = resolve_function_identity(&by_hash, &funcs, None, Some(2), None);
722 assert_eq!(result.unwrap(), 2);
723 }
724
725 #[test]
726 fn test_resolve_function_id_out_of_range() {
727 let by_hash = HashMap::new();
728 let funcs = vec![make_function("a")];
729
730 let result = resolve_function_identity(&by_hash, &funcs, None, Some(99), None);
731 assert!(result.is_err());
732 let msg = result.unwrap_err().to_string();
733 assert!(msg.contains("out of range"), "got: {}", msg);
734 }
735
736 #[test]
737 fn test_resolve_unique_name_fallback() {
738 let by_hash = HashMap::new();
739 let funcs = vec![
740 make_function("alpha"),
741 make_function("beta"),
742 make_function("gamma"),
743 ];
744
745 let result = resolve_function_identity(&by_hash, &funcs, None, None, Some("beta"));
746 assert_eq!(result.unwrap(), 1);
747 }
748
749 #[test]
750 fn test_resolve_ambiguous_name_is_error() {
751 let by_hash = HashMap::new();
752 let funcs = vec![
753 make_function("dup"),
754 make_function("other"),
755 make_function("dup"),
756 ];
757
758 let result = resolve_function_identity(&by_hash, &funcs, None, None, Some("dup"));
759 assert!(result.is_err());
760 let msg = result.unwrap_err().to_string();
761 assert!(msg.contains("ambiguous"), "got: {}", msg);
762 }
763
764 #[test]
765 fn test_resolve_name_not_found() {
766 let by_hash = HashMap::new();
767 let funcs = vec![make_function("a")];
768
769 let result = resolve_function_identity(&by_hash, &funcs, None, None, Some("missing"));
770 assert!(result.is_err());
771 let msg = result.unwrap_err().to_string();
772 assert!(msg.contains("no function named"), "got: {}", msg);
773 }
774
775 #[test]
776 fn test_resolve_no_identifiers_is_error() {
777 let by_hash = HashMap::new();
778 let funcs = vec![make_function("a")];
779
780 let result = resolve_function_identity(&by_hash, &funcs, None, None, None);
781 assert!(result.is_err());
782 let msg = result.unwrap_err().to_string();
783 assert!(msg.contains("no hash, id, or name"), "got: {}", msg);
784 }
785
786 use shape_runtime::snapshot::VmSnapshot;
794
795 #[test]
796 fn test_snapshot_ip_relocation_fields_present() {
797 let snapshot = VmSnapshot {
799 ip: 42,
800 stack: vec![],
801 locals: vec![],
802 module_bindings: vec![],
803 call_stack: vec![],
804 loop_stack: vec![],
805 timeframe_stack: vec![],
806 exception_handlers: vec![],
807 ip_blob_hash: Some([0xAB; 32]),
808 ip_local_offset: Some(10),
809 ip_function_id: Some(1),
810 };
811 assert_eq!(snapshot.ip, 42);
812 assert_eq!(snapshot.ip_blob_hash, Some([0xAB; 32]));
813 assert_eq!(snapshot.ip_local_offset, Some(10));
814 assert_eq!(snapshot.ip_function_id, Some(1));
815 }
816
817 #[test]
818 fn test_snapshot_legacy_without_relocation_fields() {
819 let snapshot = VmSnapshot {
822 ip: 100,
823 stack: vec![],
824 locals: vec![],
825 module_bindings: vec![],
826 call_stack: vec![],
827 loop_stack: vec![],
828 timeframe_stack: vec![],
829 exception_handlers: vec![],
830 ip_blob_hash: None,
831 ip_local_offset: None,
832 ip_function_id: None,
833 };
834 assert!(snapshot.ip_blob_hash.is_none());
836 assert!(snapshot.ip_local_offset.is_none());
837 assert!(snapshot.ip_function_id.is_none());
838 }
839
840 #[test]
841 fn test_snapshot_serialization_roundtrip_with_relocation() {
842 let snapshot = VmSnapshot {
843 ip: 42,
844 stack: vec![],
845 locals: vec![],
846 module_bindings: vec![],
847 call_stack: vec![],
848 loop_stack: vec![],
849 timeframe_stack: vec![],
850 exception_handlers: vec![],
851 ip_blob_hash: Some([0xCD; 32]),
852 ip_local_offset: Some(7),
853 ip_function_id: Some(2),
854 };
855 let json = serde_json::to_string(&snapshot).unwrap();
856 let restored: VmSnapshot = serde_json::from_str(&json).unwrap();
857 assert_eq!(restored.ip_blob_hash, Some([0xCD; 32]));
858 assert_eq!(restored.ip_local_offset, Some(7));
859 assert_eq!(restored.ip_function_id, Some(2));
860 }
861
862 #[test]
863 fn test_snapshot_deserialization_without_relocation_fields() {
864 let json = r#"{
866 "ip": 50,
867 "stack": [],
868 "locals": [],
869 "module_bindings": [],
870 "call_stack": [],
871 "loop_stack": [],
872 "timeframe_stack": [],
873 "exception_handlers": []
874 }"#;
875 let snapshot: VmSnapshot = serde_json::from_str(json).unwrap();
876 assert_eq!(snapshot.ip, 50);
877 assert!(snapshot.ip_blob_hash.is_none());
878 assert!(snapshot.ip_local_offset.is_none());
879 assert!(snapshot.ip_function_id.is_none());
880 }
881
882 use crate::VMConfig;
892 use crate::executor::VirtualMachine;
893 use shape_runtime::snapshot::SnapshotStore;
894
895 #[test]
900 fn test_w17_vm_snapshot_empty_ok() {
901 let vm = VirtualMachine::new(VMConfig::default());
902 let tmp = tempfile::tempdir().expect("tempdir");
903 let store = SnapshotStore::new(tmp.path()).expect("snapshot store");
904
905 let snap = vm.snapshot(&store).expect("empty snapshot should succeed");
906 assert_eq!(snap.stack.len(), 0);
907 assert_eq!(snap.call_stack.len(), 0);
908 assert_eq!(snap.ip, 0);
909 }
910
911 #[test]
916 fn test_w17_snapshot_roundtrip_scalar_state() {
917 use crate::bytecode::BytecodeProgram;
918 use shape_value::NativeKind;
919
920 let mut vm = VirtualMachine::new(VMConfig::default());
921 vm.push_kinded(42i64 as u64, NativeKind::Int64)
923 .expect("push int");
924 vm.push_kinded(3.14f64.to_bits(), NativeKind::Float64)
925 .expect("push float");
926 vm.push_kinded(1, NativeKind::Bool).expect("push bool");
927
928 let tmp = tempfile::tempdir().expect("tempdir");
929 let store = SnapshotStore::new(tmp.path()).expect("snapshot store");
930
931 let snap = vm.snapshot(&store).expect("snapshot scalar state");
932 assert_eq!(snap.stack.len(), 3);
933
934 let restored = VirtualMachine::from_snapshot(
936 BytecodeProgram::default(),
937 &snap,
938 &store,
939 )
940 .expect("restore scalar state");
941 let restored_snap = restored
942 .snapshot(&store)
943 .expect("re-snapshot restored state");
944 assert_eq!(restored_snap.stack.len(), 3);
945 use shape_runtime::snapshot::SerializableVMValue as SV;
947 assert!(matches!(restored_snap.stack[0], SV::Int(42)));
948 assert!(matches!(restored_snap.stack[1], SV::Number(f) if (f - 3.14).abs() < 1e-9));
949 assert!(matches!(restored_snap.stack[2], SV::Bool(true)));
950 }
951
952 #[test]
955 fn test_w17_snapshot_result_option_roundtrip() {
956 use crate::bytecode::BytecodeProgram;
957 use shape_value::heap_value::{OptionData, ResultData};
958 use shape_value::{HeapKind, KindedSlot, NativeKind, ValueSlot};
959 use std::sync::Arc;
960
961 let mut vm = VirtualMachine::new(VMConfig::default());
962
963 let payload =
965 KindedSlot::new(ValueSlot::from_raw(42u64), NativeKind::Int64);
966 let ok = Arc::new(ResultData::ok(payload));
967 let ok_bits = Arc::into_raw(ok) as u64;
968 vm.push_kinded(ok_bits, NativeKind::Ptr(HeapKind::Result))
969 .expect("push ok");
970
971 let str_arc = Arc::new("hello".to_string());
973 let str_kinded = KindedSlot::from_string_arc(str_arc);
974 let some = Arc::new(OptionData::some(str_kinded));
975 let some_bits = Arc::into_raw(some) as u64;
976 vm.push_kinded(some_bits, NativeKind::Ptr(HeapKind::Option))
977 .expect("push some");
978
979 let none = Arc::new(OptionData::none());
981 let none_bits = Arc::into_raw(none) as u64;
982 vm.push_kinded(none_bits, NativeKind::Ptr(HeapKind::Option))
983 .expect("push none");
984
985 let tmp = tempfile::tempdir().expect("tempdir");
986 let store = SnapshotStore::new(tmp.path()).expect("snapshot store");
987 let snap = vm.snapshot(&store).expect("snapshot result+option");
988
989 use shape_runtime::snapshot::SerializableVMValue as SV;
990 match &snap.stack[0] {
991 SV::ResultData {
992 is_ok: true,
993 payload,
994 } => match payload.as_ref() {
995 SV::Int(42) => {}
996 other => panic!("expected SV::Int(42), got {other:?}"),
997 },
998 other => panic!("expected Ok(42), got {other:?}"),
999 }
1000 match &snap.stack[1] {
1001 SV::OptionData {
1002 is_some: true,
1003 payload: Some(p),
1004 } => match p.as_ref() {
1005 SV::String(s) if s == "hello" => {}
1006 other => panic!("expected SV::String(hello), got {other:?}"),
1007 },
1008 other => panic!("expected Some(hello), got {other:?}"),
1009 }
1010 match &snap.stack[2] {
1011 SV::OptionData {
1012 is_some: false,
1013 payload: None,
1014 } => {}
1015 other => panic!("expected None, got {other:?}"),
1016 }
1017
1018 let restored = VirtualMachine::from_snapshot(
1020 BytecodeProgram::default(),
1021 &snap,
1022 &store,
1023 )
1024 .expect("restore result+option");
1025 let restored_snap = restored.snapshot(&store).expect("re-snapshot");
1026 assert_eq!(restored_snap.stack.len(), 3);
1027 assert!(matches!(
1029 &restored_snap.stack[0],
1030 SV::ResultData {
1031 is_ok: true,
1032 payload,
1033 } if matches!(payload.as_ref(), SV::Int(42))
1034 ));
1035 }
1036
1037 #[test]
1042 fn test_w17_snapshot_resume_incompatible_surfaces_error() {
1043 use crate::bytecode::BytecodeProgram;
1044 use shape_runtime::snapshot::{SerializableVMValue as SV, VmSnapshot};
1045
1046 let tmp = tempfile::tempdir().expect("tempdir");
1052 let store = SnapshotStore::new(tmp.path()).expect("snapshot store");
1053 let snap = VmSnapshot {
1054 ip: 0,
1055 stack: vec![SV::IteratorOpaque],
1056 locals: vec![],
1057 module_bindings: vec![],
1058 call_stack: vec![],
1059 loop_stack: vec![],
1060 timeframe_stack: vec![],
1061 exception_handlers: vec![],
1062 ip_blob_hash: None,
1063 ip_local_offset: None,
1064 ip_function_id: None,
1065 };
1066
1067 let result =
1068 VirtualMachine::from_snapshot(BytecodeProgram::default(), &snap, &store);
1069 let err = match result {
1070 Ok(_) => panic!("expected Err for incompatible snapshot"),
1071 Err(e) => e,
1072 };
1073 let msg = format!("{err:?}");
1074 assert!(
1075 msg.contains("W17-snapshot-roundtrip surface"),
1076 "expected W17 surface error, got: {msg}"
1077 );
1078 }
1079
1080 #[test]
1082 fn test_w17_snapshot_hashset_roundtrip() {
1083 use shape_value::NativeKind;
1084 use shape_value::heap_value::HashSetData;
1085 use std::sync::Arc;
1086
1087 let mut vm = VirtualMachine::new(VMConfig::default());
1088 let data = Arc::new(HashSetData::from_keys(vec![
1089 Arc::new("alpha".to_string()),
1090 Arc::new("beta".to_string()),
1091 ]));
1092 let bits = Arc::into_raw(data) as u64;
1093 vm.push_kinded(bits, NativeKind::Ptr(shape_value::HeapKind::HashSet))
1094 .expect("push hashset");
1095
1096 let tmp = tempfile::tempdir().expect("tempdir");
1097 let store = SnapshotStore::new(tmp.path()).expect("snapshot store");
1098 let snap = vm.snapshot(&store).expect("snapshot hashset");
1099 use shape_runtime::snapshot::SerializableVMValue as SV;
1100 match &snap.stack[0] {
1101 SV::HashSet { keys } => {
1102 assert_eq!(keys.len(), 2);
1103 assert!(keys.iter().any(|k| k == "alpha"));
1104 assert!(keys.iter().any(|k| k == "beta"));
1105 }
1106 other => panic!("expected SV::HashSet, got {other:?}"),
1107 }
1108 }
1109
1110 #[test]
1118 fn test_w17_snapshot_non_closure_callstack_roundtrip() {
1119 use crate::bytecode::BytecodeProgram;
1120 use crate::executor::CallFrame;
1121 use shape_value::NativeKind;
1122
1123 let mut vm = VirtualMachine::new(VMConfig::default());
1124 vm.push_kinded(7i64 as u64, NativeKind::Int64)
1126 .expect("push int 7");
1127 vm.push_kinded(1, NativeKind::Bool).expect("push bool true");
1128 vm.call_stack.push(CallFrame {
1131 return_ip: 0,
1132 base_pointer: 0,
1133 locals_count: 2,
1134 function_id: None,
1135 upvalues: None,
1136 blob_hash: None,
1137 closure_heap_bits: None,
1138 closure_heap_kind: None,
1139 });
1140
1141 let tmp = tempfile::tempdir().expect("tempdir");
1142 let store = SnapshotStore::new(tmp.path()).expect("snapshot store");
1143 let snap = vm.snapshot(&store).expect("snapshot non-closure frame");
1144 assert_eq!(snap.call_stack.len(), 1);
1145 let sframe = &snap.call_stack[0];
1146 assert_eq!(sframe.locals_count, 2);
1147 assert_eq!(sframe.locals_base, 0);
1148 assert!(sframe.upvalues.is_none(), "non-closure frame has no upvalues");
1149
1150 let restored = VirtualMachine::from_snapshot(
1155 BytecodeProgram::default(),
1156 &snap,
1157 &store,
1158 )
1159 .expect("restore non-closure callstack");
1160 assert_eq!(restored.call_stack.len(), 1);
1161 let restored_frame = &restored.call_stack[0];
1162 assert_eq!(restored_frame.return_ip, 0);
1163 assert_eq!(restored_frame.base_pointer, 0);
1164 assert_eq!(restored_frame.locals_count, 2);
1165 assert!(restored_frame.upvalues.is_none());
1166 assert!(restored_frame.closure_heap_bits.is_none());
1167 }
1168
1169 #[test]
1173 fn test_w17_vm_state_snapshot_empty_accessor() {
1174 use shape_runtime::module_exports::VmStateAccessor;
1175
1176 let vm = VirtualMachine::new(VMConfig::default());
1177 let snap = vm.capture_vm_state();
1178 assert!(snap.current_frame().is_none());
1179 assert!(snap.caller_frame().is_none());
1180 assert_eq!(snap.all_frames().len(), 0);
1181 assert_eq!(snap.current_args().len(), 0);
1182 assert_eq!(snap.current_locals().len(), 0);
1183 assert_eq!(snap.module_bindings().len(), 0);
1184 assert_eq!(snap.instruction_count(), 0);
1185 }
1186
1187 #[test]
1191 fn test_w17_vm_state_snapshot_kind_threaded_locals() {
1192 use crate::executor::CallFrame;
1193 use shape_runtime::module_exports::VmStateAccessor;
1194 use shape_value::NativeKind;
1195
1196 let mut vm = VirtualMachine::new(VMConfig::default());
1197 vm.push_kinded(42i64 as u64, NativeKind::Int64)
1198 .expect("push int");
1199 vm.push_kinded(3.14f64.to_bits(), NativeKind::Float64)
1200 .expect("push float");
1201 vm.call_stack.push(CallFrame {
1202 return_ip: 0,
1203 base_pointer: 0,
1204 locals_count: 2,
1205 function_id: None,
1206 upvalues: None,
1207 blob_hash: None,
1208 closure_heap_bits: None,
1209 closure_heap_kind: None,
1210 });
1211
1212 let snap = vm.capture_vm_state();
1213 let frames = snap.all_frames();
1214 assert_eq!(frames.len(), 1);
1215 let f = &frames[0];
1216 assert_eq!(f.locals.len(), 2);
1217 assert!(matches!(f.locals[0].kind(), NativeKind::Int64));
1218 assert!(matches!(f.locals[1].kind(), NativeKind::Float64));
1219 assert_eq!(f.locals[0].slot().raw(), 42);
1220 assert_eq!(f.locals[1].slot().raw(), 3.14f64.to_bits());
1221 }
1222}