diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 764338d180..02927946f5 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -96,6 +96,28 @@ jobs: # args: --ignore-tests # - name: Upload to codecov.io # uses: codecov/codecov-action@v1 + test_vm_on_linux: + name: Test VM on Linux + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.3.4 + - uses: actions-rs/toolchain@v1.0.7 + with: + toolchain: stable + override: true + profile: minimal + - name: Cache cargo + uses: actions/cache@v2.1.6 + with: + path: | + target + ~/.cargo/git + ~/.cargo/registry + key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }} + - uses: actions-rs/cargo@v1 + with: + command: test + args: ---package Boa --lib --features=vm -- vm --nocapture test_on_windows: name: Test Suite on Windows diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fc6db8b51a..075f5c8a7e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -59,6 +59,30 @@ }, "problemMatcher": [] }, + { + "type": "process", + "label": "Cargo Run (Profiler & VM)", + "command": "cargo", + "args": [ + "run", + "--features", + "Boa/profiler", + "--features", + "vm", + "../tests/js/test.js" + ], + "group": "build", + "options": { + "env": { + "RUST_BACKTRACE": "full" + }, + "cwd": "${workspaceFolder}/boa_cli" + }, + "presentation": { + "clear": true + }, + "problemMatcher": [] + }, { "type": "process", "label": "Get Tokens", diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 737de76461..3feebb5c19 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -14,6 +14,8 @@ pub trait Executable { pub(crate) enum InterpreterState { Executing, Return, + #[cfg(feature = "vm")] + Error, Break(Option>), Continue(Option>), } diff --git a/boa/src/syntax/ast/node/block/mod.rs b/boa/src/syntax/ast/node/block/mod.rs index 39c19f88e1..25abdc3369 100644 --- a/boa/src/syntax/ast/node/block/mod.rs +++ b/boa/src/syntax/ast/node/block/mod.rs @@ -87,6 +87,8 @@ impl Executable for Block { InterpreterState::Executing => { // Continue execution } + #[cfg(feature = "vm")] + InterpreterState::Error => {} } } diff --git a/boa/src/syntax/ast/node/identifier/mod.rs b/boa/src/syntax/ast/node/identifier/mod.rs index ca595d460e..d5896c1977 100644 --- a/boa/src/syntax/ast/node/identifier/mod.rs +++ b/boa/src/syntax/ast/node/identifier/mod.rs @@ -4,13 +4,16 @@ use crate::{ exec::Executable, gc::{Finalize, Trace}, syntax::ast::node::Node, - Context, Result, Value, + BoaProfiler, Context, Result, Value, }; use std::fmt; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "vm")] +use crate::vm::{compilation::CodeGen, Compiler, Instruction}; + /// An `identifier` is a sequence of characters in the code that identifies a variable, /// function, or property. /// @@ -36,10 +39,19 @@ pub struct Identifier { impl Executable for Identifier { fn run(&self, context: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("Identifier", "exec"); context.get_binding_value(self.as_ref()) } } +#[cfg(feature = "vm")] +impl CodeGen for Identifier { + fn compile(&self, compiler: &mut Compiler) { + let _timer = BoaProfiler::global().start_event("Identifier", "codeGen"); + compiler.add_instruction(Instruction::GetName(String::from(self.ident.as_ref()))); + } +} + impl fmt::Display for Identifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.ident, f) diff --git a/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs b/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs index 19a828d658..a5d8fcd01e 100644 --- a/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs @@ -89,6 +89,8 @@ impl Executable for DoWhileLoop { InterpreterState::Executing => { // Continue execution. } + #[cfg(feature = "vm")] + InterpreterState::Error => {} } if !self.cond().run(context)?.to_boolean() { break; diff --git a/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs index 1b2e53988b..628c60bd89 100644 --- a/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs @@ -205,6 +205,8 @@ impl Executable for ForInLoop { InterpreterState::Executing => { // Continue execution. } + #[cfg(feature = "vm")] + InterpreterState::Error => {} } let _ = context.pop_environment(); } diff --git a/boa/src/syntax/ast/node/iteration/for_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_loop/mod.rs index dd69027354..f33c14e137 100644 --- a/boa/src/syntax/ast/node/iteration/for_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_loop/mod.rs @@ -133,6 +133,8 @@ impl Executable for ForLoop { InterpreterState::Executing => { // Continue execution. } + #[cfg(feature = "vm")] + InterpreterState::Error => {} } if let Some(final_expr) = self.final_expr() { diff --git a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs index 9db251e91a..bbdc954726 100644 --- a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs @@ -196,6 +196,8 @@ impl Executable for ForOfLoop { InterpreterState::Executing => { // Continue execution. } + #[cfg(feature = "vm")] + InterpreterState::Error => {} } let _ = context.pop_environment(); } diff --git a/boa/src/syntax/ast/node/iteration/while_loop/mod.rs b/boa/src/syntax/ast/node/iteration/while_loop/mod.rs index bc4c3d23bd..4323a8b7b6 100644 --- a/boa/src/syntax/ast/node/iteration/while_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/while_loop/mod.rs @@ -87,6 +87,8 @@ impl Executable for WhileLoop { InterpreterState::Executing => { // Continue execution. } + #[cfg(feature = "vm")] + InterpreterState::Error => {} } } Ok(result) diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index 4919c93775..8a4fcc3724 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -5,13 +5,16 @@ use crate::{ gc::{Finalize, Trace}, property::{AccessorDescriptor, Attribute, DataDescriptor, PropertyDescriptor}, syntax::ast::node::{MethodDefinitionKind, Node, PropertyDefinition}, - Context, Result, Value, + BoaProfiler, Context, Result, Value, }; use std::fmt; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "vm")] +use crate::vm::{compilation::CodeGen, Compiler, Instruction}; + /// Objects in JavaScript may be defined as an unordered collection of related data, of /// primitive or reference types, in the form of “key: value” pairs. /// @@ -71,8 +74,23 @@ impl Object { } } +#[cfg(feature = "vm")] +impl CodeGen for Object { + fn compile(&self, compiler: &mut Compiler) { + let _timer = BoaProfiler::global().start_event("object", "codeGen"); + // Is it a new empty object? + if self.properties.len() == 0 { + compiler.add_instruction(Instruction::NewObject); + return; + } + + unimplemented!() + } +} + impl Executable for Object { fn run(&self, context: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("object", "exec"); let obj = Value::new_object(context); // TODO: Implement the rest of the property types. diff --git a/boa/src/syntax/ast/node/statement_list/mod.rs b/boa/src/syntax/ast/node/statement_list/mod.rs index 871c29e316..9f7b4eafbd 100644 --- a/boa/src/syntax/ast/node/statement_list/mod.rs +++ b/boa/src/syntax/ast/node/statement_list/mod.rs @@ -123,6 +123,8 @@ impl Executable for StatementList { InterpreterState::Executing => { // Continue execution } + #[cfg(feature = "vm")] + InterpreterState::Error => {} } if i + 1 == self.items().len() { obj = val; diff --git a/boa/src/syntax/ast/node/switch/mod.rs b/boa/src/syntax/ast/node/switch/mod.rs index f62de5ae10..41ba6a372f 100644 --- a/boa/src/syntax/ast/node/switch/mod.rs +++ b/boa/src/syntax/ast/node/switch/mod.rs @@ -161,6 +161,8 @@ impl Executable for Switch { // Continuing execution / falling through to next case statement(s). fall_through = true; } + #[cfg(feature = "vm")] + InterpreterState::Error => {} } } } diff --git a/boa/src/vm/compilation.rs b/boa/src/vm/compilation.rs index 60ce1004df..45ed6d3ced 100644 --- a/boa/src/vm/compilation.rs +++ b/boa/src/vm/compilation.rs @@ -103,6 +103,9 @@ impl CodeGen for Node { }; } } + Node::Identifier(ref name) => name.compile(compiler), + Node::Object(ref obj) => obj.compile(compiler), + _ => unimplemented!(), } } diff --git a/boa/src/vm/instructions.rs b/boa/src/vm/instructions.rs index 0b40a2334c..34d1f753ff 100644 --- a/boa/src/vm/instructions.rs +++ b/boa/src/vm/instructions.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone, Copy)] +#[derive(Debug)] pub enum Instruction { Undefined, Null, @@ -66,6 +66,13 @@ pub enum Instruction { DefConst(usize), /// The usize is the index of the value to initiate the variable with in the pool InitLexical(usize), + + // Binding values + /// Find a binding on the environment chain and push its value. + GetName(String), + // Objects + // Create and push a new object onto the stack + NewObject, } impl std::fmt::Display for Instruction { @@ -90,6 +97,7 @@ impl std::fmt::Display for Instruction { Self::BitAnd => write!(f, "BitAnd"), Self::BitOr => write!(f, "BitOr"), Self::BitXor => write!(f, "BitXor"), + Self::GetName(ref name) => write!(f, "GetName({})", name), Self::Shl => write!(f, "Shl"), Self::Shr => write!(f, "Shr"), Self::UShr => write!(f, "UShr"), @@ -113,6 +121,7 @@ impl std::fmt::Display for Instruction { Self::DefLet(name) => write!(f, "DefLet({})", name), Self::DefConst(name) => write!(f, "DefConst({})", name), Self::InitLexical(value) => write!(f, "InitLexical({})", value), + Self::NewObject => write!(f, "NewObject"), } } } diff --git a/boa/src/vm/mod.rs b/boa/src/vm/mod.rs index 84534acd02..2e714aa285 100644 --- a/boa/src/vm/mod.rs +++ b/boa/src/vm/mod.rs @@ -1,7 +1,10 @@ //! 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}; +use crate::{ + environment::lexical_environment::VariableScope, exec::InterpreterState, BoaProfiler, Context, + Result, Value, +}; pub(crate) mod compilation; pub(crate) mod instructions; @@ -31,6 +34,9 @@ struct Profiler { start_flag: bool, } +#[cfg(test)] +mod tests; + impl<'a> VM<'a> { pub fn new(compiler: Compiler, ctx: &'a mut Context) -> Self { let trace = ctx.trace; @@ -227,53 +233,69 @@ impl<'a> VM<'a> { 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))?; + self.ctx.create_mutable_binding( + name.to_string(), + false, + VariableScope::Function, + )?; None } 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))?; + self.ctx.create_mutable_binding( + name.to_string(), + false, + VariableScope::Block, + )?; None } 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))?; + self.ctx.create_immutable_binding( + name.to_string(), + false, + VariableScope::Block, + )?; None } 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.ctx.initialize_binding(&name, value.clone())?; - Some(value) + None } + // Find a binding on the environment chain and push its value. + Instruction::GetName(ref name) => match self.ctx.get_binding_value(&name) { + Ok(val) => Some(val), + Err(val) => { + self.ctx + .executor() + .set_current_state(InterpreterState::Error); + Some(val) + } + }, + // Create a new object and push to the stack + Instruction::NewObject => Some(Value::new_object(self.ctx)), }; + if let Some(value) = result { self.push(value); } self.idx += 1; + + if matches!( + self.ctx.executor().get_current_state(), + &InterpreterState::Error, + ) { + break; + } } if self.is_trace { diff --git a/boa/src/vm/tests.rs b/boa/src/vm/tests.rs new file mode 100644 index 0000000000..9deb58dd19 --- /dev/null +++ b/boa/src/vm/tests.rs @@ -0,0 +1,29 @@ +use crate::exec; + +#[test] +fn typeof_string() { + let typeof_object = r#" + const a = "hello"; + typeof a; + "#; + assert_eq!(&exec(typeof_object), "\"string\""); +} + +#[test] +fn typeof_number() { + let typeof_number = r#" + let a = 1234; + typeof a; + "#; + assert_eq!(&exec(typeof_number), "\"number\""); +} + +#[test] +fn basic_op() { + let basic_op = r#" + const a = 1; + const b = 2; + a + b + "#; + assert_eq!(&exec(basic_op), "3"); +}