From aacdd12ed900a1a0db6debd478f303422885a831 Mon Sep 17 00:00:00 2001 From: Halid Odat Date: Fri, 1 Oct 2021 21:03:17 +0200 Subject: [PATCH] Implement functions for vm (#1433) - Separate builtin function from rust JavaScript function - Removed BuiltInFunction struct --- boa/examples/closures.rs | 2 +- boa/src/builtins/array/array_iterator.rs | 4 +- boa/src/builtins/function/mod.rs | 357 +---------- boa/src/builtins/function/tests.rs | 4 +- boa/src/builtins/map/map_iterator.rs | 4 +- boa/src/builtins/number/mod.rs | 7 +- boa/src/builtins/object/for_in_iterator.rs | 4 +- .../builtins/regexp/regexp_string_iterator.rs | 4 +- boa/src/builtins/set/set_iterator.rs | 3 +- boa/src/builtins/string/string_iterator.rs | 6 +- boa/src/bytecompiler.rs | 163 ++++- boa/src/class.rs | 15 +- boa/src/context.rs | 59 +- boa/src/object/function.rs | 376 ++++++++++++ boa/src/object/gcobject.rs | 78 ++- boa/src/object/mod.rs | 51 +- boa/src/object/operations.rs | 2 + .../declaration/arrow_function_decl/mod.rs | 5 +- .../ast/node/declaration/function_decl/mod.rs | 5 +- .../ast/node/declaration/function_expr/mod.rs | 5 +- boa/src/vm/code_block.rs | 426 ++++++++++++- boa/src/vm/mod.rs | 559 +++++++++++------- boa/src/vm/opcode.rs | 20 + 23 files changed, 1446 insertions(+), 713 deletions(-) create mode 100644 boa/src/object/function.rs diff --git a/boa/examples/closures.rs b/boa/examples/closures.rs index fafb2930b3..36eddf4099 100644 --- a/boa/examples/closures.rs +++ b/boa/examples/closures.rs @@ -60,7 +60,7 @@ fn main() -> Result<(), JsValue> { // captures. let js_function = FunctionBuilder::closure_with_captures( &mut context, - |_, _, context, captures| { + |_, _, captures, context| { println!("Called `createMessage`"); // We obtain the `name` property of `captures.object` let name = captures.object.get("name", context)?; diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index be2de04be3..695595fcae 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,7 +1,7 @@ use crate::{ - builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue}, + builtins::{iterable::create_iter_result_object, Array, JsValue}, gc::{Finalize, Trace}, - object::{JsObject, ObjectData}, + object::{function::make_builtin_fn, JsObject, ObjectData}, property::{PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 1ed9df99ac..d19761fc65 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -12,368 +12,21 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function use crate::{ - builtins::{Array, BuiltIn}, + builtins::BuiltIn, context::StandardObjects, - environment::lexical_environment::Environment, - gc::{empty_trace, Finalize, Trace}, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, NativeObject, Object, ObjectData, + function::Function, internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionBuilder, ObjectData, }, - property::{Attribute, PropertyDescriptor}, - syntax::ast::node::{FormalParameter, RcStatementList}, + property::Attribute, BoaProfiler, Context, JsResult, JsValue, }; -use bitflags::bitflags; -use dyn_clone::DynClone; - -use sealed::Sealed; -use std::fmt::{self, Debug}; -use std::ops::{Deref, DerefMut}; use super::JsArgs; #[cfg(test)] mod tests; -// Allows restricting closures to only `Copy` ones. -// Used the sealed pattern to disallow external implementations -// of `DynCopy`. -mod sealed { - pub trait Sealed {} - impl Sealed for T {} -} -pub trait DynCopy: Sealed {} -impl DynCopy for T {} - -/// Type representing a native built-in function a.k.a. function pointer. -/// -/// Native functions need to have this signature in order to -/// be callable from Javascript. -pub type NativeFunction = fn(&JsValue, &[JsValue], &mut Context) -> JsResult; - -/// Trait representing a native built-in closure. -/// -/// Closures need to have this signature in order to -/// be callable from Javascript, but most of the time the compiler -/// is smart enough to correctly infer the types. -pub trait ClosureFunction: - Fn(&JsValue, &[JsValue], &mut Context, Captures) -> JsResult + DynCopy + DynClone + 'static -{ -} - -// The `Copy` bound automatically infers `DynCopy` and `DynClone` -impl ClosureFunction for T where - T: Fn(&JsValue, &[JsValue], &mut Context, Captures) -> JsResult + Copy + 'static -{ -} - -// Allows cloning Box -dyn_clone::clone_trait_object!(ClosureFunction); - -#[derive(Clone, Copy, Finalize)] -pub struct BuiltInFunction(pub(crate) NativeFunction); - -unsafe impl Trace for BuiltInFunction { - empty_trace!(); -} - -impl From for BuiltInFunction { - fn from(function: NativeFunction) -> Self { - Self(function) - } -} - -impl Debug for BuiltInFunction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("[native]") - } -} - -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!(); -} - -// We don't use a standalone `NativeObject` for `Captures` because it doesn't -// guarantee that the internal type implements `Clone`. -// This private trait guarantees that the internal type passed to `Captures` -// implements `Clone`, and `DynClone` allows us to implement `Clone` for -// `Box`. -trait CapturesObject: NativeObject + DynClone {} -impl CapturesObject for T {} -dyn_clone::clone_trait_object!(CapturesObject); - -/// Wrapper for `Box` that allows passing additional -/// captures through a `Copy` closure. -/// -/// Any type implementing `Trace + Any + Debug + Clone` -/// can be used as a capture context, so you can pass e.g. a String, -/// a tuple or even a full struct. -/// -/// You can downcast to any type and handle the fail case as you like -/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref` -/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast -/// fails. -#[derive(Debug, Clone, Trace, Finalize)] -pub struct Captures(Box); - -impl Captures { - /// Creates a new capture context. - pub(crate) fn new(captures: T) -> Self - where - T: NativeObject + Clone, - { - Self(Box::new(captures)) - } - - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or `None` otherwise. - pub fn downcast_ref(&self) -> Option<&T> - where - T: NativeObject + Clone, - { - self.0.deref().as_any().downcast_ref::() - } - - /// Mutably downcasts `Captures` to the specified type, returning a - /// mutable reference to the downcasted type if successful or `None` otherwise. - pub fn downcast_mut(&mut self) -> Option<&mut T> - where - T: NativeObject + Clone, - { - self.0.deref_mut().as_mut_any().downcast_mut::() - } - - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or a `TypeError` otherwise. - pub fn try_downcast_ref(&self, context: &mut Context) -> JsResult<&T> - where - T: NativeObject + Clone, - { - self.0 - .deref() - .as_any() - .downcast_ref::() - .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) - } - - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or a `TypeError` otherwise. - pub fn try_downcast_mut(&mut self, context: &mut Context) -> JsResult<&mut T> - where - T: NativeObject + Clone, - { - self.0 - .deref_mut() - .as_mut_any() - .downcast_mut::() - .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) - } -} - -/// Boa representation of a Function Object. -/// -/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) -/// -/// -#[derive(Clone, Trace, Finalize)] -pub enum Function { - Native { - function: BuiltInFunction, - constructable: bool, - }, - Closure { - #[unsafe_ignore_trace] - function: Box, - constructable: bool, - captures: Captures, - }, - Ordinary { - flags: FunctionFlags, - body: RcStatementList, - params: Box<[FormalParameter]>, - environment: Environment, - }, -} - -impl Debug for Function { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Function {{ ... }}") - } -} - -impl Function { - // Adds the final rest parameters to the Environment as an array - pub(crate) fn add_rest_param( - &self, - param: &FormalParameter, - index: usize, - args_list: &[JsValue], - context: &mut Context, - local_env: &Environment, - ) { - // Create array of values - let array = Array::new_array(context); - Array::add_to_array_object(&array, args_list.get(index..).unwrap_or_default(), context) - .unwrap(); - - // Create binding - local_env - // Function parameters can share names in JavaScript... - .create_mutable_binding(param.name(), false, true, context) - .expect("Failed to create binding for rest param"); - - // Set Binding to value - local_env - .initialize_binding(param.name(), array, context) - .expect("Failed to initialize rest param"); - } - - // Adds an argument to the environment - pub(crate) fn add_arguments_to_environment( - &self, - param: &FormalParameter, - value: JsValue, - local_env: &Environment, - context: &mut Context, - ) { - // Create binding - local_env - .create_mutable_binding(param.name(), false, true, context) - .expect("Failed to create binding"); - - // Set Binding to value - local_env - .initialize_binding(param.name(), value, context) - .expect("Failed to intialize binding"); - } - - /// Returns true if the function object is constructable. - pub fn is_constructable(&self) -> bool { - match self { - Self::Native { constructable, .. } => *constructable, - Self::Closure { constructable, .. } => *constructable, - Self::Ordinary { flags, .. } => flags.is_constructable(), - } - } -} - -/// Arguments. -/// -/// -pub fn create_unmapped_arguments_object( - arguments_list: &[JsValue], - context: &mut Context, -) -> JsResult { - let len = arguments_list.len(); - let obj = JsObject::new(Object::default()); - // Set length - let length = PropertyDescriptor::builder() - .value(len) - .writable(true) - .enumerable(false) - .configurable(true); - // Define length as a property - crate::object::internal_methods::ordinary_define_own_property( - &obj, - "length".into(), - length.into(), - context, - )?; - let mut index: usize = 0; - while index < len { - let val = arguments_list.get(index).expect("Could not get argument"); - let prop = PropertyDescriptor::builder() - .value(val.clone()) - .writable(true) - .enumerable(true) - .configurable(true); - - obj.insert(index, prop); - index += 1; - } - - Ok(JsValue::new(obj)) -} - -/// Creates a new member function of a `Object` or `prototype`. -/// -/// A function registered using this macro can then be called from Javascript using: -/// -/// parent.name() -/// -/// See the javascript 'Number.toString()' as an example. -/// -/// # Arguments -/// function: The function to register as a built in function. -/// name: The name of the function (how it will be called but without the ()). -/// parent: The object to register the function on, if the global object is used then the function is instead called as name() -/// without requiring the parent, see parseInt() as an example. -/// length: As described at , The value of the "length" property is an integer that -/// indicates the typical number of arguments expected by the function. However, the language permits the function to be invoked with -/// some other number of arguments. -/// -/// If no length is provided, the length will be set to 0. -pub fn make_builtin_fn( - function: NativeFunction, - name: N, - parent: &JsObject, - length: usize, - interpreter: &Context, -) where - N: Into, -{ - let name = name.into(); - let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init"); - - let mut function = Object::function( - Function::Native { - function: function.into(), - constructable: false, - }, - interpreter - .standard_objects() - .function_object() - .prototype() - .into(), - ); - let attribute = PropertyDescriptor::builder() - .writable(false) - .enumerable(false) - .configurable(true); - function.insert_property("length", attribute.clone().value(length)); - function.insert_property("name", attribute.value(name.as_str())); - - parent.clone().insert_property( - name, - PropertyDescriptor::builder() - .value(function) - .writable(true) - .enumerable(false) - .configurable(true), - ); -} - #[derive(Debug, Clone, Copy)] pub struct BuiltInFunctionObject; @@ -394,7 +47,7 @@ impl BuiltInFunctionObject { .set_prototype_instance(prototype.into()); this.set_data(ObjectData::function(Function::Native { - function: BuiltInFunction(|_, _, _| Ok(JsValue::undefined())), + function: |_, _, _| Ok(JsValue::undefined()), constructable: true, })); Ok(this) diff --git a/boa/src/builtins/function/tests.rs b/boa/src/builtins/function/tests.rs index 504aca8da6..187d12b129 100644 --- a/boa/src/builtins/function/tests.rs +++ b/boa/src/builtins/function/tests.rs @@ -238,7 +238,7 @@ fn closure_capture_clone() { let func = FunctionBuilder::closure_with_captures( &mut context, - |_, _, context, captures| { + |_, _, captures, context| { let (string, object) = &captures; let hw = JsString::concat( @@ -251,7 +251,7 @@ fn closure_capture_clone() { ); Ok(hw.into()) }, - (string.clone(), object.clone()), + (string, object), ) .name("closure") .build(); diff --git a/boa/src/builtins/map/map_iterator.rs b/boa/src/builtins/map/map_iterator.rs index 7b99905674..8b005b24aa 100644 --- a/boa/src/builtins/map/map_iterator.rs +++ b/boa/src/builtins/map/map_iterator.rs @@ -1,6 +1,6 @@ use crate::{ - builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue}, - object::{JsObject, ObjectData}, + builtins::{iterable::create_iter_result_object, Array, JsValue}, + object::{function::make_builtin_fn, JsObject, ObjectData}, property::{PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index d5c76f90bb..c4e8e4dc00 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -14,11 +14,14 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number use super::string::is_trimmable_whitespace; -use super::{function::make_builtin_fn, JsArgs}; +use super::JsArgs; use crate::context::StandardObjects; use crate::{ builtins::BuiltIn, - object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, + object::{ + function::make_builtin_fn, internal_methods::get_prototype_from_constructor, + ConstructorBuilder, ObjectData, + }, property::Attribute, value::{AbstractRelation, IntegerOrInfinity, JsValue}, BoaProfiler, Context, JsResult, diff --git a/boa/src/builtins/object/for_in_iterator.rs b/boa/src/builtins/object/for_in_iterator.rs index ac32a7100d..bd2b02c0cd 100644 --- a/boa/src/builtins/object/for_in_iterator.rs +++ b/boa/src/builtins/object/for_in_iterator.rs @@ -1,7 +1,7 @@ use crate::{ - builtins::{function::make_builtin_fn, iterable::create_iter_result_object}, + builtins::iterable::create_iter_result_object, gc::{Finalize, Trace}, - object::{JsObject, ObjectData}, + object::{function::make_builtin_fn, JsObject, ObjectData}, property::PropertyDescriptor, property::PropertyKey, symbol::WellKnownSymbols, diff --git a/boa/src/builtins/regexp/regexp_string_iterator.rs b/boa/src/builtins/regexp/regexp_string_iterator.rs index cc45d3a0a4..8e703ae65e 100644 --- a/boa/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa/src/builtins/regexp/regexp_string_iterator.rs @@ -12,9 +12,9 @@ use regexp::{advance_string_index, RegExp}; use crate::{ - builtins::{function::make_builtin_fn, iterable::create_iter_result_object, regexp}, + builtins::{iterable::create_iter_result_object, regexp}, gc::{Finalize, Trace}, - object::{JsObject, ObjectData}, + object::{function::make_builtin_fn, JsObject, ObjectData}, property::PropertyDescriptor, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, JsString, JsValue, diff --git a/boa/src/builtins/set/set_iterator.rs b/boa/src/builtins/set/set_iterator.rs index ce012236fc..baf90f6b6a 100644 --- a/boa/src/builtins/set/set_iterator.rs +++ b/boa/src/builtins/set/set_iterator.rs @@ -1,9 +1,8 @@ use crate::{ - builtins::function::make_builtin_fn, builtins::iterable::create_iter_result_object, builtins::Array, builtins::JsValue, - object::{JsObject, ObjectData}, + object::{function::make_builtin_fn, JsObject, ObjectData}, property::{PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, diff --git a/boa/src/builtins/string/string_iterator.rs b/boa/src/builtins/string/string_iterator.rs index 5f46c456f7..2028d23786 100644 --- a/boa/src/builtins/string/string_iterator.rs +++ b/boa/src/builtins/string/string_iterator.rs @@ -1,9 +1,7 @@ use crate::{ - builtins::{ - function::make_builtin_fn, iterable::create_iter_result_object, string::code_point_at, - }, + builtins::{iterable::create_iter_result_object, string::code_point_at}, gc::{Finalize, Trace}, - object::{JsObject, ObjectData}, + object::{function::make_builtin_fn, JsObject, ObjectData}, property::PropertyDescriptor, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, JsValue, diff --git a/boa/src/bytecompiler.rs b/boa/src/bytecompiler.rs index 6c5a78bc51..1978a2c511 100644 --- a/boa/src/bytecompiler.rs +++ b/boa/src/bytecompiler.rs @@ -1,6 +1,9 @@ +use gc::Gc; + use crate::{ + object::function::ThisMode, syntax::ast::{ - node::{Declaration, GetConstField, GetField, Identifier, StatementList}, + node::{Declaration, GetConstField, GetField, StatementList}, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, Const, Node, }, @@ -31,7 +34,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 +45,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 +55,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 +90,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 +268,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 +282,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 +316,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 +534,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 +582,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 +801,102 @@ 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.items() { + 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 { + self.emit(Opcode::Dup, &[]); + } + } + FunctionKind::Arrow => { + if !use_expr { + self.emit(Opcode::Pop, &[]); + } + } + } + } + #[inline] pub fn finish(self) -> CodeBlock { self.code_block diff --git a/boa/src/class.rs b/boa/src/class.rs index 4c4bf2041f..1b51020614 100644 --- a/boa/src/class.rs +++ b/boa/src/class.rs @@ -62,8 +62,10 @@ //! [class-trait]: ./trait.Class.html use crate::{ - builtins::function::NativeFunction, - object::{ConstructorBuilder, JsObject, NativeObject, ObjectData, PROTOTYPE}, + object::{ + function::NativeFunctionSignature, ConstructorBuilder, JsObject, NativeObject, ObjectData, + PROTOTYPE, + }, property::{Attribute, PropertyDescriptor, PropertyKey}, Context, JsResult, JsValue, }; @@ -174,7 +176,12 @@ impl<'context> ClassBuilder<'context> { /// /// It is added to `prototype`. #[inline] - pub fn method(&mut self, name: N, length: usize, function: NativeFunction) -> &mut Self + pub fn method( + &mut self, + name: N, + length: usize, + function: NativeFunctionSignature, + ) -> &mut Self where N: AsRef, { @@ -190,7 +197,7 @@ impl<'context> ClassBuilder<'context> { &mut self, name: N, length: usize, - function: NativeFunction, + function: NativeFunctionSignature, ) -> &mut Self where N: AsRef, diff --git a/boa/src/context.rs b/boa/src/context.rs index f4f82cdccf..4cbc59c920 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -1,14 +1,13 @@ //! Javascript context. use crate::{ - builtins::{ - self, - function::{Function, FunctionFlags, NativeFunction}, - iterable::IteratorPrototypes, - }, + builtins::{self, iterable::IteratorPrototypes}, class::{Class, ClassBuilder}, exec::Interpreter, - object::{FunctionBuilder, JsObject, Object, PROTOTYPE}, + object::{ + function::{Function, NativeFunctionSignature, ThisMode}, + FunctionBuilder, JsObject, Object, PROTOTYPE, + }, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, syntax::{ @@ -279,11 +278,11 @@ 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, - /// Whether or not strict mode is active. strict: StrictType, + + #[cfg(feature = "vm")] + pub(crate) vm: Vm, } impl Default for Context { @@ -297,8 +296,14 @@ impl Default for Context { console: Console::default(), iterator_prototypes: IteratorPrototypes::default(), standard_objects: Default::default(), - trace: false, strict: StrictType::Off, + #[cfg(feature = "vm")] + vm: Vm { + frame: None, + stack: Vec::with_capacity(1024), + trace: false, + stack_size_limit: 1024, + }, }; // Add new builtIns to Context Realm @@ -566,7 +571,8 @@ impl Context { name: N, params: P, mut body: StatementList, - flags: FunctionFlags, + constructable: bool, + this_mode: ThisMode, ) -> JsResult where N: Into, @@ -587,7 +593,8 @@ impl Context { let params = params.into(); let params_len = params.len(); let func = Function::Ordinary { - flags, + constructable, + this_mode, body: RcStatementList::from(body), params, environment: self.get_current_environment().clone(), @@ -647,7 +654,7 @@ impl Context { &mut self, name: &str, length: usize, - body: NativeFunction, + body: NativeFunctionSignature, ) -> JsResult<()> { let function = FunctionBuilder::native(self, body) .name(name) @@ -876,6 +883,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(); @@ -888,11 +899,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); @@ -914,7 +938,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/object/function.rs b/boa/src/object/function.rs new file mode 100644 index 0000000000..104ef90af7 --- /dev/null +++ b/boa/src/object/function.rs @@ -0,0 +1,376 @@ +//! This module implements the global `Function` object as well as creates Native Functions. +//! +//! Objects wrap `Function`s and expose them via call/construct slots. +//! +//! `The `Function` object is used for matching text with a pattern. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! - [MDN documentation][mdn] +//! +//! [spec]: https://tc39.es/ecma262/#sec-function-objects +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function + +use crate::{ + environment::lexical_environment::Environment, + gc::{Finalize, Trace}, + object::{JsObject, Object}, + property::PropertyDescriptor, + syntax::ast::node::{FormalParameter, RcStatementList}, + BoaProfiler, Context, JsResult, JsValue, +}; + +use dyn_clone::DynClone; +use std::{ + fmt, + ops::{Deref, DerefMut}, +}; + +use super::NativeObject; + +/// Type representing a native built-in function a.k.a. function pointer. +/// +/// Native functions need to have this signature in order to +/// be callable from Javascript. +pub type NativeFunctionSignature = fn(&JsValue, &[JsValue], &mut Context) -> JsResult; + +// Allows restricting closures to only `Copy` ones. +// Used the sealed pattern to disallow external implementations +// of `DynCopy`. +mod sealed { + pub trait Sealed {} + impl Sealed for T {} +} +pub trait DynCopy: sealed::Sealed {} +impl DynCopy for T {} + +/// Trait representing a native built-in closure. +/// +/// Closures need to have this signature in order to +/// be callable from Javascript, but most of the time the compiler +/// is smart enough to correctly infer the types. +pub trait ClosureFunctionSignature: + Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + DynCopy + DynClone + 'static +{ +} + +// The `Copy` bound automatically infers `DynCopy` and `DynClone` +impl ClosureFunctionSignature for T where + T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + Copy + 'static +{ +} + +// Allows cloning Box +dyn_clone::clone_trait_object!(ClosureFunctionSignature); + +#[derive(Debug, Trace, Finalize, PartialEq, Clone)] +pub enum ThisMode { + Lexical, + Strict, + Global, +} + +impl ThisMode { + /// Returns `true` if the this mode is `Lexical`. + pub fn is_lexical(&self) -> bool { + matches!(self, Self::Lexical) + } + + /// Returns `true` if the this mode is `Strict`. + pub fn is_strict(&self) -> bool { + matches!(self, Self::Strict) + } + + /// Returns `true` if the this mode is `Global`. + pub fn is_global(&self) -> bool { + matches!(self, Self::Global) + } +} + +#[derive(Debug, Trace, Finalize, PartialEq, Clone)] +pub enum ConstructorKind { + Base, + Derived, +} + +impl ConstructorKind { + /// Returns `true` if the constructor kind is `Base`. + pub fn is_base(&self) -> bool { + matches!(self, Self::Base) + } + + /// Returns `true` if the constructor kind is `Derived`. + pub fn is_derived(&self) -> bool { + matches!(self, Self::Derived) + } +} +// We don't use a standalone `NativeObject` for `Captures` because it doesn't +// guarantee that the internal type implements `Clone`. +// This private trait guarantees that the internal type passed to `Captures` +// implements `Clone`, and `DynClone` allows us to implement `Clone` for +// `Box`. +trait CapturesObject: NativeObject + DynClone {} +impl CapturesObject for T {} +dyn_clone::clone_trait_object!(CapturesObject); + +/// Wrapper for `Box` that allows passing additional +/// captures through a `Copy` closure. +/// +/// Any type implementing `Trace + Any + Debug + Clone` +/// can be used as a capture context, so you can pass e.g. a String, +/// a tuple or even a full struct. +/// +/// You can downcast to any type and handle the fail case as you like +/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref` +/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast +/// fails. +#[derive(Debug, Clone, Trace, Finalize)] +pub struct Captures(Box); + +impl Captures { + /// Creates a new capture context. + pub(crate) fn new(captures: T) -> Self + where + T: NativeObject + Clone, + { + Self(Box::new(captures)) + } + + /// Downcasts `Captures` to the specified type, returning a reference to the + /// downcasted type if successful or `None` otherwise. + pub fn downcast_ref(&self) -> Option<&T> + where + T: NativeObject + Clone, + { + self.0.deref().as_any().downcast_ref::() + } + + /// Mutably downcasts `Captures` to the specified type, returning a + /// mutable reference to the downcasted type if successful or `None` otherwise. + pub fn downcast_mut(&mut self) -> Option<&mut T> + where + T: NativeObject + Clone, + { + self.0.deref_mut().as_mut_any().downcast_mut::() + } + + /// Downcasts `Captures` to the specified type, returning a reference to the + /// downcasted type if successful or a `TypeError` otherwise. + pub fn try_downcast_ref(&self, context: &mut Context) -> JsResult<&T> + where + T: NativeObject + Clone, + { + self.0 + .deref() + .as_any() + .downcast_ref::() + .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) + } + + /// Downcasts `Captures` to the specified type, returning a reference to the + /// downcasted type if successful or a `TypeError` otherwise. + pub fn try_downcast_mut(&mut self, context: &mut Context) -> JsResult<&mut T> + where + T: NativeObject + Clone, + { + self.0 + .deref_mut() + .as_mut_any() + .downcast_mut::() + .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) + } +} + +/// Boa representation of a Function Object. +/// +/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) +/// +/// +#[derive(Clone, Trace, Finalize)] +pub enum Function { + Native { + #[unsafe_ignore_trace] + function: NativeFunctionSignature, + constructable: bool, + }, + Closure { + #[unsafe_ignore_trace] + function: Box, + constructable: bool, + captures: Captures, + }, + Ordinary { + constructable: bool, + this_mode: ThisMode, + body: RcStatementList, + params: Box<[FormalParameter]>, + environment: Environment, + }, + #[cfg(feature = "vm")] + VmOrdinary { + code: gc::Gc, + environment: Environment, + }, +} + +impl fmt::Debug for Function { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Function {{ ... }}") + } +} + +impl Function { + // Adds the final rest parameters to the Environment as an array + #[cfg(not(feature = "vm"))] + pub(crate) fn add_rest_param( + 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) + .unwrap(); + + // Create binding + local_env + // Function parameters can share names in JavaScript... + .create_mutable_binding(param.name(), false, true, context) + .expect("Failed to create binding for rest param"); + + // Set Binding to value + local_env + .initialize_binding(param.name(), array, context) + .expect("Failed to initialize rest param"); + } + + // Adds an argument to the environment + pub(crate) fn add_arguments_to_environment( + param: &FormalParameter, + value: JsValue, + local_env: &Environment, + context: &mut Context, + ) { + // Create binding + local_env + .create_mutable_binding(param.name(), false, true, context) + .expect("Failed to create binding"); + + // Set Binding to value + local_env + .initialize_binding(param.name(), value, context) + .expect("Failed to intialize binding"); + } + + /// Returns true if the function object is constructable. + pub fn is_constructable(&self) -> bool { + match self { + Self::Native { constructable, .. } => *constructable, + Self::Closure { constructable, .. } => *constructable, + Self::Ordinary { constructable, .. } => *constructable, + #[cfg(feature = "vm")] + Self::VmOrdinary { code, .. } => code.constructable, + } + } +} + +/// Arguments. +/// +/// +pub fn create_unmapped_arguments_object( + arguments_list: &[JsValue], + context: &mut Context, +) -> JsResult { + let len = arguments_list.len(); + let obj = JsObject::new(Object::default()); + // Set length + let length = PropertyDescriptor::builder() + .value(len) + .writable(true) + .enumerable(false) + .configurable(true) + .build(); + // Define length as a property + crate::object::internal_methods::ordinary_define_own_property( + &obj, + "length".into(), + length, + context, + )?; + let mut index: usize = 0; + while index < len { + let val = arguments_list.get(index).expect("Could not get argument"); + let prop = PropertyDescriptor::builder() + .value(val.clone()) + .writable(true) + .enumerable(true) + .configurable(true); + + obj.insert(index, prop); + index += 1; + } + + Ok(JsValue::new(obj)) +} + +/// Creates a new member function of a `Object` or `prototype`. +/// +/// A function registered using this macro can then be called from Javascript using: +/// +/// parent.name() +/// +/// See the javascript 'Number.toString()' as an example. +/// +/// # Arguments +/// function: The function to register as a built in function. +/// name: The name of the function (how it will be called but without the ()). +/// parent: The object to register the function on, if the global object is used then the function is instead called as name() +/// without requiring the parent, see parseInt() as an example. +/// length: As described at , The value of the "length" property is an integer that +/// indicates the typical number of arguments expected by the function. However, the language permits the function to be invoked with +/// some other number of arguments. +/// +/// If no length is provided, the length will be set to 0. +// TODO: deprecate/remove this. +pub(crate) fn make_builtin_fn( + function: NativeFunctionSignature, + name: N, + parent: &JsObject, + length: usize, + interpreter: &Context, +) where + N: Into, +{ + let name = name.into(); + let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init"); + + let mut function = Object::function( + Function::Native { + function, + constructable: false, + }, + interpreter + .standard_objects() + .function_object() + .prototype() + .into(), + ); + let attribute = PropertyDescriptor::builder() + .writable(false) + .enumerable(false) + .configurable(true); + function.insert_property("length", attribute.clone().value(length)); + function.insert_property("name", attribute.value(name.as_str())); + + parent.clone().insert_property( + name, + PropertyDescriptor::builder() + .value(function) + .writable(true) + .enumerable(false) + .configurable(true), + ); +} diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index 10a03d5656..9624a1709a 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -2,22 +2,12 @@ //! //! The `JsObject` is a garbage collected Object. -use super::{NativeObject, Object, PROTOTYPE}; +use super::{NativeObject, Object}; use crate::{ - builtins::function::{ - create_unmapped_arguments_object, Captures, ClosureFunction, Function, NativeFunction, - }, - environment::{ - environment_record_trait::EnvironmentRecordTrait, - function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, - lexical_environment::Environment, - }, - exec::InterpreterState, object::{ObjectData, ObjectKind}, property::{PropertyDescriptor, PropertyKey}, - syntax::ast::node::RcStatementList, value::PreferredType, - Context, Executable, JsResult, JsValue, + Context, JsResult, JsValue, }; use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace}; use std::{ @@ -28,6 +18,25 @@ use std::{ result::Result as StdResult, }; +#[cfg(not(feature = "vm"))] +use crate::{ + environment::{ + environment_record_trait::EnvironmentRecordTrait, + function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, + lexical_environment::Environment, + }, + exec::InterpreterState, + object::{ + function::{ + create_unmapped_arguments_object, Captures, ClosureFunctionSignature, Function, + NativeFunctionSignature, + }, + PROTOTYPE, + }, + syntax::ast::node::RcStatementList, + Executable, +}; + /// A wrapper type for an immutably borrowed type T. pub type Ref<'a, T> = GcCellRef<'a, T>; @@ -42,11 +51,12 @@ pub struct JsObject(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), + BuiltInFunction(NativeFunctionSignature), + BuiltInConstructor(NativeFunctionSignature), Closure { - function: Box, + function: Box, captures: Captures, }, Ordinary(RcStatementList), @@ -125,6 +135,7 @@ impl JsObject { /// /// #[track_caller] + #[cfg(not(feature = "vm"))] pub(super) fn call_construct( &self, this_target: &JsValue, @@ -137,10 +148,7 @@ impl JsObject { 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 { @@ -149,9 +157,9 @@ impl JsObject { constructable, } => { if *constructable || construct { - FunctionBody::BuiltInConstructor(function.0) + FunctionBody::BuiltInConstructor(*function) } else { - FunctionBody::BuiltInFunction(function.0) + FunctionBody::BuiltInFunction(*function) } } Function::Closure { @@ -161,10 +169,11 @@ impl JsObject { captures: captures.clone(), }, Function::Ordinary { + constructable: _, + 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 @@ -194,14 +203,14 @@ impl JsObject { // let local_env = FunctionEnvironmentRecord::new( this_function_object.clone(), - if construct || !flags.is_lexical_this_mode() { + if construct || !this_mode.is_lexical() { 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 this_mode.is_lexical() { BindingStatus::Lexical } else { BindingStatus::Uninitialized @@ -225,7 +234,7 @@ impl JsObject { // - 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 !this_mode.is_lexical() && !arguments_in_parameter_names && (has_parameter_expressions || (!body.lexically_declared_names().contains("arguments") @@ -247,7 +256,7 @@ impl JsObject { 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; } @@ -260,8 +269,9 @@ impl JsObject { 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 { @@ -271,14 +281,14 @@ impl JsObject { // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation let second_env = FunctionEnvironmentRecord::new( this_function_object, - if construct || !flags.is_lexical_this_mode() { + if construct || !this_mode.is_lexical() { 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 this_mode.is_lexical() { BindingStatus::Lexical } else { BindingStatus::Uninitialized @@ -291,6 +301,10 @@ impl JsObject { FunctionBody::Ordinary(body.clone()) } + #[cfg(feature = "vm")] + Function::VmOrdinary { .. } => { + todo!("vm call") + } } } } else { @@ -306,7 +320,7 @@ impl JsObject { } FunctionBody::BuiltInFunction(function) => function(this_target, args, context), FunctionBody::Closure { function, captures } => { - (function)(this_target, args, context, captures) + (function)(this_target, args, captures, context) } FunctionBody::Ordinary(body) => { let result = body.run(context); @@ -893,7 +907,7 @@ impl JsObject { self.borrow().is_constructable() } - /// Returns true if the GcObject is the global for a Realm + /// Returns true if the JsObject is the global for a Realm pub fn is_global(&self) -> bool { matches!( self.borrow().data, diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 8de3a485eb..9f751675bd 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -2,18 +2,14 @@ use crate::{ builtins::{ - array::array_iterator::ArrayIterator, - function::{Captures, Function, NativeFunction}, - map::map_iterator::MapIterator, - map::ordered_map::OrderedMap, - regexp::regexp_string_iterator::RegExpStringIterator, - set::ordered_set::OrderedSet, - set::set_iterator::SetIterator, - string::string_iterator::StringIterator, - Date, RegExp, + array::array_iterator::ArrayIterator, map::map_iterator::MapIterator, + map::ordered_map::OrderedMap, regexp::regexp_string_iterator::RegExpStringIterator, + set::ordered_set::OrderedSet, set::set_iterator::SetIterator, + string::string_iterator::StringIterator, Date, RegExp, }, context::StandardConstructor, gc::{Finalize, Trace}, + object::function::{Captures, Function, NativeFunctionSignature}, property::{Attribute, PropertyDescriptor, PropertyKey}, BoaProfiler, Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, }; @@ -26,6 +22,7 @@ use std::{ #[cfg(test)] mod tests; +pub mod function; mod gcobject; pub(crate) mod internal_methods; mod operations; @@ -1114,11 +1111,11 @@ pub struct FunctionBuilder<'context> { impl<'context> FunctionBuilder<'context> { /// Create a new `FunctionBuilder` for creating a native function. #[inline] - pub fn native(context: &'context mut Context, function: NativeFunction) -> Self { + pub fn native(context: &'context mut Context, function: NativeFunctionSignature) -> Self { Self { context, function: Some(Function::Native { - function: function.into(), + function, constructable: false, }), name: JsString::default(), @@ -1135,7 +1132,7 @@ impl<'context> FunctionBuilder<'context> { Self { context, function: Some(Function::Closure { - function: Box::new(move |this, args, context, _| function(this, args, context)), + function: Box::new(move |this, args, _, context| function(this, args, context)), constructable: false, captures: Captures::new(()), }), @@ -1157,15 +1154,15 @@ impl<'context> FunctionBuilder<'context> { captures: C, ) -> Self where - F: Fn(&JsValue, &[JsValue], &mut Context, &mut C) -> JsResult + Copy + 'static, + F: Fn(&JsValue, &[JsValue], &mut C, &mut Context) -> JsResult + Copy + 'static, C: NativeObject + Clone, { Self { context, function: Some(Function::Closure { - function: Box::new(move |this, args, context, mut captures: Captures| { + function: Box::new(move |this, args, mut captures: Captures, context| { let data = captures.try_downcast_mut::(context)?; - function(this, args, context, data) + function(this, args, data, context) }), constructable: false, captures: Captures::new(captures), @@ -1299,7 +1296,12 @@ impl<'context> ObjectInitializer<'context> { /// Add a function to the object. #[inline] - pub fn function(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self + pub fn function( + &mut self, + function: NativeFunctionSignature, + binding: B, + length: usize, + ) -> &mut Self where B: Into, { @@ -1347,7 +1349,7 @@ impl<'context> ObjectInitializer<'context> { /// Builder for creating constructors objects, like `Array`. pub struct ConstructorBuilder<'context> { context: &'context mut Context, - constructor_function: NativeFunction, + constructor_function: NativeFunctionSignature, constructor_object: JsObject, prototype: JsObject, name: JsString, @@ -1374,7 +1376,7 @@ impl Debug for ConstructorBuilder<'_> { impl<'context> ConstructorBuilder<'context> { /// Create a new `ConstructorBuilder`. #[inline] - pub fn new(context: &'context mut Context, constructor: NativeFunction) -> Self { + pub fn new(context: &'context mut Context, constructor: NativeFunctionSignature) -> Self { Self { context, constructor_function: constructor, @@ -1391,7 +1393,7 @@ impl<'context> ConstructorBuilder<'context> { #[inline] pub(crate) fn with_standard_object( context: &'context mut Context, - constructor: NativeFunction, + constructor: NativeFunctionSignature, object: StandardConstructor, ) -> Self { Self { @@ -1409,7 +1411,12 @@ impl<'context> ConstructorBuilder<'context> { /// Add new method to the constructors prototype. #[inline] - pub fn method(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self + pub fn method( + &mut self, + function: NativeFunctionSignature, + binding: B, + length: usize, + ) -> &mut Self where B: Into, { @@ -1435,7 +1442,7 @@ impl<'context> ConstructorBuilder<'context> { #[inline] pub fn static_method( &mut self, - function: NativeFunction, + function: NativeFunctionSignature, binding: B, length: usize, ) -> &mut Self @@ -1617,7 +1624,7 @@ impl<'context> ConstructorBuilder<'context> { pub fn build(&mut self) -> JsObject { // Create the native function let function = Function::Native { - function: self.constructor_function.into(), + function: self.constructor_function, constructable: self.constructable, }; diff --git a/boa/src/object/operations.rs b/boa/src/object/operations.rs index d4938419ee..8c7498bcbf 100644 --- a/boa/src/object/operations.rs +++ b/boa/src/object/operations.rs @@ -287,6 +287,7 @@ impl JsObject { // #[track_caller] #[inline] + #[cfg(not(feature = "vm"))] pub fn call( &self, this: &JsValue, @@ -304,6 +305,7 @@ impl JsObject { // #[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 64a460225e..2cebe53c4e 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,7 +1,7 @@ use crate::{ - builtins::function::FunctionFlags, exec::Executable, gc::{Finalize, Trace}, + object::function::ThisMode, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, Context, JsResult, JsValue, }; @@ -77,7 +77,8 @@ impl Executable for ArrowFunctionDecl { "", self.params().to_vec(), self.body().clone(), - FunctionFlags::LEXICAL_THIS_MODE, + false, + ThisMode::Lexical, ) } } 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 e19840abda..25731595a7 100644 --- a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -1,8 +1,8 @@ use crate::{ - builtins::function::FunctionFlags, environment::lexical_environment::VariableScope, exec::Executable, gc::{Finalize, Trace}, + object::function::ThisMode, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, BoaProfiler, Context, JsResult, JsValue, }; @@ -92,7 +92,8 @@ impl Executable for FunctionDecl { self.name(), self.parameters().to_vec(), self.body().clone(), - FunctionFlags::CONSTRUCTABLE, + true, + ThisMode::Global, )?; 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 57833ccbfd..b859997c18 100644 --- a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs @@ -1,7 +1,7 @@ use crate::{ - builtins::function::FunctionFlags, exec::Executable, gc::{Finalize, Trace}, + object::function::ThisMode, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, Context, JsResult, JsValue, }; @@ -103,7 +103,8 @@ impl Executable for FunctionExpr { self.name().unwrap_or(""), self.parameters().to_vec(), self.body().clone(), - FunctionFlags::CONSTRUCTABLE, + true, + ThisMode::Global, )?; Ok(val) diff --git a/boa/src/vm/code_block.rs b/boa/src/vm/code_block.rs index ae7f1bcdf4..a47ff8eb70 100644 --- a/boa/src/vm/code_block.rs +++ b/boa/src/vm/code_block.rs @@ -1,8 +1,27 @@ -use crate::{vm::Opcode, JsString, JsValue}; +use crate::{ + environment::{ + function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, + lexical_environment::Environment, + }, + gc::{Finalize, Trace}, + object::{ + function::{ + Captures, ClosureFunctionSignature, Function, NativeFunctionSignature, ThisMode, + }, + JsObject, 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}; -/// This represents wether an object can be read from [`CodeBlock`] code. +use super::CallFrame; + +/// This represents wether a value can be read from [`CodeBlock`] code. pub unsafe trait Readable {} unsafe impl Readable for u8 {} @@ -16,8 +35,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 +61,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 +95,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 +137,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 +163,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 +211,7 @@ impl CodeBlock { | Opcode::ToBoolean | Opcode::Throw | Opcode::This + | Opcode::Return | Opcode::Nop => String::new(), } } @@ -166,7 +219,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 +255,351 @@ 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) -> JsObject { + 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 = JsObject::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: NativeFunctionSignature, + }, + Closure { + function: Box, + captures: Captures, + }, +} + +impl JsObject { + 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, + }, + Function::Closure { + function, captures, .. + } => FunctionBody::Closure { + function: function.clone(), + captures: captures.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, captures } => { + (function)(this, args, captures, 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(), + context, + )?; + + // 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, + }, + Function::Closure { + function, captures, .. + } => FunctionBody::Closure { + function: function.clone(), + captures: captures.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, captures } => { + (function)(this_target, args, captures, 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(), + context, + )?; + + // 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 d9f2e263d3..4251b28b26 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, @@ -11,42 +12,41 @@ mod code_block; mod opcode; pub use code_block::CodeBlock; +pub use code_block::JsVmFunction; +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,289 +186,333 @@ 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)?; - let has_property = self.context.has_property(&rhs, &key)?; - self.push(has_property); + let key = lhs.to_property_key(self)?; + let value = self.has_property(&rhs, &key)?; + self.vm.push(value); } 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, false, VariableScope::Function)?; + self.create_mutable_binding(name.as_ref(), 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, false, VariableScope::Block)?; + self.create_mutable_binding(name.as_ref(), 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.as_ref(), - false, - VariableScope::Block, - )?; + self.create_immutable_binding(name.as_ref(), 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.as_ref())? { // Binding already exists - self.context - .set_mutable_binding(name, value, self.context.strict())?; + self.set_mutable_binding(name.as_ref(), value, self.strict())?; } else { - self.context - .create_mutable_binding(name, true, VariableScope::Function)?; - self.context.initialize_binding(name, value)?; + self.create_mutable_binding(name.as_ref(), true, VariableScope::Function)?; + self.initialize_binding(name.as_ref(), 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; @@ -447,8 +521,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 ", @@ -465,44 +539,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 531989bd85..79bee2a75c 100644 --- a/boa/src/vm/opcode.rs +++ b/boa/src/vm/opcode.rs @@ -506,6 +506,23 @@ pub enum Opcode { /// Stack: `value` **=>** Default, + /// Get function from the precompiled inner functions. + /// + /// Operands: address: `u32` + /// + /// Stack: **=>** `func` + GetFunction, + + /// Call a function. + /// + /// Operands: argc: `u32` + /// + /// Stack: `func`, `this`, `arg1`, `arg2`,...`argn` **=>** + Call, + + /// Return from a function. + Return, + /// No-operation instruction, does nothing. /// /// Operands: @@ -596,6 +613,9 @@ impl Opcode { Opcode::This => "This", Opcode::Case => "Case", Opcode::Default => "Default", + Opcode::GetFunction => "GetFunction", + Opcode::Call => "Call", + Opcode::Return => "Return", Opcode::Nop => "Nop", } }