//! The Virtual Machine (VM) handles generating instructions, then executing them. //! This module will provide an instruction set for the AST to use, various traits, plus an interpreter to execute those instructions use crate::{environment::lexical_environment::VariableScope, BoaProfiler, Context, Result, Value}; pub(crate) mod compilation; pub(crate) mod instructions; pub use compilation::Compiler; pub use instructions::Instruction; use std::time::{Duration, Instant}; /// Virtual Machine. #[derive(Debug)] pub struct VM<'a> { ctx: &'a mut Context, idx: usize, instructions: Vec, pool: Vec, stack: Vec, stack_pointer: usize, profile: Profiler, is_trace: bool, } /// This profiler is used to output trace information when `--trace` is provided by the CLI or trace is set to `true` on the [`VM`] object #[derive(Debug)] struct Profiler { instant: Instant, prev_time: Duration, trace_string: String, start_flag: bool, } impl<'a> VM<'a> { pub fn new(compiler: Compiler, ctx: &'a mut Context) -> Self { let trace = ctx.trace; Self { ctx, idx: 0, instructions: compiler.instructions, pool: compiler.pool, stack: vec![], stack_pointer: 0, is_trace: trace, profile: Profiler { instant: Instant::now(), prev_time: Duration::from_secs(0), trace_string: String::new(), // Won't allocate if we don't use trace start_flag: false, }, } } /// Push a value on the stack. #[inline] pub fn push(&mut self, value: Value) { self.stack.push(value); } /// Pop a value off the stack. /// /// # Panics /// /// If there is nothing to pop, then this will panic. #[inline] pub fn pop(&mut self) -> Value { self.stack.pop().unwrap() } pub fn run(&mut self) -> Result { let _timer = BoaProfiler::global().start_event("runVM", "vm"); self.idx = 0; while self.idx < self.instructions.len() { if self.is_trace { self.trace_print(false); }; let _timer = BoaProfiler::global().start_event(&self.instructions[self.idx].to_string(), "vm"); match self.instructions[self.idx] { Instruction::Undefined => self.push(Value::undefined()), Instruction::Null => self.push(Value::null()), Instruction::True => self.push(Value::boolean(true)), Instruction::False => self.push(Value::boolean(false)), Instruction::Zero => self.push(Value::integer(0)), Instruction::One => self.push(Value::integer(1)), Instruction::Int32(i) => self.push(Value::integer(i)), Instruction::Rational(r) => self.push(Value::rational(r)), Instruction::String(index) => { let value = self.pool[index].clone(); self.push(value) } Instruction::BigInt(index) => { let value = self.pool[index].clone(); self.push(value) } Instruction::Add => { let r = self.pop(); let l = self.pop(); let val = l.add(&r, self.ctx)?; self.push(val); } Instruction::Sub => { let r = self.pop(); let l = self.pop(); let val = l.sub(&r, self.ctx)?; self.push(val); } Instruction::Mul => { let r = self.pop(); let l = self.pop(); let val = l.mul(&r, self.ctx)?; self.push(val); } Instruction::Div => { let r = self.pop(); let l = self.pop(); let val = l.div(&r, self.ctx)?; self.push(val); } Instruction::Pow => { let r = self.pop(); let l = self.pop(); let val = l.pow(&r, self.ctx)?; self.push(val); } Instruction::Mod => { let r = self.pop(); let l = self.pop(); let val = l.rem(&r, self.ctx)?; self.push(val); } Instruction::BitAnd => { let r = self.pop(); let l = self.pop(); let val = l.bitand(&r, self.ctx)?; self.push(val); } Instruction::BitOr => { let r = self.pop(); let l = self.pop(); let val = l.bitor(&r, self.ctx)?; self.push(val); } Instruction::BitXor => { let r = self.pop(); let l = self.pop(); let val = l.bitxor(&r, self.ctx)?; self.push(val); } Instruction::Shl => { let r = self.pop(); let l = self.pop(); let val = l.shl(&r, self.ctx)?; self.push(val); } Instruction::Shr => { let r = self.pop(); let l = self.pop(); let val = l.shr(&r, self.ctx)?; self.push(val); } Instruction::UShr => { let r = self.pop(); let l = self.pop(); let val = l.ushr(&r, self.ctx)?; self.push(val); } Instruction::Eq => { let r = self.pop(); let l = self.pop(); let val = l.equals(&r, self.ctx)?; self.push(val.into()); } Instruction::NotEq => { let r = self.pop(); let l = self.pop(); let val = !l.equals(&r, self.ctx)?; self.push(val.into()); } Instruction::StrictEq => { let r = self.pop(); let l = self.pop(); let val = l.strict_equals(&r); self.push(val.into()); } Instruction::StrictNotEq => { let r = self.pop(); let l = self.pop(); let val = !l.strict_equals(&r); self.push(val.into()); } Instruction::Gt => { let r = self.pop(); let l = self.pop(); let val = l.ge(&r, self.ctx)?; self.push(val.into()); } Instruction::Ge => { let r = self.pop(); let l = self.pop(); let val = l.ge(&r, self.ctx)?; self.push(val.into()); } Instruction::Lt => { let r = self.pop(); let l = self.pop(); let val = l.lt(&r, self.ctx)?; self.push(val.into()); } Instruction::Le => { let r = self.pop(); let l = self.pop(); let val = l.le(&r, self.ctx)?; self.push(val.into()); } Instruction::In => { let r = self.pop(); let l = self.pop(); if !r.is_object() { return self.ctx.throw_type_error(format!( "right-hand side of 'in' should be an object, got {}", r.get_type().as_str() )); } let key = l.to_property_key(self.ctx)?; let val = self.ctx.has_property(&r, &key); self.push(val.into()); } Instruction::InstanceOf => { let r = self.pop(); let _l = self.pop(); if !r.is_object() { return self.ctx.throw_type_error(format!( "right-hand side of 'instanceof' should be an object, got {}", r.get_type().as_str() )); } // spec: https://tc39.es/ecma262/#sec-instanceofoperator todo!("instanceof operator") } Instruction::Void => { let _value = self.pop(); self.push(Value::undefined()); } Instruction::TypeOf => { let value = self.pop(); self.push(value.get_type().as_str().into()); } Instruction::Pos => { let value = self.pop(); let value = value.to_number(self.ctx)?; self.push(value.into()); } Instruction::Neg => { let value = self.pop(); self.push(Value::from(!value.to_boolean())); } Instruction::Not => { let value = self.pop(); self.push((!value.to_boolean()).into()); } Instruction::BitNot => { let target = self.pop(); let num = target.to_number(self.ctx)?; let value = if num.is_nan() { -1 } else { // TODO: this is not spec compliant. !(num as i32) }; self.push(value.into()); } Instruction::DefVar(name_index) => { let name: String = self.pool[name_index].to_string(self.ctx)?.to_string(); self.ctx .realm_mut() .environment .create_mutable_binding(name.to_string(), false, VariableScope::Function) .map_err(|e| e.to_error(self.ctx))?; } Instruction::DefLet(name_index) => { let name = self.pool[name_index].to_string(self.ctx)?; self.ctx .realm_mut() .environment .create_mutable_binding(name.to_string(), false, VariableScope::Block) .map_err(|e| e.to_error(self.ctx))?; } Instruction::DefConst(name_index) => { let name = self.pool[name_index].to_string(self.ctx)?; self.ctx .realm_mut() .environment .create_immutable_binding(name.to_string(), false, VariableScope::Block) .map_err(|e| e.to_error(self.ctx))?; } Instruction::InitLexical(name_index) => { let name = self.pool[name_index].to_string(self.ctx)?; let value = self.pop(); self.ctx .realm_mut() .environment .initialize_binding(&name, value.clone()) .map_err(|e| e.to_error(self.ctx))?; self.push(value); } } self.idx += 1; } if self.is_trace { self.trace_print(true); }; let res = self.pop(); Ok(res) } pub fn trace_print(&mut self, end: bool) { if self.profile.start_flag { let duration = self.profile.instant.elapsed() - self.profile.prev_time; if self.is_trace { println!( "{0: <10} {1}", format!("{}μs", duration.as_micros()), self.profile.trace_string ); } } else { let duration = self.profile.instant.elapsed() - self.profile.prev_time; println!("VM start up time: {}μs", duration.as_micros()); println!( "{0: <10} {1: <20} {2: <10}", "Time", "Instr", "Top Of Stack" ); println!(); } self.profile.start_flag = true; if self.is_trace { self.profile.trace_string = format!( "{0:<20} {1}", format!( "{:<20}", self.instructions[if end { self.idx - 1 } else { self.idx }] ), match self.stack.last() { None => "".to_string(), Some(val) => format!("{}\t{:p}", val.display(), val), } ); } if end { println!(); println!("Pool"); for (i, val) in self.pool.iter().enumerate() { println!("{:<10} {:<10} {:p}", i, val.display(), val); } println!(); println!("Stack"); for (i, val) in self.stack.iter().enumerate() { println!("{:<10} {:<10} {:p}", i, val.display(), val); } println!(); } self.profile.prev_time = self.profile.instant.elapsed(); } }