From 138a530505369a281b09c496e80253b480a985ea Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Mon, 26 Jul 2021 03:12:34 +0200 Subject: [PATCH] Implement functions for vm --- boa/src/builtins/function/mod.rs | 49 +- boa/src/bytecompiler.rs | 169 +++++- boa/src/context.rs | 46 +- boa/src/exec/tests.rs | 2 +- boa/src/object/gcobject.rs | 66 +- .../declaration/arrow_function_decl/mod.rs | 8 +- .../ast/node/declaration/function_decl/mod.rs | 4 +- .../ast/node/declaration/function_expr/mod.rs | 4 +- boa/src/vm/code_block.rs | 418 ++++++++++++- boa/src/vm/mod.rs | 567 ++++++++++-------- boa/src/vm/opcode.rs | 7 + 11 files changed, 991 insertions(+), 349 deletions(-) diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 9ee1b7567e5..1935d0959c5 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -13,7 +13,7 @@ use crate::object::PROTOTYPE; use crate::{ - builtins::{Array, BuiltIn}, + builtins::BuiltIn, environment::lexical_environment::Environment, gc::{custom_trace, empty_trace, Finalize, Trace}, object::{ConstructorBuilder, FunctionBuilder, GcObject, Object, ObjectData}, @@ -21,7 +21,7 @@ use crate::{ syntax::ast::node::{FormalParameter, RcStatementList}, BoaProfiler, Context, JsResult, JsValue, }; -use bitflags::bitflags; + use std::fmt::{self, Debug}; use std::rc::Rc; @@ -53,30 +53,6 @@ impl Debug for BuiltInFunction { } } -bitflags! { - #[derive(Finalize, Default)] - pub struct FunctionFlags: u8 { - const CONSTRUCTABLE = 0b0000_0010; - const LEXICAL_THIS_MODE = 0b0000_0100; - } -} - -impl FunctionFlags { - #[inline] - pub(crate) fn is_constructable(&self) -> bool { - self.contains(Self::CONSTRUCTABLE) - } - - #[inline] - pub(crate) fn is_lexical_this_mode(&self) -> bool { - self.contains(Self::LEXICAL_THIS_MODE) - } -} - -unsafe impl Trace for FunctionFlags { - empty_trace!(); -} - /// Boa representation of a Function Object. /// /// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) @@ -93,11 +69,17 @@ pub enum Function { constructable: bool, }, Ordinary { - flags: FunctionFlags, + constructable: bool, + lexical_this_mode: bool, body: RcStatementList, params: Box<[FormalParameter]>, environment: Environment, }, + #[cfg(feature = "vm")] + VmOrdinary { + code: gc::Gc, + environment: Environment, + }, } impl Debug for Function { @@ -114,20 +96,26 @@ unsafe impl Trace for Function { Function::Ordinary { environment, .. } => { mark(environment); } + #[cfg(feature = "vm")] + Function::VmOrdinary { code, environment } => { + mark(code); + mark(environment); + } } }); } impl Function { // Adds the final rest parameters to the Environment as an array + #[cfg(not(feature = "vm"))] pub(crate) fn add_rest_param( - &self, param: &FormalParameter, index: usize, args_list: &[JsValue], context: &mut Context, local_env: &Environment, ) { + use crate::builtins::Array; // Create array of values let array = Array::new_array(context); Array::add_to_array_object(&array, args_list.get(index..).unwrap_or_default(), context) @@ -147,7 +135,6 @@ impl Function { // Adds an argument to the environment pub(crate) fn add_arguments_to_environment( - &self, param: &FormalParameter, value: JsValue, local_env: &Environment, @@ -169,7 +156,9 @@ impl Function { match self { Self::Native { constructable, .. } => *constructable, Self::Closure { constructable, .. } => *constructable, - Self::Ordinary { flags, .. } => flags.is_constructable(), + Self::Ordinary { constructable, .. } => *constructable, + #[cfg(feature = "vm")] + Self::VmOrdinary { code, .. } => code.constructable, } } } diff --git a/boa/src/bytecompiler.rs b/boa/src/bytecompiler.rs index 6c5a78bc51c..15fba4d1699 100644 --- a/boa/src/bytecompiler.rs +++ b/boa/src/bytecompiler.rs @@ -1,10 +1,12 @@ +use gc::Gc; + use crate::{ syntax::ast::{ - node::{Declaration, GetConstField, GetField, Identifier, StatementList}, + node::{Declaration, GetConstField, GetField, StatementList}, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, Const, Node, }, - vm::{CodeBlock, Opcode}, + vm::{CodeBlock, Opcode, ThisMode}, JsBigInt, JsString, JsValue, }; use std::collections::HashMap; @@ -31,7 +33,7 @@ struct JumpControlInfo { #[derive(Debug, Clone, Copy)] enum Access<'a> { - Variable { name: &'a Identifier }, + Variable { index: u32 }, ByName { node: &'a GetConstField }, ByValue { node: &'a GetField }, This, @@ -42,13 +44,9 @@ pub struct ByteCompiler { code_block: CodeBlock, literals_map: HashMap, names_map: HashMap, + functions_map: HashMap, jump_info: Vec, -} - -impl Default for ByteCompiler { - fn default() -> Self { - Self::new() - } + top_level: bool, } impl ByteCompiler { @@ -56,12 +54,14 @@ impl ByteCompiler { const DUMMY_ADDRESS: u32 = u32::MAX; #[inline] - pub fn new() -> Self { + pub fn new(name: JsString, strict: bool) -> Self { Self { - code_block: CodeBlock::new(), + code_block: CodeBlock::new(name, 0, strict, false), literals_map: HashMap::new(), names_map: HashMap::new(), + functions_map: HashMap::new(), jump_info: Vec::new(), + top_level: true, } } @@ -89,8 +89,8 @@ impl ByteCompiler { } let name = JsString::new(name); - let index = self.code_block.names.len() as u32; - self.code_block.names.push(name.clone()); + let index = self.code_block.variables.len() as u32; + self.code_block.variables.push(name.clone()); self.names_map.insert(name, index); index } @@ -267,7 +267,10 @@ impl ByteCompiler { #[inline] fn compile_access<'a>(&mut self, node: &'a Node) -> Access<'a> { match node { - Node::Identifier(name) => Access::Variable { name }, + Node::Identifier(name) => { + let index = self.get_or_insert_name(name.as_ref()); + Access::Variable { index } + } Node::GetConstField(node) => Access::ByName { node }, Node::GetField(node) => Access::ByValue { node }, Node::This => Access::This, @@ -278,9 +281,8 @@ impl ByteCompiler { #[inline] fn access_get(&mut self, access: Access<'_>, use_expr: bool) { match access { - Access::Variable { name } => { - let index = self.get_or_insert_name(name.as_ref()); - self.emit(Opcode::GetName, &[index]); + Access::Variable { index: name } => { + self.emit(Opcode::GetName, &[name]); } Access::ByName { node } => { let index = self.get_or_insert_name(node.field()); @@ -313,8 +315,7 @@ impl ByteCompiler { } match access { - Access::Variable { name } => { - let index = self.get_or_insert_name(name.as_ref()); + Access::Variable { index } => { self.emit(Opcode::SetName, &[index]); } Access::ByName { node } => { @@ -532,7 +533,8 @@ impl ByteCompiler { } } Node::Identifier(name) => { - let access = Access::Variable { name }; + let index = self.get_or_insert_name(name.as_ref()); + let access = Access::Variable { index }; self.access_get(access, use_expr); } Node::Assign(assign) => { @@ -579,6 +581,37 @@ impl ByteCompiler { Node::This => { self.access_get(Access::This, use_expr); } + Node::FunctionExpr(_function) => self.function(expr, use_expr), + Node::ArrowFunctionDecl(_function) => self.function(expr, use_expr), + Node::Call(call) => { + for arg in call.args().iter().rev() { + self.compile_expr(arg, true); + } + match call.expr() { + Node::GetConstField(field) => { + self.compile_expr(field.obj(), true); + self.emit(Opcode::Dup, &[]); + let index = self.get_or_insert_name(field.field()); + self.emit(Opcode::GetPropertyByName, &[index]); + } + Node::GetField(field) => { + self.compile_expr(field.obj(), true); + self.emit(Opcode::Dup, &[]); + self.compile_expr(field.field(), true); + self.emit(Opcode::Swap, &[]); + self.emit(Opcode::GetPropertyByValue, &[]); + } + expr => { + self.emit(Opcode::This, &[]); + self.compile_expr(expr, true); + } + } + self.emit(Opcode::Call, &[call.args().len() as u32]); + + if !use_expr { + self.emit(Opcode::Pop, &[]); + } + } expr => todo!("TODO compile: {}", expr), } } @@ -767,11 +800,107 @@ impl ByteCompiler { self.pop_switch_control_info(); } + Node::FunctionDecl(_function) => self.function(node, false), + Node::Return(ret) => { + if let Some(expr) = ret.expr() { + self.compile_expr(expr, true); + } else { + self.emit(Opcode::PushUndefined, &[]); + } + self.emit(Opcode::Return, &[]); + } Node::Empty => {} expr => self.compile_expr(expr, use_expr), } } + pub(crate) fn function(&mut self, function: &Node, use_expr: bool) { + #[derive(Debug, Clone, Copy, PartialEq)] + enum FunctionKind { + Declaration, + Expression, + Arrow, + } + + let (kind, name, paramaters, body) = match function { + Node::FunctionDecl(function) => ( + FunctionKind::Declaration, + Some(function.name()), + function.parameters(), + function.body(), + ), + Node::FunctionExpr(function) => ( + FunctionKind::Expression, + function.name(), + function.parameters(), + function.body(), + ), + Node::ArrowFunctionDecl(function) => ( + FunctionKind::Arrow, + None, + function.params(), + function.body(), + ), + _ => unreachable!(), + }; + + let length = paramaters.len() as u32; + let mut code = CodeBlock::new(name.unwrap_or("").into(), length, false, true); + + if let FunctionKind::Arrow = kind { + code.constructable = false; + code.this_mode = ThisMode::Lexical; + } + + let mut compiler = ByteCompiler { + code_block: code, + literals_map: HashMap::new(), + names_map: HashMap::new(), + functions_map: HashMap::new(), + jump_info: Vec::new(), + top_level: false, + }; + + for node in body { + compiler.compile_stmt(node, false); + } + + compiler.code_block.params = paramaters.to_owned().into_boxed_slice(); + + compiler.emit(Opcode::PushUndefined, &[]); + compiler.emit(Opcode::Return, &[]); + + let code = Gc::new(compiler.finish()); + + let index = self.code_block.functions.len() as u32; + self.code_block.functions.push(code); + + self.emit(Opcode::GetFunction, &[index]); + + match kind { + FunctionKind::Declaration => { + let index = self.get_or_insert_name(name.unwrap()); + let access = Access::Variable { index }; + self.access_set(access, None, false); + } + FunctionKind::Expression => { + if use_expr && name.is_some() { + self.emit(Opcode::Dup, &[]); + } + if let Some(name) = name { + let index = self.get_or_insert_name(name); + let access = Access::Variable { index }; + self.access_set(access, None, false); + } + } + FunctionKind::Arrow => { + if !use_expr { + self.emit(Opcode::Pop, &[]); + } + } + } + } + #[inline] pub fn finish(self) -> CodeBlock { self.code_block diff --git a/boa/src/context.rs b/boa/src/context.rs index 9e1d265d300..32e546a9fcc 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -3,7 +3,7 @@ use crate::{ builtins::{ self, - function::{Function, FunctionFlags, NativeFunction}, + function::{Function, NativeFunction}, iterable::IteratorPrototypes, }, class::{Class, ClassBuilder}, @@ -271,8 +271,8 @@ pub struct Context { /// Cached standard objects and their prototypes. standard_objects: StandardObjects, - /// Whether or not to show trace of instructions being ran - pub trace: bool, + #[cfg(feature = "vm")] + pub(crate) vm: Vm, } impl Default for Context { @@ -286,7 +286,13 @@ impl Default for Context { console: Console::default(), iterator_prototypes: IteratorPrototypes::default(), standard_objects: Default::default(), - trace: false, + #[cfg(feature = "vm")] + vm: Vm { + frame: None, + stack: Vec::with_capacity(1024), + trace: false, + stack_size_limit: 1024, + }, }; // Add new builtIns to Context Realm @@ -524,7 +530,8 @@ impl Context { name: N, params: P, body: B, - flags: FunctionFlags, + constructable: bool, + lexical_this_mode: bool, ) -> JsResult where N: Into, @@ -541,7 +548,8 @@ impl Context { let params = params.into(); let params_len = params.len(); let func = Function::Ordinary { - flags, + constructable, + lexical_this_mode, body: RcStatementList::from(body.into()), params, environment: self.get_current_environment().clone(), @@ -816,6 +824,10 @@ impl Context { #[cfg(feature = "vm")] #[allow(clippy::unit_arg, clippy::drop_copy)] pub fn eval>(&mut self, src: T) -> JsResult { + use gc::Gc; + + use crate::vm::CallFrame; + let main_timer = BoaProfiler::global().start_event("Main", "Main"); let src_bytes: &[u8] = src.as_ref(); @@ -828,11 +840,24 @@ impl Context { Err(e) => return self.throw_syntax_error(e), }; - let mut compiler = crate::bytecompiler::ByteCompiler::default(); + let mut compiler = crate::bytecompiler::ByteCompiler::new(JsString::new("
"), false); compiler.compile_statement_list(&statement_list, true); let code_block = compiler.finish(); - let mut vm = Vm::new(code_block, self); - let result = vm.run(); + + let environment = self.get_current_environment().clone(); + let fp = self.vm.stack.len(); + let global_object = self.global_object().into(); + + self.vm.push_frame(CallFrame { + prev: None, + code: Gc::new(code_block), + this: global_object, + pc: 0, + fp, + exit_on_return: true, + environment, + }); + let result = self.run(); // The main_timer needs to be dropped before the BoaProfiler is. drop(main_timer); @@ -854,7 +879,8 @@ impl Context { } /// Set the value of trace on the context + #[cfg(feature = "vm")] pub fn set_trace(&mut self, trace: bool) { - self.trace = trace; + self.vm.trace = trace; } } diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index c650cf533b3..62f2b3240e5 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -758,7 +758,7 @@ mod in_operator { "#; check_output(&[TestAction::TestEq( - &scenario, + scenario, "Uncaught \"TypeError\": \"a is not a constructor\"", )]); } diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index 195e4279038..0ce4ae87b33 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -2,22 +2,12 @@ //! //! The `GcObject` is a garbage collected Object. -use super::{NativeObject, Object, PROTOTYPE}; +use super::{NativeObject, Object}; use crate::{ - builtins::function::{ - create_unmapped_arguments_object, ClosureFunction, Function, NativeFunction, - }, - environment::{ - environment_record_trait::EnvironmentRecordTrait, - function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, - lexical_environment::Environment, - }, - exec::InterpreterState, property::{PropertyDescriptor, PropertyKey}, symbol::WellKnownSymbols, - syntax::ast::node::RcStatementList, value::PreferredType, - Context, Executable, JsResult, JsValue, + Context, JsResult, JsValue, }; use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace}; use serde_json::{map::Map, Value as JSONValue}; @@ -26,10 +16,27 @@ use std::{ collections::HashMap, error::Error, fmt::{self, Debug, Display}, - rc::Rc, result::Result as StdResult, }; +#[cfg(not(feature = "vm"))] +use crate::{ + builtins::function::{ + create_unmapped_arguments_object, ClosureFunction, Function, NativeFunction, + }, + environment::{ + environment_record_trait::EnvironmentRecordTrait, + function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, + lexical_environment::Environment, + }, + exec::InterpreterState, + object::PROTOTYPE, + syntax::ast::node::RcStatementList, + Executable, +}; +#[cfg(not(feature = "vm"))] +use std::rc::Rc; + /// A wrapper type for an immutably borrowed type T. pub type Ref<'a, T> = GcCellRef<'a, T>; @@ -44,6 +51,7 @@ pub struct GcObject(Gc>); /// /// This is needed for the call method since we cannot mutate the function itself since we /// already borrow it so we get the function body clone it then drop the borrow and run the body +#[cfg(not(feature = "vm"))] enum FunctionBody { BuiltInFunction(NativeFunction), BuiltInConstructor(NativeFunction), @@ -124,6 +132,7 @@ impl GcObject { /// /// #[track_caller] + #[cfg(not(feature = "vm"))] fn call_construct( &self, this_target: &JsValue, @@ -136,10 +145,7 @@ impl GcObject { let body = if let Some(function) = self.borrow().as_function() { if construct && !function.is_constructable() { - let name = self - .__get__(&"name".into(), self.clone().into(), context)? - .display() - .to_string(); + let name = self.get("name", context)?.display().to_string(); return context.throw_type_error(format!("{} is not a constructor", name)); } else { match function { @@ -155,10 +161,11 @@ impl GcObject { } Function::Closure { function, .. } => FunctionBody::Closure(function.clone()), Function::Ordinary { + constructable: _, + lexical_this_mode, body, params, environment, - flags, } => { let this = if construct { // If the prototype of the constructor is not an object, then use the default object @@ -188,14 +195,14 @@ impl GcObject { // let local_env = FunctionEnvironmentRecord::new( this_function_object.clone(), - if construct || !flags.is_lexical_this_mode() { + if construct || !lexical_this_mode { Some(this.clone()) } else { None }, Some(environment.clone()), // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records - if flags.is_lexical_this_mode() { + if *lexical_this_mode { BindingStatus::Lexical } else { BindingStatus::Uninitialized @@ -218,7 +225,7 @@ impl GcObject { // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18) // // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation - if !flags.is_lexical_this_mode() + if !lexical_this_mode && !arguments_in_parameter_names && (has_parameter_expressions || (!body.lexically_declared_names().contains("arguments") @@ -245,7 +252,7 @@ impl GcObject { for (i, param) in params.iter().enumerate() { // Rest Parameters if param.is_rest_param() { - function.add_rest_param(param, i, args, context, &local_env); + Function::add_rest_param(param, i, args, context, &local_env); break; } @@ -258,8 +265,9 @@ impl GcObject { Some(value) => value, }; - function - .add_arguments_to_environment(param, value, &local_env, context); + Function::add_arguments_to_environment( + param, value, &local_env, context, + ); } if has_parameter_expressions { @@ -269,14 +277,14 @@ impl GcObject { // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation let second_env = FunctionEnvironmentRecord::new( this_function_object, - if construct || !flags.is_lexical_this_mode() { + if construct || !lexical_this_mode { Some(this) } else { None }, Some(local_env), // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records - if flags.is_lexical_this_mode() { + if *lexical_this_mode { BindingStatus::Lexical } else { BindingStatus::Uninitialized @@ -288,6 +296,10 @@ impl GcObject { FunctionBody::Ordinary(body.clone()) } + #[cfg(feature = "vm")] + Function::VmOrdinary { .. } => { + todo!("vm call") + } } } } else { @@ -345,6 +357,7 @@ impl GcObject { // #[track_caller] #[inline] + #[cfg(not(feature = "vm"))] pub fn call( &self, this: &JsValue, @@ -362,6 +375,7 @@ impl GcObject { // #[track_caller] #[inline] + #[cfg(not(feature = "vm"))] pub fn construct( &self, args: &[JsValue], diff --git a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs index 3e9e9f825e4..1e920d76935 100644 --- a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs @@ -1,5 +1,4 @@ use crate::{ - builtins::function::FunctionFlags, exec::Executable, gc::{Finalize, Trace}, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, @@ -73,12 +72,7 @@ impl ArrowFunctionDecl { impl Executable for ArrowFunctionDecl { fn run(&self, context: &mut Context) -> JsResult { - context.create_function( - "", - self.params().to_vec(), - self.body().to_vec(), - FunctionFlags::LEXICAL_THIS_MODE, - ) + context.create_function("", self.params().to_vec(), self.body().to_vec(), true, true) } } diff --git a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs index b3c39c17bcf..e69acc6dd41 100644 --- a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -1,5 +1,4 @@ use crate::{ - builtins::function::FunctionFlags, environment::lexical_environment::VariableScope, exec::Executable, gc::{Finalize, Trace}, @@ -92,7 +91,8 @@ impl Executable for FunctionDecl { self.name(), self.parameters().to_vec(), self.body().to_vec(), - FunctionFlags::CONSTRUCTABLE, + true, + false, )?; if context.has_binding(self.name()) { diff --git a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs index 77472fec758..22d781372a9 100644 --- a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs @@ -1,5 +1,4 @@ use crate::{ - builtins::function::FunctionFlags, exec::Executable, gc::{Finalize, Trace}, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, @@ -103,7 +102,8 @@ impl Executable for FunctionExpr { self.name().unwrap_or(""), self.parameters().to_vec(), self.body().to_vec(), - FunctionFlags::CONSTRUCTABLE, + true, + false, )?; Ok(val) diff --git a/boa/src/vm/code_block.rs b/boa/src/vm/code_block.rs index ae7f1bcdf41..1de4d61092f 100644 --- a/boa/src/vm/code_block.rs +++ b/boa/src/vm/code_block.rs @@ -1,8 +1,30 @@ -use crate::{vm::Opcode, JsString, JsValue}; +use crate::{ + builtins::function::{ClosureFunction, Function, NativeFunction}, + environment::{ + function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, + lexical_environment::Environment, + }, + gc::{Finalize, Trace}, + object::{GcObject, Object, PROTOTYPE}, + property::PropertyDescriptor, + syntax::ast::node::FormalParameter, + vm::Opcode, + Context, JsResult, JsString, JsValue, +}; +use gc::Gc; -use std::{convert::TryInto, fmt::Write, mem::size_of}; +use std::{convert::TryInto, fmt::Write, mem::size_of, rc::Rc}; -/// This represents wether an object can be read from [`CodeBlock`] code. +use super::CallFrame; + +#[derive(Debug, Trace, Finalize, PartialEq)] +pub enum ThisMode { + Lexical, + Strict, + Global, +} + +/// This represents wether a value can be read from [`CodeBlock`] code. pub unsafe trait Readable {} unsafe impl Readable for u8 {} @@ -16,8 +38,25 @@ unsafe impl Readable for i64 {} unsafe impl Readable for f32 {} unsafe impl Readable for f64 {} -#[derive(Debug)] +#[derive(Debug, Trace, Finalize)] pub struct CodeBlock { + /// Name of this function + pub(crate) name: JsString, + + // The length of this function. + pub(crate) length: u32, + + /// Is this function in strict mode. + pub(crate) strict: bool, + + /// Is this function constructable. + pub(crate) constructable: bool, + + /// [[ThisMode]] + pub(crate) this_mode: ThisMode, + + pub(crate) params: Box<[FormalParameter]>, + /// Bytecode pub(crate) code: Vec, @@ -25,21 +64,25 @@ pub struct CodeBlock { pub(crate) literals: Vec, /// Variables names - pub(crate) names: Vec, -} + pub(crate) variables: Vec, -impl Default for CodeBlock { - fn default() -> Self { - Self::new() - } + // Functions inside this function + pub(crate) functions: Vec>, } impl CodeBlock { - pub fn new() -> Self { + pub fn new(name: JsString, length: u32, strict: bool, constructable: bool) -> Self { Self { code: Vec::new(), literals: Vec::new(), - names: Vec::new(), + variables: Vec::new(), + functions: Vec::new(), + name, + length, + strict, + constructable, + this_mode: ThisMode::Global, + params: Vec::new().into_boxed_slice(), } } @@ -55,6 +98,7 @@ impl CodeBlock { } /// Read type T from code. + #[track_caller] pub fn read(&self, offset: usize) -> T { assert!(offset + size_of::() - 1 < self.code.len()); @@ -96,11 +140,22 @@ impl CodeBlock { | Opcode::Default | Opcode::LogicalAnd | Opcode::LogicalOr - | Opcode::Coalesce => { + | Opcode::Coalesce + | Opcode::Call => { let result = self.read::(*pc).to_string(); *pc += size_of::(); result } + Opcode::GetFunction => { + let operand = self.read::(*pc); + *pc += size_of::(); + format!( + "{:04}: '{}' (length: {})", + operand, + self.functions[operand as usize].name, + self.functions[operand as usize].length + ) + } Opcode::DefVar | Opcode::DefLet | Opcode::DefConst @@ -111,7 +166,7 @@ impl CodeBlock { | Opcode::SetPropertyByName => { let operand = self.read::(*pc); *pc += size_of::(); - format!("{:04}: '{}'", operand, self.names[operand as usize]) + format!("{:04}: '{}'", operand, self.variables[operand as usize]) } Opcode::Pop | Opcode::Dup @@ -159,6 +214,7 @@ impl CodeBlock { | Opcode::ToBoolean | Opcode::Throw | Opcode::This + | Opcode::Return | Opcode::Nop => String::new(), } } @@ -166,7 +222,11 @@ impl CodeBlock { impl std::fmt::Display for CodeBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Code: \n")?; + write!( + f, + "----------------- name '{}' (length: {}) ------------------", + self.name, self.length + )?; writeln!(f, " Location Count Opcode Operands")?; let mut pc = 0; @@ -198,14 +258,338 @@ impl std::fmt::Display for CodeBlock { f.write_char('\n')?; f.write_str("Names:\n")?; - if !self.names.is_empty() { - for (i, value) in self.names.iter().enumerate() { + if !self.variables.is_empty() { + for (i, value) in self.variables.iter().enumerate() { writeln!(f, " {:04}: {}", i, value)?; } } else { writeln!(f, " ")?; } + f.write_char('\n')?; + + f.write_str("Functions:\n")?; + if !self.functions.is_empty() { + for (i, code) in self.functions.iter().enumerate() { + writeln!( + f, + " {:04}: name: '{}' (length: {})", + i, code.name, code.length + )?; + } + } else { + writeln!(f, " ")?; + } + Ok(()) } } + +#[derive(Debug)] +#[allow(missing_copy_implementations)] +pub struct JsVmFunction { + inner: (), +} + +impl JsVmFunction { + #[allow(clippy::new_ret_no_self)] + pub fn new(code: Gc, environment: Environment, context: &mut Context) -> GcObject { + let function_prototype = context.standard_objects().function_object().prototype(); + + let prototype = context.construct_object(); + + let name_property = PropertyDescriptor::builder() + .value(code.name.clone()) + .writable(true) + .enumerable(false) + .configurable(true) + .build(); + + let length_property = PropertyDescriptor::builder() + .value(code.length) + .writable(false) + .enumerable(false) + .configurable(true) + .build(); + + let function = Function::VmOrdinary { code, environment }; + + let constructor = GcObject::new(Object::function(function, function_prototype.into())); + + let constructor_property = PropertyDescriptor::builder() + .value(constructor.clone()) + .writable(true) + .enumerable(false) + .configurable(true) + .build(); + + prototype + .define_property_or_throw("constructor", constructor_property, context) + .unwrap(); + + let prototype_property = PropertyDescriptor::builder() + .value(prototype) + .writable(false) + .enumerable(false) + .configurable(true) + .build(); + + constructor + .define_property_or_throw("prototype", prototype_property, context) + .unwrap(); + constructor + .define_property_or_throw("name", name_property, context) + .unwrap(); + constructor + .define_property_or_throw("length", length_property, context) + .unwrap(); + + constructor + } +} + +pub(crate) enum FunctionBody { + Ordinary { + code: Gc, + environment: Environment, + }, + Native { + function: NativeFunction, + }, + Closure { + function: Rc, + }, +} + +impl GcObject { + pub(crate) fn call_internal( + &self, + this: &JsValue, + args: &[JsValue], + context: &mut Context, + exit_on_return: bool, + ) -> JsResult { + let this_function_object = self.clone(); + // let mut has_parameter_expressions = false; + + if !self.is_callable() { + return context.throw_type_error("not a callable function"); + } + + let body = { + let object = self.borrow(); + let function = object.as_function().unwrap(); + + match function { + Function::Native { function, .. } => FunctionBody::Native { + function: function.0, + }, + Function::Closure { function, .. } => FunctionBody::Closure { + function: function.clone(), + }, + Function::VmOrdinary { code, environment } => FunctionBody::Ordinary { + code: code.clone(), + environment: environment.clone(), + }, + Function::Ordinary { .. } => unreachable!(), + } + }; + + match body { + FunctionBody::Native { function } => function(this, args, context), + FunctionBody::Closure { function } => (function)(this, args, context), + FunctionBody::Ordinary { code, environment } => { + let lexical_this_mode = code.this_mode == ThisMode::Lexical; + + // Create a new Function environment whose parent is set to the scope of the function declaration (self.environment) + // + let local_env = FunctionEnvironmentRecord::new( + this_function_object, + if !lexical_this_mode { + Some(this.clone()) + } else { + None + }, + Some(environment.clone()), + // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records + if lexical_this_mode { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + JsValue::undefined(), + ); + + // Turn local_env into Environment so it can be cloned + let local_env: Environment = local_env.into(); + + // Push the environment first so that it will be used by default parameters + context.push_environment(local_env.clone()); + + // Add argument bindings to the function environment + for (i, param) in code.params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param() { + todo!("Rest parameter"); + } + + let value = match args.get(i).cloned() { + None => JsValue::undefined(), + Some(value) => value, + }; + + Function::add_arguments_to_environment(param, value, &local_env, context); + } + + context.vm.push_frame(CallFrame { + prev: None, + code, + this: this.clone(), + pc: 0, + fp: context.vm.stack.len(), + exit_on_return, + environment: local_env, + }); + + let result = context.run(); + + context.pop_environment(); + + result + } + } + } + + pub fn call( + &self, + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + self.call_internal(this, args, context, true) + } + + pub(crate) fn construct_internal( + &self, + args: &[JsValue], + this_target: &JsValue, + context: &mut Context, + exit_on_return: bool, + ) -> JsResult { + let this_function_object = self.clone(); + // let mut has_parameter_expressions = false; + + if !self.is_constructable() { + return context.throw_type_error("not a constructable function"); + } + + let body = { + let object = self.borrow(); + let function = object.as_function().unwrap(); + + match function { + Function::Native { function, .. } => FunctionBody::Native { + function: function.0, + }, + Function::Closure { function, .. } => FunctionBody::Closure { + function: function.clone(), + }, + Function::VmOrdinary { code, environment } => FunctionBody::Ordinary { + code: code.clone(), + environment: environment.clone(), + }, + Function::Ordinary { .. } => unreachable!(), + } + }; + + match body { + FunctionBody::Native { function } => function(this_target, args, context), + FunctionBody::Closure { function } => (function)(this_target, args, context), + FunctionBody::Ordinary { code, environment } => { + let this = { + // If the prototype of the constructor is not an object, then use the default object + // prototype as prototype for the new object + // see + // see + let proto = this_target.as_object().unwrap().__get__( + &PROTOTYPE.into(), + this_target.clone(), + context, + )?; + let proto = if proto.is_object() { + proto + } else { + context + .standard_objects() + .object_object() + .prototype() + .into() + }; + JsValue::from(Object::create(proto)) + }; + let lexical_this_mode = code.this_mode == ThisMode::Lexical; + + // Create a new Function environment whose parent is set to the scope of the function declaration (self.environment) + // + let local_env = FunctionEnvironmentRecord::new( + this_function_object, + Some(this.clone()), + Some(environment), + // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records + if lexical_this_mode { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + JsValue::undefined(), + ); + + // Turn local_env into Environment so it can be cloned + let local_env: Environment = local_env.into(); + + // Push the environment first so that it will be used by default parameters + context.push_environment(local_env.clone()); + + // Add argument bindings to the function environment + for (i, param) in code.params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param() { + todo!("Rest parameter"); + } + + let value = match args.get(i).cloned() { + None => JsValue::undefined(), + Some(value) => value, + }; + + Function::add_arguments_to_environment(param, value, &local_env, context); + } + + context.vm.push_frame(CallFrame { + prev: None, + code, + this, + pc: 0, + fp: context.vm.stack.len(), + exit_on_return, + environment: local_env, + }); + + let _result = context.run(); + + context.pop_environment(); + + context.get_this_binding() + } + } + } + + pub fn construct( + &self, + args: &[JsValue], + this_target: &JsValue, + context: &mut Context, + ) -> JsResult { + self.construct_internal(args, this_target, context, true) + } +} diff --git a/boa/src/vm/mod.rs b/boa/src/vm/mod.rs index b0dfd140761..8d8a291b630 100644 --- a/boa/src/vm/mod.rs +++ b/boa/src/vm/mod.rs @@ -2,6 +2,7 @@ //! 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::Environment; use crate::{ builtins::Array, environment::lexical_environment::VariableScope, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, JsValue, @@ -10,43 +11,42 @@ use crate::{ mod code_block; mod opcode; -pub use code_block::CodeBlock; +pub use code_block::JsVmFunction; +pub use code_block::{CodeBlock, ThisMode}; +use gc::Gc; pub use opcode::Opcode; use std::{convert::TryInto, mem::size_of, time::Instant}; use self::code_block::Readable; -/// Virtual Machine. -#[derive(Debug)] -pub struct Vm<'a> { - context: &'a mut Context, - pc: usize, - code: CodeBlock, - stack: Vec, - stack_pointer: usize, - is_trace: bool, -} - #[cfg(test)] mod tests; -impl<'a> Vm<'a> { - pub fn new(code: CodeBlock, context: &'a mut Context) -> Self { - let trace = context.trace; - Self { - context, - pc: 0, - code, - stack: Vec::with_capacity(128), - stack_pointer: 0, - is_trace: trace, - } - } +#[derive(Debug)] +pub struct CallFrame { + pub(crate) prev: Option>, + pub(crate) code: Gc, + pub(crate) pc: usize, + pub(crate) fp: usize, + pub(crate) exit_on_return: bool, + pub(crate) this: JsValue, + pub(crate) environment: Environment, +} + +/// Virtual Machine. +#[derive(Debug)] +pub struct Vm { + pub(crate) frame: Option>, + pub(crate) stack: Vec, + pub(crate) trace: bool, + pub(crate) stack_size_limit: usize, +} +impl Vm { /// Push a value on the stack. #[inline] - pub fn push(&mut self, value: T) + pub(crate) fn push(&mut self, value: T) where T: Into, { @@ -60,88 +60,118 @@ impl<'a> Vm<'a> { /// If there is nothing to pop, then this will panic. #[inline] #[track_caller] - pub fn pop(&mut self) -> JsValue { + pub(crate) fn pop(&mut self) -> JsValue { self.stack.pop().unwrap() } - fn read(&mut self) -> T { - let value = self.code.read::(self.pc); - self.pc += size_of::(); + #[track_caller] + #[inline] + pub(crate) fn read(&mut self) -> T { + let value = self.frame().code.read::(self.frame().pc); + self.frame_mut().pc += size_of::(); value } - fn execute_instruction(&mut self) -> JsResult<()> { + #[inline] + pub(crate) fn frame(&self) -> &CallFrame { + self.frame.as_ref().unwrap() + } + + #[inline] + pub(crate) fn frame_mut(&mut self) -> &mut CallFrame { + self.frame.as_mut().unwrap() + } + + #[inline] + pub(crate) fn push_frame(&mut self, mut frame: CallFrame) { + let prev = self.frame.take(); + frame.prev = prev; + self.frame = Some(Box::new(frame)); + } + + #[inline] + pub(crate) fn pop_frame(&mut self) -> Option> { + let mut current = self.frame.take()?; + self.frame = current.prev.take(); + Some(current) + } +} + +impl Context { + fn execute_instruction(&mut self) -> JsResult { let _timer = BoaProfiler::global().start_event("execute_instruction", "vm"); macro_rules! bin_op { ($op:ident) => {{ - let rhs = self.pop(); - let lhs = self.pop(); - let value = lhs.$op(&rhs, self.context)?; - self.push(value) + let rhs = self.vm.pop(); + let lhs = self.vm.pop(); + let value = lhs.$op(&rhs, self)?; + self.vm.push(value) }}; } - let opcode = self.code.code[self.pc].try_into().unwrap(); - self.pc += 1; + let opcode = self.vm.frame().code.code[self.vm.frame().pc] + .try_into() + .unwrap(); + self.vm.frame_mut().pc += 1; match opcode { Opcode::Nop => {} Opcode::Pop => { - let _ = self.pop(); + let _ = self.vm.pop(); } Opcode::Dup => { - let value = self.pop(); - self.push(value.clone()); - self.push(value); + let value = self.vm.pop(); + self.vm.push(value.clone()); + self.vm.push(value); } Opcode::Swap => { - let first = self.pop(); - let second = self.pop(); - - self.push(first); - self.push(second); - } - Opcode::PushUndefined => self.push(JsValue::undefined()), - Opcode::PushNull => self.push(JsValue::null()), - Opcode::PushTrue => self.push(true), - Opcode::PushFalse => self.push(false), - Opcode::PushZero => self.push(0), - Opcode::PushOne => self.push(1), + let first = self.vm.pop(); + let second = self.vm.pop(); + + self.vm.push(first); + self.vm.push(second); + } + Opcode::PushUndefined => self.vm.push(JsValue::undefined()), + Opcode::PushNull => self.vm.push(JsValue::null()), + Opcode::PushTrue => self.vm.push(true), + Opcode::PushFalse => self.vm.push(false), + Opcode::PushZero => self.vm.push(0), + Opcode::PushOne => self.vm.push(1), Opcode::PushInt8 => { - let value = self.read::(); - self.push(value as i32); + let value = self.vm.read::(); + self.vm.push(value as i32); } Opcode::PushInt16 => { - let value = self.read::(); - self.push(value as i32); + let value = self.vm.read::(); + self.vm.push(value as i32); } Opcode::PushInt32 => { - let value = self.read::(); - self.push(value); + let value = self.vm.read::(); + self.vm.push(value); } Opcode::PushRational => { - let value = self.read::(); - self.push(value); + let value = self.vm.read::(); + self.vm.push(value); } - Opcode::PushNaN => self.push(JsValue::nan()), - Opcode::PushPositiveInfinity => self.push(JsValue::positive_inifnity()), - Opcode::PushNegativeInfinity => self.push(JsValue::negative_inifnity()), + Opcode::PushNaN => self.vm.push(JsValue::nan()), + Opcode::PushPositiveInfinity => self.vm.push(JsValue::positive_inifnity()), + Opcode::PushNegativeInfinity => self.vm.push(JsValue::negative_inifnity()), Opcode::PushLiteral => { - let index = self.read::() as usize; - let value = self.code.literals[index].clone(); - self.push(value) + let index = self.vm.read::() as usize; + let value = self.vm.frame().code.literals[index].clone(); + self.vm.push(value) } - Opcode::PushEmptyObject => self.push(JsValue::new_object(self.context)), + Opcode::PushEmptyObject => self.vm.push(JsValue::new_object(self)), Opcode::PushNewArray => { - let count = self.read::(); + let count = self.vm.read::(); let mut elements = Vec::with_capacity(count as usize); for _ in 0..count { - elements.push(self.pop()); + elements.push(self.vm.pop()); } - let array = Array::new_array(self.context); - Array::add_to_array_object(&array, &elements, self.context)?; - self.push(array); + let array = Array::new_array(self); + Array::add_to_array_object(&array, &elements, self)?; + self.vm.push(array); } Opcode::Add => bin_op!(add), Opcode::Sub => bin_op!(sub), @@ -156,296 +186,332 @@ impl<'a> Vm<'a> { Opcode::ShiftRight => bin_op!(shr), Opcode::UnsignedShiftRight => bin_op!(ushr), Opcode::Eq => { - let rhs = self.pop(); - let lhs = self.pop(); - let value = lhs.equals(&rhs, self.context)?; - self.push(value); + let rhs = self.vm.pop(); + let lhs = self.vm.pop(); + let value = lhs.equals(&rhs, self)?; + self.vm.push(value); } Opcode::NotEq => { - let rhs = self.pop(); - let lhs = self.pop(); - let value = !lhs.equals(&rhs, self.context)?; - self.push(value); + let rhs = self.vm.pop(); + let lhs = self.vm.pop(); + let value = !lhs.equals(&rhs, self)?; + self.vm.push(value); } Opcode::StrictEq => { - let rhs = self.pop(); - let lhs = self.pop(); - self.push(lhs.strict_equals(&rhs)); + let rhs = self.vm.pop(); + let lhs = self.vm.pop(); + self.vm.push(lhs.strict_equals(&rhs)); } Opcode::StrictNotEq => { - let rhs = self.pop(); - let lhs = self.pop(); - self.push(!lhs.strict_equals(&rhs)); + let rhs = self.vm.pop(); + let lhs = self.vm.pop(); + self.vm.push(!lhs.strict_equals(&rhs)); } Opcode::GreaterThan => bin_op!(gt), Opcode::GreaterThanOrEq => bin_op!(ge), Opcode::LessThan => bin_op!(lt), Opcode::LessThanOrEq => bin_op!(le), Opcode::In => { - let rhs = self.pop(); - let lhs = self.pop(); + let rhs = self.vm.pop(); + let lhs = self.vm.pop(); if !rhs.is_object() { - return Err(self.context.construct_type_error(format!( + return Err(self.construct_type_error(format!( "right-hand side of 'in' should be an object, got {}", rhs.type_of() ))); } - let key = lhs.to_property_key(self.context)?; - self.push(self.context.has_property(&rhs, &key)); + let key = lhs.to_property_key(self)?; + self.vm.push(self.has_property(&rhs, &key)); } Opcode::InstanceOf => { - let y = self.pop(); - let x = self.pop(); + let y = self.vm.pop(); + let x = self.vm.pop(); let value = if let Some(object) = y.as_object() { let key = WellKnownSymbols::has_instance(); - match object.get_method(self.context, key)? { - Some(instance_of_handler) => instance_of_handler - .call(&y, &[x], self.context)? - .to_boolean(), - None if object.is_callable() => { - object.ordinary_has_instance(self.context, &x)? + match object.get_method(self, key)? { + Some(instance_of_handler) => { + instance_of_handler.call(&y, &[x], self)?.to_boolean() } + None if object.is_callable() => object.ordinary_has_instance(self, &x)?, None => { - return Err(self.context.construct_type_error( + return Err(self.construct_type_error( "right-hand side of 'instanceof' is not callable", )); } } } else { - return Err(self.context.construct_type_error(format!( + return Err(self.construct_type_error(format!( "right-hand side of 'instanceof' should be an object, got {}", y.type_of() ))); }; - self.push(value); + self.vm.push(value); } Opcode::Void => { - let _ = self.pop(); - self.push(JsValue::undefined()); + let _ = self.vm.pop(); + self.vm.push(JsValue::undefined()); } Opcode::TypeOf => { - let value = self.pop(); - self.push(value.type_of()); + let value = self.vm.pop(); + self.vm.push(value.type_of()); } Opcode::Pos => { - let value = self.pop(); - let value = value.to_number(self.context)?; - self.push(value); + let value = self.vm.pop(); + let value = value.to_number(self)?; + self.vm.push(value); } Opcode::Neg => { - let value = self.pop().neg(self.context)?; - self.push(value); + let value = self.vm.pop().neg(self)?; + self.vm.push(value); } Opcode::LogicalNot => { - let value = self.pop(); - self.push(!value.to_boolean()); + let value = self.vm.pop(); + self.vm.push(!value.to_boolean()); } Opcode::BitNot => { - let target = self.pop(); - let num = target.to_number(self.context)?; + let target = self.vm.pop(); + let num = target.to_number(self)?; let value = if num.is_nan() { -1 } else { // TODO: this is not spec compliant. !(num as i32) }; - self.push(value); + self.vm.push(value); } Opcode::DefVar => { - let index = self.read::(); - let name = &self.code.names[index as usize]; + let index = self.vm.read::(); + let name = self.vm.frame().code.variables[index as usize].clone(); - self.context.create_mutable_binding( - name.to_string(), - false, - VariableScope::Function, - )?; + self.create_mutable_binding(name.to_string(), false, VariableScope::Function)?; } Opcode::DefLet => { - let index = self.read::(); - let name = &self.code.names[index as usize]; + let index = self.vm.read::(); + let name = self.vm.frame().code.variables[index as usize].clone(); - self.context.create_mutable_binding( - name.to_string(), - false, - VariableScope::Block, - )?; + self.create_mutable_binding(name.to_string(), false, VariableScope::Block)?; } Opcode::DefConst => { - let index = self.read::(); - let name = &self.code.names[index as usize]; + let index = self.vm.read::(); + let name = self.vm.frame().code.variables[index as usize].clone(); - self.context.create_immutable_binding( - name.to_string(), - false, - VariableScope::Block, - )?; + self.create_immutable_binding(name.to_string(), false, VariableScope::Block)?; } Opcode::InitLexical => { - let index = self.read::(); - let value = self.pop(); - let name = &self.code.names[index as usize]; + let index = self.vm.read::(); + let value = self.vm.pop(); + let name = self.vm.frame().code.variables[index as usize].clone(); - self.context.initialize_binding(name, value)?; + self.initialize_binding(&name, value)?; } Opcode::GetName => { - let index = self.read::(); - let name = &self.code.names[index as usize]; + let index = self.vm.read::(); + let name = self.vm.frame().code.variables[index as usize].clone(); - let value = self.context.get_binding_value(name)?; - self.push(value); + let value = self.get_binding_value(&name)?; + self.vm.push(value); } Opcode::SetName => { - let index = self.read::(); - let value = self.pop(); - let name = &self.code.names[index as usize]; + let index = self.vm.read::(); + let value = self.vm.pop(); + let name = self.vm.frame().code.variables[index as usize].clone(); - if self.context.has_binding(name) { + if self.has_binding(&name) { // Binding already exists - self.context.set_mutable_binding(name, value, true)?; + self.set_mutable_binding(&name, value, true)?; } else { - self.context.create_mutable_binding( - name.to_string(), - true, - VariableScope::Function, - )?; - self.context.initialize_binding(name, value)?; + self.create_mutable_binding(name.to_string(), true, VariableScope::Function)?; + self.initialize_binding(&name, value)?; } } Opcode::Jump => { - let address = self.read::(); - self.pc = address as usize; + let address = self.vm.read::(); + self.vm.frame_mut().pc = address as usize; } Opcode::JumpIfFalse => { - let address = self.read::(); - if !self.pop().to_boolean() { - self.pc = address as usize; + let address = self.vm.read::(); + if !self.vm.pop().to_boolean() { + self.vm.frame_mut().pc = address as usize; } } Opcode::JumpIfTrue => { - let address = self.read::(); - if self.pop().to_boolean() { - self.pc = address as usize; + let address = self.vm.read::(); + if self.vm.pop().to_boolean() { + self.vm.frame_mut().pc = address as usize; } } Opcode::LogicalAnd => { - let exit = self.read::(); - let lhs = self.pop(); + let exit = self.vm.read::(); + let lhs = self.vm.pop(); if !lhs.to_boolean() { - self.pc = exit as usize; - self.push(false); + self.vm.frame_mut().pc = exit as usize; + self.vm.push(false); } } Opcode::LogicalOr => { - let exit = self.read::(); - let lhs = self.pop(); + let exit = self.vm.read::(); + let lhs = self.vm.pop(); if lhs.to_boolean() { - self.pc = exit as usize; - self.push(true); + self.vm.frame_mut().pc = exit as usize; + self.vm.push(true); } } Opcode::Coalesce => { - let exit = self.read::(); - let lhs = self.pop(); + let exit = self.vm.read::(); + let lhs = self.vm.pop(); if !lhs.is_null_or_undefined() { - self.pc = exit as usize; - self.push(lhs); + self.vm.frame_mut().pc = exit as usize; + self.vm.push(lhs); } } Opcode::ToBoolean => { - let value = self.pop(); - self.push(value.to_boolean()); + let value = self.vm.pop(); + self.vm.push(value.to_boolean()); } Opcode::GetPropertyByName => { - let index = self.read::(); + let index = self.vm.read::(); - let value = self.pop(); + let value = self.vm.pop(); let object = if let Some(object) = value.as_object() { object } else { - value.to_object(self.context)? + value.to_object(self)? }; - let name = self.code.names[index as usize].clone(); - let result = object.get(name, self.context)?; + let name = self.vm.frame().code.variables[index as usize].clone(); + let result = object.get(name, self)?; - self.push(result) + self.vm.push(result) } Opcode::GetPropertyByValue => { - let value = self.pop(); - let key = self.pop(); + let value = self.vm.pop(); + let key = self.vm.pop(); let object = if let Some(object) = value.as_object() { object } else { - value.to_object(self.context)? + value.to_object(self)? }; - let key = key.to_property_key(self.context)?; - let result = object.get(key, self.context)?; + let key = key.to_property_key(self)?; + let result = object.get(key, self)?; - self.push(result) + self.vm.push(result) } Opcode::SetPropertyByName => { - let index = self.read::(); + let index = self.vm.read::(); - let object = self.pop(); - let value = self.pop(); + let object = self.vm.pop(); + let value = self.vm.pop(); let object = if let Some(object) = object.as_object() { object } else { - object.to_object(self.context)? + object.to_object(self)? }; - let name = self.code.names[index as usize].clone(); + let name = self.vm.frame().code.variables[index as usize].clone(); - object.set(name, value, true, self.context)?; + object.set(name, value, true, self)?; } Opcode::SetPropertyByValue => { - let object = self.pop(); - let key = self.pop(); - let value = self.pop(); + let object = self.vm.pop(); + let key = self.vm.pop(); + let value = self.vm.pop(); let object = if let Some(object) = object.as_object() { object } else { - object.to_object(self.context)? + object.to_object(self)? }; - let key = key.to_property_key(self.context)?; - object.set(key, value, true, self.context)?; + let key = key.to_property_key(self)?; + object.set(key, value, true, self)?; } Opcode::Throw => { - let value = self.pop(); + let value = self.vm.pop(); return Err(value); } Opcode::This => { - let this = self.context.get_this_binding()?; - self.push(this); + let this = self.get_this_binding()?; + self.vm.push(this); } Opcode::Case => { - let address = self.read::(); - let cond = self.pop(); - let value = self.pop(); + let address = self.vm.read::(); + let cond = self.vm.pop(); + let value = self.vm.pop(); if !value.strict_equals(&cond) { - self.push(value); + self.vm.push(value); } else { - self.pc = address as usize; + self.vm.frame_mut().pc = address as usize; } } Opcode::Default => { - let exit = self.read::(); - let _ = self.pop(); - self.pc = exit as usize; + let exit = self.vm.read::(); + let _ = self.vm.pop(); + self.vm.frame_mut().pc = exit as usize; + } + Opcode::GetFunction => { + let index = self.vm.read::(); + let code = self.vm.frame().code.functions[index as usize].clone(); + let environment = self.vm.frame().environment.clone(); + let function = JsVmFunction::new(code, environment, self); + self.vm.push(function); + } + Opcode::Call => { + if self.vm.stack_size_limit <= self.vm.stack.len() { + return Err(self.construct_range_error("Maximum call stack size exceeded")); + } + let argc = self.vm.read::(); + let func = self.vm.pop(); + let this = self.vm.pop(); + let mut args = Vec::with_capacity(argc as usize); + for _ in 0..argc { + args.push(self.vm.pop()); + } + + let object = match func { + JsValue::Object(ref object) if object.is_callable() => object.clone(), + _ => return Err(self.construct_type_error("not a callable function")), + }; + + let result = object.call_internal(&this, &args, self, false)?; + + self.vm.push(result); + } + Opcode::Return => { + let exit = self.vm.frame().exit_on_return; + + let _ = self.vm.pop_frame(); + + if exit { + return Ok(true); + } } } - Ok(()) + Ok(false) } - pub fn run(&mut self) -> JsResult { + /// Unwind the stack. + fn unwind(&mut self) -> bool { + let mut fp = 0; + while let Some(mut frame) = self.vm.frame.take() { + fp = frame.fp; + if frame.exit_on_return { + break; + } + + self.vm.frame = frame.prev.take(); + } + while self.vm.stack.len() > fp { + let _ = self.vm.pop(); + } + true + } + + pub(crate) fn run(&mut self) -> JsResult { let _timer = BoaProfiler::global().start_event("run", "vm"); const COLUMN_WIDTH: usize = 24; @@ -454,8 +520,8 @@ impl<'a> Vm<'a> { const OPERAND_COLUMN_WIDTH: usize = COLUMN_WIDTH; const NUMBER_OF_COLUMNS: usize = 4; - if self.is_trace { - println!("{}\n", self.code); + if self.vm.trace { + println!("{}\n", self.vm.frame().code); println!( "{:-^width$}", " Vm Start ", @@ -472,44 +538,77 @@ impl<'a> Vm<'a> { ); } - self.pc = 0; - while self.pc < self.code.code.len() { - if self.is_trace { - let mut pc = self.pc; + self.vm.frame_mut().pc = 0; + while self.vm.frame().pc < self.vm.frame().code.code.len() { + let result = if self.vm.trace { + let mut pc = self.vm.frame().pc; + let opcode: Opcode = self.vm.frame().code.read::(pc).try_into().unwrap(); + let operands = self.vm.frame().code.instruction_operands(&mut pc); let instant = Instant::now(); - self.execute_instruction()?; + let result = self.execute_instruction(); let duration = instant.elapsed(); - let opcode: Opcode = self.code.read::(pc).try_into().unwrap(); - println!( "{: "".to_string(), - Some(value) => format!("{}", value.display()), + Some(value) => { + if value.is_function() { + "[function]".to_string() + } else if value.is_object() { + "[object]".to_string() + } else { + format!("{}", value.display()) + } + } }, time_width = TIME_COLUMN_WIDTH, opcode_width = OPCODE_COLUMN_WIDTH, operand_width = OPERAND_COLUMN_WIDTH, ); + + result } else { - self.execute_instruction()?; + self.execute_instruction() + }; + + match result { + Ok(should_exit) => { + if should_exit { + let result = self.vm.pop(); + return Ok(result); + } + } + Err(e) => { + let should_exit = self.unwind(); + if should_exit { + return Err(e); + } else { + self.vm.push(e); + } + } } } - if self.is_trace { + if self.vm.trace { println!("\nStack:"); - if !self.stack.is_empty() { - for (i, value) in self.stack.iter().enumerate() { + if !self.vm.stack.is_empty() { + for (i, value) in self.vm.stack.iter().enumerate() { println!( "{:04}{: Vm<'a> { println!("\n"); } - if self.stack.is_empty() { + if self.vm.stack.is_empty() { return Ok(JsValue::undefined()); } - Ok(self.pop()) + Ok(self.vm.pop()) } } diff --git a/boa/src/vm/opcode.rs b/boa/src/vm/opcode.rs index 531989bd85f..5021aa9fc3a 100644 --- a/boa/src/vm/opcode.rs +++ b/boa/src/vm/opcode.rs @@ -506,6 +506,10 @@ pub enum Opcode { /// Stack: `value` **=>** Default, + GetFunction, + Call, + Return, + /// No-operation instruction, does nothing. /// /// Operands: @@ -596,6 +600,9 @@ impl Opcode { Opcode::This => "This", Opcode::Case => "Case", Opcode::Default => "Default", + Opcode::GetFunction => "GetFunction", + Opcode::Call => "Call", + Opcode::Return => "Return", Opcode::Nop => "Nop", } }