Skip to content

Commit

Permalink
Bytecode Interpreter (new branch) (#860)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
3 people authored Jan 2, 2021
1 parent 14ef50c commit 1052ccd
Show file tree
Hide file tree
Showing 12 changed files with 626 additions and 5 deletions.
25 changes: 23 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,35 @@
"kind": "build",
"isDefault": true
},
"presentation": {
"clear": true
},
"options": {
"env": {
"RUST_BACKTRACE": "full"
"RUST_BACKTRACE": "1"
}
},
"problemMatcher": []
},
{
"type": "process",
"label": "Cargo Run (VM)",
"command": "cargo",
"args": ["run", "--features", "vm", "../tests/js/test.js"],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"clear": true
}
},
"options": {
"cwd": "${workspaceFolder}/boa_cli",
"env": {
"RUST_BACKTRACE": "1"
}
},
"problemMatcher": []
},
{
"type": "process",
Expand Down
3 changes: 3 additions & 0 deletions boa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ edition = "2018"
profiler = ["measureme", "once_cell"]
deser = []

# Enable Bytecode generation & execution instead of tree walking
vm = []

# Enable Boa's WHATWG console object implementation.
console = []

Expand Down
51 changes: 50 additions & 1 deletion boa/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ use std::result::Result as StdResult;
#[cfg(feature = "console")]
use crate::builtins::console::Console;

#[cfg(feature = "vm")]
use crate::vm::{
compilation::{CodeGen, Compiler},
VM,
};

/// Store a builtin constructor (such as `Object`) and its corresponding prototype.
#[derive(Debug, Clone)]
pub struct StandardConstructor {
Expand Down Expand Up @@ -225,7 +231,7 @@ pub struct Context {
/// Cached iterator prototypes.
iterator_prototypes: IteratorPrototypes,

/// Cached standard objects and their prototypes
/// Cached standard objects and their prototypes.
standard_objects: StandardObjects,
}

Expand Down Expand Up @@ -700,6 +706,7 @@ impl Context {
/// assert!(value.is_number());
/// assert_eq!(value.as_number().unwrap(), 4.0);
/// ```
#[cfg(not(feature = "vm"))]
#[allow(clippy::unit_arg, clippy::drop_copy)]
#[inline]
pub fn eval<T: AsRef<[u8]>>(&mut self, src: T) -> Result<Value> {
Expand All @@ -722,6 +729,48 @@ impl Context {
execution_result
}

/// Evaluates the given code by compiling down to bytecode, then interpreting the bytecode into a value
///
/// # Examples
/// ```
///# use boa::Context;
/// let mut context = Context::new();
///
/// let value = context.eval("1 + 3").unwrap();
///
/// assert!(value.is_number());
/// assert_eq!(value.as_number().unwrap(), 4.0);
/// ```
#[cfg(feature = "vm")]
#[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval<T: AsRef<[u8]>>(&mut self, src: T) -> Result<Value> {
let main_timer = BoaProfiler::global().start_event("Main", "Main");
let src_bytes: &[u8] = src.as_ref();

let parsing_result = Parser::new(src_bytes, false)
.parse_all()
.map_err(|e| e.to_string());

let statement_list = match parsing_result {
Ok(statement_list) => statement_list,
Err(e) => return self.throw_syntax_error(e),
};

let mut compiler = Compiler::default();
statement_list.compile(&mut compiler);
dbg!(&compiler);

let mut vm = VM::new(compiler, self);
// Generate Bytecode and place it into instruction_stack
// Interpret the Bytecode
let result = vm.run();
// The main_timer needs to be dropped before the BoaProfiler is.
drop(main_timer);
BoaProfiler::global().drop();

result
}

/// Returns a structure that contains the JavaScript well known symbols.
///
/// # Examples
Expand Down
2 changes: 2 additions & 0 deletions boa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub mod property;
pub mod realm;
pub mod syntax;
pub mod value;
#[cfg(feature = "vm")]
pub mod vm;

pub mod context;

Expand Down
2 changes: 0 additions & 2 deletions boa/src/profiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ use std::{
thread::{current, ThreadId},
};

#[cfg(feature = "profiler")]
type SerializationSink = measureme::SerializationSink;
#[cfg(feature = "profiler")]
pub struct BoaProfiler {
profiler: Profiler,
Expand Down
56 changes: 56 additions & 0 deletions boa/src/syntax/ast/node/operator/bin_op/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ use std::fmt;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};

#[cfg(feature = "vm")]
use crate::{
profiler::BoaProfiler,
vm::{compilation::CodeGen, Compiler, Instruction},
};

/// Binary operators requires two operands, one before the operator and one after the operator.
///
/// More information:
Expand Down Expand Up @@ -230,6 +236,56 @@ impl Executable for BinOp {
}
}

#[cfg(feature = "vm")]
impl CodeGen for BinOp {
fn compile(&self, compiler: &mut Compiler) {
let _timer = BoaProfiler::global().start_event("binOp", "codeGen");
match self.op() {
op::BinOp::Num(op) => {
self.lhs().compile(compiler);
self.rhs().compile(compiler);
match op {
NumOp::Add => compiler.add_instruction(Instruction::Add),
NumOp::Sub => compiler.add_instruction(Instruction::Sub),
NumOp::Mul => compiler.add_instruction(Instruction::Mul),
NumOp::Div => compiler.add_instruction(Instruction::Div),
NumOp::Exp => compiler.add_instruction(Instruction::Pow),
NumOp::Mod => compiler.add_instruction(Instruction::Mod),
}
}
op::BinOp::Bit(op) => {
self.lhs().compile(compiler);
self.rhs().compile(compiler);
match op {
BitOp::And => compiler.add_instruction(Instruction::BitAnd),
BitOp::Or => compiler.add_instruction(Instruction::BitOr),
BitOp::Xor => compiler.add_instruction(Instruction::BitXor),
BitOp::Shl => compiler.add_instruction(Instruction::Shl),
BitOp::Shr => compiler.add_instruction(Instruction::Shr),
BitOp::UShr => compiler.add_instruction(Instruction::UShr),
}
}
op::BinOp::Comp(op) => {
self.lhs().compile(compiler);
self.rhs().compile(compiler);
match op {
CompOp::Equal => compiler.add_instruction(Instruction::Eq),
CompOp::NotEqual => compiler.add_instruction(Instruction::NotEq),
CompOp::StrictEqual => compiler.add_instruction(Instruction::StrictEq),
CompOp::StrictNotEqual => compiler.add_instruction(Instruction::StrictNotEq),
CompOp::GreaterThan => compiler.add_instruction(Instruction::Gt),
CompOp::GreaterThanOrEqual => compiler.add_instruction(Instruction::Ge),
CompOp::LessThan => compiler.add_instruction(Instruction::Lt),
CompOp::LessThanOrEqual => compiler.add_instruction(Instruction::Le),
CompOp::In => compiler.add_instruction(Instruction::In),
CompOp::InstanceOf => compiler.add_instruction(Instruction::InstanceOf),
}
}
_ => unimplemented!(),
}
}
}

impl fmt::Display for BinOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.lhs, self.op, self.rhs)
Expand Down
27 changes: 27 additions & 0 deletions boa/src/syntax/ast/node/operator/unary_op/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ use std::fmt;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};

