diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7592103972a..87324c151d6 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -12,14 +12,15 @@ "kind": "build", "isDefault": true }, + "presentation": { + "clear": true + }, "options": { "env": { - "RUST_BACKTRACE": "full" + "RUST_BACKTRACE": "1" } }, - "presentation": { - "clear": true - } + "problemMatcher": [] }, { "type": "process", diff --git a/boa/Cargo.toml b/boa/Cargo.toml index 419c7f79d45..2067745bfb4 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -13,6 +13,9 @@ edition = "2018" [features] profiler = ["measureme", "once_cell"] +# Enable Bytecode generation & execution instead of tree walking +vm = [] + # Enable Boa's WHATWG console object implementation. console = [] diff --git a/boa/src/context.rs b/boa/src/context.rs index eba8a1336db..9354ff0bbb4 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -23,6 +23,7 @@ use crate::{ Parser, }, value::{RcString, RcSymbol, Value}, + vm::VM, BoaProfiler, Executable, Result, }; use std::result::Result as StdResult; @@ -30,6 +31,10 @@ use std::result::Result as StdResult; #[cfg(feature = "console")] use crate::builtins::console::Console; +#[cfg(feature = "vm")] +use crate::vm::compilation::CodeGen; +use crate::vm::instructions::Instruction; + /// Store a builtin constructor (such as `Object`) and its corresponding prototype. #[derive(Debug, Clone)] pub struct StandardConstructor { @@ -196,6 +201,10 @@ pub struct Context { /// Cached standard objects and their prototypes standard_objects: StandardObjects, + + /// Holds instructions for ByteCode generation + #[cfg(feature = "vm")] + instruction_stack: Vec, } impl Default for Context { @@ -212,6 +221,8 @@ impl Default for Context { well_known_symbols, iterator_prototypes: IteratorPrototypes::default(), standard_objects: Default::default(), + #[cfg(feature = "vm")] + instruction_stack: vec![], }; // Add new builtIns to Context Realm @@ -237,6 +248,10 @@ impl Context { &mut self.realm } + pub fn instructions_mut(&mut self) -> &mut Vec { + &mut self.instruction_stack + } + pub fn executor(&mut self) -> &mut Interpreter { &mut self.executor } @@ -649,6 +664,38 @@ 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_bytecode("1 + 3").unwrap(); + /// + /// assert!(value.is_number()); + /// assert_eq!(value.as_number().unwrap(), 4.0); + /// ``` + pub fn eval_bytecode(&mut self, src: &str) -> Result { + let main_timer = BoaProfiler::global().start_event("Main", "Main"); + + let parsing_result = Parser::new(src.as_bytes()) + .parse_all() + .map_err(|e| e.to_string()); + + let statement_list = parsing_result.expect("unable to get statementList"); + // Generate Bytecode and place it into instruction_stack + statement_list.compile(self); + // Interpret the Bytecode + let mut vm = VM::new(self); + 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 @@ -676,4 +723,9 @@ impl Context { pub fn standard_objects(&self) -> &StandardObjects { &self.standard_objects } + + // Add a new instruction + pub fn add_instruction(&mut self, instr: Instruction) { + self.instruction_stack.push(instr); + } } diff --git a/boa/src/lib.rs b/boa/src/lib.rs index a302e468c74..9775714eedf 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -53,6 +53,7 @@ pub mod property; pub mod realm; pub mod syntax; pub mod value; +pub mod vm; pub mod context; diff --git a/boa/src/syntax/ast/node/operator/bin_op/mod.rs b/boa/src/syntax/ast/node/operator/bin_op/mod.rs index db8985eec2a..b551656326c 100644 --- a/boa/src/syntax/ast/node/operator/bin_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/bin_op/mod.rs @@ -4,6 +4,8 @@ use crate::{ node::Node, op::{self, AssignOp, BitOp, CompOp, LogOp, NumOp}, }, + vm::compilation::CodeGen, + vm::instructions::Instruction, Context, Result, Value, }; use gc::{Finalize, Trace}; @@ -185,6 +187,22 @@ impl Executable for BinOp { } } +impl CodeGen for BinOp { + fn compile(&self, ctx: &mut Context) { + match self.op() { + op::BinOp::Num(op) => { + self.lhs().compile(ctx); + self.rhs().compile(ctx); + match op { + NumOp::Add => ctx.add_instruction(Instruction::Add), + _ => unimplemented!(), + } + } + _ => unimplemented!(), + } + } +} + impl fmt::Display for BinOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} {} {}", self.lhs, self.op, self.rhs) diff --git a/boa/src/syntax/ast/node/statement_list/mod.rs b/boa/src/syntax/ast/node/statement_list/mod.rs index 4c8ab7cd274..ec6f81d9a28 100644 --- a/boa/src/syntax/ast/node/statement_list/mod.rs +++ b/boa/src/syntax/ast/node/statement_list/mod.rs @@ -3,6 +3,7 @@ use crate::{ exec::{Executable, InterpreterState}, syntax::ast::node::Node, + vm::compilation::CodeGen, BoaProfiler, Context, Result, Value, }; use gc::{unsafe_empty_trace, Finalize, Trace}; @@ -94,6 +95,20 @@ impl Executable for StatementList { } } +impl CodeGen for StatementList { + fn compile(&self, ctx: &mut Context) { + let _timer = BoaProfiler::global().start_event("StatementList", "codeGen"); + + // https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation + // The return value is uninitialized, which means it defaults to Value::Undefined + ctx.executor() + .set_current_state(InterpreterState::Executing); + for (i, item) in self.statements().iter().enumerate() { + item.compile(ctx); + } + } +} + impl From for StatementList where T: Into>, diff --git a/boa/src/vm/compilation.rs b/boa/src/vm/compilation.rs new file mode 100644 index 00000000000..77b059a0f72 --- /dev/null +++ b/boa/src/vm/compilation.rs @@ -0,0 +1,22 @@ +use super::*; +use crate::{syntax::ast::Const, syntax::ast::Node, Context}; + +#[derive(Debug, Default)] +pub(crate) struct Compiler { + res: Vec, + next_free: u8, +} + +pub(crate) trait CodeGen { + fn compile(&self, ctx: &mut Context); +} + +impl CodeGen for Node { + fn compile(&self, ctx: &mut Context) { + match *self { + Node::BinOp(ref op) => op.compile(ctx), + Node::Const(Const::Int(num)) => ctx.add_instruction(Instruction::Int32(num)), + _ => unimplemented!(), + } + } +} diff --git a/boa/src/vm/instructions.rs b/boa/src/vm/instructions.rs new file mode 100644 index 00000000000..1307dbb5eb8 --- /dev/null +++ b/boa/src/vm/instructions.rs @@ -0,0 +1,20 @@ +use std::fmt::{Debug, Error, Formatter}; + +#[derive(Clone, Copy)] +pub enum Instruction { + /// Adds the values from destination and source and stores the result in destination + Add, + + // Loads an i32 onto the stack + Int32(i32), +} + +impl Debug for Instruction { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + match self { + Self::Add => write!(f, "Add"), + Self::Int32(i) => write!(f, "Int32\t{}", format!("{}", i)), + _ => write!(f, "unimplemented"), + } + } +} diff --git a/boa/src/vm/mod.rs b/boa/src/vm/mod.rs new file mode 100644 index 00000000000..98ad59b2f9c --- /dev/null +++ b/boa/src/vm/mod.rs @@ -0,0 +1,50 @@ +use self::instructions::Instruction; +use crate::{Context, Value}; + +pub(crate) mod compilation; +pub(crate) mod instructions; + +// === Execution +#[derive(Debug)] +pub struct VM<'a> { + ctx: &'a mut Context, + instructions: Vec, + stack: Vec, + stack_pointer: usize, +} + +impl<'a> VM<'a> { + pub fn new(ctx: &'a mut Context) -> Self { + let instr = ctx.instructions_mut().clone(); + VM { + ctx, + instructions: instr, + stack: vec![], + stack_pointer: 0, + } + } + + pub fn run(&mut self) -> super::Result { + let mut idx = 0; + + while idx < self.instructions.len() { + match self.instructions[idx] { + Instruction::Int32(i) => self.stack.push(Value::Integer(i)), + Instruction::Add => { + let r = self.stack.pop().unwrap(); + let l = self.stack.pop().unwrap(); + let val = l.add(&r, self.ctx)?; + + self.stack.push(val); + } + + _ => unimplemented!(), + } + + idx += 1; + } + + let res = self.stack.pop().unwrap(); + Ok(res) + } +} diff --git a/boa_cli/Cargo.toml b/boa_cli/Cargo.toml index a238f2338fa..7afd80532b9 100644 --- a/boa_cli/Cargo.toml +++ b/boa_cli/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" default-run = "boa" [dependencies] -Boa = { path = "../boa", features = ["serde", "console"] } +Boa = { path = "../boa", features = ["serde", "console", "vm"] } rustyline = "6.3.0" rustyline-derive = "0.3.1" structopt = "0.3.20" @@ -21,6 +21,9 @@ colored = "2.0.0" regex = "1.4.0" lazy_static = "1.4.0" +# [features] +# vm = ["Boa/vm"] + [target.x86_64-unknown-linux-gnu.dependencies] jemallocator = "0.3.2" diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index 8ca8fb8f7ef..25a20b49c38 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -31,6 +31,9 @@ use rustyline::{config::Config, error::ReadlineError, EditMode, Editor}; use std::{fs::read_to_string, path::PathBuf}; use structopt::{clap::arg_enum, StructOpt}; +#[cfg(feature = "vm")] +use boa::vm::VM; + mod helper; #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] @@ -149,7 +152,7 @@ pub fn main() -> Result<(), std::io::Error> { eprintln!("{}", e); } } else { - match engine.eval(&buffer) { + match engine.eval_bytecode(&buffer) { Ok(v) => println!("{}", v.display()), Err(v) => eprintln!("Uncaught {}", v.display()), }