diff --git a/boa_engine/src/bytecompiler/class.rs b/boa_engine/src/bytecompiler/class.rs index def85b471f9..de83caa9e32 100644 --- a/boa_engine/src/bytecompiler/class.rs +++ b/boa_engine/src/bytecompiler/class.rs @@ -70,6 +70,7 @@ impl ByteCompiler<'_, '_> { } else { if class.super_ref().is_some() { compiler.emit_opcode(Opcode::SuperCallDerived); + compiler.emit_opcode(Opcode::BindThisValue); } else { compiler.emit_opcode(Opcode::PushUndefined); } diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index a9ae59ce2ed..1808a5900c5 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -326,6 +326,7 @@ impl ByteCompiler<'_, '_> { super_call.arguments().len() as u32, ); } + self.emit_opcode(Opcode::BindThisValue); if !use_expr { self.emit_opcode(Opcode::Pop); diff --git a/boa_engine/src/object/internal_methods/bound_function.rs b/boa_engine/src/object/internal_methods/bound_function.rs index 114758654ea..8ff1da7dcf2 100644 --- a/boa_engine/src/object/internal_methods/bound_function.rs +++ b/boa_engine/src/object/internal_methods/bound_function.rs @@ -90,6 +90,7 @@ fn bound_function_exotic_construct( let bound_args = bound_function.args(); // 4. Let args be the list-concatenation of boundArgs and argumentsList. + let arguments_start_index = context.vm.stack.len() - argument_count; context .vm .insert_values_at(bound_args, arguments_start_index); diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 4618e8fd789..0cef1c11c52 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -578,6 +578,7 @@ impl CodeBlock { | Instruction::ImportCall | Instruction::GetReturnValue | Instruction::SetReturnValue + | Instruction::BindThisValue | Instruction::Nop => String::new(), Instruction::U16Operands @@ -637,8 +638,7 @@ impl CodeBlock { | Instruction::Reserved53 | Instruction::Reserved54 | Instruction::Reserved55 - | Instruction::Reserved56 - | Instruction::Reserved57 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved56 => unreachable!("Reserved opcodes are unrechable"), } } } diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs index 461e087e93b..e5495cafed0 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -457,6 +457,7 @@ impl CodeBlock { | Instruction::Exception | Instruction::MaybeException | Instruction::CheckReturn + | Instruction::BindThisValue | Instruction::Nop => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); @@ -521,8 +522,7 @@ impl CodeBlock { | Instruction::Reserved53 | Instruction::Reserved54 | Instruction::Reserved55 - | Instruction::Reserved56 - | Instruction::Reserved57 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved56 => unreachable!("Reserved opcodes are unrechable"), } } diff --git a/boa_engine/src/vm/opcode/environment/mod.rs b/boa_engine/src/vm/opcode/environment/mod.rs index ed31ab095c3..1aecd2e03d1 100644 --- a/boa_engine/src/vm/opcode/environment/mod.rs +++ b/boa_engine/src/vm/opcode/environment/mod.rs @@ -80,23 +80,14 @@ impl Operation for SuperCallPrepare { .get_this_environment() .as_function() .expect("super call must be in function environment"); - let new_target = this_env - .slots() - .new_target() - .expect("must have new target") - .clone(); let active_function = this_env.slots().function_object().clone(); let super_constructor = active_function .__get_prototype_of__(context) .expect("function object must have prototype"); - if let Some(constructor) = super_constructor { - context.vm.push(constructor); - } else { - context.vm.push(JsValue::Null); - } - context.vm.push(new_target); - + context + .vm + .push(super_constructor.map_or_else(JsValue::null, JsValue::from)); Ok(CompletionType::Normal) } } @@ -110,27 +101,14 @@ pub(crate) struct SuperCall; impl SuperCall { fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult { - let mut arguments = Vec::with_capacity(argument_count); - for _ in 0..argument_count { - arguments.push(context.vm.pop()); - } - arguments.reverse(); - - let new_target_value = context.vm.pop(); - let super_constructor = context.vm.pop(); - - let new_target = new_target_value - .as_object() - .expect("new target must be object"); - + let super_constructor_index = context.vm.stack.len() - argument_count - 1; + let super_constructor = context.vm.stack[super_constructor_index].clone(); let Some(super_constructor) = super_constructor.as_constructor() else { return Err(JsNativeError::typ() .with_message("super constructor object must be constructor") .into()); }; - let result = super_constructor.construct(&arguments, Some(new_target), context)?; - let this_env = context .vm .environments @@ -138,12 +116,17 @@ impl SuperCall { .as_function() .expect("super call must be in function environment"); - this_env.bind_this_value(result.clone())?; - let function_object = this_env.slots().function_object().clone(); + let new_target = this_env + .slots() + .new_target() + .expect("must have new.target") + .clone(); - result.initialize_instance_elements(&function_object, context)?; + context.vm.push(new_target); - context.vm.push(result); + super_constructor + .__construct__(argument_count) + .resolve(context)?; Ok(CompletionType::Normal) } } @@ -176,8 +159,8 @@ impl Operation for SuperCall { pub(crate) struct SuperCallSpread; impl Operation for SuperCallSpread { - const NAME: &'static str = "SuperCallWithRest"; - const INSTRUCTION: &'static str = "INST - SuperCallWithRest"; + const NAME: &'static str = "SuperCallSpread"; + const INSTRUCTION: &'static str = "INST - SuperCallSpread"; fn execute(context: &mut Context<'_>) -> JsResult { // Get the arguments that are stored as an array object on the stack. @@ -192,20 +175,17 @@ impl Operation for SuperCallSpread { .expect("arguments array in call spread function must be dense") .clone(); - let new_target_value = context.vm.pop(); let super_constructor = context.vm.pop(); - let new_target = new_target_value - .as_object() - .expect("new target must be object"); - let Some(super_constructor) = super_constructor.as_constructor() else { return Err(JsNativeError::typ() .with_message("super constructor object must be constructor") .into()); }; - let result = super_constructor.construct(&arguments, Some(new_target), context)?; + context.vm.push(super_constructor.clone()); + + context.vm.push_values(&arguments); let this_env = context .vm @@ -214,12 +194,17 @@ impl Operation for SuperCallSpread { .as_function() .expect("super call must be in function environment"); - this_env.bind_this_value(result.clone())?; - let function_object = this_env.slots().function_object().clone(); + let new_target = this_env + .slots() + .new_target() + .expect("must have new.target") + .clone(); - result.initialize_instance_elements(&function_object, context)?; + context.vm.push(new_target); - context.vm.push(result); + super_constructor + .__construct__(arguments.len()) + .resolve(context)?; Ok(CompletionType::Normal) } } @@ -237,7 +222,6 @@ impl Operation for SuperCallDerived { fn execute(context: &mut Context<'_>) -> JsResult { let argument_count = context.vm.frame().argument_count as usize; - let arguments = context.vm.pop_n_values(argument_count); let this_env = context .vm @@ -262,8 +246,45 @@ impl Operation for SuperCallDerived { .into()); } - let result = super_constructor.construct(&arguments, Some(&new_target), context)?; + let arguments_start_index = context.vm.stack.len() - argument_count; + context + .vm + .stack + .insert(arguments_start_index, super_constructor.clone().into()); + + context.vm.push(new_target); + + super_constructor + .__construct__(argument_count) + .resolve(context)?; + Ok(CompletionType::Normal) + } +} + +/// `BindThisValue` implements the Opcode Operation for `Opcode::BindThisValue` +/// +/// Operation: +/// - Binds `this` value and initializes the instance elements. +#[derive(Debug, Clone, Copy)] +pub(crate) struct BindThisValue; + +impl Operation for BindThisValue { + const NAME: &'static str = "BindThisValue"; + const INSTRUCTION: &'static str = "INST - BindThisValue"; + + fn execute(context: &mut Context<'_>) -> JsResult { + // Taken from `SuperCall : super Arguments` steps 7-12. + // + // + let result = context + .vm + .pop() + .as_object() + .expect("construct result should be an object") + .clone(); + + // 7. Let thisER be GetThisEnvironment(). let this_env = context .vm .environments @@ -271,10 +292,17 @@ impl Operation for SuperCallDerived { .as_function() .expect("super call must be in function environment"); + // 8. Perform ? thisER.BindThisValue(result). this_env.bind_this_value(result.clone())?; + // 9. Let F be thisER.[[FunctionObject]]. + // SKIP: 10. Assert: F is an ECMAScript function object. + let active_function = this_env.slots().function_object().clone(); + + // 11. Perform ? InitializeInstanceElements(result, F). result.initialize_instance_elements(&active_function, context)?; + // 12. Return result. context.vm.push(result); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index abd1a95ef3c..53bb6d28477 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -1580,21 +1580,21 @@ generate_opcodes! { /// /// Operands: /// - /// Stack: **=>** super_constructor, new_target + /// Stack: **=>** super_constructor SuperCallPrepare, /// Execute the `super()` method. /// /// Operands: argument_count: `u32` /// - /// Stack: super_constructor, new_target, argument_1, ... argument_n **=>** + /// Stack: super_constructor, argument_1, ... argument_n **=>** SuperCall { argument_count: VaryingOperand }, /// Execute the `super()` method where the arguments contain spreads. /// /// Operands: /// - /// Stack: super_constructor, new_target, arguments_array **=>** + /// Stack: super_constructor, arguments_array **=>** SuperCallSpread, /// Execute the `super()` method when no constructor of the class is defined. @@ -1604,6 +1604,17 @@ generate_opcodes! { /// Stack: argument_n, ... argument_1 **=>** SuperCallDerived, + /// Binds `this` value and initializes the instance elements. + /// + /// Performs steps 7-12 of [`SuperCall: super Arguments`][spec] + /// + /// Operands: + /// + /// Stack: result **=>** result + /// + /// [spec]: https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation + BindThisValue, + /// Dynamically import a module. /// /// Operands: @@ -2178,8 +2189,6 @@ generate_opcodes! { Reserved55 => Reserved, /// Reserved [`Opcode`]. Reserved56 => Reserved, - /// Reserved [`Opcode`]. - Reserved57 => Reserved, } /// Specific opcodes for bindings.