use std::cell::{Cell, RefCell};
use std::mem::MaybeUninit;
use std::rc::Rc;
use std::{array, env};
use crate::Error;
use crate::rite::{Irep, Rite, insn};
use super::op::Op;
use super::prelude::prelude;
use super::value::RHashMap;
use super::value::*;
use super::{op, optable::*};
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const ENGINE: &str = "mruby/edge";
const MAX_REGS_SIZE: usize = 256;
#[derive(Debug, Clone)]
pub enum TargetContext {
Class(Rc<RClass>),
Module(Rc<RModule>),
}
impl TargetContext {
pub fn name(&self) -> String {
match self {
TargetContext::Class(c) => c.full_name(),
TargetContext::Module(m) => m.full_name(),
}
}
}
#[derive(Debug)]
pub struct Breadcrumb {
pub event: &'static str, pub caller: Option<String>,
pub return_reg: Option<usize>,
pub upper: Option<Rc<Breadcrumb>>,
}
#[derive(Debug)]
pub struct KArgs {
pub args: RefCell<RHashMap<RSym, Rc<RObject>>>,
pub kwrest_reg: Cell<usize>,
pub upper: Option<Rc<KArgs>>,
}
impl Breadcrumb {
#[cfg(feature = "mrubyedge-debug")]
pub fn display_breadcrumb_for_debug(&self, level: usize, max_level: usize) -> bool {
if level > max_level {
return false;
}
eprintln!(
"{}- Breadcrumb: event='{}', caller={}, return_reg={:?}",
" ".repeat(level),
self.event,
self.caller.as_deref().unwrap_or("(none)"),
self.return_reg
);
if let Some(upper) = &self.upper {
upper.display_breadcrumb_for_debug(level + 1, max_level);
}
true
}
}
pub struct VM {
pub irep: Rc<IREP>,
pub id: usize,
pub bytecode: Vec<u8>,
pub current_irep: Rc<IREP>,
pub pc: Cell<usize>,
pub regs: [Option<Rc<RObject>>; MAX_REGS_SIZE],
pub current_regs_offset: usize,
pub current_callinfo: Option<Rc<CALLINFO>>,
pub current_breadcrumb: Option<Rc<Breadcrumb>>,
pub kargs: RefCell<Option<RHashMap<RSym, Rc<RObject>>>>,
pub current_kargs: RefCell<Option<Rc<KArgs>>>,
pub target_class: TargetContext,
pub exception: Option<Rc<RException>>,
pub flag_preemption: Cell<bool>,
#[cfg(feature = "insn-limit")]
pub insn_count: Cell<usize>,
#[cfg(feature = "insn-limit")]
pub insn_limit: usize,
pub object_class: Rc<RClass>,
pub builtin_class_table: RHashMap<&'static str, Rc<RClass>>,
pub class_object_table: RHashMap<String, Rc<RObject>>,
pub globals: RHashMap<String, Rc<RObject>>,
pub consts: RHashMap<String, Rc<RObject>>,
pub upper: Option<Rc<ENV>>,
pub cur_env: RHashMap<usize, Rc<ENV>>,
pub has_env_ref: RHashMap<usize, bool>,
pub fn_table: RFnTable,
pub fn_block_stack: RFnStack,
}
pub struct RFnTable {
pub size: Cell<usize>,
pub table: [MaybeUninit<Rc<RFn>>; 4096],
}
impl RFnTable {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
size: Cell::new(0),
table: array::from_fn(|_| MaybeUninit::uninit()),
}
}
pub fn set(&mut self, f: Rc<RFn>) {
let i = self.size.get();
if i >= self.table.len() {
panic!("RFnTable overflow");
}
self.table[i].write(f);
let size = self.size.get();
if i >= size {
self.size.set(i + 1);
}
}
pub fn get(&self, i: usize) -> Option<Rc<RFn>> {
if i >= self.size.get() {
return None;
}
unsafe { self.table[i].assume_init_ref() }.clone().into()
}
pub fn len(&self) -> usize {
self.size.get()
}
pub fn is_empty(&self) -> bool {
self.size.get() == 0
}
}
pub struct RFnStack {
pub size: Cell<usize>,
pub stack: [Option<Rc<RFn>>; 64],
}
impl RFnStack {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
size: Cell::new(0),
stack: array::from_fn(|_| None),
}
}
pub fn push(&mut self, f: Rc<RFn>) -> Result<(), Error> {
let i = self.size.get();
if i >= self.stack.len() {
return Err(Error::internal("RFnStack overflow"));
}
self.stack[i] = Some(f);
let size = self.size.get();
if i >= size {
self.size.set(i + 1);
}
Ok(())
}
pub fn pop(&self) -> Result<Rc<RFn>, Error> {
let i = self.size.get();
if i == 0 {
return Err(Error::internal("RFnStack underflow"));
}
self.size.set(i - 1);
self.stack[i - 1]
.as_ref()
.cloned()
.ok_or_else(|| Error::internal("RFnStack invalid state"))
}
pub fn len(&self) -> usize {
self.size.get()
}
pub fn is_empty(&self) -> bool {
self.size.get() == 0
}
}
impl VM {
pub fn open(rite: &mut Rite) -> VM {
let irep = rite_to_irep(rite);
VM::new_by_raw_irep(irep)
}
pub fn empty() -> VM {
let irep = IREP {
__id: 0,
nlocals: 0,
nregs: 0,
rlen: 0,
code: vec![op::Op {
code: insn::OpCode::STOP,
operand: insn::Fetched::Z,
pos: 18,
len: 1,
}],
syms: Vec::new(),
pool: Vec::new(),
reps: Vec::new(),
lv: None,
catch_target_pos: Vec::new(),
};
Self::new_by_raw_irep(irep)
}
pub fn new_by_raw_irep(irep: IREP) -> VM {
let irep = Rc::new(irep);
let globals = RHashMap::default();
let consts = RHashMap::default();
let builtin_class_table = RHashMap::default();
let class_object_table = RHashMap::default();
let object_class = Rc::new(RClass::new("Object", None, None));
object_class.update_module_weakref();
let id = 1; let bytecode = Vec::new();
let current_irep = irep.clone();
let pc = Cell::new(0);
let regs: [Option<Rc<RObject>>; MAX_REGS_SIZE] = [const { None }; MAX_REGS_SIZE];
let current_regs_offset = 0;
let current_callinfo = None;
let current_breadcrumb = Some(Rc::new(Breadcrumb {
upper: None,
event: "root",
caller: None,
return_reg: None,
}));
let kargs = RefCell::new(None);
let current_kargs = RefCell::new(None);
let target_class = TargetContext::Class(object_class.clone());
let exception = None;
let flag_preemption = Cell::new(false);
let fn_table = RFnTable::new();
let fn_block_stack = RFnStack::new();
let upper = None;
let cur_env = RHashMap::default();
let has_env_ref = RHashMap::default();
#[cfg(feature = "insn-limit")]
let insn_count = Cell::new(0);
#[cfg(feature = "insn-limit")]
let insn_limit = {
let limit_str = env!(
"MRUBYEDGE_INSN_LIMIT",
"MRUBYEDGE_INSN_LIMIT must be set when insn-limit feature is enabled"
);
limit_str
.parse::<usize>()
.expect("MRUBYEDGE_INSN_LIMIT must be a valid number")
};
let mut vm = VM {
id,
bytecode,
irep,
current_irep,
pc,
regs,
current_regs_offset,
current_callinfo,
current_breadcrumb,
kargs,
current_kargs,
target_class,
exception,
flag_preemption,
#[cfg(feature = "insn-limit")]
insn_count,
#[cfg(feature = "insn-limit")]
insn_limit,
object_class,
builtin_class_table,
class_object_table,
globals,
consts,
upper,
cur_env,
has_env_ref,
fn_table,
fn_block_stack,
};
prelude(&mut vm);
vm
}
#[cfg(feature = "insn-limit")]
pub fn reset_insn_count(&mut self) {
self.insn_count.set(0);
}
#[cfg(feature = "insn-limit")]
pub fn get_insn_count(&self) -> usize {
self.insn_count.get()
}
pub fn run(&mut self) -> Result<Rc<RObject>, Box<dyn std::error::Error>> {
self.current_irep = self.irep.clone();
self.pc.set(0);
let upper = self.current_breadcrumb.take();
let new_breadcrumb = Rc::new(Breadcrumb {
upper,
event: "run",
caller: None,
return_reg: None,
});
self.current_breadcrumb.replace(new_breadcrumb);
self.__run()
}
pub fn run_internal(&mut self) -> Result<Rc<RObject>, Box<dyn std::error::Error>> {
let upper = self.current_breadcrumb.take();
let new_breadcrumb = Rc::new(Breadcrumb {
upper,
event: "run_internal",
caller: None,
return_reg: None,
});
self.current_breadcrumb.replace(new_breadcrumb);
self.__run()
}
pub fn eval_rite(
&mut self,
rite: &mut Rite,
) -> Result<Rc<RObject>, Box<dyn std::error::Error>> {
let irep = rite_to_irep(rite);
self.pc.set(0);
self.current_irep = Rc::new(irep);
let upper = self.current_breadcrumb.take();
let new_breadcrumb = Rc::new(Breadcrumb {
upper,
event: "eval",
caller: None,
return_reg: None,
});
self.current_breadcrumb.replace(new_breadcrumb);
self.__run()
}
fn __run(&mut self) -> Result<Rc<RObject>, Box<dyn std::error::Error>> {
let class = self.object_class.clone();
let top_self = RObject {
tt: RType::Instance,
value: RValue::Instance(RInstance {
class,
ref_count: 1,
}),
object_id: 0.into(),
singleton_class: RefCell::new(None),
ivar: RefCell::new(RHashMap::default()),
}
.to_refcount_assigned();
if self.current_regs()[0].is_none() {
self.current_regs()[0].replace(top_self.clone());
}
let mut rescued = false;
loop {
if !rescued && let Some(e) = self.exception.clone() {
let operand = insn::Fetched::B(0);
let mut retreg = None;
if let Some(pos) = self.find_next_handler_pos() {
self.pc.set(pos);
rescued = true;
continue;
}
if let Error::BlockReturn(id, v) = e.error_type.borrow().clone()
&& self.current_irep.__id == id
{
let operand = insn::Fetched::B(16); self.current_regs()[16].replace(v);
self.exception.take();
op_return(self, &operand).expect("[bug]cannot return");
continue;
}
if matches!(e.error_type.borrow().clone(), Error::Break(_)) {
retreg = match self.current_breadcrumb.as_ref() {
Some(bc) if bc.event == "do_op_send" => {
let retreg = bc.as_ref().return_reg.unwrap_or(0);
Some(retreg)
}
_ => None,
};
}
match op_return(self, &operand) {
Ok(_) => {}
Err(_) => {
if let Some(retreg) = retreg
&& let Error::Break(brkval) = e.error_type.borrow().clone()
{
self.current_regs()[retreg].replace(brkval);
self.exception.take();
} else {
break;
}
}
}
if self.flag_preemption.get() {
break;
} else {
continue;
}
}
rescued = false;
let pc = self.pc.get();
if self.current_irep.code.len() <= pc {
break;
}
let op = *self
.current_irep
.code
.get(pc)
.ok_or_else(|| Error::internal("end of opcode reached"))?;
let operand = op.operand;
self.pc.set(pc + 1);
#[cfg(feature = "insn-limit")]
{
let count = self.insn_count.get();
if count >= self.insn_limit {
return Err(Error::internal(format!(
"instruction limit exceeded: {} instructions",
self.insn_limit
))
.into());
}
self.insn_count.set(count + 1);
}
#[cfg(feature = "mrubyedge-debug")]
if let Ok(v) = env::var("MRUBYEDGE_DEBUG") {
let level: i32 = v.parse().unwrap_or(1);
if level >= 2 {
self.debug_dump_to_stdout(32);
}
eprintln!(
"{:?}: {:?} (pos={} len={})",
op.code, &operand, op.pos, op.len
);
}
match consume_expr(self, op.code, &operand, op.pos, op.len) {
Ok(_) => {}
Err(e) => {
let exception = RException::from_error(self, &e);
self.exception = Some(Rc::new(exception));
continue;
}
}
if self.flag_preemption.get() {
break;
}
}
self.flag_preemption.set(false);
if let Some(e) = self.exception.clone() {
return Err(e.error_type.borrow().clone().into());
}
let retval = match self.current_regs()[0].take() {
Some(v) => Ok(v),
None => Ok(Rc::new(RObject::nil())),
};
self.current_regs()[0].replace(top_self.clone());
retval
}
pub(crate) fn find_next_handler_pos(&mut self) -> Option<usize> {
let ci = self.pc.get();
for p in self.current_irep.catch_target_pos.iter() {
if ci < *p {
return Some(*p);
}
}
None
}
pub(crate) fn current_regs(&mut self) -> &mut [Option<Rc<RObject>>] {
&mut self.regs[self.current_regs_offset..]
}
pub(crate) fn get_current_regs_cloned(&mut self, i: usize) -> Result<Rc<RObject>, Error> {
self.current_regs()[i]
.clone()
.ok_or_else(|| Error::internal(format!("register {} is not assigned", i)))
}
pub(crate) fn take_current_regs(&mut self, i: usize) -> Result<Rc<RObject>, Error> {
self.current_regs()[i]
.take()
.ok_or_else(|| Error::internal(format!("register {} is not assigned", i)))
}
pub fn getself(&mut self) -> Result<Rc<RObject>, Error> {
self.get_current_regs_cloned(0)
}
pub fn must_getself(&mut self) -> Rc<RObject> {
self.current_regs()[0]
.clone()
.expect("self is not assigned")
}
pub fn get_kwargs(&self) -> Option<RHashMap<String, Rc<RObject>>> {
let kwargs = self.current_kargs.borrow().clone();
kwargs.map(|kargs| {
kargs
.args
.borrow()
.iter()
.map(|(k, v)| (k.name.clone(), v.clone()))
.collect()
})
}
pub(crate) fn register_fn(&mut self, f: RFn) -> usize {
self.fn_table.set(Rc::new(f));
self.fn_table.len() - 1
}
pub(crate) fn push_fnblock(&mut self, f: Rc<RFn>) -> Result<(), Error> {
self.fn_block_stack.push(f)
}
pub(crate) fn pop_fnblock(&mut self) -> Result<Rc<RFn>, Error> {
self.fn_block_stack.pop()
}
pub(crate) fn get_fn(&self, i: usize) -> Option<Rc<RFn>> {
self.fn_table.get(i)
}
pub fn get_class_by_name(&self, name: &str) -> Rc<RClass> {
self.builtin_class_table
.get(name)
.cloned()
.unwrap_or_else(|| panic!("Class {} not found", name))
}
pub fn get_module_by_name(&self, name: &str) -> Rc<RModule> {
match self.consts.get(name).cloned() {
Some(obj) => match &obj.value {
RValue::Module(m) => m.clone(),
_ => panic!("Module {} not found", name),
},
None => panic!("Module {} not found", name),
}
}
pub fn get_const_by_name(&self, name: &str) -> Option<Rc<RObject>> {
self.consts.get(name).cloned()
}
pub fn define_class(
&mut self,
name: &str,
superclass: Option<Rc<RClass>>,
parent_module: Option<Rc<RModule>>,
) -> Rc<RClass> {
let superclass = match superclass {
Some(c) => c,
None => self.object_class.clone(),
};
let class = Rc::new(RClass::new(name, Some(superclass), parent_module.clone()));
class.update_module_weakref();
let object = RObject::class(class.clone(), self);
self.consts.insert(name.to_string(), object.clone());
if let Some(parent) = parent_module {
parent
.consts
.borrow_mut()
.insert(name.to_string(), object.clone());
} else {
self.object_class
.consts
.borrow_mut()
.insert(name.to_string(), object);
}
class
}
pub fn define_module(&mut self, name: &str, parent_module: Option<Rc<RModule>>) -> Rc<RModule> {
let existing = if let Some(ref parent) = parent_module {
parent.consts.borrow().get(name).cloned()
} else {
self.consts.get(name).cloned()
};
if let Some(existing) = existing
&& let RValue::Module(ref m) = existing.value
{
return m.clone();
}
let module = Rc::new(RModule::new(name));
if let Some(parent) = parent_module {
module.parent.replace(Some(parent));
}
let object = RObject::module(module.clone()).to_refcount_assigned();
self.consts.insert(name.to_string(), object.clone());
self.object_class
.consts
.borrow_mut()
.insert(name.to_string(), object);
module
}
pub(crate) fn define_standard_class(&mut self, name: &'static str) -> Rc<RClass> {
let class = self.define_class(name, None, None);
self.builtin_class_table.insert(name, class.clone());
class
}
pub(crate) fn define_standard_class_with_superclass(
&mut self,
name: &'static str,
superclass: Rc<RClass>,
) -> Rc<RClass> {
let class = self.define_class(name, Some(superclass.clone()), None);
self.builtin_class_table.insert(name, class.clone());
class
}
#[allow(dead_code)]
pub(crate) fn define_standard_class_under(
&mut self,
name: &'static str,
parent: Rc<RModule>,
) -> Rc<RClass> {
let class = self.define_class(name, None, Some(parent));
self.builtin_class_table.insert(name, class.clone());
class
}
#[allow(dead_code)]
pub(crate) fn define_standard_class_with_superclass_under(
&mut self,
name: &'static str,
superclass: Rc<RClass>,
parent: Rc<RModule>,
) -> Rc<RClass> {
let class = self.define_class(name, Some(superclass.clone()), Some(parent));
self.builtin_class_table.insert(name, class.clone());
class
}
#[allow(unused)]
pub fn debug_dump_to_stdout(&mut self, max_breadcrumb_level: usize) {
#[cfg(feature = "mrubyedge-debug")]
{
use crate::yamrb::helpers::mrb_call_inspect;
eprintln!("=== VM Dump ===");
eprintln!("ID: {}", self.id);
eprintln!("Current IREP ID: {}", self.current_irep.__id);
eprintln!("PC: {}", self.pc.get());
let current_regs_offset = self.current_regs_offset;
eprintln!("IREPs:");
self.current_irep
.code
.iter()
.enumerate()
.for_each(|(i, op)| {
eprintln!("{:04} {:?}: {:?}", i, op.code, op.operand);
});
eprintln!("Current Regs Offset: {}", current_regs_offset);
eprintln!("Regs:");
let size = self.regs.len();
for i in 0..size {
let reg = self.regs.get(i).unwrap().clone();
if let Some(obj) = reg {
let inspect: String = mrb_call_inspect(self, obj.clone())
.unwrap()
.as_ref()
.try_into()
.unwrap_or_else(|_| "(uninspectable)".into());
if i < current_regs_offset {
eprintln!(" R{}(--): {}(oid={})", i, inspect, obj.object_id.get());
} else {
eprintln!(
" R{}(R{}): {}(oid={})",
i,
i - current_regs_offset,
inspect,
obj.object_id.get()
);
}
} else if i < 16 || i < current_regs_offset {
eprintln!(" R{}(--): <None>", i);
} else {
break;
}
}
eprintln!("Target Class: {}", self.target_class.name());
eprintln!(
"Exception: {:?}",
self.exception
.as_deref()
.map(|e| e.error_type.borrow().clone())
);
eprintln!("--- Breadcrumb ---");
if let Some(bc) = &self.current_breadcrumb {
bc.display_breadcrumb_for_debug(0, max_breadcrumb_level);
} else {
eprintln!("(none)");
}
eprintln!("=== End of VM Dump ===");
}
}
pub fn get_outermost_env(&self) -> Option<Rc<ENV>> {
let mut env = self.upper.clone();
while let Some(e) = env.clone() {
if e.upper.is_none() {
return env;
}
env = e.upper.clone();
}
env
}
}
fn interpret_insn(mut insns: &[u8]) -> Vec<Op> {
let mut pos: usize = 0;
let mut ops = Vec::new();
while !insns.is_empty() {
let op = insns[0];
let opcode: insn::OpCode = op.try_into().unwrap();
let fetched = insn::FETCH_TABLE[op as usize](&mut insns).unwrap();
ops.push(Op::new(opcode, fetched, pos, 1 + fetched.len()));
pos += 1 + fetched.len();
}
ops
}
fn load_irep_1(reps: &mut [Irep], pos: usize) -> (IREP, usize) {
let irep = &mut reps[pos];
let mut irep1 = IREP {
__id: pos,
nlocals: irep.nlocals(),
nregs: irep.nregs(),
rlen: irep.rlen(),
code: Vec::new(),
syms: Vec::new(),
pool: Vec::new(),
reps: Vec::new(),
lv: None,
catch_target_pos: Vec::new(),
};
for sym in irep.syms.iter() {
irep1
.syms
.push(RSym::new(sym.to_string_lossy().to_string()));
}
for val in irep.pool.iter() {
match val {
crate::rite::PoolValue::Str(s) | crate::rite::PoolValue::SStr(s) => {
irep1.pool.push(RPool::Str(s.to_string_lossy().to_string()));
}
crate::rite::PoolValue::Int32(i) => {
irep1.pool.push(RPool::Int(*i as i64));
}
crate::rite::PoolValue::Int64(i) => {
irep1.pool.push(RPool::Int(*i));
}
crate::rite::PoolValue::Float(f) => {
irep1.pool.push(RPool::Float(*f));
}
crate::rite::PoolValue::BigInt(_) => {
irep1.pool.push(RPool::Int(0));
}
}
}
let code = interpret_insn(irep.insn);
for ch in irep.catch_handlers.iter() {
let pos = ch.target;
let (i, _) = code
.iter()
.enumerate()
.find(|(_, op)| op.pos == pos)
.expect("catch handler mismatch");
irep1.catch_target_pos.push(i);
}
let mut map = RHashMap::default();
for (reg, name) in irep.lv.iter().enumerate() {
if let Some(name) = name {
map.insert(reg + 1, name.to_string_lossy().to_string());
}
}
if !map.is_empty() {
irep1.lv = Some(map);
}
irep1.catch_target_pos.sort();
irep1.code = code;
(irep1, pos + 1)
}
fn load_irep_0(reps: &mut [Irep], pos: usize) -> (IREP, usize) {
let (mut irep0, newpos) = load_irep_1(reps, pos);
let mut pos = newpos;
for _ in 0..irep0.rlen {
let (rep, newpos) = load_irep_0(reps, pos);
pos = newpos;
irep0.reps.push(Rc::new(rep));
}
(irep0, pos)
}
fn rite_to_irep(rite: &mut Rite) -> IREP {
let (irep0, _) = load_irep_0(&mut rite.irep, 0);
irep0
}
#[derive(Debug, Clone)]
pub struct IREP {
pub __id: usize,
pub nlocals: usize,
pub nregs: usize, pub rlen: usize,
pub code: Vec<Op>,
pub syms: Vec<RSym>,
pub pool: Vec<RPool>,
pub reps: Vec<Rc<IREP>>,
pub lv: Option<RHashMap<usize, String>>,
pub catch_target_pos: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct CALLINFO {
pub prev: Option<Rc<CALLINFO>>,
pub method_id: RSym,
pub pc_irep: Rc<IREP>,
pub pc: usize,
pub current_regs_offset: usize,
pub target_class: TargetContext,
pub n_args: usize,
pub return_reg: usize,
pub method_owner: Option<Rc<RModule>>,
pub has_block: Cell<bool>,
}
#[derive(Debug, Clone)]
pub struct ENV {
pub __irep_id: usize,
pub upper: Option<Rc<ENV>>,
pub captured: RefCell<Option<Vec<Option<Rc<RObject>>>>>,
pub current_regs_offset: usize,
pub is_expired: Cell<bool>,
}
impl ENV {
#[allow(unused)]
pub(crate) fn has_captured(&self) -> bool {
self.captured.borrow().is_some()
}
#[allow(unused)]
pub(crate) fn capture(&self, regs: &[Option<Rc<RObject>>]) {
let mut captured = self.captured.borrow_mut();
captured.replace(regs.to_vec());
}
pub(crate) fn capture_no_clone(&self, regs: Vec<Option<Rc<RObject>>>) {
let mut captured = self.captured.borrow_mut();
captured.replace(regs);
}
pub(crate) fn expire(&self) {
self.is_expired.set(true);
}
pub(crate) fn expired(&self) -> bool {
self.is_expired.get()
}
}