1use crate::{
4 frame::Frame, memory::MemoryManager, stack::Stack, CapabilitySet, Instruction, RuntimeError,
5};
6use glyph_intrinsics::IntrinsicRegistry;
7use glyph_types::Value;
8use std::collections::HashMap;
9use std::sync::Arc;
10use thiserror::Error;
11
12#[derive(Debug, Error)]
13pub enum VMError {
14 #[error("Runtime error: {0}")]
15 Runtime(#[from] RuntimeError),
16
17 #[error("Invalid bytecode")]
18 InvalidBytecode,
19
20 #[error("Execution limit exceeded")]
21 ExecutionLimitExceeded,
22}
23
24#[derive(Debug, Clone)]
26pub struct VMConfig {
27 pub max_stack_size: usize,
29 pub max_call_depth: usize,
31 pub max_execution_steps: usize,
33 pub capabilities: CapabilitySet,
35}
36
37impl Default for VMConfig {
38 fn default() -> Self {
39 Self {
40 max_stack_size: 10_000,
41 max_call_depth: 1_000,
42 max_execution_steps: 1_000_000,
43 capabilities: CapabilitySet::new(),
44 }
45 }
46}
47
48pub struct VM {
56 config: VMConfig,
58 stack: Stack,
60 frames: Vec<Frame>,
62 globals: HashMap<String, Value>,
64 ip: usize,
66 current_function: usize,
68 steps: usize,
70 memory: Arc<MemoryManager>,
72 bytecode: Vec<Vec<Instruction>>,
74 telemetry: Vec<(String, Value)>,
76
77 intrinsics: Arc<IntrinsicRegistry>,
79}
80
81impl VM {
82 pub fn new(config: VMConfig) -> Self {
84 let memory = Arc::new(MemoryManager::new(
85 config
86 .capabilities
87 .iter()
88 .find_map(|cap| match cap {
89 crate::Capability::MemoryLimited(limit) => Some(*limit),
90 crate::Capability::MemoryUnlimited => Some(usize::MAX),
91 _ => None,
92 })
93 .unwrap_or(10 * 1024 * 1024), ));
95
96 Self {
97 stack: Stack::new(config.max_stack_size),
98 frames: Vec::new(),
99 globals: HashMap::new(),
100 ip: 0,
101 current_function: 0,
102 steps: 0,
103 memory,
104 bytecode: Vec::new(),
105 telemetry: Vec::new(),
106 config,
107 intrinsics: Arc::new(IntrinsicRegistry::new()),
108 }
109 }
110
111 pub fn load_bytecode(&mut self, bytecode: Vec<Vec<Instruction>>) {
113 self.bytecode = bytecode;
114 }
115
116 pub fn execute(&mut self) -> Result<Value, VMError> {
118 self.current_function = 0;
120 self.ip = 0;
121 self.steps = 0;
122
123 if self.frames.is_empty() {
125 self.frames.push(Frame::new(0, 0));
126 }
127
128 loop {
130 if self.steps >= self.config.max_execution_steps {
132 return Err(VMError::ExecutionLimitExceeded);
133 }
134 self.steps += 1;
135
136 let instruction = self.fetch_instruction()?;
138
139 eprintln!("[VM] Executing instruction: {instruction:?}");
140
141 match self.execute_instruction(instruction)? {
143 ExecutionResult::Continue => {}
144 ExecutionResult::Halt(value) => {
145 eprintln!("[VM] Halting with value: {value:?}");
146 return Ok(value);
147 }
148 }
149 }
150 }
151
152 fn fetch_instruction(&mut self) -> Result<Instruction, VMError> {
154 let function = self
155 .bytecode
156 .get(self.current_function)
157 .ok_or(VMError::Runtime(RuntimeError::FunctionNotFound(
158 self.current_function,
159 )))?;
160
161 let instruction = function
162 .get(self.ip)
163 .ok_or(VMError::InvalidBytecode)?
164 .clone();
165
166 self.ip += 1;
167 Ok(instruction)
168 }
169
170 fn execute_instruction(
172 &mut self,
173 instruction: Instruction,
174 ) -> Result<ExecutionResult, VMError> {
175 use Instruction::*;
176
177 match instruction {
178 Push(value) => self.push(value)?,
180 Pop => {
181 self.pop()?;
182 }
183 Dup => self
184 .stack
185 .dup()
186 .map_err(|_| VMError::Runtime(RuntimeError::StackUnderflow))?,
187 Swap => self
188 .stack
189 .swap()
190 .map_err(|_| VMError::Runtime(RuntimeError::StackUnderflow))?,
191 Rot3 => self.rotate_three()?,
192
193 Add => self.binary_op(|a, b| match (a, b) {
195 (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x + y)),
196 (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x + y)),
197 (Value::Str(x), Value::Str(y)) => Ok(Value::Str(x + &y)),
198 _ => Err(RuntimeError::TypeError(
199 "Invalid types for addition".to_string(),
200 )),
201 })?,
202
203 Sub => self.binary_op(|a, b| match (a, b) {
204 (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x - y)),
205 (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x - y)),
206 _ => Err(RuntimeError::TypeError(
207 "Invalid types for subtraction".to_string(),
208 )),
209 })?,
210
211 Mul => self.binary_op(|a, b| match (a, b) {
212 (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x * y)),
213 (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x * y)),
214 _ => Err(RuntimeError::TypeError(
215 "Invalid types for multiplication".to_string(),
216 )),
217 })?,
218
219 Div => self.binary_op(|a, b| match (a, b) {
220 (Value::Int(x), Value::Int(y)) => {
221 if y == 0 {
222 return Err(RuntimeError::DivisionByZero);
223 }
224 Ok(Value::Int(x / y))
225 }
226 (Value::Float(x), Value::Float(y)) => {
227 if y == 0.0 {
228 return Err(RuntimeError::DivisionByZero);
229 }
230 Ok(Value::Float(x / y))
231 }
232 _ => Err(RuntimeError::TypeError(
233 "Invalid types for division".to_string(),
234 )),
235 })?,
236
237 Eq => self.binary_op(|a, b| Ok(Value::Bool(a == b)))?,
239 Ne => self.binary_op(|a, b| Ok(Value::Bool(a != b)))?,
240
241 Lt => self.binary_op(|a, b| match (a, b) {
242 (Value::Int(x), Value::Int(y)) => Ok(Value::Bool(x < y)),
243 (Value::Float(x), Value::Float(y)) => Ok(Value::Bool(x < y)),
244 _ => Err(RuntimeError::TypeError(
245 "Invalid types for less than".to_string(),
246 )),
247 })?,
248
249 Le => self.binary_op(|a, b| match (a, b) {
250 (Value::Int(x), Value::Int(y)) => Ok(Value::Bool(x <= y)),
251 (Value::Float(x), Value::Float(y)) => Ok(Value::Bool(x <= y)),
252 _ => Err(RuntimeError::TypeError(
253 "Invalid types for less than or equal".to_string(),
254 )),
255 })?,
256
257 Gt => self.binary_op(|a, b| match (a, b) {
258 (Value::Int(x), Value::Int(y)) => Ok(Value::Bool(x > y)),
259 (Value::Float(x), Value::Float(y)) => Ok(Value::Bool(x > y)),
260 _ => Err(RuntimeError::TypeError(
261 "Invalid types for greater than".to_string(),
262 )),
263 })?,
264
265 Ge => self.binary_op(|a, b| match (a, b) {
266 (Value::Int(x), Value::Int(y)) => Ok(Value::Bool(x >= y)),
267 (Value::Float(x), Value::Float(y)) => Ok(Value::Bool(x >= y)),
268 _ => Err(RuntimeError::TypeError(
269 "Invalid types for greater than or equal".to_string(),
270 )),
271 })?,
272
273 Jump(target) => {
275 self.ip = target;
276 }
277 JumpIf(target) => {
278 let condition = self.pop()?;
279 if condition.is_truthy() {
280 self.ip = target;
281 }
282 }
283 JumpIfNot(target) => {
284 let condition = self.pop()?;
285 if !condition.is_truthy() {
286 self.ip = target;
287 }
288 }
289
290 BindLocal(name) => {
292 let value = self.pop()?;
293 let frame = self
294 .frames
295 .last_mut()
296 .ok_or(VMError::Runtime(RuntimeError::StackUnderflow))?;
297 frame.set_local(name, value);
298 }
299
300 LoadLocal(name) => {
301 let value = self
302 .frames
303 .last()
304 .and_then(|f| f.get_local(&name))
305 .cloned()
306 .ok_or(VMError::Runtime(RuntimeError::UndefinedVariable(name)))?;
307 self.push(value)?;
308 }
309
310 LoadGlobal(name) => {
311 let value = self
312 .globals
313 .get(&name)
314 .cloned()
315 .ok_or(VMError::Runtime(RuntimeError::UndefinedVariable(name)))?;
316 self.push(value)?;
317 }
318
319 MakeList(count) => {
321 let items = self
322 .stack
323 .pop_n(count)
324 .map_err(|_| VMError::Runtime(RuntimeError::StackUnderflow))?;
325 self.push(Value::List(items))?;
326 }
327
328 MakeDict(count) => {
329 let pairs = self
330 .stack
331 .pop_n(count * 2)
332 .map_err(|_| VMError::Runtime(RuntimeError::StackUnderflow))?;
333 let mut dict = HashMap::new();
334 for chunk in pairs.chunks(2) {
335 if let (Value::Str(key), value) = (&chunk[0], &chunk[1]) {
336 dict.insert(key.clone(), value.clone());
337 } else {
338 return Err(VMError::Runtime(RuntimeError::TypeError(
339 "Dict keys must be strings".to_string(),
340 )));
341 }
342 }
343 self.push(Value::Dict(dict))?;
344 }
345
346 RequireCapability(cap) => {
348 self.check_capability(&cap)?;
349 }
350
351 CheckCapability(cap) => {
352 let has_cap = self.config.capabilities.has_by_name(&cap);
353 self.push(Value::Bool(has_cap))?;
354 }
355
356 TraceValue(label) => {
358 let value = self
359 .stack
360 .peek()
361 .map_err(|_| VMError::Runtime(RuntimeError::StackUnderflow))?
362 .clone();
363 self.telemetry.push((label, value));
364 }
365
366 RecordTelemetry(event) => {
367 self.telemetry.push((event, Value::None));
368 }
369
370 Return => {
372 if let Some(frame) = self.frames.pop() {
373 if let (Some(ret_ip), Some(ret_fn)) = (frame.return_ip, frame.return_function) {
374 self.ip = ret_ip;
375 self.current_function = ret_fn;
376 self.stack.truncate(frame.bp);
378 } else {
379 let result = self.pop().unwrap_or(Value::None);
381 eprintln!("[VM] Return from main with result: {result:?}");
382 return Ok(ExecutionResult::Halt(result));
383 }
384 } else {
385 let result = self.pop().unwrap_or(Value::None);
387 eprintln!("[VM] Return with no frames, result: {result:?}");
388 return Ok(ExecutionResult::Halt(result));
389 }
390 }
391
392 Halt => {
393 let result = self.pop().unwrap_or(Value::None);
394 return Ok(ExecutionResult::Halt(result));
395 }
396
397 Nop => {}
398
399 And => self.binary_op(|a, b| match (a, b) {
401 (Value::Bool(x), Value::Bool(y)) => Ok(Value::Bool(x && y)),
402 _ => Err(RuntimeError::TypeError("Invalid types for AND".to_string())),
403 })?,
404
405 Or => self.binary_op(|a, b| match (a, b) {
406 (Value::Bool(x), Value::Bool(y)) => Ok(Value::Bool(x || y)),
407 _ => Err(RuntimeError::TypeError("Invalid types for OR".to_string())),
408 })?,
409
410 Not => {
411 let value = self.pop()?;
412 match value {
413 Value::Bool(b) => self.push(Value::Bool(!b))?,
414 _ => {
415 return Err(VMError::Runtime(RuntimeError::TypeError(
416 "NOT requires boolean".to_string(),
417 )))
418 }
419 }
420 }
421
422 ListAppend => {
424 let item = self.pop()?;
425 let list = self.pop()?;
426 match list {
427 Value::List(mut items) => {
428 items.push(item);
429 self.push(Value::List(items))?;
430 }
431 _ => {
432 return Err(VMError::Runtime(RuntimeError::TypeError(
433 "ListAppend requires a list".to_string(),
434 )))
435 }
436 }
437 }
438
439 ListConcat => self.binary_op(|a, b| match (a, b) {
440 (Value::List(mut list1), Value::List(list2)) => {
441 list1.extend(list2);
442 Ok(Value::List(list1))
443 }
444 _ => Err(RuntimeError::TypeError(
445 "ListConcat requires two lists".to_string(),
446 )),
447 })?,
448
449 DictInsert => {
451 let value = self.pop()?;
452 let key = self.pop()?;
453 let dict = self.pop()?;
454 match (dict, key) {
455 (Value::Dict(mut map), Value::Str(key_str)) => {
456 map.insert(key_str, value);
457 self.push(Value::Dict(map))?;
458 }
459 _ => {
460 return Err(VMError::Runtime(RuntimeError::TypeError(
461 "DictInsert requires dict and string key".to_string(),
462 )))
463 }
464 }
465 }
466
467 DictMerge => self.binary_op(|a, b| match (a, b) {
468 (Value::Dict(mut dict1), Value::Dict(dict2)) => {
469 dict1.extend(dict2);
470 Ok(Value::Dict(dict1))
471 }
472 _ => Err(RuntimeError::TypeError(
473 "DictMerge requires two dicts".to_string(),
474 )),
475 })?,
476
477 Call(function_idx) => {
479 if self.frames.len() >= self.config.max_call_depth {
481 return Err(VMError::Runtime(RuntimeError::StackOverflow));
482 }
483
484 let frame = Frame::with_return(
486 function_idx,
487 self.stack.depth(),
488 self.ip,
489 self.current_function,
490 );
491 self.frames.push(frame);
492
493 self.current_function = function_idx;
495 self.ip = 0;
496 }
497
498 GetIndex => {
500 let index = self.pop()?;
501 let collection = self.pop()?;
502 match (&collection, &index) {
503 (Value::List(items), Value::Int(idx)) => {
504 if *idx < 0 || *idx as usize >= items.len() {
505 return Err(VMError::Runtime(RuntimeError::IndexOutOfBounds {
506 index: *idx,
507 length: items.len(),
508 }));
509 }
510 self.push(items[*idx as usize].clone())?;
511 }
512 (Value::Str(s), Value::Int(idx)) => {
513 if *idx < 0 || *idx as usize >= s.len() {
514 return Err(VMError::Runtime(RuntimeError::IndexOutOfBounds {
515 index: *idx,
516 length: s.len(),
517 }));
518 }
519 let ch = s.chars().nth(*idx as usize).unwrap();
521 self.push(Value::Str(ch.to_string()))?;
522 }
523 (Value::Dict(map), Value::Str(key)) => match map.get(key) {
524 Some(value) => self.push(value.clone())?,
525 None => {
526 return Err(VMError::Runtime(RuntimeError::KeyNotFound(key.clone())))
527 }
528 },
529 _ => {
530 return Err(VMError::Runtime(RuntimeError::TypeError(format!(
531 "Invalid types for indexing: {collection:?}[{index:?}]"
532 ))))
533 }
534 }
535 }
536
537 CallNative(name) => match name.as_str() {
539 "len" => {
540 let value = self.pop()?;
541 let length = match value {
542 Value::List(ref items) => items.len() as i64,
543 Value::Str(ref s) => s.len() as i64,
544 Value::Dict(ref map) => map.len() as i64,
545 _ => {
546 return Err(VMError::Runtime(RuntimeError::TypeError(format!(
547 "len() not supported for {value:?}"
548 ))))
549 }
550 };
551 self.push(Value::Int(length))?;
552 }
553 _ => {
554 return Err(VMError::Runtime(RuntimeError::NativeFunctionError(
555 format!("Unknown native function: {name}"),
556 )))
557 }
558 },
559
560 CallIntrinsic { name, capability } => {
562 if let Some(cap) = capability {
564 self.check_capability(&cap)?;
565 }
566
567 eprintln!("[VM] Looking for intrinsic: {name}");
569 eprintln!("[VM] Available intrinsics: {:?}", self.intrinsics.names());
570
571 let intrinsic = self.intrinsics.get(&name).ok_or(
573 VMError::Runtime(RuntimeError::NativeFunctionError(format!(
574 "Unknown intrinsic: {name}"
575 )))
576 )?;
577
578 let args = match name.as_str() {
581 "voice.speak" => {
582 vec![self.pop()?]
584 }
585 "display.chart" | "display.image" => {
586 let title = self.pop()?;
588 let data = self.pop()?;
589 vec![data, title]
590 }
591 "net.fetch" => {
592 vec![self.pop()?]
594 }
595 "wait.confirm" => {
596 vec![self.pop()?]
598 }
599 _ => {
600 return Err(VMError::Runtime(RuntimeError::NativeFunctionError(
602 format!("No argument handling for intrinsic: {name}"),
603 )));
604 }
605 };
606
607 eprintln!("[VM] Executing intrinsic {name} with args: {args:?}");
608
609 let result = tokio::task::block_in_place(|| {
611 tokio::runtime::Handle::current()
612 .block_on(async { intrinsic.execute(args).await })
613 })
614 .map_err(|e| {
615 VMError::Runtime(RuntimeError::NativeFunctionError(format!(
616 "Intrinsic error: {e}"
617 )))
618 })?;
619
620 eprintln!("[VM] Intrinsic result: {result:?}");
621
622 self.push(result)?;
624 }
625
626 _ => {
628 return Err(VMError::Runtime(RuntimeError::NativeFunctionError(
629 format!("Instruction {instruction:?} not yet implemented"),
630 )))
631 }
632 }
633
634 Ok(ExecutionResult::Continue)
635 }
636
637 pub fn push(&mut self, value: Value) -> Result<(), VMError> {
639 self.stack
640 .push(value)
641 .map_err(|_| VMError::Runtime(RuntimeError::StackOverflow))
642 }
643
644 pub fn pop(&mut self) -> Result<Value, VMError> {
646 self.stack
647 .pop()
648 .map_err(|_| VMError::Runtime(RuntimeError::StackUnderflow))
649 }
650
651 pub fn check_capability(&self, capability: &str) -> Result<(), VMError> {
653 if self.config.capabilities.has_by_name(capability) {
654 Ok(())
655 } else {
656 Err(VMError::Runtime(RuntimeError::CapabilityError(format!(
657 "Capability '{capability}' not granted"
658 ))))
659 }
660 }
661
662 fn binary_op<F>(&mut self, op: F) -> Result<(), VMError>
664 where
665 F: FnOnce(Value, Value) -> Result<Value, RuntimeError>,
666 {
667 let b = self.pop()?;
668 let a = self.pop()?;
669 let result = op(a, b).map_err(VMError::Runtime)?;
670 self.push(result)
671 }
672
673 fn rotate_three(&mut self) -> Result<(), VMError> {
675 let c = self.pop()?;
676 let b = self.pop()?;
677 let a = self.pop()?;
678 self.push(b)?;
679 self.push(c)?;
680 self.push(a)?;
681 Ok(())
682 }
683
684 pub fn telemetry(&self) -> &[(String, Value)] {
686 &self.telemetry
687 }
688
689 pub fn clear_telemetry(&mut self) {
691 self.telemetry.clear();
692 }
693
694 pub fn memory_usage(&self) -> usize {
696 self.memory.current_usage()
697 }
698
699 pub fn memory_usage_percentage(&self) -> f64 {
701 self.memory.usage_percentage()
702 }
703}
704
705enum ExecutionResult {
707 Continue,
709 Halt(Value),
711}
712
713#[cfg(test)]
714mod tests {
715 use super::*;
716 use crate::Capability;
717
718 fn create_test_vm() -> VM {
719 let config = VMConfig {
720 max_stack_size: 1000,
721 max_call_depth: 100,
722 max_execution_steps: 10000,
723 capabilities: CapabilitySet::new(),
724 };
725 VM::new(config)
726 }
727
728 #[test]
729 fn test_arithmetic_operations() {
730 let mut vm = create_test_vm();
731
732 vm.load_bytecode(vec![vec![
734 Instruction::Push(Value::Int(5)),
735 Instruction::Push(Value::Int(3)),
736 Instruction::Add,
737 Instruction::Halt,
738 ]]);
739
740 let result = vm.execute().unwrap();
741 assert_eq!(result, Value::Int(8));
742 }
743
744 #[test]
745 fn test_immutable_bindings() {
746 let mut vm = create_test_vm();
747
748 vm.frames.push(Frame::new(0, 0));
750
751 vm.load_bytecode(vec![vec![
752 Instruction::Push(Value::Int(42)),
753 Instruction::BindLocal("x".to_string()),
754 Instruction::LoadLocal("x".to_string()),
755 Instruction::Push(Value::Int(10)),
756 Instruction::Add,
757 Instruction::Halt,
758 ]]);
759
760 let result = vm.execute().unwrap();
761 assert_eq!(result, Value::Int(52));
762 }
763
764 #[test]
765 fn test_list_operations() {
766 let mut vm = create_test_vm();
767
768 vm.load_bytecode(vec![vec![
769 Instruction::Push(Value::Int(1)),
770 Instruction::Push(Value::Int(2)),
771 Instruction::Push(Value::Int(3)),
772 Instruction::MakeList(3),
773 Instruction::Halt,
774 ]]);
775
776 let result = vm.execute().unwrap();
777 match result {
778 Value::List(items) => {
779 assert_eq!(items.len(), 3);
780 assert_eq!(items[0], Value::Int(1));
781 assert_eq!(items[1], Value::Int(2));
782 assert_eq!(items[2], Value::Int(3));
783 }
784 _ => panic!("Expected list"),
785 }
786 }
787
788 #[test]
789 fn test_dict_operations() {
790 let mut vm = create_test_vm();
791
792 vm.load_bytecode(vec![vec![
793 Instruction::Push(Value::Str("key1".to_string())),
794 Instruction::Push(Value::Int(100)),
795 Instruction::Push(Value::Str("key2".to_string())),
796 Instruction::Push(Value::Str("value2".to_string())),
797 Instruction::MakeDict(2),
798 Instruction::Halt,
799 ]]);
800
801 let result = vm.execute().unwrap();
802 match result {
803 Value::Dict(map) => {
804 assert_eq!(map.len(), 2);
805 assert_eq!(map.get("key1"), Some(&Value::Int(100)));
806 assert_eq!(map.get("key2"), Some(&Value::Str("value2".to_string())));
807 }
808 _ => panic!("Expected dict"),
809 }
810 }
811
812 #[test]
813 fn test_capability_check() {
814 let mut config = VMConfig::default();
815 config.capabilities.grant(Capability::AudioSpeak);
816 let mut vm = VM::new(config);
817
818 vm.load_bytecode(vec![vec![
819 Instruction::CheckCapability("audio.speak".to_string()),
820 Instruction::Halt,
821 ]]);
822
823 let result = vm.execute().unwrap();
824 assert_eq!(result, Value::Bool(true));
825 }
826
827 #[test]
828 fn test_capability_requirement_failure() {
829 let mut vm = create_test_vm();
830
831 vm.load_bytecode(vec![vec![
832 Instruction::RequireCapability("network.fetch".to_string()),
833 Instruction::Halt,
834 ]]);
835
836 let result = vm.execute();
837 assert!(matches!(
838 result,
839 Err(VMError::Runtime(RuntimeError::CapabilityError(_)))
840 ));
841 }
842
843 #[test]
844 fn test_control_flow() {
845 let mut vm = create_test_vm();
846
847 vm.load_bytecode(vec![vec![
848 Instruction::Push(Value::Bool(true)),
849 Instruction::JumpIf(3),
850 Instruction::Push(Value::Int(1)), Instruction::Push(Value::Int(2)), Instruction::Halt,
853 ]]);
854
855 let result = vm.execute().unwrap();
856 assert_eq!(result, Value::Int(2));
857 }
858
859 #[test]
860 fn test_execution_limit() {
861 let config = VMConfig {
862 max_execution_steps: 5,
863 ..Default::default()
864 };
865 let mut vm = VM::new(config);
866
867 vm.load_bytecode(vec![vec![
869 Instruction::Push(Value::Int(1)),
870 Instruction::Jump(0),
871 ]]);
872
873 let result = vm.execute();
874 assert!(matches!(result, Err(VMError::ExecutionLimitExceeded)));
875 }
876
877 #[test]
878 fn test_telemetry() {
879 let mut vm = create_test_vm();
880
881 vm.load_bytecode(vec![vec![
882 Instruction::Push(Value::Int(42)),
883 Instruction::TraceValue("debug_value".to_string()),
884 Instruction::RecordTelemetry("test_event".to_string()),
885 Instruction::Halt,
886 ]]);
887
888 vm.execute().unwrap();
889
890 let telemetry = vm.telemetry();
891 assert_eq!(telemetry.len(), 2);
892 assert_eq!(telemetry[0].0, "debug_value");
893 assert_eq!(telemetry[0].1, Value::Int(42));
894 assert_eq!(telemetry[1].0, "test_event");
895 }
896}