Skip to content

Commit

Permalink
Make super calls non-recursive
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed Oct 9, 2023
1 parent 0b5d910 commit b72857a
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 54 deletions.
1 change: 1 addition & 0 deletions boa_engine/src/bytecompiler/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions boa_engine/src/bytecompiler/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions boa_engine/src/object/internal_methods/bound_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions boa_engine/src/vm/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ impl CodeBlock {
| Instruction::ImportCall
| Instruction::GetReturnValue
| Instruction::SetReturnValue
| Instruction::BindThisValue
| Instruction::Nop => String::new(),

Instruction::U16Operands
Expand Down Expand Up @@ -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"),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions boa_engine/src/vm/flowgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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"),
}
}

Expand Down
118 changes: 73 additions & 45 deletions boa_engine/src/vm/opcode/environment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand All @@ -110,40 +101,32 @@ pub(crate) struct SuperCall;

impl SuperCall {
fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> {
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
.get_this_environment()
.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)
}
}
Expand Down Expand Up @@ -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<CompletionType> {
// Get the arguments that are stored as an array object on the stack.
Expand All @@ -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
Expand All @@ -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)
}
}
Expand All @@ -237,7 +222,6 @@ impl Operation for SuperCallDerived {

fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let argument_count = context.vm.frame().argument_count as usize;
let arguments = context.vm.pop_n_values(argument_count);

let this_env = context
.vm
Expand All @@ -262,19 +246,63 @@ 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<CompletionType> {
// Taken from `SuperCall : super Arguments` steps 7-12.
//
// <https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation>

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
.get_this_environment()
.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)
}
Expand Down
19 changes: 14 additions & 5 deletions boa_engine/src/vm/opcode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -2178,8 +2189,6 @@ generate_opcodes! {
Reserved55 => Reserved,
/// Reserved [`Opcode`].
Reserved56 => Reserved,
/// Reserved [`Opcode`].
Reserved57 => Reserved,
}

/// Specific opcodes for bindings.
Expand Down

0 comments on commit b72857a

Please sign in to comment.