diff --git a/boa_cli/src/debug/function.rs b/boa_cli/src/debug/function.rs index ba4425b839..3c6736da8c 100644 --- a/boa_cli/src/debug/function.rs +++ b/boa_cli/src/debug/function.rs @@ -159,10 +159,26 @@ fn trace(_: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult) -> JsResult { + let value = args.get_or_undefined(0); + let traceable = args.get_or_undefined(1).to_boolean(); + + let Some(callable) = value.as_callable() else { + return Err(JsNativeError::typ() + .with_message("expected callable object") + .into()); + }; + + set_trace_flag_in_function_object(callable, traceable)?; + + Ok(value.clone()) +} + pub(super) fn create_object(context: &mut Context<'_>) -> JsObject { ObjectInitializer::new(context) .function(NativeFunction::from_fn_ptr(flowgraph), "flowgraph", 1) .function(NativeFunction::from_fn_ptr(bytecode), "bytecode", 1) .function(NativeFunction::from_fn_ptr(trace), "trace", 1) + .function(NativeFunction::from_fn_ptr(traceable), "traceable", 2) .build() } diff --git a/boa_engine/src/builtins/async_function/mod.rs b/boa_engine/src/builtins/async_function/mod.rs index b796d3cbb6..5dbc7f2b58 100644 --- a/boa_engine/src/builtins/async_function/mod.rs +++ b/boa_engine/src/builtins/async_function/mod.rs @@ -70,7 +70,7 @@ impl BuiltInConstructor for AsyncFunction { context .intrinsics() .constructors() - .generator_function() + .async_function() .constructor() }); BuiltInFunctionObject::create_dynamic_function( diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs index 1520db5a01..872767db95 100644 --- a/boa_engine/src/builtins/async_generator/mod.rs +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -490,49 +490,22 @@ impl AsyncGenerator { .expect("already checked before") .state = AsyncGeneratorState::Executing; + let (value, resume_kind) = match completion { + (Ok(value), r#return) => ( + value, + if r#return { + GeneratorResumeKind::Return + } else { + GeneratorResumeKind::Normal + }, + ), + (Err(value), _) => (value.to_opaque(context), GeneratorResumeKind::Throw), + }; // 6. Push genContext onto the execution context stack; genContext is now the running execution context. - std::mem::swap( - &mut context.vm.environments, - &mut generator_context.environments, - ); - std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); - std::mem::swap( - &mut context.vm.active_function, - &mut generator_context.active_function, - ); - let old_realm = context.enter_realm(generator_context.realm.clone()); - context.vm.push_frame(generator_context.call_frame.clone()); + let result = generator_context.resume(Some(value), resume_kind, context); // 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation. - match completion { - (Ok(value), r#return) => { - context.vm.push(value); - if r#return { - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return; - } else { - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; - } - } - (Err(value), _) => { - let value = value.to_opaque(context); - context.vm.push(value); - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; - } - } - let result = context.run(); - - std::mem::swap( - &mut context.vm.environments, - &mut generator_context.environments, - ); - std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); - generator_context.call_frame = context.vm.pop_frame().expect("generator frame must exist"); - std::mem::swap( - &mut context.vm.active_function, - &mut generator_context.active_function, - ); - context.enter_realm(old_realm); generator .borrow_mut() @@ -543,7 +516,8 @@ impl AsyncGenerator { // 8. Assert: result is never an abrupt completion. assert!(!result.is_throw_completion()); - // 9. Assert: When we return here, genContext has already been removed from the execution context stack and callerContext is the currently running execution context. + // 9. Assert: When we return here, genContext has already been removed from the execution context stack and + // callerContext is the currently running execution context. // 10. Return unused. } diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 2d30ffcf05..82c91eb685 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -416,18 +416,6 @@ impl Function { } } - /// Returns the promise capability if the function is an async function. - pub(crate) const fn get_promise_capability(&self) -> Option<&PromiseCapability> { - if let FunctionKind::Async { - promise_capability, .. - } = &self.kind - { - Some(promise_capability) - } else { - None - } - } - /// Sets the class object. pub(crate) fn set_class_object(&mut self, object: JsObject) { match &mut self.kind { diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index d0cbe860df..408fb54d3a 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -44,12 +44,72 @@ pub(crate) enum GeneratorState { #[derive(Debug, Clone, Finalize, Trace)] pub(crate) struct GeneratorContext { pub(crate) environments: DeclarativeEnvironmentStack, - pub(crate) call_frame: CallFrame, pub(crate) stack: Vec, pub(crate) active_function: Option, + pub(crate) call_frame: Option, pub(crate) realm: Realm, } +impl GeneratorContext { + /// Creates a new `GeneratorContext` from the raw `Context` state components. + pub(crate) fn new( + environments: DeclarativeEnvironmentStack, + stack: Vec, + active_function: Option, + call_frame: CallFrame, + realm: Realm, + ) -> Self { + Self { + environments, + stack, + active_function, + call_frame: Some(call_frame), + realm, + } + } + + /// Creates a new `GeneratorContext` from the current `Context` state. + pub(crate) fn from_current(context: &mut Context<'_>) -> Self { + Self { + environments: context.vm.environments.clone(), + call_frame: Some(context.vm.frame().clone()), + stack: context.vm.stack.clone(), + active_function: context.vm.active_function.clone(), + realm: context.realm().clone(), + } + } + + /// Resumes execution with `GeneratorContext` as the current execution context. + pub(crate) fn resume( + &mut self, + value: Option, + resume_kind: GeneratorResumeKind, + context: &mut Context<'_>, + ) -> CompletionRecord { + std::mem::swap(&mut context.vm.environments, &mut self.environments); + std::mem::swap(&mut context.vm.stack, &mut self.stack); + std::mem::swap(&mut context.vm.active_function, &mut self.active_function); + context.swap_realm(&mut self.realm); + context + .vm + .push_frame(self.call_frame.take().expect("should have a call frame")); + context.vm.frame_mut().generator_resume_kind = resume_kind; + if let Some(value) = value { + context.vm.push(value); + } + + let result = context.run(); + + std::mem::swap(&mut context.vm.environments, &mut self.environments); + std::mem::swap(&mut context.vm.stack, &mut self.stack); + std::mem::swap(&mut context.vm.active_function, &mut self.active_function); + context.swap_realm(&mut self.realm); + self.call_frame = context.vm.pop_frame(); + assert!(self.call_frame.is_some()); + result + } +} + /// The internal representation of a `Generator` object. #[derive(Debug, Finalize, Trace)] pub struct Generator { @@ -117,15 +177,19 @@ impl Generator { args: &[JsValue], context: &mut Context<'_>, ) -> JsResult { + // Extracted `GeneratorValidate` since we don't need to worry about validation + // of async functions. + let generator = Self::generator_validate(this)?; + // 1. Return ? GeneratorResume(this value, value, empty). - this.as_object().map_or_else( - || { - Err(JsNativeError::typ() - .with_message("Generator.prototype.next called on non generator") - .into()) - }, - |obj| Self::generator_resume(obj, args.get_or_undefined(0), context), - ) + let completion = + Self::generator_resume(generator, args.get_or_undefined(0).clone(), context); + + match completion { + CompletionRecord::Return(value) => Ok(create_iter_result_object(value, false, context)), + CompletionRecord::Normal(value) => Ok(create_iter_result_object(value, true, context)), + CompletionRecord::Throw(err) => Err(err), + } } /// `Generator.prototype.return ( value )` @@ -144,9 +208,17 @@ impl Generator { context: &mut Context<'_>, ) -> JsResult { // 1. Let g be the this value. + let generator = Self::generator_validate(this)?; // 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) + let completion = + Self::generator_resume_abrupt(generator, Ok(args.get_or_undefined(0).clone()), context); + + match completion { + CompletionRecord::Return(value) => Ok(create_iter_result_object(value, false, context)), + CompletionRecord::Normal(value) => Ok(create_iter_result_object(value, true, context)), + CompletionRecord::Throw(err) => Err(err), + } } /// `Generator.prototype.throw ( exception )` @@ -166,46 +238,67 @@ impl Generator { context: &mut Context<'_>, ) -> JsResult { // 1. Let g be the this value. + let generator = Self::generator_validate(this)?; // 2. Let C be ThrowCompletion(exception). // 3. Return ? GeneratorResumeAbrupt(g, C, empty). - Self::generator_resume_abrupt( - this, + let completion = Self::generator_resume_abrupt( + generator, Err(JsError::from_opaque(args.get_or_undefined(0).clone())), context, - ) + ); + + match completion { + CompletionRecord::Return(value) => Ok(create_iter_result_object(value, false, context)), + CompletionRecord::Normal(value) => Ok(create_iter_result_object(value, true, context)), + CompletionRecord::Throw(err) => Err(err), + } } - /// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )` + /// `27.5.3.3 GeneratorValidate ( generator, 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). + /// [spec]:https://tc39.es/ecma262/#sec-generatorvalidate + fn generator_validate(gen: &JsValue) -> JsResult<&JsObject> { + let generator_obj = gen.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Generator method called on non generator") + })?; let mut generator_obj_mut = generator_obj.borrow_mut(); let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| { JsNativeError::typ().with_message("generator resumed on non generator object") })?; - let state = generator.state; - if state == GeneratorState::Executing { - return Err(JsNativeError::typ() + if generator.state == GeneratorState::Executing { + Err(JsNativeError::typ() .with_message("Generator should not be executing") - .into()); + .into()) + } else { + Ok(generator_obj) } + } + + /// `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( + gen: &JsObject, + value: JsValue, + context: &mut Context<'_>, + ) -> CompletionRecord { + // 1. Let state be ? GeneratorValidate(generator, generatorBrand). + let mut generator_obj_mut = gen.borrow_mut(); + let generator = generator_obj_mut + .as_generator_mut() + .expect("already validated that this is a generator"); + let state = generator.state; // 2. If state is completed, return CreateIterResultObject(undefined, true). if state == GeneratorState::Completed { - return Ok(create_iter_result_object( - JsValue::undefined(), - true, - context, - )); + return CompletionRecord::Normal(JsValue::undefined()); } // 3. Assert: state is either suspendedStart or suspendedYield. @@ -227,64 +320,30 @@ impl Generator { .expect("generator context cannot be empty here"); drop(generator_obj_mut); - std::mem::swap( - &mut context.vm.environments, - &mut generator_context.environments, - ); - std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); - std::mem::swap( - &mut context.vm.active_function, - &mut generator_context.active_function, - ); - let old_realm = context.enter_realm(generator_context.realm.clone()); - context.vm.push_frame(generator_context.call_frame.clone()); - if !first_execution { - context.vm.push(value.clone()); - } - - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; - - let record = context.run(); - - generator_context.call_frame = context - .vm - .pop_frame() - .expect("generator call frame must exist"); - std::mem::swap( - &mut context.vm.environments, - &mut generator_context.environments, - ); - std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); - std::mem::swap( - &mut context.vm.active_function, - &mut generator_context.active_function, + let record = generator_context.resume( + (!first_execution).then_some(value), + GeneratorResumeKind::Normal, + context, ); - context.enter_realm(old_realm); - let mut generator_obj_mut = generator_obj.borrow_mut(); + let mut generator_obj_mut = gen.borrow_mut(); let generator = generator_obj_mut .as_generator_mut() .expect("already checked this object type"); - match record { - CompletionRecord::Return(value) => { - generator.state = GeneratorState::SuspendedYield; + generator.state = match record { + CompletionRecord::Return(_) => { generator.context = Some(generator_context); - Ok(create_iter_result_object(value, false, context)) - } - CompletionRecord::Normal(value) => { - generator.state = GeneratorState::Completed; - Ok(create_iter_result_object(value, true, context)) + GeneratorState::SuspendedYield } - CompletionRecord::Throw(err) => { - generator.state = GeneratorState::Completed; - Err(err) - } - } + CompletionRecord::Normal(_) | CompletionRecord::Throw(_) => GeneratorState::Completed, + }; + // 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). + record } /// `27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )` @@ -294,26 +353,17 @@ impl Generator { /// /// [spec]: https://tc39.es/ecma262/#sec-generatorresumeabrupt pub(crate) fn generator_resume_abrupt( - this: &JsValue, + gen: &JsObject, abrupt_completion: JsResult, context: &mut Context<'_>, - ) -> JsResult { + ) -> CompletionRecord { // 1. Let state be ? GeneratorValidate(generator, generatorBrand). - let generator_obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("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(|| { - JsNativeError::typ().with_message("generator resumed on non generator object") - })?; + let mut generator_obj_mut = gen.borrow_mut(); + let generator = generator_obj_mut + .as_generator_mut() + .expect("already validated the generator object"); let mut state = generator.state; - if state == GeneratorState::Executing { - return Err(JsNativeError::typ() - .with_message("Generator should not be executing") - .into()); - } - // 2. If state is suspendedStart, then if state == GeneratorState::SuspendedStart { // a. Set generator.[[GeneratorState]] to completed. @@ -327,12 +377,10 @@ impl Generator { // 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)); - } + // i. Return CreateIterResultObject(abruptCompletion.[[Value]], true). // b. Return Completion(abruptCompletion). - return abrupt_completion; + return abrupt_completion + .map_or_else(CompletionRecord::Throw, CompletionRecord::Normal); } // 4. Assert: state is suspendedYield. @@ -352,65 +400,26 @@ impl Generator { generator.state = GeneratorState::Executing; drop(generator_obj_mut); - std::mem::swap( - &mut context.vm.environments, - &mut generator_context.environments, - ); - std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); - std::mem::swap( - &mut context.vm.active_function, - &mut generator_context.active_function, - ); - let old_realm = context.enter_realm(generator_context.realm.clone()); - context.vm.push_frame(generator_context.call_frame.clone()); - - let completion_record = match abrupt_completion { - Ok(value) => { - context.vm.push(value); - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return; - context.run() - } - Err(value) => { - let value = value.to_opaque(context); - context.vm.push(value); - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; - context.run() - } + let (value, resume_kind) = match abrupt_completion { + Ok(value) => (value, GeneratorResumeKind::Return), + Err(err) => (err.to_opaque(context), GeneratorResumeKind::Throw), }; - generator_context.call_frame = context - .vm - .pop_frame() - .expect("generator call frame must exist"); - std::mem::swap( - &mut context.vm.environments, - &mut generator_context.environments, - ); - std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); - std::mem::swap( - &mut context.vm.active_function, - &mut generator_context.active_function, - ); - context.enter_realm(old_realm); - let mut generator_obj_mut = generator_obj.borrow_mut(); + let record = generator_context.resume(Some(value), resume_kind, context); + + let mut generator_obj_mut = gen.borrow_mut(); let generator = generator_obj_mut .as_generator_mut() .expect("already checked this object type"); - match completion_record { - CompletionRecord::Return(value) => { - generator.state = GeneratorState::SuspendedYield; + generator.state = match record { + CompletionRecord::Return(_) => { generator.context = Some(generator_context); - Ok(create_iter_result_object(value, false, context)) + GeneratorState::SuspendedYield } - CompletionRecord::Normal(value) => { - generator.state = GeneratorState::Completed; - Ok(create_iter_result_object(value, true, context)) - } - CompletionRecord::Throw(err) => { - generator.state = GeneratorState::Completed; - Err(err) - } - } + CompletionRecord::Normal(_) | CompletionRecord::Throw(_) => GeneratorState::Completed, + }; + + record } } diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 4f727c4352..19e16f1f29 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -536,6 +536,13 @@ impl<'host> Context<'host> { // ==== Private API ==== +impl Context<'_> { + /// Swaps the currently active realm with `realm`. + pub(crate) fn swap_realm(&mut self, realm: &mut Realm) { + std::mem::swap(&mut self.realm, realm); + } +} + #[cfg(feature = "intl")] impl<'host> Context<'host> { /// Get the ICU related utilities diff --git a/boa_engine/src/environments/runtime.rs b/boa_engine/src/environments/runtime.rs index a9a56ba53b..ddf9ae7946 100644 --- a/boa_engine/src/environments/runtime.rs +++ b/boa_engine/src/environments/runtime.rs @@ -552,26 +552,6 @@ impl DeclarativeEnvironmentStack { .expect("environment stack is cannot be empty") } - /// Get the most outer function environment slots. - /// - /// # Panics - /// - /// Panics if no environment exists on the stack. - pub(crate) fn current_function_slots(&self) -> &EnvironmentSlots { - for env in self - .stack - .iter() - .filter_map(Environment::as_declarative) - .rev() - { - if let Some(slots) = &env.slots { - return slots; - } - } - - panic!("global environment must exist") - } - /// Get the most outer environment. /// /// # Panics diff --git a/boa_engine/src/vm/call_frame/mod.rs b/boa_engine/src/vm/call_frame/mod.rs index 7e69fae3fc..fb92fc5093 100644 --- a/boa_engine/src/vm/call_frame/mod.rs +++ b/boa_engine/src/vm/call_frame/mod.rs @@ -5,7 +5,7 @@ mod abrupt_record; mod env_stack; -use crate::{object::JsObject, vm::CodeBlock}; +use crate::{builtins::promise::PromiseCapability, object::JsObject, vm::CodeBlock}; use boa_gc::{Finalize, Gc, Trace}; use thin_vec::ThinVec; @@ -21,7 +21,7 @@ pub struct CallFrame { #[unsafe_ignore_trace] pub(crate) abrupt_completion: Option, #[unsafe_ignore_trace] - pub(crate) early_return: Option, + pub(crate) r#yield: bool, pub(crate) pop_on_return: usize, // Tracks the number of environments in environment entry. // On abrupt returns this is used to decide how many environments need to be pop'ed. @@ -31,6 +31,7 @@ pub struct CallFrame { pub(crate) arg_count: usize, #[unsafe_ignore_trace] pub(crate) generator_resume_kind: GeneratorResumeKind, + pub(crate) promise_capability: Option, // When an async generator is resumed, the generator object is needed // to fulfill the steps 4.e-j in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart). @@ -52,10 +53,11 @@ impl CallFrame { pop_on_return: 0, env_stack: Vec::from([EnvStackEntry::new(0, max_length)]), abrupt_completion: None, - early_return: None, + r#yield: false, param_count: 0, arg_count: 0, generator_resume_kind: GeneratorResumeKind::Normal, + promise_capability: None, async_generator: None, iterators: ThinVec::new(), } @@ -107,10 +109,3 @@ pub(crate) enum GeneratorResumeKind { Throw, Return, } - -// An enum to mark whether a return is early due to Async or Yield -#[derive(Copy, Clone, Debug, PartialEq)] -pub(crate) enum EarlyReturnType { - Await, - Yield, -} diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index f2c8001342..c3ae0a17b5 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -824,7 +824,7 @@ impl JsObject { let context = &mut ContextCleanupGuard::new(context, realm, active_function); - let (code, mut environments, class_object, promise, async_, gen) = + let (code, mut environments, class_object, promise_cap, async_, gen) = match function_object.kind() { FunctionKind::Native { function, @@ -874,7 +874,7 @@ impl JsObject { code.clone(), environments.clone(), class_object.clone(), - Some(promise_capability.promise().clone()), + Some(promise_capability.clone()), true, false, ), @@ -1009,11 +1009,12 @@ impl JsObject { let param_count = code.params.as_ref().len(); - context.vm.push_frame( - CallFrame::new(code) - .with_param_count(param_count) - .with_arg_count(arg_count), - ); + let mut frame = CallFrame::new(code) + .with_param_count(param_count) + .with_arg_count(arg_count); + frame.promise_capability = promise_cap.clone(); + + context.vm.push_frame(frame); let result = context .run() @@ -1024,13 +1025,10 @@ impl JsObject { std::mem::swap(&mut environments, &mut context.vm.environments); std::mem::swap(&mut context.vm.stack, &mut stack); - if let Some(promise) = promise { - return Ok(promise.into()); - } - - let value = result?; - - let value = if gen { + if let Some(promise_cap) = promise_cap { + Ok(promise_cap.promise().clone().into()) + } else if gen { + result?; let proto = this_function_object .get(PROTOTYPE, context) .expect("generator must have a prototype property") @@ -1045,34 +1043,32 @@ impl JsObject { }, Clone::clone, ); + let data = if async_ { + ObjectData::async_generator(AsyncGenerator { + state: AsyncGeneratorState::SuspendedStart, + context: Some(GeneratorContext::new( + environments, + stack, + context.vm.active_function.clone(), + call_frame, + context.realm().clone(), + )), + queue: VecDeque::new(), + }) + } else { + ObjectData::generator(Generator { + state: GeneratorState::SuspendedStart, + context: Some(GeneratorContext::new( + environments, + stack, + context.vm.active_function.clone(), + call_frame, + context.realm().clone(), + )), + }) + }; - let generator = Self::from_proto_and_data( - proto, - if async_ { - ObjectData::async_generator(AsyncGenerator { - state: AsyncGeneratorState::SuspendedStart, - context: Some(GeneratorContext { - environments, - call_frame, - stack, - active_function: context.vm.active_function.clone(), - realm: context.realm().clone(), - }), - queue: VecDeque::new(), - }) - } else { - ObjectData::generator(Generator { - state: GeneratorState::SuspendedStart, - context: Some(GeneratorContext { - environments, - call_frame, - stack, - active_function: context.vm.active_function.clone(), - realm: context.realm().clone(), - }), - }) - }, - ); + let generator = Self::from_proto_and_data(proto, data); if async_ { let gen_clone = generator.clone(); @@ -1081,15 +1077,18 @@ impl JsObject { .as_async_generator_mut() .expect("must be object here"); let gen_context = gen.context.as_mut().expect("must exist"); - gen_context.call_frame.async_generator = Some(gen_clone); + // TODO: try to move this to the context itself. + gen_context + .call_frame + .as_mut() + .expect("should have a call frame initialized") + .async_generator = Some(gen_clone); } - generator.into() + Ok(generator.into()) } else { - value - }; - - Ok(value) + result + } } pub(crate) fn construct_internal( diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 868eba5a9d..baf40f4135 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -7,7 +7,7 @@ use crate::{ builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, environments::{DeclarativeEnvironment, DeclarativeEnvironmentStack}, - vm::{call_frame::EarlyReturnType, code_block::Readable}, + vm::code_block::Readable, Context, JsError, JsObject, JsResult, JsValue, }; #[cfg(feature = "fuzz")] @@ -98,6 +98,7 @@ impl Vm { /// # Panics /// /// If there is no frame, then this will panic. + #[track_caller] pub(crate) fn frame(&self) -> &CallFrame { self.frames.last().expect("no frame found") } @@ -107,6 +108,7 @@ impl Vm { /// # Panics /// /// If there is no frame, then this will panic. + #[track_caller] pub(crate) fn frame_mut(&mut self) -> &mut CallFrame { self.frames.last_mut().expect("no frame found") } @@ -189,19 +191,7 @@ impl Context<'_> { // If the current executing function is an async function we have to resolve/reject it's promise at the end. // The relevant spec section is 3. in [AsyncBlockStart](https://tc39.es/ecma262/#sec-asyncblockstart). - let promise_capability = self - .vm - .environments - .current_function_slots() - .as_function_slots() - .and_then(|slots| { - let slots_borrow = slots.borrow(); - let function_object = slots_borrow.function_object(); - let function = function_object.borrow(); - function - .as_function() - .and_then(|f| f.get_promise_capability().cloned()) - }); + let promise_capability = self.vm.frame().promise_capability.clone(); let execution_completion = loop { // 1. Exit the execution loop if program counter ever is equal to or exceeds the amount of instructions @@ -300,20 +290,10 @@ impl Context<'_> { }; // Early return immediately after loop. - if let Some(early_return) = self.vm.frame().early_return { - match early_return { - EarlyReturnType::Await => { - let result = self.vm.pop(); - self.vm.stack.truncate(self.vm.frame().fp); - self.vm.frame_mut().early_return = None; - return CompletionRecord::Normal(result); - } - EarlyReturnType::Yield => { - let result = self.vm.pop(); - self.vm.frame_mut().early_return = None; - return CompletionRecord::Return(result); - } - } + if self.vm.frame().r#yield { + let result = self.vm.pop(); + self.vm.frame_mut().r#yield = false; + return CompletionRecord::Return(result); } #[cfg(feature = "trace")] @@ -359,7 +339,6 @@ impl Context<'_> { }; if let Some(promise) = promise_capability { - // Step 3.e-g in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart) match execution_completion { CompletionType::Normal => { promise @@ -383,6 +362,7 @@ impl Context<'_> { } } } else if let Some(generator_object) = self.vm.frame().async_generator.clone() { + // Step 3.e-g in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart) let mut generator_object_mut = generator_object.borrow_mut(); let generator = generator_object_mut .as_async_generator_mut() diff --git a/boa_engine/src/vm/opcode/await_stm/mod.rs b/boa_engine/src/vm/opcode/await_stm/mod.rs index 9e401e8a11..01dcbde700 100644 --- a/boa_engine/src/vm/opcode/await_stm/mod.rs +++ b/boa_engine/src/vm/opcode/await_stm/mod.rs @@ -1,14 +1,10 @@ -use boa_gc::GcRefCell; +use boa_gc::{Gc, GcRefCell}; use crate::{ builtins::{generator::GeneratorContext, Promise}, native_function::NativeFunction, object::FunctionObjectBuilder, - vm::{ - call_frame::{EarlyReturnType, GeneratorResumeKind}, - opcode::Operation, - CompletionType, - }, + vm::{opcode::Operation, CompletionType, GeneratorResumeKind}, Context, JsArgs, JsResult, JsValue, }; @@ -33,13 +29,9 @@ impl Operation for Await { context, )?; - let generator_context = GeneratorContext { - environments: context.vm.environments.clone(), - call_frame: context.vm.frame().clone(), - stack: context.vm.stack.clone(), - active_function: context.vm.active_function.clone(), - realm: context.realm().clone(), - }; + let gen = GeneratorContext::from_current(context); + + let captures = Gc::new(GcRefCell::new(Some(gen))); // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). @@ -47,49 +39,22 @@ impl Operation for Await { context, NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { - let mut generator_context = std::mem::take(&mut *captures.borrow_mut()) - .expect("function should only be called once"); // a. Let prevContext be the running execution context. // b. Suspend prevContext. // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. - // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. - // f. Return undefined. - - std::mem::swap( - &mut context.vm.environments, - &mut generator_context.environments, - ); - std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); - std::mem::swap( - &mut context.vm.active_function, - &mut generator_context.active_function, - ); - let old_realm = context.enter_realm(generator_context.realm.clone()); - context.vm.push_frame(generator_context.call_frame.clone()); + let mut gen = captures.borrow_mut().take().expect("should only run once"); - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; - context.vm.push(args.get_or_undefined(0).clone()); - context.run(); - - context - .vm - .pop_frame() - .expect("generator call frame must exist"); - std::mem::swap( - &mut context.vm.environments, - &mut generator_context.environments, - ); - std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); - std::mem::swap( - &mut context.vm.active_function, - &mut generator_context.active_function, + gen.resume( + Some(args.get_or_undefined(0).clone()), + GeneratorResumeKind::Normal, + context, ); - context.enter_realm(old_realm); - + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // f. Return undefined. Ok(JsValue::undefined()) }, - GcRefCell::new(Some(generator_context.clone())), + captures.clone(), ), ) .name("") @@ -102,8 +67,6 @@ impl Operation for Await { context, NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { - let mut generator_context = std::mem::take(&mut *captures.borrow_mut()) - .expect("function should only be called once"); // a. Let prevContext be the running execution context. // b. Suspend prevContext. // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. @@ -111,40 +74,17 @@ impl Operation for Await { // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. // f. Return undefined. - std::mem::swap( - &mut context.vm.environments, - &mut generator_context.environments, - ); - std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); - std::mem::swap( - &mut context.vm.active_function, - &mut generator_context.active_function, - ); - let old_realm = context.enter_realm(generator_context.realm.clone()); - context.vm.push_frame(generator_context.call_frame.clone()); - - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; - context.vm.push(args.get_or_undefined(0).clone()); - context.run(); + let mut gen = captures.borrow_mut().take().expect("should only run once"); - context - .vm - .pop_frame() - .expect("generator call frame must exist"); - std::mem::swap( - &mut context.vm.environments, - &mut generator_context.environments, - ); - std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); - std::mem::swap( - &mut context.vm.active_function, - &mut generator_context.active_function, + gen.resume( + Some(args.get_or_undefined(0).clone()), + GeneratorResumeKind::Throw, + context, ); - context.enter_realm(old_realm); Ok(JsValue::undefined()) }, - GcRefCell::new(Some(generator_context)), + captures, ), ) .name("") @@ -161,7 +101,7 @@ impl Operation for Await { ); context.vm.push(JsValue::undefined()); - context.vm.frame_mut().early_return = Some(EarlyReturnType::Await); + context.vm.frame_mut().r#yield = true; Ok(CompletionType::Return) } } diff --git a/boa_engine/src/vm/opcode/generator/mod.rs b/boa_engine/src/vm/opcode/generator/mod.rs index a5fb7d41e7..267a218981 100644 --- a/boa_engine/src/vm/opcode/generator/mod.rs +++ b/boa_engine/src/vm/opcode/generator/mod.rs @@ -6,7 +6,7 @@ use crate::{ error::JsNativeError, string::utf16, vm::{ - call_frame::{AbruptCompletionRecord, EarlyReturnType, GeneratorResumeKind}, + call_frame::{AbruptCompletionRecord, GeneratorResumeKind}, opcode::Operation, CompletionType, }, @@ -155,7 +155,7 @@ impl Operation for GeneratorNextDelegate { context.vm.push(iterator.clone()); context.vm.push(next_method.clone()); context.vm.push(value); - context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield); + context.vm.frame_mut().r#yield = true; Ok(CompletionType::Return) } GeneratorResumeKind::Throw => { @@ -177,7 +177,7 @@ impl Operation for GeneratorNextDelegate { context.vm.push(iterator.clone()); context.vm.push(next_method.clone()); context.vm.push(value); - context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield); + context.vm.frame_mut().r#yield = true; return Ok(CompletionType::Return); } context.vm.frame_mut().pc = done_address as usize; @@ -207,7 +207,7 @@ impl Operation for GeneratorNextDelegate { context.vm.push(iterator.clone()); context.vm.push(next_method.clone()); context.vm.push(value); - context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield); + context.vm.frame_mut().r#yield = true; return Ok(CompletionType::Return); } context.vm.frame_mut().pc = done_address as usize; diff --git a/boa_engine/src/vm/opcode/generator/yield_stm.rs b/boa_engine/src/vm/opcode/generator/yield_stm.rs index 420cd6a58d..34622fa75f 100644 --- a/boa_engine/src/vm/opcode/generator/yield_stm.rs +++ b/boa_engine/src/vm/opcode/generator/yield_stm.rs @@ -1,5 +1,5 @@ use crate::{ - vm::{call_frame::EarlyReturnType, opcode::Operation, CompletionType}, + vm::{opcode::Operation, CompletionType}, Context, JsResult, }; @@ -15,7 +15,7 @@ impl Operation for Yield { const INSTRUCTION: &'static str = "INST - Yield"; fn execute(context: &mut Context<'_>) -> JsResult { - context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield); + context.vm.frame_mut().r#yield = true; Ok(CompletionType::Return) } } diff --git a/docs/boa_object.md b/docs/boa_object.md index 271e9e6a64..4f2fea040e 100644 --- a/docs/boa_object.md +++ b/docs/boa_object.md @@ -76,6 +76,45 @@ their instructions aren't traced. The `this` value can be changed as well as the arguments that are passed to the function. +### Function `$boa.function.traceable(func, mode)` + +Marks a single function as traceable on all future executions of the function. Both useful to mark +several functions as traceable and to trace functions that suspend their execution (async functions, +generators, async generators). + +#### Input + +```Javascript +function* g() { + yield 1; + yield 2; + yield 3; +} +$boa.function.traceable(g, true); +var iter = g(); +iter.next(); +iter.next(); +iter.next(); +``` + +#### Output + +```bash +1μs RestParameterPop +1μs PushUndefined undefined +2μs Yield undefined +4μs GetName 0000: 'a' 1 +0μs Yield 1 +1μs GeneratorNext undefined +1μs Pop +15μs GetName 0001: 'b' 2 +1μs Yield 2 +1μs GeneratorNext undefined +1μs Pop +4μs GetName 0002: 'c' 3 +1μs Yield 3 +``` + ## Function `$boa.function.flowgraph(func, options)` It can be used to get the instruction flowgraph, like the command-line flag.