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