arrow_vm/
execute.rs

1use crate::error::ExecErrorType;
2use arrow_parser::symbol::Symbol;
3use arrowc::bytecode::BytecodeOp;
4use arrowc::compile::Constant;
5use arrowc::compile::IdentifierId;
6use arrowc::compile::Program;
7use itertools::Itertools;
8use ordered_float::OrderedFloat;
9use rustc_hash::FxHashMap;
10use std::io::Write;
11use std::rc::Rc;
12use std::sync::mpsc::channel;
13use std::sync::mpsc::Receiver;
14use std::sync::mpsc::Sender;
15use std::sync::Arc;
16
17struct OperandStack {
18  stack: Vec<Value>,
19}
20
21impl OperandStack {
22  pub fn new() -> OperandStack {
23    OperandStack { stack: Vec::new() }
24  }
25
26  pub fn len(&self) -> usize {
27    self.stack.len()
28  }
29
30  pub fn peek(&self) -> &Value {
31    self.stack.last().unwrap()
32  }
33
34  pub fn pop(&mut self) -> Value {
35    self.stack.pop().unwrap()
36  }
37
38  // This will return the top 2 elements in order from least recent to most recent. This is the reverse of repeatedly calling `.pop()`, and makes life easier by not having to declare vars in reverse order.
39  pub fn pop_2(&mut self) -> (Value, Value) {
40    let right = self.pop();
41    let left = self.pop();
42    (left, right)
43  }
44
45  // This will return the top `n` elements in order from least recent to most recent. This is the reverse of repeatedly calling `.pop()`, and makes life easier by not having to temporarily allocate a Vec and then reverse it in place, or perform logic in reverse order.
46  pub fn pop_n(&mut self, n: usize) -> Vec<Value> {
47    let len = self.stack.len();
48    assert!(len >= n);
49    self.stack.split_off(len - n)
50  }
51
52  pub fn push(&mut self, v: Value) {
53    self.stack.push(v);
54  }
55}
56
57// This is passed/copied everywhere, so keep this as small as possible. As a rule of thumb, do not exceed 2 * WORD_SIZE.
58#[derive(Clone)]
59pub enum Value {
60  Boolean(bool),
61  Closure { closure_id: usize }, // We store closures in a heap as the data is too large to performantly pass around frequently.
62  Float(OrderedFloat<f64>),
63  Int(i64),
64  Native(Rc<dyn NativeValue>), // Unfortunately we cannot avoid the cost of Rc, even if the type doesn't want it or itself uses Rc. A `dyn` cannot be sized so cannot subtype Clone.
65  NativeCallable(&'static NativeCallable),
66  None,
67  Object { object_id: usize, impl_id: i16 }, // If `impl_id` is negative, it's a plain object.
68  String(Arc<Vec<u8>>), // We don't need to store strings on the heap as they are immutable. It's Arc and not Rc because Constant::String is Arc.
69  Vec { vec_id: usize }, // We store vectors on the heap for cheap shallow pointer-based diffing. This also allows mutability, as we shallow clone but Rc<RefCell<>> won't allow mutating if cloned.
70}
71
72struct Frame {
73  box_ids: FxHashMap<Symbol, usize>, // Map from a declared variable that is boxed to the index of its latest allocated box on the box heap.
74  func_id: usize,
75  op_stack: OperandStack,
76  pc: usize,
77  vars: Vec<Value>,
78}
79
80impl Frame {
81  fn set_up(program: &Program, func_id: usize) -> Frame {
82    let func = &program.funcs[func_id];
83    Frame {
84      box_ids: FxHashMap::default(),
85      func_id,
86      op_stack: OperandStack::new(),
87      pc: 0,
88      vars: vec![Value::None; func.var_ids.len()],
89    }
90  }
91}
92
93struct Closure {
94  bound_args: Vec<Value>,
95  bound_offset: usize,
96  captured_box_ids: FxHashMap<Symbol, usize>,
97  func_id: usize,
98}
99
100pub type NativeCallable = fn(vm: &mut VM, args: Vec<Value>) -> NativeResult<Value>;
101
102struct Object {
103  fields: FxHashMap<Arc<Vec<u8>>, Value>,
104}
105
106// 99% of the time, the struct that implements this will need to use RefCell to allow for mutability with &self. However, we define it like this to allow other types where RefCell<> is unnecessary e.g. static, immutable, unsafe, copy-on-write, cell, etc., instead of always forcing it by simply wrapping all NativeValue values with Heap<Rc<RefCell<>>>. Remember that Value is clone because we spray/copy them everywhere (op stack, fields, vars, args, etc.), so we must use RefCell if we want to mutate. Additionally, some native values may want to be async internally and use Arc and Mutex/RwLock/etc. instead.
107pub trait NativeValue {
108  fn call_method(
109    &self,
110    vm: &mut VM,
111    // `method_id` will always be a std identifier.
112    method_id: IdentifierId,
113    args: Vec<Value>,
114  ) -> NativeResult<Value>;
115
116  #[allow(unused_variables)]
117  fn operator_add(&self, vm: &mut VM, other: Value) -> NativeResult<Value> {
118    unimplemented!();
119  }
120
121  #[allow(unused_variables)]
122  fn iterate(&self, vm: &mut VM) -> NativeResult<Value> {
123    unimplemented!();
124  }
125
126  #[allow(unused_variables)]
127  fn iterator_next(&self, vm: &mut VM) -> NativeResult<Option<Value>> {
128    unimplemented!();
129  }
130}
131
132struct Heap<T> {
133  // We can't store in Vec, as we don't want to have to search and replace all IDs across everywhere when GCing.
134  entries: FxHashMap<usize, T>,
135  next_id: usize,
136}
137
138impl<T> Heap<T> {
139  pub fn new() -> Heap<T> {
140    Heap {
141      entries: FxHashMap::default(),
142      next_id: 0,
143    }
144  }
145
146  pub fn allocate(&mut self, v: T) -> usize {
147    let id = self.next_id;
148    self.next_id += 1;
149    self.entries.insert(id, v);
150    id
151  }
152
153  pub fn set(&mut self, id: usize, val: T) {
154    self.entries.insert(id, val).unwrap();
155  }
156
157  pub fn get(&self, id: usize) -> &T {
158    self.entries.get(&id).unwrap()
159  }
160
161  pub fn get_mut(&mut self, id: usize) -> &mut T {
162    self.entries.get_mut(&id).unwrap()
163  }
164}
165
166enum CoroutineWaitedOnBy {
167  Coroutine(CoroutineId),
168  CtlRequest(CtlRequestId),
169}
170
171struct Coroutine {
172  program: Arc<Program>,
173  stack_frames: Vec<Frame>,
174  waited_on_by: CoroutineWaitedOnBy,
175}
176
177pub type CoroutineId = usize;
178
179// This is separate from Value as native functions can be async which means they'll provide a Value eventually, but Value isn't Send or Sync. We don't want to make Value Send or Sync as that taints everything and causes pointless inefficiency, which means we need a separate type for values sent across threads that owns the data and must be converted to Value on arrival. The same applies for CtlRequest and CtlResponse.
180pub enum ThreadSafeValue {
181  Boolean(bool),
182  Float(OrderedFloat<f64>),
183  Int(i64),
184  Native(Box<dyn NativeValue + Send + Sync>),
185  None,
186  String(Arc<Vec<u8>>),
187  Vec(Vec<ThreadSafeValue>),
188}
189
190impl ThreadSafeValue {
191  pub fn into_value(self, vm: &mut VM) -> Value {
192    match self {
193      ThreadSafeValue::Boolean(v) => Value::Boolean(v),
194      ThreadSafeValue::Float(v) => Value::Float(v),
195      ThreadSafeValue::Int(v) => Value::Int(v),
196      ThreadSafeValue::Native(v) => Value::Native({
197        let v: Box<dyn NativeValue> = v;
198        let v: Rc<dyn NativeValue> = v.into();
199        v
200      }),
201      ThreadSafeValue::None => Value::None,
202      ThreadSafeValue::String(v) => Value::String(v),
203      ThreadSafeValue::Vec(v) => {
204        let vec = v.into_iter().map(|v| v.into_value(vm)).collect_vec();
205        Value::Vec {
206          vec_id: vm.vecs.allocate(vec),
207        }
208      }
209    }
210  }
211
212  pub fn from_value(vm: &VM, v: Value) -> CtlResponse {
213    CtlResponse::Ok(match v {
214      Value::Boolean(v) => ThreadSafeValue::Boolean(v),
215      Value::Float(v) => ThreadSafeValue::Float(v),
216      Value::Int(v) => ThreadSafeValue::Int(v),
217      Value::None => ThreadSafeValue::None,
218      Value::String(v) => ThreadSafeValue::String(v),
219      Value::Vec { vec_id } => {
220        let mut vec = vec![];
221        for v in vm.vecs.get(vec_id) {
222          match ThreadSafeValue::from_value(vm, v.clone()) {
223            CtlResponse::Ok(v) => vec.push(v),
224            res => return res,
225          };
226        }
227        ThreadSafeValue::Vec(vec)
228      }
229      _ => return CtlResponse::ValueUnserialisable,
230    })
231  }
232}
233
234enum Event {
235  CompletedPromise {
236    coroutine_id: CoroutineId,
237    value: ThreadSafeValue,
238  },
239  CtlRequest {
240    id: CtlRequestId,
241    request: CtlRequest,
242  },
243}
244
245pub type CtlRequestId = usize;
246
247pub enum CtlRequest {
248  Execute {
249    program: Arc<Program>,
250  },
251  Call {
252    module_var_name: String,
253    args: Vec<ThreadSafeValue>,
254  },
255}
256
257pub enum CtlResponse {
258  ExecError(ExecErrorType),
259  Ok(ThreadSafeValue),
260  ValueUnserialisable,
261}
262
263pub struct Promise {
264  coroutine_id: CoroutineId,
265  sender: Sender<Event>,
266}
267
268impl Promise {
269  pub fn complete(self, value: ThreadSafeValue) {
270    self
271      .sender
272      .send(Event::CompletedPromise {
273        coroutine_id: self.coroutine_id,
274        value,
275      })
276      .unwrap();
277  }
278}
279
280pub enum NativeResult<T> {
281  ExecError(ExecErrorType),
282  Value(T),
283  Promise,
284}
285
286pub struct VM {
287  boxes: Heap<Value>,
288  closures: Heap<Closure>,
289  suspended_coroutines: FxHashMap<CoroutineId, Coroutine>,
290  ctl_sender: Sender<(usize, CtlResponse)>,
291  current_coroutine_id: Option<CoroutineId>,
292  event_receiver: Receiver<Event>,
293  event_sender: Sender<Event>,
294  host_vars: FxHashMap<IdentifierId, Value>,
295  next_coroutine_id: CoroutineId,
296  objects: Heap<Object>,
297  #[cfg(feature = "tokio")]
298  tokio: tokio::runtime::Handle,
299  vecs: Heap<Vec<Value>>,
300}
301
302pub struct VMCtl {
303  receiver: Receiver<(usize, CtlResponse)>,
304  sender: Sender<Event>,
305}
306
307impl VM {
308  #[cfg(feature = "tokio")]
309  pub fn tokio(&self) -> tokio::runtime::Handle {
310    self.tokio.clone()
311  }
312
313  // WARNING: This function must only be called at most once from a native function when it's called. If it is called, the native function must return NativeResult::Promise. Anything else will lead to corrupted internal state.
314  pub fn promise(&mut self) -> Promise {
315    Promise {
316      coroutine_id: self.current_coroutine_id.unwrap(),
317      sender: self.event_sender.clone(),
318    }
319  }
320
321  fn new_vm_only(
322    native_callables: &[(IdentifierId, &'static NativeCallable)],
323    ctl_sender: Sender<(usize, CtlResponse)>,
324    event_receiver: Receiver<Event>,
325    event_sender: Sender<Event>,
326    #[cfg(feature = "tokio")] tokio: tokio::runtime::Handle,
327  ) -> VM {
328    let mut host_vars = FxHashMap::<IdentifierId, Value>::default();
329
330    for (name, func) in native_callables {
331      host_vars.insert(*name, Value::NativeCallable(func));
332    }
333
334    VM {
335      boxes: Heap::new(),
336      closures: Heap::new(),
337      suspended_coroutines: FxHashMap::default(),
338      ctl_sender,
339      current_coroutine_id: None,
340      event_receiver,
341      event_sender,
342      host_vars,
343      next_coroutine_id: 0,
344      objects: Heap::new(),
345      #[cfg(feature = "tokio")]
346      tokio,
347      vecs: Heap::new(),
348    }
349  }
350
351  // VM is not send, for good reason. If we just return (VM, VMCtl), we allow the user to use them however they want, but it makes it really tricky to start the VM loop in a different thread as it can't be sent/moved to a different thread. This function helps by creating and starting the thread automatically for the user.
352  pub fn new_with_background_thread(
353    native_callables: &'static [(IdentifierId, &'static NativeCallable)],
354    #[cfg(feature = "tokio")] tokio: tokio::runtime::Handle,
355  ) -> VMCtl {
356    let (event_sender, event_receiver) = channel();
357    let (ctl_sender, ctl_receiver) = channel();
358
359    let vm_ctl = VMCtl {
360      sender: event_sender.clone(),
361      receiver: ctl_receiver,
362    };
363    std::thread::spawn(move || {
364      let mut vm = VM::new_vm_only(
365        native_callables,
366        ctl_sender,
367        event_receiver,
368        event_sender,
369        #[cfg(feature = "tokio")]
370        tokio,
371      );
372      vm.start_execution_loop();
373    });
374    vm_ctl
375  }
376
377  // NOTE: It's recommended to use `new_with_background_thread` instead; see that method for details.
378  pub fn new(
379    native_callables: &[(IdentifierId, &'static NativeCallable)],
380    #[cfg(feature = "tokio")] tokio: tokio::runtime::Handle,
381  ) -> (VM, VMCtl) {
382    let (event_sender, event_receiver) = channel();
383    let (ctl_sender, ctl_receiver) = channel();
384
385    let vm = VM::new_vm_only(
386      native_callables,
387      ctl_sender,
388      event_receiver,
389      event_sender.clone(),
390      #[cfg(feature = "tokio")]
391      tokio,
392    );
393    let vm_ctl = VMCtl {
394      sender: event_sender.clone(),
395      receiver: ctl_receiver,
396    };
397    (vm, vm_ctl)
398  }
399
400  // WARNING: Currently this function never exits as it holds a reference to the event queue sender as it's necessary for `.promise()`.
401  pub fn start_execution_loop(&mut self) {
402    loop {
403      match self.event_receiver.recv().unwrap() {
404        Event::CompletedPromise {
405          coroutine_id,
406          value,
407        } => {
408          let mut coroutine = self.suspended_coroutines.remove(&coroutine_id).unwrap();
409          coroutine
410            .stack_frames
411            .last_mut()
412            .unwrap()
413            .op_stack
414            .push(value.into_value(self));
415          self.execute_coroutine(coroutine_id, coroutine).unwrap();
416        }
417        Event::CtlRequest { id, request } => {
418          match request {
419            CtlRequest::Execute { program } => {
420              let frame = Frame::set_up(&program, program.funcs.len() - 1);
421              let coroutine = Coroutine {
422                program,
423                stack_frames: vec![frame],
424                waited_on_by: CoroutineWaitedOnBy::CtlRequest(id),
425              };
426              let coroutine_id: CoroutineId = self.next_coroutine_id;
427              self.next_coroutine_id += 1;
428              self.execute_coroutine(coroutine_id, coroutine).unwrap();
429            }
430            CtlRequest::Call {
431              module_var_name,
432              args,
433            } => todo!(),
434          };
435        }
436      };
437    }
438  }
439
440  // This takes ownership of Coroutine (instead of getting mut ref in self.coroutines) to allow mutating it and `self` at the same time.
441  // Return scenarios:
442  // - All code has executed and stack is empty.
443  // - Native function was called and it returned a promise, so the coroutine must be suspended.
444  fn execute_coroutine(
445    &mut self,
446    coroutine_id: CoroutineId,
447    mut coroutine: Coroutine,
448  ) -> Result<(), ExecErrorType> {
449    self.current_coroutine_id = Some(coroutine_id);
450
451    let mut module_vars = FxHashMap::<IdentifierId, Value>::default();
452    let program = &coroutine.program;
453
454    // We pop so we can take ownership and therefore mutate it and `coroutine` at the same time.
455    let mut f = coroutine.stack_frames.pop().unwrap();
456    loop {
457      let func = &program.funcs[f.func_id];
458      let code = &func.code;
459      // If this is Some after the loop breaks, we are entering a new function. If this is None after the loop breaks, we are returning from our current function; there should be exactly one value in the operand stack and that's our return value.
460      let mut next_frame = None;
461      loop {
462        // WARNING: Remember to `break` after calling this!
463        let mut call_function =
464          |next_func_id: usize, args: Vec<Value>, captured_box_ids: FxHashMap<Symbol, usize>| {
465            let mut new_frame = Frame::set_up(program, next_func_id);
466            // We must assert expected argc is actual argc, as otherwise the function will not pop the correct amount of args. Note that we can't simply directly place in the new frame's local vars as some may be boxed and require more complex logic, so we just push everything onto its stack and let it deal with them. We will push in reverse order so they pop off in the right order as a convenience.
467            assert_eq!(program.funcs[next_func_id].argc, args.len());
468            for arg in args.into_iter().rev() {
469              new_frame.op_stack.push(arg);
470            }
471            new_frame.box_ids = captured_box_ids;
472            next_frame = Some(new_frame);
473          };
474        let inst = code[f.pc];
475        // Increment now instead of after this giant block in case we break (i.e. return inside the VM).
476        f.pc += 1;
477        match inst {
478          BytecodeOp::Add => {
479            let (left, right) = f.op_stack.pop_2();
480            let op = match (left, right) {
481              (Value::Float(l), Value::Float(r)) => Value::Float(l + r),
482              (Value::Int(l), Value::Int(r)) => Value::Int(l + r),
483              (Value::String(l), Value::String(r)) => {
484                let mut res = l.to_vec();
485                res.extend_from_slice(r.as_slice());
486                Value::String(Arc::new(res))
487              }
488              _ => panic!("invalid operands"),
489            };
490            f.op_stack.push(op);
491          }
492          BytecodeOp::AllocateBox { symbol } => {
493            let box_id = self.boxes.allocate(Value::None);
494            f.box_ids.insert(symbol, box_id);
495          }
496          BytecodeOp::BindImplMethod {
497            func_id,
498            bound_argc,
499          } => {
500            let bound_args = f.op_stack.pop_n(bound_argc.into());
501            let closure_id = self.closures.allocate(Closure {
502              bound_args,
503              bound_offset: 1,
504              captured_box_ids: FxHashMap::default(),
505              func_id,
506            });
507            f.op_stack.push(Value::Closure { closure_id });
508          }
509          BytecodeOp::Call { argc } => {
510            let mut args = f.op_stack.pop_n(argc.into());
511            let callable = f.op_stack.pop();
512            match callable {
513              Value::Closure { closure_id } => {
514                let Closure {
515                  bound_args,
516                  bound_offset,
517                  captured_box_ids,
518                  func_id,
519                } = self.closures.get(closure_id);
520                args.splice(bound_offset..bound_offset, bound_args.iter().cloned());
521                call_function(*func_id, args, captured_box_ids.clone());
522                break;
523              }
524              Value::NativeCallable(func) => {
525                match func(self, args) {
526                  NativeResult::ExecError(_) => todo!(),
527                  NativeResult::Value(retval) => f.op_stack.push(retval),
528                  NativeResult::Promise => {
529                    coroutine.stack_frames.push(f);
530                    self.suspended_coroutines.insert(coroutine_id, coroutine);
531                    // We don't need to bump the program counter as it has already been incremented.
532                    return Ok(());
533                  }
534                };
535              }
536              _ => panic!("not a callable"),
537            };
538          }
539          BytecodeOp::CallMethod {
540            argc,
541            method_identifier_id,
542          } => {
543            let mut args = f.op_stack.pop_n(argc.into());
544            let obj = f.op_stack.pop();
545            match obj {
546              Value::Object { impl_id, .. } => {
547                args.insert(0, obj.clone());
548                let impl_ = &program.impls[usize::try_from(impl_id).unwrap()];
549                let func_id = impl_.methods.get(&method_identifier_id).unwrap();
550                call_function(*func_id, args, FxHashMap::default());
551                break;
552              }
553              Value::Native(nv) => {
554                match nv.call_method(self, method_identifier_id.into(), args) {
555                  NativeResult::ExecError(_) => todo!(),
556                  NativeResult::Value(retval) => f.op_stack.push(retval),
557                  NativeResult::Promise => {
558                    coroutine.stack_frames.push(f);
559                    self.suspended_coroutines.insert(coroutine_id, coroutine);
560                    // We don't need to bump the program counter as it has already been incremented.
561                    return Ok(());
562                  }
563                };
564              }
565              // TODO intrinsics.
566              _ => panic!("cannot call method"),
567            };
568          }
569          BytecodeOp::CastObjectToImpl { impl_id } => {
570            let obj = f.op_stack.pop();
571            let Value::Object { object_id, .. } = obj else {
572              panic!("not an object");
573            };
574            f.op_stack.push(Value::Object {
575              object_id,
576              impl_id: i16::try_from(impl_id).unwrap(),
577            });
578          }
579          BytecodeOp::CopyTopOfStack => {
580            let val = f.op_stack.peek();
581            f.op_stack.push(val.clone());
582          }
583          BytecodeOp::Eq => {
584            let (left, right) = f.op_stack.pop_2();
585            let op = match (left, right) {
586              (Value::Float(l), Value::Float(r)) => Value::Boolean(l == r),
587              (Value::Int(l), Value::Int(r)) => Value::Boolean(l == r),
588              (Value::String(l), Value::String(r)) => Value::Boolean(l == r),
589              _ => panic!("invalid operands"),
590            };
591            f.op_stack.push(op);
592          }
593          BytecodeOp::GetIterator => {
594            let iterable = f.op_stack.pop();
595            let iterator = match iterable {
596              Value::Native(v) => match v.iterate(self) {
597                NativeResult::ExecError(_) => todo!(),
598                NativeResult::Value(next) => next,
599                NativeResult::Promise => todo!(),
600              },
601              _ => panic!("not iterable"),
602            };
603            f.op_stack.push(iterator);
604          }
605          BytecodeOp::GetNextOfIteratorOrGoto(pc) => {
606            let iterator = f.op_stack.peek();
607            let next = match iterator {
608              Value::Native(v) => match v.iterator_next(self) {
609                NativeResult::ExecError(_) => todo!(),
610                NativeResult::Value(next) => next,
611                NativeResult::Promise => todo!(),
612              },
613              _ => panic!("not an iterable"),
614            };
615            match next {
616              Some(v) => f.op_stack.push(v),
617              None => f.pc = pc,
618            };
619          }
620          BytecodeOp::Goto(loc) => {
621            f.pc = loc;
622          }
623          BytecodeOp::GotoIfFalse(loc) => {
624            let cond = f.op_stack.pop();
625            let Value::Boolean(cond) = cond else {
626              panic!("condition is not boolean");
627            };
628            if !cond {
629              f.pc = loc;
630            };
631          }
632          BytecodeOp::GotoIfNone(loc) => {
633            let val = f.op_stack.peek();
634            if let Value::None = val {
635              f.op_stack.pop();
636              f.pc = loc;
637            };
638          }
639          BytecodeOp::Index => {
640            let (obj_val, idx) = f.op_stack.pop_2();
641            match (&obj_val, &idx) {
642              (Value::Vec { vec_id }, Value::Int(idx)) => {
643                let vec = self.vecs.get(*vec_id);
644                let Some(val) = usize::try_from(*idx).ok().and_then(|idx| vec.get(idx)) else {
645                  panic!("out of bounds");
646                };
647                f.op_stack.push(val.clone());
648              }
649              _ => panic!("cannot index"),
650            };
651          }
652          BytecodeOp::IndexAssign => todo!(),
653          BytecodeOp::InitialiseObjectField {
654            field_name_string_const_id,
655          } => {
656            let (obj_val, value) = f.op_stack.pop_2();
657            let Value::Object { object_id, .. } = obj_val else {
658              panic!("not an object");
659            };
660            let field_name = program
661              .constants
662              .get(field_name_string_const_id)
663              .unwrap()
664              .string();
665            self
666              .objects
667              .get_mut(object_id)
668              .fields
669              .insert(field_name, value);
670            f.op_stack.push(obj_val);
671          }
672          BytecodeOp::LoadBool(v) => {
673            f.op_stack.push(Value::Boolean(v));
674          }
675          BytecodeOp::LoadBox { symbol } => {
676            let box_id = *f.box_ids.get(&symbol).unwrap();
677            let val = self.boxes.get(box_id).clone();
678            f.op_stack.push(val);
679          }
680          BytecodeOp::LoadClosure(func_id) => {
681            let cfg = &program.funcs[func_id];
682            let mut captured_box_ids = FxHashMap::default();
683            for symbol in cfg.box_captures.iter() {
684              captured_box_ids.insert(*symbol, *f.box_ids.get(&symbol).unwrap());
685            }
686            let closure_id = self.closures.allocate(Closure {
687              func_id,
688              captured_box_ids,
689              bound_args: vec![],
690              bound_offset: 0,
691            });
692            f.op_stack.push(Value::Closure { closure_id });
693          }
694          BytecodeOp::LoadConstant(constant_id) => {
695            f.op_stack
696              .push(match program.constants[constant_id].clone() {
697                Constant::Float(v) => Value::Float(v),
698                Constant::Int(v) => Value::Int(v),
699                Constant::String(v) => Value::String(v.clone()),
700              });
701          }
702          BytecodeOp::LoadField {
703            field_name_string_const_id,
704          } => {
705            let Value::Object { object_id, .. } = f.op_stack.pop() else {
706              panic!("not an object");
707            };
708            let obj = self.objects.get(object_id);
709            let field_name = program
710              .constants
711              .get(field_name_string_const_id)
712              .unwrap()
713              .string();
714            let val = obj.fields.get(&field_name).unwrap().clone();
715            f.op_stack.push(val);
716          }
717          BytecodeOp::LoadHostVar(id) => {
718            let val = self.host_vars.get(&id).unwrap().clone();
719            f.op_stack.push(val);
720          }
721          BytecodeOp::LoadModuleVar(id) => {
722            let val = module_vars.get(&id).unwrap().clone();
723            f.op_stack.push(val);
724          }
725          BytecodeOp::LoadNone => {
726            f.op_stack.push(Value::None);
727          }
728          BytecodeOp::LoadVar(var_id) => {
729            f.op_stack.push(f.vars[var_id].clone());
730          }
731          BytecodeOp::NewArray { argc: _ } => todo!(),
732          BytecodeOp::NewObject => {
733            let object_id = self.objects.allocate(Object {
734              fields: FxHashMap::default(),
735            });
736            f.op_stack.push(Value::Object {
737              object_id,
738              impl_id: -1,
739            });
740          }
741          BytecodeOp::NewString { argc } => {
742            let mut res = Vec::new();
743            for part in f.op_stack.pop_n(argc.into()) {
744              match part {
745                Value::Boolean(v) => write!(res, "{}", v).unwrap(),
746                Value::Float(v) => write!(res, "{}", v).unwrap(),
747                Value::None => res.extend_from_slice(b"None"),
748                Value::Int(v) => write!(res, "{}", v).unwrap(),
749                Value::String(v) => res.extend_from_slice(&v),
750                _ => panic!("cannot convert to string"),
751              };
752            }
753            f.op_stack.push(Value::String(Arc::new(res)));
754          }
755          BytecodeOp::Not => {
756            let val = f.op_stack.pop();
757            let res = match val {
758              Value::Boolean(b) => Value::Boolean(!b),
759              _ => panic!("invalid operand"),
760            };
761            f.op_stack.push(res);
762          }
763          BytecodeOp::PopStack => {
764            f.op_stack.pop();
765          }
766          BytecodeOp::Return => {
767            break;
768          }
769          BytecodeOp::StoreBox { symbol } => {
770            let val = f.op_stack.pop();
771            let box_id = *f.box_ids.get(&symbol).unwrap();
772            self.boxes.set(box_id, val);
773          }
774          BytecodeOp::StoreField {
775            field_name_string_const_id,
776          } => {
777            let val = f.op_stack.pop();
778            let Value::Object { object_id, .. } = f.op_stack.pop() else {
779              panic!("not an object");
780            };
781            let field_name = program
782              .constants
783              .get(field_name_string_const_id)
784              .unwrap()
785              .string();
786            self
787              .objects
788              .get_mut(object_id)
789              .fields
790              .insert(field_name, val);
791          }
792          BytecodeOp::StoreModuleVar(id) => {
793            let val = f.op_stack.pop();
794            module_vars.insert(id, val);
795          }
796          BytecodeOp::StoreVar(var_id) => {
797            let val = f.op_stack.pop();
798            f.vars[var_id] = val;
799          }
800          BytecodeOp::SwapTop2OfStack => {
801            let (op0, op1) = f.op_stack.pop_2();
802            f.op_stack.push(op1);
803            f.op_stack.push(op0);
804          }
805        };
806      }
807
808      match next_frame {
809        Some(next_frame) => {
810          coroutine
811            .stack_frames
812            .push(std::mem::replace(&mut f, next_frame));
813        }
814        None => {
815          assert_eq!(f.op_stack.len(), 1);
816          let retval = f.op_stack.pop();
817          if coroutine.stack_frames.is_empty() {
818            match coroutine.waited_on_by {
819              CoroutineWaitedOnBy::Coroutine(_) => todo!(),
820              CoroutineWaitedOnBy::CtlRequest(request_id) => {
821                self
822                  .ctl_sender
823                  .send((request_id, ThreadSafeValue::from_value(self, retval)))
824                  .unwrap();
825              }
826            };
827            self.current_coroutine_id = None;
828            return Ok(());
829          };
830          f = coroutine.stack_frames.pop().unwrap();
831          f.op_stack.push(retval);
832        }
833      };
834    }
835  }
836}
837
838impl VMCtl {
839  pub fn request(&self, id: usize, request: CtlRequest) {
840    self.sender.send(Event::CtlRequest { id, request }).unwrap();
841  }
842
843  pub fn receiver(&self) -> &Receiver<(usize, CtlResponse)> {
844    &self.receiver
845  }
846}