Skip to main content

mrubyedge/yamrb/
vm.rs

1use std::cell::{Cell, RefCell};
2use std::mem::MaybeUninit;
3use std::rc::Rc;
4use std::{array, env};
5
6use crate::Error;
7use crate::rite::{Irep, Rite, insn};
8
9use super::op::Op;
10use super::prelude::prelude;
11use super::value::RHashMap;
12use super::value::*;
13use super::{op, optable::*};
14
15pub const VERSION: &str = env!("CARGO_PKG_VERSION");
16pub const ENGINE: &str = "mruby/edge";
17
18const MAX_REGS_SIZE: usize = 256;
19
20#[derive(Debug, Clone)]
21pub enum TargetContext {
22    Class(Rc<RClass>),
23    Module(Rc<RModule>),
24}
25
26impl TargetContext {
27    pub fn name(&self) -> String {
28        match self {
29            TargetContext::Class(c) => c.full_name(),
30            TargetContext::Module(m) => m.full_name(),
31        }
32    }
33}
34
35#[derive(Debug)]
36pub struct Breadcrumb {
37    pub event: &'static str, // TODO: be enum
38    pub caller: Option<String>,
39    pub return_reg: Option<usize>,
40    pub upper: Option<Rc<Breadcrumb>>,
41}
42
43#[derive(Debug)]
44pub struct KArgs {
45    pub args: RefCell<RHashMap<RSym, Rc<RObject>>>,
46    pub kwrest_reg: Cell<usize>,
47    pub upper: Option<Rc<KArgs>>,
48}
49
50impl Breadcrumb {
51    #[cfg(feature = "mrubyedge-debug")]
52    pub fn display_breadcrumb_for_debug(&self, level: usize, max_level: usize) -> bool {
53        if level > max_level {
54            return false;
55        }
56        eprintln!(
57            "{}- Breadcrumb: event='{}', caller={}, return_reg={:?}",
58            "  ".repeat(level),
59            self.event,
60            self.caller.as_deref().unwrap_or("(none)"),
61            self.return_reg
62        );
63        if let Some(upper) = &self.upper {
64            upper.display_breadcrumb_for_debug(level + 1, max_level);
65        }
66        true
67    }
68}
69
70pub struct VM {
71    pub irep: Rc<IREP>,
72
73    pub id: usize,
74    pub bytecode: Vec<u8>,
75    pub current_irep: Rc<IREP>,
76    pub pc: Cell<usize>,
77    pub regs: [Option<Rc<RObject>>; MAX_REGS_SIZE],
78    pub current_regs_offset: usize,
79    pub current_callinfo: Option<Rc<CALLINFO>>,
80    pub current_breadcrumb: Option<Rc<Breadcrumb>>,
81    pub kargs: RefCell<Option<RHashMap<RSym, Rc<RObject>>>>,
82    pub current_kargs: RefCell<Option<Rc<KArgs>>>,
83    pub target_class: TargetContext,
84    pub exception: Option<Rc<RException>>,
85
86    pub flag_preemption: Cell<bool>,
87
88    #[cfg(feature = "insn-limit")]
89    pub insn_count: Cell<usize>,
90    #[cfg(feature = "insn-limit")]
91    pub insn_limit: usize,
92
93    // common class
94    pub object_class: Rc<RClass>,
95    pub builtin_class_table: RHashMap<&'static str, Rc<RClass>>,
96    pub class_object_table: RHashMap<String, Rc<RObject>>,
97
98    pub globals: RHashMap<String, Rc<RObject>>,
99    pub consts: RHashMap<String, Rc<RObject>>,
100
101    pub upper: Option<Rc<ENV>>,
102    // TODO: using fixed array?
103    pub cur_env: RHashMap<usize, Rc<ENV>>,
104    pub has_env_ref: RHashMap<usize, bool>,
105
106    pub fn_table: RFnTable,
107    pub fn_block_stack: RFnStack,
108}
109
110pub struct RFnTable {
111    pub size: Cell<usize>,
112    pub table: [MaybeUninit<Rc<RFn>>; 4096],
113}
114
115impl RFnTable {
116    #[allow(clippy::new_without_default)]
117    pub fn new() -> Self {
118        Self {
119            size: Cell::new(0),
120            table: array::from_fn(|_| MaybeUninit::uninit()),
121        }
122    }
123
124    pub fn set(&mut self, f: Rc<RFn>) {
125        let i = self.size.get();
126        if i >= self.table.len() {
127            panic!("RFnTable overflow");
128        }
129
130        self.table[i].write(f);
131        let size = self.size.get();
132        if i >= size {
133            self.size.set(i + 1);
134        }
135    }
136
137    pub fn get(&self, i: usize) -> Option<Rc<RFn>> {
138        if i >= self.size.get() {
139            return None;
140        }
141
142        unsafe { self.table[i].assume_init_ref() }.clone().into()
143    }
144
145    pub fn len(&self) -> usize {
146        self.size.get()
147    }
148
149    pub fn is_empty(&self) -> bool {
150        self.size.get() == 0
151    }
152}
153
154pub struct RFnStack {
155    pub size: Cell<usize>,
156    pub stack: [Option<Rc<RFn>>; 64],
157}
158
159impl RFnStack {
160    #[allow(clippy::new_without_default)]
161    pub fn new() -> Self {
162        Self {
163            size: Cell::new(0),
164            stack: array::from_fn(|_| None),
165        }
166    }
167
168    pub fn push(&mut self, f: Rc<RFn>) -> Result<(), Error> {
169        let i = self.size.get();
170        if i >= self.stack.len() {
171            return Err(Error::internal("RFnStack overflow"));
172        }
173
174        self.stack[i] = Some(f);
175        let size = self.size.get();
176        if i >= size {
177            self.size.set(i + 1);
178        }
179        Ok(())
180    }
181
182    pub fn pop(&self) -> Result<Rc<RFn>, Error> {
183        let i = self.size.get();
184        if i == 0 {
185            return Err(Error::internal("RFnStack underflow"));
186        }
187
188        self.size.set(i - 1);
189
190        self.stack[i - 1]
191            .as_ref()
192            .cloned()
193            .ok_or_else(|| Error::internal("RFnStack invalid state"))
194    }
195
196    pub fn len(&self) -> usize {
197        self.size.get()
198    }
199
200    pub fn is_empty(&self) -> bool {
201        self.size.get() == 0
202    }
203}
204
205impl VM {
206    /// Builds a VM from a parsed Rite chunk, consuming the bytecode and
207    /// preparing the VM so it can be executed via [`VM::run`].
208    pub fn open(rite: &mut Rite) -> VM {
209        let irep = rite_to_irep(rite);
210
211        VM::new_by_raw_irep(irep)
212    }
213
214    /// Returns a VM backed by an empty IREP that immediately executes a
215    /// `STOP` instruction. Useful for tests or placeholder VMs.
216    pub fn empty() -> VM {
217        let irep = IREP {
218            __id: 0,
219            nlocals: 0,
220            nregs: 0,
221            rlen: 0,
222            code: vec![op::Op {
223                code: insn::OpCode::STOP,
224                operand: insn::Fetched::Z,
225                pos: 18,
226                len: 1,
227            }],
228            syms: Vec::new(),
229            pool: Vec::new(),
230            reps: Vec::new(),
231            lv: None,
232            catch_target_pos: Vec::new(),
233        };
234        Self::new_by_raw_irep(irep)
235    }
236
237    /// Creates a VM directly from a raw [`IREP`] tree without going through the
238    /// Rite loader. This wires up the register file, globals, and builtin
239    /// tables and runs the prelude to seed standard classes.
240    pub fn new_by_raw_irep(irep: IREP) -> VM {
241        let irep = Rc::new(irep);
242        let globals = RHashMap::default();
243        let consts = RHashMap::default();
244        let builtin_class_table = RHashMap::default();
245        let class_object_table = RHashMap::default();
246
247        let object_class = Rc::new(RClass::new("Object", None, None));
248        object_class.update_module_weakref();
249
250        let id = 1; // TODO generator
251        let bytecode = Vec::new();
252        let current_irep = irep.clone();
253        let pc = Cell::new(0);
254        let regs: [Option<Rc<RObject>>; MAX_REGS_SIZE] = [const { None }; MAX_REGS_SIZE];
255        let current_regs_offset = 0;
256        let current_callinfo = None;
257        let current_breadcrumb = Some(Rc::new(Breadcrumb {
258            upper: None,
259            event: "root",
260            caller: None,
261            return_reg: None,
262        }));
263        let kargs = RefCell::new(None);
264        let current_kargs = RefCell::new(None);
265        let target_class = TargetContext::Class(object_class.clone());
266        let exception = None;
267        let flag_preemption = Cell::new(false);
268        let fn_table = RFnTable::new();
269        let fn_block_stack = RFnStack::new();
270        let upper = None;
271        let cur_env = RHashMap::default();
272        let has_env_ref = RHashMap::default();
273
274        #[cfg(feature = "insn-limit")]
275        let insn_count = Cell::new(0);
276        #[cfg(feature = "insn-limit")]
277        let insn_limit = {
278            let limit_str = env!(
279                "MRUBYEDGE_INSN_LIMIT",
280                "MRUBYEDGE_INSN_LIMIT must be set when insn-limit feature is enabled"
281            );
282            limit_str
283                .parse::<usize>()
284                .expect("MRUBYEDGE_INSN_LIMIT must be a valid number")
285        };
286
287        let mut vm = VM {
288            id,
289            bytecode,
290            irep,
291            current_irep,
292            pc,
293            regs,
294            current_regs_offset,
295            current_callinfo,
296            current_breadcrumb,
297            kargs,
298            current_kargs,
299            target_class,
300            exception,
301            flag_preemption,
302            #[cfg(feature = "insn-limit")]
303            insn_count,
304            #[cfg(feature = "insn-limit")]
305            insn_limit,
306            object_class,
307            builtin_class_table,
308            class_object_table,
309            globals,
310            consts,
311            upper,
312            cur_env,
313            has_env_ref,
314            fn_table,
315            fn_block_stack,
316        };
317
318        prelude(&mut vm);
319
320        vm
321    }
322
323    /// Resets the instruction counter. Only available when the `insn-limit` feature is enabled.
324    #[cfg(feature = "insn-limit")]
325    pub fn reset_insn_count(&mut self) {
326        self.insn_count.set(0);
327    }
328
329    /// Returns the current instruction count. Only available when the `insn-limit` feature is enabled.
330    #[cfg(feature = "insn-limit")]
331    pub fn get_insn_count(&self) -> usize {
332        self.insn_count.get()
333    }
334
335    /// Executes the current IREP until completion, returning the value in
336    /// register 0 or propagating any raised exception as an error. The
337    /// top-level `self` is initialized automatically before evaluation.
338    pub fn run(&mut self) -> Result<Rc<RObject>, Box<dyn std::error::Error>> {
339        self.current_irep = self.irep.clone();
340        self.pc.set(0);
341
342        let upper = self.current_breadcrumb.take();
343        let new_breadcrumb = Rc::new(Breadcrumb {
344            upper,
345            event: "run",
346            caller: None,
347            return_reg: None,
348        });
349        self.current_breadcrumb.replace(new_breadcrumb);
350        self.__run()
351    }
352
353    /// Internal run method that manages breadcrumb stack for internal calls.
354    pub fn run_internal(&mut self) -> Result<Rc<RObject>, Box<dyn std::error::Error>> {
355        let upper = self.current_breadcrumb.take();
356        let new_breadcrumb = Rc::new(Breadcrumb {
357            upper,
358            event: "run_internal",
359            caller: None,
360            return_reg: None,
361        });
362        self.current_breadcrumb.replace(new_breadcrumb);
363        self.__run()
364    }
365
366    pub fn eval_rite(
367        &mut self,
368        rite: &mut Rite,
369    ) -> Result<Rc<RObject>, Box<dyn std::error::Error>> {
370        let irep = rite_to_irep(rite);
371        self.pc.set(0);
372        self.current_irep = Rc::new(irep);
373
374        let upper = self.current_breadcrumb.take();
375        let new_breadcrumb = Rc::new(Breadcrumb {
376            upper,
377            event: "eval",
378            caller: None,
379            return_reg: None,
380        });
381        self.current_breadcrumb.replace(new_breadcrumb);
382        self.__run()
383    }
384
385    fn __run(&mut self) -> Result<Rc<RObject>, Box<dyn std::error::Error>> {
386        let class = self.object_class.clone();
387        // Insert top_self
388        let top_self = RObject {
389            tt: RType::Instance,
390            value: RValue::Instance(RInstance {
391                class,
392                ref_count: 1,
393            }),
394            object_id: 0.into(),
395            singleton_class: RefCell::new(None),
396            ivar: RefCell::new(RHashMap::default()),
397        }
398        .to_refcount_assigned();
399        if self.current_regs()[0].is_none() {
400            self.current_regs()[0].replace(top_self.clone());
401        }
402        let mut rescued = false;
403
404        loop {
405            if !rescued && let Some(e) = self.exception.clone() {
406                let operand = insn::Fetched::B(0);
407                let mut retreg = None;
408                if let Some(pos) = self.find_next_handler_pos() {
409                    self.pc.set(pos);
410                    rescued = true;
411                    continue;
412                }
413
414                if let Error::BlockReturn(id, v) = e.error_type.borrow().clone()
415                    && self.current_irep.__id == id
416                {
417                    // reached caller method's IREP, just return
418                    let operand = insn::Fetched::B(16); // FIXME: just a bit far reg
419                    self.current_regs()[16].replace(v);
420                    self.exception.take();
421                    op_return(self, &operand).expect("[bug]cannot return");
422                    continue;
423                }
424
425                if matches!(e.error_type.borrow().clone(), Error::Break(_)) {
426                    retreg = match self.current_breadcrumb.as_ref() {
427                        Some(bc) if bc.event == "do_op_send" => {
428                            let retreg = bc.as_ref().return_reg.unwrap_or(0);
429                            Some(retreg)
430                        }
431                        _ => None,
432                    };
433                }
434                match op_return(self, &operand) {
435                    Ok(_) => {}
436                    Err(_) => {
437                        if let Some(retreg) = retreg
438                            && let Error::Break(brkval) = e.error_type.borrow().clone()
439                        {
440                            self.current_regs()[retreg].replace(brkval);
441                            self.exception.take();
442                        } else {
443                            break;
444                        }
445                    }
446                }
447                if self.flag_preemption.get() {
448                    break;
449                } else {
450                    continue;
451                }
452            }
453            rescued = false;
454
455            let pc = self.pc.get();
456            if self.current_irep.code.len() <= pc {
457                // reached end of the IREP
458                break;
459            }
460            let op = *self
461                .current_irep
462                .code
463                .get(pc)
464                .ok_or_else(|| Error::internal("end of opcode reached"))?;
465            let operand = op.operand;
466            self.pc.set(pc + 1);
467
468            #[cfg(feature = "insn-limit")]
469            {
470                let count = self.insn_count.get();
471                if count >= self.insn_limit {
472                    return Err(Error::internal(format!(
473                        "instruction limit exceeded: {} instructions",
474                        self.insn_limit
475                    ))
476                    .into());
477                }
478                self.insn_count.set(count + 1);
479            }
480
481            #[cfg(feature = "mrubyedge-debug")]
482            if let Ok(v) = env::var("MRUBYEDGE_DEBUG") {
483                let level: i32 = v.parse().unwrap_or(1);
484                if level >= 2 {
485                    self.debug_dump_to_stdout(32);
486                }
487                eprintln!(
488                    "{:?}: {:?} (pos={} len={})",
489                    op.code, &operand, op.pos, op.len
490                );
491            }
492
493            match consume_expr(self, op.code, &operand, op.pos, op.len) {
494                Ok(_) => {}
495                Err(e) => {
496                    let exception = RException::from_error(self, &e);
497                    self.exception = Some(Rc::new(exception));
498                    continue;
499                }
500            }
501
502            if self.flag_preemption.get() {
503                break;
504            }
505        }
506
507        self.flag_preemption.set(false);
508
509        if let Some(e) = self.exception.clone() {
510            return Err(e.error_type.borrow().clone().into());
511        }
512
513        let retval = match self.current_regs()[0].take() {
514            Some(v) => Ok(v),
515            None => Ok(Rc::new(RObject::nil())),
516        };
517        self.current_regs()[0].replace(top_self.clone());
518
519        retval
520    }
521
522    pub(crate) fn find_next_handler_pos(&mut self) -> Option<usize> {
523        let ci = self.pc.get();
524        for p in self.current_irep.catch_target_pos.iter() {
525            if ci < *p {
526                return Some(*p);
527            }
528        }
529        None
530    }
531
532    pub(crate) fn current_regs(&mut self) -> &mut [Option<Rc<RObject>>] {
533        &mut self.regs[self.current_regs_offset..]
534    }
535
536    pub(crate) fn get_current_regs_cloned(&mut self, i: usize) -> Result<Rc<RObject>, Error> {
537        self.current_regs()[i]
538            .clone()
539            .ok_or_else(|| Error::internal(format!("register {} is not assigned", i)))
540    }
541
542    pub(crate) fn take_current_regs(&mut self, i: usize) -> Result<Rc<RObject>, Error> {
543        self.current_regs()[i]
544            .take()
545            .ok_or_else(|| Error::internal(format!("register {} is not assigned", i)))
546    }
547
548    /// Returns the current `self` object from register 0, or an error if it has
549    /// not been initialized yet.
550    pub fn getself(&mut self) -> Result<Rc<RObject>, Error> {
551        self.get_current_regs_cloned(0)
552    }
553
554    /// Retrieves `self` without error handling, panicking if register 0 is
555    /// empty. Prefer [`VM::getself`] when the value may be absent.
556    pub fn must_getself(&mut self) -> Rc<RObject> {
557        self.current_regs()[0]
558            .clone()
559            .expect("self is not assigned")
560    }
561
562    pub fn get_kwargs(&self) -> Option<RHashMap<String, Rc<RObject>>> {
563        let kwargs = self.current_kargs.borrow().clone();
564        kwargs.map(|kargs| {
565            kargs
566                .args
567                .borrow()
568                .iter()
569                .map(|(k, v)| (k.name.clone(), v.clone()))
570                .collect()
571        })
572    }
573
574    pub(crate) fn register_fn(&mut self, f: RFn) -> usize {
575        self.fn_table.set(Rc::new(f));
576        self.fn_table.len() - 1
577    }
578
579    pub(crate) fn push_fnblock(&mut self, f: Rc<RFn>) -> Result<(), Error> {
580        self.fn_block_stack.push(f)
581    }
582
583    pub(crate) fn pop_fnblock(&mut self) -> Result<Rc<RFn>, Error> {
584        self.fn_block_stack.pop()
585    }
586
587    pub(crate) fn get_fn(&self, i: usize) -> Option<Rc<RFn>> {
588        self.fn_table.get(i)
589    }
590
591    /// Looks up a previously defined builtin class by name. Panics if the
592    /// class does not exist, which usually signals a missing prelude setup.
593    pub fn get_class_by_name(&self, name: &str) -> Rc<RClass> {
594        self.builtin_class_table
595            .get(name)
596            .cloned()
597            .unwrap_or_else(|| panic!("Class {} not found", name))
598    }
599
600    pub fn get_module_by_name(&self, name: &str) -> Rc<RModule> {
601        match self.consts.get(name).cloned() {
602            Some(obj) => match &obj.value {
603                RValue::Module(m) => m.clone(),
604                _ => panic!("Module {} not found", name),
605            },
606            None => panic!("Module {} not found", name),
607        }
608    }
609
610    pub fn get_const_by_name(&self, name: &str) -> Option<Rc<RObject>> {
611        self.consts.get(name).cloned()
612    }
613
614    /// Defines a new class under the optional parent module, inheriting from
615    /// `superclass` or `Object` by default, and registers it in the constant
616    /// table. The resulting class object is returned for further mutation.
617    pub fn define_class(
618        &mut self,
619        name: &str,
620        superclass: Option<Rc<RClass>>,
621        parent_module: Option<Rc<RModule>>,
622    ) -> Rc<RClass> {
623        let superclass = match superclass {
624            Some(c) => c,
625            None => self.object_class.clone(),
626        };
627        let class = Rc::new(RClass::new(name, Some(superclass), parent_module.clone()));
628        class.update_module_weakref();
629
630        let object = RObject::class(class.clone(), self);
631        self.consts.insert(name.to_string(), object.clone());
632        if let Some(parent) = parent_module {
633            parent
634                .consts
635                .borrow_mut()
636                .insert(name.to_string(), object.clone());
637        } else {
638            self.object_class
639                .consts
640                .borrow_mut()
641                .insert(name.to_string(), object);
642        }
643        class
644    }
645
646    /// Defines a new module, optionally nested under another module, and stores
647    /// it in the VM's constant table so it becomes accessible to Ruby code.
648    pub fn define_module(&mut self, name: &str, parent_module: Option<Rc<RModule>>) -> Rc<RModule> {
649        let module = Rc::new(RModule::new(name));
650        if let Some(parent) = parent_module {
651            module.parent.replace(Some(parent));
652        }
653        let object = RObject::module(module.clone()).to_refcount_assigned();
654        self.consts.insert(name.to_string(), object.clone());
655        self.object_class
656            .consts
657            .borrow_mut()
658            .insert(name.to_string(), object);
659        module
660    }
661
662    pub(crate) fn define_standard_class(&mut self, name: &'static str) -> Rc<RClass> {
663        let class = self.define_class(name, None, None);
664        self.builtin_class_table.insert(name, class.clone());
665        class
666    }
667
668    pub(crate) fn define_standard_class_with_superclass(
669        &mut self,
670        name: &'static str,
671        superclass: Rc<RClass>,
672    ) -> Rc<RClass> {
673        let class = self.define_class(name, Some(superclass.clone()), None);
674        self.builtin_class_table.insert(name, class.clone());
675        class
676    }
677
678    #[allow(dead_code)]
679    pub(crate) fn define_standard_class_under(
680        &mut self,
681        name: &'static str,
682        parent: Rc<RModule>,
683    ) -> Rc<RClass> {
684        let class = self.define_class(name, None, Some(parent));
685        self.builtin_class_table.insert(name, class.clone());
686        class
687    }
688
689    #[allow(dead_code)]
690    pub(crate) fn define_standard_class_with_superclass_under(
691        &mut self,
692        name: &'static str,
693        superclass: Rc<RClass>,
694        parent: Rc<RModule>,
695    ) -> Rc<RClass> {
696        let class = self.define_class(name, Some(superclass.clone()), Some(parent));
697        self.builtin_class_table.insert(name, class.clone());
698        class
699    }
700
701    #[allow(unused)]
702    pub fn debug_dump_to_stdout(&mut self, max_breadcrumb_level: usize) {
703        #[cfg(feature = "mrubyedge-debug")]
704        {
705            use crate::yamrb::helpers::mrb_call_inspect;
706            eprintln!("=== VM Dump ===");
707            eprintln!("ID: {}", self.id);
708            eprintln!("Current IREP ID: {}", self.current_irep.__id);
709            eprintln!("PC: {}", self.pc.get());
710            let current_regs_offset = self.current_regs_offset;
711            eprintln!("IREPs:");
712            self.current_irep
713                .code
714                .iter()
715                .enumerate()
716                .for_each(|(i, op)| {
717                    eprintln!("{:04} {:?}: {:?}", i, op.code, op.operand);
718                });
719            eprintln!("Current Regs Offset: {}", current_regs_offset);
720            eprintln!("Regs:");
721            let size = self.regs.len();
722            for i in 0..size {
723                let reg = self.regs.get(i).unwrap().clone();
724                if let Some(obj) = reg {
725                    let inspect: String = mrb_call_inspect(self, obj.clone())
726                        .unwrap()
727                        .as_ref()
728                        .try_into()
729                        .unwrap_or_else(|_| "(uninspectable)".into());
730                    if i < current_regs_offset {
731                        eprintln!("  R{}(--): {}(oid={})", i, inspect, obj.object_id.get());
732                    } else {
733                        eprintln!(
734                            "  R{}(R{}): {}(oid={})",
735                            i,
736                            i - current_regs_offset,
737                            inspect,
738                            obj.object_id.get()
739                        );
740                    }
741                } else if i < 16 || i < current_regs_offset {
742                    eprintln!("  R{}(--): <None>", i);
743                } else {
744                    break;
745                }
746            }
747            // eprintln!("Current CallInfo: {:?}", self.current_callinfo);
748            eprintln!("Target Class: {}", self.target_class.name());
749            eprintln!(
750                "Exception: {:?}",
751                self.exception
752                    .as_deref()
753                    .map(|e| e.error_type.borrow().clone())
754            );
755            eprintln!("--- Breadcrumb ---");
756            if let Some(bc) = &self.current_breadcrumb {
757                bc.display_breadcrumb_for_debug(0, max_breadcrumb_level);
758            } else {
759                eprintln!("(none)");
760            }
761            eprintln!("=== End of VM Dump ===");
762        }
763    }
764
765    pub fn get_outermost_env(&self) -> Option<Rc<ENV>> {
766        let mut env = self.upper.clone();
767        while let Some(e) = env.clone() {
768            if e.upper.is_none() {
769                return env;
770            }
771            env = e.upper.clone();
772        }
773        env
774    }
775}
776
777fn interpret_insn(mut insns: &[u8]) -> Vec<Op> {
778    let mut pos: usize = 0;
779    let mut ops = Vec::new();
780    while !insns.is_empty() {
781        let op = insns[0];
782        let opcode: insn::OpCode = op.try_into().unwrap();
783        let fetched = insn::FETCH_TABLE[op as usize](&mut insns).unwrap();
784        ops.push(Op::new(opcode, fetched, pos, 1 + fetched.len()));
785        pos += 1 + fetched.len();
786    }
787    ops
788}
789
790fn load_irep_1(reps: &mut [Irep], pos: usize) -> (IREP, usize) {
791    let irep = &mut reps[pos];
792    let mut irep1 = IREP {
793        __id: pos,
794        nlocals: irep.nlocals(),
795        nregs: irep.nregs(),
796        rlen: irep.rlen(),
797        code: Vec::new(),
798        syms: Vec::new(),
799        pool: Vec::new(),
800        reps: Vec::new(),
801        lv: None,
802        catch_target_pos: Vec::new(),
803    };
804    for sym in irep.syms.iter() {
805        irep1
806            .syms
807            .push(RSym::new(sym.to_string_lossy().to_string()));
808    }
809    for val in irep.pool.iter() {
810        match val {
811            crate::rite::PoolValue::Str(s) | crate::rite::PoolValue::SStr(s) => {
812                irep1.pool.push(RPool::Str(s.to_string_lossy().to_string()));
813            }
814            crate::rite::PoolValue::Int32(i) => {
815                irep1.pool.push(RPool::Int(*i as i64));
816            }
817            crate::rite::PoolValue::Int64(i) => {
818                irep1.pool.push(RPool::Int(*i));
819            }
820            crate::rite::PoolValue::Float(f) => {
821                irep1.pool.push(RPool::Float(*f));
822            }
823            crate::rite::PoolValue::BigInt(_) => {
824                // BigInt not yet supported, store as 0 for now
825                irep1.pool.push(RPool::Int(0));
826            }
827        }
828    }
829    let code = interpret_insn(irep.insn);
830    for ch in irep.catch_handlers.iter() {
831        let pos = ch.target;
832        let (i, _) = code
833            .iter()
834            .enumerate()
835            .find(|(_, op)| op.pos == pos)
836            .expect("catch handler mismatch");
837        irep1.catch_target_pos.push(i);
838    }
839    let mut map = RHashMap::default();
840    for (reg, name) in irep.lv.iter().enumerate() {
841        if let Some(name) = name {
842            // lv register index in mruby is 1-based
843            map.insert(reg + 1, name.to_string_lossy().to_string());
844        }
845    }
846    if !map.is_empty() {
847        irep1.lv = Some(map);
848    }
849    irep1.catch_target_pos.sort();
850
851    irep1.code = code;
852    (irep1, pos + 1)
853}
854
855fn load_irep_0(reps: &mut [Irep], pos: usize) -> (IREP, usize) {
856    let (mut irep0, newpos) = load_irep_1(reps, pos);
857    let mut pos = newpos;
858    for _ in 0..irep0.rlen {
859        let (rep, newpos) = load_irep_0(reps, pos);
860        pos = newpos;
861        irep0.reps.push(Rc::new(rep));
862    }
863    (irep0, pos)
864}
865
866// This will consume the Rite object and return the IREP
867fn rite_to_irep(rite: &mut Rite) -> IREP {
868    let (irep0, _) = load_irep_0(&mut rite.irep, 0);
869    irep0
870}
871
872#[derive(Debug, Clone)]
873pub struct IREP {
874    pub __id: usize,
875
876    pub nlocals: usize,
877    pub nregs: usize, // NOTE: is u8 better?
878    pub rlen: usize,
879    pub code: Vec<Op>,
880    pub syms: Vec<RSym>,
881    pub pool: Vec<RPool>,
882    pub reps: Vec<Rc<IREP>>,
883    pub lv: Option<RHashMap<usize, String>>,
884    pub catch_target_pos: Vec<usize>,
885}
886
887#[derive(Debug, Clone)]
888pub struct CALLINFO {
889    pub prev: Option<Rc<CALLINFO>>,
890    pub method_id: RSym,
891    pub pc_irep: Rc<IREP>,
892    pub pc: usize,
893    pub current_regs_offset: usize,
894    pub target_class: TargetContext,
895    pub n_args: usize,
896    pub return_reg: usize,
897    pub method_owner: Option<Rc<RModule>>,
898}
899
900#[derive(Debug, Clone)]
901pub struct ENV {
902    pub __irep_id: usize,
903    pub upper: Option<Rc<ENV>>,
904    pub captured: RefCell<Option<Vec<Option<Rc<RObject>>>>>,
905    pub current_regs_offset: usize,
906    pub is_expired: Cell<bool>,
907}
908
909impl ENV {
910    #[allow(unused)]
911    pub(crate) fn has_captured(&self) -> bool {
912        self.captured.borrow().is_some()
913    }
914
915    #[allow(unused)]
916    pub(crate) fn capture(&self, regs: &[Option<Rc<RObject>>]) {
917        let mut captured = self.captured.borrow_mut();
918        captured.replace(regs.to_vec());
919    }
920
921    pub(crate) fn capture_no_clone(&self, regs: Vec<Option<Rc<RObject>>>) {
922        let mut captured = self.captured.borrow_mut();
923        captured.replace(regs);
924    }
925
926    pub(crate) fn expire(&self) {
927        self.is_expired.set(true);
928    }
929
930    pub(crate) fn expired(&self) -> bool {
931        self.is_expired.get()
932    }
933}