mirror of https://github.com/boa-dev/boa.git
Browse Source
Nodes implement CodeGen which generates instructions onto a stack held in Context. The VM will interpret the instructions from Context. There are some issues: - Only basic instructions are added, but I'm working off https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Internals/Bytecode for now it should be easy to add more in. - The Stack is a Vec, this isn't ideal (we may be able to live with it for now) but the stack should really be a fixed sized array. This isn't possible because Value can't be copied in there as it holds Rc and Gc values. Can we have fixed-sized Values that hold a pointer? Something like the "stackvec" crate should help - put all VM related code behind "vm" feature flag Co-authored-by: Jason Williams <jwilliams720@bloomberg.net> Co-authored-by: Halid Odat <halidodat@gmail.com>pull/1031/head
Jason Williams
4 years ago
committed by
GitHub
12 changed files with 626 additions and 5 deletions
@ -0,0 +1,64 @@
|
||||
use super::*; |
||||
use crate::{syntax::ast::Const, syntax::ast::Node, value::RcBigInt, value::RcString}; |
||||
|
||||
#[derive(Debug, Default)] |
||||
pub struct Compiler { |
||||
pub(super) instructions: Vec<Instruction>, |
||||
pub(super) pool: Vec<Value>, |
||||
} |
||||
|
||||
impl Compiler { |
||||
// Add a new instruction.
|
||||
pub fn add_instruction(&mut self, instr: Instruction) { |
||||
self.instructions.push(instr); |
||||
} |
||||
|
||||
pub fn add_string_instruction<S>(&mut self, string: S) |
||||
where |
||||
S: Into<RcString>, |
||||
{ |
||||
let index = self.pool.len(); |
||||
self.add_instruction(Instruction::String(index)); |
||||
self.pool.push(string.into().into()); |
||||
} |
||||
|
||||
pub fn add_bigint_instruction<B>(&mut self, bigint: B) |
||||
where |
||||
B: Into<RcBigInt>, |
||||
{ |
||||
let index = self.pool.len(); |
||||
self.add_instruction(Instruction::BigInt(index)); |
||||
self.pool.push(bigint.into().into()); |
||||
} |
||||
} |
||||
|
||||
pub(crate) trait CodeGen { |
||||
fn compile(&self, compiler: &mut Compiler); |
||||
} |
||||
|
||||
impl CodeGen for Node { |
||||
fn compile(&self, compiler: &mut Compiler) { |
||||
let _timer = BoaProfiler::global().start_event(&format!("Node ({})", &self), "codeGen"); |
||||
match *self { |
||||
Node::Const(Const::Undefined) => compiler.add_instruction(Instruction::Undefined), |
||||
Node::Const(Const::Null) => compiler.add_instruction(Instruction::Null), |
||||
Node::Const(Const::Bool(true)) => compiler.add_instruction(Instruction::True), |
||||
Node::Const(Const::Bool(false)) => compiler.add_instruction(Instruction::False), |
||||
Node::Const(Const::Num(num)) => compiler.add_instruction(Instruction::Rational(num)), |
||||
Node::Const(Const::Int(num)) => match num { |
||||
0 => compiler.add_instruction(Instruction::Zero), |
||||
1 => compiler.add_instruction(Instruction::One), |
||||
_ => compiler.add_instruction(Instruction::Int32(num)), |
||||
}, |
||||
Node::Const(Const::String(ref string)) => { |
||||
compiler.add_string_instruction(string.clone()) |
||||
} |
||||
Node::Const(Const::BigInt(ref bigint)) => { |
||||
compiler.add_bigint_instruction(bigint.clone()) |
||||
} |
||||
Node::BinOp(ref op) => op.compile(compiler), |
||||
Node::UnaryOp(ref op) => op.compile(compiler), |
||||
_ => unimplemented!(), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,105 @@
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub enum Instruction { |
||||
Undefined, |
||||
Null, |
||||
True, |
||||
False, |
||||
Zero, |
||||
One, |
||||
String(usize), |
||||
BigInt(usize), |
||||
|
||||
/// Loads an i32 onto the stack
|
||||
Int32(i32), |
||||
|
||||
/// Loads an f32 onto the stack
|
||||
Rational(f64), |
||||
|
||||
/// Adds the values from destination and source and stores the result in destination
|
||||
Add, |
||||
|
||||
/// subtracts the values from destination and source and stores the result in destination
|
||||
Sub, |
||||
|
||||
/// Multiplies the values from destination and source and stores the result in destination
|
||||
Mul, |
||||
|
||||
/// Divides the values from destination and source and stores the result in destination
|
||||
Div, |
||||
|
||||
Pow, |
||||
|
||||
Mod, |
||||
|
||||
BitAnd, |
||||
BitOr, |
||||
BitXor, |
||||
Shl, |
||||
Shr, |
||||
UShr, |
||||
|
||||
Eq, |
||||
NotEq, |
||||
StrictEq, |
||||
StrictNotEq, |
||||
|
||||
Gt, |
||||
Ge, |
||||
Lt, |
||||
Le, |
||||
|
||||
In, |
||||
InstanceOf, |
||||
|
||||
Void, |
||||
TypeOf, |
||||
Pos, |
||||
Neg, |
||||
BitNot, |
||||
Not, |
||||
} |
||||
|
||||
impl std::fmt::Display for Instruction { |
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||
match *self { |
||||
Self::Undefined => write!(f, "Undefined"), |
||||
Self::Null => write!(f, "Null"), |
||||
Self::True => write!(f, "True"), |
||||
Self::False => write!(f, "False"), |
||||
Self::Zero => write!(f, "Zero"), |
||||
Self::One => write!(f, "One"), |
||||
Self::String(usize) => write!(f, "String({})", usize), |
||||
Self::BigInt(usize) => write!(f, "BigInt({})", usize), |
||||
Self::Int32(i32) => write!(f, "Int32({})", i32), |
||||
Self::Rational(f64) => write!(f, "Rational({})", f64), |
||||
Self::Add => write!(f, "Add"), |
||||
Self::Sub => write!(f, "Sub"), |
||||
Self::Mul => write!(f, "Mul"), |
||||
Self::Div => write!(f, "Div"), |
||||
Self::Pow => write!(f, "Pow"), |
||||
Self::Mod => write!(f, "Mod"), |
||||
Self::BitAnd => write!(f, "BitAnd"), |
||||
Self::BitOr => write!(f, "BitOr"), |
||||
Self::BitXor => write!(f, "BitXor"), |
||||
Self::Shl => write!(f, "Shl"), |
||||
Self::Shr => write!(f, "Shr"), |
||||
Self::UShr => write!(f, "UShr"), |
||||
Self::Eq => write!(f, "Eq"), |
||||
Self::NotEq => write!(f, "NotEq"), |
||||
Self::StrictEq => write!(f, "StrictEq"), |
||||
Self::StrictNotEq => write!(f, "StrictNotEq"), |
||||
Self::Gt => write!(f, "Gt"), |
||||
Self::Ge => write!(f, "Ge"), |
||||
Self::Lt => write!(f, "Lt"), |
||||
Self::Le => write!(f, "Le"), |
||||
Self::In => write!(f, "In"), |
||||
Self::InstanceOf => write!(f, "InstanceOf"), |
||||
Self::Void => write!(f, "Void"), |
||||
Self::TypeOf => write!(f, "TypeOf"), |
||||
Self::Pos => write!(f, "Pos"), |
||||
Self::Neg => write!(f, "Neg"), |
||||
Self::BitNot => write!(f, "BitNot"), |
||||
Self::Not => write!(f, "Not"), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,279 @@
|
||||
use crate::{Context, Result, Value}; |
||||
|
||||
pub(crate) mod compilation; |
||||
pub(crate) mod instructions; |
||||
|
||||
use crate::BoaProfiler; |
||||
pub use compilation::Compiler; |
||||
pub use instructions::Instruction; |
||||
|
||||
// Virtual Machine.
|
||||
#[derive(Debug)] |
||||
pub struct VM<'a> { |
||||
ctx: &'a mut Context, |
||||
instructions: Vec<Instruction>, |
||||
pool: Vec<Value>, |
||||
stack: Vec<Value>, |
||||
stack_pointer: usize, |
||||
} |
||||
|
||||
impl<'a> VM<'a> { |
||||
pub fn new(compiler: Compiler, ctx: &'a mut Context) -> Self { |
||||
Self { |
||||
ctx, |
||||
instructions: compiler.instructions, |
||||
pool: compiler.pool, |
||||
stack: vec![], |
||||
stack_pointer: 0, |
||||
} |
||||
} |
||||
|
||||
/// 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<Value> { |
||||
let _timer = BoaProfiler::global().start_event("runVM", "vm"); |
||||
let mut idx = 0; |
||||
|
||||
while idx < self.instructions.len() { |
||||
let _timer = |
||||
BoaProfiler::global().start_event(&self.instructions[idx].to_string(), "vm"); |
||||
match self.instructions[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()); |
||||
} |
||||
} |
||||
|
||||
idx += 1; |
||||
} |
||||
|
||||
let res = self.pop(); |
||||
Ok(res) |
||||
} |
||||
} |
Loading…
Reference in new issue