#[cfg(feature = "vm")]
use crate::{
profiler::BoaProfiler,
vm::{compilation::CodeGen, Compiler, Instruction},
};

/// A unary operation is an operation with only one operand.
///
/// More information:
Expand Down Expand Up @@ -128,3 +134,24 @@ impl From<UnaryOp> for Node {
Self::UnaryOp(op)
}
}

#[cfg(feature = "vm")]
impl CodeGen for UnaryOp {
fn compile(&self, compiler: &mut Compiler) {
let _timer = BoaProfiler::global().start_event("UnaryOp", "codeGen");
self.target().compile(compiler);
match self.op {
op::UnaryOp::Void => compiler.add_instruction(Instruction::Void),
op::UnaryOp::Plus => compiler.add_instruction(Instruction::Pos),
op::UnaryOp::Minus => compiler.add_instruction(Instruction::Neg),
op::UnaryOp::TypeOf => compiler.add_instruction(Instruction::TypeOf),
op::UnaryOp::Not => compiler.add_instruction(Instruction::Not),
op::UnaryOp::Tilde => compiler.add_instruction(Instruction::BitNot),
op::UnaryOp::IncrementPost => {}
op::UnaryOp::IncrementPre => {}
op::UnaryOp::DecrementPost => {}
op::UnaryOp::DecrementPre => {}
op::UnaryOp::Delete => {}
}
}
}
14 changes: 14 additions & 0 deletions boa/src/syntax/ast/node/statement_list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use std::{fmt, ops::Deref, rc::Rc};
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};

#[cfg(feature = "vm")]
use crate::vm::{compilation::CodeGen, Compiler};

/// List of statements.
///
/// Similar to `Node::Block` but without the braces.
Expand Down Expand Up @@ -92,6 +95,17 @@ impl Executable for StatementList {
}
}

#[cfg(feature = "vm")]
impl CodeGen for StatementList {
fn compile(&self, compiler: &mut Compiler) {
let _timer = BoaProfiler::global().start_event("StatementList - Code Gen", "codeGen");

for item in self.items().iter() {
item.compile(compiler);
}
}
}

impl<T> From<T> for StatementList
where
T: Into<Box<[Node]>>,
Expand Down
64 changes: 64 additions & 0 deletions boa/src/vm/compilation.rs
Original file line number Diff line number Diff line change
@@ -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!(),
}
}
}
Loading

0 comments on commit 1052ccd

Please sign in to comment.