diff --git a/boa/benches/full.rs b/boa/benches/full.rs index 197094c5c72..e0eedc3ef51 100644 --- a/boa/benches/full.rs +++ b/boa/benches/full.rs @@ -47,7 +47,7 @@ macro_rules! full_benchmarks { static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); let mut context = Context::default(); let statement_list = context.parse(CODE).expect("parsing failed"); - let code_block = context.compile(&statement_list); + let code_block = context.compile(&statement_list).unwrap(); c.bench_function(concat!($id, " (Execution)"), move |b| { b.iter(|| { context.execute(black_box(code_block.clone())).unwrap() diff --git a/boa/src/builtins/function/arguments.rs b/boa/src/builtins/function/arguments.rs index 050ace2ee45..a27c30ba80c 100644 --- a/boa/src/builtins/function/arguments.rs +++ b/boa/src/builtins/function/arguments.rs @@ -1,14 +1,15 @@ use crate::{ builtins::Array, - environment::lexical_environment::Environment, + environments::DeclarativeEnvironment, gc::{Finalize, Trace}, object::{FunctionBuilder, JsObject, ObjectData}, - property::PropertyDescriptor, + property::{PropertyDescriptor, PropertyKey}, symbol::{self, WellKnownSymbols}, syntax::ast::node::FormalParameter, Context, JsValue, }; -use rustc_hash::FxHashSet; +use gc::Gc; +use rustc_hash::FxHashMap; #[derive(Debug, Clone, Trace, Finalize)] pub struct MappedArguments(JsObject); @@ -57,7 +58,7 @@ impl Arguments { .configurable(true), context, ) - .expect("DefinePropertyOrThrow must not fail per the spec"); + .expect("Defining new own properties for a new ordinary object cannot fail"); // 5. Let index be 0. // 6. Repeat, while index < len, @@ -65,7 +66,7 @@ impl Arguments { // a. Let val be argumentsList[index]. // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). obj.create_data_property_or_throw(index, value, context) - .expect("CreateDataPropertyOrThrow must not fail per the spec"); + .expect("Defining new own properties for a new ordinary object cannot fail"); // c. Set index to index + 1. } @@ -82,7 +83,7 @@ impl Arguments { .configurable(true), context, ) - .expect("DefinePropertyOrThrow must not fail per the spec"); + .expect("Defining new own properties for a new ordinary object cannot fail"); let throw_type_error = context.intrinsics().throw_type_error(); @@ -98,7 +99,7 @@ impl Arguments { .configurable(false), context, ) - .expect("DefinePropertyOrThrow must not fail per the spec"); + .expect("Defining new own properties for a new ordinary object cannot fail"); // 9. Return obj. obj @@ -111,7 +112,7 @@ impl Arguments { func: &JsObject, formals: &[FormalParameter], arguments_list: &[JsValue], - env: &Environment, + env: &Gc, context: &mut Context, ) -> JsObject { // 1. Assert: formals does not contain a rest parameter, any binding patterns, or any initializers. @@ -142,7 +143,7 @@ impl Arguments { // a. Let val be argumentsList[index]. // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). obj.create_data_property_or_throw(index, val, context) - .expect("CreateDataPropertyOrThrow must not fail per the spec"); + .expect("Defining new own properties for a new ordinary object cannot fail"); // c. Set index to index + 1. } @@ -157,90 +158,105 @@ impl Arguments { .configurable(true), context, ) - .expect("DefinePropertyOrThrow must not fail per the spec"); + .expect("Defining new own properties for a new ordinary object cannot fail"); - // 17. Let mappedNames be a new empty List. - // using a set to optimize `contains` - let mut mapped_names = FxHashSet::default(); + // The section 17-19 differs from the spec, due to the way the runtime environments work. + // + // This section creates getters and setters for all mapped arguments. + // Getting and setting values on the `arguments` object will actually access the bindings in the environment: + // ``` + // function f(a) {console.log(a); arguments[0] = 1; console.log(a)}; + // f(0) // 0, 1 + // ``` + // + // The spec assumes, that identifiers are used at runtime to reference bindings in the environment. + // We use indices to access environment bindings at runtime. + // To map to function parameters to binding indices, we use the fact, that bindings in a + // function environment start with all of the arguments in order: + // `function f (a,b,c)` + // | binding index | `arguments` property key | identifier | + // | 0 | 0 | a | + // | 1 | 1 | b | + // | 2 | 2 | c | + // + // Notice that the binding index does not correspond to the argument index: + // `function f (a,a,b)` => binding indices 0 (a), 1 (b), 2 (c) + // | binding index | `arguments` property key | identifier | + // | - | 0 | - | + // | 0 | 1 | a | + // | 1 | 2 | b | + // While the `arguments` object contains all arguments, they must not be all bound. + // In the case of duplicate parameter names, the last one is bound as the environment binding. + // + // The following logic implements the steps 17-19 adjusted for our environment structure. - // 12. Let parameterNames be the BoundNames of formals. - // 13. Let numberOfParameters be the number of elements in parameterNames. - // 18. Set index to numberOfParameters - 1. - // 19. Repeat, while index ≥ 0, - // a. Let name be parameterNames[index]. - - for (index, parameter_name_vec) in - formals.iter().map(FormalParameter::names).enumerate().rev() - { - for parameter_name in parameter_name_vec.iter().copied() { - // b. If name is not an element of mappedNames, then - if !mapped_names.contains(¶meter_name) { - // i. Add name as an element of the list mappedNames. - mapped_names.insert(parameter_name); - // ii. If index < len, then - if index < len { - // 1. Let g be MakeArgGetter(name, env). - // https://tc39.es/ecma262/#sec-makearggetter - let g = { - // 2. Let getter be ! CreateBuiltinFunction(getterClosure, 0, "", « »). - // 3. NOTE: getter is never directly accessible to ECMAScript code. - // 4. Return getter. - FunctionBuilder::closure_with_captures( - context, - // 1. Let getterClosure be a new Abstract Closure with no parameters that captures - // name and env and performs the following steps when called: - |_, _, captures, context| { - captures.0.get_binding_value(captures.1, false, context) - }, - (env.clone(), parameter_name), - ) - .length(0) - .name("") - .build() - }; - // 2. Let p be MakeArgSetter(name, env). - // https://tc39.es/ecma262/#sec-makeargsetter - let p = { - // 2. Let setter be ! CreateBuiltinFunction(setterClosure, 1, "", « »). - // 3. NOTE: setter is never directly accessible to ECMAScript code. - // 4. Return setter. - FunctionBuilder::closure_with_captures( - context, - // 1. Let setterClosure be a new Abstract Closure with parameters (value) that captures - // name and env and performs the following steps when called: - |_, args, captures, context| { - let value = args.get(0).cloned().unwrap_or_default(); - // a. Return env.SetMutableBinding(name, value, false). - captures - .0 - .set_mutable_binding(captures.1, value, false, context) - .map(|_| JsValue::Undefined) - // Ok(JsValue::Undefined) - }, - (env.clone(), parameter_name), - ) - .length(1) - .name("") - .build() - }; - - // 3. Perform map.[[DefineOwnProperty]](! ToString(𝔽(index)), PropertyDescriptor { - // [[Set]]: p, [[Get]]: g, [[Enumerable]]: false, [[Configurable]]: true }). - map.__define_own_property__( - index.into(), - PropertyDescriptor::builder() - .set(p) - .get(g) - .enumerable(false) - .configurable(true) - .build(), - context, - ) - .expect("[[DefineOwnProperty]] must not fail per the spec"); - } + let mut bindings = FxHashMap::default(); + let mut property_index = 0; + 'outer: for formal in formals { + for name in formal.names() { + if property_index >= len { + break 'outer; } + let binding_index = bindings.len() + 1; + let entry = bindings + .entry(name) + .or_insert((binding_index, property_index)); + entry.1 = property_index; + property_index += 1; } } + for (binding_index, property_index) in bindings.values() { + // 19.b.ii.1. Let g be MakeArgGetter(name, env). + // https://tc39.es/ecma262/#sec-makearggetter + let g = { + // 2. Let getter be ! CreateBuiltinFunction(getterClosure, 0, "", « »). + // 3. NOTE: getter is never directly accessible to ECMAScript code. + // 4. Return getter. + FunctionBuilder::closure_with_captures( + context, + // 1. Let getterClosure be a new Abstract Closure with no parameters that captures + // name and env and performs the following steps when called: + |_, _, captures, _| Ok(captures.0.get(captures.1)), + (env.clone(), *binding_index), + ) + .length(0) + .build() + }; + // 19.b.ii.2. Let p be MakeArgSetter(name, env). + // https://tc39.es/ecma262/#sec-makeargsetter + let p = { + // 2. Let setter be ! CreateBuiltinFunction(setterClosure, 1, "", « »). + // 3. NOTE: setter is never directly accessible to ECMAScript code. + // 4. Return setter. + FunctionBuilder::closure_with_captures( + context, + // 1. Let setterClosure be a new Abstract Closure with parameters (value) that captures + // name and env and performs the following steps when called: + |_, args, captures, _| { + let value = args.get(0).cloned().unwrap_or_default(); + captures.0.set(captures.1, value); + Ok(JsValue::undefined()) + }, + (env.clone(), *binding_index), + ) + .length(1) + .build() + }; + + // 19.b.ii.3. Perform map.[[DefineOwnProperty]](! ToString(𝔽(index)), PropertyDescriptor { + // [[Set]]: p, [[Get]]: g, [[Enumerable]]: false, [[Configurable]]: true }). + map.__define_own_property__( + PropertyKey::from(*property_index), + PropertyDescriptor::builder() + .set(p) + .get(g) + .enumerable(false) + .configurable(true) + .build(), + context, + ) + .expect("Defining new own properties for a new ordinary object cannot fail"); + } // 20. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, @@ -254,7 +270,7 @@ impl Arguments { .configurable(true), context, ) - .expect("DefinePropertyOrThrow must not fail per the spec"); + .expect("Defining new own properties for a new ordinary object cannot fail"); // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). @@ -267,7 +283,7 @@ impl Arguments { .configurable(true), context, ) - .expect("DefinePropertyOrThrow must not fail per the spec"); + .expect("Defining new own properties for a new ordinary object cannot fail"); // 22. Return obj. obj diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 82b885a06f3..fede86466e8 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -11,11 +11,10 @@ //! [spec]: https://tc39.es/ecma262/#sec-function-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function -use super::JsArgs; use crate::{ - builtins::BuiltIn, + builtins::{BuiltIn, JsArgs}, context::StandardObjects, - environment::lexical_environment::Environment, + environments::DeclarativeEnvironmentStack, gc::{self, Finalize, Gc, Trace}, object::{ internal_methods::get_prototype_from_constructor, JsObject, NativeObject, Object, @@ -177,7 +176,11 @@ pub enum Function { }, VmOrdinary { code: Gc, - environment: Environment, + environments: DeclarativeEnvironmentStack, + }, + VmGenerator { + code: Gc, + environments: DeclarativeEnvironmentStack, }, } @@ -193,6 +196,7 @@ impl Function { match self { Self::Native { constructor, .. } | Self::Closure { constructor, .. } => *constructor, Self::VmOrdinary { code, .. } => code.constructor, + Self::VmGenerator { code, .. } => code.constructor, } } } @@ -459,6 +463,10 @@ impl BuiltInFunctionObject { } (Function::VmOrdinary { .. }, Some(name)) => Ok(format!("[Function: {name}]").into()), (Function::VmOrdinary { .. }, None) => Ok("[Function (anonymous)]".into()), + (Function::VmGenerator { .. }, Some(name)) => { + Ok(format!("[Function*: {}]", &name).into()) + } + (Function::VmGenerator { .. }, None) => Ok("[Function* (anonymous)]".into()), _ => Ok("TODO".into()), } } diff --git a/boa/src/builtins/generator/mod.rs b/boa/src/builtins/generator/mod.rs new file mode 100644 index 00000000000..8debb396cca --- /dev/null +++ b/boa/src/builtins/generator/mod.rs @@ -0,0 +1,412 @@ +//! This module implements the global `Generator` object. +//! +//! A Generator is an instance of a generator function and conforms to both the Iterator and Iterable interfaces. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! - [MDN documentation][mdn] +//! +//! [spec]: https://tc39.es/ecma262/#sec-generator-objects +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator + +use crate::{ + builtins::{BuiltIn, JsArgs}, + environments::DeclarativeEnvironmentStack, + gc::{Finalize, Trace}, + object::{ConstructorBuilder, JsObject, ObjectData}, + property::{Attribute, PropertyDescriptor}, + symbol::WellKnownSymbols, + value::JsValue, + vm::{CallFrame, GeneratorResumeKind, ReturnType}, + BoaProfiler, Context, JsResult, +}; +use gc::{Gc, GcCell}; + +use super::iterable::create_iter_result_object; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum GeneratorState { + Undefined, + SuspendedStart, + SuspendedYield, + Executing, + Completed, +} + +#[derive(Debug, Clone, Finalize, Trace)] +pub(crate) struct GeneratorContext { + pub(crate) environments: DeclarativeEnvironmentStack, + pub(crate) call_frame: CallFrame, + pub(crate) stack: Vec, +} + +/// The internal representation on a `Generator` object. +#[derive(Debug, Clone, Finalize, Trace)] +pub struct Generator { + /// The `[[GeneratorState]]` internal slot. + #[unsafe_ignore_trace] + pub(crate) state: GeneratorState, + + // The `[[GeneratorContext]]` internal slot. + pub(crate) context: Option>>, +} + +impl BuiltIn for Generator { + const NAME: &'static str = "Generator"; + + const ATTRIBUTE: Attribute = Attribute::NON_ENUMERABLE.union(Attribute::CONFIGURABLE); + + fn init(context: &mut Context) -> JsValue { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + let iterator_prototype = context.iterator_prototypes().iterator_prototype(); + + let generator_function_prototype = context + .standard_objects() + .generator_function_object() + .prototype(); + + let obj = ConstructorBuilder::with_standard_object( + context, + Self::constructor, + context.standard_objects().generator_object().clone(), + ) + .name(Self::NAME) + .length(Self::LENGTH) + .property( + WellKnownSymbols::to_string_tag(), + "Generator", + Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .method(Self::next, "next", 1) + .method(Self::r#return, "return", 1) + .method(Self::throw, "throw", 1) + .inherit(iterator_prototype) + .build(); + + context + .standard_objects() + .generator_object() + .prototype + .insert_property( + "constructor", + PropertyDescriptor::builder() + .value(generator_function_prototype) + .writable(false) + .enumerable(false) + .configurable(true), + ); + + obj.into() + } +} + +impl Generator { + pub(crate) const LENGTH: usize = 0; + + #[allow(clippy::unnecessary_wraps)] + pub(crate) fn constructor( + _: &JsValue, + _: &[JsValue], + context: &mut Context, + ) -> JsResult { + let prototype = context.standard_objects().generator_object().prototype(); + + let this = JsObject::from_proto_and_data( + prototype, + ObjectData::generator(Self { + state: GeneratorState::Undefined, + context: None, + }), + ); + + Ok(this.into()) + } + + /// `Generator.prototype.next ( value )` + /// + /// The `next()` method returns an object with two properties done and value. + /// You can also provide a parameter to the next method to send a value to the generator. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.next + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next + pub(crate) fn next( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Return ? GeneratorResume(this value, value, empty). + match this.as_object() { + Some(obj) if obj.is_generator() => { + Self::generator_resume(obj, args.get_or_undefined(0), context) + } + _ => context.throw_type_error("Generator.prototype.next called on non generator"), + } + } + + /// `Generator.prototype.return ( value )` + /// + /// The `return()` method returns the given value and finishes the generator. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.return + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return + pub(crate) fn r#return( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let g be the this value. + // 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + // 3. Return ? GeneratorResumeAbrupt(g, C, empty). + Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0).clone()), context) + } + + /// `Generator.prototype.throw ( exception )` + /// + /// The `throw()` method resumes the execution of a generator by throwing an error into it + /// and returns an object with two properties done and value. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.throw + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/throw + pub(crate) fn throw( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let g be the this value. + // 2. Let C be ThrowCompletion(exception). + // 3. Return ? GeneratorResumeAbrupt(g, C, empty). + Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0).clone()), context) + } + + /// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-generatorresume + pub(crate) fn generator_resume( + generator_obj: &JsObject, + value: &JsValue, + context: &mut Context, + ) -> JsResult { + // 1. Let state be ? GeneratorValidate(generator, generatorBrand). + let mut generator_obj_mut = generator_obj.borrow_mut(); + let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + })?; + let state = generator.state; + + if state == GeneratorState::Executing { + return Err(context.construct_type_error("Generator should not be executing")); + } + + // 2. If state is completed, return CreateIterResultObject(undefined, true). + if state == GeneratorState::Completed { + return Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )); + } + + // 3. Assert: state is either suspendedStart or suspendedYield. + assert!(matches!( + state, + GeneratorState::SuspendedStart | GeneratorState::SuspendedYield + )); + + // 4. Let genContext be generator.[[GeneratorContext]]. + // 5. Let methodContext be the running execution context. + // 6. Suspend methodContext. + // 7. Set generator.[[GeneratorState]] to executing. + generator.state = GeneratorState::Executing; + let first_execution = matches!(state, GeneratorState::SuspendedStart); + + let generator_context_cell = generator + .context + .take() + .expect("generator context cannot be empty here"); + let mut generator_context = generator_context_cell.borrow_mut(); + drop(generator_obj_mut); + + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + context.vm.push_frame(generator_context.call_frame.clone()); + if !first_execution { + context.vm.push(value); + } + + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; + + let result = context.run(); + + generator_context.call_frame = *context + .vm + .pop_frame() + .expect("generator call frame must exist"); + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + + let mut generator_obj_mut = generator_obj.borrow_mut(); + let generator = generator_obj_mut + .as_generator_mut() + .expect("already checked this object type"); + + match result { + Ok((value, ReturnType::Yield)) => { + generator.state = GeneratorState::SuspendedYield; + drop(generator_context); + generator.context = Some(generator_context_cell); + Ok(create_iter_result_object(value, false, context)) + } + Ok((value, _)) => { + generator.state = GeneratorState::Completed; + Ok(create_iter_result_object(value, true, context)) + } + Err(value) => { + generator.state = GeneratorState::Completed; + Err(value) + } + } + + // 8. Push genContext onto the execution context stack; genContext is now the running execution context. + // 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the result of the operation that suspended it. Let result be the value returned by the resumed computation. + // 10. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. + // 11. Return Completion(result). + } + + /// `27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-generatorresumeabrupt + pub(crate) fn generator_resume_abrupt( + this: &JsValue, + abrupt_completion: JsResult, + context: &mut Context, + ) -> JsResult { + // 1. Let state be ? GeneratorValidate(generator, generatorBrand). + let generator_obj = this.as_object().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + })?; + let mut generator_obj_mut = generator_obj.borrow_mut(); + let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + })?; + let mut state = generator.state; + + if state == GeneratorState::Executing { + return Err(context.construct_type_error("Generator should not be executing")); + } + + // 2. If state is suspendedStart, then + if state == GeneratorState::SuspendedStart { + // a. Set generator.[[GeneratorState]] to completed. + generator.state = GeneratorState::Completed; + // b. Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with generator can be discarded at this point. + generator.context = None; + // c. Set state to completed. + state = GeneratorState::Completed; + } + + // 3. If state is completed, then + if state == GeneratorState::Completed { + // a. If abruptCompletion.[[Type]] is return, then + if let Ok(value) = abrupt_completion { + // i. Return CreateIterResultObject(abruptCompletion.[[Value]], true). + return Ok(create_iter_result_object(value, true, context)); + } + // b. Return Completion(abruptCompletion). + return abrupt_completion; + } + + // 4. Assert: state is suspendedYield. + // 5. Let genContext be generator.[[GeneratorContext]]. + // 6. Let methodContext be the running execution context. + // 7. Suspend methodContext. + // 8. Set generator.[[GeneratorState]] to executing. + // 9. Push genContext onto the execution context stack; genContext is now the running execution context. + // 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation. + // 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. + // 12. Return Completion(result). + let generator_context_cell = generator + .context + .take() + .expect("generator context cannot be empty here"); + let mut generator_context = generator_context_cell.borrow_mut(); + + generator.state = GeneratorState::Executing; + drop(generator_obj_mut); + + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + context.vm.push_frame(generator_context.call_frame.clone()); + + let result = match abrupt_completion { + Ok(value) => { + context.vm.push(value); + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return; + context.run() + } + Err(value) => { + context.vm.push(value); + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; + context.run() + } + }; + generator_context.call_frame = *context + .vm + .pop_frame() + .expect("generator call frame must exist"); + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + + let mut generator_obj_mut = generator_obj.borrow_mut(); + let generator = generator_obj_mut + .as_generator_mut() + .expect("already checked this object type"); + + match result { + Ok((value, ReturnType::Yield)) => { + generator.state = GeneratorState::SuspendedYield; + drop(generator_context); + generator.context = Some(generator_context_cell); + Ok(create_iter_result_object(value, false, context)) + } + Ok((value, _)) => { + generator.state = GeneratorState::Completed; + Ok(create_iter_result_object(value, true, context)) + } + Err(value) => { + generator.state = GeneratorState::Completed; + Err(value) + } + } + } +} diff --git a/boa/src/builtins/generator_function/mod.rs b/boa/src/builtins/generator_function/mod.rs new file mode 100644 index 00000000000..02812e5612b --- /dev/null +++ b/boa/src/builtins/generator_function/mod.rs @@ -0,0 +1,130 @@ +//! This module implements the global `GeneratorFunction` object. +//! +//! The GeneratorFunction constructor creates a new generator function object. +//! In JavaScript, every generator function is actually a GeneratorFunction object. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! - [MDN documentation][mdn] +//! +//! [spec]: https://tc39.es/ecma262/#sec-generatorfunction-objects +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/GeneratorFunction + +use crate::{ + builtins::{function::Function, BuiltIn}, + context::StandardObjects, + object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + property::{Attribute, PropertyDescriptor}, + symbol::WellKnownSymbols, + value::JsValue, + BoaProfiler, Context, JsResult, +}; + +/// The internal representation on a `Generator` object. +#[derive(Debug, Clone, Copy)] +pub struct GeneratorFunction; + +impl BuiltIn for GeneratorFunction { + const NAME: &'static str = "GeneratorFunction"; + + const ATTRIBUTE: Attribute = Attribute::NON_ENUMERABLE.union(Attribute::WRITABLE); + + fn init(context: &mut Context) -> JsValue { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + let prototype = &context + .standard_objects() + .generator_function_object() + .prototype; + let constructor = &context + .standard_objects() + .generator_function_object() + .constructor; + + constructor.set_prototype(Some( + context.standard_objects().function_object().constructor(), + )); + let property = PropertyDescriptor::builder() + .value(1) + .writable(false) + .enumerable(false) + .configurable(true); + constructor.borrow_mut().insert("length", property); + let property = PropertyDescriptor::builder() + .value("GeneratorFunction") + .writable(false) + .enumerable(false) + .configurable(true); + constructor.borrow_mut().insert("name", property); + let property = PropertyDescriptor::builder() + .value( + context + .standard_objects() + .generator_function_object() + .prototype(), + ) + .writable(false) + .enumerable(false) + .configurable(false); + constructor.borrow_mut().insert("prototype", property); + constructor.borrow_mut().data = ObjectData::function(Function::Native { + function: Self::constructor, + constructor: true, + }); + + prototype.set_prototype(Some( + context.standard_objects().function_object().prototype(), + )); + let property = PropertyDescriptor::builder() + .value( + context + .standard_objects() + .generator_function_object() + .constructor(), + ) + .writable(false) + .enumerable(false) + .configurable(true); + prototype.borrow_mut().insert("constructor", property); + let property = PropertyDescriptor::builder() + .value(context.standard_objects().generator_object().prototype()) + .writable(false) + .enumerable(false) + .configurable(true); + prototype.borrow_mut().insert("prototype", property); + let property = PropertyDescriptor::builder() + .value("GeneratorFunction") + .writable(false) + .enumerable(false) + .configurable(true); + prototype + .borrow_mut() + .insert(WellKnownSymbols::to_string_tag(), property); + + JsValue::Null + } +} + +impl GeneratorFunction { + pub(crate) fn constructor( + new_target: &JsValue, + _: &[JsValue], + context: &mut Context, + ) -> JsResult { + let prototype = get_prototype_from_constructor( + new_target, + StandardObjects::generator_function_object, + context, + )?; + + let this = JsObject::from_proto_and_data( + prototype, + ObjectData::function(Function::Native { + function: |_, _, _| Ok(JsValue::undefined()), + constructor: true, + }), + ); + + Ok(this.into()) + } +} diff --git a/boa/src/builtins/global_this/mod.rs b/boa/src/builtins/global_this/mod.rs index 3de2be3ca2e..b483158f538 100644 --- a/boa/src/builtins/global_this/mod.rs +++ b/boa/src/builtins/global_this/mod.rs @@ -28,6 +28,6 @@ impl BuiltIn for GlobalThis { fn init(context: &mut Context) -> JsValue { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - context.global_object().into() + context.global_object().clone().into() } } diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index fbdef7765f5..65795897ed4 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -10,6 +10,8 @@ pub mod dataview; pub mod date; pub mod error; pub mod function; +pub mod generator; +pub mod generator_function; pub mod global_this; pub mod infinity; pub mod intl; @@ -64,7 +66,9 @@ pub(crate) use self::{ }; use crate::{ - builtins::array_buffer::ArrayBuffer, + builtins::{ + array_buffer::ArrayBuffer, generator::Generator, generator_function::GeneratorFunction, + }, property::{Attribute, PropertyDescriptor}, Context, JsValue, }; @@ -101,11 +105,11 @@ fn init_builtin(context: &mut Context) { .value(value) .writable(B::ATTRIBUTE.writable()) .enumerable(B::ATTRIBUTE.enumerable()) - .configurable(B::ATTRIBUTE.configurable()); + .configurable(B::ATTRIBUTE.configurable()) + .build(); context - .global_object() - .borrow_mut() - .insert(B::NAME, property); + .global_bindings_mut() + .insert(B::NAME.into(), property); } /// Initializes built-in objects and functions @@ -162,6 +166,9 @@ pub fn init(context: &mut Context) { Reflect }; + Generator::init(context); + GeneratorFunction::init(context); + #[cfg(feature = "console")] init_builtin::(context); } diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 5bf33f451a4..0cf5e300713 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -13,13 +13,12 @@ //! [spec]: https://tc39.es/ecma262/#sec-number-object //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number -use super::string::is_trimmable_whitespace; -use super::JsArgs; -use crate::context::StandardObjects; -use crate::object::JsObject; use crate::{ - builtins::{function::make_builtin_fn, BuiltIn}, - object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, + builtins::{string::is_trimmable_whitespace, BuiltIn, JsArgs}, + context::StandardObjects, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, + }, property::Attribute, value::{AbstractRelation, IntegerOrInfinity, JsValue}, BoaProfiler, Context, JsResult, @@ -39,12 +38,6 @@ const BUF_SIZE: usize = 2200; #[derive(Debug, Clone, Copy)] pub(crate) struct Number; -/// Maximum number of arguments expected to the builtin parseInt() function. -const PARSE_INT_MAX_ARG_COUNT: usize = 2; - -/// Maximum number of arguments expected to the builtin parseFloat() function. -const PARSE_FLOAT_MAX_ARG_COUNT: usize = 1; - impl BuiltIn for Number { const NAME: &'static str = "Number"; @@ -83,23 +76,10 @@ impl BuiltIn for Number { .static_method(Self::number_is_integer, "isInteger", 1) .build(); - let global = context.global_object(); - make_builtin_fn( - Self::parse_int, - "parseInt", - &global, - PARSE_INT_MAX_ARG_COUNT, - context, - ); - make_builtin_fn( - Self::parse_float, - "parseFloat", - &global, - PARSE_FLOAT_MAX_ARG_COUNT, - context, - ); - make_builtin_fn(Self::global_is_finite, "isFinite", &global, 1, context); - make_builtin_fn(Self::global_is_nan, "isNaN", &global, 1, context); + context.register_global_builtin_function("parseInt", 2, Self::parse_int); + context.register_global_builtin_function("parseFloat", 1, Self::parse_float); + context.register_global_builtin_function("isFinite", 1, Self::global_is_finite); + context.register_global_builtin_function("isNaN", 1, Self::global_is_nan); number_object.into() } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index cfd532b416a..0a24bc97c6a 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -308,7 +308,7 @@ impl RegExp { pub(crate) fn create(p: JsValue, f: JsValue, context: &mut Context) -> JsResult { // 1. Let obj be ? RegExpAlloc(%RegExp%). let obj = Self::alloc( - &context.global_object().get(Self::NAME, context)?, + &context.global_object().clone().get(Self::NAME, context)?, &[], context, )?; diff --git a/boa/src/builtins/set/mod.rs b/boa/src/builtins/set/mod.rs index 35c2da665f7..c82d9c16b0f 100644 --- a/boa/src/builtins/set/mod.rs +++ b/boa/src/builtins/set/mod.rs @@ -330,7 +330,7 @@ impl Set { let this_arg = args.get_or_undefined(1); // TODO: if condition should also check that we are not in strict mode let this_arg = if this_arg.is_undefined() { - JsValue::Object(context.global_object()) + context.global_object().clone().into() } else { this_arg.clone() }; diff --git a/boa/src/bytecompiler.rs b/boa/src/bytecompiler.rs index b98b977e862..df5c9840700 100644 --- a/boa/src/bytecompiler.rs +++ b/boa/src/bytecompiler.rs @@ -1,19 +1,20 @@ use crate::{ builtins::function::ThisMode, + environments::BindingLocator, gc::Gc, syntax::ast::{ node::{ declaration::{BindingPatternTypeArray, BindingPatternTypeObject, DeclarationPattern}, iteration::IterableLoopInitializer, template::TemplateElement, - Declaration, GetConstField, GetField, MethodDefinitionKind, PropertyDefinition, + Declaration, GetConstField, GetField, MethodDefinition, PropertyDefinition, PropertyName, StatementList, }, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, Const, Node, }, - vm::{CodeBlock, Opcode}, - JsBigInt, JsString, JsValue, + vm::{BindingOpcode, CodeBlock, Opcode}, + Context, JsBigInt, JsResult, JsString, JsValue, }; use boa_interner::{Interner, Sym}; use rustc_hash::FxHashMap; @@ -38,6 +39,9 @@ struct JumpControlInfo { kind: JumpControlInfoKind, breaks: Vec